Преглед изворни кода

Merge branch 'dev' into drawcalls

Conflicts:
	examples/js/loaders/AWDLoader.js
dubejf пре 10 година
родитељ
комит
10049e7c02
97 измењених фајлова са 8962 додато и 6389 уклоњено
  1. 519 522
      build/three.js
  2. 440 436
      build/three.min.js
  3. 5 1
      docs/api/core/BufferAttribute.html
  4. 1 76
      docs/api/loaders/JSONLoader.html
  5. 1 18
      docs/api/loaders/Loader.html
  6. 0 28
      docs/api/renderers/WebGLRenderer.html
  7. 59 0
      docs/api/renderers/webgl/WebGLState.html
  8. 2 1
      docs/list.js
  9. 1 0
      editor/index.html
  10. 74 0
      editor/js/Menubar.Edit.js
  11. 96 33
      editor/js/Script.js
  12. 17 2
      editor/js/Sidebar.Project.js
  13. 15 24
      editor/js/Viewport.js
  14. 29 4
      editor/js/libs/app.js
  15. 202 0
      editor/js/libs/glslprep.min.js
  16. 1 1
      examples/canvas_morphtargets_horse.html
  17. 1 0
      examples/index.html
  18. 3 3
      examples/js/MarchingCubes.js
  19. 207 150
      examples/js/controls/TransformControls.js
  20. 852 958
      examples/js/loaders/AWDLoader.js
  21. 6 4
      examples/js/loaders/AssimpJSONLoader.js
  22. 434 491
      examples/js/loaders/BinaryLoader.js
  23. 457 404
      examples/js/loaders/ColladaLoader.js
  24. 41 11
      examples/js/loaders/MTLLoader.js
  25. 6 0
      examples/js/loaders/OBJLoader.js
  26. 9 2
      examples/js/loaders/OBJMTLLoader.js
  27. 6 0
      examples/js/loaders/PDBLoader.js
  28. 17 29
      examples/js/loaders/PLYLoader.js
  29. 3 1
      examples/js/loaders/PVRLoader.js
  30. 8 3
      examples/js/loaders/STLLoader.js
  31. 8 1
      examples/js/loaders/SVGLoader.js
  32. 15 26
      examples/js/loaders/VRMLLoader.js
  33. 6 0
      examples/js/loaders/VTKLoader.js
  34. 2 2
      examples/js/loaders/ctm/CTMLoader.js
  35. 149 149
      examples/js/loaders/gltf/glTFLoader.js
  36. 1 1
      examples/misc_controls_orbit.html
  37. 1 1
      examples/misc_controls_trackball.html
  38. 1 1
      examples/misc_controls_transform.html
  39. 17 15
      examples/webgl_animation_cloth.html
  40. 4 4
      examples/webgl_buffergeometry_instancing.html
  41. 3 3
      examples/webgl_buffergeometry_instancing_dynamic.html
  42. 3 3
      examples/webgl_buffergeometry_instancing_interleaved_dynamic.html
  43. 1 8
      examples/webgl_effects_parallaxbarrier.html
  44. 1 4
      examples/webgl_geometry_large_mesh.html
  45. 14 5
      examples/webgl_interactive_draggablecubes.html
  46. 1 4
      examples/webgl_lights_pointlights.html
  47. 2 7
      examples/webgl_loader_ctm_materials.html
  48. 1 3
      examples/webgl_loader_ply.html
  49. 2 3
      examples/webgl_loader_vrml.html
  50. 1 5
      examples/webgl_materials_bumpmap.html
  51. 1 5
      examples/webgl_materials_bumpmap_skin.html
  52. 1 8
      examples/webgl_materials_cars.html
  53. 1 5
      examples/webgl_materials_cubemap.html
  54. 1 5
      examples/webgl_materials_cubemap_refraction.html
  55. 1 5
      examples/webgl_materials_normaldisplacementmap.html
  56. 1 5
      examples/webgl_materials_normalmap.html
  57. 1 5
      examples/webgl_materials_skin.html
  58. 1 1
      examples/webgl_morphtargets_horse.html
  59. 24 1
      examples/webgl_multiple_renderers.html
  60. 1 1
      examples/webgl_nearestneighbour.html
  61. 2 7
      examples/webgl_particles_dynamic.html
  62. 1 4
      examples/webgl_postprocessing_advanced.html
  63. 222 0
      examples/webgl_postprocessing_ssao.html
  64. 33 0
      src/Three.js
  65. 9 3
      src/core/BufferAttribute.js
  66. 2 2
      src/core/BufferGeometry.js
  67. 13 1
      src/core/InterleavedBuffer.js
  68. 2 2
      src/core/InterleavedBufferAttribute.js
  69. 1 1
      src/extras/geometries/EdgesGeometry.js
  70. 10 8
      src/extras/geometries/SphereBufferGeometry.js
  71. 1 1
      src/extras/geometries/SphereGeometry.js
  72. 11 2
      src/loaders/BinaryTextureLoader.js
  73. 1 1
      src/loaders/Cache.js
  74. 14 5
      src/loaders/CompressedTextureLoader.js
  75. 1 1
      src/loaders/GeometryLoader.js
  76. 10 1
      src/loaders/ImageLoader.js
  77. 265 309
      src/loaders/JSONLoader.js
  78. 167 202
      src/loaders/Loader.js
  79. 24 6
      src/loaders/LoadingManager.js
  80. 17 1
      src/loaders/XHRLoader.js
  81. 161 174
      src/renderers/WebGLRenderer.js
  82. 47 48
      src/renderers/webgl/WebGLObjects.js
  83. 58 20
      src/renderers/webgl/WebGLProgram.js
  84. 7 3
      src/renderers/webgl/WebGLProperties.js
  85. 10 21
      src/renderers/webgl/WebGLShadowMap.js
  86. 34 34
      src/renderers/webgl/WebGLState.js
  87. 3 3
      src/renderers/webgl/plugins/LensFlarePlugin.js
  88. 85 0
      test/unit/extras/ImageUtils.test.js
  89. 7 5
      test/unit/geometry/EdgesGeometry.js
  90. 0 1977
      test/unit/qunit-1.10.0.js
  91. 106 50
      test/unit/qunit-1.18.0.css
  92. 3829 0
      test/unit/qunit-1.18.0.js
  93. 17 0
      test/unit/qunit-utils.js
  94. 3 2
      test/unit/unittests_sources.html
  95. 3 2
      test/unit/unittests_three-math.html
  96. 5 3
      test/unit/unittests_three.html
  97. 3 2
      test/unit/unittests_three.min.html

Разлика између датотеке није приказан због своје велике величине
+ 519 - 522
build/three.js


Разлика између датотеке није приказан због своје велике величине
+ 440 - 436
build/three.min.js


+ 5 - 1
docs/api/core/BufferAttribute.html

@@ -43,10 +43,14 @@
 		Flag to indicate that this attribute has changed and should be re-send to the GPU. Set this to true when you modify the value of the array.
 		</div>
 
+		<h3>[property:Integer version]</h3>
+		<div>
+		A version number, incremented every time the needsUpdate property is set to true.
+		</div>
 
 
 		<h2>Methods</h2>
-		
+
 		<h3>[method:null copyAt] ( [page:Integer index1], attribute, [page:Integer index2] ) </h3>
 		<div>
 		Copies itemSize values in the array from the vertex at index2 to the vertex at index1.

+ 1 - 76
docs/api/loaders/JSONLoader.html

@@ -15,10 +15,7 @@
 
 		<h2>Constructor</h2>
 
-		<h3>[name]( [page:Boolean showStatus] )</h3>
-		<div>
-		[page:Boolean showStatus] — Show the status of loading div.
-		</div>
+		<h3>[name]()</h3>
 		<div>
 		Creates a new [name].
 		</div>
@@ -32,29 +29,6 @@
 		</div>
 
 
-		<h2>Properties inherited from [page:Loader]</h2>
-
-		<h3>[property:Boolean showStatus]</h3>
-		<div>If true, show loading status in the statusDomElement.</div>
-
-		<h3>[property:DOMElement statusDomElement]</h3>
-		<div>This is the recipient of status messages.</div>
-
-		<h3>[property:Function onLoadStart]</h3>
-		<div>Will be called when load starts.</div>
-		<div>The default is a function with empty body.</div>
-
-		<!--
-		<h3>[property:Function onLoadProgress]</h3>
-		<div>Will be called while load progresses.</div>
-		<div>The default is a function with empty body.</div>
-		-->
-
-		<h3>[property:Function onLoadComplete]</h3>
-		<div>Will be called when load completes.</div>
-		<div>The default is a function with empty body.</div>
-
-
 		<h2>Methods</h2>
 
 		<h3>[method:null load]( [page:String url], [page:Function callback], [page:String texturePath] )</h3>
@@ -85,55 +59,6 @@
 		Parse a <em>JSON</em> structure and return an [page:Object] containing the parsed .[page:Geometry] and .[page:Array materials].
 		</div>
 
-		<h2>Methods inherited from [page:Loader]</h2>
-
-		<h3>[method:Boolean needsTangents]( [page:Array materials] )</h3>
-		<div>
-		[page:Array materials] — an array of [page:Material]
-		</div>
-		<div>
-		Checks if the loaded object needs tangents based on its materials.
-		</div>
-
-		<h3>[method:null updateProgress]( [page:object progress] )</h3>
-		<div>
-		[page:Object progress] — an object containing loaded(contains the amount of bytes loaded) and optionally total (containing the total amount of bytes).
-		</div>
-		<div>
-		Updates the DOM object with the progress made.
-		</div>
-
-		<h3>[method:Material createMaterial]( [page:object m], [page:string texturePath] )</h3>
-		<div>
-		[page:Object m] — The parameters to create the material. <br />
-		[page:String texturePath] — The base path of the textures.
-		</div>
-		<div>
-		Creates the Material based on the parameters m.
-		</div>
-
-		<h3>[method:Array initMaterials]( [page:Array materials], [page:string texturePath] )</h3>
-		<div>
-		[page:Array materials] — an array of parameters to create materials. <br />
-		[page:String texturePath] —  The base path of the textures.
-		</div>
-		<div>
-		Creates an array of [page:Material] based on the array of parameters m. The index of the parameters decide the correct index of the materials.
-		</div>
-
-		<h3>[method:String extractUrlBase]( [page:string url] )</h3>
-		<div>
-		[page:String url] —  The url to extract the base url from.
-		</div>
-		<div>
-		Extract the base from the URL.
-		</div>
-
-		<h3>[method:DOMElement addStatusElement]()</h3>
-		<div>
-		Add a DOM element to indicate the progress and return the DOMElement
-		</div>
-
 
 		<h2>Example</h2>
 

+ 1 - 18
docs/api/loaders/Loader.html

@@ -15,10 +15,7 @@
 		<h2>Constructor</h2>
 
 
-		<h3>[name]( [page:Boolean showStatus] )</h3>
-		<div>
-		[page:Boolean showStatus] — Show the status of loading div.
-		</div>
+		<h3>[name]()</h3>
 		<div>
 		Creates a new [name]. This should be called as base class.
 		</div>
@@ -26,12 +23,6 @@
 
 		<h2>Properties</h2>
 
-		<h3>[property:Boolean showStatus]</h3>
-		<div>If true, show loading status in the statusDomElement.</div>
-
-		<h3>[property:DOMElement statusDomElement]</h3>
-		<div>This is the recipient of status messages.</div>
-
 		<h3>[property:Function onLoadStart]</h3>
 		<div>Will be called when load starts.</div>
 		<div>The default is a function with empty body.</div>
@@ -59,14 +50,6 @@
 		Checks if the loaded object needs tangents based on its materials.
 		</div>
 
-		<h3>[method:null updateProgress]( [page:object progress] )</h3>
-		<div>
-		[page:Object progress] — an object containing loaded(contains the amount of bytes loaded) and optionally total (containing the total amount of bytes).
-		</div>
-		<div>
-		Updates the DOM object with the progress made.
-		</div>
-
 		<h3>[method:Material createMaterial]( [page:object m], [page:string texturePath] )</h3>
 		<div>
 		[page:Object m] — The parameters to create the material. <br />

+ 0 - 28
docs/api/renderers/WebGLRenderer.html

@@ -235,34 +235,6 @@
 		<div>If cullFace is false, culling will be disabled.</div>
 
 
-		<h3>[method:null setDepthTest]( [page:boolean depthTest] )</h3>
-		<div>
-		depthTest -- The boolean to decide if depth of a fragment needs to be tested against the depth buffer . <br />
-		</div>
-		<div>
-		This sets, based on depthTest, whether or not the depth data needs to be tested against the depth buffer.
-		</div>
-
-		<h3>[method:null setDepthWrite]( [page:boolean depthWrite] )</h3>
-		<div>
-		depthWrite -- The boolean to decide if depth of a fragment needs to be kept. <br />
-		</div>
-		<div>
-		This sets, based on depthWrite, whether or not the depth data needs to be written in the depth buffer.
-		</div>
-
-
-		<h3>[method:null setBlending]( [page:number blending], [page:number blendEquation], [page:number blendSrc], [page:number blendDst] )</h3>
-		<div>
-		blending -- A number indicating the blending mode. Possible value are THREE.NoBlending, THREE.NormalBlending, THREE.AdditiveBlending, THREE.SubtractiveBlending, THREE.MultiplyBlending or THREE.CustomBlending <br />
-		blendEquation -- When blending is THREE.CustomBlending, then you can set the blendEquation. Possible values are THREE.AddEquation, THREE.SubtractEquation or THREE.ReverseSubtractEquation.<br />
-		blendSrc -- When blending is THREE.CustomBlending, then you can set the blendSrc. Possible values are THREE.ZeroFactor, THREE.OneFactor,THREE.SrcColorFactor, THREE.OneMinusSrcColorFactor, THREE.SrcAlphaFactor, THREE.OneMinusSrcAlphaFactor, THREE.DstAlphaFactor, THREE.OneMinusDstAlphaFactor, THREE.DstColorFactor,THREE.OneMinusDstColorFactor or THREE.SrcAlphaSaturateFactor<br />
-		blendDst -- When blending is THREE.CustomBlending, then you can set the blendDst. Possible values are THREE.ZeroFactor, THREE.OneFactor,THREE.SrcColorFactor, THREE.OneMinusSrcColorFactor, THREE.SrcAlphaFactor, THREE.OneMinusSrcAlphaFactor, THREE.DstAlphaFactor, THREE.OneMinusDstAlphaFactor, THREE.DstColorFactor,THREE.OneMinusDstColorFactor or THREE.SrcAlphaSaturateFactor
-		</div>
-		<div>
-		This method sets the correct blending.
-		</div>
-
 		<h3>[method:null setTexture]( [page:Texture texture], [page:number slot] )</h3>
 		<div>
 		texture -- The [page:Texture texture] that needs to be set.<br />

+ 59 - 0
docs/api/renderers/webgl/WebGLState.html

@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc"></div>
+
+
+		<h2>Methods</h2>
+
+		<h3>[method:null enable]( [page:integer id], [page:boolean boolean] )</h3>
+		<div>
+		TODO
+		</div>
+
+		<h3>[method:null disable]( [page:integer id], [page:boolean boolean] )</h3>
+		<div>
+		TODO
+		</div>
+
+		<h3>[method:null setDepthTest]( [page:boolean depthTest] )</h3>
+		<div>
+		depthTest -- The boolean to decide if depth of a fragment needs to be tested against the depth buffer . <br />
+		</div>
+		<div>
+		This sets, based on depthTest, whether or not the depth data needs to be tested against the depth buffer.
+		</div>
+
+		<h3>[method:null setDepthWrite]( [page:boolean depthWrite] )</h3>
+		<div>
+		depthWrite -- The boolean to decide if depth of a fragment needs to be kept. <br />
+		</div>
+		<div>
+		This sets, based on depthWrite, whether or not the depth data needs to be written in the depth buffer.
+		</div>
+
+		<h3>[method:null setBlending]( [page:number blending], [page:number blendEquation], [page:number blendSrc], [page:number blendDst] )</h3>
+		<div>
+		blending -- A number indicating the blending mode. Possible value are THREE.NoBlending, THREE.NormalBlending, THREE.AdditiveBlending, THREE.SubtractiveBlending, THREE.MultiplyBlending or THREE.CustomBlending <br />
+		blendEquation -- When blending is THREE.CustomBlending, then you can set the blendEquation. Possible values are THREE.AddEquation, THREE.SubtractEquation or THREE.ReverseSubtractEquation.<br />
+		blendSrc -- When blending is THREE.CustomBlending, then you can set the blendSrc. Possible values are THREE.ZeroFactor, THREE.OneFactor,THREE.SrcColorFactor, THREE.OneMinusSrcColorFactor, THREE.SrcAlphaFactor, THREE.OneMinusSrcAlphaFactor, THREE.DstAlphaFactor, THREE.OneMinusDstAlphaFactor, THREE.DstColorFactor,THREE.OneMinusDstColorFactor or THREE.SrcAlphaSaturateFactor<br />
+		blendDst -- When blending is THREE.CustomBlending, then you can set the blendDst. Possible values are THREE.ZeroFactor, THREE.OneFactor,THREE.SrcColorFactor, THREE.OneMinusSrcColorFactor, THREE.SrcAlphaFactor, THREE.OneMinusSrcAlphaFactor, THREE.DstAlphaFactor, THREE.OneMinusDstAlphaFactor, THREE.DstColorFactor,THREE.OneMinusDstColorFactor or THREE.SrcAlphaSaturateFactor
+		</div>
+		<div>
+		This method sets the correct blending.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 2 - 1
docs/list.js

@@ -136,7 +136,8 @@ var list = {
 
 		"Renderers / WebGL": [
 			[ "WebGLProgram", "api/renderers/webgl/WebGLProgram" ],
-			[ "WebGLShader", "api/renderers/webgl/WebGLShader" ]
+			[ "WebGLShader", "api/renderers/webgl/WebGLShader" ],
+			[ "WebGLState", "api/renderers/webgl/WebGLState" ]
 		],
 
 		"Renderers / WebGL / Plugins": [

+ 1 - 0
editor/index.html

@@ -44,6 +44,7 @@
 		<script src="js/libs/codemirror/mode/javascript.js"></script>
 		<script src="js/libs/esprima.js"></script>
 		<script src="js/libs/jsonlint.js"></script>
+		<script src="js/libs/glslprep.min.js"></script>
 
 		<script src="js/libs/codemirror/mode/glsl.js"></script>
 

+ 74 - 0
editor/js/Menubar.Edit.js

@@ -53,6 +53,80 @@ Menubar.Edit = function ( editor ) {
 	} );
 	options.add( option );
 
+	// Minify shaders
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Minify Shaders' );
+	option.onClick( function() {
+
+		var root = editor.selected || editor.scene;
+
+		var errors = [];
+		var nMaterialsChanged = 0;
+
+		var path = [];
+
+		function getPath ( object ) {
+
+			path.length = 0;
+
+			var parent = object.parent;
+			if ( parent !== undefined ) getPath( parent );
+
+			path.push( object.name || object.uuid );
+
+			return path;
+
+		}
+
+		root.traverse( function ( object ) {
+
+			var material = object.material;
+
+			if ( material instanceof THREE.ShaderMaterial ) {
+
+				try {
+
+					var shader = glslprep.minifyGlsl( [
+							material.vertexShader, material.fragmentShader ] );
+
+					material.vertexShader = shader[ 0 ];
+					material.fragmentShader = shader[ 1 ];
+
+					++nMaterialsChanged;
+
+				} catch ( e ) {
+
+					var path = getPath( object ).join( "/" );
+
+					if ( e instanceof glslprep.SyntaxError )
+
+						errors.push( path + ":" +
+								e.line + ":" + e.column + ": " + e.message );
+
+					else {
+
+						errors.push( path +
+								": Unexpected error (see console for details)." );
+
+						console.error( e.stack || e );
+
+					}
+
+				}
+
+			}
+
+		} );
+
+		window.alert( nMaterialsChanged +
+				" material(s) were changed.\n" + errors.join( "\n" ) );
+
+	} );
+	options.add( option );
+
+
 	return container;
 
 };

+ 96 - 33
editor/js/Script.js

@@ -42,6 +42,16 @@ var Script = function ( editor ) {
 	} );
 	header.add( close );
 
+
+	var renderer;
+
+	signals.rendererChanged.add( function ( newRenderer ) {
+
+		renderer = newRenderer;
+
+	} );
+
+
 	var delay;
 	var currentMode;
 	var currentScript;
@@ -75,31 +85,17 @@ var Script = function ( editor ) {
 				return;
 			}
 
-			switch ( currentScript ) {
-
-				case 'vertexShader':
-
-					currentObject.vertexShader = value;
-					break;
-
-				case 'fragmentShader':
-
-					currentObject.fragmentShader = value;
-					break;
-
-				case 'programInfo':
+			if ( currentScript !== 'programInfo' ) return;
 
-					var json = JSON.parse( value );
-					currentObject.defines = json.defines;
-					currentObject.uniforms = json.uniforms;
-					currentObject.attributes = json.attributes;
-
-			}
+			var json = JSON.parse( value );
+			currentObject.defines = json.defines;
+			currentObject.uniforms = json.uniforms;
+			currentObject.attributes = json.attributes;
 
 			currentObject.needsUpdate = true;
 			signals.materialChanged.dispatch( currentObject );
 
-		}, 200 );
+		}, 300 );
 
 	});
 
@@ -118,7 +114,8 @@ var Script = function ( editor ) {
 
 	var validate = function ( string ) {
 
-		var errors;
+		var valid;
+		var errors = [];
 
 		return codemirror.operation( function () {
 
@@ -147,10 +144,12 @@ var Script = function ( editor ) {
 
 					} catch ( error ) {
 
-						errors = [
+						errors.push( {
+
+							lineNumber: error.lineNumber - 1,
+							message: error.message
 
-							{ lineNumber: error.lineNumber,message: error.message }
-						];
+						} );
 
 					}
 
@@ -171,10 +170,12 @@ var Script = function ( editor ) {
 
 						message = message.split('\n')[3];
 
-						errors.push({
-							lineNumber: info.loc.first_line,
+						errors.push( {
+
+							lineNumber: info.loc.first_line - 1,
 							message: message
-						});
+
+						} );
 
 					};
 
@@ -192,13 +193,75 @@ var Script = function ( editor ) {
 
 				case 'glsl':
 
-					// TODO validate GLSL (compiling shader?)
+					try {
+
+						var shaderType = currentScript === 'vertexShader' ?
+								glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
 
-				default:
+						glslprep.parseGlsl( string, shaderType );
 
-					errors = [];
+					} catch( error ) {
 
-			}
+						if ( error instanceof glslprep.SyntaxError ) {
+
+							errors.push( {
+
+								lineNumber: error.line,
+								message: "Syntax Error: " + error.message
+
+							} );
+
+						} else {
+
+							console.error( error.stack || error );
+
+						}
+
+					}
+
+					if ( errors.length !== 0 ) break;
+					if ( renderer instanceof THREE.WebGLRenderer === false ) break;
+
+					currentObject[ currentScript ] = string;
+					currentObject.needsUpdate = true;
+					signals.materialChanged.dispatch( currentObject );
+
+					var programs = renderer.info.programs;
+
+					valid = true;
+					var parseMessage = /^(?:ERROR|WARNING): \d+:(\d+): (.*)/g;
+
+					for ( var i = 0, n = programs.length; i !== n; ++ i ) {
+
+						var diagnostics = programs[i].diagnostics;
+
+						if ( diagnostics === undefined ||
+								diagnostics.material !== currentObject ) continue;
+
+						if ( ! diagnostics.runnable ) valid = false;
+
+						var shaderInfo = diagnostics[ currentScript ];
+						var lineOffset = shaderInfo.prefix.split(/\r\n|\r|\n/).length;
+
+						while ( true ) {
+
+							var parseResult = parseMessage.exec( shaderInfo.log );
+							if ( parseResult === null ) break;
+
+							errors.push( {
+
+								lineNumber: parseResult[ 1 ] - lineOffset,
+								message: parseResult[ 2 ]
+
+							} );
+
+						} // messages
+
+						break;
+
+					} // programs
+
+			} // mode switch
 
 			for ( var i = 0; i < errors.length; i ++ ) {
 
@@ -208,7 +271,7 @@ var Script = function ( editor ) {
 				message.className = 'esprima-error';
 				message.textContent = error.message;
 
-				var lineNumber = error.lineNumber - 1;
+				var lineNumber = Math.max( error.lineNumber, 0 );
 				errorLines.push( lineNumber );
 
 				codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
@@ -219,7 +282,7 @@ var Script = function ( editor ) {
 
 			}
 
-			return errorLines.length === 0;
+			return valid !== undefined ? valid : errors.length === 0;
 
 		});
 

+ 17 - 2
editor/js/Sidebar.Project.js

@@ -64,7 +64,7 @@ Sidebar.Project = function ( editor ) {
 	var rendererAntialias = new UI.Checkbox( editor.config.getKey( 'project/renderer/antialias' ) ).setLeft( '100px' ).onChange( function () {
 
 		editor.config.setKey( 'project/renderer/antialias', this.getValue() );
-		// updateRenderer();
+		updateRenderer();
 
 	} );
 
@@ -92,10 +92,25 @@ Sidebar.Project = function ( editor ) {
 
 	function updateRenderer() {
 
-		signals.rendererChanged.dispatch( rendererType.getValue(), rendererAntialias.getValue() );
+		createRenderer( rendererType.getValue(), rendererAntialias.getValue() );
+
+	}
+
+	function createRenderer( type, antialias ) {
+
+		if ( type === 'WebGLRenderer' && System.support.webgl === false ) {
+
+			type = 'CanvasRenderer';
+
+		}
+
+		var renderer = new rendererTypes[ type ]( { antialias: antialias } );
+		signals.rendererChanged.dispatch( renderer );
 
 	}
 
+	createRenderer( editor.config.getKey( 'project/renderer' ), editor.config.getKey( 'project/renderer/antialias' ) );
+
 	return container;
 
 }

+ 15 - 24
editor/js/Viewport.js

@@ -232,6 +232,8 @@ var Viewport = function ( editor ) {
 
 	} );
 
+	var clearColor;
+
 	signals.themeChanged.add( function ( value ) {
 
 		switch ( value ) {
@@ -271,11 +273,20 @@ var Viewport = function ( editor ) {
 
 	} );
 
-	signals.rendererChanged.add( function ( type, antialias ) {
+	signals.rendererChanged.add( function ( newRenderer ) {
+
+		if ( renderer !== null ) {
+
+			container.dom.removeChild( renderer.domElement );
+
+		}
 
-		container.dom.removeChild( renderer.domElement );
+		renderer = newRenderer;
 
-		renderer = createRenderer( type, antialias );
+		renderer.autoClear = false;
+		renderer.autoUpdateScene = false;
+		renderer.setClearColor( clearColor );
+		renderer.setPixelRatio( window.devicePixelRatio );
 		renderer.setSize( container.dom.offsetWidth, container.dom.offsetHeight );
 
 		container.dom.appendChild( renderer.domElement );
@@ -510,27 +521,7 @@ var Viewport = function ( editor ) {
 
 	//
 
-	var createRenderer = function ( type, antialias ) {
-
-		if ( type === 'WebGLRenderer' && System.support.webgl === false ) {
-
-			type = 'CanvasRenderer';
-
-		}
-
-		var renderer = new THREE[ type ]( { antialias: antialias } );
-		renderer.setClearColor( clearColor );
-		renderer.setPixelRatio( window.devicePixelRatio );
-		renderer.autoClear = false;
-		renderer.autoUpdateScene = false;
-
-		return renderer;
-
-	};
-
-	var clearColor;
-	var renderer = createRenderer( editor.config.getKey( 'project/renderer' ), editor.config.getKey( 'project/renderer/antialias' ) );
-	container.dom.appendChild( renderer.domElement );
+	var renderer = null;
 
 	animate();
 

+ 29 - 4
editor/js/libs/app.js

@@ -33,6 +33,9 @@ var APP = {
 			this.setCamera( loader.parse( json.camera ) );
 
 			events = {
+				init: [],
+				start: [],
+				stop: [],
 				keydown: [],
 				keyup: [],
 				mousedown: [],
@@ -44,6 +47,15 @@ var APP = {
 				update: []
 			};
 
+			var scriptWrapParams = 'player,renderer,scene';
+			var scriptWrapResultObj = {};
+			for ( var eventKey in events ) {
+				scriptWrapParams += ',' + eventKey;
+				scriptWrapResultObj[ eventKey ] = eventKey;
+			}
+			var scriptWrapResult =
+					JSON.stringify( scriptWrapResultObj ).replace( /\"/g, '' );
+
 			for ( var uuid in json.scripts ) {
 
 				var object = scene.getObjectByProperty( 'uuid', uuid, true );
@@ -54,7 +66,8 @@ var APP = {
 
 					var script = scripts[ i ];
 
-					var functions = ( new Function( 'player, scene, keydown, keyup, mousedown, mouseup, mousemove, touchstart, touchend, touchmove, update', script.source + '\nreturn { keydown: keydown, keyup: keyup, mousedown: mousedown, mouseup: mouseup, mousemove: mousemove, touchstart: touchstart, touchend: touchend, touchmove: touchmove, update: update };' ).bind( object ) )( this, scene );
+					var functions = ( new Function( scriptWrapParams,
+							script.source + '\nreturn ' + scriptWrapResult+ ';' ).bind( object ) )( this, renderer, scene );
 
 					for ( var name in functions ) {
 
@@ -75,6 +88,8 @@ var APP = {
 
 			}
 
+			dispatch( events.init, arguments );
+
 		};
 
 		this.setCamera = function ( value ) {
@@ -146,7 +161,15 @@ var APP = {
 
 			for ( var i = 0, l = array.length; i < l; i ++ ) {
 
-				array[ i ]( event );
+				try {
+
+					array[ i ]( event );
+
+				} catch (e) {
+
+					console.error(e.stack || e);
+
+				}
 
 			}
 
@@ -186,9 +209,10 @@ var APP = {
 			document.addEventListener( 'touchend', onDocumentTouchEnd );
 			document.addEventListener( 'touchmove', onDocumentTouchMove );
 
+			dispatch( events.start, arguments );
+
 			request = requestAnimationFrame( animate );
 			prevTime = performance.now();
-
 		};
 
 		this.stop = function () {
@@ -202,8 +226,9 @@ var APP = {
 			document.removeEventListener( 'touchend', onDocumentTouchEnd );
 			document.removeEventListener( 'touchmove', onDocumentTouchMove );
 
-			cancelAnimationFrame( request );
+			dispatch( events.stop, arguments );
 
+			cancelAnimationFrame( request );
 		};
 
 		//

+ 202 - 0
editor/js/libs/glslprep.min.js

@@ -0,0 +1,202 @@
+/* Copyright 2011-2015 Roy Williams, Tobias Schwinger and the Closure team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(){var aa=this;function da(c){return void 0!==c}
+function ea(c){var f=typeof c;if("object"==f)if(c){if(c instanceof Array)return"array";if(c instanceof Object)return f;var k=Object.prototype.toString.call(c);if("[object Window]"==k)return"object";if("[object Array]"==k||"number"==typeof c.length&&"undefined"!=typeof c.splice&&"undefined"!=typeof c.propertyIsEnumerable&&!c.propertyIsEnumerable("splice"))return"array";if("[object Function]"==k||"undefined"!=typeof c.call&&"undefined"!=typeof c.propertyIsEnumerable&&!c.propertyIsEnumerable("call"))return"function"}else return"null";
+else if("function"==f&&"undefined"==typeof c.call)return"object";return f}function fa(c,f,k){return c.call.apply(c.bind,arguments)}function ga(c,f,k){if(!c)throw Error();if(2<arguments.length){var n=Array.prototype.slice.call(arguments,2);return function(){var g=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(g,n);return c.apply(f,g)}}return function(){return c.apply(f,arguments)}}
+function ia(c,f,k){ia=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?fa:ga;return ia.apply(null,arguments)}function ka(c,f){var k=c.split("."),n=aa;k[0]in n||!n.execScript||n.execScript("var "+k[0]);for(var g;k.length&&(g=k.shift());)!k.length&&da(f)?n[g]=f:n[g]?n=n[g]:n=n[g]={}}
+function v(c,f){function k(){}k.prototype=f.prototype;c.Ma=f.prototype;c.prototype=new k;c.prototype.constructor=c;c.La=function(c,g,k){for(var B=Array(arguments.length-2),q=2;q<arguments.length;q++)B[q-2]=arguments[q];return f.prototype[g].apply(c,B)}};var la={};
+function z(c,f){function k(b){var a=b.charCodeAt(0),e;255>=a?(b="x",e=2):(b="u",e=4);var d=a.toString(16).toUpperCase(),a=d;e-=d.length;for(d=0;d<e;d++)a="0"+a;return"\\"+b+a}function n(b){return'"'+b.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g,k)+'"'}function g(c){b<ta||(b>ta&&(ta=b,ob=[]),ob.push(c))}function u(){var w="newLine@"+b,a=p[w];if(a)return b=
+a.b,a.result;var e;e=b;/^[\n]/.test(c.charAt(b))?(a=c.charAt(b),b++):(a=null,0===l&&g("[\\n]"));null!==a&&(a="\n");null===a&&(b=e);p[w]={b:b,result:a};return a}function B(){var w="EOF@"+b,a=p[w];if(a)return b=a.b,a.result;var e;e=b;l++;c.length>b?(a=c.charAt(b),b++):(a=null,0===l&&g("any character"));l--;null===a?a="":(a=null,b=e);p[w]={b:b,result:a};return a}function q(){var w="_@"+b,a=p[w];if(a)return b=a.b,a.result;var e;l++;e=u();null===e&&(/^[\\\n]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,
+0===l&&g("[\\\\\\n]")),null===e&&(/^[\r\t\f\x0B ]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[\\r\\t\\f\\x0B ]")),null===e&&(e=C())));if(null!==e)for(a=[];null!==e;)a.push(e),e=u(),null===e&&(/^[\\\n]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[\\\\\\n]")),null===e&&(/^[\r\t\f\x0B ]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[\\r\\t\\f\\x0B ]")),null===e&&(e=C())));else a=null;l--;0===l&&null===a&&g("whitespace");p[w]={b:b,result:a};return a}function x(){var w=
+"noNewlineComment@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t;m=b;"/*"===c.substr(b,2)?(a="/*",b+=2):(a=null,0===l&&g('"/*"'));if(null!==a){e=[];h=t=b;l++;"*/"===c.substr(b,2)?(d="*/",b+=2):(d=null,0===l&&g('"*/"'));l--;null===d?d="":(d=null,b=h);null!==d?(c.length>b?(h=c.charAt(b),b++):(h=null,0===l&&g("any character")),null!==h?d=[d,h]:(d=null,b=t)):(d=null,b=t);for(;null!==d;)e.push(d),h=t=b,l++,"*/"===c.substr(b,2)?(d="*/",b+=2):(d=null,0===l&&g('"*/"')),l--,null===d?d="":(d=null,b=h),
+null!==d?(c.length>b?(h=c.charAt(b),b++):(h=null,0===l&&g("any character")),null!==h?d=[d,h]:(d=null,b=t)):(d=null,b=t);null!==e?("*/"===c.substr(b,2)?(d="*/",b+=2):(d=null,0===l&&g('"*/"')),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)}else a=null,b=m;if(null===a)if(m=b,"//"===c.substr(b,2)?(a="//",b+=2):(a=null,0===l&&g('"//"')),null!==a){e=[];/^[^\n]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[^\\n]"));for(;null!==d;)e.push(d),/^[^\n]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,
+0===l&&g("[^\\n]"));null!==e?a=[a,e]:(a=null,b=m)}else a=null,b=m;p[w]={b:b,result:a};return a}function ha(){var w="noNewlineWhitespace@"+b,a=p[w];if(a)return b=a.b,a.result;var e;/^[\r\t\f\x0B ]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[\\r\\t\\f\\x0B ]"));null===e&&(e=x());if(null!==e)for(a=[];null!==e;)a.push(e),/^[\r\t\f\x0B ]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[\\r\\t\\f\\x0B ]")),null===e&&(e=x());else a=null;p[w]={b:b,result:a};return a}function C(){var w=
+"comment@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t;l++;m=b;"/*"===c.substr(b,2)?(a="/*",b+=2):(a=null,0===l&&g('"/*"'));if(null!==a){e=[];h=t=b;l++;"*/"===c.substr(b,2)?(d="*/",b+=2):(d=null,0===l&&g('"*/"'));l--;null===d?d="":(d=null,b=h);null!==d?(c.length>b?(h=c.charAt(b),b++):(h=null,0===l&&g("any character")),null!==h?d=[d,h]:(d=null,b=t)):(d=null,b=t);for(;null!==d;)e.push(d),h=t=b,l++,"*/"===c.substr(b,2)?(d="*/",b+=2):(d=null,0===l&&g('"*/"')),l--,null===d?d="":(d=null,b=h),null!==
+d?(c.length>b?(h=c.charAt(b),b++):(h=null,0===l&&g("any character")),null!==h?d=[d,h]:(d=null,b=t)):(d=null,b=t);null!==e?("*/"===c.substr(b,2)?(d="*/",b+=2):(d=null,0===l&&g('"*/"')),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)}else a=null,b=m;if(null===a)if(m=b,"//"===c.substr(b,2)?(a="//",b+=2):(a=null,0===l&&g('"//"')),null!==a){e=[];/^[^\n]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[^\\n]"));for(;null!==d;)e.push(d),/^[^\n]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&
+g("[^\\n]"));null!==e?(d=u(),null===d&&(d=B()),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)}else a=null,b=m;l--;0===l&&null===a&&g("comment");p[w]={b:b,result:a};return a}function A(){var w="semicolon@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(59===c.charCodeAt(b)?(e=";",b+=1):(e=null,0===l&&g('";"')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function H(){var w="comma@"+b,a=p[w];
+if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(44===c.charCodeAt(b)?(e=",",b+=1):(e=null,0===l&&g('","')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function ua(){var w="left_bracket@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(91===c.charCodeAt(b)?(e="[",b+=1):(e=null,0===l&&g('"["')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,
+b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function va(){var w="right_bracket@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(93===c.charCodeAt(b)?(e="]",b+=1):(e=null,0===l&&g('"]"')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function cc(){var w="equals@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(61===c.charCodeAt(b)?(e="=",b+=1):(e=null,
+0===l&&g('"="')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function ba(){var w="left_paren@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(40===c.charCodeAt(b)?(e="(",b+=1):(e=null,0===l&&g('"("')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function ca(){var w="right_paren@"+b,a=p[w];if(a)return b=a.b,a.result;
+var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(41===c.charCodeAt(b)?(e=")",b+=1):(e=null,0===l&&g('")"')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function pb(){var w="left_brace@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(123===c.charCodeAt(b)?(e="{",b+=1):(e=null,0===l&&g('"{"')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,
+result:a};return a}function qb(){var w="right_brace@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h;h=b;a=q();a=null!==a?a:"";null!==a?(125===c.charCodeAt(b)?(e="}",b+=1):(e=null,0===l&&g('"}"')),null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=h)):(a=null,b=h)):(a=null,b=h);p[w]={b:b,result:a};return a}function ja(){var c="external_statement_list@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,a=b;e=[];for(d=rb();null!==d;)e.push(d),d=rb();if(null!==e){d=new y({type:"root",C:[]});for(var h=
+0;h<e.length;h++)e[h]&&(d.C=d.C.concat(e[h]));e=d}null===e&&(b=a);p[c]={b:b,result:e};return e}function rb(){var c="external_statement@"+b,a=p[c];if(a)return b=a.b,a.result;var e;e=b;a=dc();null===a&&(a=ec());null===a&&(b=e);null===a&&(e=b,a=q(),null!==a&&(a=""),null===a&&(b=e));p[c]={b:b,result:a};return a}function ec(){var c="external_declaration@"+b,a=p[c];if(a)return b=a.b,a.result;a=fc();null===a&&(a=gc(),null===a&&(a=sb(),null===a&&(a=tb(),null===a&&(a=hc(),null===a&&(a=Oa())))));p[c]={b:b,
+result:a};return a}function tb(){var w="preprocessor_operator@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f,k;f=t=b;35===c.charCodeAt(b)?(a="#",b+=1):(a=null,0===l&&g('"#"'));if(null!==a)if("undef"===c.substr(b,5)?(e="undef",b+=5):(e=null,0===l&&g('"undef"')),null===e&&("pragma"===c.substr(b,6)?(e="pragma",b+=6):(e=null,0===l&&g('"pragma"')),null===e&&("version"===c.substr(b,7)?(e="version",b+=7):(e=null,0===l&&g('"version"')),null===e&&("error"===c.substr(b,5)?(e="error",b+=5):(e=null,0===
+l&&g('"error"')),null===e&&("extension"===c.substr(b,9)?(e="extension",b+=9):(e=null,0===l&&g('"extension"')),null===e&&("line"===c.substr(b,4)?(e="line",b+=4):(e=null,0===l&&g('"line"'))))))),null!==e)if(d=q(),null!==d){k=b;h=[];/^[^\n]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[^\\n]"));for(;null!==m;)h.push(m),/^[^\n]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[^\\n]"));null!==h&&(h=h.join(""));null===h&&(b=k);null!==h?(m=u(),null===m&&(m=B()),null!==m?a=[a,e,d,h,m]:
+(a=null,b=f)):(a=null,b=f)}else a=null,b=f;else a=null,b=f;else a=null,b=f;null!==a&&(a=new y({type:"preprocessor",B:"#"+a[1],value:a[3]}));null===a&&(b=t);p[w]={b:b,result:a};return a}function na(){var w="macro_identifier@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;/^[A-Za-z_]/.test(c.charAt(b))?(a=c.charAt(b),b++):(a=null,0===l&&g("[A-Za-z_]"));if(null!==a){e=[];/^[A-Za-z_0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[A-Za-z_0-9]"));for(;null!==d;)e.push(d),/^[A-Za-z_0-9]/.test(c.charAt(b))?
+(d=c.charAt(b),b++):(d=null,0===l&&g("[A-Za-z_0-9]"));null!==e?a=[a,e]:(a=null,b=m)}else a=null,b=m;null!==a&&(a=new y({type:"identifier",name:a[0]+a[1].join("")}));null===a&&(b=h);p[w]={b:b,result:a};return a}function ic(){var w="preprocessor_parameter_list@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f,k;f=t=b;40===c.charCodeAt(b)?(a="(",b+=1):(a=null,0===l&&g('"("'));if(null!==a)if(e=na(),e=null!==e?e:"",null!==e){d=[];k=b;h=H();null!==h?(m=na(),null!==m?h=[h,m]:(h=null,b=k)):(h=null,b=
+k);for(;null!==h;)d.push(h),k=b,h=H(),null!==h?(m=na(),null!==m?h=[h,m]:(h=null,b=k)):(h=null,b=k);null!==d?(h=ca(),null!==h?a=[a,e,d,h]:(a=null,b=f)):(a=null,b=f)}else a=null,b=f;else a=null,b=f;null!==a&&(a=function(a,b){return a?[a].concat(b.map(function(a){return a[1]})):[]}(a[1],a[2]));null===a&&(b=t);p[w]={b:b,result:a};return a}function ub(){var w="macro_paren_parameter@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f,k,r;f=t=b;a=ba();if(null!==a){r=k=b;e=[];/^[^()]/.test(c.charAt(b))?
+(d=c.charAt(b),b++):(d=null,0===l&&g("[^()]"));for(;null!==d;)e.push(d),/^[^()]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[^()]"));if(null!==e)if(d=ub(),d=null!==d?d:"",null!==d){h=[];/^[^()]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[^()]"));for(;null!==m;)h.push(m),/^[^()]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[^()]"));null!==h?e=[e,d,h]:(e=null,b=r)}else e=null,b=r;else e=null,b=r;null!==e&&(e=e[0].join("")+e[1]+e[2].join(""));null===e&&(b=k);null!==
+e?(d=ca(),null!==d?a=[a,e,d]:(a=null,b=f)):(a=null,b=f)}else a=null,b=f;null!==a&&(a="("+a[1]+")");null===a&&(b=t);p[w]={b:b,result:a};return a}function Pa(){var w="macro_call_parameter@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,a=ub();if(null===a){d=b;a=[];/^[^,)]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[^,)]"));for(;null!==e;)a.push(e),/^[^,)]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[^,)]"));null!==a&&(a=a.join(""));null===a&&(b=d)}p[w]={b:b,result:a};return a}
+function Oa(){var w="macro_call@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f;f=a=b;e=na();null!==e?(d=q(),d=null!==d?d:"",null!==d?(h=ba(),null!==h?(m=vb(),m=null!==m?m:"",null!==m?(41===c.charCodeAt(b)?(t=")",b+=1):(t=null,0===l&&g('")"')),null!==t?e=[e,d,h,m,t]:(e=null,b=f)):(e=null,b=f)):(e=null,b=f)):(e=null,b=f)):(e=null,b=f);null!==e&&(d=e[3],e=new y({type:"macro_call",oa:e[0],l:d}),d||(e.l=[]));null===e&&(b=a);p[w]={b:b,result:e};return e}function sb(){var w="preprocessor_define@"+
+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f,k,r,n,x,A;x=n=b;35===c.charCodeAt(b)?(a="#",b+=1):(a=null,0===l&&g('"#"'));if(null!==a)if(e=q(),e=null!==e?e:"",null!==e)if("define"===c.substr(b,6)?(d="define",b+=6):(d=null,0===l&&g('"define"')),null!==d)if(h=q(),null!==h)if(m=na(),null!==m)if(t=ic(),t=null!==t?t:"",null!==t){f=[];/^[ \t]/.test(c.charAt(b))?(k=c.charAt(b),b++):(k=null,0===l&&g("[ \\t]"));for(;null!==k;)f.push(k),/^[ \t]/.test(c.charAt(b))?(k=c.charAt(b),b++):(k=null,0===l&&g("[ \\t]"));
+if(null!==f){A=b;k=[];/^[^\n]/.test(c.charAt(b))?(r=c.charAt(b),b++):(r=null,0===l&&g("[^\\n]"));for(;null!==r;)k.push(r),/^[^\n]/.test(c.charAt(b))?(r=c.charAt(b),b++):(r=null,0===l&&g("[^\\n]"));null!==k&&(k=k.join(""));null===k&&(b=A);null!==k?(r=u(),null===r&&(r=B()),null!==r?a=[a,e,d,h,m,t,f,k,r]:(a=null,b=x)):(a=null,b=x)}else a=null,b=x}else a=null,b=x;else a=null,b=x;else a=null,b=x;else a=null,b=x;else a=null,b=x;else a=null,b=x;null!==a&&(a=new y({type:"preprocessor",B:"#define",identifier:a[4].name,
+pa:a[7],l:a[5]||null}));null===a&&(b=n);p[w]={b:b,result:a};return a}function wb(){var w="preprocessor_if@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f,k,r;k=f=b;35===c.charCodeAt(b)?(a="#",b+=1):(a=null,0===l&&g('"#"'));if(null!==a)if(e=q(),e=null!==e?e:"",null!==e)if("ifdef"===c.substr(b,5)?(d="ifdef",b+=5):(d=null,0===l&&g('"ifdef"')),null===d&&("ifndef"===c.substr(b,6)?(d="ifndef",b+=6):(d=null,0===l&&g('"ifndef"')),null===d&&("if"===c.substr(b,2)?(d="if",b+=2):(d=null,0===l&&g('"if"')))),
+null!==d)if(h=q(),null!==h){r=b;m=[];/^[^\n]/.test(c.charAt(b))?(t=c.charAt(b),b++):(t=null,0===l&&g("[^\\n]"));for(;null!==t;)m.push(t),/^[^\n]/.test(c.charAt(b))?(t=c.charAt(b),b++):(t=null,0===l&&g("[^\\n]"));null!==m&&(m=m.join(""));null===m&&(b=r);null!==m?(t=u(),null===t&&(t=B()),null!==t?a=[a,e,d,h,m,t]:(a=null,b=k)):(a=null,b=k)}else a=null,b=k;else a=null,b=k;else a=null,b=k;else a=null,b=k;null!==a&&(a=new y({type:"preprocessor",B:"#"+a[2],value:a[4]}));null===a&&(b=f);p[w]={b:b,result:a};
+return a}function wa(){var w="preprocessor_else_if@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,t,f,k,r;k=f=b;35===c.charCodeAt(b)?(a="#",b+=1):(a=null,0===l&&g('"#"'));if(null!==a)if(e=q(),e=null!==e?e:"",null!==e)if("elif"===c.substr(b,4)?(d="elif",b+=4):(d=null,0===l&&g('"elif"')),null!==d)if(h=q(),null!==h){r=b;m=[];/^[^\n]/.test(c.charAt(b))?(t=c.charAt(b),b++):(t=null,0===l&&g("[^\\n]"));for(;null!==t;)m.push(t),/^[^\n]/.test(c.charAt(b))?(t=c.charAt(b),b++):(t=null,0===l&&g("[^\\n]"));
+null!==m&&(m=m.join(""));null===m&&(b=r);null!==m?(t=u(),null===t&&(t=B()),null!==t?a=[a,e,d,h,m,t]:(a=null,b=k)):(a=null,b=k)}else a=null,b=k;else a=null,b=k;else a=null,b=k;else a=null,b=k;null!==a&&(a=new y({type:"preprocessor",B:"#elif",value:a[4]}));null===a&&(b=f);p[w]={b:b,result:a};return a}function xb(){var w="preprocessor_else@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,f,k;k=f=b;35===c.charCodeAt(b)?(a="#",b+=1):(a=null,0===l&&g('"#"'));null!==a?(e=q(),e=null!==e?e:"",null!==e?("else"===
+c.substr(b,4)?(d="else",b+=4):(d=null,0===l&&g('"else"')),null!==d?(h=ha(),h=null!==h?h:"",null!==h?(m=u(),null!==m?a=[a,e,d,h,m]:(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k);null!==a&&(a=new y({type:"preprocessor",B:"#else"}));null===a&&(b=f);p[w]={b:b,result:a};return a}function yb(){var w="preprocessor_end@"+b,a=p[w];if(a)return b=a.b,a.result;var e,d,h,m,f,k;k=b;35===c.charCodeAt(b)?(a="#",b+=1):(a=null,0===l&&g('"#"'));null!==a?(e=q(),e=null!==e?e:"",null!==e?("endif"===
+c.substr(b,5)?(d="endif",b+=5):(d=null,0===l&&g('"endif"')),null!==d?(h=ha(),h=null!==h?h:"",null!==h?(m=u(),null===m&&(m=B()),null!==m?(f=q(),f=null!==f?f:"",null!==f?a=[a,e,d,h,m,f]:(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k);p[w]={b:b,result:a};return a}function dc(){var c="preprocessor_external_branch@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,g,f;f=g=m=b;a=wb();null!==a?(e=ja(),null!==e?a=[a,e]:(a=null,b=f)):(a=null,b=f);if(null!==a){e=[];f=b;d=wa();
+null!==d?(h=ja(),null!==h?d=[d,h]:(d=null,b=f)):(d=null,b=f);for(;null!==d;)e.push(d),f=b,d=wa(),null!==d?(h=ja(),null!==h?d=[d,h]:(d=null,b=f)):(d=null,b=f);null!==e?(f=b,d=xb(),null!==d?(h=ja(),null!==h?d=[d,h]:(d=null,b=f)):(d=null,b=f),d=null!==d?d:"",null!==d?(h=yb(),null!==h?a=[a,e,d,h]:(a=null,b=g)):(a=null,b=g)):(a=null,b=g)}else a=null,b=g;null!==a&&(a=jc(a[0],a[1],a[2]));null===a&&(b=m);p[c]={b:b,result:a};return a}function zb(){var c="preprocessor_statement_branch@"+b,a=p[c];if(a)return b=
+a.b,a.result;var e,d,h,m,g,f;f=g=m=b;a=wb();null!==a?(e=oa(),null!==e?a=[a,e]:(a=null,b=f)):(a=null,b=f);if(null!==a){e=[];f=b;d=wa();null!==d?(h=oa(),null!==h?d=[d,h]:(d=null,b=f)):(d=null,b=f);for(;null!==d;)e.push(d),f=b,d=wa(),null!==d?(h=oa(),null!==h?d=[d,h]:(d=null,b=f)):(d=null,b=f);null!==e?(f=b,d=xb(),null!==d?(h=oa(),null!==h?d=[d,h]:(d=null,b=f)):(d=null,b=f),d=null!==d?d:"",null!==d?(h=yb(),null!==h?a=[a,e,d,h]:(a=null,b=g)):(a=null,b=g)):(a=null,b=g)}else a=null,b=g;null!==a&&(a=jc(a[0],
+a[1],a[2]));null===a&&(b=m);p[c]={b:b,result:a};return a}function fc(){var c="function_definition@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h;h=d=b;a=Ab();null!==a?(e=Qa(),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(e=a[0],a=L=new y({type:"function_declaration",name:e.name,ma:e.ma,l:e.l,body:a[1]}));null===a&&(b=d);p[c]={b:b,result:a};return a}function Qa(){var c="compound_statement@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;m=a=b;e=pb();null!==e?(d=oa(),d=null!==d?d:"",null!==
+d?(h=qb(),null!==h?e=[e,d,h]:(e=null,b=m)):(e=null,b=m)):(e=null,b=m);null!==e&&(e=e[1],L=new y({type:"scope",C:[]}),e&&e.C&&(L.C=e.C),e=L);null===e&&(b=a);p[c]={b:b,result:e};return e}function oa(){var c="statement_list@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;a=q();a=null!==a?a:"";if(null!==a){e=[];for(d=xa();null!==d;)e.push(d),d=xa();null!==e?(d=q(),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)}else a=null,b=m;null!==a&&(a={C:a[1]});null===a&&(b=h);p[c]={b:b,result:a};
+return a}function xa(){var c="statement_no_new_scope@"+b,a=p[c];if(a)return b=a.b,a.result;a=Qa();null===a&&(a=Bb(),null===a&&(a=zb()));p[c]={b:b,result:a};return a}function Ra(){var c="statement_with_scope@"+b,a=p[c];if(a)return b=a.b,a.result;a=Qa();null===a&&(a=Bb(),null===a&&(a=zb()));p[c]={b:b,result:a};return a}function Bb(){var c="simple_statement@"+b,a=p[c];if(a)return b=a.b,a.result;var e;e=b;a=Sa();null===a&&(a=Cb(),null===a&&(a=kc(),null===a&&(a=lc(),null===a&&(a=mc(),null===a&&(a=sb(),
+null===a&&(a=tb(),null===a&&(a=Oa())))))));null===a&&(b=e);p[c]={b:b,result:a};return a}function kc(){var f="selection_statement@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,t,k,n,r,u,x;u=r=b;"if"===c.substr(b,2)?(a="if",b+=2):(a=null,0===l&&g('"if"'));null!==a?(e=ba(),null!==e?(d=S(),null!==d?(h=ca(),null!==h?(m=Ra(),null!==m?(x=b,"else"===c.substr(b,4)?(t="else",b+=4):(t=null,0===l&&g('"else"')),null!==t?(k=q(),k=null!==k?k:"",null!==k?(n=Ra(),null!==n?t=[t,k,n]:(t=null,b=x)):(t=null,b=x)):
+(t=null,b=x),t=null!==t?t:"",null!==t?a=[a,e,d,h,m,t]:(a=null,b=u)):(a=null,b=u)):(a=null,b=u)):(a=null,b=u)):(a=null,b=u)):(a=null,b=u);null!==a&&(e=a[5],L=new y({type:"if_statement",F:a[2],body:a[4]}),e&&(L.M=e[2]),a=L);null===a&&(b=r);p[f]={b:b,result:a};return a}function nc(){var f="for_loop@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,t,k,q,r,n;n=r=b;"for"===c.substr(b,3)?(a="for",b+=3):(a=null,0===l&&g('"for"'));null!==a?(e=ba(),null!==e?(d=Cb(),null===d&&(d=Sa()),null!==d?(h=Db(),h=null!==
+h?h:"",null!==h?(m=A(),null!==m?(t=S(),t=null!==t?t:"",null!==t?(k=ca(),null!==k?(q=xa(),null!==q?a=[a,e,d,h,m,t,k,q]:(a=null,b=n)):(a=null,b=n)):(a=null,b=n)):(a=null,b=n)):(a=null,b=n)):(a=null,b=n)):(a=null,b=n)):(a=null,b=n);null!==a&&(a=new y({type:"for_statement",H:a[2],F:a[3],Fa:a[5],body:a[7]}));null===a&&(b=r);p[f]={b:b,result:a};return a}function Eb(){var f="while_statement@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,t;t=m=b;"while"===c.substr(b,5)?(a="while",b+=5):(a=null,0===l&&
+g('"while"'));null!==a?(e=ba(),null!==e?(d=Db(),null!==d?(h=ca(),null!==h?a=[a,e,d,h]:(a=null,b=t)):(a=null,b=t)):(a=null,b=t)):(a=null,b=t);null!==a&&(a={F:a[2]});null===a&&(b=m);p[f]={b:b,result:a};return a}function oc(){var c="while_loop@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h;h=d=b;a=Eb();null!==a?(e=xa(),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"while_statement",F:a[0].F,body:a[1]}));null===a&&(b=d);p[c]={b:b,result:a};return a}function pc(){var f="do_while@"+
+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;"do"===c.substr(b,2)?(a="do",b+=2):(a=null,0===l&&g('"do"'));null!==a?(e=Ra(),null!==e?(d=Eb(),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(a=new y({type:"do_statement",F:a[2].F,body:a[1]}));null===a&&(b=h);p[f]={b:b,result:a};return a}function lc(){var c="iteration_statement@"+b,a=p[c];if(a)return b=a.b,a.result;a=oc();null===a&&(a=pc(),null===a&&(a=nc()));p[c]={b:b,result:a};return a}function mc(){var f="jump_statement@"+
+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,t;t=m=b;"return"===c.substr(b,6)?(a="return",b+=6):(a=null,0===l&&g('"return"'));null!==a?(e=q(),e=null!==e?e:"",null!==e?(d=S(),null!==d?(h=A(),null!==h?a=[a,e,d,h]:(a=null,b=t)):(a=null,b=t)):(a=null,b=t)):(a=null,b=t);null!==a&&(a=new y({type:"return",value:a[2]}));null===a&&(b=m);null===a&&(t=m=b,"continue"===c.substr(b,8)?(a="continue",b+=8):(a=null,0===l&&g('"continue"')),null!==a?(e=A(),null!==e?a=[a,e]:(a=null,b=t)):(a=null,b=t),null===a&&(t=
+b,"break"===c.substr(b,5)?(a="break",b+=5):(a=null,0===l&&g('"break"')),null!==a?(e=A(),null!==e?a=[a,e]:(a=null,b=t)):(a=null,b=t),null===a&&(t=b,"return"===c.substr(b,6)?(a="return",b+=6):(a=null,0===l&&g('"return"')),null!==a?(e=A(),null!==e?a=[a,e]:(a=null,b=t)):(a=null,b=t),null===a&&(h=d=t=b,a="fs"==ya?"":null,null!==a?("discard"===c.substr(b,7)?(e="discard",b+=7):(e=null,0===l&&g('"discard"')),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h),null!==a&&(a="discard"),null===a&&(b=d),null!==a?(e=A(),
+null!==e?a=[a,e]:(a=null,b=t)):(a=null,b=t)))),null!==a&&(a=new y({type:a[0]})),null===a&&(b=m));p[f]={b:b,result:a};return a}function Cb(){var c="expression_statement@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h;h=d=b;a=S();a=null!==a?a:"";null!==a?(e=A(),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"expression",N:a[0]}));null===a&&(b=d);p[c]={b:b,result:a};return a}function Sa(){var f="declaration@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,t,k,n,r,u;l++;r=n=b;a=
+Ab();null!==a?(e=A(),null!==e?a=[a,e]:(a=null,b=r)):(a=null,b=r);null!==a&&(a=a[0]);null===a&&(b=n);if(null===a&&(r=n=b,a=za(),null!==a?(e=q(),null!==e?(d=Fb(),null!==d?(h=A(),null!==h?a=[a,e,d,h]:(a=null,b=r)):(a=null,b=r)):(a=null,b=r)):(a=null,b=r),null!==a&&(a=new y({type:"declarator",w:a[0],A:a[2]})),null===a&&(b=n),null===a)){r=n=b;a="vs"==ya?"":null;if(null!==a)if("invariant"===c.substr(b,9)?(e="invariant",b+=9):(e=null,0===l&&g('"invariant"')),null!==e)if(d=q(),null!==d)if(h=Q(),null!==h){m=
+[];u=b;t=H();null!==t?(k=Q(),null!==k?t=[t,k]:(t=null,b=u)):(t=null,b=u);for(;null!==t;)m.push(t),u=b,t=H(),null!==t?(k=Q(),null!==k?t=[t,k]:(t=null,b=u)):(t=null,b=u);null!==m?(t=A(),null!==t?a=[a,e,d,h,m,t]:(a=null,b=r)):(a=null,b=r)}else a=null,b=r;else a=null,b=r;else a=null,b=r;else a=null,b=r;null!==a&&(a=function(a,b){var d=[a].concat(b.map(function(a){return a[1]}));return new y({type:"invariant",Ea:d})}(a[3],a[4]));null===a&&(b=n);null===a&&(r=n=b,"precision"===c.substr(b,9)?(a="precision",
+b+=9):(a=null,0===l&&g('"precision"')),null!==a?(e=q(),null!==e?(d=Ta(),null!==d?(h=q(),null!==h?(m=Aa(),null!==m?(t=A(),null!==t?a=[a,e,d,h,m,t]:(a=null,b=r)):(a=null,b=r)):(a=null,b=r)):(a=null,b=r)):(a=null,b=r)):(a=null,b=r),null!==a&&(a=new y({type:"precision",precision:a[2],typeName:a[4]})),null===a&&(b=n))}l--;0===l&&null===a&&g("declaration");p[f]={b:b,result:a};return a}function gc(){var c="global_declaration@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,a=Sa();null===a&&(f=m=b,a=qc(),
+null!==a?(e=q(),null!==e?(d=Fb(),null!==d?(h=A(),null!==h?a=[a,e,d,h]:(a=null,b=f)):(a=null,b=f)):(a=null,b=f)):(a=null,b=f),null!==a&&(a=new y({type:"declarator",w:a[0],A:a[2]})),null===a&&(b=m),null===a&&(f=m=b,a=rc(),null!==a?(e=q(),null!==e?(d=sc(),null!==d?(h=A(),null!==h?a=[a,e,d,h]:(a=null,b=f)):(a=null,b=f)):(a=null,b=f)):(a=null,b=f),null!==a&&(a=new y({type:"declarator",w:a[0],A:a[2]})),null===a&&(b=m)));p[c]={b:b,result:a};return a}function tc(){var f="function_prototype_parameter_list@"+
+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,t,k;"void"===c.substr(b,4)?(a="void",b+=4):(a=null,0===l&&g('"void"'));if(null===a){t=m=b;a=Ua();if(null!==a){e=[];k=b;d=H();null!==d?(h=Ua(),null!==h?d=[d,h]:(d=null,b=k)):(d=null,b=k);for(;null!==d;)e.push(d),k=b,d=H(),null!==d?(h=Ua(),null!==h?d=[d,h]:(d=null,b=k)):(d=null,b=k);null!==e?a=[a,e]:(a=null,b=t)}else a=null,b=t;null!==a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=m)}p[f]={b:b,result:a};
+return a}function Ab(){var c="function_prototype@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g,k;k=g=b;a=uc();null===a&&(a=Ba());null!==a?(e=q(),null!==e?(d=Q(),null!==d?(h=ba(),null!==h?(m=tc(),m=null!==m?m:"",null!==m?(f=ca(),null!==f?a=[a,e,d,h,m,f]:(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k)):(a=null,b=k);null!==a&&(e=a[4],L=new y({type:"function_prototype",name:a[2].name,ma:a[0],l:e}),"void"!=e&&e||(L.l=[]),a=L);null===a&&(b=g);p[c]={b:b,result:a};return a}function vc(){var f=
+"parameter_qualifier@"+b,a=p[f];if(a)return b=a.b,a.result;"inout"===c.substr(b,5)?(a="inout",b+=5):(a=null,0===l&&g('"inout"'));null===a&&("in"===c.substr(b,2)?(a="in",b+=2):(a=null,0===l&&g('"in"')),null===a&&("out"===c.substr(b,3)?(a="out",b+=3):(a=null,0===l&&g('"out"'))));p[f]={b:b,result:a};return a}function Ua(){var c="parameter_declaration@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g,k,l,n,u,x;x=u=a=b;e=Va();null!==e?(d=q(),null!==d?e=[e,d]:(e=null,b=x)):(e=null,b=x);e=null!==e?e:
+"";null!==e?(x=b,d=vc(),null!==d?(h=q(),null!==h?d=[d,h]:(d=null,b=x)):(d=null,b=x),d=null!==d?d:"",null!==d?(x=b,h=Ta(),null!==h?(m=q(),null!==m?h=[h,m]:(h=null,b=x)):(h=null,b=x),h=null!==h?h:"",null!==h?(m=Aa(),null!==m?(f=q(),null!==f?(g=Q(),null!==g?(x=b,k=ua(),null!==k?(l=pa(),null!==l?(n=va(),null!==n?k=[k,l,n]:(k=null,b=x)):(k=null,b=x)):(k=null,b=x),k=null!==k?k:"",null!==k?e=[e,d,h,m,f,g,k]:(e=null,b=u)):(e=null,b=u)):(e=null,b=u)):(e=null,b=u)):(e=null,b=u)):(e=null,b=u)):(e=null,b=u);
+null!==e&&(d=e[0],h=e[1],m=e[2],f=e[6],e=new y({type:"parameter",Ka:e[3],name:e[5].name}),d&&(e.qa=d[0]),h&&(e.la=h[0]),m&&(e.precision=m[0]),f&&(e.$=f[1]),e=e.qa&&e.la&&"in"!=e.la?null:e);null===e&&(b=a);p[c]={b:b,result:e};return e}function Fb(){var c="init_declarator_list@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g;f=m=b;a=Wa();if(null!==a){e=[];g=b;d=H();null!==d?(h=Wa(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);for(;null!==d;)e.push(d),g=b,d=H(),null!==d?(h=Wa(),null!==h?d=[d,h]:
+(d=null,b=g)):(d=null,b=g);null!==e?a=[a,e]:(a=null,b=f)}else a=null,b=f;null!==a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=m);p[c]={b:b,result:a};return a}function wc(){var c="declarator_list@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g;f=m=b;a=Ca();if(null!==a){e=[];g=b;d=H();null!==d?(h=Ca(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);for(;null!==d;)e.push(d),g=b,d=H(),null!==d?(h=Ca(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);null!==
+e?a=[a,e]:(a=null,b=f)}else a=null,b=f;null!==a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=m);p[c]={b:b,result:a};return a}function sc(){var c="declarator_list_no_array@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g;f=m=b;a=Da();if(null!==a){e=[];g=b;d=H();null!==d?(h=Da(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);for(;null!==d;)e.push(d),g=b,d=H(),null!==d?(h=Da(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);null!==e?a=[a,e]:(a=null,b=f)}else a=
+null,b=f;null!==a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=m);p[c]={b:b,result:a};return a}function Gb(){var c="declarator_list_arrays_have_size@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g;f=m=b;a=Ea();if(null!==a){e=[];g=b;d=H();null!==d?(h=Ea(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);for(;null!==d;)e.push(d),g=b,d=H(),null!==d?(h=Ea(),null!==h?d=[d,h]:(d=null,b=g)):(d=null,b=g);null!==e?a=[a,e]:(a=null,b=f)}else a=null,b=f;null!==
+a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=m);p[c]={b:b,result:a};return a}function Da(){var c="declarator_no_array@"+b,a=p[c];if(a)return b=a.b,a.result;var e;e=b;a=Q();null!==a&&(a=new y({type:"declarator_item",name:a}));null===a&&(b=e);p[c]={b:b,result:a};return a}function Ea(){var c="declarator_array_with_size@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f;f=m=b;a=Q();null!==a?(e=ua(),null!==e?(d=pa(),null!==d?(h=va(),null!==h?a=[a,e,d,
+h]:(a=null,b=f)):(a=null,b=f)):(a=null,b=f)):(a=null,b=f);null!==a&&(a=new y({type:"declarator_item",name:a[0],$:a[2],isArray:!0}));null===a&&(b=m);null===a&&(a=Da());p[c]={b:b,result:a};return a}function Ca(){var c="declarator@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;a=Q();null!==a?(e=ua(),null!==e?(d=va(),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(a=new y({type:"declarator_item",name:a[0],isArray:!0}));null===a&&(b=h);null===a&&(a=Ea());p[c]={b:b,result:a};
+return a}function Wa(){var c="init_declarator@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;a=Q();null!==a?(e=cc(),null!==e?(d=pa(),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(a=new y({type:"declarator_item",name:a[0],H:a[2]}));null===a&&(b=h);null===a&&(a=Ca());p[c]={b:b,result:a};return a}function xc(){var c="member_list@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m,f,g;g=f=b;e=za();null!==e?(d=q(),null!==d?(h=Gb(),null!==h?(m=A(),null!==m?e=[e,d,h,m]:(e=
+null,b=g)):(e=null,b=g)):(e=null,b=g)):(e=null,b=g);if(null!==e)for(a=[];null!==e;)a.push(e),g=b,e=za(),null!==e?(d=q(),null!==d?(h=Gb(),null!==h?(m=A(),null!==m?e=[e,d,h,m]:(e=null,b=g)):(e=null,b=g)):(e=null,b=g)):(e=null,b=g);else a=null;null!==a&&(a=function(a){return a.map(function(a){return new y({type:"declarator",w:a[0],A:a[2]})})}(a));null===a&&(b=f);p[c]={b:b,result:a};return a}function hc(){var f="struct_definition@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k,n,u,r,x;k=x=a=b;e=Hb();
+null===e&&(e=Ib());null!==e?(d=q(),null!==d?e=[e,d]:(e=null,b=k)):(e=null,b=k);e=null!==e?e:"";null!==e?("struct"===c.substr(b,6)?(d="struct",b+=6):(d=null,0===l&&g('"struct"')),null!==d?(k=b,h=q(),null!==h?(m=Q(),null!==m?h=[h,m]:(h=null,b=k)):(h=null,b=k),h=null!==h?h:"",null!==h?(m=pb(),null!==m?(k=xc(),null!==k?(n=qb(),null!==n?(u=wc(),u=null!==u?u:"",null!==u?(r=A(),null!==r?e=[e,d,h,m,k,n,u,r]:(e=null,b=x)):(e=null,b=x)):(e=null,b=x)):(e=null,b=x)):(e=null,b=x)):(e=null,b=x)):(e=null,b=x)):
+(e=null,b=x);null!==e&&(d=e[0],h=e[2],m=e[6],e=new y({type:"struct_definition",Ia:e[4]}),d&&(e.qualifier=d[0]),h&&(e.name=h[1].name,yc[e.name]=e),m&&(e.A=m));null===e&&(b=a);p[f]={b:b,result:e};return e}function Ba(){var c="precision_type@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;m=h=a=b;e=Ta();null!==e?(d=q(),null!==d?e=[e,d]:(e=null,b=m)):(e=null,b=m);e=null!==e?e:"";null!==e?(d=Aa(),null!==d?e=[e,d]:(e=null,b=h)):(e=null,b=h);null!==e&&(d=e[0],e=new y({type:"type",name:e[1]}),d&&(e.precision=
+d[0]));null===e&&(b=a);p[c]={b:b,result:e};return e}function za(){var c="locally_specified_type@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;l++;m=h=a=b;e=Va();null!==e?(d=q(),null!==d?e=[e,d]:(e=null,b=m)):(e=null,b=m);e=null!==e?e:"";null!==e?(d=Ba(),null!==d?e=[e,d]:(e=null,b=h)):(e=null,b=h);null!==e&&(d=e[0],e=e[1],d&&(e.qualifier=d[0]));null===e&&(b=a);l--;0===l&&null===e&&g("locally specified type");p[c]={b:b,result:e};return e}function Ib(){var f="attribute_qualifier@"+b,a=p[f];if(a)return b=
+a.b,a.result;var e,d,h;h=d=b;a="vs"==ya?"":null;null!==a?("attribute"===c.substr(b,9)?(e="attribute",b+=9):(e=null,0===l&&g('"attribute"')),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a="attribute");null===a&&(b=d);p[f]={b:b,result:a};return a}function rc(){var c="attribute_type@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;l++;m=h=b;a=Ib();null!==a?(e=q(),null!==e?(d=Ba(),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(e=a[2],e.qualifier=a[0],a=e);null===a&&
+(b=h);l--;0===l&&null===a&&g("locally specified type");p[c]={b:b,result:a};return a}function qc(){var c="fully_specified_type@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,m;l++;m=h=a=b;e=Hb();null!==e?(d=q(),null!==d?e=[e,d]:(e=null,b=m)):(e=null,b=m);e=null!==e?e:"";null!==e?(d=Ba(),null!==d?e=[e,d]:(e=null,b=h)):(e=null,b=h);null!==e&&(d=e[0],e=e[1],d&&(e.qualifier=d[0]));null===e&&(b=a);l--;0===l&&null===e&&g("fully specified type");p[c]={b:b,result:e};return e}function Ta(){var f="precision_qualifier@"+
+b,a=p[f];if(a)return b=a.b,a.result;l++;"highp"===c.substr(b,5)?(a="highp",b+=5):(a=null,0===l&&g('"highp"'));null===a&&("mediump"===c.substr(b,7)?(a="mediump",b+=7):(a=null,0===l&&g('"mediump"')),null===a&&("lowp"===c.substr(b,4)?(a="lowp",b+=4):(a=null,0===l&&g('"lowp"'))));l--;0===l&&null===a&&g("precision qualifier");p[f]={b:b,result:a};return a}function Va(){var f="const_qualifier@"+b,a=p[f];if(a)return b=a.b,a.result;"const"===c.substr(b,5)?(a="const",b+=5):(a=null,0===l&&g('"const"'));p[f]=
+{b:b,result:a};return a}function Hb(){var f="type_qualifier@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;l++;a=Va();null===a&&("varying"===c.substr(b,7)?(a="varying",b+=7):(a=null,0===l&&g('"varying"')),null===a&&(m=h=b,"invariant"===c.substr(b,9)?(a="invariant",b+=9):(a=null,0===l&&g('"invariant"')),null!==a?(e=q(),null!==e?("varying"===c.substr(b,7)?(d="varying",b+=7):(d=null,0===l&&g('"varying"')),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m),null!==a&&(a="invariant varying"),
+null===a&&(b=h),null===a&&("uniform"===c.substr(b,7)?(a="uniform",b+=7):(a=null,0===l&&g('"uniform"')))));l--;0===l&&null===a&&g("type qualifier");p[f]={b:b,result:a};return a}function uc(){var f="void_type@"+b,a=p[f];if(a)return b=a.b,a.result;var e;l++;e=b;"void"===c.substr(b,4)?(a="void",b+=4):(a=null,0===l&&g('"void"'));null!==a&&(a=new y({type:"type",name:"void"}));null===a&&(b=e);l--;0===l&&null===a&&g("void");p[f]={b:b,result:a};return a}function Aa(){var f="type_name@"+b,a=p[f];if(a)return b=
+a.b,a.result;var e;l++;"float"===c.substr(b,5)?(a="float",b+=5):(a=null,0===l&&g('"float"'));null===a&&("int"===c.substr(b,3)?(a="int",b+=3):(a=null,0===l&&g('"int"')),null===a&&("bool"===c.substr(b,4)?(a="bool",b+=4):(a=null,0===l&&g('"bool"')),null===a&&("sampler2D"===c.substr(b,9)?(a="sampler2D",b+=9):(a=null,0===l&&g('"sampler2D"')),null===a&&("samplerCube"===c.substr(b,11)?(a="samplerCube",b+=11):(a=null,0===l&&g('"samplerCube"')),null===a&&(a=Jb(),null===a&&(a=Kb(),null===a&&(e=b,a=Q(),null!==
+a&&(a=a.name in yc?a.name:null),null===a&&(b=e))))))));l--;0===l&&null===a&&g("type name");p[f]={b:b,result:a};return a}function Q(){var f="identifier@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k;l++;d=k=m=b;l++;h=b;a=zc();null!==a?(/^[^A-Za-z_0-9]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[^A-Za-z_0-9]")),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);l--;null===a?a="":(a=null,b=d);if(null!==a)if(/^[A-Za-z_]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[A-Za-z_]")),
+null!==e){d=[];/^[A-Za-z_0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[A-Za-z_0-9]"));for(;null!==h;)d.push(h),/^[A-Za-z_0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[A-Za-z_0-9]"));null!==d?a=[a,e,d]:(a=null,b=k)}else a=null,b=k;else a=null,b=k;null!==a&&(a=new y({type:"identifier",name:a[1]+a[2].join("")}));null===a&&(b=m);l--;0===l&&null===a&&g("identifier");p[f]={b:b,result:a};return a}function zc(){var f="keyword@"+b,a=p[f];if(a)return b=a.b,a.result;l++;"attribute"===
+c.substr(b,9)?(a="attribute",b+=9):(a=null,0===l&&g('"attribute"'));null===a&&("const"===c.substr(b,5)?(a="const",b+=5):(a=null,0===l&&g('"const"')),null===a&&("bool"===c.substr(b,4)?(a="bool",b+=4):(a=null,0===l&&g('"bool"')),null===a&&("float"===c.substr(b,5)?(a="float",b+=5):(a=null,0===l&&g('"float"')),null===a&&("int"===c.substr(b,3)?(a="int",b+=3):(a=null,0===l&&g('"int"')),null===a&&("break"===c.substr(b,5)?(a="break",b+=5):(a=null,0===l&&g('"break"')),null===a&&("continue"===c.substr(b,8)?
+(a="continue",b+=8):(a=null,0===l&&g('"continue"')),null===a&&("do"===c.substr(b,2)?(a="do",b+=2):(a=null,0===l&&g('"do"')),null===a&&("else"===c.substr(b,4)?(a="else",b+=4):(a=null,0===l&&g('"else"')),null===a&&("for"===c.substr(b,3)?(a="for",b+=3):(a=null,0===l&&g('"for"')),null===a&&("if"===c.substr(b,2)?(a="if",b+=2):(a=null,0===l&&g('"if"')),null===a&&("discard"===c.substr(b,7)?(a="discard",b+=7):(a=null,0===l&&g('"discard"')),null===a&&("return"===c.substr(b,6)?(a="return",b+=6):(a=null,0===
+l&&g('"return"')),null===a&&(a=Jb(),null===a&&(a=Kb(),null===a&&("in"===c.substr(b,2)?(a="in",b+=2):(a=null,0===l&&g('"in"')),null===a&&("out"===c.substr(b,3)?(a="out",b+=3):(a=null,0===l&&g('"out"')),null===a&&("inout"===c.substr(b,5)?(a="inout",b+=5):(a=null,0===l&&g('"inout"')),null===a&&("uniform"===c.substr(b,7)?(a="uniform",b+=7):(a=null,0===l&&g('"uniform"')),null===a&&("varying"===c.substr(b,7)?(a="varying",b+=7):(a=null,0===l&&g('"varying"')),null===a&&("sampler2D"===c.substr(b,9)?(a="sampler2D",
+b+=9):(a=null,0===l&&g('"sampler2D"')),null===a&&("samplerCube"===c.substr(b,11)?(a="samplerCube",b+=11):(a=null,0===l&&g('"samplerCube"')),null===a&&("struct"===c.substr(b,6)?(a="struct",b+=6):(a=null,0===l&&g('"struct"')),null===a&&("void"===c.substr(b,4)?(a="void",b+=4):(a=null,0===l&&g('"void"')),null===a&&("while"===c.substr(b,5)?(a="while",b+=5):(a=null,0===l&&g('"while"')),null===a&&("highp"===c.substr(b,5)?(a="highp",b+=5):(a=null,0===l&&g('"highp"')),null===a&&("mediump"===c.substr(b,7)?
+(a="mediump",b+=7):(a=null,0===l&&g('"mediump"')),null===a&&("lowp"===c.substr(b,4)?(a="lowp",b+=4):(a=null,0===l&&g('"lowp"')),null===a&&("true"===c.substr(b,4)?(a="true",b+=4):(a=null,0===l&&g('"true"')),null===a&&("false"===c.substr(b,5)?(a="false",b+=5):(a=null,0===l&&g('"false"')))))))))))))))))))))))))))))));l--;0===l&&null===a&&g("keyword");p[f]={b:b,result:a};return a}function Jb(){var f="vector@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;/^[bi]/.test(c.charAt(b))?(a=c.charAt(b),
+b++):(a=null,0===l&&g("[bi]"));a=null!==a?a:"";null!==a?("vec"===c.substr(b,3)?(e="vec",b+=3):(e=null,0===l&&g('"vec"')),null!==e?(/^[234]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[234]")),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(a=a.join(""));null===a&&(b=h);p[f]={b:b,result:a};return a}function Kb(){var f="matrix@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h;h=d=b;"mat"===c.substr(b,3)?(a="mat",b+=3):(a=null,0===l&&g('"mat"'));null!==a?(/^[234]/.test(c.charAt(b))?
+(e=c.charAt(b),b++):(e=null,0===l&&g("[234]")),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=a.join(""));null===a&&(b=d);p[f]={b:b,result:a};return a}function Lb(){var f="single_underscore_identifier@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;m=b;a=[];/^[A-Za-z0-9]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[A-Za-z0-9]"));for(;null!==e;)a.push(e),/^[A-Za-z0-9]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[A-Za-z0-9]"));if(null!==a)if(95===c.charCodeAt(b)?
+(e="_",b+=1):(e=null,0===l&&g('"_"')),null!==e){/^[A-Za-z0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[A-Za-z0-9]"));if(null!==h)for(d=[];null!==h;)d.push(h),/^[A-Za-z0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[A-Za-z0-9]"));else d=null;null!==d?a=[a,e,d]:(a=null,b=m)}else a=null,b=m;else a=null,b=m;p[f]={b:b,result:a};return a}function Ac(){var f="int_constant@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k;k=m=b;/^[1-9]/.test(c.charAt(b))?(a=c.charAt(b),b++):
+(a=null,0===l&&g("[1-9]"));if(null!==a){e=[];/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));for(;null!==d;)e.push(d),/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));null!==e?a=[a,e]:(a=null,b=k)}else a=null,b=k;null!==a&&(a=new y({type:"int",value:parseInt([a[0]].concat(a[1]).join(""),10)}));null===a&&(b=m);if(null===a){k=m=b;48===c.charCodeAt(b)?(a="0",b+=1):(a=null,0===l&&g('"0"'));if(null!==a)if(/^[Xx]/.test(c.charAt(b))?(e=c.charAt(b),b++):
+(e=null,0===l&&g("[Xx]")),null!==e){/^[0-9A-Fa-f]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[0-9A-Fa-f]"));if(null!==h)for(d=[];null!==h;)d.push(h),/^[0-9A-Fa-f]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[0-9A-Fa-f]"));else d=null;null!==d?a=[a,e,d]:(a=null,b=k)}else a=null,b=k;else a=null,b=k;null!==a&&(a=new y({type:"int",value:parseInt(a[2].join(""),16)}));null===a&&(b=m);if(null===a){k=m=b;48===c.charCodeAt(b)?(a="0",b+=1):(a=null,0===l&&g('"0"'));if(null!==a){/^[0-7]/.test(c.charAt(b))?
+(d=c.charAt(b),b++):(d=null,0===l&&g("[0-7]"));if(null!==d)for(e=[];null!==d;)e.push(d),/^[0-7]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-7]"));else e=null;null!==e?a=[a,e]:(a=null,b=k)}else a=null,b=k;null!==a&&(a=new y({type:"int",value:parseInt(a[1].join(""),8)}));null===a&&(b=m);null===a&&(m=b,48===c.charCodeAt(b)?(a="0",b+=1):(a=null,0===l&&g('"0"')),null!==a&&(a=new y({type:"int",value:0})),null===a&&(b=m))}}p[f]={b:b,result:a};return a}function Bc(){var f="float_constant@"+
+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k;k=a=b;e=[];/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));for(;null!==d;)e.push(d),/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));if(null!==e)if(46===c.charCodeAt(b)?(d=".",b+=1):(d=null,0===l&&g('"."')),null!==d){/^[0-9]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[0-9]"));if(null!==m)for(h=[];null!==m;)h.push(m),/^[0-9]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[0-9]"));
+else h=null;null!==h?(m=Xa(),m=null!==m?m:"",null!==m?e=[e,d,h,m]:(e=null,b=k)):(e=null,b=k)}else e=null,b=k;else e=null,b=k;if(null===e){k=b;/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));if(null!==d)for(e=[];null!==d;)e.push(d),/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));else e=null;if(null!==e)if(46===c.charCodeAt(b)?(d=".",b+=1):(d=null,0===l&&g('"."')),null!==d){h=[];/^[0-9]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[0-9]"));
+for(;null!==m;)h.push(m),/^[0-9]/.test(c.charAt(b))?(m=c.charAt(b),b++):(m=null,0===l&&g("[0-9]"));null!==h?(m=Xa(),m=null!==m?m:"",null!==m?e=[e,d,h,m]:(e=null,b=k)):(e=null,b=k)}else e=null,b=k;else e=null,b=k}null!==e&&(e[0]=e[0].join(""),e[2]=e[2].join(""),e=new y({type:"float",value:parseFloat(e.join(""))}));null===e&&(b=a);if(null===e){k=a=b;/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[0-9]"));if(null!==d)for(e=[];null!==d;)e.push(d),/^[0-9]/.test(c.charAt(b))?(d=c.charAt(b),
+b++):(d=null,0===l&&g("[0-9]"));else e=null;null!==e?(d=Xa(),null!==d?e=[e,d]:(e=null,b=k)):(e=null,b=k);null!==e&&(e=new y({type:"float",value:parseFloat(e[0].join("")+e[1])}));null===e&&(b=a)}p[f]={b:b,result:e};return e}function Xa(){var f="float_exponent@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k;k=m=b;/^[Ee]/.test(c.charAt(b))?(a=c.charAt(b),b++):(a=null,0===l&&g("[Ee]"));if(null!==a)if(/^[+\-]/.test(c.charAt(b))?(e=c.charAt(b),b++):(e=null,0===l&&g("[+\\-]")),e=null!==e?e:"",null!==
+e){/^[0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[0-9]"));if(null!==h)for(d=[];null!==h;)d.push(h),/^[0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[0-9]"));else d=null;null!==d?a=[a,e,d]:(a=null,b=k)}else a=null,b=k;else a=null,b=k;null!==a&&(a=["e",a[1]].concat(a[2]).join(""));null===a&&(b=m);p[f]={b:b,result:a};return a}function Cc(){var c="paren_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f;f=h=b;a=ba();null!==a?(e=S(),null!==e?(d=ca(),null!==
+d?a=[a,e,d]:(a=null,b=f)):(a=null,b=f)):(a=null,b=f);null!==a&&(a=a[1]);null===a&&(b=h);p[c]={b:b,result:a};return a}function Dc(){var f="bool_constant@"+b,a=p[f];if(a)return b=a.b,a.result;var e;e=b;"true"===c.substr(b,4)?(a="true",b+=4):(a=null,0===l&&g('"true"'));null===a&&("false"===c.substr(b,5)?(a="false",b+=5):(a=null,0===l&&g('"false"')));null!==a&&(a=new y({type:"bool",value:"true"==a}));null===a&&(b=e);p[f]={b:b,result:a};return a}function Ec(){var c="primary_expression@"+b,a=p[c];if(a)return b=
+a.b,a.result;a=Fc();null===a&&(a=Q(),null===a&&(a=Bc(),null===a&&(a=Ac(),null===a&&(a=Dc(),null===a&&(a=Cc())))));p[c]={b:b,result:a};return a}function Fa(){var c="index_accessor@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f;f=h=b;a=ua();null!==a?(e=S(),null!==e?(d=va(),null!==d?a=[a,e,d]:(a=null,b=f)):(a=null,b=f)):(a=null,b=f);null!==a&&(a=new y({type:"accessor",index:a[1]}));null===a&&(b=h);p[c]={b:b,result:a};return a}function Ga(){var f="field_selector@"+b,a=p[f];if(a)return b=a.b,a.result;
+var e,d,h;h=d=b;46===c.charCodeAt(b)?(a=".",b+=1):(a=null,0===l&&g('"."'));null!==a?(e=Q(),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"field_selector",selection:a[1].name}));null===a&&(b=d);p[f]={b:b,result:a};return a}function Gc(){var c="postfix_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f;f=a=b;e=Ec();if(null!==e){d=[];h=Ga();for(null===h&&(h=Fa());null!==h;)d.push(h),h=Ga(),null===h&&(h=Fa());null!==d?e=[e,d]:(e=null,b=f)}else e=null,b=f;if(null!==e)for(d=
+e[1],e=e[0],h=0;h<d.length;h++)e=new y({type:"postfix",j:d[h],N:e});null===e&&(b=a);p[c]={b:b,result:e};return e}function Hc(){var f="postfix_expression_no_repeat@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k,n;n=a=b;e=Gc();if(null!==e)if(d=q(),d=null!==d?d:"",null!==d)if("++"===c.substr(b,2)?(h="++",b+=2):(h=null,0===l&&g('"++"')),null===h&&("--"===c.substr(b,2)?(h="--",b+=2):(h=null,0===l&&g('"--"'))),h=null!==h?h:"",null!==h){m=[];k=Ga();for(null===k&&(k=Fa());null!==k;)m.push(k),k=Ga(),
+null===k&&(k=Fa());null!==m?e=[e,d,h,m]:(e=null,b=n)}else e=null,b=n;else e=null,b=n;else e=null,b=n;if(null!==e)for(h=e[2],d=e[3],e=e[0],h&&(e=new y({type:"postfix",j:new y({id:Ic++,type:"operator",j:h}),N:e})),h=0;h<d.length;h++)e=new y({type:"postfix",j:d[h],N:e});null===e&&(b=a);p[f]={b:b,result:e};return e}function vb(){var f="parameter_list@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m,k,n;m=b;"void"===c.substr(b,4)?(a="void",b+=4):(a=null,0===l&&g('"void"'));null!==a&&(a=[]);null===a&&
+(b=m);if(null===a){k=m=b;a=S();if(null!==a){e=[];n=b;d=H();null!==d?(h=S(),null!==h?d=[d,h]:(d=null,b=n)):(d=null,b=n);for(;null!==d;)e.push(d),n=b,d=H(),null!==d?(h=S(),null!==h?d=[d,h]:(d=null,b=n)):(d=null,b=n);null!==e?a=[a,e]:(a=null,b=k)}else a=null,b=k;null!==a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=m)}p[f]={b:b,result:a};return a}function Fc(){var c="function_call@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f,g;g=a=b;e=Jc();null!==
+e?(d=ba(),null!==d?(h=vb(),h=null!==h?h:"",null!==h?(f=ca(),null!==f?e=[e,d,h,f]:(e=null,b=g)):(e=null,b=g)):(e=null,b=g)):(e=null,b=g);null!==e&&(d=e[2],e=new y({type:"function_call",I:e[0],l:d}),d||(e.l=[]));null===e&&(b=a);p[c]={b:b,result:e};return e}function Jc(){var c="function_identifier@"+b,a=p[c];if(a)return b=a.b,a.result;var e;e=b;a=Q();null!==a&&(a=a.name);null===a&&(b=e);null===a&&(a=Aa());p[c]={b:b,result:a};return a}function Ya(){var f="unary_expression@"+b,a=p[f];if(a)return b=a.b,
+a.result;var e,d,h,m;m=h=b;"++"===c.substr(b,2)?(a="++",b+=2):(a=null,0===l&&g('"++"'));null===a&&("--"===c.substr(b,2)?(a="--",b+=2):(a=null,0===l&&g('"--"')),null===a&&(33===c.charCodeAt(b)?(a="!",b+=1):(a=null,0===l&&g('"!"')),null===a&&(126===c.charCodeAt(b)?(a="~",b+=1):(a=null,0===l&&g('"~"')),null===a&&(43===c.charCodeAt(b)?(a="+",b+=1):(a=null,0===l&&g('"+"')),null===a&&(45===c.charCodeAt(b)?(a="-",b+=1):(a=null,0===l&&g('"-"')))))));a=null!==a?a:"";null!==a?(e=q(),e=null!==e?e:"",null!==
+e?(d=Hc(),null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(e=a[0],L=a[2],e&&(L=new y({type:"unary",N:L,j:new y({type:"operator",j:e})})),a=L);null===a&&(b=h);p[f]={b:b,result:a};return a}function Mb(){var f="multiplicative_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;h=d=b;42===c.charCodeAt(b)?(a="*",b+=1):(a=null,0===l&&g('"*"'));null===a&&(47===c.charCodeAt(b)?(a="/",b+=1):(a=null,0===l&&g('"/"')),null===a&&(37===c.charCodeAt(b)?(a="%",b+=1):(a=null,0===l&&g('"%"'))));
+null!==a?(m=b,l++,61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="')),l--,null===e?e="":(e=null,b=m),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"operator",j:a[0]}));null===a&&(b=d);p[f]={b:b,result:a};return a}function Za(){var c="multiplicative_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f,g,k,l,n;l=k=b;a=Ya();if(null!==a){e=[];n=b;d=q();d=null!==d?d:"";null!==d?(h=Mb(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=Ya(),null!==g?d=[d,h,f,g]:(d=null,b=
+n)):(d=null,b=n)):(d=null,b=n)):(d=null,b=n);for(;null!==d;)e.push(d),n=b,d=q(),d=null!==d?d:"",null!==d?(h=Mb(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=Ya(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,b=n)):(d=null,b=n)):(d=null,b=n);null!==e?a=[a,e]:(a=null,b=l)}else a=null,b=l;null!==a&&(a=X(a[0],a[1]));null===a&&(b=k);p[c]={b:b,result:a};return a}function Nb(){var f="additive_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;h=d=b;43===c.charCodeAt(b)?(a="+",b+=1):(a=null,0===l&&g('"+"'));
+null!==a?(m=b,l++,43===c.charCodeAt(b)?(e="+",b+=1):(e=null,0===l&&g('"+"')),null===e&&(61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="'))),l--,null===e?e="":(e=null,b=m),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"operator",j:"+"}));null===a&&(b=d);null===a&&(h=d=b,45===c.charCodeAt(b)?(a="-",b+=1):(a=null,0===l&&g('"-"')),null!==a?(m=b,l++,45===c.charCodeAt(b)?(e="-",b+=1):(e=null,0===l&&g('"-"')),null===e&&(61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="'))),
+l--,null===e?e="":(e=null,b=m),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h),null!==a&&(a=new y({type:"operator",j:"-"})),null===a&&(b=d));p[f]={b:b,result:a};return a}function $a(){var c="additive_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f,g,k,l,n;l=k=b;a=Za();if(null!==a){e=[];n=b;d=q();d=null!==d?d:"";null!==d?(h=Nb(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=Za(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,b=n)):(d=null,b=n)):(d=null,b=n);for(;null!==d;)e.push(d),n=b,d=q(),d=
+null!==d?d:"",null!==d?(h=Nb(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=Za(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,b=n)):(d=null,b=n)):(d=null,b=n);null!==e?a=[a,e]:(a=null,b=l)}else a=null,b=l;null!==a&&(a=X(a[0],a[1]));null===a&&(b=k);p[c]={b:b,result:a};return a}function Ob(){var f="shift_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;h=d=b;"<<"===c.substr(b,2)?(a="<<",b+=2):(a=null,0===l&&g('"<<"'));null===a&&(">>"===c.substr(b,2)?(a=">>",b+=2):(a=null,0===l&&g('">>"')));null!==
+a?(m=b,l++,61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="')),l--,null===e?e="":(e=null,b=m),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"operator",j:a[0]}));null===a&&(b=d);p[f]={b:b,result:a};return a}function ab(){var c="shift_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f,g,k,l,n;l=k=b;a=$a();if(null!==a){e=[];n=b;d=q();d=null!==d?d:"";null!==d?(h=Ob(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=$a(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,b=n)):
+(d=null,b=n)):(d=null,b=n);for(;null!==d;)e.push(d),n=b,d=q(),d=null!==d?d:"",null!==d?(h=Ob(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=$a(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,b=n)):(d=null,b=n)):(d=null,b=n);null!==e?a=[a,e]:(a=null,b=l)}else a=null,b=l;null!==a&&(a=X(a[0],a[1]));null===a&&(b=k);p[c]={b:b,result:a};return a}function Pb(){var f="relational_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;m=h=b;60===c.charCodeAt(b)?(a="<",b+=1):(a=null,0===l&&g('"<"'));null!==
+a?(d=b,l++,60===c.charCodeAt(b)?(e="<",b+=1):(e=null,0===l&&g('"<"')),l--,null===e?e="":(e=null,b=d),null!==e?(61===c.charCodeAt(b)?(d="=",b+=1):(d=null,0===l&&g('"="')),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m);null!==a&&(a=new y({type:"operator",j:"<"+a[2]}));null===a&&(b=h);null===a&&(m=h=b,62===c.charCodeAt(b)?(a=">",b+=1):(a=null,0===l&&g('">"')),null!==a?(d=b,l++,62===c.charCodeAt(b)?(e=">",b+=1):(e=null,0===l&&g('">"')),l--,null===e?e="":(e=null,b=d),null!==
+e?(61===c.charCodeAt(b)?(d="=",b+=1):(d=null,0===l&&g('"="')),d=null!==d?d:"",null!==d?a=[a,e,d]:(a=null,b=m)):(a=null,b=m)):(a=null,b=m),null!==a&&(a=new y({type:"operator",j:">"+a[2]})),null===a&&(b=h));p[f]={b:b,result:a};return a}function bb(){var c="relational_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,h,f,g,k,l,n;l=k=b;a=ab();if(null!==a){e=[];n=b;d=q();d=null!==d?d:"";null!==d?(h=Pb(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=ab(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,
+b=n)):(d=null,b=n)):(d=null,b=n);for(;null!==d;)e.push(d),n=b,d=q(),d=null!==d?d:"",null!==d?(h=Pb(),null!==h?(f=q(),f=null!==f?f:"",null!==f?(g=ab(),null!==g?d=[d,h,f,g]:(d=null,b=n)):(d=null,b=n)):(d=null,b=n)):(d=null,b=n);null!==e?a=[a,e]:(a=null,b=l)}else a=null,b=l;null!==a&&(a=X(a[0],a[1]));null===a&&(b=k);p[c]={b:b,result:a};return a}function Qb(){var f="equality_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e;e=b;"=="===c.substr(b,2)?(a="==",b+=2):(a=null,0===l&&g('"=="'));null===a&&
+("!="===c.substr(b,2)?(a="!=",b+=2):(a=null,0===l&&g('"!="')));null!==a&&(a=new y({type:"operator",j:a}));null===a&&(b=e);p[f]={b:b,result:a};return a}function cb(){var c="equality_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l,n,r;n=l=b;a=bb();if(null!==a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Qb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=bb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==
+d?(f=Qb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=bb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};return a}function Rb(){var f="bitwise_and_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,m;h=d=b;38===c.charCodeAt(b)?(a="&",b+=1):(a=null,0===l&&g('"&"'));null!==a?(m=b,l++,61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="')),null===e&&(38===
+c.charCodeAt(b)?(e="&",b+=1):(e=null,0===l&&g('"&"'))),l--,null===e?e="":(e=null,b=m),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"operator",j:"&"}));null===a&&(b=d);p[f]={b:b,result:a};return a}function db(){var c="bitwise_and_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l,n,r;n=l=b;a=cb();if(null!==a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Rb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=cb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,
+b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==d?(f=Rb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=cb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};return a}function Sb(){var f="bitwise_xor_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,k;h=d=b;94===c.charCodeAt(b)?(a="^",b+=1):(a=null,0===l&&g('"^"'));null!==a?(k=b,
+l++,61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="')),null===e&&(94===c.charCodeAt(b)?(e="^",b+=1):(e=null,0===l&&g('"^"'))),l--,null===e?e="":(e=null,b=k),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"operator",j:"^"}));null===a&&(b=d);p[f]={b:b,result:a};return a}function eb(){var c="bitwise_xor_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l,n,r;n=l=b;a=db();if(null!==a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Sb(),null!==f?(g=q(),g=null!==g?
+g:"",null!==g?(k=db(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==d?(f=Sb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=db(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};return a}function Tb(){var f="bitwise_or_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,k;h=
+d=b;124===c.charCodeAt(b)?(a="|",b+=1):(a=null,0===l&&g('"|"'));null!==a?(k=b,l++,61===c.charCodeAt(b)?(e="=",b+=1):(e=null,0===l&&g('"="')),null===e&&(124===c.charCodeAt(b)?(e="|",b+=1):(e=null,0===l&&g('"|"'))),l--,null===e?e="":(e=null,b=k),null!==e?a=[a,e]:(a=null,b=h)):(a=null,b=h);null!==a&&(a=new y({type:"operator",j:"|"}));null===a&&(b=d);p[f]={b:b,result:a};return a}function fb(){var c="bitwise_or_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l,n,r;n=l=b;a=eb();if(null!==
+a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Tb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=eb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==d?(f=Tb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=eb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};return a}function Ub(){var f=
+"logical_and_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e;e=b;"&&"===c.substr(b,2)?(a="&&",b+=2):(a=null,0===l&&g('"&&"'));null!==a&&(a=new y({type:"operator",j:"&&"}));null===a&&(b=e);p[f]={b:b,result:a};return a}function gb(){var c="logical_and_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l,n,r;n=l=b;a=fb();if(null!==a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Ub(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=fb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,
+b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==d?(f=Ub(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=fb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};return a}function Vb(){var f="logical_xor_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e;e=b;"^^"===c.substr(b,2)?(a="^^",b+=2):(a=null,0===l&&g('"^^"'));null!==a&&(a=new y({type:"operator",
+j:"^^"}));null===a&&(b=e);p[f]={b:b,result:a};return a}function hb(){var c="logical_xor_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l,n,r;n=l=b;a=gb();if(null!==a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Vb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=gb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==d?(f=Vb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=gb(),null!==k?d=[d,f,g,k]:(d=null,
+b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};return a}function Wb(){var f="logical_or_operator@"+b,a=p[f];if(a)return b=a.b,a.result;var e;e=b;"||"===c.substr(b,2)?(a="||",b+=2):(a=null,0===l&&g('"||"'));null!==a&&(a=new y({type:"operator",j:"||"}));null===a&&(b=e);p[f]={b:b,result:a};return a}function Kc(){var c="logical_or_expression@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,
+l,n,r;n=l=b;a=hb();if(null!==a){e=[];r=b;d=q();d=null!==d?d:"";null!==d?(f=Wb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=hb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);for(;null!==d;)e.push(d),r=b,d=q(),d=null!==d?d:"",null!==d?(f=Wb(),null!==f?(g=q(),g=null!==g?g:"",null!==g?(k=hb(),null!==k?d=[d,f,g,k]:(d=null,b=r)):(d=null,b=r)):(d=null,b=r)):(d=null,b=r);null!==e?a=[a,e]:(a=null,b=n)}else a=null,b=n;null!==a&&(a=X(a[0],a[1]));null===a&&(b=l);p[c]={b:b,result:a};
+return a}function pa(){var f="conditional_expression@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,k,n,u,x,r,B,A,C;A=a=b;e=Kc();null!==e?(C=b,d=q(),d=null!==d?d:"",null!==d?(63===c.charCodeAt(b)?(h="?",b+=1):(h=null,0===l&&g('"?"')),null!==h?(k=q(),k=null!==k?k:"",null!==k?(n=S(),null!==n?(u=q(),u=null!==u?u:"",null!==u?(58===c.charCodeAt(b)?(x=":",b+=1):(x=null,0===l&&g('":"')),null!==x?(r=q(),r=null!==r?r:"",null!==r?(B=S(),null!==B?d=[d,h,k,n,u,x,r,B]:(d=null,b=C)):(d=null,b=C)):(d=null,b=C)):
+(d=null,b=C)):(d=null,b=C)):(d=null,b=C)):(d=null,b=C)):(d=null,b=C),d=null!==d?d:"",null!==d?e=[e,d]:(e=null,b=A)):(e=null,b=A);null!==e&&(d=e[0],e=e[1],L=d,e&&(L=new y({type:"ternary",F:d,Ha:e[3],Ga:e[7]})),e=L);null===e&&(b=a);p[f]={b:b,result:e};return e}function S(){var f="assignment_expression@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,k,n,u;u=n=b;a=pa();null!==a?(e=q(),e=null!==e?e:"",null!==e?(61===c.charCodeAt(b)?(d="=",b+=1):(d=null,0===l&&g('"="')),null===d&&("*="===c.substr(b,2)?
+(d="*=",b+=2):(d=null,0===l&&g('"*="')),null===d&&("/="===c.substr(b,2)?(d="/=",b+=2):(d=null,0===l&&g('"/="')),null===d&&("%="===c.substr(b,2)?(d="%=",b+=2):(d=null,0===l&&g('"%="')),null===d&&("+="===c.substr(b,2)?(d="+=",b+=2):(d=null,0===l&&g('"+="')),null===d&&("-="===c.substr(b,2)?(d="-=",b+=2):(d=null,0===l&&g('"-="')),null===d&&("<<="===c.substr(b,3)?(d="<<=",b+=3):(d=null,0===l&&g('"<<="')),null===d&&(">>="===c.substr(b,3)?(d=">>=",b+=3):(d=null,0===l&&g('">>="')),null===d&&("&="===c.substr(b,
+2)?(d="&=",b+=2):(d=null,0===l&&g('"&="')),null===d&&("^="===c.substr(b,2)?(d="^=",b+=2):(d=null,0===l&&g('"^="')),null===d&&("|="===c.substr(b,2)?(d="|=",b+=2):(d=null,0===l&&g('"|="')))))))))))),null!==d?(h=q(),h=null!==h?h:"",null!==h?(k=S(),null!==k?a=[a,e,d,h,k]:(a=null,b=u)):(a=null,b=u)):(a=null,b=u)):(a=null,b=u)):(a=null,b=u);null!==a&&(e=a[0],d=a[4],a=new y({type:"binary",j:new y({type:"operator",j:a[2]}),left:e,right:d}));null===a&&(b=n);null===a&&(a=pa());p[f]={b:b,result:a};return a}
+function Db(){var f="condition@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,k,n,u,x;x=b;a=za();null!==a?(e=q(),null!==e?(d=Q(),null!==d?(h=q(),h=null!==h?h:"",null!==h?(61===c.charCodeAt(b)?(k="=",b+=1):(k=null,0===l&&g('"="')),null!==k?(n=q(),n=null!==n?n:"",null!==n?(u=S(),null!==u?a=[a,e,d,h,k,n,u]:(a=null,b=x)):(a=null,b=x)):(a=null,b=x)):(a=null,b=x)):(a=null,b=x)):(a=null,b=x)):(a=null,b=x);null===a&&(a=S());p[f]={b:b,result:a};return a}function nd(){var f;a:{f=ob;f.sort();for(var a=null,
+e=[],d=0;d<f.length;d++)f[d]!==a&&(e.push(f[d]),a=f[d]);switch(e.length){case 0:f="end of input";break a;case 1:f=e[0];break a;default:f=e.slice(0,e.length-1).join(", ")+" or "+e[e.length-1]}}a=Math.max(b,ta);a=a<c.length?n(c.charAt(a)):"end of input";return"Expected "+f+" but "+a+" found."}function Lc(){for(var f=1,a=1,e=!1,d=0;d<Math.max(b,ta);d++){var g=c.charAt(d);"\n"===g?(e||f++,a=1,e=!1):"\r"===g||"\u2028"===g||"\u2029"===g?(f++,a=1,e=!0):(a++,e=!1)}return{ka:f,Da:a}}function y(b){this.id=
+Ic++;this.ka=Lc().ka;for(var a in b)b.hasOwnProperty(a)&&(this[a]=b[a])}function X(b,a){for(var c=b,d=0;d<a.length;d++)c=new y({type:"binary",j:a[d][1],left:c,right:a[d][3]});return c}function jc(b,a,c){c&&(a=a.concat([c]));c=b[0];c.ia=b[1].C;b=c;for(var d=0;d<a.length;d++)b.M=a[d][0],b.M.ia=a[d][1].C,b=b.M;return c}var Mc={EOF:B,_:q,additive_expression:$a,additive_operator:Nb,assignment_expression:S,attribute_qualifier:Ib,attribute_type:rc,bitwise_and_expression:db,bitwise_and_operator:Rb,bitwise_or_expression:fb,
+bitwise_or_operator:Tb,bitwise_xor_expression:eb,bitwise_xor_operator:Sb,bool_constant:Dc,comma:H,comment:C,compound_statement:Qa,condition:Db,conditional_expression:pa,const_qualifier:Va,declaration:Sa,declarator:Ca,declarator_array_with_size:Ea,declarator_list:wc,declarator_list_arrays_have_size:Gb,declarator_list_no_array:sc,declarator_no_array:Da,do_while:pc,equality_expression:cb,equality_operator:Qb,equals:cc,expression_statement:Cb,external_declaration:ec,external_statement:rb,external_statement_list:ja,
+field_selector:Ga,float_constant:Bc,float_exponent:Xa,for_loop:nc,fragment_start:function(){var c="fragment_start@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f;f=d=b;ya="fs";a="";null!==a?(e=ja(),null!==e?a=[a,e]:(a=null,b=f)):(a=null,b=f);null!==a&&(a=a[1]);null===a&&(b=d);p[c]={b:b,result:a};return a},fully_specified_type:qc,function_call:Fc,function_definition:fc,function_identifier:Jc,function_prototype:Ab,function_prototype_parameter_list:tc,global_declaration:gc,identifier:Q,index_accessor:Fa,
+init_declarator:Wa,init_declarator_list:Fb,int_constant:Ac,iteration_statement:lc,jump_statement:mc,keyword:zc,left_brace:pb,left_bracket:ua,left_paren:ba,locally_specified_type:za,logical_and_expression:gb,logical_and_operator:Ub,logical_or_expression:Kc,logical_or_operator:Wb,logical_xor_expression:hb,logical_xor_operator:Vb,macro_call:Oa,macro_call_line:function(){var f="macro_call_line@"+b,a=p[f];if(a)return b=a.b,a.result;var e,d,h,k;k=h=b;a=Oa();a=null!==a?a:"";if(null!==a){e=[];/^[^\n]/.test(c.charAt(b))?
+(d=c.charAt(b),b++):(d=null,0===l&&g("[^\\n]"));for(;null!==d;)e.push(d),/^[^\n]/.test(c.charAt(b))?(d=c.charAt(b),b++):(d=null,0===l&&g("[^\\n]"));null!==e?a=[a,e]:(a=null,b=k)}else a=null,b=k;null!==a&&(a={aa:a[0],Ja:a[1].join("")});null===a&&(b=h);p[f]={b:b,result:a};return a},macro_call_parameter:Pa,macro_call_parameter_list:function(){var c="macro_call_parameter_list@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f,g,k,l;k=g=b;a=Pa();if(null!==a){e=[];l=b;d=H();null!==d?(f=Pa(),null!==f?d=[d,
+f]:(d=null,b=l)):(d=null,b=l);for(;null!==d;)e.push(d),l=b,d=H(),null!==d?(f=Pa(),null!==f?d=[d,f]:(d=null,b=l)):(d=null,b=l);null!==e?a=[a,e]:(a=null,b=k)}else a=null,b=k;null!==a&&(a=function(a,b){return[a].concat(b.map(function(a){return a[1]}))}(a[0],a[1]));null===a&&(b=g);p[c]={b:b,result:a};return a},macro_identifier:na,macro_paren_parameter:ub,matrix:Kb,member_list:xc,multiplicative_expression:Za,multiplicative_operator:Mb,newLine:u,noNewlineComment:x,noNewlineWhitespace:ha,parameter_declaration:Ua,
+parameter_list:vb,parameter_qualifier:vc,paren_expression:Cc,postfix_expression:Gc,postfix_expression_no_repeat:Hc,precision_qualifier:Ta,precision_type:Ba,preprocessor_define:sb,preprocessor_else:xb,preprocessor_else_if:wa,preprocessor_end:yb,preprocessor_external_branch:dc,preprocessor_if:wb,preprocessor_operator:tb,preprocessor_parameter_list:ic,preprocessor_statement_branch:zb,primary_expression:Ec,relational_expression:bb,relational_operator:Pb,reserved:function(){var f="reserved@"+b,a=p[f];
+if(a)return b=a.b,a.result;var e,d,h,k;l++;k=b;a=[];for(e=Lb();null!==e;)a.push(e),e=Lb();if(null!==a)if("__"===c.substr(b,2)?(e="__",b+=2):(e=null,0===l&&g('"__"')),null!==e){d=[];/^[A-Za-z_0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[A-Za-z_0-9]"));for(;null!==h;)d.push(h),/^[A-Za-z_0-9]/.test(c.charAt(b))?(h=c.charAt(b),b++):(h=null,0===l&&g("[A-Za-z_0-9]"));null!==d?a=[a,e,d]:(a=null,b=k)}else a=null,b=k;else a=null,b=k;l--;0===l&&null===a&&g("reserved name");p[f]={b:b,result:a};
+return a},right_brace:qb,right_bracket:va,right_paren:ca,selection_statement:kc,semicolon:A,shift_expression:ab,shift_operator:Ob,simple_statement:Bb,single_underscore_identifier:Lb,statement_list:oa,statement_no_new_scope:xa,statement_with_scope:Ra,struct_definition:hc,type_name:Aa,type_qualifier:Hb,unary_expression:Ya,vector:Jb,vertex_start:function(){var c="vertex_start@"+b,a=p[c];if(a)return b=a.b,a.result;var e,d,f;f=d=b;ya="vs";a="";null!==a?(e=ja(),null!==e?a=[a,e]:(a=null,b=f)):(a=null,b=
+f);null!==a&&(a=a[1]);null===a&&(b=d);p[c]={b:b,result:a};return a},void_type:uc,while_loop:oc,while_statement:Eb};if(void 0!==f){if(void 0===Mc[f])throw Error("Invalid rule name: "+n(f)+".");}else f="external_statement_list";var b=0,l=0,ta=0,ob=[],p={},yc={},Ic=0,ya="vs",L=Mc[f]();if(null===L||b!==c.length){var Nc=Lc();throw new la.SyntaxError(nd(),Nc.ka,Nc.Da);}return L}la.SyntaxError=function(c,f,k){this.name="SyntaxError";this.message=c;this.line=f;this.column=k};la.SyntaxError.prototype=Error.prototype;function ma(c){return c.split(/_/g).map(function(c){return c.slice(0,1).toUpperCase()+c.slice(1).toLowerCase()}).join("")}["varying mediump vec4 gl_FragCoord;","varying bool gl_FrontFacing;","varying mediump vec2 gl_PointCoord;"].map(function(c){return z(c,"global_declaration")});var qa=Array.prototype,D=qa.forEach?function(c,f,k){qa.forEach.call(c,f,k)}:function(c,f,k){for(var n=c.length,g="string"==typeof c?c.split(""):c,u=0;u<n;u++)u in g&&f.call(k,g[u],u,c)};function ra(c,f,k){for(var n="string"==typeof c?c.split(""):c,g=c.length-1;0<=g;--g)g in n&&f.call(k,n[g],g,c)}var sa=qa.every?function(c,f,k){return qa.every.call(c,f,k)}:function(c,f,k){for(var n=c.length,g="string"==typeof c?c.split(""):c,u=0;u<n;u++)if(u in g&&!f.call(k,g[u],u,c))return!1;return!0};
+function Ha(c){var f=c.length;if(0<f){for(var k=Array(f),n=0;n<f;n++)k[n]=c[n];return k}return[]};function Ia(){}Ia.prototype.i=function(c){return this["beforeVisit"+ma(c.type)]};Ia.prototype.u=function(c){return this["afterVisit"+ma(c.type)]};Ia.prototype.m=function(c){return this["visit"+ma(c.type)]};function E(c,f){if(f&&f.type){var k=c.i(f);k&&k.apply(c,[f]);(k=c.m(f))?k.apply(c,[f]):Ja(c,f);(k=c.u(f))&&k.apply(c,[f])}}function Ka(c,f){D(f,function(c){E(this,c)},c)}
+function Ja(c,f){for(var k in f){var n=f[k];if("array"==ea(n))Ka(c,n);else{var g=typeof n;("object"==g&&null!=n||"function"==g)&&n.type&&E(c,n)}}};function F(c,f){this.c=c||"\n";this.h=!!f;this.a="";this.f=0}v(F,Ia);ka("glslunit.Generator",F);function G(c,f,k){this.a=c;this.c=f||0;this.f=k||2}
+var La={function_call:new G(0,2),identifier:new G(0,2),"float":new G(0,2),"int":new G(0,2),bool:new G(0,2),postfix:new G(1,0,1),unary:new G(2,1,1),"*":new G(3,0),"/":new G(3),"%":new G(3),"+":new G(4,0),"-":new G(4),"<<":new G(5),">>":new G(5),"<":new G(6),">":new G(6),"<=":new G(6),">=":new G(6),"==":new G(7),"!=":new G(7),"&":new G(8,0),"^":new G(9,0),"|":new G(10,0),"&&":new G(11,0),"^^":new G(12,0),"||":new G(13,0),ternary:new G(14,1,3),"=":new G(15),"-=":new G(15),"+=":new G(15),"*=":new G(15),
+"/=":new G(15),"%=":new G(15),"<<=":new G(15),">>=":new G(15),"&=":new G(15),"^=":new G(15),"|=":new G(15)};function I(c,f,k){f=new F(f,k);E(f,c);return f.a}function Ma(c,f,k){return f?c+f+k:""}function Na(c,f,k){for(var n=0;n<f.length;n++)0!=n&&(c.a+=k),E(c,f[n])}function ib(c){c.h&&(c.a+=c.c+Array(c.f+1).join("  "))}function jb(c){c.h&&"  "==c.a.slice(-2)&&(c.a=c.a.slice(0,-2))}
+F.prototype.za=function(c){this.a+=Ma("",c.qualifier," ");this.a+="struct";this.a+=Ma(" ",c.name,"");this.a+="{";this.f++;Na(this,c.Ia,"");this.f--;jb(this);this.a+="}";c.A&&Na(this,c.A,",");this.a+=";";ib(this)};F.prototype.visitStructDefinition=F.prototype.za;F.prototype.ya=function(c){this.a+="{";this.f++;ib(this);Na(this,c.C,"");this.f--;jb(this);this.a+="}";ib(this)};F.prototype.visitScope=F.prototype.ya;F.prototype.va=function(c){this.a+="precision "+c.precision+" "+c.typeName+";";ib(this)};
+F.prototype.visitPrecision=F.prototype.va;F.prototype.ha=function(c){this.a+="invariant ";Na(this,c.Ea,",");this.a+=";";ib(this)};F.prototype.visitInvariant=F.prototype.ha;F.prototype.ta=function(c){this.a+=Ma("",c.qa," ");this.a+=Ma("",c.la," ");this.a+=Ma("",c.precision," ");this.a+=c.Ka+" "+c.name;c.$&&(this.a+="[",E(this,c.$),this.a+="]")};F.prototype.visitParameter=F.prototype.ta;function kb(c,f){E(c,f.ma);c.a+=" "+f.name+"(";Na(c,f.l,",");c.a+=")"}
+F.prototype.da=function(c){kb(this,c);this.a+=";";ib(this)};F.prototype.visitFunctionPrototype=F.prototype.da;F.prototype.ca=function(c){kb(this,c);E(this,c.body)};F.prototype.visitFunctionDeclaration=F.prototype.ca;
+F.prototype.wa=function(c){var f=this.a.slice(-1*this.c.length);f&&f!=this.c&&(this.a+=this.c);this.a+=c.B;"#define"==c.B?(this.a+=" "+c.identifier,c.l&&(this.a+="(",Na(this,c.l,","),this.a+=")"),this.a+=Ma(" ",c.pa,"")):this.a+=Ma(" ",c.value,"");this.a+=this.c;c.ia&&(Na(this,c.ia,""),c.M&&E(this,c.M),"#if"==c.B.slice(0,3)&&(f=this.a.slice(-1*this.c.length),f!=this.c&&(this.a+=this.c),this.a+="#endif"+this.c))};F.prototype.visitPreprocessor=F.prototype.wa;
+function lb(c,f){c.a+="while(";E(c,f.F);c.a+=")"}F.prototype.U=function(c){this.a+="do";"scope"!=c.body.type&&(this.a+=" ");E(this,c.body);lb(this,c)};F.prototype.visitDoStatement=F.prototype.U;F.prototype.Ca=function(c){lb(this,c);E(this,c.body)};F.prototype.visitWhileStatement=F.prototype.Ca;F.prototype.ba=function(c){this.a+="for(";E(this,c.H);E(this,c.F);this.a+=";";E(this,c.Fa);this.a+=")";E(this,c.body)};F.prototype.visitForStatement=F.prototype.ba;
+F.prototype.fa=function(c){this.a+="if(";E(this,c.F);this.a+=")";E(this,c.body);c.M&&(this.a+="else","scope"!=c.M.type&&(this.a+=" "),E(this,c.M))};F.prototype.visitIfStatement=F.prototype.fa;F.prototype.S=function(c){E(this,c.name);c.isArray&&(this.a+="[",c.$&&E(this,c.$),this.a+="]");c.H&&(this.a+="=",E(this,c.H))};F.prototype.visitDeclaratorItem=F.prototype.S;F.prototype.K=function(c){E(this,c.w);this.a+=" ";Na(this,c.A,",");this.a+=";";ib(this)};F.prototype.visitDeclarator=F.prototype.K;
+F.prototype.Ba=function(c){this.a+=Ma("",c.qualifier," ");this.a+=Ma("",c.precision," ");this.a+=c.name};F.prototype.visitType=F.prototype.Ba;F.prototype.X=function(c){E(this,c.N);this.a+=";";ib(this)};F.prototype.visitExpression=F.prototype.X;F.prototype.g=function(c){this.a+=c.type;c.value&&(this.a+=" ",E(this,c.value));this.a+=";";ib(this)};F.prototype.visitJump=F.prototype.g;F.prototype.xa=F.prototype.g;F.prototype.visitReturn=F.prototype.xa;F.prototype.W=F.prototype.g;
+F.prototype.visitBreak=F.prototype.W;F.prototype.T=F.prototype.g;F.prototype.visitDiscard=F.prototype.T;F.prototype.J=F.prototype.g;F.prototype.visitContinue=F.prototype.J;F.prototype.ra=function(c){this.a.slice(-1)==c.j.j&&(this.a+=" ");E(this,c.j);mb(this,c.N,c,0)};F.prototype.visitUnary=F.prototype.ra;F.prototype.ua=function(c){mb(this,c.N,c,0);E(this,c.j)};F.prototype.visitPostfix=F.prototype.ua;F.prototype.sa=function(c){this.a+=c.j};F.prototype.visitOperator=F.prototype.sa;
+F.prototype.Y=function(c){this.a+="."+c.selection};F.prototype.visitFieldSelector=F.prototype.Y;F.prototype.D=function(c){this.a+="[";E(this,c.index);this.a+="]"};F.prototype.visitAccessor=F.prototype.D;F.prototype.v=function(c){this.a+=c.I+"(";Na(this,c.l,",");this.a+=")"};F.prototype.visitFunctionCall=F.prototype.v;F.prototype.ea=function(c){this.a+=c.name};F.prototype.visitIdentifier=F.prototype.ea;
+function nb(c){function f(c){return c.toLowerCase().replace(/^0*|\+/g,"").replace(/(?:(\.[1-9]+)|\.)0*e/g,"$1e")}if(0==c)return"0.";var k=f(""+c);c=f(c.toExponential());-1==k.indexOf(".")&&-1==k.indexOf("e")&&(k+=".");return k.length<=c.length?k:c}F.prototype.Z=function(c){this.a+=nb(c.value)};F.prototype.visitFloat=F.prototype.Z;F.prototype.o=function(c){this.a+=c.value};F.prototype.visitValue=F.prototype.o;
+F.prototype.ga=function(c){var f=c.value;c=f.toString(10);f=(0>f?"-":"")+"0x"+Math.abs(f).toString(16).toLowerCase();this.a+=c.length<=f.length?c:f};F.prototype.visitInt=F.prototype.ga;F.prototype.P=F.prototype.o;F.prototype.visitBool=F.prototype.P;F.prototype.O=function(c){mb(this,c.left,c,0);E(this,c.j);mb(this,c.right,c,1)};F.prototype.visitBinary=F.prototype.O;F.prototype.Aa=function(c){mb(this,c.F,c,0);this.a+="?";mb(this,c.Ha,c,1);this.a+=":";mb(this,c.Ga,c,2)};F.prototype.visitTernary=F.prototype.Aa;
+function mb(c,f,k,n){var g=La["binary"==f.type?f.j.j:f.type];k=La["binary"==k.type?k.j.j:k.type];var u=!1;if(u=g.a>k.a?!0:g.a==k.a?0==k.c&&0==n||1==k.c&&n==k.f-1?!1:!0:!1)c.a+="(";E(c,f);u&&(c.a+=")")};function J(c,f){this.U=c||null;this.o=!!f;this.D={};this.h=[];this.a=[];this.v={};this.c=[]}v(J,Ia);ka("glslunit.VariableScopeVisitor",J);function Xb(c){var f={};ra(c.h.concat([c.a]),function(c){D(c,function(c){"declarator"==c.type?D(c.A,function(g){da(f[g.name.name])||(f[g.name.name]=c)},this):"parameter"==c.type&&(f[c.name]=c)},this)},c);return f}J.prototype.g=function(){this.h.push(this.a);this.a=[];[].push.apply(this.a,this.c);this.c=[]};J.prototype.beforeVisitScope=J.prototype.g;
+J.prototype.f=function(c){this.v[c.id]=this.a;c==this.U&&(this.D=Xb(this));this.a=this.h.pop()};J.prototype.afterVisitScope=J.prototype.f;J.prototype.K=J.prototype.g;J.prototype.beforeVisitRoot=J.prototype.K;J.prototype.O=function(c){this.o||this.f(c)};J.prototype.afterVisitPreprocessor=J.prototype.O;J.prototype.J=function(){this.o||this.g()};J.prototype.beforeVisitPreprocessor=J.prototype.J;J.prototype.P=J.prototype.f;J.prototype.afterVisitRoot=J.prototype.P;J.prototype.W=function(c){this.a.push(c)};
+J.prototype.beforeVisitDeclarator=J.prototype.W;J.prototype.T=function(c){this.c=c.l};J.prototype.beforeVisitFunctionDeclaration=J.prototype.T;J.prototype.S=function(){this.c=[]};J.prototype.afterVisitFunctionDeclaration=J.prototype.S;function Yb(c){var f=new J;E(f,c);return f.v}J.getScopeToDeclarationMap=Yb;function Zb(c,f){var k=new J(f,!0);E(k,c);return k.D};function $b(c){this.c=[];this.a=[];this.f=c}v($b,Ia);$b.prototype.g=function(c){this.f(c,this.a.slice(0,-1))&&this.c.push(c);Ja(this,c)};$b.prototype.i=function(){return ia(Array.prototype.push,this.a)};$b.prototype.u=function(){return ia(Array.prototype.pop,this.a)};$b.prototype.m=function(){return this.g};function ac(c,f){var k=new $b(f);E(k,c);return k.c};function K(){this.a={};this.a[bc]=[];this.c=bc}v(K,Ia);var bc="#";K.prototype.g=function(c){c.name in this.a||(this.a[c.name]=[]);this.c=c.name};K.prototype.beforeVisitFunctionDeclaration=K.prototype.g;K.prototype.f=function(){this.c=bc};K.prototype.afterVisitFunctionDeclaration=K.prototype.f;K.prototype.h=function(c){this.a[this.c].push(c.I);Ja(this,c)};K.prototype.visitFunctionCall=K.prototype.h;function Oc(c){c=ac(c,function(c,f){return"declarator"==c.type&&"struct_definition"==f.slice(-1)[0].type});var f=[];D(c,function(c){f[c.id]=!0});return f};function M(c){var f={},k;for(k in c)f[k]=c[k];return f};function N(){this.c=[]}var Pc=-1;function O(c){c=M(c);c.id=Pc--;return c}N.prototype.W=function(c){return this["transform"+ma(c.type)]};function Qc(c,f,k,n){var g=c[k+ma(f.type)];return ia(function(c){D(this.c,function(f){var g=n(f).apply(f,[c]);g&&g.apply(f,[c])});g&&g.apply(this,[c])},c)}N.prototype.P=function(c){return Qc(this,c,"beforeTransform",function(c){return c.i})};N.prototype.O=function(c){return Qc(this,c,"afterTransform",function(c){return c.u})};
+function P(c,f){var k=!1,n=c.P(f);n&&n.apply(c,[f]);var n=O(f),g;for(g in f){var u=f[g];if("array"==ea(u)){n[g]=[];for(var B=0;B<u.length;B++){var q=u[B],x=P(c,q);x!=q&&(k=!0);null!=x&&Array.prototype.push.apply(n[g],"array"==ea(x)?x:[x])}}else u&&u.type&&(x=P(c,u),x!=u&&(k=!0,null!=x?n[g]=x:delete n[g]))}g=c.W(f);n=k?n:f;g&&(n=g.apply(c,[n,f]));(k=c.O(f))&&k.apply(c,[f,n]);return n};function Rc(c,f,k,n,g){this.c=[];this.u=g;this.i=c;this.a=f;this.h=k;this.f=n}v(Rc,N);ka("glslunit.SpliceTransformer",Rc);Rc.prototype.g=function(c){if(c==this.i){c=O(c);if("array"==ea(c[this.a])){var f=this.u.map(O);c[this.a]=Ha(c[this.a]);[].splice.apply(c[this.a],[this.h,this.f].concat(f));return c}throw this.a+" wasn't an array.";}return c};Rc.prototype.W=function(){return this.g};function Sc(){this.s=this.a="";this.c=[]}Sc.prototype.clone=function(){var c=M(this);c.c=Ha(this.c);return c};function Tc(){this.f=this.s="";this.c=this.a=this.g=0}function Uc(){this.type=this.a=this.s=""}function R(){this.i=[];this.f=[];this.h={};this.L={};this.R={};this.g=[];this.a={};this.c={};this.u={}}
+R.prototype.clone=function(){var c=M(this);c.L=M(this.L);c.R=M(this.R);c.h=M(this.h);c.i=Ha(this.i);c.u=M(this.u);c.f=this.f.map(function(c){return M(c)});c.g=this.g.map(function(c){return c.clone()});return c};R.prototype.P=function(c){return I(this.c,c||"\\n",!1)};R.prototype.getVertexSource=R.prototype.P;R.prototype.o=function(c){return I(this.a,c||"\\n",!1)};R.prototype.getFragmentSource=R.prototype.o;R.prototype.v=function(c){return"".replace(/\n/g,c||"\\n").replace(/'/g,"\\'")};
+R.prototype.getOriginalFragmentSource=R.prototype.v;R.prototype.D=function(c){return"".replace(/\n/g,c||"\\n").replace(/'/g,"\\'")};R.prototype.getOriginalVertexSource=R.prototype.D;R.prototype.m=function(){var c=[],f;for(f in this.L)c.push(this.L[f]);0<c.length&&(c[c.length-1].last=!0);return c};R.prototype.getAttributes=R.prototype.m;R.prototype.O=function(){var c=[],f;for(f in this.R)c.push(this.R[f]);0<c.length&&(c[c.length-1].last=!0);return c};R.prototype.getUniforms=R.prototype.O;
+function Vc(c){D(c.f,function(c){""==c.s&&(c.s=c.a)});var f={},k;for(k in c.L){var n=c.L[k];f[n.s]=n}k={};for(var g in c.R)n=c.R[g],k[n.s]=n;Wc(c,c.c,f,k);Wc(c,c.a,f,k)}
+function Wc(c,f,k,n){f=ac(f,function(c){return"declarator"==c.type&&("attribute"==c.w.qualifier||"uniform"==c.w.qualifier)});D(f,function(c){D(c.A,function(f){f=f.name.name;var B=c.w.name;if("attribute"==c.w.qualifier){var q=k[f];if(!q){q=new Tc;q.s=f;q.f=f;var x=I(c),ha="".search(x),B=parseInt(B.slice(3,4),10);q.g=isNaN(B)?1:B;q.a=ha;q.c=x.length;this.L[f]=q}}else q=n[f],q||(q=new Uc,q.s=f,q.a=f,q.type=B,this.R[f]=q)},this)},c)};function T(){this.c=[];this.a=[]}v(T,N);var Xc={vec2:2,vec3:3,vec4:4,bvec2:2,bvec3:3,bvec4:4,ivec2:2,ivec3:3,ivec4:4,mat2:4,mat3:9,mat4:16,"float":1,"int":1,bool:1};T.prototype.P=function(){return ia(Array.prototype.push,this.a)};T.prototype.O=function(){return ia(Array.prototype.pop,this.a)};function Yc(c){c=c.a.slice(-2)[0];return"function_call"==c.type&&c.I in Xc}
+T.prototype.i=function(c){if(Yc(this)&&65536>Math.abs(c.value)&&c.value==Math.round(c.value)){var f=O(c);f.type="int";f.value=Number(c.value);return f}return c};T.prototype.o=T.prototype.i;T.prototype.transformFloat=T.prototype.o;T.prototype.m=T.prototype.i;T.prototype.transformBool=T.prototype.m;
+T.prototype.h=function(c){var f=Xc[c.I];if(f){if(Yc(this)&&c.l.length==f)return c.l;if(c.l&&1<c.l.length){var k=I(c.l[0]),n=!1;if("mat"==c.I.slice(0,3)){if(c.l.length==f){for(var f=!0,g=parseInt(c.I.slice(-1),10),u=0;u<g&&f;u++)for(var B=0;B<g&&f;B++)f=I(c.l[u*g+B])==(u==B?k:"0");f&&(n=!0)}}else sa(c.l,function(c){return I(c)==k})&&(n=!0);if(n)return n=O(c),n.l=[c.l[0]],n}}return c};T.prototype.transformFunctionCall=T.prototype.h;T.prototype.f=function(){return"ConstructorMinifier"};
+T.prototype.g=function(){return[]};T.prototype.u=function(c,f){var k=new T;f.c=P(new T,f.c);f.a=P(k,f.a);return[]};function U(){this.c=[]}v(U,N);function Zc(c,f){var k=c[f],n=c;k&&"scope"==k.type&&1==k.C.length&&(n=O(c),n[f]=k.C[0]);return n}U.prototype.h=function(c){c=Zc(c,"body");return Zc(c,"elseBody")};U.prototype.transformIfStatement=U.prototype.h;U.prototype.m=function(c){return Zc(c,"body")};U.prototype.transformWhileStatement=U.prototype.m;U.prototype.a=function(c){return Zc(c,"body")};U.prototype.transformDoStatement=U.prototype.a;U.prototype.i=function(c){return Zc(c,"body")};
+U.prototype.transformForStatement=U.prototype.i;U.prototype.f=function(){return"BraceReducer"};U.prototype.g=function(){return[]};U.prototype.u=function(c,f){var k=new U;f.c=P(new U,f.c);f.a=P(k,f.a);return[]};function V(){this.c=[];this.a={}}v(V,N);V.prototype.h=function(c){var f=new K;E(f,c);c=f.a;this.a={};for(var k in c)this.a[k]=!1;$c(this,"main",c);$c(this,bc,c)};V.prototype.beforeTransformRoot=V.prototype.h;function $c(c,f,k){f in c.a&&!c.a[f]&&(c.a[f]=!0,D(k[f],function(c){$c(this,c,k)},c))}V.prototype.i=function(c){return this.a[c.name]?c:null};V.prototype.transformFunctionDeclaration=V.prototype.i;V.prototype.m=V.prototype.i;V.prototype.transformFunctionPrototype=V.prototype.m;V.prototype.f=function(){return"DeadFunctionRemover"};
+V.prototype.g=function(){return[]};V.prototype.u=function(c,f){var k=new V;f.c=P(new V,f.c);f.a=P(k,f.a);return[]};function W(c){this.c=[];this.J={};this.h=null;this.a=[];this.o=[];this.v=[];this.i=c}v(W,N);W.prototype.X=function(c){var f=ac(c,function(c){return"for_statement"==c.type});D(f,function(c){this.o[c.H.id]=!0},this);this.v=Oc(c);var f=Yb(c),k;for(k in f){var n={};D(f[+k],function(f){if("declarator"==f.type&&ad(this,f,k==c.id)){var u=I(f.w);n[u]||(n[u]=[]);D(f.A,function(c){var f=c;f.H&&(f=O(c),delete f.H);n[u].push(f)},this)}},this);this.J[+k]=n}this.m(c)};W.prototype.beforeTransformRoot=W.prototype.X;
+W.prototype.U=function(c){this.h=c};W.prototype.beforeTransformDeclarator=W.prototype.U;W.prototype.T=function(){this.h=null};W.prototype.afterTransformDeclarator=W.prototype.T;W.prototype.m=function(c){this.a.push(this.J[c.id])};W.prototype.beforeTransformScope=W.prototype.m;W.prototype.S=W.prototype.m;W.prototype.beforeTransformPreprocessor=W.prototype.S;W.prototype.D=function(){this.a.pop()};W.prototype.afterTransformScope=W.prototype.D;W.prototype.K=W.prototype.D;
+W.prototype.afterTransformPreprocessor=W.prototype.K;function ad(c,f,k){var n=I(f.w),g=null;0<c.a.length&&(g=c.a.slice(-1)[0][n]);return null!=f&&(c.i||"attribute"!=f.w.qualifier)&&"const"!=f.w.qualifier&&(!g||1<g.length)&&!(f.id in c.o)&&!(f.id in c.v)&&!(k&&!sa(f.A,function(c){return!da(c.H)}))}W.prototype.Z=function(c){return ad(this,this.h,1==this.a.length)?c.H?{id:Pc--,type:"expression",N:{id:Pc--,type:"binary",j:{id:Pc--,type:"operator",j:"="},left:c.name,right:c.H}}:null:c};
+W.prototype.transformDeclaratorItem=W.prototype.Z;W.prototype.Y=function(c){if(!ad(this,c,1==this.a.length))return c;var f=[],k=I(c.w),n=this.a.slice(-1)[0],g=n[k];g&&(f=O(c),f.A=g,f=[f],delete n[k]);c.A&&0!=c.A.length&&Array.prototype.push.apply(f,c.A);return f};W.prototype.transformDeclarator=W.prototype.Y;W.prototype.f=function(){return"DeclarationConsolidation"};W.prototype.g=function(){return[]};W.prototype.u=function(c,f){var k=new W(this.i);f.c=P(new W(this.i),f.c);f.a=P(k,f.a);return[]};function bd(c){this.a={};this.i=c;this.f={};this.c={};D(cd,function(c){this.a[c]=[]},this)}var cd=[0,1];function dd(c,f){c.c[f.f()]=f;c.a[1].push(f)}function ed(c){D(cd,function(c){D(this.a[c],function(c){fd(this,c,[])},this)},c);return c.i}function fd(c,f,k){var n=f.f(),g=k.concat(n);if(-1!=k.indexOf(n))throw"Circular dependcy in compiler steps.  "+g.join("->");n in c.f||(D(f.g(),function(c){c in this.c&&fd(this,this.c[c],g)},c),c.f[n]=f.u(c.f,c.i))};function gd(){this.c={};this.g={};this.f=this.a=0}function hd(c){var f="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[c%52];for(c=Math.floor(c/52);0<c;)--c,f+="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"[c%62],c=Math.floor(c/62);return f}function id(c){return"_"+hd(c)}function jd(c,f){if(c.c[f])return c.c[f];for(var k=null;!k||k in c.g;)k=hd(c.a++);c.g[k]=!0;return c.c[f]=k}function kd(c,f){return c.c[f]||f}
+gd.prototype.clone=function(){var c=new gd;c.c=M(this.c);c.g=M(this.g);c.a=this.a;c.f=this.f;return c};function Y(c){this.c=[];this.K=[];this.a=new gd;this.h=null;this.m=0;this.D=c;this.J=[]}v(Y,N);function ld(c,f){if(!f)return!1;var k=f.w.qualifier;return(c.D||"uniform"!=k&&"attribute"!=k)&&!(f.id in c.J)}Y.prototype.ea=function(c){this.J=Oc(c);var f=[];c=Zb(c,c);for(var k in c){var n=c[k];ld(this,n)&&(n=n.w.qualifier,"varying"==n||"uniform"==n?jd(this.a,k):f.push(k))}this.v();D(f,function(c){jd(this.a,c)},this)};Y.prototype.beforeTransformRoot=Y.prototype.ea;Y.prototype.ba=function(c){this.h=c};
+Y.prototype.beforeTransformDeclarator=Y.prototype.ba;Y.prototype.Y=function(){this.h=null};Y.prototype.afterTransformDeclarator=Y.prototype.Y;Y.prototype.ha=function(c){var f=O(c);f.name=jd(this.a,c.name);return f};Y.prototype.transformParameter=Y.prototype.ha;Y.prototype.v=function(){this.K.push(this.a);this.a=this.a.clone()};Y.prototype.o=function(){this.m=Math.max(this.m,this.a.a);this.a=this.K.pop()};Y.prototype.fa=Y.prototype.v;Y.prototype.beforeTransformScope=Y.prototype.fa;Y.prototype.Z=Y.prototype.o;
+Y.prototype.afterTransformScope=Y.prototype.Z;Y.prototype.U=Y.prototype.o;Y.prototype.afterTransformRoot=Y.prototype.U;Y.prototype.ca=Y.prototype.v;Y.prototype.beforeTransformFunctionDeclaration=Y.prototype.ca;Y.prototype.S=Y.prototype.o;Y.prototype.afterTransformFunctionDeclaration=Y.prototype.S;Y.prototype.da=Y.prototype.v;Y.prototype.beforeTransformFunctionPrototype=Y.prototype.da;Y.prototype.T=Y.prototype.o;Y.prototype.afterTransformFunctionPrototype=Y.prototype.T;
+Y.prototype.X=function(c){var f=c.name.name,k;this.h&&(k=this.h.w.qualifier);ld(this,this.h)&&(f=jd(this.a,c.name.name));this.i&&this.h&&("attribute"==k?this.i.L[c.name.name].s=f:"uniform"==k&&(this.i.R[c.name.name].s=f));return c};Y.prototype.beforeTransformDeclaratorItem=Y.prototype.X;Y.prototype.ga=function(c){var f=kd(this.a,c.name);c.name!=f&&(c=O(c),c.name=f);return c};Y.prototype.transformIdentifier=Y.prototype.ga;Y.prototype.f=function(){return"VariableMinifier"};Y.prototype.g=function(){return[]};
+Y.prototype.u=function(c,f){var k=new Y(this.D);k.i=f;f.c=P(k,f.c);var n=new Y(this.D);n.i=f;n.a=k.a;f.a=P(n,f.a);return{vertexMaxId:k.m,fragmentMaxId:n.m}};function Z(){this.c=[];this.a=new gd}v(Z,N);var md={main:!0};Z.prototype.i=function(c){c.name in md||jd(this.a,c.name)};Z.prototype.beforeTransformFunctionDeclaration=Z.prototype.i;Z.prototype.h=function(c){var f=kd(this.a,c.name);f!=c.name&&(c=O(c),c.name=f);return c};Z.prototype.transformFunctionDeclaration=Z.prototype.h;Z.prototype.o=function(c){var f=kd(this.a,c.I);f!=c.I&&(c=O(c),c.I=f);return c};Z.prototype.transformFunctionCall=Z.prototype.o;Z.prototype.m=Z.prototype.i;
+Z.prototype.beforeTransformFunctionPrototype=Z.prototype.m;Z.prototype.v=Z.prototype.h;Z.prototype.transformFunctionPrototype=Z.prototype.v;Z.prototype.f=function(){return"Function Minifier"};Z.prototype.g=function(){return["VariableMinifier"]};Z.prototype.u=function(c,f){var k=0,n=0;"VariableMinifier"in c&&(k=c.VariableMinifier.vertexMaxId,n=c.VariableMinifier.fragmentMaxId);var g=new Z;g.a.a=k;k=new Z;f.c=P(g,f.c);k.a.a=n;f.a=P(k,f.a);return[]};function od(c,f,k,n){this.c=c;this.a=f;this.i=k;this.h=n}function pd(c){this.c=[];this.f=!1;this.g=c;this.a=!0}v(pd,N);function qd(c,f){c.a&&(c.a=0==ac(f,ia(function(c,f){if("function_call"==c.type||"operator"==c.type||"int"==c.type||"binary"==c.type||"unary"==c.type)return!1;if("identifier"==c.type){var g=this.g[c.name];if(g=da(g)&&g.a)this.f=!0;return!(g||0<f.length&&"function_call"==f.slice(-1)[0].type)}return!0},c)).length)}
+function rd(c,f){var k=z(c,"condition"),n=new pd(f),k=P(n,k),g="";n.a&&(g=sd(I(k),f),k=z(g,"condition"),qd(n,k));g={ja:n.a,G:n.f,value:0,na:g};if(n.a&&!n.f)try{g.value=Number(eval(I(k)))}catch(u){g.ja=!1}return g}var td=/([\.=;,\(\)\[\]{}\+\-:?!\|&<>\/\*]|[ \t\n]+)/g,ud=/[A-Za-z_][A-Za-z_0-9]*/g;function vd(c,f){this.name=c.identifier;this.arguments=c.l?c.l.map(function(c){return c.name}):null;this.c=c.pa.split(td).filter(function(c){return 0<c.length});this.a=f||null}
+function wd(c,f,k){if(c in k)return c;var n=f[c],g=c;if(da(n))if(n.a)g=n.a.s||n.name;else if(!n.arguments){var u=M(k);u[c]=!0;g=n.c.map(function(c){return wd(c,f,u)}).join("")}return g}
+function xd(c,f,k,n){for(var g in c){var u=f[c[g]];if(da(u)&&u.arguments){var B=c.slice(g).join(""),B=z(B,"macro_call_line");if(B.aa&&B.aa.l.length==u.arguments.length&&!(B.aa.oa.name in n)){var q=B.aa,x=M(n);x[B.aa.oa.name]=!0;var q=q.l.map(function(c){return sd(I(c),f,k,x)}),ha={},C;for(C in u.arguments)ha[u.arguments[C]]=q[C];u=sd(u.c.join(""),f,ha,x);B=sd(B.Ja,f,k,n);return sd(c.slice(0,g).join("")+u+B,f,k,n)}}}return c.join("")}
+function sd(c,f,k,n){k=k||{};n=n||{};var g=M(f),u;for(u in k)g[u]=new vd(z("#define "+u+" "+k[u],"preprocessor_define"));c=c.split(td).filter(function(c){return 0<c.length}).map(function(c){return wd(c,g,{})});return xd(c,f,k,n)}function yd(c,f,k){this.status=c;this.V=f;this.G=k}
+function zd(c,f,k){var n={};D(f,function(c){var f=z("#define "+c.a,"preprocessor_define");n[c.a]=new vd(f,c);c.s&&(f=z("#define "+c.s,"preprocessor_define"),n[c.s]=new vd(f,c))});c=I(c);var g=[new yd(1,!0,!1)],u=0,B=[];D(c.split(/\n/g),function(c){var f=g.slice(-1)[0],k=0==f.status;if(0!=c.search("#define")||k)if(0!=c.search("#undef")||k)if(0==c.search("#if")){f=c;c=z(f,"preprocessor_if");if(k)g.push(new yd(0,!1,!1)),f=!1;else{var C=!1,A=!1;if("#ifdef"==c.B||"#ifndef"==c.B){var H=c.value.match(ud)[0];
+if(!da(H))throw"Invalid "+c.B+": "+f;H=n[H];da(H)&&H.a?(C=!0,f=c.B+" "+(H.a.s||H.name)):A=da(H)==("#ifndef"==c.B)}else{A=rd(c.value,n);if(!A.ja)throw"Invalid "+c.B+": "+f;f="#if "+A.na;C=A.G;A=0==A.value}C?g.push(new yd(2,!1,!0)):g.push(new yd(A?0:1,!A,!1));C&&B.push(f);f=C}u+=f&&!k}else if(0==c.search("#else"))k=!1,0!=g.slice(-2,-1)[0].status&&(f.G&&!f.V?(k=!0,C=1,A=f.V,f=!0):0!=f.status||f.V?(C=0,A=!0,f=f.G):(C=1,A=!0,f=!1),g[g.length-1]=new yd(C,A,f)),k&&B.push(c);else if(0==c.search("#elif")){k=
+c;c=!1;if(0!=g.slice(-2,-1)[0].status){A=z(k,"preprocessor_else_if");C=rd(A.value,n);if(!C.ja)throw"Invalid "+A.B+": "+k;k="#elif "+C.na;A=0==C.value;f.G&&!f.V?(f=!0,C.G?(C=2,A=!1):A?(C=0,A=!1):(k="#else",C=1,A=!0)):f.V?(C=0,A=f.V,f=f.G):C.G?(k=k.replace(/^#elif/,"#if"),C=2,A=!1,c=f=!0):(C=A?0:1,A=!A,f=f.G);g[g.length-1]=new yd(C,A,f);f&&0!=C&&B.push(k)}u+=c}else 0==c.search("#endif")?(g.pop(),f.G&&(u--,B.push(c))):k||(f=sd(c,n),B.push(f));else{if(0<u)throw"Definitions can not be changed inside of Modes: "+
+c;f=z(c,"preprocessor_operator");delete n[f.value]}else{if(0<u)throw"Definitions can not be changed inside of Modes: "+c;f=z(c,"preprocessor_define");f=new vd(f);n[f.name]=f}});return z(B.join("\n"),k)}od.prototype.f=function(){return"Preprocessor"};od.prototype.g=function(){return["VariableMinifier"]};function Ad(c,f){var k="statements";for(k in c)if(c[k]===c.C)break;return P(new Rc(c,k,0,0,f.C),c)}
+od.prototype.u=function(c,f){var k=this.a,n=new gd;if(this.h)for(var g in f.g){var u=id(n.f++);f.g[g].s=u}if(this.i)for(g in f.f)u=id(n.f++),k.push(f.f[g].a+" "+u),f.f[g].s=u;n=this.c.map(function(c){var f=new Sc;f.a=c;return f}).concat(f.g);k=z(k.map(function(c){return"#define "+c}).join("\n"));f.c=zd(Ad(f.c,k),n,"vertex_start");f.a=zd(Ad(f.a,k),n,"fragment_start");return[]};var Bd={};ka("glslprep.SyntaxError",la.SyntaxError);ka("glslprep.Shader",Bd);Bd.VERTEX=0;Bd.FRAGMENT=1;ka("glslprep.parseGlsl",function(c,f){return z(c,0===f?"vertex_start":"fragment_start")});
+ka("glslprep.minifyGlsl",function(c,f){var k=new R;k.c=z(c[0],"vertex_start");k.a=z(c[1],"fragment_start");Vc(k);k=new bd(k);if(null!=f){var n=["GL_ES 1"],g=[],u;for(u in f){var B=f[u];null!=B?n.push(u+" "+B):g.push(u)}dd(k,new od(g,n,!1,!1))}dd(k,new V);dd(k,new W(!0));dd(k,new Y(!1));dd(k,new Z);dd(k,new U);dd(k,new T);k=ed(k);c[0]=I(k.c);c[1]=I(k.a);return c});})();
+

+ 1 - 1
examples/canvas_morphtargets_horse.html

@@ -62,7 +62,7 @@
 				light.position.set( -1, -1, -1 ).normalize();
 				scene.add( light );
 
-				var loader = new THREE.JSONLoader( true );
+				var loader = new THREE.JSONLoader();
 				loader.load( "models/animated/horse.js", function ( geometry ) {
 
 					mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x606060, morphTargets: true, overdraw: 0.5 } ) );

+ 1 - 0
examples/index.html

@@ -335,6 +335,7 @@
 				"webgl_postprocessing_godrays",
 				"webgl_postprocessing_crossfade",
 				"webgl_postprocessing_glitch",
+				"webgl_postprocessing_ssao",
 				"webgl_rtt",
 				"webgl_sandbox",
 				"webgl_shader",

+ 3 - 3
examples/js/MarchingCubes.js

@@ -18,7 +18,7 @@ THREE.MarchingCubes = function ( resolution, material, enableUvs, enableColors )
 	// prototype functions kill performance
 	// (tested and it was 4x slower !!!)
 
-	this.init = function( resolution ) {
+	this.init = function ( resolution ) {
 
 		this.resolution = resolution;
 
@@ -632,7 +632,7 @@ THREE.MarchingCubes = function ( resolution, material, enableUvs, enableColors )
 	// Updates
 	/////////////////////////////////////
 
-	this.reset = function() {
+	this.reset = function () {
 
 		var i;
 
@@ -647,7 +647,7 @@ THREE.MarchingCubes = function ( resolution, material, enableUvs, enableColors )
 
 	};
 
-	this.render = function( renderCallback ) {
+	this.render = function ( renderCallback ) {
 
 		this.begin();
 

+ 207 - 150
examples/js/controls/TransformControls.js

@@ -7,6 +7,7 @@
 
 	'use strict';
 
+
 	var GizmoMaterial = function ( parameters ) {
 
 		THREE.MeshBasicMaterial.call( this );
@@ -42,6 +43,7 @@
 	GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype );
 	GizmoMaterial.prototype.constructor = GizmoMaterial;
 
+
 	var GizmoLineMaterial = function ( parameters ) {
 
 		THREE.LineBasicMaterial.call( this );
@@ -77,11 +79,13 @@
 	GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype );
 	GizmoLineMaterial.prototype.constructor = GizmoLineMaterial;
 
+
+	var pickerMaterial = new GizmoMaterial( { visible: false, transparent: false } );
+
+
 	THREE.TransformGizmo = function () {
 
 		var scope = this;
-		var showPickers = false; //debug
-		var showActivePlane = false; //debug
 
 		this.init = function () {
 
@@ -98,8 +102,7 @@
 			//// PLANES
 
 			var planeGeometry = new THREE.PlaneBufferGeometry( 50, 50, 2, 2 );
-			var planeMaterial = new THREE.MeshBasicMaterial( { wireframe: true } );
-			planeMaterial.side = THREE.DoubleSide;
+			var planeMaterial = new THREE.MeshBasicMaterial( { visible: false, side: THREE.DoubleSide } );
 
 			var planes = {
 				"XY":   new THREE.Mesh( planeGeometry, planeMaterial ),
@@ -117,7 +120,6 @@
 				planes[i].name = i;
 				this.planes.add(planes[i]);
 				this.planes[i] = planes[i];
-				planes[i].visible = false;
 			}
 
 			//// HANDLES AND PICKERS
@@ -151,7 +153,9 @@
 			// reset Transformations
 
 			this.traverse(function ( child ) {
+
 				if (child instanceof THREE.Mesh) {
+
 					child.updateMatrix();
 
 					var tempGeometry = child.geometry.clone();
@@ -161,36 +165,33 @@
 					child.position.set( 0, 0, 0 );
 					child.rotation.set( 0, 0, 0 );
 					child.scale.set( 1, 1, 1 );
-				}
-			});
 
-		};
+				}
 
-		this.hide = function () {
-			this.traverse(function( child ) {
-				child.visible = false;
 			});
-		};
 
-		this.show = function () {
-			this.traverse(function( child ) {
-				child.visible = true;
-				if (child.parent == scope.pickers ) child.visible = showPickers;
-				if (child.parent == scope.planes ) child.visible = false;
-			});
-			this.activePlane.visible = showActivePlane;
 		};
 
 		this.highlight = function ( axis ) {
-			this.traverse(function( child ) {
+
+			this.traverse( function( child ) {
+
 				if ( child.material && child.material.highlight ) {
-					if ( child.name == axis ) {
+
+					if ( child.name === axis ) {
+
 						child.material.highlight( true );
+
 					} else {
+
 						child.material.highlight( false );
+
 					}
+
 				}
-			});
+
+			} );
+
 		};
 
 	};
@@ -204,13 +205,19 @@
 		var vec2 = new THREE.Vector3( 0, 1, 0 );
 		var lookAtMatrix = new THREE.Matrix4();
 
-		this.traverse(function(child) {
-			if ( child.name.search("E") != -1 ) {
+		this.traverse( function(child) {
+
+			if ( child.name.search("E") !== -1 ) {
+
 				child.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( eye, vec1, vec2 ) );
-			} else if ( child.name.search("X") != -1 || child.name.search("Y") != -1 || child.name.search("Z") != -1 ) {
+
+			} else if ( child.name.search("X") !== -1 || child.name.search("Y") !== -1 || child.name.search("Z") !== -1 ) {
+
 				child.quaternion.setFromEuler( rotation );
+
 			}
-		});
+
+		} );
 
 	};
 
@@ -235,54 +242,70 @@
 		lineZGeometry.addAttribute( 'position', new THREE.Float32Attribute( [ 0, 0, 0,  0, 0, 1 ], 3 ) );
 
 		this.handleGizmos = {
+
 			X: [
 				[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ],
 				[ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ]
 			],
+
 			Y: [
 				[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ],
 				[	new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ]
 			],
+
 			Z: [
 				[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ],
 				[ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ]
 			],
+
 			XYZ: [
 				[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, 0 ] ]
 			],
+
 			XY: [
 				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.15, 0.15, 0 ] ]
 			],
+
 			YZ: [
 				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ] ]
 			],
+
 			XZ: [
 				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.15, 0, 0.15 ], [ -Math.PI / 2, 0, 0 ] ]
 			]
+
 		};
 
 		this.pickerGizmos = {
+
 			X: [
-				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
 			],
+
 			Y: [
-				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0.6, 0 ] ]
+				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0.6, 0 ] ]
 			],
+
 			Z: [
-				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]
+				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]
 			],
+
 			XYZ: [
-				[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ]
+				[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), pickerMaterial ) ]
 			],
+
 			XY: [
-				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.2, 0.2, 0 ] ]
+				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0.2, 0.2, 0 ] ]
 			],
+
 			YZ: [
-				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ]
+				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ]
 			],
+
 			XZ: [
-				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.2, 0, 0.2 ], [ -Math.PI / 2, 0, 0 ] ]
+				[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0.2, 0, 0.2 ], [ -Math.PI / 2, 0, 0 ] ]
 			]
+
 		};
 
 		this.setActivePlane = function ( axis, eye ) {
@@ -290,31 +313,37 @@
 			var tempMatrix = new THREE.Matrix4();
 			eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) );
 
-			if ( axis == "X" ) {
+			if ( axis === "X" ) {
+
 				this.activePlane = this.planes[ "XY" ];
+
 				if ( Math.abs(eye.y) > Math.abs(eye.z) ) this.activePlane = this.planes[ "XZ" ];
+
 			}
 
-			if ( axis == "Y" ) {
+			if ( axis === "Y" ) {
+
 				this.activePlane = this.planes[ "XY" ];
+
 				if ( Math.abs(eye.x) > Math.abs(eye.z) ) this.activePlane = this.planes[ "YZ" ];
+
 			}
 
-			if ( axis == "Z" ) {
+			if ( axis === "Z" ) {
+
 				this.activePlane = this.planes[ "XZ" ];
+
 				if ( Math.abs(eye.x) > Math.abs(eye.y) ) this.activePlane = this.planes[ "YZ" ];
-			}
 
-			if ( axis == "XYZ" ) this.activePlane = this.planes[ "XYZE" ];
+			}
 
-			if ( axis == "XY" ) this.activePlane = this.planes[ "XY" ];
+			if ( axis === "XYZ" ) this.activePlane = this.planes[ "XYZE" ];
 
-			if ( axis == "YZ" ) this.activePlane = this.planes[ "YZ" ];
+			if ( axis === "XY" ) this.activePlane = this.planes[ "XY" ];
 
-			if ( axis == "XZ" ) this.activePlane = this.planes[ "XZ" ];
+			if ( axis === "YZ" ) this.activePlane = this.planes[ "YZ" ];
 
-			this.hide();
-			this.show();
+			if ( axis === "XZ" ) this.activePlane = this.planes[ "XZ" ];
 
 		};
 
@@ -334,63 +363,77 @@
 			var geometry = new THREE.BufferGeometry();
 			var vertices = [];
 			arc = arc ? arc : 1;
+
 			for ( var i = 0; i <= 64 * arc; ++ i ) {
-				if ( facing == 'x' ) vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );
-				if ( facing == 'y' ) vertices.push( Math.cos( i / 32 * Math.PI ) * radius, 0, Math.sin( i / 32 * Math.PI ) * radius );
-				if ( facing == 'z' ) vertices.push( Math.sin( i / 32 * Math.PI ) * radius, Math.cos( i / 32 * Math.PI ) * radius, 0 );
+
+				if ( facing === 'x' ) vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );
+				if ( facing === 'y' ) vertices.push( Math.cos( i / 32 * Math.PI ) * radius, 0, Math.sin( i / 32 * Math.PI ) * radius );
+				if ( facing === 'z' ) vertices.push( Math.sin( i / 32 * Math.PI ) * radius, Math.cos( i / 32 * Math.PI ) * radius, 0 );
+
 			}
+
 			geometry.addAttribute( 'position', new THREE.Float32Attribute( vertices, 3 ) );
 			return geometry;
+
 		};
 
 		this.handleGizmos = {
+
 			X: [
 				[ new THREE.Line( new CircleGeometry(1,'x',0.5), new GizmoLineMaterial( { color: 0xff0000 } ) ) ]
 			],
+
 			Y: [
 				[ new THREE.Line( new CircleGeometry(1,'y',0.5), new GizmoLineMaterial( { color: 0x00ff00 } ) ) ]
 			],
+
 			Z: [
 				[ new THREE.Line( new CircleGeometry(1,'z',0.5), new GizmoLineMaterial( { color: 0x0000ff } ) ) ]
 			],
+
 			E: [
 				[ new THREE.Line( new CircleGeometry(1.25,'z',1), new GizmoLineMaterial( { color: 0xcccc00 } ) ) ]
 			],
+
 			XYZE: [
 				[ new THREE.Line( new CircleGeometry(1,'z',1), new GizmoLineMaterial( { color: 0x787878 } ) ) ]
 			]
+
 		};
 
 		this.pickerGizmos = {
+
 			X: [
-				[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, -Math.PI / 2, -Math.PI / 2 ] ]
+				[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, -Math.PI / 2, -Math.PI / 2 ] ]
 			],
+
 			Y: [
-				[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ]
+				[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ]
 			],
+
 			Z: [
-				[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+				[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
 			],
+
 			E: [
-				[ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.12, 2, 24 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ) ]
+				[ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.12, 2, 24 ), pickerMaterial ) ]
 			],
+
 			XYZE: [
 				[ new THREE.Mesh( new THREE.Geometry() ) ]// TODO
 			]
+
 		};
 
 		this.setActivePlane = function ( axis ) {
 
-			if ( axis == "E" ) this.activePlane = this.planes[ "XYZE" ];
+			if ( axis === "E" ) this.activePlane = this.planes[ "XYZE" ];
 
-			if ( axis == "X" ) this.activePlane = this.planes[ "YZ" ];
+			if ( axis === "X" ) this.activePlane = this.planes[ "YZ" ];
 
-			if ( axis == "Y" ) this.activePlane = this.planes[ "XZ" ];
+			if ( axis === "Y" ) this.activePlane = this.planes[ "XZ" ];
 
-			if ( axis == "Z" ) this.activePlane = this.planes[ "XY" ];
-
-			this.hide();
-			this.show();
+			if ( axis === "Z" ) this.activePlane = this.planes[ "XY" ];
 
 		};
 
@@ -399,8 +442,10 @@
 			THREE.TransformGizmo.prototype.update.apply( this, arguments );
 
 			var group = {
+
 				handles: this["handles"],
 				pickers: this["pickers"],
+
 			};
 
 			var tempMatrix = new THREE.Matrix4();
@@ -424,22 +469,28 @@
 
 				tempQuaternion.setFromEuler( worldRotation );
 
-				if ( child.name == "X" ) {
+				if ( child.name === "X" ) {
+
 					quaternionX.setFromAxisAngle( unitX, Math.atan2( -eye.y, eye.z ) );
 					tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );
 					child.quaternion.copy( tempQuaternion );
+
 				}
 
-				if ( child.name == "Y" ) {
+				if ( child.name === "Y" ) {
+
 					quaternionY.setFromAxisAngle( unitY, Math.atan2( eye.x, eye.z ) );
 					tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );
 					child.quaternion.copy( tempQuaternion );
+
 				}
 
-				if ( child.name == "Z" ) {
+				if ( child.name === "Z" ) {
+
 					quaternionZ.setFromAxisAngle( unitZ, Math.atan2( eye.y, eye.x ) );
 					tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );
 					child.quaternion.copy( tempQuaternion );
+
 				}
 
 			});
@@ -474,36 +525,46 @@
 		lineZGeometry.addAttribute( 'position', new THREE.Float32Attribute( [ 0, 0, 0,  0, 0, 1 ], 3 ) );
 
 		this.handleGizmos = {
+
 			X: [
 				[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ],
 				[ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ]
 			],
+
 			Y: [
 				[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ],
 				[ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ]
 			],
+
 			Z: [
 				[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ],
 				[ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ]
 			],
+
 			XYZ: [
 				[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ]
 			]
+
 		};
 
 		this.pickerGizmos = {
+
 			X: [
-				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
 			],
+
 			Y: [
-				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0.6, 0 ] ]
+				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0.6, 0 ] ]
 			],
+
 			Z: [
-				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]
+				[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]
 			],
+
 			XYZ: [
-				[ new THREE.Mesh( new THREE.BoxGeometry( 0.4, 0.4, 0.4 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ]
+				[ new THREE.Mesh( new THREE.BoxGeometry( 0.4, 0.4, 0.4 ), pickerMaterial ) ]
 			]
+
 		};
 
 		this.setActivePlane = function ( axis, eye ) {
@@ -511,25 +572,22 @@
 			var tempMatrix = new THREE.Matrix4();
 			eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) );
 
-			if ( axis == "X" ) {
+			if ( axis === "X" ) {
 				this.activePlane = this.planes[ "XY" ];
 				if ( Math.abs(eye.y) > Math.abs(eye.z) ) this.activePlane = this.planes[ "XZ" ];
 			}
 
-			if ( axis == "Y" ) {
+			if ( axis === "Y" ) {
 				this.activePlane = this.planes[ "XY" ];
 				if ( Math.abs(eye.x) > Math.abs(eye.z) ) this.activePlane = this.planes[ "YZ" ];
 			}
 
-			if ( axis == "Z" ) {
+			if ( axis === "Z" ) {
 				this.activePlane = this.planes[ "XZ" ];
 				if ( Math.abs(eye.x) > Math.abs(eye.y) ) this.activePlane = this.planes[ "YZ" ];
 			}
 
-			if ( axis == "XYZ" ) this.activePlane = this.planes[ "XYZE" ];
-
-			this.hide();
-			this.show();
+			if ( axis === "XYZ" ) this.activePlane = this.planes[ "XYZE" ];
 
 		};
 
@@ -549,20 +607,8 @@
 
 		domElement = ( domElement !== undefined ) ? domElement : document;
 
-		this.gizmo = {};
-		this.gizmo["translate"] = new THREE.TransformGizmoTranslate();
-		this.gizmo["rotate"] = new THREE.TransformGizmoRotate();
-		this.gizmo["scale"] = new THREE.TransformGizmoScale();
-
-		this.add(this.gizmo["translate"]);
-		this.add(this.gizmo["rotate"]);
-		this.add(this.gizmo["scale"]);
-
-		this.gizmo["translate"].hide();
-		this.gizmo["rotate"].hide();
-		this.gizmo["scale"].hide();
-
 		this.object = undefined;
+		this.visible = false;
 		this.snap = null;
 		this.space = "world";
 		this.size = 1;
@@ -570,9 +616,24 @@
 
 		var scope = this;
 
-		var _dragging = false;
 		var _mode = "translate";
+		var _dragging = false;
 		var _plane = "XY";
+		var _gizmo = {
+
+			"translate": new THREE.TransformGizmoTranslate(),
+			"rotate": new THREE.TransformGizmoRotate(),
+			"scale": new THREE.TransformGizmoScale()
+		};
+
+		for (var type in _gizmo) {
+
+			var gizmoObj = _gizmo[type];
+
+			gizmoObj.visible = ( type === _mode );
+			this.add( gizmoObj );
+
+		}
 
 		var changeEvent = { type: "change" };
 		var mouseDownEvent = { type: "mouseDown" };
@@ -580,7 +641,7 @@
 		var objectChangeEvent = { type: "objectChange" };
 
 		var ray = new THREE.Raycaster();
-		var pointerVector = new THREE.Vector3();
+		var pointerVector = new THREE.Vector2();
 
 		var point = new THREE.Vector3();
 		var offset = new THREE.Vector3();
@@ -634,6 +695,7 @@
 		domElement.addEventListener( "touchleave", onPointerUp, false );
 
 		this.dispose = function () {
+
 			domElement.removeEventListener( "mousedown", onPointerDown );
 			domElement.removeEventListener( "touchstart", onPointerDown );
 
@@ -648,42 +710,32 @@
 			domElement.removeEventListener( "touchend", onPointerUp );
 			domElement.removeEventListener( "touchcancel", onPointerUp );
 			domElement.removeEventListener( "touchleave", onPointerUp );
+
 		};
 
 		this.attach = function ( object ) {
 
-			scope.object = object;
-
-			this.gizmo["translate"].hide();
-			this.gizmo["rotate"].hide();
-			this.gizmo["scale"].hide();
-			this.gizmo[_mode].show();
-
-			scope.update();
+			this.object = object;
+			this.visible = true;
+			this.update();
 
 		};
 
 		this.detach = function () {
 
-			scope.object = undefined;
+			this.object = undefined;
+			this.visible = false;
 			this.axis = null;
 
-			this.gizmo["translate"].hide();
-			this.gizmo["rotate"].hide();
-			this.gizmo["scale"].hide();
-
 		};
 
 		this.setMode = function ( mode ) {
 
 			_mode = mode ? mode : _mode;
 
-			if ( _mode == "scale" ) scope.space = "local";
+			if ( _mode === "scale" ) scope.space = "local";
 
-			this.gizmo["translate"].hide();
-			this.gizmo["rotate"].hide();
-			this.gizmo["scale"].hide();
-			this.gizmo[_mode].show();
+			for (var type in _gizmo) _gizmo[type].visible = ( type === _mode );
 
 			this.update();
 			scope.dispatchEvent( changeEvent );
@@ -730,13 +782,17 @@
 
 			eye.copy( camPosition ).sub( worldPosition ).normalize();
 
-			if ( scope.space == "local" )
-				this.gizmo[_mode].update( worldRotation, eye );
+			if ( scope.space === "local" ) {
 
-			else if ( scope.space == "world" )
-				this.gizmo[_mode].update( new THREE.Euler(), eye );
+				_gizmo[_mode].update( worldRotation, eye );
 
-			this.gizmo[_mode].highlight( scope.axis );
+			} else if ( scope.space === "world" ) {
+
+				_gizmo[_mode].update( new THREE.Euler(), eye );
+
+			}
+
+			_gizmo[_mode].highlight( scope.axis );
 
 		};
 
@@ -746,7 +802,7 @@
 
 			var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
 
-			var intersect = intersectObjects( pointer, scope.gizmo[_mode].pickers.children );
+			var intersect = intersectObjects( pointer, _gizmo[_mode].pickers.children );
 
 			var axis = null;
 
@@ -776,7 +832,7 @@
 
 			if ( pointer.button === 0 || pointer.button === undefined ) {
 
-				var intersect = intersectObjects( pointer, scope.gizmo[_mode].pickers.children );
+				var intersect = intersectObjects( pointer, _gizmo[_mode].pickers.children );
 
 				if ( intersect ) {
 
@@ -791,9 +847,9 @@
 
 					eye.copy( camPosition ).sub( worldPosition ).normalize();
 
-					scope.gizmo[_mode].setActivePlane( scope.axis, eye );
+					_gizmo[_mode].setActivePlane( scope.axis, eye );
 
-					var planeIntersect = intersectObjects( pointer, [ scope.gizmo[_mode].activePlane ] );
+					var planeIntersect = intersectObjects( pointer, [ _gizmo[_mode].activePlane ] );
 
 					if ( planeIntersect ) {
 
@@ -824,7 +880,7 @@
 
 			var pointer = event.changedTouches ? event.changedTouches[0] : event;
 
-			var planeIntersect = intersectObjects( pointer, [ scope.gizmo[_mode].activePlane ] );
+			var planeIntersect = intersectObjects( pointer, [ _gizmo[_mode].activePlane ] );
 
 			if ( planeIntersect === false ) return;
 
@@ -833,18 +889,18 @@
 
 			point.copy( planeIntersect.point );
 
-			if ( _mode == "translate" ) {
+			if ( _mode === "translate" ) {
 
 				point.sub( offset );
 				point.multiply(parentScale);
 
-				if ( scope.space == "local" ) {
+				if ( scope.space === "local" ) {
 
 					point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );
 
-					if ( scope.axis.search("X") == -1 ) point.x = 0;
-					if ( scope.axis.search("Y") == -1 ) point.y = 0;
-					if ( scope.axis.search("Z") == -1 ) point.z = 0;
+					if ( scope.axis.search("X") === -1 ) point.x = 0;
+					if ( scope.axis.search("Y") === -1 ) point.y = 0;
+					if ( scope.axis.search("Z") === -1 ) point.z = 0;
 
 					point.applyMatrix4( oldRotationMatrix );
 
@@ -853,11 +909,11 @@
 
 				}
 
-				if ( scope.space == "world" || scope.axis.search("XYZ") != -1 ) {
+				if ( scope.space === "world" || scope.axis.search("XYZ") !== -1 ) {
 
-					if ( scope.axis.search("X") == -1 ) point.x = 0;
-					if ( scope.axis.search("Y") == -1 ) point.y = 0;
-					if ( scope.axis.search("Z") == -1 ) point.z = 0;
+					if ( scope.axis.search("X") === -1 ) point.x = 0;
+					if ( scope.axis.search("Y") === -1 ) point.y = 0;
+					if ( scope.axis.search("Z") === -1 ) point.z = 0;
 
 					point.applyMatrix4( tempMatrix.getInverse( parentRotationMatrix ) );
 
@@ -868,20 +924,20 @@
 
 				if ( scope.snap !== null ) {
 
-					if ( scope.axis.search("X") != -1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snap ) * scope.snap;
-					if ( scope.axis.search("Y") != -1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snap ) * scope.snap;
-					if ( scope.axis.search("Z") != -1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snap ) * scope.snap;
+					if ( scope.axis.search("X") !== -1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snap ) * scope.snap;
+					if ( scope.axis.search("Y") !== -1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snap ) * scope.snap;
+					if ( scope.axis.search("Z") !== -1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snap ) * scope.snap;
 
 				}
 
-			} else if ( _mode == "scale" ) {
+			} else if ( _mode === "scale" ) {
 
 				point.sub( offset );
 				point.multiply(parentScale);
 
-				if ( scope.space == "local" ) {
+				if ( scope.space === "local" ) {
 
-					if ( scope.axis == "XYZ") {
+					if ( scope.axis === "XYZ") {
 
 						scale = 1 + ( ( point.y ) / 50 );
 
@@ -893,22 +949,22 @@
 
 						point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );
 
-						if ( scope.axis == "X" ) scope.object.scale.x = oldScale.x * ( 1 + point.x / 50 );
-						if ( scope.axis == "Y" ) scope.object.scale.y = oldScale.y * ( 1 + point.y / 50 );
-						if ( scope.axis == "Z" ) scope.object.scale.z = oldScale.z * ( 1 + point.z / 50 );
+						if ( scope.axis === "X" ) scope.object.scale.x = oldScale.x * ( 1 + point.x / 50 );
+						if ( scope.axis === "Y" ) scope.object.scale.y = oldScale.y * ( 1 + point.y / 50 );
+						if ( scope.axis === "Z" ) scope.object.scale.z = oldScale.z * ( 1 + point.z / 50 );
 
 					}
 
 				}
 
-			} else if ( _mode == "rotate" ) {
+			} else if ( _mode === "rotate" ) {
 
 				point.sub( worldPosition );
-				point.multiply(parentScale);
-				tempVector.copy(offset).sub( worldPosition );
-				tempVector.multiply(parentScale);
+				point.multiply( parentScale );
+				tempVector.copy( offset ).sub( worldPosition );
+				tempVector.multiply( parentScale );
 
-				if ( scope.axis == "E" ) {
+				if ( scope.axis === "E" ) {
 
 					point.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) );
 					tempVector.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) );
@@ -926,7 +982,7 @@
 
 					scope.object.quaternion.copy( tempQuaternion );
 
-				} else if ( scope.axis == "XYZE" ) {
+				} else if ( scope.axis === "XYZE" ) {
 
 					quaternionE.setFromEuler( point.clone().cross(tempVector).normalize() ); // rotation axis
 
@@ -939,7 +995,7 @@
 
 					scope.object.quaternion.copy( tempQuaternion );
 
-				} else if ( scope.space == "local" ) {
+				} else if ( scope.space === "local" ) {
 
 					point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );
 
@@ -953,13 +1009,13 @@
 					quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
 					quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
 
-					if ( scope.axis == "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX );
-					if ( scope.axis == "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY );
-					if ( scope.axis == "Z" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ );
+					if ( scope.axis === "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX );
+					if ( scope.axis === "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY );
+					if ( scope.axis === "Z" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ );
 
 					scope.object.quaternion.copy( quaternionXYZ );
 
-				} else if ( scope.space == "world" ) {
+				} else if ( scope.space === "world" ) {
 
 					rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) );
 					offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) );
@@ -971,9 +1027,9 @@
 					quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
 					quaternionXYZ.setFromRotationMatrix( worldRotationMatrix );
 
-					if ( scope.axis == "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );
-					if ( scope.axis == "Y" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );
-					if ( scope.axis == "Z" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );
+					if ( scope.axis === "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );
+					if ( scope.axis === "Y" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );
+					if ( scope.axis === "Z" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );
 
 					tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ );
 
@@ -992,9 +1048,12 @@
 		function onPointerUp( event ) {
 
 			if ( _dragging && ( scope.axis !== null ) ) {
+
 				mouseUpEvent.mode = _mode;
 				scope.dispatchEvent( mouseUpEvent )
+
 			}
+
 			_dragging = false;
 			onPointerHover( event );
 
@@ -1006,10 +1065,8 @@
 			var x = ( pointer.clientX - rect.left ) / rect.width;
 			var y = ( pointer.clientY - rect.top ) / rect.height;
 
-			pointerVector.set( ( x * 2 ) - 1, - ( y * 2 ) + 1, 0.5 );
-			pointerVector.unproject( camera );
-
-			ray.set( camPosition, pointerVector.sub( camPosition ).normalize() );
+			pointerVector.set( ( x * 2 ) - 1, - ( y * 2 ) + 1 );
+			ray.setFromCamera( pointerVector, camera );
 
 			var intersections = ray.intersectObjects( objects, true );
 			return intersections[0] ? intersections[0] : false;

+ 852 - 958
examples/js/loaders/AWDLoader.js

@@ -3,98 +3,79 @@
  * Date: 09/12/2013 17:21
  */
 
-THREE.AWDLoader = (function () {
-
-
+(function (){
 
 	var UNCOMPRESSED  = 0,
-      DEFLATE       = 1,
-      LZMA          = 2,
-
-      AWD_FIELD_INT8      = 1,
-      AWD_FIELD_INT16     = 2,
-      AWD_FIELD_INT32     = 3,
-      AWD_FIELD_UINT8     = 4,
-      AWD_FIELD_UINT16    = 5,
-      AWD_FIELD_UINT32    = 6,
-      AWD_FIELD_FLOAT32   = 7,
-      AWD_FIELD_FLOAT64   = 8,
-      AWD_FIELD_BOOL      = 21,
-      AWD_FIELD_COLOR     = 22,
-      AWD_FIELD_BADDR     = 23,
-      AWD_FIELD_STRING    = 31,
-      AWD_FIELD_BYTEARRAY = 32,
-      AWD_FIELD_VECTOR2x1 = 41,
-      AWD_FIELD_VECTOR3x1 = 42,
-      AWD_FIELD_VECTOR4x1 = 43,
-      AWD_FIELD_MTX3x2    = 44,
-      AWD_FIELD_MTX3x3    = 45,
-      AWD_FIELD_MTX4x3    = 46,
-      AWD_FIELD_MTX4x4    = 47,
-
-      BOOL       = 21,
-      COLOR      = 22,
-      BADDR      = 23,
-
-      INT8    = 1,
-      INT16   = 2,
-      INT32   = 3,
-      UINT8   = 4,
-      UINT16  = 5,
-      UINT32  = 6,
-      FLOAT32 = 7,
-      FLOAT64 = 8;
-
+			DEFLATE       = 1,
+			LZMA          = 2,
+
+			AWD_FIELD_INT8      = 1,
+			AWD_FIELD_INT16     = 2,
+			AWD_FIELD_INT32     = 3,
+			AWD_FIELD_UINT8     = 4,
+			AWD_FIELD_UINT16    = 5,
+			AWD_FIELD_UINT32    = 6,
+			AWD_FIELD_FLOAT32   = 7,
+			AWD_FIELD_FLOAT64   = 8,
+			AWD_FIELD_BOOL      = 21,
+			AWD_FIELD_COLOR     = 22,
+			AWD_FIELD_BADDR     = 23,
+			AWD_FIELD_STRING    = 31,
+			AWD_FIELD_BYTEARRAY = 32,
+			AWD_FIELD_VECTOR2x1 = 41,
+			AWD_FIELD_VECTOR3x1 = 42,
+			AWD_FIELD_VECTOR4x1 = 43,
+			AWD_FIELD_MTX3x2    = 44,
+			AWD_FIELD_MTX3x3    = 45,
+			AWD_FIELD_MTX4x3    = 46,
+			AWD_FIELD_MTX4x4    = 47,
+
+			BOOL       = 21,
+			COLOR      = 22,
+			BADDR      = 23,
+
+			INT8    = 1,
+			INT16   = 2,
+			INT32   = 3,
+			UINT8   = 4,
+			UINT16  = 5,
+			UINT32  = 6,
+			FLOAT32 = 7,
+			FLOAT64 = 8;
 
 	var littleEndian = true;
 
-  // ResourcesLoader
-  // =============
-  // handle loading for external resources
-	function ResourcesLoader( awdUrl ) {
-
-		this._baseDir = awdUrl.substr( 0, awdUrl.lastIndexOf( '/' ) + 1 );
-
-		this._loadingManager = new THREE.LoadingManager();
-
-	}
-
-	ResourcesLoader.prototype = {
-
-    loadTexture : function( path ) {
-	var tex = new THREE.Texture();
-
-	var loader = new THREE.ImageLoader( this._loadingManager );
-
-	loader.load( this._baseDir + path, function( image ) {
-		tex.image = image;
-		tex.needsUpdate = true;
-	});
-
-	return tex;
-
-    }
-  };
-
-
-
-	function Block() {
+	function Block(){
 		this.id = 0;
 		this.data = null;
 	}
 
+	AWDProperties = function(){}
 
+	AWDProperties.prototype = {
+		set : function(key, value)
+		{
+			this[key] = value;
+		},
+
+		get : function(key, fallback)
+		{
+			if ( this.hasOwnProperty(key) )
+				return this[key];
+			else return fallback;
+		}
+	}
 
-	function AWDLoader( showStatus ) {
+	THREE.AWDLoader = function ( manager ) {
 
-		THREE.Loader.call( this, showStatus );
+		this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 		this.trunk = new THREE.Object3D();
 
 		this.materialFactory = undefined;
 
-		this._resourceLoader = null;
-		this._url = '';
+		this._url     = '';
+		this._baseDir = '';
 
 		this._data;
 		this._ptr = 0;
@@ -105,1118 +86,1031 @@ THREE.AWDLoader = (function () {
 		this._compression = 0;
 		this._bodylen = 0xFFFFFFFF;
 
-
 		this._blocks = [ new Block() ];
 
 		this._accuracyMatrix  = false;
 		this._accuracyGeo     = false;
 		this._accuracyProps   = false;
 
-
-	}
-
-
-	AWDLoader.prototype = Object.create( THREE.Loader.prototype );
-	AWDLoader.prototype.constructor = AWDLoader;
-
-	AWDLoader.prototype.load = function ( url, callback ) {
-
-		var that = this;
-		this._url = url;
-		var xhr = new XMLHttpRequest();
-		xhr.open( "GET", url, true );
-		xhr.responseType = 'arraybuffer';
-
-		xhr.onreadystatechange = function () {
-
-			if ( xhr.readyState == 4 ) {
-
-				if ( xhr.status == 200 || xhr.status == 0 ) {
-					that.parse( xhr.response );
-					callback( that.trunk );
-
-				} else {
-
-					console.error( 'AWDLoader: Couldn\'t load ' + url + ' (' + xhr.status + ')' );
-
-				}
-
-			}
-
-		};
-
-		xhr.send( null );
-
 	};
 
-	AWDLoader.prototype.parse = function ( data ) {
+	THREE.AWDLoader.prototype = {
 
-		var blen = data.byteLength;
+		constructor: THREE.AWDLoader,
 
-		this._ptr = 0;
-		this._data = new DataView( data );
+		load: function ( url, onLoad, onProgress, onError ) {
 
-		this._parseHeader( );
+			var scope = this;
 
-		if ( this._compression != 0  ) {
-			console.error( 'compressed AWD not supported' );
-		}
+			this._url = url;
+			this._baseDir = url.substr( 0, url.lastIndexOf( '/' )+1 );
 
-		if (!this._streaming && this._bodylen != data.byteLength - this._ptr ) {
-			console.error('AWDLoader: body len does not match file length', this._bodylen,  blen - this._ptr);
-		}
+			var loader = new THREE.XHRLoader( this.manager );
+			loader.setCrossOrigin( this.crossOrigin );
+			loader.setResponseType('arraybuffer');
+			loader.load( url, function ( text ) {
 
-		while ( this._ptr < blen ) {
-			this.parseNextBlock();
-		}
+				onLoad( scope.parse(text) );
 
-		return this.trunk;
+			}, onProgress, onError );
 
-	};
+		},
 
+		setCrossOrigin: function ( value ) {
 
+			this.crossOrigin = value;
 
-	AWDLoader.prototype.parseNextBlock = function ( ) {
-
-		var assetData,
-        ns, type, len, block,
-        blockId = this.readU32(),
-        ns      = this.readU8(),
-        type    = this.readU8(),
-        flags   = this.readU8(),
-        len     = this.readU32();
-
-
-		switch (type) {
-			case 1:
-				assetData = this.parseMeshData(len);
-				break;
-			case 22:
-				assetData = this.parseContainer(len);
-				break;
-			case 23:
-				assetData = this.parseMeshInstance(len);
-				break;
-			case 81:
-				assetData = this.parseMaterial(len);
-				break;
-			case 82:
-				assetData = this.parseTexture(len);
-				break;
-			case 101:
-				assetData = this.parseSkeleton(len);
-				break;
-
-//      case 111:
-//        assetData = this.parseMeshPoseAnimation(len, true);
-//        break;
-			case 112:
-				assetData = this.parseMeshPoseAnimation(len, false);
-				break;
-			case 113:
-				assetData = this.parseVertexAnimationSet(len);
-				break;
-			case 102:
-				assetData = this.parseSkeletonPose(len);
-				break;
-			case 103:
-				assetData = this.parseSkeletonAnimation(len);
-				break;
-			case 122:
-				assetData = this.parseAnimatorSet(len);
-				break;
-      // case 121:
-      // 	assetData = parseUVAnimation(len);
-      // 	break;
-			default:
-        //debug('Ignoring block!',type, len);
-				this._ptr += len;
-				break;
-		}
+		},
 
+		parse: function ( data ) {
 
-    // Store block reference for later use
-		this._blocks[blockId] = block = new Block();
-		block.data = assetData;
-		block.id = blockId;
+			var blen = data.byteLength;
 
-	};
+			this._ptr = 0;
+			this._data = new DataView( data );
 
+			this._parseHeader( );
 
-	AWDLoader.prototype._parseHeader = function () {
+			if( this._compression != 0  ) {
+				console.error( 'compressed AWD not supported' );
+			}
 
-		var version = this._version,
-        awdmagic =
-            ( this.readU8()<<16)
-        |   ( this.readU8()<<8 )
-        |     this.readU8();
+			if (!this._streaming && this._bodylen != data.byteLength - this._ptr ) {
+				console.error('AWDLoader: body len does not match file length', this._bodylen ,  blen - this._ptr);
+			}
 
-		if ( awdmagic != 4282180 )
-      throw new Error( "AWDLoader - bad magic" );
+			while ( this._ptr < blen ) {
+				this.parseNextBlock();
+			}
 
-		version[0] = this.readU8();
-		version[1] = this.readU8();
+			return this.trunk;
+
+		},
+
+		parseNextBlock: function() {
+
+			var assetData,
+					ns, type, len, block,
+					blockId = this.readU32(),
+					ns      = this.readU8(),
+					type    = this.readU8(),
+					flags   = this.readU8(),
+					len     = this.readU32();
+
+
+			switch (type) {
+				case 1:
+					assetData = this.parseMeshData(len);
+					break;
+				case 22:
+					assetData = this.parseContainer(len);
+					break;
+				case 23:
+					assetData = this.parseMeshInstance(len);
+					break;
+				case 81:
+					assetData = this.parseMaterial(len);
+					break;
+				case 82:
+					assetData = this.parseTexture(len);
+					break;
+				case 101:
+					assetData = this.parseSkeleton(len);
+					break;
+
+	//      case 111:
+	//        assetData = this.parseMeshPoseAnimation(len, true);
+	//        break;
+				case 112:
+					assetData = this.parseMeshPoseAnimation(len, false);
+					break;
+				case 113:
+					assetData = this.parseVertexAnimationSet(len);
+					break;
+				case 102:
+					assetData = this.parseSkeletonPose(len);
+					break;
+				case 103:
+					assetData = this.parseSkeletonAnimation(len);
+					break;
+				case 122:
+					assetData = this.parseAnimatorSet(len);
+					break;
+				// case 121:
+				//  assetData = parseUVAnimation(len);
+				//  break;
+				default:
+					//debug('Ignoring block!',type, len);
+					this._ptr += len;
+					break;
+			}
 
-		var flags = this.readU16();
 
-		this._streaming = (flags & 0x1) == 0x1;
+			// Store block reference for later use
+			this._blocks[blockId] = block = new Block();
+			block.data = assetData;
+			block.id = blockId;
 
-		if ((version[0] === 2) && (version[1] === 1)) {
-			this._accuracyMatrix =  (flags & 0x2) === 0x2;
-			this._accuracyGeo =     (flags & 0x4) === 0x4;
-			this._accuracyProps =   (flags & 0x8) === 0x8;
-		}
 
-		this._geoNrType     = this._accuracyGeo     ? FLOAT64 : FLOAT32;
-		this._matrixNrType  = this._accuracyMatrix  ? FLOAT64 : FLOAT32;
-		this._propsNrType   = this._accuracyProps   ? FLOAT64 : FLOAT32;
+		},
 
-		this._optimized_for_accuracy 	= (flags & 0x2) === 0x2;
+		_parseHeader: function () {
 
-		this._compression = this.readU8();
-		this._bodylen = this.readU32();
+			var version = this._version,
+					awdmagic =
+							( this.readU8()<<16)
+					|   ( this.readU8()<<8 )
+					|     this.readU8();
 
+			if( awdmagic != 4282180 )
+				throw new Error( "AWDLoader - bad magic" );
 
-	};
+			version[0] = this.readU8();
+			version[1] = this.readU8();
 
+			var flags = this.readU16();
 
-	AWDLoader.prototype.parseContainer = function ( len ) {
-		var parent,
-        ctr     = new THREE.Object3D(),
-        par_id  = this.readU32(),
-        mtx     = this.parseMatrix4();
+			this._streaming = (flags & 0x1) == 0x1;
 
-		ctr.name = this.readUTF();
-		ctr.applyMatrix( mtx );
+			if ((version[0] === 2) && (version[1] === 1)) {
+				this._accuracyMatrix =  (flags & 0x2) === 0x2;
+				this._accuracyGeo =     (flags & 0x4) === 0x4;
+				this._accuracyProps =   (flags & 0x8) === 0x8;
+			}
 
-		parent = this._blocks[par_id].data || this.trunk;
-		parent.add(ctr);
+			this._geoNrType     = this._accuracyGeo     ? FLOAT64 : FLOAT32;
+			this._matrixNrType  = this._accuracyMatrix  ? FLOAT64 : FLOAT32;
+			this._propsNrType   = this._accuracyProps   ? FLOAT64 : FLOAT32;
 
-		this.parseProperties({
-      1:this._matrixNrType,
-      2:this._matrixNrType,
-      3:this._matrixNrType,
-      4:UINT8
-    });
+			this._optimized_for_accuracy  = (flags & 0x2) === 0x2;
 
-		ctr.extra = this.parseUserAttributes();
+			this._compression = this.readU8();
+			this._bodylen = this.readU32();
 
-		return ctr;
-	};
+		},
 
+		parseContainer: function ( len ) {
+			var parent,
+					ctr     = new THREE.Object3D(),
+					par_id  = this.readU32(),
+					mtx     = this.parseMatrix4();
 
+			ctr.name = this.readUTF();
+			ctr.applyMatrix( mtx );
 
-	AWDLoader.prototype.parseMeshInstance = function ( len ) {
-		var name,
-        mesh, geometries, meshLen, meshes,
-        par_id, data_id,
-        mtx,
-        materials, mat, mat_id,
-        num_materials,
-        materials_parsed,
-        parent,
-        i;
+			parent = this._blocks[par_id].data || this.trunk;
+			parent.add(ctr);
 
-		par_id        = this.readU32();
-		mtx           = this.parseMatrix4();
-		name          = this.readUTF();
-		data_id       = this.readU32();
-		num_materials = this.readU16();
+			this.parseProperties({
+				1:this._matrixNrType,
+				2:this._matrixNrType,
+				3:this._matrixNrType,
+				4:UINT8
+			});
 
-		geometries = this.getBlock( data_id );
+			ctr.extra = this.parseUserAttributes();
 
-		materials = [];
-		materials_parsed = 0;
+			return ctr;
+		},
 
-		for ( i = 0; i < num_materials; i ++) {
-			mat_id = this.readU32();
-			mat = this.getBlock( mat_id );
-			materials.push( mat );
-		}
+		parseMeshInstance: function ( len ) {
+			var name,
+					mesh, geometries, meshLen, meshes,
+					par_id, data_id,
+					mtx,
+					materials, mat, mat_id,
+					num_materials,
+					materials_parsed,
+					parent,
+					i;
 
+			par_id        = this.readU32();
+			mtx           = this.parseMatrix4();
+			name          = this.readUTF();
+			data_id       = this.readU32();
+			num_materials = this.readU16();
 
+			geometries = this.getBlock( data_id );
 
-		meshLen = geometries.length;
-		meshes = [];
+			materials = [];
+			materials_parsed = 0;
 
-    // TODO : BufferGeometry don't support "geometryGroups" for now.
-    // so we create sub meshes for each groups
-		if ( meshLen  > 1 ) {
-			mesh = new THREE.Object3D();
-			for ( i = 0; i < meshLen; i ++) {
-				var sm = new THREE.Mesh( geometries[i] );
-				meshes.push( sm );
-				mesh.add( sm );
+			for ( i = 0; i < num_materials; i++) {
+				mat_id = this.readU32();
+				mat = this.getBlock( mat_id );
+				materials.push( mat );
 			}
-		}
-		else {
-			mesh = new THREE.Mesh( geometries[0] );
-			meshes.push( mesh );
-		}
-
-		mesh.applyMatrix( mtx );
-		mesh.name = name;
-
-
-		parent = this.getBlock( par_id ) || this.trunk;
-		parent.add( mesh );
-
-
-		var matLen = materials.length;
-		var maxLen = Math.max( meshLen, matLen);
-		for ( i = 0; i < maxLen; i ++ )
-      meshes[ i%meshLen ].material = materials[ i % matLen ];
-
-
-    // Ignore for now
-		this.parseProperties( null );
-		mesh.extra = this.parseUserAttributes();
-
-		return mesh;
-	};
 
+			meshLen = geometries.length;
+			meshes = [];
+
+			// TODO : BufferGeometry don't support "geometryGroups" for now.
+			// so we create sub meshes for each groups
+			if( meshLen  > 1 ) {
+				mesh = new THREE.Object3D();
+				for ( i = 0; i < meshLen; i++) {
+					var sm = new THREE.Mesh( geometries[i] );
+					meshes.push( sm );
+					mesh.add( sm );
+				}
+			}
+			else {
+				mesh = new THREE.Mesh( geometries[0] );
+				meshes.push( mesh );
+			}
 
+			mesh.applyMatrix( mtx );
+			mesh.name = name;
 
-	AWDLoader.prototype.parseMaterial = function ( len ) {
-		var name,
-        type,
-        props,
-        mat,
-        attributes,
-        finalize,
-        num_methods,
-        methods_parsed;
 
-		name        = this.readUTF();
-		type        = this.readU8();
-		num_methods = this.readU8();
+			parent = this.getBlock( par_id ) || this.trunk;
+			parent.add( mesh );
 
-    //log( "AWDLoader parseMaterial ",name )
 
-    // Read material numerical properties
-    // (1=color, 2=bitmap url, 11=alpha_blending, 12=alpha_threshold, 13=repeat)
-		props = this.parseProperties({
-      1:  AWD_FIELD_INT32,
-      2:  AWD_FIELD_BADDR,
-      11: AWD_FIELD_BOOL,
-      12: AWD_FIELD_FLOAT32,
-      13: AWD_FIELD_BOOL
-    });
+			var matLen = materials.length;
+			var maxLen = Math.max( meshLen, matLen);
+			for( i = 0; i< maxLen; i++ )
+				meshes[ i%meshLen ].material = materials[ i % matLen ];
 
-		methods_parsed = 0;
 
-		while ( methods_parsed < num_methods ) {
-			var method_type = this.readU16();
+			// Ignore for now
 			this.parseProperties( null );
-			this.parseUserAttributes();
-		}
-
-		attributes = this.parseUserAttributes();
-
-		if ( this.materialFactory !== undefined ) {
-			mat = this.materialFactory( name );
-			if ( mat ) return mat;
-		}
+			mesh.extra = this.parseUserAttributes();
+
+			return mesh;
+		},
+
+		parseMaterial: function ( len ) {
+			var name,
+					type,
+					props,
+					mat,
+					attributes,
+					finalize,
+					num_methods,
+					methods_parsed;
+
+			name        = this.readUTF();
+			type        = this.readU8();
+			num_methods = this.readU8();
+
+			//log( "AWDLoader parseMaterial ",name )
+
+			// Read material numerical properties
+			// (1=color, 2=bitmap url, 11=alpha_blending, 12=alpha_threshold, 13=repeat)
+			props = this.parseProperties({
+				1:  AWD_FIELD_INT32,
+				2:  AWD_FIELD_BADDR,
+				11: AWD_FIELD_BOOL,
+				12: AWD_FIELD_FLOAT32,
+				13: AWD_FIELD_BOOL
+			});
+
+			methods_parsed = 0;
+
+			while( methods_parsed < num_methods ) {
+				var method_type = this.readU16();
+				this.parseProperties( null );
+				this.parseUserAttributes();
+			}
 
-		mat = new THREE.MeshPhongMaterial();
+			attributes = this.parseUserAttributes();
 
-		if (type === 1) { // Color material
-			mat.color.setHex( props.get(1, 0xcccccc) );
-		}
-    else if (type === 2) { // Bitmap material
-	var tex_addr = props.get(2, 0);
-	mat.map = this.getBlock( tex_addr );
-    }
+			if( this.materialFactory !== undefined ) {
+				mat = this.materialFactory( name );
+				if( mat ) return mat;
+			}
 
-		mat.extra = attributes;
-		mat.alphaThreshold = props.get(12, 0.0);
-		mat.repeat = props.get(13, false);
+			mat = new THREE.MeshPhongMaterial();
 
+			if (type === 1) { // Color material
+				mat.color.setHex( props.get(1, 0xcccccc) );
+			}
+			else if (type === 2) { // Bitmap material
+				var tex_addr = props.get(2, 0);
+				mat.map = this.getBlock( tex_addr );
+			}
 
-		return mat;
-	};
+			mat.extra = attributes;
+			mat.alphaThreshold = props.get(12, 0.0);
+			mat.repeat = props.get(13, false);
 
 
+			return mat;
+		},
 
+		parseTexture: function( len ) {
+			var name = this.readUTF(),
+					type = this.readU8(),
+					asset,
+					data_len;
 
-	AWDLoader.prototype.parseTexture = function( len ) {
+			// External
+			if (type === 0) {
+				data_len = this.readU32();
+				var url = this.readUTFBytes(data_len);
+				console.log( url );
 
+				asset = this.loadTexture( url );
+			} else {
+				// embed texture not supported
+			}
+			// Ignore for now
+			this.parseProperties( null );
 
-		var name = this.readUTF(),
-        type = this.readU8(),
-        asset,
-        data_len;
+			this.parseUserAttributes();
+			return asset;
+		},
 
-    // External
-		if (type === 0) {
-			data_len = this.readU32();
-			var url = this.readUTFBytes(data_len);
-			console.log( url );
+		loadTexture: function( url ) {
+			var tex = new THREE.Texture();
 
-			asset = this.loadTexture( url );
-		} else {
-      // embed texture not supported
-		}
-    // Ignore for now
-		this.parseProperties( null );
+			var loader = new THREE.ImageLoader( this.manager );
 
-		this.parseUserAttributes();
-		return asset;
-	};
+			loader.load( this._baseDir+url, function( image ) {
+				tex.image = image;
+				tex.needsUpdate = true;
+			});
 
-	AWDLoader.prototype.loadTexture = function( url ) {
+			return tex;
+		},
 
-		if ( null === this._resourceLoader )
-      this._resourceLoader = new ResourcesLoader( this._url );
+		parseSkeleton: function( len ) { // Array<Bone>
+			var name          = this.readUTF(),
+					num_joints    = this.readU16(),
+					skeleton      = [],
+					joints_parsed = 0;
 
-		return this._resourceLoader.loadTexture( url );
-	};
+			this.parseProperties( null );
 
-  // broken : skeleton pose format is different than threejs one
-	AWDLoader.prototype.parseSkeleton = function(len) // Array<Bone>
-  {
-		var name          = this.readUTF(),
-        num_joints    = this.readU16(),
-        skeleton      = [],
-        joints_parsed = 0;
+			while (joints_parsed < num_joints) {
+				var joint, ibp;
 
-		this.parseProperties( null );
+				// Ignore joint id
+				this.readU16();
 
-		while (joints_parsed < num_joints) {
-			var joint, ibp;
+				joint = new THREE.Bone();
+				joint.parent = this.readU16() - 1; // 0=null in AWD
+				joint.name = this.readUTF();
 
-      // Ignore joint id
-			this.readU16();
+				ibp = this.parseMatrix4();
+				joint.skinMatrix = ibp;
 
-			joint = new THREE.Bone();
-			joint.parent = this.readU16() - 1; // 0=null in AWD
-			joint.name = this.readUTF();
+				// Ignore joint props/attributes for now
+				this.parseProperties(null);
+				this.parseUserAttributes();
 
-			ibp = this.parseMatrix4();
-			joint.skinMatrix = ibp;
+				skeleton.push(joint);
+				joints_parsed++;
+			}
 
-      // Ignore joint props/attributes for now
-			this.parseProperties(null);
+			// Discard attributes for now
 			this.parseUserAttributes();
 
-			skeleton.push(joint);
-			joints_parsed ++;
-		}
-
-    // Discard attributes for now
-		this.parseUserAttributes();
-
-
-		return skeleton;
-	};
-
-
 
-	AWDLoader.prototype.parseSkeletonPose = function(blockID)
-  {
-		var name = this.readUTF();
+			return skeleton;
+		},
 
+		parseSkeletonPose: function( blockID ) {
+			var name = this.readUTF();
 
-		var num_joints = this.readU16();
-		this.parseProperties(null);
+			var num_joints = this.readU16();
+			this.parseProperties(null);
 
-    // debug( 'parse Skeleton Pose. joints : ' + num_joints);
+			// debug( 'parse Skeleton Pose. joints : ' + num_joints);
 
-		var pose = [];
+			var pose = [];
 
-		var joints_parsed = 0;
+			var joints_parsed = 0;
 
-		while (joints_parsed < num_joints) {
+			while (joints_parsed < num_joints) {
 
-			var joint_pose;
+				var joint_pose;
 
-			var has_transform; //:uint;
-			var mtx_data;
+				var has_transform; //:uint;
+				var mtx_data;
 
-			has_transform = this.readU8();
+				has_transform = this.readU8();
 
-			if (has_transform === 1) {
-				mtx_data = this.parseMatrix4();
-			} else
-			{
-				mtx_data = new THREE.Matrix4();
+				if (has_transform === 1) {
+					mtx_data = this.parseMatrix4();
+				} else
+				{
+					mtx_data = new THREE.Matrix4();
+				}
+				pose[joints_parsed] = mtx_data;
+				joints_parsed++;
 			}
-			pose[joints_parsed] = mtx_data;
-			joints_parsed ++;
-		}
-    // Skip attributes for now
-		this.parseUserAttributes();
-
-		return pose
-	};
-
-	AWDLoader.prototype.parseSkeletonAnimation = function(blockID)
-  {
-		var frame_dur;
-		var pose_addr;
-		var pose;
-
-		var name = this.readUTF();
 
-		var clip = [];
-
-		var num_frames = this.readU16();
-		this.parseProperties(null);
-
-		var frames_parsed = 0;
-		var returnedArray;
-
-
-    // debug( 'parse Skeleton Animation. frames : ' + num_frames);
+			// Skip attributes for now
+			this.parseUserAttributes();
 
-		while (frames_parsed < num_frames) {
-			pose_addr = this.readU32();
-			frame_dur = this.readU16();
+			return pose;
 
-			pose = this._blocks[pose_addr].data;
-      // debug( 'pose address ',pose[2].elements[12],pose[2].elements[13],pose[2].elements[14] );
-			clip.push( {
-        pose : pose,
-        duration : frame_dur
-      } );
+		},
 
-			frames_parsed ++;
-		}
-		if (clip.length == 0) {
-      // debug("Could not this SkeletonClipNode, because no Frames where set.");
-			return;
-		}
-    // Ignore attributes for now
-		this.parseUserAttributes();
-		return clip;
-	};
+		parseSkeletonAnimation: function( blockID ) {
 
+			var frame_dur;
+			var pose_addr;
+			var pose;
 
+			var name = this.readUTF();
 
-	AWDLoader.prototype.parseVertexAnimationSet = function(len)
-  {
-		var poseBlockAdress,
-        name           = this.readUTF(),
-        num_frames     = this.readU16(),
-        props          = this.parseProperties({ 1:UINT16 }),
-        frames_parsed  = 0,
-        skeletonFrames = [];
+			var clip = [];
 
-		while (frames_parsed < num_frames) {
-			poseBlockAdress = this.readU32();
-			skeletonFrames.push(this._blocks[poseBlockAdress].data);
-			frames_parsed ++;
-		}
-
-		this.parseUserAttributes();
+			var num_frames = this.readU16();
+			this.parseProperties( null );
 
+			var frames_parsed = 0;
+			var returnedArray;
 
-		return skeletonFrames;
-	};
+			// debug( 'parse Skeleton Animation. frames : ' + num_frames);
 
+			while ( frames_parsed < num_frames ) {
+				pose_addr = this.readU32();
+				frame_dur = this.readU16();
 
-	AWDLoader.prototype.parseAnimatorSet = function(len)
-  {
-		var targetMesh;
+				pose = this._blocks[pose_addr].data;
+				// debug( 'pose address ',pose[2].elements[12],pose[2].elements[13],pose[2].elements[14] );
+				clip.push( {
+					pose : pose,
+					duration : frame_dur
+				} );
 
-		var animSetBlockAdress; //:int
+				frames_parsed++;
+			}
 
-		var targetAnimationSet; //:AnimationSetBase;
-		var outputString = ""; //:String = "";
-		var name = this.readUTF();
-		var type = this.readU16();
+			if ( clip.length === 0 ) {
+				// debug("Could not this SkeletonClipNode, because no Frames where set.");
+				return;
+			}
+			// Ignore attributes for now
+			this.parseUserAttributes();
+			return clip;
 
-		var props = this.parseProperties({ 1:BADDR });
+		},
 
-		animSetBlockAdress = this.readU32();
-		var targetMeshLength = this.readU16();
+		parseVertexAnimationSet: function( len ) {
 
-		var meshAdresses = []; //:Vector.<uint> = new Vector.<uint>;
+			var poseBlockAdress,
+					name           = this.readUTF(),
+					num_frames     = this.readU16(),
+					props          = this.parseProperties( { 1:UINT16 } ),
+					frames_parsed  = 0,
+					skeletonFrames = [];
 
-		for (var i = 0; i < targetMeshLength; i ++)
-      meshAdresses.push( this.readU32() );
+			while ( frames_parsed < num_frames ) {
+				poseBlockAdress = this.readU32();
+				skeletonFrames.push( this._blocks[poseBlockAdress].data );
+				frames_parsed++;
+			}
 
-		var activeState = this.readU16();
-		var autoplay = Boolean(this.readU8());
-		this.parseUserAttributes();
-		this.parseUserAttributes();
+			this.parseUserAttributes();
 
-		var returnedArray;
-		var targetMeshes = []; //:Vector.<Mesh> = new Vector.<Mesh>;
 
-		for (i = 0; i < meshAdresses.length; i ++) {
-//      returnedArray = getAssetByID(meshAdresses[i], [AssetType.MESH]);
-//      if (returnedArray[0])
-			targetMeshes.push(this._blocks[meshAdresses[i]].data);
-		}
+			return skeletonFrames;
 
-		targetAnimationSet = this._blocks[animSetBlockAdress].data;
-		var thisAnimator;
+		},
 
-		if (type == 1) {
+		parseAnimatorSet: function( len ) {
+			var targetMesh;
 
+			var animSetBlockAdress; //:int
 
-			thisAnimator = {
-        animationSet : targetAnimationSet,
-        skeleton : this._blocks[props.get(1, 0)].data
-      };
+			var targetAnimationSet; //:AnimationSetBase;
+			var outputString = ""; //:String = "";
+			var name = this.readUTF();
+			var type = this.readU16();
 
-		} else if (type == 2) {
-      // debug( "vertex Anim???");
-		}
+			var props = this.parseProperties( { 1:BADDR } );
 
+			animSetBlockAdress = this.readU32();
+			var targetMeshLength = this.readU16();
 
-		for (i = 0; i < targetMeshes.length; i ++) {
-			targetMeshes[i].animator = thisAnimator;
-		}
-    // debug("Parsed a Animator: Name = " + name);
+			var meshAdresses = []; //:Vector.<uint> = new Vector.<uint>;
 
-		return thisAnimator;
-	};
+			for (var i = 0; i < targetMeshLength; i++)
+				meshAdresses.push( this.readU32() );
 
+			var activeState = this.readU16();
+			var autoplay = Boolean( this.readU8() );
+			this.parseUserAttributes();
+			this.parseUserAttributes();
 
+			var returnedArray;
+			var targetMeshes = []; //:Vector.<Mesh> = new Vector.<Mesh>;
 
+			for ( i = 0; i < meshAdresses.length; i++ ) {
+	//      returnedArray = getAssetByID(meshAdresses[i], [AssetType.MESH]);
+	//      if (returnedArray[0])
+					targetMeshes.push( this._blocks[meshAdresses[i]].data );
+			}
 
+			targetAnimationSet = this._blocks[animSetBlockAdress].data;
+			var thisAnimator;
 
+			if (type == 1) {
 
 
-	AWDLoader.prototype.parseMeshData = function ( len ) {
+				thisAnimator = {
+					animationSet : targetAnimationSet,
+					skeleton : this._blocks[props.get(1, 0)].data
+				};
 
-		var name      = this.readUTF(),
-        num_subs  = this.readU16(),
-        geom,
-        subs_parsed = 0,
-        props,
-        buffer,
-        skinW, skinI,
-        geometries = [];
+			} else if (type == 2) {
+				// debug( "vertex Anim???");
+			}
 
 
+			for ( i = 0; i < targetMeshes.length; i++ ) {
+					targetMeshes[i].animator = thisAnimator;
+			}
+			// debug("Parsed a Animator: Name = " + name);
 
+			return thisAnimator;
 
-		props = this.parseProperties({
-      1: this._geoNrType,
-      2: this._geoNrType
-    });
+		},
 
+		parseMeshData: function ( len ) {
+			var name      = this.readUTF(),
+				num_subs  = this.readU16(),
+				geom,
+				subs_parsed = 0,
+				props,
+				buffer,
+				skinW, skinI,
+				geometries = [];
 
+			props = this.parseProperties({
+				1: this._geoNrType,
+				2: this._geoNrType
+			});
 
-    // Loop through sub meshes
-		while (subs_parsed < num_subs) {
+			// Loop through sub meshes
+			while ( subs_parsed < num_subs ) {
 
-			var sm_len, sm_end, attrib;
+				var sm_len, sm_end, attrib;
 
-			geom = new THREE.BufferGeometry();
-			geom.name = name;
-			geometries.push( geom );
+				geom = new THREE.BufferGeometry();
+				geom.name = name;
+				geometries.push( geom );
 
 
-			sm_len = this.readU32();
-			sm_end = this._ptr + sm_len;
+				sm_len = this.readU32();
+				sm_end = this._ptr + sm_len;
 
 
-      // Ignore for now
-			this.parseProperties({ 1:this._geoNrType, 2:this._geoNrType });
+				// Ignore for now
+				this.parseProperties( { 1:this._geoNrType, 2:this._geoNrType } );
 
-      // Loop through data streams
-			while ( this._ptr < sm_end ) {
+				// Loop through data streams
+				while ( this._ptr < sm_end ) {
 
+					var idx = 0,
+							str_type  = this.readU8(),
+							str_ftype = this.readU8(),
+							str_len   = this.readU32(),
+							str_end   = str_len + this._ptr;
 
-				var idx = 0,
-            str_type  = this.readU8(),
-            str_ftype = this.readU8(),
-            str_len   = this.readU32(),
-            str_end   = str_len + this._ptr;
+					// VERTICES
+					// ------------------
+					if ( str_type === 1 ) {
 
+						buffer = new Float32Array( ( str_len / 12 ) * 3 );
+						attrib = new THREE.BufferAttribute( buffer, 3 );
 
+						geom.addAttribute( 'position', attrib );
+						idx = 0;
 
+						while (this._ptr < str_end) {
+							buffer[idx]   = -this.readF32();
+							buffer[idx+1] = this.readF32();
+							buffer[idx+2] = this.readF32();
+							idx+=3;
+						}
+					}
 
+					// INDICES
+					// -----------------
+					else if (str_type === 2) {
 
-        // VERTICES
-        // ------------------
-				if ( str_type === 1 ) {
+						buffer = new Uint16Array( str_len / 2 );
+						attrib = new THREE.BufferAttribute( buffer, 1 );
+						geom.addAttribute( 'index', attrib );
 
-					buffer = new Float32Array( ( str_len / 12 ) * 3 );
-					attrib = new THREE.BufferAttribute( buffer, 3 );
+						geom.offsets.push({
+							start: 0,
+							index: 0,
+							count: str_len/2
+						});
 
-					geom.addAttribute( 'position', attrib );
-					idx = 0;
+						idx = 0;
 
-					while (this._ptr < str_end) {
-						buffer[idx]   = -this.readF32();
-						buffer[idx + 1] = this.readF32();
-						buffer[idx + 2] = this.readF32();
-						idx+=3;
+						while (this._ptr < str_end) {
+							buffer[idx+1]   = this.readU16();
+							buffer[idx]     = this.readU16();
+							buffer[idx+2]   = this.readU16();
+							idx+=3;
+						}
 					}
-				}
-
-
-        // INDICES
-        // -----------------
-        else if (str_type === 2) {
 
-	buffer = new Uint16Array( str_len / 2 );
-	attrib = new THREE.BufferAttribute( buffer, 1 );
-	geom.addAttribute( 'index', attrib );
+					// UVS
+					// -------------------
+					else if (str_type === 3) {
 
-	geom.addDrawCall( 0, str_len / 2, 0 );
+						buffer = new Float32Array( ( str_len / 8 ) * 2 );
+						attrib = new THREE.BufferAttribute( buffer, 2 );
 
-	idx = 0;
-
-	while (this._ptr < str_end) {
-		buffer[idx + 1]   = this.readU16();
-		buffer[idx]     = this.readU16();
-		buffer[idx + 2]   = this.readU16();
-		idx+=3;
-	}
-        }
-
-        // UVS
-        // -------------------
-        else if (str_type === 3) {
+						geom.addAttribute( 'uv', attrib );
+						idx = 0;
 
-	buffer = new Float32Array( ( str_len / 8 ) * 2 );
-	attrib = new THREE.BufferAttribute( buffer, 2 );
+						while (this._ptr < str_end) {
+							buffer[idx]   = this.readF32();
+							buffer[idx+1] = 1.0-this.readF32();
+							idx+=2;
+						}
+					}
 
-	geom.addAttribute( 'uv', attrib );
-	idx = 0;
+					// NORMALS
+					else if (str_type === 4) {
 
-	while (this._ptr < str_end) {
-		buffer[idx]   = this.readF32();
-		buffer[idx + 1] = 1.0 - this.readF32();
-		idx+=2;
-	}
-        }
+						buffer = new Float32Array( ( str_len / 12 ) * 3 );
+						attrib = new THREE.BufferAttribute( buffer, 3 );
+						geom.addAttribute( 'normal', attrib );
+						idx = 0;
 
-        // NORMALS
-        else if (str_type === 4) {
+						while (this._ptr < str_end) {
+							buffer[idx]   = -this.readF32();
+							buffer[idx+1] = this.readF32();
+							buffer[idx+2] = this.readF32();
+							idx+=3;
+						}
 
-	buffer = new Float32Array( ( str_len / 12 ) * 3 );
-	attrib = new THREE.BufferAttribute( buffer, 3 );
-	geom.addAttribute( 'normal', attrib );
-	idx = 0;
+					}
 
-	while (this._ptr < str_end) {
-		buffer[idx]   = -this.readF32();
-		buffer[idx + 1] = this.readF32();
-		buffer[idx + 2] = this.readF32();
-		idx+=3;
-	}
+					// else if (str_type == 6) {
+					//   skinI = new Float32Array( str_len>>1 );
+					//   idx = 0
+
+					//   while (this._ptr < str_end) {
+					//     skinI[idx]   = this.readU16();
+					//     idx++;
+					//   }
+
+					// }
+					// else if (str_type == 7) {
+					//   skinW = new Float32Array( str_len>>2 );
+					//   idx = 0;
+
+					//   while (this._ptr < str_end) {
+					//     skinW[idx]   = this.readF32();
+					//     idx++;
+					//   }
+					// }
+					else {
+						this._ptr = str_end;
+					}
 
-        }
-
-        // else if (str_type == 6) {
-        //   skinI = new Float32Array( str_len>>1 );
-        //   idx = 0
-
-        //   while (this._ptr < str_end) {
-        //     skinI[idx]   = this.readU16();
-        //     idx++;
-        //   }
-
-        // }
-        // else if (str_type == 7) {
-        //   skinW = new Float32Array( str_len>>2 );
-        //   idx = 0;
-
-        //   while (this._ptr < str_end) {
-        //     skinW[idx]   = this.readF32();
-        //     idx++;
-        //   }
-        // }
-				else {
-					this._ptr = str_end;
 				}
 
+				this.parseUserAttributes();
 
-
+				geom.computeBoundingSphere();
+				subs_parsed++;
 			}
 
-			this.parseUserAttributes();
-
-
-			geom.computeBoundingSphere();
-			subs_parsed ++;
-		}
-
-
-    //geom.computeFaceNormals();
+			//geom.computeFaceNormals();
 
+			this.parseUserAttributes();
+			//finalizeAsset(geom, name);
+
+			return geometries;
+
+		},
+
+		parseMeshPoseAnimation: function( len, poseOnly ) {
+			var num_frames = 1,
+					num_submeshes,
+					frames_parsed,
+					subMeshParsed,
+					frame_dur,
+					x, y, z,
+
+					str_len,
+					str_end,
+					geom,
+					subGeom,
+					idx = 0,
+					clip = {},
+					indices,
+					verts,
+					num_Streams,
+					streamsParsed,
+					streamtypes = [],
+
+					props,
+					thisGeo,
+					name = this.readUTF(),
+					geoAdress = this.readU32();
+
+			var mesh = this.getBlock( geoAdress );
+
+			if (mesh === null) {
+				console.log( "parseMeshPoseAnimation target mesh not found at:", geoAdress );
+				return;
+			}
 
-		this.parseUserAttributes();
-    //finalizeAsset(geom, name);
+			geom = mesh.geometry;
+			geom.morphTargets = [];
 
-		return geometries;
-	};
+			if (!poseOnly)
+				num_frames = this.readU16();
 
-	AWDLoader.prototype.parseMeshPoseAnimation = function(len, poseOnly)
-  {
-		var num_frames = 1,
-        num_submeshes,
-        frames_parsed,
-        subMeshParsed,
-        frame_dur,
-        x, y, z,
-
-        str_len,
-        str_end,
-        geom,
-        subGeom,
-        idx = 0,
-        clip = {},
-        indices,
-        verts,
-        num_Streams,
-        streamsParsed,
-        streamtypes = [],
-
-        props,
-        thisGeo,
-        name = this.readUTF(),
-        geoAdress = this.readU32();
-
-
-		var mesh = this.getBlock( geoAdress );
-
-		if (mesh == null) {
-			console.log( "parseMeshPoseAnimation target mesh not found at:", geoAdress );
-			return;
-		}
+			num_submeshes = this.readU16();
+			num_Streams = this.readU16();
 
-		geom = mesh.geometry;
-		geom.morphTargets = [];
+			// debug("VA num_frames : ", num_frames );
+			// debug("VA num_submeshes : ", num_submeshes );
+			// debug("VA numstreams : ", num_Streams );
 
-		if (!poseOnly)
-      num_frames = this.readU16();
-
-		num_submeshes = this.readU16();
-		num_Streams = this.readU16();
+			streamsParsed = 0;
+			while ( streamsParsed < num_Streams ) {
+				streamtypes.push(this.readU16());
+				streamsParsed++;
+			}
+			props = this.parseProperties( { 1:BOOL, 2:BOOL } );
 
-    // debug("VA num_frames : ", num_frames );
-    // debug("VA num_submeshes : ", num_submeshes );
-    // debug("VA numstreams : ", num_Streams );
+			clip.looping = props.get( 1, true );
+			clip.stitchFinalFrame = props.get( 2, false );
 
-		streamsParsed = 0;
-		while (streamsParsed < num_Streams) {
-			streamtypes.push(this.readU16());
-			streamsParsed ++;
-		}
-		props = this.parseProperties({ 1:BOOL, 2:BOOL });
+			frames_parsed = 0;
 
-		clip.looping = props.get(1, true);
-		clip.stitchFinalFrame = props.get(2, false);
+			while ( frames_parsed < num_frames ) {
 
-		frames_parsed = 0;
+				frame_dur = this.readU16();
+				subMeshParsed = 0;
 
-		while (frames_parsed < num_frames) {
+				while ( subMeshParsed < num_submeshes ) {
 
-			frame_dur = this.readU16();
-			subMeshParsed = 0;
+					streamsParsed = 0;
+					str_len = this.readU32();
+					str_end = this._ptr + str_len;
 
-			while (subMeshParsed < num_submeshes) {
+					while ( streamsParsed < num_Streams ) {
 
-				streamsParsed = 0;
-				str_len = this.readU32();
-				str_end = this._ptr + str_len;
+						if ( streamtypes[streamsParsed] === 1 ) {
 
-				while (streamsParsed < num_Streams) {
+							//geom.addAttribute( 'morphTarget'+frames_parsed, Float32Array, str_len/12, 3 );
+							var buffer = new Float32Array(str_len/4);
+							geom.morphTargets.push( {
+								array : buffer
+							});
 
-					if (streamtypes[streamsParsed] == 1) {
+							//buffer = geom.attributes['morphTarget'+frames_parsed].array
+							idx = 0;
 
-            //geom.addAttribute( 'morphTarget'+frames_parsed, Float32Array, str_len/12, 3 );
-						var buffer = new Float32Array(str_len / 4);
-						geom.morphTargets.push( {
-              array : buffer
-            });
+							while ( this._ptr < str_end ) {
+								buffer[idx]     = this.readF32();
+								buffer[idx+1]   = this.readF32();
+								buffer[idx+2]   = this.readF32();
+								idx += 3;
+							}
 
-            //buffer = geom.attributes['morphTarget'+frames_parsed].array
-						idx = 0;
 
-						while ( this._ptr < str_end ) {
-							buffer[idx]     = this.readF32();
-							buffer[idx + 1]   = this.readF32();
-							buffer[idx + 2]   = this.readF32();
-							idx += 3;
-						}
+							subMeshParsed++;
+						} else
+							this._ptr = str_end;
+						streamsParsed++;
+					}
+				}
 
 
-						subMeshParsed ++;
-					} else
-            this._ptr = str_end;
-					streamsParsed ++;
-				}
+				frames_parsed++;
 			}
 
+			this.parseUserAttributes();
 
-			frames_parsed ++;
-		}
-		this.parseUserAttributes();
+			return null;
+		},
 
-		return null;
-	};
+		getBlock: function ( id ) {
 
+			return this._blocks[id].data;
 
+		},
 
+		parseMatrix4: function () {
 
+			var mtx = new THREE.Matrix4();
+			var e = mtx.elements;
 
+			e[0] = this.readF32();
+			e[1] = this.readF32();
+			e[2] = this.readF32();
+			e[3] = 0.0;
+			//e[3] = 0.0;
 
+			e[4] = this.readF32();
+			e[5] = this.readF32();
+			e[6] = this.readF32();
+			//e[7] = this.readF32();
+			e[7] = 0.0;
 
+			e[8] = this.readF32();
+			e[9] = this.readF32();
+			e[10] = this.readF32();
+			//e[11] = this.readF32();
+			e[11] = 0.0;
 
+			e[12] = -this.readF32();
+			e[13] = this.readF32();
+			e[14] = this.readF32();
+			//e[15] = this.readF32();
+			e[15] = 1.0;
+			return mtx;
 
-	AWDLoader.prototype.getBlock = function ( id ) {
-		return this._blocks[id].data;
-	},
+		},
 
+		parseProperties: function ( expected ) {
+			var list_len = this.readU32();
+			var list_end = this._ptr + list_len;
 
-  AWDLoader.prototype.parseMatrix4 = function ( ) {
-	var mtx = new THREE.Matrix4();
-	var e = mtx.elements;
+			var props = new AWDProperties();
 
-	e[0] = this.readF32();
-	e[1] = this.readF32();
-	e[2] = this.readF32();
-	e[3] = 0.0;
-    //e[3] = 0.0;
+			if( expected ) {
 
-	e[4] = this.readF32();
-	e[5] = this.readF32();
-	e[6] = this.readF32();
-    //e[7] = this.readF32();
-	e[7] = 0.0;
+				while( this._ptr < list_end ) {
 
-	e[8] = this.readF32();
-	e[9] = this.readF32();
-	e[10] = this.readF32();
-    //e[11] = this.readF32();
-	e[11] = 0.0;
-
-	e[12] = -this.readF32();
-	e[13] = this.readF32();
-	e[14] = this.readF32();
-    //e[15] = this.readF32();
-	e[15] = 1.0;
-	return mtx;
-  };
+					var key = this.readU16();
+					var len = this.readU32();
+					var type;
 
+					if( expected.hasOwnProperty( key ) ) {
+						type = expected[ key ];
+						props.set( key, this.parseAttrValue( type, len ) );
+					} else {
+						this._ptr += len;
+					}
+				}
 
-	AWDLoader.prototype.parseProperties = function ( expected ) {
-		var list_len = this.readU32();
-		var list_end = this._ptr + list_len;
+			}
 
-		var props = new AWDProperties();
+			return props;
+		},
+
+		parseUserAttributes: function () {
+			// skip for now
+			this._ptr = this.readU32() + this._ptr;
+			return null;
+		},
+
+		parseAttrValue: function ( type, len ) {
+			var elem_len;
+			var read_func;
+
+			switch (type) {
+				case AWD_FIELD_INT8:
+					elem_len = 1;
+					read_func = this.readI8;
+					break;
+				case AWD_FIELD_INT16:
+					elem_len = 2;
+					read_func = this.readI16;
+					break;
+				case AWD_FIELD_INT32:
+					elem_len = 4;
+					read_func = this.readI32;
+					break;
+				case AWD_FIELD_BOOL:
+				case AWD_FIELD_UINT8:
+					elem_len = 1;
+					read_func = this.readU8;
+					break;
+				case AWD_FIELD_UINT16:
+					elem_len = 2;
+					read_func = this.readU16;
+					break;
+				case AWD_FIELD_UINT32:
+				case AWD_FIELD_BADDR:
+					elem_len = 4;
+					read_func = this.readU32;
+					break;
+				case AWD_FIELD_FLOAT32:
+					elem_len = 4;
+					read_func = this.readF32;
+					break;
+				case AWD_FIELD_FLOAT64:
+					elem_len = 8;
+					read_func = this.readF64;
+					break;
+				case AWD_FIELD_VECTOR2x1:
+				case AWD_FIELD_VECTOR3x1:
+				case AWD_FIELD_VECTOR4x1:
+				case AWD_FIELD_MTX3x2:
+				case AWD_FIELD_MTX3x3:
+				case AWD_FIELD_MTX4x3:
+				case AWD_FIELD_MTX4x4:
+					elem_len = 8;
+					read_func = this.readF64;
+					break;
+			}
 
-		if ( expected ) {
+			if (elem_len < len) {
+				var list;
+				var num_read;
+				var num_elems;
 
-			while ( this._ptr < list_end ) {
+				list = [];
+				num_read = 0;
+				num_elems = len / elem_len;
 
-				var key = this.readU16();
-				var len = this.readU32();
-				var type;
+				while (num_read < num_elems) {
+					list.push(read_func.call( this ) );
+					num_read++;
+				}
 
-				if ( expected.hasOwnProperty( key ) ) {
-					type = expected[ key ];
-					props.set( key, this.parseAttrValue( type, len ) );
+				return list;
+			}
+			else {
+				return read_func.call( this );
+			}
+		},
+
+		readU8: function () {
+			return this._data.getUint8( this._ptr++ );
+		},
+		readI8: function () {
+			return this._data.getInt8( this._ptr++ );
+		},
+		readU16: function () {
+			var a = this._data.getUint16( this._ptr, littleEndian );
+			this._ptr += 2;
+			return a;
+		},
+		readI16: function () {
+			var a = this._data.getInt16( this._ptr, littleEndian );
+			this._ptr += 2;
+			return a;
+		},
+		readU32: function () {
+			var a = this._data.getUint32( this._ptr, littleEndian );
+			this._ptr += 4;
+			return a;
+		},
+		readI32: function () {
+			var a = this._data.getInt32( this._ptr, littleEndian );
+			this._ptr += 4;
+			return a;
+		},
+		readF32: function () {
+			var a = this._data.getFloat32( this._ptr, littleEndian );
+			this._ptr += 4;
+			return a;
+		},
+		readF64: function () {
+			var a = this._data.getFloat64( this._ptr, littleEndian );
+			this._ptr += 8;
+			return a;
+		},
+
+		/**
+	 * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
+	 * @param {Array.<number>} bytes UTF-8 byte array.
+	 * @return {string} 16-bit Unicode string.
+	 */
+		readUTF: function () {
+			var len = this.readU16();
+			return this.readUTFBytes( len );
+		},
+
+		/**
+		 * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
+		 * @param {Array.<number>} bytes UTF-8 byte array.
+		 * @return {string} 16-bit Unicode string.
+		 */
+		readUTFBytes: function ( len ) {
+			// TODO(user): Use native implementations if/when available
+			var out = [], c = 0;
+
+			while ( out.length < len ) {
+				var c1 = this._data.getUint8( this._ptr++, littleEndian );
+				if (c1 < 128) {
+					out[c++] = String.fromCharCode(c1);
+				} else if (c1 > 191 && c1 < 224) {
+					var c2 = this._data.getUint8( this._ptr++, littleEndian );
+					out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
 				} else {
-					this._ptr += len;
+					var c2 = this._data.getUint8( this._ptr++, littleEndian );
+					var c3 = this._data.getUint8( this._ptr++, littleEndian );
+					out[c++] = String.fromCharCode(
+							(c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63
+					);
 				}
 			}
-
-		}
-
-		return props;
-
-	};
-
-
-	AWDLoader.prototype.parseUserAttributes = function ( ) {
-    // skip for now
-		this._ptr = this.readU32() + this._ptr;
-		return null;
-	};
-
-
-	AWDLoader.prototype.parseAttrValue = function ( type, len ) {
-
-		var elem_len;
-		var read_func;
-
-		switch (type) {
-			case AWD_FIELD_INT8:
-				elem_len = 1;
-				read_func = this.readI8;
-				break;
-			case AWD_FIELD_INT16:
-				elem_len = 2;
-				read_func = this.readI16;
-				break;
-			case AWD_FIELD_INT32:
-				elem_len = 4;
-				read_func = this.readI32;
-				break;
-			case AWD_FIELD_BOOL:
-			case AWD_FIELD_UINT8:
-				elem_len = 1;
-				read_func = this.readU8;
-				break;
-			case AWD_FIELD_UINT16:
-				elem_len = 2;
-				read_func = this.readU16;
-				break;
-			case AWD_FIELD_UINT32:
-			case AWD_FIELD_BADDR:
-				elem_len = 4;
-				read_func = this.readU32;
-				break;
-			case AWD_FIELD_FLOAT32:
-				elem_len = 4;
-				read_func = this.readF32;
-				break;
-			case AWD_FIELD_FLOAT64:
-				elem_len = 8;
-				read_func = this.readF64;
-				break;
-			case AWD_FIELD_VECTOR2x1:
-			case AWD_FIELD_VECTOR3x1:
-			case AWD_FIELD_VECTOR4x1:
-			case AWD_FIELD_MTX3x2:
-			case AWD_FIELD_MTX3x3:
-			case AWD_FIELD_MTX4x3:
-			case AWD_FIELD_MTX4x4:
-				elem_len = 8;
-				read_func = this.readF64;
-				break;
-		}
-
-		if (elem_len < len) {
-			var list;
-			var num_read;
-			var num_elems;
-
-			list = [];
-			num_read = 0;
-			num_elems = len / elem_len;
-
-			while (num_read < num_elems) {
-				list.push(read_func.call( this ) );
-				num_read ++;
-			}
-
-			return list;
-		}
-		else {
-			return read_func.call( this );
+			return out.join('');
 		}
 
 	};
 
-
-	AWDLoader.prototype.readU8 = function () {
-		return this._data.getUint8( this._ptr ++ );
-	};
-	AWDLoader.prototype.readI8 = function () {
-		return this._data.getInt8( this._ptr ++ );
-	};
-
-	AWDLoader.prototype.readU16 = function () {
-		var a = this._data.getUint16( this._ptr, littleEndian );
-		this._ptr += 2;
-		return a;
-	};
-	AWDLoader.prototype.readI16 = function () {
-		var a = this._data.getInt16( this._ptr, littleEndian );
-		this._ptr += 2;
-		return a;
-	};
-
-	AWDLoader.prototype.readU32 = function () {
-		var a = this._data.getUint32( this._ptr, littleEndian );
-		this._ptr += 4;
-		return a;
-	};
-	AWDLoader.prototype.readI32 = function () {
-		var a = this._data.getInt32( this._ptr, littleEndian );
-		this._ptr += 4;
-		return a;
-	};
-	AWDLoader.prototype.readF32 = function () {
-		var a = this._data.getFloat32( this._ptr, littleEndian );
-		this._ptr += 4;
-		return a;
-	};
-	AWDLoader.prototype.readF64 = function () {
-		var a = this._data.getFloat64( this._ptr, littleEndian );
-		this._ptr += 8;
-		return a;
-	};
-
-
-  /**
-   * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
-   * @param {Array.<number>} bytes UTF-8 byte array.
-   * @return {string} 16-bit Unicode string.
-   */
-	AWDLoader.prototype.readUTF = function () {
-		var len = this.readU16();
-
-		return this.readUTFBytes( len );
-	};
-
-  /**
-   * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
-   * @param {Array.<number>} bytes UTF-8 byte array.
-   * @return {string} 16-bit Unicode string.
-   */
-	AWDLoader.prototype.readUTFBytes = function ( len ) {
-
-
-    // TODO(user): Use native implementations if/when available
-
-		var out = [], c = 0;
-
-		while ( out.length < len ) {
-			var c1 = this._data.getUint8( this._ptr ++, littleEndian );
-			if (c1 < 128) {
-				out[c ++] = String.fromCharCode(c1);
-			} else if (c1 > 191 && c1 < 224) {
-				var c2 = this._data.getUint8( this._ptr ++, littleEndian );
-				out[c ++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
-			} else {
-				var c2 = this._data.getUint8( this._ptr ++, littleEndian );
-				var c3 = this._data.getUint8( this._ptr ++, littleEndian );
-				out[c ++] = String.fromCharCode(
-            (c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63
-        );
-			}
-		}
-		return out.join('');
-	};
-
-
-
-
-
-
-
-
-
-	AWDProperties = function() {};
-
-	AWDProperties.prototype = {
-
-
-    set : function(key, value)
-    {
-	this[key] = value;
-    },
-
-    get : function(key, fallback)
-    {
-	if ( this.hasOwnProperty(key) )
-        return this[key];
-      else return fallback;
-    }
-  };
-
-	return AWDLoader;
-
 })();

+ 6 - 4
examples/js/loaders/AssimpJSONLoader.js

@@ -20,13 +20,11 @@ THREE.AssimpJSONLoader.prototype = {
 
 	constructor: THREE.AssimpJSONLoader,
 
-	texturePath : '',
-
-	load: function ( url, onLoad, onProgress, onError, texturePath ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 		var scope = this;
 
-		this.texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url );
+		this.texturePath = this.texturePath && ( typeof this.texturePath === "string" ) ? this.texturePath : this.extractUrlBase( url );
 
 		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
@@ -60,6 +58,10 @@ THREE.AssimpJSONLoader.prototype = {
 		this.crossOrigin = value;
 	},
 
+	setTexturePath: function ( value ) {
+		this.texturePath = value;
+	},
+
 	extractUrlBase: function ( url ) { // from three/src/loaders/Loader.js
 		var parts = url.split( '/' );
 		parts.pop();

+ 434 - 491
examples/js/loaders/BinaryLoader.js

@@ -2,751 +2,694 @@
  * @author alteredq / http://alteredqualia.com/
  */
 
-THREE.BinaryLoader = function ( showStatus ) {
+THREE.BinaryLoader = function ( manager ) {
 
-	THREE.Loader.call( this, showStatus );
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 };
 
-THREE.BinaryLoader.prototype = Object.create( THREE.Loader.prototype );
-THREE.BinaryLoader.prototype.constructor = THREE.BinaryLoader;
+THREE.BinaryLoader.prototype = {
 
-// Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary)
-//  - binary models consist of two files: JS and BIN
-//  - parameters
-//		- url (required)
-//		- callback (required)
-//		- texturePath (optional: if not specified, textures will be assumed to be in the same folder as JS model file)
-//		- binaryPath (optional: if not specified, binary file will be assumed to be in the same folder as JS model file)
+	constructor: THREE.BinaryLoader,
 
-THREE.BinaryLoader.prototype.load = function ( url, callback, texturePath, binaryPath ) {
+	// Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary)
+	//  - binary models consist of two files: JS and BIN
+	//  - parameters
+	//		- url (required)
+	//		- callback (required)
+	//		- texturePath (optional: if not specified, textures will be assumed to be in the same folder as JS model file)
+	//		- binaryPath (optional: if not specified, binary file will be assumed to be in the same folder as JS model file)
+	load: function ( url, onLoad, onProgress, onError ) {
 
-	// todo: unify load API to for easier SceneLoader use
+		// todo: unify load API to for easier SceneLoader use
 
-	texturePath = texturePath || this.extractUrlBase( url );
-	binaryPath = binaryPath || this.extractUrlBase( url );
+		var texturePath = this.texturePath || THREE.Loader.prototype.extractUrlBase( url );
+		var binaryPath  = this.binaryPath || THREE.Loader.prototype.extractUrlBase( url );
 
-	var callbackProgress = this.showProgress ? THREE.Loader.prototype.updateProgress : undefined;
+		// #1 load JS part via web worker
 
-	this.onLoadStart();
+		var scope = this;
 
-	// #1 load JS part via web worker
+		var jsonloader = new THREE.XHRLoader( this.manager );
+		jsonloader.setCrossOrigin( this.crossOrigin );
+		jsonloader.load( url, function ( data ) {
 
-	this.loadAjaxJSON( this, url, callback, texturePath, binaryPath, callbackProgress );
+			var json = JSON.parse( data );
 
-};
-
-THREE.BinaryLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, binaryPath, callbackProgress ) {
-
-	var xhr = new XMLHttpRequest();
-
-	texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url );
-	binaryPath = binaryPath && ( typeof binaryPath === "string" ) ? binaryPath : this.extractUrlBase( url );
-
-	xhr.onreadystatechange = function () {
-
-		if ( xhr.readyState == 4 ) {
-
-			if ( xhr.status == 200 || xhr.status == 0 ) {
-
-				var json = JSON.parse( xhr.responseText );
-				context.loadAjaxBuffers( json, callback, binaryPath, texturePath, callbackProgress );
-
-			} else {
-
-				console.error( "THREE.BinaryLoader: Couldn't load [" + url + "] [" + xhr.status + "]" );
+			var bufferUrl = binaryPath + json.buffers;
 
-			}
-
-		}
-
-	};
-
-	xhr.open( "GET", url, true );
-	xhr.send( null );
-
-};
-
-THREE.BinaryLoader.prototype.loadAjaxBuffers = function ( json, callback, binaryPath, texturePath, callbackProgress ) {
-
-	var scope = this;
+			var bufferLoader = new THREE.XHRLoader( scope.manager );
+			bufferLoader.setCrossOrigin( scope.crossOrigin );
+			bufferLoader.setResponseType( 'arraybuffer' );
+			bufferLoader.load( bufferUrl, function ( bufData ) {
 
-	var xhr = new XMLHttpRequest(),
-		url = binaryPath + json.buffers;
+				// IEWEBGL needs this ???
+				//buffer = ( new Uint8Array( xhr.responseBody ) ).buffer;
 
-	xhr.addEventListener( 'load', function ( event ) {
+				//// iOS and other XMLHttpRequest level 1 ???
 
-		var buffer = xhr.response;
+				scope.parse( bufData, onLoad, texturePath, json.materials );
 
-		if ( buffer === undefined ) {
+			}, onProgress, onError);
 
-			// IEWEBGL needs this
-			buffer = ( new Uint8Array( xhr.responseBody ) ).buffer;
+		}, onProgress, onError );
 
-		}
+	},
 
-		if ( buffer.byteLength == 0 ) {  // iOS and other XMLHttpRequest level 1
+	setBinaryPath: function ( value ) {
 
-			var buffer = new ArrayBuffer( xhr.responseText.length );
+		this.binaryPath = value;
 
-			var bufView = new Uint8Array( buffer );
+	},
 
-			for ( var i = 0, l = xhr.responseText.length; i < l; i ++ ) {
+	setCrossOrigin: function ( value ) {
 
-				bufView[ i ] = xhr.responseText.charCodeAt( i ) & 0xff;
-
-			}
-
-		}
-
-		scope.createBinModel( buffer, callback, texturePath, json.materials );
-
-	}, false );
-
-	if ( callbackProgress !== undefined ) {
-
-		xhr.addEventListener( 'progress', function ( event ) {
-
-			if ( event.lengthComputable ) {
-
-				callbackProgress( event );
-
-			}
-
-		}, false );
-
-	}
-
-	xhr.addEventListener( 'error', function ( event ) {
-
-		console.error( "THREE.BinaryLoader: Couldn't load [" + url + "] [" + xhr.status + "]" );
-
-	}, false );
-
-
-	xhr.open( "GET", url, true );
-	xhr.responseType = "arraybuffer";
-	if ( xhr.overrideMimeType ) xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
-	xhr.send( null );
-
-};
+		this.crossOrigin = value;
 
-// Binary AJAX parser
+	},
 
-THREE.BinaryLoader.prototype.createBinModel = function ( data, callback, texturePath, jsonMaterials ) {
+	setTexturePath: function ( value ) {
 
-	var Model = function ( texturePath ) {
+		this.texturePath = value;
 
-		var scope = this,
-			currentOffset = 0,
-			md,
-			normals = [],
-			uvs = [],
-			start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv,
-			start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv,
-			tri_size, quad_size,
-			len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv,
-			len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv;
+	},
 
+	parse: function ( data, callback, texturePath, jsonMaterials ) {
 
-		THREE.Geometry.call( this );
+		var Model = function ( texturePath ) {
 
-		md = parseMetaData( data, currentOffset );
+			var scope = this,
+				currentOffset = 0,
+				md,
+				normals = [],
+				uvs = [],
+				start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv,
+				start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv,
+				tri_size, quad_size,
+				len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv,
+				len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv;
 
-		currentOffset += md.header_bytes;
-/*
-		md.vertex_index_bytes = Uint32Array.BYTES_PER_ELEMENT;
-		md.material_index_bytes = Uint16Array.BYTES_PER_ELEMENT;
-		md.normal_index_bytes = Uint32Array.BYTES_PER_ELEMENT;
-		md.uv_index_bytes = Uint32Array.BYTES_PER_ELEMENT;
-*/
-		// buffers sizes
 
-		tri_size =  md.vertex_index_bytes * 3 + md.material_index_bytes;
-		quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes;
+			THREE.Geometry.call( this );
 
-		len_tri_flat      = md.ntri_flat      * ( tri_size );
-		len_tri_smooth    = md.ntri_smooth    * ( tri_size + md.normal_index_bytes * 3 );
-		len_tri_flat_uv   = md.ntri_flat_uv   * ( tri_size + md.uv_index_bytes * 3 );
-		len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 );
+			md = parseMetaData( data, currentOffset );
 
-		len_quad_flat      = md.nquad_flat      * ( quad_size );
-		len_quad_smooth    = md.nquad_smooth    * ( quad_size + md.normal_index_bytes * 4 );
-		len_quad_flat_uv   = md.nquad_flat_uv   * ( quad_size + md.uv_index_bytes * 4 );
-		len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 );
+			currentOffset += md.header_bytes;
+	/*
+			md.vertex_index_bytes = Uint32Array.BYTES_PER_ELEMENT;
+			md.material_index_bytes = Uint16Array.BYTES_PER_ELEMENT;
+			md.normal_index_bytes = Uint32Array.BYTES_PER_ELEMENT;
+			md.uv_index_bytes = Uint32Array.BYTES_PER_ELEMENT;
+	*/
+			// buffers sizes
 
-		// read buffers
+			tri_size =  md.vertex_index_bytes * 3 + md.material_index_bytes;
+			quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes;
 
-		currentOffset += init_vertices( currentOffset );
+			len_tri_flat      = md.ntri_flat      * ( tri_size );
+			len_tri_smooth    = md.ntri_smooth    * ( tri_size + md.normal_index_bytes * 3 );
+			len_tri_flat_uv   = md.ntri_flat_uv   * ( tri_size + md.uv_index_bytes * 3 );
+			len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 );
 
-		currentOffset += init_normals( currentOffset );
-		currentOffset += handlePadding( md.nnormals * 3 );
+			len_quad_flat      = md.nquad_flat      * ( quad_size );
+			len_quad_smooth    = md.nquad_smooth    * ( quad_size + md.normal_index_bytes * 4 );
+			len_quad_flat_uv   = md.nquad_flat_uv   * ( quad_size + md.uv_index_bytes * 4 );
+			len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 );
 
-		currentOffset += init_uvs( currentOffset );
+			// read buffers
 
-		start_tri_flat 		= currentOffset;
-		start_tri_smooth    = start_tri_flat    + len_tri_flat    + handlePadding( md.ntri_flat * 2 );
-		start_tri_flat_uv   = start_tri_smooth  + len_tri_smooth  + handlePadding( md.ntri_smooth * 2 );
-		start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv + handlePadding( md.ntri_flat_uv * 2 );
+			currentOffset += init_vertices( currentOffset );
 
-		start_quad_flat     = start_tri_smooth_uv + len_tri_smooth_uv  + handlePadding( md.ntri_smooth_uv * 2 );
-		start_quad_smooth   = start_quad_flat     + len_quad_flat	   + handlePadding( md.nquad_flat * 2 );
-		start_quad_flat_uv  = start_quad_smooth   + len_quad_smooth    + handlePadding( md.nquad_smooth * 2 );
-		start_quad_smooth_uv = start_quad_flat_uv  + len_quad_flat_uv   + handlePadding( md.nquad_flat_uv * 2 );
+			currentOffset += init_normals( currentOffset );
+			currentOffset += handlePadding( md.nnormals * 3 );
 
-		// have to first process faces with uvs
-		// so that face and uv indices match
+			currentOffset += init_uvs( currentOffset );
 
-		init_triangles_flat_uv( start_tri_flat_uv );
-		init_triangles_smooth_uv( start_tri_smooth_uv );
+			start_tri_flat 		= currentOffset;
+			start_tri_smooth    = start_tri_flat    + len_tri_flat    + handlePadding( md.ntri_flat * 2 );
+			start_tri_flat_uv   = start_tri_smooth  + len_tri_smooth  + handlePadding( md.ntri_smooth * 2 );
+			start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv + handlePadding( md.ntri_flat_uv * 2 );
 
-		init_quads_flat_uv( start_quad_flat_uv );
-		init_quads_smooth_uv( start_quad_smooth_uv );
+			start_quad_flat     = start_tri_smooth_uv + len_tri_smooth_uv  + handlePadding( md.ntri_smooth_uv * 2 );
+			start_quad_smooth   = start_quad_flat     + len_quad_flat	   + handlePadding( md.nquad_flat * 2 );
+			start_quad_flat_uv  = start_quad_smooth   + len_quad_smooth    + handlePadding( md.nquad_smooth * 2 );
+			start_quad_smooth_uv= start_quad_flat_uv  + len_quad_flat_uv   + handlePadding( md.nquad_flat_uv * 2 );
 
-		// now we can process untextured faces
+			// have to first process faces with uvs
+			// so that face and uv indices match
 
-		init_triangles_flat( start_tri_flat );
-		init_triangles_smooth( start_tri_smooth );
+			init_triangles_flat_uv( start_tri_flat_uv );
+			init_triangles_smooth_uv( start_tri_smooth_uv );
 
-		init_quads_flat( start_quad_flat );
-		init_quads_smooth( start_quad_smooth );
+			init_quads_flat_uv( start_quad_flat_uv );
+			init_quads_smooth_uv( start_quad_smooth_uv );
 
-		this.computeFaceNormals();
+			// now we can process untextured faces
 
-		function handlePadding( n ) {
+			init_triangles_flat( start_tri_flat );
+			init_triangles_smooth( start_tri_smooth );
 
-			return ( n % 4 ) ? ( 4 - n % 4 ) : 0;
+			init_quads_flat( start_quad_flat );
+			init_quads_smooth( start_quad_smooth );
 
-		}
+			this.computeFaceNormals();
 
-		function parseMetaData( data, offset ) {
+			function handlePadding( n ) {
 
-			var metaData = {
+				return ( n % 4 ) ? ( 4 - n % 4 ) : 0;
 
-				'signature'               :parseString( data, offset,  12 ),
-				'header_bytes'            :parseUChar8( data, offset + 12 ),
-
-				'vertex_coordinate_bytes' :parseUChar8( data, offset + 13 ),
-				'normal_coordinate_bytes' :parseUChar8( data, offset + 14 ),
-				'uv_coordinate_bytes'     :parseUChar8( data, offset + 15 ),
-
-				'vertex_index_bytes'      :parseUChar8( data, offset + 16 ),
-				'normal_index_bytes'      :parseUChar8( data, offset + 17 ),
-				'uv_index_bytes'          :parseUChar8( data, offset + 18 ),
-				'material_index_bytes'    :parseUChar8( data, offset + 19 ),
-
-				'nvertices'    :parseUInt32( data, offset + 20 ),
-				'nnormals'     :parseUInt32( data, offset + 20 + 4 * 1 ),
-				'nuvs'         :parseUInt32( data, offset + 20 + 4 * 2 ),
-
-				'ntri_flat'      :parseUInt32( data, offset + 20 + 4 * 3 ),
-				'ntri_smooth'    :parseUInt32( data, offset + 20 + 4 * 4 ),
-				'ntri_flat_uv'   :parseUInt32( data, offset + 20 + 4 * 5 ),
-				'ntri_smooth_uv' :parseUInt32( data, offset + 20 + 4 * 6 ),
-
-				'nquad_flat'      :parseUInt32( data, offset + 20 + 4 * 7 ),
-				'nquad_smooth'    :parseUInt32( data, offset + 20 + 4 * 8 ),
-				'nquad_flat_uv'   :parseUInt32( data, offset + 20 + 4 * 9 ),
-				'nquad_smooth_uv' :parseUInt32( data, offset + 20 + 4 * 10 )
-
-			};
-/*
-			console.log( "signature: " + metaData.signature );
-
-			console.log( "header_bytes: " + metaData.header_bytes );
-			console.log( "vertex_coordinate_bytes: " + metaData.vertex_coordinate_bytes );
-			console.log( "normal_coordinate_bytes: " + metaData.normal_coordinate_bytes );
-			console.log( "uv_coordinate_bytes: " + metaData.uv_coordinate_bytes );
-
-			console.log( "vertex_index_bytes: " + metaData.vertex_index_bytes );
-			console.log( "normal_index_bytes: " + metaData.normal_index_bytes );
-			console.log( "uv_index_bytes: " + metaData.uv_index_bytes );
-			console.log( "material_index_bytes: " + metaData.material_index_bytes );
-
-			console.log( "nvertices: " + metaData.nvertices );
-			console.log( "nnormals: " + metaData.nnormals );
-			console.log( "nuvs: " + metaData.nuvs );
-
-			console.log( "ntri_flat: " + metaData.ntri_flat );
-			console.log( "ntri_smooth: " + metaData.ntri_smooth );
-			console.log( "ntri_flat_uv: " + metaData.ntri_flat_uv );
-			console.log( "ntri_smooth_uv: " + metaData.ntri_smooth_uv );
+			}
 
-			console.log( "nquad_flat: " + metaData.nquad_flat );
-			console.log( "nquad_smooth: " + metaData.nquad_smooth );
-			console.log( "nquad_flat_uv: " + metaData.nquad_flat_uv );
-			console.log( "nquad_smooth_uv: " + metaData.nquad_smooth_uv );
+			function parseMetaData( data, offset ) {
+
+				var metaData = {
+
+					'signature'               :parseString( data, offset,  12 ),
+					'header_bytes'            :parseUChar8( data, offset + 12 ),
+
+					'vertex_coordinate_bytes' :parseUChar8( data, offset + 13 ),
+					'normal_coordinate_bytes' :parseUChar8( data, offset + 14 ),
+					'uv_coordinate_bytes'     :parseUChar8( data, offset + 15 ),
+
+					'vertex_index_bytes'      :parseUChar8( data, offset + 16 ),
+					'normal_index_bytes'      :parseUChar8( data, offset + 17 ),
+					'uv_index_bytes'          :parseUChar8( data, offset + 18 ),
+					'material_index_bytes'    :parseUChar8( data, offset + 19 ),
+
+					'nvertices'    :parseUInt32( data, offset + 20 ),
+					'nnormals'     :parseUInt32( data, offset + 20 + 4*1 ),
+					'nuvs'         :parseUInt32( data, offset + 20 + 4*2 ),
+
+					'ntri_flat'      :parseUInt32( data, offset + 20 + 4*3 ),
+					'ntri_smooth'    :parseUInt32( data, offset + 20 + 4*4 ),
+					'ntri_flat_uv'   :parseUInt32( data, offset + 20 + 4*5 ),
+					'ntri_smooth_uv' :parseUInt32( data, offset + 20 + 4*6 ),
+
+					'nquad_flat'      :parseUInt32( data, offset + 20 + 4*7 ),
+					'nquad_smooth'    :parseUInt32( data, offset + 20 + 4*8 ),
+					'nquad_flat_uv'   :parseUInt32( data, offset + 20 + 4*9 ),
+					'nquad_smooth_uv' :parseUInt32( data, offset + 20 + 4*10 )
+
+				};
+	/*
+				console.log( "signature: " + metaData.signature );
+
+				console.log( "header_bytes: " + metaData.header_bytes );
+				console.log( "vertex_coordinate_bytes: " + metaData.vertex_coordinate_bytes );
+				console.log( "normal_coordinate_bytes: " + metaData.normal_coordinate_bytes );
+				console.log( "uv_coordinate_bytes: " + metaData.uv_coordinate_bytes );
+
+				console.log( "vertex_index_bytes: " + metaData.vertex_index_bytes );
+				console.log( "normal_index_bytes: " + metaData.normal_index_bytes );
+				console.log( "uv_index_bytes: " + metaData.uv_index_bytes );
+				console.log( "material_index_bytes: " + metaData.material_index_bytes );
+
+				console.log( "nvertices: " + metaData.nvertices );
+				console.log( "nnormals: " + metaData.nnormals );
+				console.log( "nuvs: " + metaData.nuvs );
+
+				console.log( "ntri_flat: " + metaData.ntri_flat );
+				console.log( "ntri_smooth: " + metaData.ntri_smooth );
+				console.log( "ntri_flat_uv: " + metaData.ntri_flat_uv );
+				console.log( "ntri_smooth_uv: " + metaData.ntri_smooth_uv );
+
+				console.log( "nquad_flat: " + metaData.nquad_flat );
+				console.log( "nquad_smooth: " + metaData.nquad_smooth );
+				console.log( "nquad_flat_uv: " + metaData.nquad_flat_uv );
+				console.log( "nquad_smooth_uv: " + metaData.nquad_smooth_uv );
+
+				var total = metaData.header_bytes
+						  + metaData.nvertices * metaData.vertex_coordinate_bytes * 3
+						  + metaData.nnormals * metaData.normal_coordinate_bytes * 3
+						  + metaData.nuvs * metaData.uv_coordinate_bytes * 2
+						  + metaData.ntri_flat * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes )
+						  + metaData.ntri_smooth * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 )
+						  + metaData.ntri_flat_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.uv_index_bytes*3 )
+						  + metaData.ntri_smooth_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 + metaData.uv_index_bytes*3 )
+						  + metaData.nquad_flat * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes )
+						  + metaData.nquad_smooth * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 )
+						  + metaData.nquad_flat_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.uv_index_bytes*4 )
+						  + metaData.nquad_smooth_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 + metaData.uv_index_bytes*4 );
+				console.log( "total bytes: " + total );
+	*/
+
+				return metaData;
 
-			var total = metaData.header_bytes
-					  + metaData.nvertices * metaData.vertex_coordinate_bytes * 3
-					  + metaData.nnormals * metaData.normal_coordinate_bytes * 3
-					  + metaData.nuvs * metaData.uv_coordinate_bytes * 2
-					  + metaData.ntri_flat * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes )
-					  + metaData.ntri_smooth * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 )
-					  + metaData.ntri_flat_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.uv_index_bytes*3 )
-					  + metaData.ntri_smooth_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 + metaData.uv_index_bytes*3 )
-					  + metaData.nquad_flat * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes )
-					  + metaData.nquad_smooth * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 )
-					  + metaData.nquad_flat_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.uv_index_bytes*4 )
-					  + metaData.nquad_smooth_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 + metaData.uv_index_bytes*4 );
-			console.log( "total bytes: " + total );
-*/
+			}
 
-			return metaData;
+			function parseString( data, offset, length ) {
 
-		}
+				var charArray = new Uint8Array( data, offset, length );
 
-		function parseString( data, offset, length ) {
+				var text = "";
 
-			var charArray = new Uint8Array( data, offset, length );
+				for ( var i = 0; i < length; i ++ ) {
 
-			var text = "";
+					text += String.fromCharCode( charArray[ offset + i ] );
 
-			for ( var i = 0; i < length; i ++ ) {
+				}
 
-				text += String.fromCharCode( charArray[ offset + i ] );
+				return text;
 
 			}
 
-			return text;
+			function parseUChar8( data, offset ) {
 
-		}
+				var charArray = new Uint8Array( data, offset, 1 );
 
-		function parseUChar8( data, offset ) {
+				return charArray[ 0 ];
 
-			var charArray = new Uint8Array( data, offset, 1 );
+			}
 
-			return charArray[ 0 ];
+			function parseUInt32( data, offset ) {
 
-		}
+				var intArray = new Uint32Array( data, offset, 1 );
 
-		function parseUInt32( data, offset ) {
+				return intArray[ 0 ];
 
-			var intArray = new Uint32Array( data, offset, 1 );
+			}
 
-			return intArray[ 0 ];
+			function init_vertices( start ) {
 
-		}
+				var nElements = md.nvertices;
 
-		function init_vertices( start ) {
+				var coordArray = new Float32Array( data, start, nElements * 3 );
 
-			var nElements = md.nvertices;
+				var i, x, y, z;
 
-			var coordArray = new Float32Array( data, start, nElements * 3 );
+				for( i = 0; i < nElements; i ++ ) {
 
-			var i, x, y, z;
+					x = coordArray[ i * 3 ];
+					y = coordArray[ i * 3 + 1 ];
+					z = coordArray[ i * 3 + 2 ];
 
-			for ( i = 0; i < nElements; i ++ ) {
+					scope.vertices.push( new THREE.Vector3( x, y, z ) );
 
-				x = coordArray[ i * 3 ];
-				y = coordArray[ i * 3 + 1 ];
-				z = coordArray[ i * 3 + 2 ];
+				}
 
-				scope.vertices.push( new THREE.Vector3( x, y, z ) );
+				return nElements * 3 * Float32Array.BYTES_PER_ELEMENT;
 
 			}
 
-			return nElements * 3 * Float32Array.BYTES_PER_ELEMENT;
+			function init_normals( start ) {
 
-		}
+				var nElements = md.nnormals;
 
-		function init_normals( start ) {
+				if ( nElements ) {
 
-			var nElements = md.nnormals;
+					var normalArray = new Int8Array( data, start, nElements * 3 );
 
-			if ( nElements ) {
+					var i, x, y, z;
 
-				var normalArray = new Int8Array( data, start, nElements * 3 );
-
-				var i, x, y, z;
+					for( i = 0; i < nElements; i ++ ) {
 
-				for ( i = 0; i < nElements; i ++ ) {
+						x = normalArray[ i * 3 ];
+						y = normalArray[ i * 3 + 1 ];
+						z = normalArray[ i * 3 + 2 ];
 
-					x = normalArray[ i * 3 ];
-					y = normalArray[ i * 3 + 1 ];
-					z = normalArray[ i * 3 + 2 ];
+						normals.push( x/127, y/127, z/127 );
 
-					normals.push( x / 127, y / 127, z / 127 );
+					}
 
 				}
 
-			}
+				return nElements * 3 * Int8Array.BYTES_PER_ELEMENT;
 
-			return nElements * 3 * Int8Array.BYTES_PER_ELEMENT;
+			}
 
-		}
+			function init_uvs( start ) {
 
-		function init_uvs( start ) {
+				var nElements = md.nuvs;
 
-			var nElements = md.nuvs;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var uvArray = new Float32Array( data, start, nElements * 2 );
 
-				var uvArray = new Float32Array( data, start, nElements * 2 );
+					var i, u, v;
 
-				var i, u, v;
+					for( i = 0; i < nElements; i ++ ) {
 
-				for ( i = 0; i < nElements; i ++ ) {
+						u = uvArray[ i * 2 ];
+						v = uvArray[ i * 2 + 1 ];
 
-					u = uvArray[ i * 2 ];
-					v = uvArray[ i * 2 + 1 ];
+						uvs.push( u, v );
 
-					uvs.push( u, v );
+					}
 
 				}
 
-			}
+				return nElements * 2 * Float32Array.BYTES_PER_ELEMENT;
 
-			return nElements * 2 * Float32Array.BYTES_PER_ELEMENT;
+			}
 
-		}
+			function init_uvs3( nElements, offset ) {
 
-		function init_uvs3( nElements, offset ) {
+				var i, uva, uvb, uvc, u1, u2, u3, v1, v2, v3;
 
-			var i, uva, uvb, uvc, u1, u2, u3, v1, v2, v3;
+				var uvIndexBuffer = new Uint32Array( data, offset, 3 * nElements );
 
-			var uvIndexBuffer = new Uint32Array( data, offset, 3 * nElements );
+				for( i = 0; i < nElements; i ++ ) {
 
-			for ( i = 0; i < nElements; i ++ ) {
+					uva = uvIndexBuffer[ i * 3 ];
+					uvb = uvIndexBuffer[ i * 3 + 1 ];
+					uvc = uvIndexBuffer[ i * 3 + 2 ];
 
-				uva = uvIndexBuffer[ i * 3 ];
-				uvb = uvIndexBuffer[ i * 3 + 1 ];
-				uvc = uvIndexBuffer[ i * 3 + 2 ];
+					u1 = uvs[ uva*2 ];
+					v1 = uvs[ uva*2 + 1 ];
 
-				u1 = uvs[ uva * 2 ];
-				v1 = uvs[ uva * 2 + 1 ];
+					u2 = uvs[ uvb*2 ];
+					v2 = uvs[ uvb*2 + 1 ];
 
-				u2 = uvs[ uvb * 2 ];
-				v2 = uvs[ uvb * 2 + 1 ];
+					u3 = uvs[ uvc*2 ];
+					v3 = uvs[ uvc*2 + 1 ];
 
-				u3 = uvs[ uvc * 2 ];
-				v3 = uvs[ uvc * 2 + 1 ];
+					scope.faceVertexUvs[ 0 ].push( [
+						new THREE.Vector2( u1, v1 ),
+						new THREE.Vector2( u2, v2 ),
+						new THREE.Vector2( u3, v3 )
+					] );
 
-				scope.faceVertexUvs[ 0 ].push( [
-					new THREE.Vector2( u1, v1 ),
-					new THREE.Vector2( u2, v2 ),
-					new THREE.Vector2( u3, v3 )
-				] );
+				}
 
 			}
 
-		}
+			function init_uvs4( nElements, offset ) {
 
-		function init_uvs4( nElements, offset ) {
+				var i, uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4;
 
-			var i, uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4;
+				var uvIndexBuffer = new Uint32Array( data, offset, 4 * nElements );
 
-			var uvIndexBuffer = new Uint32Array( data, offset, 4 * nElements );
+				for( i = 0; i < nElements; i ++ ) {
 
-			for ( i = 0; i < nElements; i ++ ) {
+					uva = uvIndexBuffer[ i * 4 ];
+					uvb = uvIndexBuffer[ i * 4 + 1 ];
+					uvc = uvIndexBuffer[ i * 4 + 2 ];
+					uvd = uvIndexBuffer[ i * 4 + 3 ];
 
-				uva = uvIndexBuffer[ i * 4 ];
-				uvb = uvIndexBuffer[ i * 4 + 1 ];
-				uvc = uvIndexBuffer[ i * 4 + 2 ];
-				uvd = uvIndexBuffer[ i * 4 + 3 ];
+					u1 = uvs[ uva*2 ];
+					v1 = uvs[ uva*2 + 1 ];
 
-				u1 = uvs[ uva * 2 ];
-				v1 = uvs[ uva * 2 + 1 ];
+					u2 = uvs[ uvb*2 ];
+					v2 = uvs[ uvb*2 + 1 ];
 
-				u2 = uvs[ uvb * 2 ];
-				v2 = uvs[ uvb * 2 + 1 ];
+					u3 = uvs[ uvc*2 ];
+					v3 = uvs[ uvc*2 + 1 ];
 
-				u3 = uvs[ uvc * 2 ];
-				v3 = uvs[ uvc * 2 + 1 ];
+					u4 = uvs[ uvd*2 ];
+					v4 = uvs[ uvd*2 + 1 ];
 
-				u4 = uvs[ uvd * 2 ];
-				v4 = uvs[ uvd * 2 + 1 ];
+					scope.faceVertexUvs[ 0 ].push( [
+						new THREE.Vector2( u1, v1 ),
+						new THREE.Vector2( u2, v2 ),
+						new THREE.Vector2( u4, v4 )
+					] );
 
-				scope.faceVertexUvs[ 0 ].push( [
-					new THREE.Vector2( u1, v1 ),
-					new THREE.Vector2( u2, v2 ),
-					new THREE.Vector2( u4, v4 )
-				] );
+					scope.faceVertexUvs[ 0 ].push( [
+						new THREE.Vector2( u2, v2 ),
+						new THREE.Vector2( u3, v3 ),
+						new THREE.Vector2( u4, v4 )
+					] );
 
-				scope.faceVertexUvs[ 0 ].push( [
-					new THREE.Vector2( u2, v2 ),
-					new THREE.Vector2( u3, v3 ),
-					new THREE.Vector2( u4, v4 )
-				] );
+				}
 
 			}
 
-		}
+			function init_faces3_flat( nElements, offsetVertices, offsetMaterials ) {
 
-		function init_faces3_flat( nElements, offsetVertices, offsetMaterials ) {
+				var i, a, b, c, m;
 
-			var i, a, b, c, m;
+				var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements );
+				var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
 
-			var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements );
-			var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
+				for( i = 0; i < nElements; i ++ ) {
 
-			for ( i = 0; i < nElements; i ++ ) {
+					a = vertexIndexBuffer[ i * 3 ];
+					b = vertexIndexBuffer[ i * 3 + 1 ];
+					c = vertexIndexBuffer[ i * 3 + 2 ];
 
-				a = vertexIndexBuffer[ i * 3 ];
-				b = vertexIndexBuffer[ i * 3 + 1 ];
-				c = vertexIndexBuffer[ i * 3 + 2 ];
+					m = materialIndexBuffer[ i ];
 
-				m = materialIndexBuffer[ i ];
+					scope.faces.push( new THREE.Face3( a, b, c ) );
 
-				scope.faces.push( new THREE.Face3( a, b, c ) );
+				}
 
 			}
 
-		}
+			function init_faces4_flat( nElements, offsetVertices, offsetMaterials ) {
 
-		function init_faces4_flat( nElements, offsetVertices, offsetMaterials ) {
+				var i, a, b, c, d, m;
 
-			var i, a, b, c, d, m;
+				var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements );
+				var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
 
-			var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements );
-			var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
+				for( i = 0; i < nElements; i ++ ) {
 
-			for ( i = 0; i < nElements; i ++ ) {
+					a = vertexIndexBuffer[ i * 4 ];
+					b = vertexIndexBuffer[ i * 4 + 1 ];
+					c = vertexIndexBuffer[ i * 4 + 2 ];
+					d = vertexIndexBuffer[ i * 4 + 3 ];
 
-				a = vertexIndexBuffer[ i * 4 ];
-				b = vertexIndexBuffer[ i * 4 + 1 ];
-				c = vertexIndexBuffer[ i * 4 + 2 ];
-				d = vertexIndexBuffer[ i * 4 + 3 ];
+					m = materialIndexBuffer[ i ];
 
-				m = materialIndexBuffer[ i ];
+					scope.faces.push( new THREE.Face3( a, b, d ) );
+					scope.faces.push( new THREE.Face3( b, c, d ) );
 
-				scope.faces.push( new THREE.Face3( a, b, d ) );
-				scope.faces.push( new THREE.Face3( b, c, d ) );
+				}
 
 			}
 
-		}
+			function init_faces3_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) {
 
-		function init_faces3_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) {
+				var i, a, b, c, m;
+				var na, nb, nc;
 
-			var i, a, b, c, m;
-			var na, nb, nc;
+				var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements );
+				var normalIndexBuffer = new Uint32Array( data, offsetNormals, 3 * nElements );
+				var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
 
-			var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements );
-			var normalIndexBuffer = new Uint32Array( data, offsetNormals, 3 * nElements );
-			var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
+				for( i = 0; i < nElements; i ++ ) {
 
-			for ( i = 0; i < nElements; i ++ ) {
+					a = vertexIndexBuffer[ i * 3 ];
+					b = vertexIndexBuffer[ i * 3 + 1 ];
+					c = vertexIndexBuffer[ i * 3 + 2 ];
 
-				a = vertexIndexBuffer[ i * 3 ];
-				b = vertexIndexBuffer[ i * 3 + 1 ];
-				c = vertexIndexBuffer[ i * 3 + 2 ];
+					na = normalIndexBuffer[ i * 3 ];
+					nb = normalIndexBuffer[ i * 3 + 1 ];
+					nc = normalIndexBuffer[ i * 3 + 2 ];
 
-				na = normalIndexBuffer[ i * 3 ];
-				nb = normalIndexBuffer[ i * 3 + 1 ];
-				nc = normalIndexBuffer[ i * 3 + 2 ];
+					m = materialIndexBuffer[ i ];
 
-				m = materialIndexBuffer[ i ];
+					var nax = normals[ na*3     ],
+						nay = normals[ na*3 + 1 ],
+						naz = normals[ na*3 + 2 ],
 
-				var nax = normals[ na * 3     ],
-					nay = normals[ na * 3 + 1 ],
-					naz = normals[ na * 3 + 2 ],
+						nbx = normals[ nb*3     ],
+						nby = normals[ nb*3 + 1 ],
+						nbz = normals[ nb*3 + 2 ],
 
-					nbx = normals[ nb * 3     ],
-					nby = normals[ nb * 3 + 1 ],
-					nbz = normals[ nb * 3 + 2 ],
+						ncx = normals[ nc*3     ],
+						ncy = normals[ nc*3 + 1 ],
+						ncz = normals[ nc*3 + 2 ];
 
-					ncx = normals[ nc * 3     ],
-					ncy = normals[ nc * 3 + 1 ],
-					ncz = normals[ nc * 3 + 2 ];
+					scope.faces.push( new THREE.Face3( a, b, c, [
+						new THREE.Vector3( nax, nay, naz ),
+						new THREE.Vector3( nbx, nby, nbz ),
+						new THREE.Vector3( ncx, ncy, ncz )
+					] ) );
 
-				scope.faces.push( new THREE.Face3( a, b, c, [
-					new THREE.Vector3( nax, nay, naz ),
-					new THREE.Vector3( nbx, nby, nbz ),
-					new THREE.Vector3( ncx, ncy, ncz )
-				] ) );
+				}
 
 			}
 
-		}
+			function init_faces4_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) {
 
-		function init_faces4_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) {
+				var i, a, b, c, d, m;
+				var na, nb, nc, nd;
 
-			var i, a, b, c, d, m;
-			var na, nb, nc, nd;
+				var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements );
+				var normalIndexBuffer = new Uint32Array( data, offsetNormals, 4 * nElements );
+				var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
 
-			var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements );
-			var normalIndexBuffer = new Uint32Array( data, offsetNormals, 4 * nElements );
-			var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements );
+				for( i = 0; i < nElements; i ++ ) {
 
-			for ( i = 0; i < nElements; i ++ ) {
+					a = vertexIndexBuffer[ i * 4 ];
+					b = vertexIndexBuffer[ i * 4 + 1 ];
+					c = vertexIndexBuffer[ i * 4 + 2 ];
+					d = vertexIndexBuffer[ i * 4 + 3 ];
 
-				a = vertexIndexBuffer[ i * 4 ];
-				b = vertexIndexBuffer[ i * 4 + 1 ];
-				c = vertexIndexBuffer[ i * 4 + 2 ];
-				d = vertexIndexBuffer[ i * 4 + 3 ];
+					na = normalIndexBuffer[ i * 4 ];
+					nb = normalIndexBuffer[ i * 4 + 1 ];
+					nc = normalIndexBuffer[ i * 4 + 2 ];
+					nd = normalIndexBuffer[ i * 4 + 3 ];
 
-				na = normalIndexBuffer[ i * 4 ];
-				nb = normalIndexBuffer[ i * 4 + 1 ];
-				nc = normalIndexBuffer[ i * 4 + 2 ];
-				nd = normalIndexBuffer[ i * 4 + 3 ];
+					m = materialIndexBuffer[ i ];
 
-				m = materialIndexBuffer[ i ];
+					var nax = normals[ na*3     ],
+						nay = normals[ na*3 + 1 ],
+						naz = normals[ na*3 + 2 ],
 
-				var nax = normals[ na * 3     ],
-					nay = normals[ na * 3 + 1 ],
-					naz = normals[ na * 3 + 2 ],
+						nbx = normals[ nb*3     ],
+						nby = normals[ nb*3 + 1 ],
+						nbz = normals[ nb*3 + 2 ],
 
-					nbx = normals[ nb * 3     ],
-					nby = normals[ nb * 3 + 1 ],
-					nbz = normals[ nb * 3 + 2 ],
+						ncx = normals[ nc*3     ],
+						ncy = normals[ nc*3 + 1 ],
+						ncz = normals[ nc*3 + 2 ],
 
-					ncx = normals[ nc * 3     ],
-					ncy = normals[ nc * 3 + 1 ],
-					ncz = normals[ nc * 3 + 2 ],
+						ndx = normals[ nd*3     ],
+						ndy = normals[ nd*3 + 1 ],
+						ndz = normals[ nd*3 + 2 ];
 
-					ndx = normals[ nd * 3     ],
-					ndy = normals[ nd * 3 + 1 ],
-					ndz = normals[ nd * 3 + 2 ];
+					scope.faces.push( new THREE.Face3( a, b, d, [
+						new THREE.Vector3( nax, nay, naz ),
+						new THREE.Vector3( nbx, nby, nbz ),
+						new THREE.Vector3( ndx, ndy, ndz )
+					] ) );
 
-				scope.faces.push( new THREE.Face3( a, b, d, [
-					new THREE.Vector3( nax, nay, naz ),
-					new THREE.Vector3( nbx, nby, nbz ),
-					new THREE.Vector3( ndx, ndy, ndz )
-				] ) );
+					scope.faces.push( new THREE.Face3( b, c, d, [
+						new THREE.Vector3( nbx, nby, nbz ),
+						new THREE.Vector3( ncx, ncy, ncz ),
+						new THREE.Vector3( ndx, ndy, ndz )
+					] ) );
 
-				scope.faces.push( new THREE.Face3( b, c, d, [
-					new THREE.Vector3( nbx, nby, nbz ),
-					new THREE.Vector3( ncx, ncy, ncz ),
-					new THREE.Vector3( ndx, ndy, ndz )
-				] ) );
+				}
 
 			}
 
-		}
+			function init_triangles_flat( start ) {
 
-		function init_triangles_flat( start ) {
+				var nElements = md.ntri_flat;
 
-			var nElements = md.ntri_flat;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					init_faces3_flat( nElements, start, offsetMaterials );
 
-				var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
-				init_faces3_flat( nElements, start, offsetMaterials );
+				}
 
 			}
 
-		}
+			function init_triangles_flat_uv( start ) {
 
-		function init_triangles_flat_uv( start ) {
+				var nElements = md.ntri_flat_uv;
 
-			var nElements = md.ntri_flat_uv;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
 
-				var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
-				var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					init_faces3_flat( nElements, start, offsetMaterials );
+					init_uvs3( nElements, offsetUvs );
 
-				init_faces3_flat( nElements, start, offsetMaterials );
-				init_uvs3( nElements, offsetUvs );
+				}
 
 			}
 
-		}
+			function init_triangles_smooth( start ) {
 
-		function init_triangles_smooth( start ) {
+				var nElements = md.ntri_smooth;
 
-			var nElements = md.ntri_smooth;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
 
-				var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
-				var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials );
 
-				init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials );
+				}
 
 			}
 
-		}
+			function init_triangles_smooth_uv( start ) {
 
-		function init_triangles_smooth_uv( start ) {
+				var nElements = md.ntri_smooth_uv;
 
-			var nElements = md.ntri_smooth_uv;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
 
-				var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
-				var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
-				var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3;
+					init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials );
+					init_uvs3( nElements, offsetUvs );
 
-				init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials );
-				init_uvs3( nElements, offsetUvs );
+				}
 
 			}
 
-		}
+			function init_quads_flat( start ) {
 
-		function init_quads_flat( start ) {
+				var nElements = md.nquad_flat;
 
-			var nElements = md.nquad_flat;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					init_faces4_flat( nElements, start, offsetMaterials );
 
-				var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
-				init_faces4_flat( nElements, start, offsetMaterials );
+				}
 
 			}
 
-		}
+			function init_quads_flat_uv( start ) {
 
-		function init_quads_flat_uv( start ) {
+				var nElements = md.nquad_flat_uv;
 
-			var nElements = md.nquad_flat_uv;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
 
-				var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
-				var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					init_faces4_flat( nElements, start, offsetMaterials );
+					init_uvs4( nElements, offsetUvs );
 
-				init_faces4_flat( nElements, start, offsetMaterials );
-				init_uvs4( nElements, offsetUvs );
+				}
 
 			}
 
-		}
+			function init_quads_smooth( start ) {
 
-		function init_quads_smooth( start ) {
+				var nElements = md.nquad_smooth;
 
-			var nElements = md.nquad_smooth;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
 
-				var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
-				var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials );
 
-				init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials );
+				}
 
 			}
 
-		}
+			function init_quads_smooth_uv( start ) {
 
-		function init_quads_smooth_uv( start ) {
+				var nElements = md.nquad_smooth_uv;
 
-			var nElements = md.nquad_smooth_uv;
+				if ( nElements ) {
 
-			if ( nElements ) {
+					var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
 
-				var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
-				var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
-				var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4;
+					init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials );
+					init_uvs4( nElements, offsetUvs );
 
-				init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials );
-				init_uvs4( nElements, offsetUvs );
+				}
 
 			}
 
-		}
+		};
 
-	};
+		Model.prototype = Object.create( THREE.Geometry.prototype );
+		Model.prototype.constructor = Model;
 
-	Model.prototype = Object.create( THREE.Geometry.prototype );
-	Model.prototype.constructor = Model;
+		var geometry = new Model( texturePath );
+		var materials = THREE.Loader.prototype.initMaterials( jsonMaterials, texturePath, this.crossOrigin );
 
-	var geometry = new Model( texturePath );
-	var materials = this.initMaterials( jsonMaterials, texturePath );
+		if ( THREE.Loader.prototype.needsTangents( materials ) ) geometry.computeTangents();
 
-	if ( this.needsTangents( materials ) ) geometry.computeTangents();
+		callback( geometry, materials );
 
-	callback( geometry, materials );
+	}
 
 };

Разлика између датотеке није приказан због своје велике величине
+ 457 - 404
examples/js/loaders/ColladaLoader.js


+ 41 - 11
examples/js/loaders/MTLLoader.js

@@ -4,11 +4,9 @@
  * @author angelxuanchang
  */
 
-THREE.MTLLoader = function( baseUrl, options, crossOrigin ) {
+THREE.MTLLoader = function( manager ) {
 
-	this.baseUrl = baseUrl;
-	this.options = options;
-	this.crossOrigin = crossOrigin;
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 };
 
@@ -20,7 +18,7 @@ THREE.MTLLoader.prototype = {
 
 		var scope = this;
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.load( url, function ( text ) {
 
@@ -30,6 +28,24 @@ THREE.MTLLoader.prototype = {
 
 	},
 
+	setBaseUrl: function( value ) {
+
+		this.baseUrl = value;
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
+	setMaterialOptions: function ( value ) {
+
+		this.materialOptions = value;
+
+	},
+
 	/**
 	 * Parses loaded MTL file
 	 * @param text - Content of MTL file
@@ -86,8 +102,9 @@ THREE.MTLLoader.prototype = {
 
 		}
 
-		var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.options );
-		materialCreator.crossOrigin = this.crossOrigin;
+		var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.materialOptions );
+		materialCreator.setCrossOrigin( this.crossOrigin );
+		materialCreator.setManager( this.manager );
 		materialCreator.setMaterials( materialsInfo );
 		return materialCreator;
 
@@ -130,6 +147,18 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 	constructor: THREE.MTLLoader.MaterialCreator,
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
+	setManager: function ( value ) {
+
+		this.manager = value;
+
+	},
+
 	setMaterials: function( materialsInfo ) {
 
 		this.materialsInfo = this.convert( materialsInfo );
@@ -372,10 +401,11 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 	},
 
 
-	loadTexture: function ( url, mapping, onLoad, onError ) {
+	loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
 
 		var texture;
 		var loader = THREE.Loader.Handlers.get( url );
+		var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
 
 		if ( loader !== null ) {
 
@@ -385,8 +415,8 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 			texture = new THREE.Texture();
 
-			loader = new THREE.ImageLoader();
-			loader.crossOrigin = this.crossOrigin;
+			loader = new THREE.ImageLoader( manager );
+			loader.setCrossOrigin( this.crossOrigin );
 			loader.load( url, function ( image ) {
 
 				texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image );
@@ -394,7 +424,7 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 				if ( onLoad ) onLoad( texture );
 
-			} );
+			}, onProgress, onError );
 
 		}
 

+ 6 - 0
examples/js/loaders/OBJLoader.js

@@ -26,6 +26,12 @@ THREE.OBJLoader.prototype = {
 
 	},
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	parse: function ( text ) {
 
 		console.time( 'OBJLoader' );

+ 9 - 2
examples/js/loaders/OBJMTLLoader.js

@@ -19,8 +19,9 @@ THREE.OBJMTLLoader.prototype = {
 
 		var scope = this;
 
-		var mtlLoader = new THREE.MTLLoader( url.substr( 0, url.lastIndexOf( "/" ) + 1 ) );
-		mtlLoader.crossOrigin = scope.crossOrigin;
+		var mtlLoader = new THREE.MTLLoader( this.manager );
+		mtlLoader.setBaseUrl( url.substr( 0, url.lastIndexOf( "/" ) + 1 ) );
+		mtlLoader.setCrossOrigin( this.crossOrigin );
 		mtlLoader.load( mtlurl, function ( materials ) {
 
 			var materialsCreator = materials;
@@ -56,6 +57,12 @@ THREE.OBJMTLLoader.prototype = {
 
 	},
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	/**
 	 * Parses loaded .obj file
 	 * @param data - content of .obj file

+ 6 - 0
examples/js/loaders/PDBLoader.js

@@ -27,6 +27,12 @@ THREE.PDBLoader.prototype = {
 
 	},
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	// Based on CanvasMol PDB parser
 
 	parsePDB: function ( text ) {

+ 17 - 29
examples/js/loaders/PLYLoader.js

@@ -27,7 +27,9 @@
  */
 
 
-THREE.PLYLoader = function () {
+THREE.PLYLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 	this.propertyNameMapping = {};
 
@@ -37,42 +39,30 @@ THREE.PLYLoader.prototype = {
 
 	constructor: THREE.PLYLoader,
 
-	setPropertyNameMapping: function ( mapping ) {
-
-		this.propertyNameMapping = mapping;
-
-	},
-
-	load: function ( url, callback ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 		var scope = this;
-		var request = new XMLHttpRequest();
-
-		request.addEventListener( 'load', function ( event ) {
 
-			var geometry = scope.parse( event.target.response );
+		var loader = new THREE.XHRLoader( this.manager );
+		loader.setCrossOrigin( this.crossOrigin );
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( text ) {
 
-			scope.dispatchEvent( { type: 'load', content: geometry } );
+			onLoad( scope.parse( text ) );
 
-			if ( callback ) callback( geometry );
+		}, onProgress, onError );
 
-		}, false );
-
-		request.addEventListener( 'progress', function ( event ) {
-
-			scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
+	},
 
-		}, false );
+	setCrossOrigin: function ( value ) {
 
-		request.addEventListener( 'error', function () {
+		this.crossOrigin = value;
 
-			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
+	},
 
-		}, false );
+	setPropertyNameMapping: function ( mapping ) {
 
-		request.open( 'GET', url, true );
-		request.responseType = "arraybuffer";
-		request.send( null );
+		this.propertyNameMapping = mapping;
 
 	},
 
@@ -477,6 +467,4 @@ THREE.PLYLoader.prototype = {
 
 	}
 
-};
-
-THREE.EventDispatcher.prototype.apply( THREE.PLYLoader.prototype );
+};

+ 3 - 1
examples/js/loaders/PVRLoader.js

@@ -8,8 +8,10 @@
  *   TODO : implement loadMipmaps option
  */
 
+THREE.PVRLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
-THREE.PVRLoader = function () {
 	this._parser = THREE.PVRLoader.parse;
 };
 

+ 8 - 3
examples/js/loaders/STLLoader.js

@@ -43,14 +43,19 @@ THREE.STLLoader.prototype = {
 
 		var loader = new THREE.XHRLoader( scope.manager );
 		loader.setCrossOrigin( this.crossOrigin );
-		loader.setResponseType('arraybuffer');
-		var request = loader.load( url, function ( text ) {
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( text ) {
 
 			onLoad( scope.parse( text ) );
 
 		}, onProgress, onError );
 
-		return request;
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
 	},
 
 	parse: function ( data ) {

+ 8 - 1
examples/js/loaders/SVGLoader.js

@@ -11,7 +11,7 @@ THREE.SVGLoader = function ( manager ) {
 
 THREE.SVGLoader.prototype = {
 
-	constructor: THREE.MaterialLoader,
+	constructor: THREE.SVGLoader,
 
 	load: function ( url, onLoad, onProgress, onError ) {
 
@@ -29,5 +29,12 @@ THREE.SVGLoader.prototype = {
 
 		}, onProgress, onError );
 
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
 	}
+
 };

+ 15 - 26
examples/js/loaders/VRMLLoader.js

@@ -2,7 +2,11 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
-THREE.VRMLLoader = function () {};
+THREE.VRMLLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
 
 THREE.VRMLLoader.prototype = {
 
@@ -22,35 +26,23 @@ THREE.VRMLLoader.prototype = {
 
 	recordingFieldname: null,
 
-	load: function ( url, callback ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 		var scope = this;
-		var request = new XMLHttpRequest();
-
-		request.addEventListener( 'load', function ( event ) {
-
-			var object = scope.parse( event.target.responseText );
-
-			scope.dispatchEvent( { type: 'load', content: object } );
-
-			if ( callback ) callback( object );
 
-		}, false );
+		var loader = new THREE.XHRLoader( this.manager );
+		loader.setCrossOrigin( this.crossOrigin );
+		loader.load( url, function ( text ) {
 
-		request.addEventListener( 'progress', function ( event ) {
+			onLoad( scope.parse( text ) );
 
-			scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
+		}, onProgress, onError );
 
-		}, false );
-
-		request.addEventListener( 'error', function () {
-
-			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
+	},
 
-		}, false );
+	setCrossOrigin: function ( value ) {
 
-		request.open( 'GET', url, true );
-		request.send( null );
+		this.crossOrigin = value;
 
 	},
 
@@ -833,7 +825,4 @@ THREE.VRMLLoader.prototype = {
 
 	}
 
-};
-
-THREE.EventDispatcher.prototype.apply( THREE.VRMLLoader.prototype );
-
+};

+ 6 - 0
examples/js/loaders/VTKLoader.js

@@ -26,6 +26,12 @@ THREE.VTKLoader.prototype = {
 
 	},
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	parse: function ( data ) {
 
 		var indices = [];

+ 2 - 2
examples/js/loaders/ctm/CTMLoader.js

@@ -8,9 +8,9 @@
  * @author alteredq / http://alteredqualia.com/
  */
 
-THREE.CTMLoader = function ( showStatus ) {
+THREE.CTMLoader = function () {
 
-	THREE.Loader.call( this, showStatus );
+	THREE.Loader.call( this );
 
 };
 

Разлика између датотеке није приказан због своје велике величине
+ 149 - 149
examples/js/loaders/gltf/glTFLoader.js


+ 1 - 1
examples/misc_controls_orbit.html

@@ -77,7 +77,7 @@
 				// world
 
 				var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
-				var material =  new THREE.MeshLambertMaterial( { color:0xffffff, shading: THREE.FlatShading } );
+				var material =  new THREE.MeshPhongMaterial( { color:0xffffff, shading: THREE.FlatShading } );
 
 				for ( var i = 0; i < 500; i ++ ) {
 

+ 1 - 1
examples/misc_controls_trackball.html

@@ -85,7 +85,7 @@
 				scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );
 
 				var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
-				var material =  new THREE.MeshLambertMaterial( { color:0xffffff, shading: THREE.FlatShading } );
+				var material =  new THREE.MeshPhongMaterial( { color:0xffffff, shading: THREE.FlatShading } );
 
 				for ( var i = 0; i < 500; i ++ ) {
 

+ 1 - 1
examples/misc_controls_transform.html

@@ -98,7 +98,7 @@
 						control.setSize( control.size + 0.1 );
 						break;
 					case 189:
-					case 10: // -,_,num-
+					case 109: // -,_,num-
 						control.setSize( Math.max(control.size - 0.1, 0.1 ) );
 						break;
 		            }

+ 17 - 15
examples/webgl_animation_cloth.html

@@ -128,7 +128,7 @@
 
 			var clothGeometry;
 			var sphere;
-			var object, arrow;
+			var object;
 
 			var rotate = true;
 
@@ -143,7 +143,6 @@
 				// scene
 
 				scene = new THREE.Scene();
-
 				scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 );
 
 				// camera
@@ -164,7 +163,7 @@
 				light.position.multiplyScalar( 1.3 );
 
 				light.castShadow = true;
-				//light.shadowCameraVisible = true;
+				// light.shadowCameraVisible = true;
 
 				light.shadowMapWidth = 1024;
 				light.shadowMapHeight = 1024;
@@ -187,7 +186,14 @@
 				clothTexture.wrapS = clothTexture.wrapT = THREE.RepeatWrapping;
 				clothTexture.anisotropy = 16;
 
-				var clothMaterial = new THREE.MeshPhongMaterial( { alphaTest: 0.5, color: 0xffffff, specular: 0x030303, emissive: 0x111111, shiness: 10, map: clothTexture, side: THREE.DoubleSide } );
+				var clothMaterial = new THREE.MeshPhongMaterial( {
+					specular: 0x030303,
+					emissive: 0x111111,
+					shiness: 10,
+					map: clothTexture,
+					side: THREE.DoubleSide,
+					alphaTest: 0.5
+				} );
 
 				// cloth geometry
 				clothGeometry = new THREE.ParametricGeometry( clothFunction, cloth.w, cloth.h );
@@ -202,27 +208,25 @@
 				object = new THREE.Mesh( clothGeometry, clothMaterial );
 				object.position.set( 0, 0, 0 );
 				object.castShadow = true;
-				object.receiveShadow = true;
 				scene.add( object );
 
-				object.customDepthMaterial = new THREE.ShaderMaterial( { uniforms: uniforms, vertexShader: vertexShader, fragmentShader: fragmentShader } );
+				object.customDepthMaterial = new THREE.ShaderMaterial( {
+					uniforms: uniforms,
+					vertexShader: vertexShader,
+					fragmentShader: fragmentShader,
+					side: THREE.DoubleSide
+				} );
 
 				// sphere
 
 				var ballGeo = new THREE.SphereGeometry( ballSize, 20, 20 );
-				var ballMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } );
+				var ballMaterial = new THREE.MeshPhongMaterial( { color: 0xaaaaaa } );
 
 				sphere = new THREE.Mesh( ballGeo, ballMaterial );
 				sphere.castShadow = true;
 				sphere.receiveShadow = true;
 				scene.add( sphere );
 
-				// arrow
-
-				arrow = new THREE.ArrowHelper( new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 0 ), 50, 0xff0000 );
-				arrow.position.set( -200, 0, -200 );
-				// scene.add( arrow );
-
 				// ground
 
 				var groundTexture = THREE.ImageUtils.loadTexture( "textures/terrain/grasslight-big.jpg" );
@@ -327,8 +331,6 @@
 
 				windStrength = Math.cos( time / 7000 ) * 20 + 40;
 				windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) ).normalize().multiplyScalar( windStrength );
-				arrow.setLength( windStrength );
-				arrow.setDirection( windForce );
 
 				simulate(time);
 				render();

+ 4 - 4
examples/webgl_buffergeometry_instancing.html

@@ -145,7 +145,7 @@
 
             var offsets = new THREE.InstancedBufferAttribute( new Float32Array( instances * 3 ), 3, 1, false );
 
-            for ( var i = 0, ul = offsets.length; i < ul; i++ ) {
+            for ( var i = 0, ul = offsets.count; i < ul; i++ ) {
 
                 offsets.setXYZ( i, Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5 );
 
@@ -155,7 +155,7 @@
 
             var colors = new THREE.InstancedBufferAttribute( new Float32Array( instances * 4 ), 4, 1, false );
 
-            for ( var i = 0, ul = colors.length; i < ul; i++ ) {
+            for ( var i = 0, ul = colors.count; i < ul; i++ ) {
 
                 colors.setXYZW( i, Math.random(), Math.random(), Math.random(), Math.random() );
 
@@ -167,7 +167,7 @@
 
             var orientationsStart = new THREE.InstancedBufferAttribute( new Float32Array( instances * 4 ), 4, 1, false );
 
-            for ( var i = 0, ul = orientationsStart.length; i < ul; i++ ) {
+            for ( var i = 0, ul = orientationsStart.count; i < ul; i++ ) {
 
                 vector.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
                 vector.normalize();
@@ -180,7 +180,7 @@
 
             var orientationsEnd = new THREE.InstancedBufferAttribute( new Float32Array( instances * 4 ), 4, 1, false );
 
-            for ( var i = 0, ul = orientationsEnd.length; i < ul; i++ ) {
+            for ( var i = 0, ul = orientationsEnd.count; i < ul; i++ ) {
 
                 vector.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
                 vector.normalize();

+ 3 - 3
examples/webgl_buffergeometry_instancing_dynamic.html

@@ -213,7 +213,7 @@
             var offsets = new THREE.InstancedBufferAttribute( new Float32Array( instances * 3 ), 3, 1, false );
 
             var vector = new THREE.Vector4();
-            for ( var i = 0, ul = offsets.length; i < ul; i++ ) {
+            for ( var i = 0, ul = offsets.count; i < ul; i++ ) {
                 var x = Math.random() * 100 - 50;
                 var y = Math.random() * 100 - 50;
                 var z = Math.random() * 100 - 50;
@@ -228,7 +228,7 @@
 
             orientations = new THREE.InstancedBufferAttribute( new Float32Array( instances * 4 ), 4, 1, true );
 
-            for ( var i = 0, ul = orientations.length; i < ul; i++ ) {
+            for ( var i = 0, ul = orientations.count; i < ul; i++ ) {
 
                 vector.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
                 vector.normalize();
@@ -318,7 +318,7 @@
             var delta = ( time - lastTime ) / 5000;
             tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize();
 
-            for ( var i = 0, ul = orientations.length; i < ul; i++ ) {
+            for ( var i = 0, ul = orientations.count; i < ul; i++ ) {
                 var index = i * 4;
                 currentQ.set( orientations.array[index], orientations.array[index + 1], orientations.array[index + 2], orientations.array[index + 3] );
                 currentQ.multiply( tmpQ );

+ 3 - 3
examples/webgl_buffergeometry_instancing_interleaved_dynamic.html

@@ -185,7 +185,7 @@
         var offsets = new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 );
 
         var vector = new THREE.Vector4();
-        for ( var i = 0, ul = offsets.length; i < ul; i++ ) {
+        for ( var i = 0, ul = offsets.count; i < ul; i++ ) {
             var x = Math.random() * 100 - 50;
             var y = Math.random() * 100 - 50;
             var z = Math.random() * 100 - 50;
@@ -199,7 +199,7 @@
 
         orientations = new THREE.InterleavedBufferAttribute( instanceBuffer, 4, 4 );
 
-        for ( var i = 0, ul = orientations.length; i < ul; i++ ) {
+        for ( var i = 0, ul = orientations.count; i < ul; i++ ) {
 
             vector.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
             vector.normalize();
@@ -290,7 +290,7 @@
         var delta = ( time - lastTime ) / 5000;
         tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize();
 
-        for ( var i = 0, ul = orientations.length; i < ul; i++ ) {
+        for ( var i = 0, ul = orientations.count; i < ul; i++ ) {
             var index = i * instanceBuffer.stride + orientations.offset;
             currentQ.set( instanceBuffer.array[index], instanceBuffer.array[index + 1], instanceBuffer.array[index + 2], instanceBuffer.array[index + 3] );
             currentQ.multiply( tmpQ );

+ 1 - 8
examples/webgl_effects_parallaxbarrier.html

@@ -151,8 +151,7 @@
 			var windowHalfX = window.innerWidth / 2;
 			var windowHalfY = window.innerHeight / 2;
 
-			var loader = new THREE.BinaryLoader( true );
-			document.body.appendChild( loader.statusDomElement );
+			var loader = new THREE.BinaryLoader();
 
 			init();
 			animate();
@@ -486,7 +485,6 @@
 
 					if ( ! CARS[ car ].object ) {
 
-						loader.statusDomElement.style.display = "block";
 						loader.load( CARS[ car ].url, function( geometry ) { createScene( geometry, car ) } );
 
 					} else {
@@ -562,8 +560,6 @@
 
 			function createScene( geometry, car ) {
 
-				loader.statusDomElement.innerHTML = "Creating model ...";
-
 				var m = new THREE.MeshFaceMaterial(),
 					s = CARS[ car ].scale * 1,
 					r = CARS[ car ].init_rotation,
@@ -591,9 +587,6 @@
 
 				switchCar( car );
 
-				loader.statusDomElement.style.display = "none";
-				loader.statusDomElement.innerHTML = "Loading model ...";
-
 			}
 
 			function onDocumentMouseMove(event) {

+ 1 - 4
examples/webgl_geometry_large_mesh.html

@@ -162,8 +162,7 @@
 				bcanvas.addEventListener( "click", toggleCanvas, false );
 				bwebgl.addEventListener( "click", toggleWebGL, false );
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
+				loader = new THREE.BinaryLoader();
 
 				var start = Date.now();
 
@@ -174,8 +173,6 @@
 					addMesh( geometry, 0.75, -300, 0, 0, 0,0,0, new THREE.MeshPhongMaterial( { color: 0x111111, specular: 0xffaa00, shininess: 10 } ) );
 					addMesh( geometry, 0.75, -900, 0, 0, 0,0,0, new THREE.MeshPhongMaterial( { color: 0x555555, specular: 0x666666, shininess: 10 } ) );
 
-					loader.statusDomElement.style.display = "none";
-
 					log( "geometry.vertices: " + geometry.vertices.length );
 					log( "geometry.faces: " + geometry.faces.length );
 

+ 14 - 5
examples/webgl_interactive_draggablecubes.html

@@ -162,7 +162,13 @@
 				if ( SELECTED ) {
 
 					var intersects = raycaster.intersectObject( plane );
-					SELECTED.position.copy( intersects[ 0 ].point.sub( offset ) );
+
+					if ( intersects.length > 0 ) {
+
+						SELECTED.position.copy( intersects[ 0 ].point.sub( offset ) );
+
+					}
+
 					return;
 
 				}
@@ -201,9 +207,7 @@
 
 				event.preventDefault();
 
-				var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 ).unproject( camera );
-
-				var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
+				raycaster.setFromCamera( mouse, camera );
 
 				var intersects = raycaster.intersectObjects( objects );
 
@@ -214,7 +218,12 @@
 					SELECTED = intersects[ 0 ].object;
 
 					var intersects = raycaster.intersectObject( plane );
-					offset.copy( intersects[ 0 ].point ).sub( plane.position );
+
+					if ( intersects.length > 0 ) {
+
+						offset.copy( intersects[ 0 ].point ).sub( plane.position );
+
+					}
 
 					container.style.cursor = 'move';
 

+ 1 - 4
examples/webgl_lights_pointlights.html

@@ -68,8 +68,7 @@
 
 				scene = new THREE.Scene();
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
+				loader = new THREE.BinaryLoader();
 
 				var callback = function( geometry ) {
 
@@ -77,8 +76,6 @@
 					object.scale.x = object.scale.y = object.scale.z = 0.80;
 					scene.add( object );
 
-					loader.statusDomElement.style.display = "none";
-
 				};
 
 				loader.load( "obj/walt/WaltHead_bin.js", callback );

+ 2 - 7
examples/webgl_loader_ctm_materials.html

@@ -178,8 +178,7 @@
 
 				// new way via CTMLoader and separate parts
 
-				loaderCTM = new THREE.CTMLoader( true );
-				document.body.appendChild( loaderCTM.statusDomElement );
+				loaderCTM = new THREE.CTMLoader();
 
 				var position = new THREE.Vector3( -105, -78, -40 );
 				var scale = new THREE.Vector3( 30, 30, 30 );
@@ -197,8 +196,6 @@
 
 					}
 
-					loaderCTM.statusDomElement.style.display = "none";
-
 					var end = Date.now();
 
 					console.log( "load time:", end - start, "ms" );
@@ -269,10 +266,8 @@
 
 			function createScene( geometry, materials, x, y, z, s ) {
 
-				loader.statusDomElement.style.display = "none";
-
 				geometry.center();
-				
+
 				hackMaterials( materials );
 
 				var material = new THREE.MeshFaceMaterial( materials );

+ 1 - 3
examples/webgl_loader_ply.html

@@ -90,9 +90,8 @@
 				// PLY file
 
 				var loader = new THREE.PLYLoader();
-				loader.addEventListener( 'load', function ( event ) {
+				loader.load( './models/ply/ascii/dolphins.ply', function ( geometry ) {
 
-					var geometry = event.content;
 					var material = new THREE.MeshPhongMaterial( { color: 0x0055ff, specular: 0x111111, shininess: 200 } );
 					var mesh = new THREE.Mesh( geometry, material );
 
@@ -106,7 +105,6 @@
 					scene.add( mesh );
 
 				} );
-				loader.load( './models/ply/ascii/dolphins.ply' );
 
 				// Lights
 

+ 2 - 3
examples/webgl_loader_vrml.html

@@ -79,12 +79,11 @@
 				camera.add( dirLight.target );
 
 				var loader = new THREE.VRMLLoader();
-				loader.addEventListener( 'load', function ( event ) {
+				loader.load( 'models/vrml/house.wrl', function ( object ) {
 
-					scene.add(event.content);
+					scene.add( object );
 
 				} );
-				loader.load( "models/vrml/house.wrl" );
 
 				// renderer
 

+ 1 - 5
examples/webgl_materials_bumpmap.html

@@ -171,9 +171,7 @@
 
 				var material = new THREE.MeshPhongMaterial( { color: 0x552811, specular: 0x333333, shininess: 25, bumpMap: mapHeight, bumpScale: 19, metal: false } );
 
-				loader = new THREE.JSONLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.JSONLoader();
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100, material ) } );
 
 				renderer = new THREE.WebGLRenderer( { antialias: false } );
@@ -218,8 +216,6 @@
 
 				scene.add( mesh );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			//

+ 1 - 5
examples/webgl_materials_bumpmap_skin.html

@@ -172,9 +172,7 @@
 
 				//
 
-				loader = new THREE.JSONLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.JSONLoader();
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100 ) } );
 
 				//
@@ -294,8 +292,6 @@
 
 				scene.add( mesh );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			//

+ 1 - 8
examples/webgl_materials_cars.html

@@ -151,8 +151,7 @@
 			var windowHalfX = window.innerWidth / 2;
 			var windowHalfY = window.innerHeight / 2;
 
-			var loader = new THREE.BinaryLoader( true );
-			document.body.appendChild( loader.statusDomElement );
+			var loader = new THREE.BinaryLoader();
 
 			init();
 			animate();
@@ -494,7 +493,6 @@
 
 					if ( ! CARS[ car ].object ) {
 
-						loader.statusDomElement.style.display = "block";
 						loader.load( CARS[ car ].url, function( geometry ) { createScene( geometry, car ) } );
 
 					} else {
@@ -570,8 +568,6 @@
 
 			function createScene( geometry, car ) {
 
-				loader.statusDomElement.innerHTML = "Creating model ...";
-
 				var m = new THREE.MeshFaceMaterial(),
 					s = CARS[ car ].scale * 1,
 					r = CARS[ car ].init_rotation,
@@ -602,9 +598,6 @@
 
 				switchCar( car );
 
-				loader.statusDomElement.style.display = "none";
-				loader.statusDomElement.innerHTML = "Loading model ...";
-
 			}
 
 			function onDocumentMouseMove(event) {

+ 1 - 5
examples/webgl_materials_cubemap.html

@@ -152,9 +152,7 @@
 
 				//
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.BinaryLoader();
 				loader.load( "obj/walt/WaltHead_bin.js", function( geometry ) { createScene( geometry, cubeMaterial1, cubeMaterial2, cubeMaterial3 ) } );
 
 				//
@@ -199,8 +197,6 @@
 				mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
 				scene.add( mesh );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			function onDocumentMouseMove(event) {

+ 1 - 5
examples/webgl_materials_cubemap_refraction.html

@@ -140,9 +140,7 @@
 				stats.domElement.style.zIndex = 100;
 				container.appendChild( stats.domElement );
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.BinaryLoader();
 				loader.load( 'obj/lucy/Lucy100k_bin.js', function( geometry ) { createScene( geometry, cubeMaterial1, cubeMaterial2, cubeMaterial3 ) } );
 
 				document.addEventListener('mousemove', onDocumentMouseMove, false);
@@ -189,8 +187,6 @@
 				mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
 				scene.add( mesh );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			function onDocumentMouseMove(event) {

+ 1 - 5
examples/webgl_materials_normaldisplacementmap.html

@@ -194,9 +194,7 @@
 
 				//
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.BinaryLoader();
 				loader.load( "obj/ninja/NinjaLo_bin.js", function( geometry ) { createScene( geometry, scale, material1, material2 ) } );
 
 				//
@@ -272,8 +270,6 @@
 				mesh2.receiveShadow = true;
 				scene.add( mesh2 );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			function onDocumentMouseMove(event) {

+ 1 - 5
examples/webgl_materials_normalmap.html

@@ -128,9 +128,7 @@
 					normalScale: new THREE.Vector2( 0.8, 0.8 )
 				} );
 
-				loader = new THREE.JSONLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.JSONLoader();
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100, material ) } );
 
 				renderer = new THREE.WebGLRenderer( { antialias: false } );
@@ -201,8 +199,6 @@
 
 				scene.add( mesh1 );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			//

+ 1 - 5
examples/webgl_materials_skin.html

@@ -148,9 +148,7 @@
 
 				// LOADER
 
-				loader = new THREE.JSONLoader( true );
-				document.body.appendChild( loader.statusDomElement );
-
+				loader = new THREE.JSONLoader();
 				loader.load(  "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100, material ) } );
 
 				// RENDERER
@@ -283,8 +281,6 @@
 
 				scene.add( mesh );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			function onDocumentMouseMove( event ) {

+ 1 - 1
examples/webgl_morphtargets_horse.html

@@ -59,7 +59,7 @@
 				light.position.set( -1, -1, -1 ).normalize();
 				scene.add( light );
 
-				var loader = new THREE.JSONLoader( true );
+				var loader = new THREE.JSONLoader();
 				loader.load( "models/animated/horse.js", function( geometry ) {
 
 					mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x606060, morphTargets: true } ) );

+ 24 - 1
examples/webgl_multiple_renderers.html

@@ -99,6 +99,8 @@
 
 			function animate() {
 
+				updateScene();
+
 				for ( var i = 0; i < apps.length; ++i ) {
 
 					apps[ i ].animate();
@@ -223,7 +225,10 @@
 				group2.rotation.x = 0;
 				scene.add( group2 );
 
-				group3 = THREE.SceneUtils.createMultiMaterialObject( geometry3, materials );
+				group3 = new THREE.Group();
+				group3.add( new THREE.Mesh( new THREE.BufferGeometry().fromGeometry( geometry3 ), materials[0] ) );
+				group3.add( new THREE.Mesh( geometry3, materials[1] ) );
+				group3.name = 'rotating ball';
 				group3.position.x = 0;
 				group3.rotation.x = 0;
 				scene.add( group3 );
@@ -231,6 +236,24 @@
 				return scene;
 			}
 
+			function updateScene () {
+
+				var group = scene.getObjectByName( 'rotating ball' )
+				group.rotation.x += Math.PI / 600;
+
+				var geometry = group.children[0].geometry;
+				var array = geometry.attributes.color.array;
+
+				for (var i = 0; i < array.length; i ++) {
+
+					array[i] = ( array[i] + 0.99 ) % 1.0;
+
+				}
+
+				geometry.attributes.color.needsUpdate = true;
+
+			}
+
 			function App( container, fullWidth, fullHeight ) {
 
 				var container, stats;

+ 1 - 1
examples/webgl_nearestneighbour.html

@@ -70,7 +70,7 @@
 		<script>		
 		
 			var camera, scene, renderer;
-			var geometry, material, mesh;
+			var geometry, mesh;
 			var controls;
 
 			var objects = [];

+ 2 - 7
examples/webgl_particles_dynamic.html

@@ -95,11 +95,8 @@
 
 				//
 
-				aloader = new THREE.JSONLoader( );
-				bloader = new THREE.BinaryLoader( true );
-
-				document.body.appendChild( bloader.statusDomElement );
-
+				aloader = new THREE.JSONLoader();
+				bloader = new THREE.BinaryLoader();
 				aloader.load( "obj/terrain.js", function( geometry ) {
 
 					createMesh( geometry, scene, 16.8, -11000, -200,  -5000, 0x00ff44, false );
@@ -265,8 +262,6 @@
 
 				}
 
-				bloader.statusDomElement.style.display = "none";
-
 				meshes.push( {
 					mesh: mesh, vertices: geometry.vertices, vertices_tmp: vertices_tmp, vl: vl,
 					down: 0, up: 0, direction: 0, speed: 35, delay: Math.floor( 200 + 200 * Math.random() ),

+ 1 - 4
examples/webgl_postprocessing_advanced.html

@@ -111,8 +111,7 @@
 				directionalLight.position.set( 0, -0.1, 1 ).normalize();
 				sceneModel.add( directionalLight );
 
-				loader = new THREE.JSONLoader( true );
-				document.body.appendChild( loader.statusDomElement );
+				loader = new THREE.JSONLoader();
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createMesh( geometry, sceneModel, 100 ) } );
 
 				//
@@ -349,8 +348,6 @@
 
 				scene.add( mesh );
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 
 			//

+ 222 - 0
examples/webgl_postprocessing_ssao.html

@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+
+<!--Reference: 
+SSAO algo: http://devlog-martinsh.blogspot.tw/2011/12/ssao-shader-update-v12.html?showComment=1398158188712#c1563204765906693531
+log depth http://outerra.blogspot.tw/2013/07/logarithmic-depth-buffer-optimizations.html
+convert the exponential depth to a linear value: http://www.ozone3d.net/blogs/lab/20090206/how-to-linearize-the-depth-value/
+Spiral sampling http://web.archive.org/web/20120421191837/http://www.cgafaq.info/wiki/Evenly_distributed_points_on_sphere-->
+
+<html lang="en">
+	<head>
+		<title>three.js webgl - postprocessing - Screen Space Ambient Occlusion</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				font-weight: bold;
+			}
+
+			a {
+				color:#0078ff;
+			}
+
+			#info {
+				color:#fff;
+				position: relative;
+				top: 0px;
+				width: 50em;
+				margin: 0 auto -2.1em;
+				padding: 5px;
+				z-index:100;
+			}
+		</style>
+	</head>
+	<body>
+		<script src="../build/three.js"></script>
+		<script src="js/shaders/SSAOShader.js"></script>
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src='js/libs/dat.gui.min.js'></script>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - webgl screen space ambient occlusion example -
+			shader by <a href="http://alteredqualia.com">alteredq</a>
+		</div>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+						
+			var container, stats;   
+			var camera, scene, renderer,
+				depthMaterial;
+			var group;
+			var depthScale = 1.0;
+			var height = window.innerHeight;
+			var postprocessing = { enabled : true, renderMode: 0 }; // renderMode: 0('framebuffer'), 1('onlyAO')
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				renderer = new THREE.WebGLRenderer( { antialias: false } );
+				renderer.setClearColor( 0xa0a0a0 );
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+				var depthShader = THREE.ShaderLib[ "depthRGBA" ];
+				var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );
+
+				depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader,
+					uniforms: depthUniforms, blending: THREE.NoBlending } );
+								
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 100, 700 );
+				camera.position.z = 500;
+
+				scene = new THREE.Scene();
+
+				group = new THREE.Object3D();
+				scene.add( group );
+		   
+				var geometry = new THREE.IcosahedronGeometry( 5, 1 );
+				for ( var i = 0; i < 200; i ++ ) {
+				
+					var material = new THREE.MeshBasicMaterial();
+					material.color.r = Math.random();
+					material.color.g = Math.random();
+					material.color.b = Math.random();
+				   
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.position.x = Math.random() * 400 - 200;
+					mesh.position.y = Math.random() * 400 - 200;
+					mesh.position.z = Math.random() * 400 - 200;
+					mesh.rotation.x = Math.random();
+					mesh.rotation.y = Math.random();
+					mesh.rotation.z = Math.random();
+
+					mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 10 + 1;
+					group.add( mesh );
+				}
+				
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				// postprocessing                
+				initPostprocessing();
+			
+				// Init gui
+				var gui = new dat.GUI();
+				gui.add( postprocessing, "enabled" ).onChange();
+				gui.add( postprocessing, "renderMode", { framebuffer: 0, onlyAO: 1 } ).onChange( renderModeChange ).listen();
+
+				window.addEventListener( 'resize', onWindowResize, false );
+			}
+
+			function renderModeChange( value ) {
+
+				if ( value == 0 ) { // framebuffer
+					postprocessing.ssao_uniforms[ 'onlyAO' ].value = false;
+				} else if ( value == 1 ) {  // onlyAO
+					postprocessing.ssao_uniforms[ 'onlyAO' ].value = true;
+				} else {
+					console.error( "Not define renderModeChange type: " + value );
+				}
+			}
+			
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				initPostprocessing();
+			}
+
+			function initPostprocessing() {
+				
+				postprocessing.scene = new THREE.Scene();
+				postprocessing.camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2,  window.innerHeight / 2, window.innerHeight / - 2, -1000, 1000 );
+				postprocessing.camera.position.z = 1;                
+				
+				postprocessing.scene.add( postprocessing.camera );
+				
+				var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter };
+				postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget( window.innerWidth * depthScale, height * depthScale, pars );
+				postprocessing.rtTextureColor = new THREE.WebGLRenderTarget( window.innerWidth, height, pars );
+
+				// Setup SSAO material
+				var ssao_shader = THREE.SSAOShader;
+				postprocessing.ssao_uniforms = THREE.UniformsUtils.clone( ssao_shader.uniforms );
+				postprocessing.ssao_uniforms[ "tDiffuse" ].value = postprocessing.rtTextureColor; 
+				postprocessing.ssao_uniforms[ "tDepth" ].value = postprocessing.rtTextureDepth; 
+				postprocessing.ssao_uniforms[ 'size' ].value.set( window.innerWidth*depthScale, window.innerHeight*depthScale );
+				postprocessing.ssao_uniforms[ 'cameraNear' ].value = camera.near;
+				postprocessing.ssao_uniforms[ 'cameraFar' ].value = camera.far;
+				postprocessing.ssao_uniforms[ 'onlyAO' ].value = ( postprocessing.renderMode == 1 );
+				
+				postprocessing.materialSSAO = new THREE.ShaderMaterial( {
+					
+					uniforms: postprocessing.ssao_uniforms,
+					vertexShader: ssao_shader.vertexShader,
+					fragmentShader: ssao_shader.fragmentShader
+				});
+				
+				postprocessing.ssaoQuad = new THREE.Mesh( new THREE.PlaneBufferGeometry( window.innerWidth, window.innerHeight ), postprocessing.materialSSAO );
+				postprocessing.ssaoQuad.position.z = -500;
+				postprocessing.scene.add( postprocessing.ssaoQuad );
+			}
+			
+			function shaderUpdate() {
+				postprocessing.materialSSAO.needsUpdate = true;
+			}
+			
+			function animate() {
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+			}
+
+			function render() {                                
+				var timer = performance.now();
+				group.rotation.x = timer * 0.0002;
+				group.rotation.y = timer * 0.0001;
+
+				if ( postprocessing.enabled ) {
+					
+					renderer.clear();
+
+					// Render scene into texture
+					scene.overrideMaterial = null;
+					renderer.render( scene, camera, postprocessing.rtTextureColor , true );
+
+					// Render depth into texture                    
+					scene.overrideMaterial = depthMaterial;
+					renderer.render( scene, camera, postprocessing.rtTextureDepth, true );
+
+					// Render SSAO composite
+					renderer.render( postprocessing.scene, postprocessing.camera );
+					
+				} else {
+					
+					scene.overrideMaterial = null;
+					renderer.clear();
+					renderer.render( scene, camera );
+				}
+			}
+			
+		</script>
+	</body>
+</html>

+ 33 - 0
src/Three.js

@@ -14,6 +14,39 @@ if ( typeof module === 'object' ) {
 
 // polyfills
 
+( function () {
+
+	var lastTime = 0;
+	var vendors = [ 'ms', 'moz', 'webkit', 'o' ];
+
+	for ( var x = 0; x < vendors.length && !self.requestAnimationFrame; ++ x ) {
+
+		self.requestAnimationFrame = self[ vendors[ x ] + 'RequestAnimationFrame' ];
+		self.cancelAnimationFrame = self[ vendors[ x ] + 'CancelAnimationFrame' ] || self[ vendors[ x ] + 'CancelRequestAnimationFrame' ];
+
+	}
+
+	if ( self.requestAnimationFrame === undefined && self.setTimeout !== undefined ) {
+
+		self.requestAnimationFrame = function ( callback ) {
+
+			var currTime = Date.now(), timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
+			var id = self.setTimeout( function () { callback( currTime + timeToCall ) }, timeToCall );
+			lastTime = currTime + timeToCall;
+			return id;
+
+		};
+
+	}
+
+	if ( self.cancelAnimationFrame === undefined && self.clearTimeout !== undefined ) {
+
+		self.cancelAnimationFrame = function ( id ) { self.clearTimeout( id ) };
+
+	}
+
+}() );
+
 if ( Math.sign === undefined ) {
 
 	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign

+ 9 - 3
src/core/BufferAttribute.js

@@ -9,7 +9,7 @@ THREE.BufferAttribute = function ( array, itemSize ) {
 	this.array = array;
 	this.itemSize = itemSize;
 
-	this.needsUpdate = false;
+	this.version = 0;
 
 };
 
@@ -19,8 +19,8 @@ THREE.BufferAttribute.prototype = {
 
 	get length () {
 
-		console.warn( 'THREE.BufferAttribute: .length has been renamed to .count.' );
-		return this.count;
+		console.warn( 'THREE.BufferAttribute: .length has been deprecated. Please use .count.' );
+		return this.array.length;
 
 	},
 
@@ -30,6 +30,12 @@ THREE.BufferAttribute.prototype = {
 
 	},
 
+	set needsUpdate( value ) {
+
+		if ( value === true ) this.version ++;
+
+	},
+
 	copyAt: function ( index1, attribute, index2 ) {
 
 		index1 *= this.itemSize;

+ 2 - 2
src/core/BufferGeometry.js

@@ -1082,7 +1082,7 @@ THREE.BufferGeometry.prototype = {
 				itemSize: attribute.itemSize,
 				type: attribute.array.constructor.name,
 				array: array
-			}
+			};
 
 		}
 
@@ -1097,7 +1097,7 @@ THREE.BufferGeometry.prototype = {
 			data.data.boundingSphere = {
 				center: boundingSphere.center.toArray(),
 				radius: boundingSphere.radius
-			}
+			};
 
 		}
 

+ 13 - 1
src/core/InterleavedBuffer.js

@@ -9,7 +9,7 @@ THREE.InterleavedBuffer = function ( array, stride, dynamic ) {
 	this.array = array;
 	this.stride = stride;
 
-	this.needsUpdate = false;
+	this.version = 0;
 
 	this.dynamic = dynamic || false;
 	this.updateRange = { offset: 0, count: -1 };
@@ -26,6 +26,18 @@ THREE.InterleavedBuffer.prototype = {
 
 	},
 
+	get count () {
+
+		return this.array.length / this.stride;
+
+	},
+
+	set needsUpdate( value ) {
+
+		if ( value === true ) this.version ++;
+
+	},
+
 	copyAt: function ( index1, attribute, index2 ) {
 
 		index1 *= this.stride;

+ 2 - 2
src/core/InterleavedBufferAttribute.js

@@ -19,8 +19,8 @@ THREE.InterleavedBufferAttribute.prototype = {
 
 	get length() {
 
-		console.warn( 'THREE.InterleavedBufferAttribute: .length has been renamed to .count.' );
-		return this.count;
+		console.warn( 'THREE.BufferAttribute: .length has been deprecated. Please use .count.' );
+		return this.array.length;
 
 	},
 

+ 1 - 1
src/extras/geometries/EdgesGeometry.js

@@ -11,7 +11,7 @@ THREE.EdgesGeometry = function ( geometry, thresholdAngle ) {
 	var thresholdDot = Math.cos( THREE.Math.degToRad( thresholdAngle ) );
 
 	var edge = [ 0, 0 ], hash = {};
-	var sortFunction = function ( a, b ) { return a - b };
+	var sortFunction = function ( a, b ) { return a - b; };
 
 	var keys = [ 'a', 'b', 'c' ];
 

+ 10 - 8
src/extras/geometries/SphereBufferGeometry.js

@@ -21,7 +21,7 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 
 	radius = radius || 50;
 
-	widthSegments = Math.max( 2, Math.floor( widthSegments ) || 8 );
+	widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 );
 	heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );
 
 	phiStart = phiStart !== undefined ? phiStart : 0;
@@ -30,10 +30,12 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 	thetaStart = thetaStart !== undefined ? thetaStart : 0;
 	thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;
 
+	var thetaEnd = thetaStart + thetaLength;
+
 	var vertexCount = ( ( widthSegments + 1 ) * ( heightSegments + 1 ) );
 
 	var positions = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 );
-	var normals = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3);
+	var normals = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 );
 	var uvs = new THREE.BufferAttribute( new Float32Array( vertexCount * 2 ), 2 );
 
 	var index = 0, vertices = [], normal = new THREE.Vector3();
@@ -60,7 +62,7 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 
 			verticesRow.push( index );
 
-			index++;
+			index ++;
 
 		}
 
@@ -74,13 +76,13 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 
 		for ( var x = 0; x < widthSegments; x ++ ) {
 
-			var v1 = vertices[ y     ][ x + 1 ];
-			var v2 = vertices[ y     ][ x     ];
-			var v3 = vertices[ y + 1 ][ x     ];
+			var v1 = vertices[ y ][ x + 1 ];
+			var v2 = vertices[ y ][ x ];
+			var v3 = vertices[ y + 1 ][ x ];
 			var v4 = vertices[ y + 1 ][ x + 1 ];
 
-			if ( y !== 0 ) indices.push( v1, v2, v4 );
-			if ( y !== heightSegments - 1 ) indices.push( v2, v3, v4 );
+			if ( y !== 0 || thetaStart > 0 ) indices.push( v1, v2, v4 );
+			if ( y !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( v2, v3, v4 );
 
 		}
 

+ 1 - 1
src/extras/geometries/SphereGeometry.js

@@ -22,7 +22,7 @@ THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStar
 
 	radius = radius || 50;
 
-	widthSegments = Math.max( 2, Math.floor( widthSegments ) || 8 );
+	widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 );
 	heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );
 
 	phiStart = phiStart !== undefined ? phiStart : 0;

+ 11 - 2
src/loaders/BinaryTextureLoader.js

@@ -4,7 +4,9 @@
  * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...)
  */
 
-THREE.DataTextureLoader = THREE.BinaryTextureLoader = function () {
+THREE.DataTextureLoader = THREE.BinaryTextureLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 	// override in sub classes
 	this._parser = null;
@@ -21,7 +23,8 @@ THREE.BinaryTextureLoader.prototype = {
 
 		var texture = new THREE.DataTexture( );
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
+		loader.setCrossOrigin( this.crossOrigin );
 		loader.setResponseType( 'arraybuffer' );
 
 		loader.load( url, function ( buffer ) {
@@ -82,6 +85,12 @@ THREE.BinaryTextureLoader.prototype = {
 
 		return texture;
 
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
 	}
 
 };

+ 1 - 1
src/loaders/Cache.js

@@ -30,7 +30,7 @@ THREE.Cache = {
 
 	clear: function () {
 
-		this.files = {}
+		this.files = {};
 
 	}
 

+ 14 - 5
src/loaders/CompressedTextureLoader.js

@@ -4,7 +4,9 @@
  * Abstract Base class to block based textures loader (dds, pvr, ...)
  */
 
-THREE.CompressedTextureLoader = function () {
+THREE.CompressedTextureLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 	// override in sub classes
 	this._parser = null;
@@ -16,7 +18,7 @@ THREE.CompressedTextureLoader.prototype = {
 
 	constructor: THREE.CompressedTextureLoader,
 
-	load: function ( url, onLoad, onError ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 		var scope = this;
 
@@ -25,7 +27,8 @@ THREE.CompressedTextureLoader.prototype = {
 		var texture = new THREE.CompressedTexture();
 		texture.image = images;
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
+		loader.setCrossOrigin( this.crossOrigin );
 		loader.setResponseType( 'arraybuffer' );
 
 		if ( Array.isArray( url ) ) {
@@ -59,7 +62,7 @@ THREE.CompressedTextureLoader.prototype = {
 
 					}
 
-				} );
+				}, onProgress, onError );
 
 			};
 
@@ -115,12 +118,18 @@ THREE.CompressedTextureLoader.prototype = {
 
 				if ( onLoad ) onLoad( texture );
 
-			} );
+			}, onProgress, onError );
 
 		}
 
 		return texture;
 
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
 	}
 
 };

+ 1 - 1
src/loaders/GeometryLoader.js

@@ -16,7 +16,7 @@ THREE.GeometryLoader.prototype = {
 
 		var scope = this;
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.load( url, function ( text ) {
 

+ 10 - 1
src/loaders/ImageLoader.js

@@ -20,7 +20,16 @@ THREE.ImageLoader.prototype = {
 
 		if ( cached !== undefined ) {
 
-			if ( onLoad ) onLoad( cached );
+			if ( onLoad ) {
+
+				setTimeout( function () {
+
+					onLoad( cached );
+
+				}, 0 );
+
+			}
+
 			return cached;
 
 		}

+ 265 - 309
src/loaders/JSONLoader.js

@@ -3,548 +3,504 @@
  * @author alteredq / http://alteredqualia.com/
  */
 
-THREE.JSONLoader = function ( showStatus ) {
+THREE.JSONLoader = function ( manager ) {
 
-	THREE.Loader.call( this, showStatus );
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 	this.withCredentials = false;
 
 };
 
-THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype );
-THREE.JSONLoader.prototype.constructor = THREE.JSONLoader;
+THREE.JSONLoader.prototype = {
 
-THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) {
+	constructor: THREE.JSONLoader,
 
-	// TODO: unify load API to for easier SceneLoader use
+	load: function( url, onLoad, onProgress, onError ) {
 
-	texturePath = texturePath && ( typeof texturePath === 'string' ) ? texturePath : this.extractUrlBase( url );
+		var scope = this;
 
-	this.onLoadStart();
-	this.loadAjaxJSON( this, url, callback, texturePath );
+		var texturePath = this.texturePath && ( typeof this.texturePath === "string" ) ? this.texturePath : THREE.Loader.prototype.extractUrlBase( url );
 
-};
-
-THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) {
-
-	var xhr = new XMLHttpRequest();
-
-	var length = 0;
-
-	xhr.onreadystatechange = function () {
-
-		if ( xhr.readyState === xhr.DONE ) {
-
-			if ( xhr.status === 200 || xhr.status === 0 ) {
-
-				if ( xhr.responseText ) {
-
-					var json = JSON.parse( xhr.responseText );
-					var metadata = json.metadata;
-
-					if ( metadata !== undefined ) {
-
-						if ( metadata.type === 'object' ) {
-
-							console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' );
-							return;
-
-						}
+		var loader = new THREE.XHRLoader( this.manager );
+		loader.setCrossOrigin( this.crossOrigin );
+		loader.setWithCredentials( this.withCredentials );
+		loader.load( url, function ( text ) {
 
-						if ( metadata.type === 'scene' ) {
+			var json = JSON.parse( text );
+			var metadata = json.metadata;
 
-							console.error( 'THREE.JSONLoader: ' + url + ' seems to be a Scene. Use THREE.SceneLoader instead.' );
-							return;
+			if ( metadata !== undefined ) {
 
-						}
-
-					}
+				if ( metadata.type === 'object' ) {
 
-					var result = context.parse( json, texturePath );
-					callback( result.geometry, result.materials );
-
-				} else {
-
-					console.error( 'THREE.JSONLoader: ' + url + ' seems to be unreachable or the file is empty.' );
+					console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' );
+					return;
 
 				}
 
-				// in context of more complex asset initialization
-				// do not block on single failed file
-				// maybe should go even one more level up
-
-				context.onLoadComplete();
-
-			} else {
-
-				console.error( 'THREE.JSONLoader: Couldn\'t load ' + url + ' (' + xhr.status + ')' );
-
-			}
-
-		} else if ( xhr.readyState === xhr.LOADING ) {
-
-			if ( callbackProgress ) {
-
-				if ( length === 0 ) {
+				if ( metadata.type === 'scene' ) {
 
-					length = xhr.getResponseHeader( 'Content-Length' );
+					console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.SceneLoader instead.' );
+					return;
 
 				}
 
-				callbackProgress( { total: length, loaded: xhr.responseText.length } );
-
 			}
 
-		} else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) {
+			var object = scope.parse( json, texturePath );
+			onLoad( object.geometry, object.materials );
 
-			if ( callbackProgress !== undefined ) {
+		} );
 
-				length = xhr.getResponseHeader( 'Content-Length' );
+	},
 
-			}
+	setCrossOrigin: function ( value ) {
 
-		}
+		this.crossOrigin = value;
 
-	};
+	},
 
-	xhr.open( 'GET', url, true );
-	xhr.withCredentials = this.withCredentials;
-	xhr.send( null );
+	setTexturePath: function ( value ) {
 
-};
+		this.texturePath = value;
 
-THREE.JSONLoader.prototype.parse = function ( json, texturePath ) {
+	},
 
-	var geometry = new THREE.Geometry(),
-	scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0;
+	parse: function ( json, texturePath ) {
 
-	parseModel( scale );
+		var scope = this,
+		geometry = new THREE.Geometry(),
+		scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0;
 
-	parseSkin();
-	parseMorphing( scale );
+		parseModel( scale );
 
-	geometry.computeFaceNormals();
-	geometry.computeBoundingSphere();
+		parseSkin();
+		parseMorphing( scale );
 
-	function parseModel( scale ) {
+		geometry.computeFaceNormals();
+		geometry.computeBoundingSphere();
 
-		function isBitSet( value, position ) {
+		function parseModel( scale ) {
 
-			return value & ( 1 << position );
+			function isBitSet( value, position ) {
 
-		}
+				return value & ( 1 << position );
+
+			}
 
-		var i, j, fi,
+			var i, j, fi,
 
-		offset, zLength,
+			offset, zLength,
 
 		colorIndex, normalIndex, uvIndex,
 
-		type,
-		isQuad,
-		hasMaterial,
-		hasFaceVertexUv,
-		hasFaceNormal, hasFaceVertexNormal,
-		hasFaceColor, hasFaceVertexColor,
+			type,
+			isQuad,
+			hasMaterial,
+			hasFaceVertexUv,
+			hasFaceNormal, hasFaceVertexNormal,
+			hasFaceColor, hasFaceVertexColor,
 
 		vertex, face, faceA, faceB, hex, normal,
 
-		uvLayer, uv, u, v,
+			uvLayer, uv, u, v,
 
-		faces = json.faces,
-		vertices = json.vertices,
-		normals = json.normals,
-		colors = json.colors,
+			faces = json.faces,
+			vertices = json.vertices,
+			normals = json.normals,
+			colors = json.colors,
 
-		nUvLayers = 0;
+			nUvLayers = 0;
 
-		if ( json.uvs !== undefined ) {
+			if ( json.uvs !== undefined ) {
 
-			// disregard empty arrays
+				// disregard empty arrays
 
-			for ( i = 0; i < json.uvs.length; i ++ ) {
+				for ( i = 0; i < json.uvs.length; i ++ ) {
 
-				if ( json.uvs[ i ].length ) nUvLayers ++;
+					if ( json.uvs[ i ].length ) nUvLayers ++;
 
-			}
+				}
 
-			for ( i = 0; i < nUvLayers; i ++ ) {
+				for ( i = 0; i < nUvLayers; i ++ ) {
 
-				geometry.faceVertexUvs[ i ] = [];
+					geometry.faceVertexUvs[ i ] = [];
 
-			}
+				}
 
-		}
+			}
 
-		offset = 0;
-		zLength = vertices.length;
+			offset = 0;
+			zLength = vertices.length;
 
-		while ( offset < zLength ) {
+			while ( offset < zLength ) {
 
-			vertex = new THREE.Vector3();
+				vertex = new THREE.Vector3();
 
-			vertex.x = vertices[ offset ++ ] * scale;
-			vertex.y = vertices[ offset ++ ] * scale;
-			vertex.z = vertices[ offset ++ ] * scale;
+				vertex.x = vertices[ offset ++ ] * scale;
+				vertex.y = vertices[ offset ++ ] * scale;
+				vertex.z = vertices[ offset ++ ] * scale;
 
-			geometry.vertices.push( vertex );
+				geometry.vertices.push( vertex );
 
-		}
+			}
 
-		offset = 0;
-		zLength = faces.length;
+			offset = 0;
+			zLength = faces.length;
 
-		while ( offset < zLength ) {
+			while ( offset < zLength ) {
 
-			type = faces[ offset ++ ];
+				type = faces[ offset ++ ];
 
 
-			isQuad              = isBitSet( type, 0 );
-			hasMaterial         = isBitSet( type, 1 );
-			hasFaceVertexUv     = isBitSet( type, 3 );
-			hasFaceNormal       = isBitSet( type, 4 );
-			hasFaceVertexNormal = isBitSet( type, 5 );
-			hasFaceColor	     = isBitSet( type, 6 );
-			hasFaceVertexColor  = isBitSet( type, 7 );
+				isQuad              = isBitSet( type, 0 );
+				hasMaterial         = isBitSet( type, 1 );
+				hasFaceVertexUv     = isBitSet( type, 3 );
+				hasFaceNormal       = isBitSet( type, 4 );
+				hasFaceVertexNormal = isBitSet( type, 5 );
+				hasFaceColor	     = isBitSet( type, 6 );
+				hasFaceVertexColor  = isBitSet( type, 7 );
 
-			// console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
+				// console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
 
-			if ( isQuad ) {
+				if ( isQuad ) {
 
-				faceA = new THREE.Face3();
-				faceA.a = faces[ offset ];
-				faceA.b = faces[ offset + 1 ];
-				faceA.c = faces[ offset + 3 ];
+					faceA = new THREE.Face3();
+					faceA.a = faces[ offset ];
+					faceA.b = faces[ offset + 1 ];
+					faceA.c = faces[ offset + 3 ];
 
-				faceB = new THREE.Face3();
-				faceB.a = faces[ offset + 1 ];
-				faceB.b = faces[ offset + 2 ];
-				faceB.c = faces[ offset + 3 ];
+					faceB = new THREE.Face3();
+					faceB.a = faces[ offset + 1 ];
+					faceB.b = faces[ offset + 2 ];
+					faceB.c = faces[ offset + 3 ];
 
-				offset += 4;
+					offset += 4;
 
-				if ( hasMaterial ) {
+					if ( hasMaterial ) {
 
 					offset ++;
 
-				}
+					}
 
-				// to get face <=> uv index correspondence
+					// to get face <=> uv index correspondence
 
-				fi = geometry.faces.length;
+					fi = geometry.faces.length;
 
-				if ( hasFaceVertexUv ) {
+					if ( hasFaceVertexUv ) {
 
-					for ( i = 0; i < nUvLayers; i ++ ) {
+						for ( i = 0; i < nUvLayers; i ++ ) {
 
-						uvLayer = json.uvs[ i ];
+							uvLayer = json.uvs[ i ];
 
-						geometry.faceVertexUvs[ i ][ fi ] = [];
+							geometry.faceVertexUvs[ i ][ fi ] = [];
 						geometry.faceVertexUvs[ i ][ fi + 1 ] = [];
 
-						for ( j = 0; j < 4; j ++ ) {
+							for ( j = 0; j < 4; j ++ ) {
+
+								uvIndex = faces[ offset ++ ];
 
-							uvIndex = faces[ offset ++ ];
+								u = uvLayer[ uvIndex * 2 ];
+								v = uvLayer[ uvIndex * 2 + 1 ];
 
-							u = uvLayer[ uvIndex * 2 ];
-							v = uvLayer[ uvIndex * 2 + 1 ];
+								uv = new THREE.Vector2( u, v );
 
-							uv = new THREE.Vector2( u, v );
+								if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );
+								if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );
 
-							if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );
-							if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );
+							}
 
 						}
 
 					}
 
-				}
-
-				if ( hasFaceNormal ) {
-
-					normalIndex = faces[ offset ++ ] * 3;
-
-					faceA.normal.set(
-						normals[ normalIndex ++ ],
-						normals[ normalIndex ++ ],
-						normals[ normalIndex ]
-					);
-
-					faceB.normal.copy( faceA.normal );
-
-				}
-
-				if ( hasFaceVertexNormal ) {
-
-					for ( i = 0; i < 4; i ++ ) {
+					if ( hasFaceNormal ) {
 
 						normalIndex = faces[ offset ++ ] * 3;
 
-						normal = new THREE.Vector3(
+						faceA.normal.set(
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ]
 						);
 
-
-						if ( i !== 2 ) faceA.vertexNormals.push( normal );
-						if ( i !== 0 ) faceB.vertexNormals.push( normal );
+						faceB.normal.copy( faceA.normal );
 
 					}
 
-				}
+					if ( hasFaceVertexNormal ) {
 
+						for ( i = 0; i < 4; i ++ ) {
 
-				if ( hasFaceColor ) {
+							normalIndex = faces[ offset ++ ] * 3;
 
-					colorIndex = faces[ offset ++ ];
-					hex = colors[ colorIndex ];
+							normal = new THREE.Vector3(
+								normals[ normalIndex ++ ],
+								normals[ normalIndex ++ ],
+								normals[ normalIndex ]
+							);
 
-					faceA.color.setHex( hex );
-					faceB.color.setHex( hex );
 
-				}
+							if ( i !== 2 ) faceA.vertexNormals.push( normal );
+							if ( i !== 0 ) faceB.vertexNormals.push( normal );
 
+						}
+
+					}
 
-				if ( hasFaceVertexColor ) {
 
-					for ( i = 0; i < 4; i ++ ) {
+					if ( hasFaceColor ) {
 
 						colorIndex = faces[ offset ++ ];
 						hex = colors[ colorIndex ];
 
-						if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) );
-						if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) );
+						faceA.color.setHex( hex );
+						faceB.color.setHex( hex );
 
 					}
 
-				}
 
-				geometry.faces.push( faceA );
-				geometry.faces.push( faceB );
+					if ( hasFaceVertexColor ) {
 
-			} else {
+						for ( i = 0; i < 4; i ++ ) {
 
-				face = new THREE.Face3();
-				face.a = faces[ offset ++ ];
-				face.b = faces[ offset ++ ];
-				face.c = faces[ offset ++ ];
+							colorIndex = faces[ offset ++ ];
+							hex = colors[ colorIndex ];
 
-				if ( hasMaterial ) {
+							if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) );
+							if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) );
 
-					offset ++;
+						}
 
-				}
+					}
 
-				// to get face <=> uv index correspondence
+					geometry.faces.push( faceA );
+					geometry.faces.push( faceB );
 
-				fi = geometry.faces.length;
+				} else {
 
-				if ( hasFaceVertexUv ) {
+					face = new THREE.Face3();
+					face.a = faces[ offset ++ ];
+					face.b = faces[ offset ++ ];
+					face.c = faces[ offset ++ ];
 
-					for ( i = 0; i < nUvLayers; i ++ ) {
+					if ( hasMaterial ) {
 
-						uvLayer = json.uvs[ i ];
+					offset ++;
 
-						geometry.faceVertexUvs[ i ][ fi ] = [];
+					}
 
-						for ( j = 0; j < 3; j ++ ) {
+					// to get face <=> uv index correspondence
 
-							uvIndex = faces[ offset ++ ];
+					fi = geometry.faces.length;
 
-							u = uvLayer[ uvIndex * 2 ];
-							v = uvLayer[ uvIndex * 2 + 1 ];
+					if ( hasFaceVertexUv ) {
 
-							uv = new THREE.Vector2( u, v );
+						for ( i = 0; i < nUvLayers; i ++ ) {
 
-							geometry.faceVertexUvs[ i ][ fi ].push( uv );
+							uvLayer = json.uvs[ i ];
 
-						}
+							geometry.faceVertexUvs[ i ][ fi ] = [];
 
-					}
+							for ( j = 0; j < 3; j ++ ) {
 
-				}
+								uvIndex = faces[ offset ++ ];
 
-				if ( hasFaceNormal ) {
+								u = uvLayer[ uvIndex * 2 ];
+								v = uvLayer[ uvIndex * 2 + 1 ];
 
-					normalIndex = faces[ offset ++ ] * 3;
+								uv = new THREE.Vector2( u, v );
 
-					face.normal.set(
-						normals[ normalIndex ++ ],
-						normals[ normalIndex ++ ],
-						normals[ normalIndex ]
-					);
+								geometry.faceVertexUvs[ i ][ fi ].push( uv );
 
-				}
+							}
 
-				if ( hasFaceVertexNormal ) {
+						}
+
+					}
 
-					for ( i = 0; i < 3; i ++ ) {
+					if ( hasFaceNormal ) {
 
 						normalIndex = faces[ offset ++ ] * 3;
 
-						normal = new THREE.Vector3(
+						face.normal.set(
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ]
 						);
 
-						face.vertexNormals.push( normal );
-
 					}
 
-				}
+					if ( hasFaceVertexNormal ) {
 
+						for ( i = 0; i < 3; i ++ ) {
 
-				if ( hasFaceColor ) {
+							normalIndex = faces[ offset ++ ] * 3;
 
-					colorIndex = faces[ offset ++ ];
-					face.color.setHex( colors[ colorIndex ] );
+							normal = new THREE.Vector3(
+								normals[ normalIndex ++ ],
+								normals[ normalIndex ++ ],
+								normals[ normalIndex ]
+							);
 
-				}
+							face.vertexNormals.push( normal );
 
+						}
+
+					}
 
-				if ( hasFaceVertexColor ) {
 
-					for ( i = 0; i < 3; i ++ ) {
+					if ( hasFaceColor ) {
 
 						colorIndex = faces[ offset ++ ];
-						face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) );
+						face.color.setHex( colors[ colorIndex ] );
 
 					}
 
-				}
 
-				geometry.faces.push( face );
+					if ( hasFaceVertexColor ) {
+
+						for ( i = 0; i < 3; i ++ ) {
+
+							colorIndex = faces[ offset ++ ];
+							face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) );
+
+						}
+
+					}
+
+					geometry.faces.push( face );
+
+				}
 
 			}
 
-		}
+		};
 
-	}
+		function parseSkin() {
+			var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2;
 
-	function parseSkin() {
-		var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2;
+			if ( json.skinWeights ) {
 
-		if ( json.skinWeights ) {
+				for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) {
 
-			for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) {
+					var x =                               json.skinWeights[ i     ];
+					var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0;
+					var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0;
+					var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0;
 
-				var x =                               json.skinWeights[ i     ];
-				var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0;
-				var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0;
-				var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0;
+					geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
 
-				geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
+				}
 
 			}
 
-		}
+			if ( json.skinIndices ) {
 
-		if ( json.skinIndices ) {
+				for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) {
 
-			for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) {
+					var a =                               json.skinIndices[ i     ];
+					var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0;
+					var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0;
+					var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0;
 
-				var a =                               json.skinIndices[ i     ];
-				var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0;
-				var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0;
-				var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0;
+					geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
 
-				geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
+				}
 
 			}
 
-		}
+			geometry.bones = json.bones;
 
-		geometry.bones = json.bones;
+			if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) {
 
-		if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) {
+					console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' +
+						geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' );
 
-			console.warn( 'THREE.JSONLoader: When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' +
-					geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' );
+			}
 
-		}
 
+			// could change this to json.animations[0] or remove completely
 
-		// could change this to json.animations[0] or remove completely
+			geometry.animation = json.animation;
+			geometry.animations = json.animations;
 
-		geometry.animation = json.animation;
-		geometry.animations = json.animations;
+		};
 
-	}
+		function parseMorphing( scale ) {
 
-	function parseMorphing( scale ) {
+			if ( json.morphTargets !== undefined ) {
 
-		if ( json.morphTargets !== undefined ) {
+				var i, l, v, vl, dstVertices, srcVertices;
 
-			var i, l, v, vl, dstVertices, srcVertices;
+				for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) {
 
-			for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) {
+					geometry.morphTargets[ i ] = {};
+					geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
+					geometry.morphTargets[ i ].vertices = [];
 
-				geometry.morphTargets[ i ] = {};
-				geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
-				geometry.morphTargets[ i ].vertices = [];
+					dstVertices = geometry.morphTargets[ i ].vertices;
+					srcVertices = json.morphTargets [ i ].vertices;
 
-				dstVertices = geometry.morphTargets[ i ].vertices;
-				srcVertices = json.morphTargets [ i ].vertices;
+					for ( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
 
-				for ( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
+						var vertex = new THREE.Vector3();
+						vertex.x = srcVertices[ v ] * scale;
+						vertex.y = srcVertices[ v + 1 ] * scale;
+						vertex.z = srcVertices[ v + 2 ] * scale;
 
-					var vertex = new THREE.Vector3();
-					vertex.x = srcVertices[ v ] * scale;
-					vertex.y = srcVertices[ v + 1 ] * scale;
-					vertex.z = srcVertices[ v + 2 ] * scale;
+						dstVertices.push( vertex );
 
-					dstVertices.push( vertex );
+					}
 
 				}
 
 			}
 
-		}
+			if ( json.morphColors !== undefined ) {
 
-		if ( json.morphColors !== undefined ) {
+				var i, l, c, cl, dstColors, srcColors, color;
 
-			var i, l, c, cl, dstColors, srcColors, color;
+				for ( i = 0, l = json.morphColors.length; i < l; i ++ ) {
 
-			for ( i = 0, l = json.morphColors.length; i < l; i ++ ) {
+					geometry.morphColors[ i ] = {};
+					geometry.morphColors[ i ].name = json.morphColors[ i ].name;
+					geometry.morphColors[ i ].colors = [];
 
-				geometry.morphColors[ i ] = {};
-				geometry.morphColors[ i ].name = json.morphColors[ i ].name;
-				geometry.morphColors[ i ].colors = [];
+					dstColors = geometry.morphColors[ i ].colors;
+					srcColors = json.morphColors [ i ].colors;
 
-				dstColors = geometry.morphColors[ i ].colors;
-				srcColors = json.morphColors [ i ].colors;
+					for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) {
 
-				for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) {
+						color = new THREE.Color( 0xffaa00 );
+						color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] );
+						dstColors.push( color );
 
-					color = new THREE.Color( 0xffaa00 );
-					color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] );
-					dstColors.push( color );
+					}
 
 				}
 
 			}
 
-		}
+		};
 
-	}
+		if ( json.materials === undefined || json.materials.length === 0 ) {
 
-	if ( json.materials === undefined || json.materials.length === 0 ) {
+			return { geometry: geometry };
 
-		return { geometry: geometry };
+		} else {
 
-	} else {
+			var materials = THREE.Loader.prototype.initMaterials( json.materials, texturePath, this.crossOrigin );
 
-		var materials = this.initMaterials( json.materials, texturePath );
+			if ( THREE.Loader.prototype.needsTangents( materials ) ) {
 
-		if ( this.needsTangents( materials ) ) {
+				geometry.computeTangents();
 
-			geometry.computeTangents();
+			}
 
-		}
+			return { geometry: geometry, materials: materials };
 
-		return { geometry: geometry, materials: materials };
+		}
 
 	}
 

+ 167 - 202
src/loaders/Loader.js

@@ -2,12 +2,7 @@
  * @author alteredq / http://alteredqualia.com/
  */
 
-THREE.Loader = function ( showStatus ) {
-
-	this.showStatus = showStatus;
-	this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null;
-
-	this.imageLoader = new THREE.ImageLoader();
+THREE.Loader = function () {
 
 	this.onLoadStart = function () {};
 	this.onLoadProgress = function () {};
@@ -21,46 +16,6 @@ THREE.Loader.prototype = {
 
 	crossOrigin: undefined,
 
-	addStatusElement: function () {
-
-		var e = document.createElement( 'div' );
-
-		e.style.position = 'absolute';
-		e.style.right = '0px';
-		e.style.top = '0px';
-		e.style.fontSize = '0.8em';
-		e.style.textAlign = 'left';
-		e.style.background = 'rgba(0,0,0,0.25)';
-		e.style.color = '#fff';
-		e.style.width = '120px';
-		e.style.padding = '0.5em 0.5em 0.5em 0.5em';
-		e.style.zIndex = 1000;
-
-		e.innerHTML = 'Loading ...';
-
-		return e;
-
-	},
-
-	updateProgress: function ( progress ) {
-
-		var message = 'Loaded ';
-
-		if ( progress.total ) {
-
-			message += ( 100 * progress.loaded / progress.total ).toFixed( 0 ) + '%';
-
-
-		} else {
-
-			message += ( progress.loaded / 1024 ).toFixed( 2 ) + ' KB';
-
-		}
-
-		this.statusDomElement.innerHTML = message;
-
-	},
-
 	extractUrlBase: function ( url ) {
 
 		var parts = url.split( '/' );
@@ -73,13 +28,13 @@ THREE.Loader.prototype = {
 
 	},
 
-	initMaterials: function ( materials, texturePath ) {
+	initMaterials: function ( materials, texturePath, crossOrigin ) {
 
 		var array = [];
 
 		for ( var i = 0; i < materials.length; ++ i ) {
 
-			array[ i ] = this.createMaterial( materials[ i ], texturePath );
+			array[ i ] = this.createMaterial( materials[ i ], texturePath, crossOrigin );
 
 		}
 
@@ -101,303 +56,313 @@ THREE.Loader.prototype = {
 
 	},
 
-	createMaterial: function ( m, texturePath ) {
+	createMaterial: ( function () {
 
-		var scope = this;
+		var imageLoader;
 
-		function nearest_pow2( n ) {
+		return function ( m, texturePath, crossOrigin ) {
 
-			var l = Math.log( n ) / Math.LN2;
-			return Math.pow( 2, Math.round(  l ) );
+			var scope = this;
 
-		}
+			if ( crossOrigin === undefined && scope.crossOrigin !== undefined ) crossOrigin = scope.crossOrigin;
 
-		function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) {
+			if ( imageLoader === undefined ) imageLoader = new THREE.ImageLoader();
 
-			var fullPath = texturePath + sourceFile;
+			function nearest_pow2( n ) {
 
-			var texture;
+				var l = Math.log( n ) / Math.LN2;
+				return Math.pow( 2, Math.round(  l ) );
 
-			var loader = THREE.Loader.Handlers.get( fullPath );
+			}
 
-			if ( loader !== null ) {
+			function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) {
 
-				texture = loader.load( fullPath );
+				var fullPath = texturePath + sourceFile;
 
-			} else {
+				var texture;
 
-				texture = new THREE.Texture();
+				var loader = THREE.Loader.Handlers.get( fullPath );
 
-				loader = scope.imageLoader;
-				loader.crossOrigin = scope.crossOrigin;
-				loader.load( fullPath, function ( image ) {
+				if ( loader !== null ) {
+
+					texture = loader.load( fullPath );
+
+				} else {
+
+					texture = new THREE.Texture();
+
+					loader = imageLoader;
+					loader.setCrossOrigin( crossOrigin );
+					loader.load( fullPath, function ( image ) {
 
 					if ( THREE.Math.isPowerOfTwo( image.width ) === false ||
 						 THREE.Math.isPowerOfTwo( image.height ) === false ) {
 
-						var width = nearest_pow2( image.width );
-						var height = nearest_pow2( image.height );
+							var width = nearest_pow2( image.width );
+							var height = nearest_pow2( image.height );
 
-						var canvas = document.createElement( 'canvas' );
-						canvas.width = width;
-						canvas.height = height;
+							var canvas = document.createElement( 'canvas' );
+							canvas.width = width;
+							canvas.height = height;
 
-						var context = canvas.getContext( '2d' );
-						context.drawImage( image, 0, 0, width, height );
+							var context = canvas.getContext( '2d' );
+							context.drawImage( image, 0, 0, width, height );
 
-						texture.image = canvas;
+							texture.image = canvas;
 
-					} else {
+						} else {
 
-						texture.image = image;
+							texture.image = image;
 
-					}
+						}
 
-					texture.needsUpdate = true;
+						texture.needsUpdate = true;
 
-				} );
+					} );
 
-			}
+				}
 
-			texture.sourceFile = sourceFile;
+				texture.sourceFile = sourceFile;
 
-			if ( repeat ) {
+				if ( repeat ) {
 
-				texture.repeat.set( repeat[ 0 ], repeat[ 1 ] );
+					texture.repeat.set( repeat[ 0 ], repeat[ 1 ] );
 
-				if ( repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping;
-				if ( repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping;
+					if ( repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping;
+					if ( repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping;
 
-			}
+				}
 
-			if ( offset ) {
+				if ( offset ) {
 
-				texture.offset.set( offset[ 0 ], offset[ 1 ] );
+					texture.offset.set( offset[ 0 ], offset[ 1 ] );
 
-			}
+				}
 
-			if ( wrap ) {
+				if ( wrap ) {
 
-				var wrapMap = {
-					'repeat': THREE.RepeatWrapping,
-					'mirror': THREE.MirroredRepeatWrapping
-				};
+					var wrapMap = {
+						'repeat': THREE.RepeatWrapping,
+						'mirror': THREE.MirroredRepeatWrapping
+					};
 
-				if ( wrapMap[ wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ wrap[ 0 ] ];
-				if ( wrapMap[ wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ wrap[ 1 ] ];
+					if ( wrapMap[ wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ wrap[ 0 ] ];
+					if ( wrapMap[ wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ wrap[ 1 ] ];
 
-			}
+				}
 
-			if ( anisotropy ) {
+				if ( anisotropy ) {
 
-				texture.anisotropy = anisotropy;
+					texture.anisotropy = anisotropy;
 
-			}
+				}
 
-			where[ name ] = texture;
+				where[ name ] = texture;
 
-		}
+			}
 
-		function rgb2hex( rgb ) {
+			function rgb2hex( rgb ) {
 
-			return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255;
+				return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255;
 
-		}
+			}
 
-		// defaults
+			// defaults
 
-		var mtype = 'MeshLambertMaterial';
-		var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false };
+			var mtype = 'MeshLambertMaterial';
+			var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false };
 
-		// parameters from model file
+			// parameters from model file
 
-		if ( m.shading ) {
+			if ( m.shading ) {
 
-			var shading = m.shading.toLowerCase();
+				var shading = m.shading.toLowerCase();
 
-			if ( shading === 'phong' ) mtype = 'MeshPhongMaterial';
-			else if ( shading === 'basic' ) mtype = 'MeshBasicMaterial';
+				if ( shading === 'phong' ) mtype = 'MeshPhongMaterial';
+				else if ( shading === 'basic' ) mtype = 'MeshBasicMaterial';
 
-		}
+			}
 
-		if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) {
+			if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) {
 
-			mpars.blending = THREE[ m.blending ];
+				mpars.blending = THREE[ m.blending ];
 
-		}
+			}
 
-		if ( m.transparent !== undefined ) {
+			if ( m.transparent !== undefined ) {
 
-			mpars.transparent = m.transparent;
+				mpars.transparent = m.transparent;
 
-		}
+			}
 
-		if ( m.opacity !== undefined && m.opacity < 1.0 ) {
+			if ( m.opacity !== undefined && m.opacity < 1.0 ) {
 
-			mpars.transparent = true;
+				mpars.transparent = true;
 
-		}
+			}
 
-		if ( m.depthTest !== undefined ) {
+			if ( m.depthTest !== undefined ) {
 
-			mpars.depthTest = m.depthTest;
+				mpars.depthTest = m.depthTest;
 
-		}
+			}
 
-		if ( m.depthWrite !== undefined ) {
+			if ( m.depthWrite !== undefined ) {
 
-			mpars.depthWrite = m.depthWrite;
+				mpars.depthWrite = m.depthWrite;
 
-		}
+			}
 
-		if ( m.visible !== undefined ) {
+			if ( m.visible !== undefined ) {
 
-			mpars.visible = m.visible;
+				mpars.visible = m.visible;
 
-		}
+			}
 
-		if ( m.flipSided !== undefined ) {
+			if ( m.flipSided !== undefined ) {
 
-			mpars.side = THREE.BackSide;
+				mpars.side = THREE.BackSide;
 
-		}
+			}
 
-		if ( m.doubleSided !== undefined ) {
+			if ( m.doubleSided !== undefined ) {
 
-			mpars.side = THREE.DoubleSide;
+				mpars.side = THREE.DoubleSide;
 
-		}
+			}
 
-		if ( m.wireframe !== undefined ) {
+			if ( m.wireframe !== undefined ) {
 
-			mpars.wireframe = m.wireframe;
+				mpars.wireframe = m.wireframe;
 
-		}
+			}
 
-		if ( m.vertexColors !== undefined ) {
+			if ( m.vertexColors !== undefined ) {
 
-			if ( m.vertexColors === 'face' ) {
+				if ( m.vertexColors === 'face' ) {
 
-				mpars.vertexColors = THREE.FaceColors;
+					mpars.vertexColors = THREE.FaceColors;
 
-			} else if ( m.vertexColors ) {
+				} else if ( m.vertexColors ) {
 
-				mpars.vertexColors = THREE.VertexColors;
+					mpars.vertexColors = THREE.VertexColors;
+
+				}
 
 			}
 
-		}
+			// colors
 
-		// colors
+			if ( m.colorDiffuse ) {
 
-		if ( m.colorDiffuse ) {
+				mpars.color = rgb2hex( m.colorDiffuse );
 
-			mpars.color = rgb2hex( m.colorDiffuse );
+			} else if ( m.DbgColor ) {
 
-		} else if ( m.DbgColor ) {
+				mpars.color = m.DbgColor;
 
-			mpars.color = m.DbgColor;
+			}
 
-		}
+			if ( m.colorSpecular ) {
 
-		if ( m.colorSpecular ) {
+				mpars.specular = rgb2hex( m.colorSpecular );
 
-			mpars.specular = rgb2hex( m.colorSpecular );
+			}
 
-		}
+			if ( m.colorEmissive ) {
 
-		if ( m.colorEmissive ) {
+				mpars.emissive = rgb2hex( m.colorEmissive );
 
-			mpars.emissive = rgb2hex( m.colorEmissive );
+			}
 
-		}
+			// modifiers
 
-		// modifiers
+			if ( m.transparency !== undefined ) {
 
-		if ( m.transparency !== undefined ) {
+				console.warn( 'THREE.Loader: transparency has been renamed to opacity' );
+				m.opacity = m.transparency;
 
-			console.warn( 'THREE.Loader: transparency has been renamed to opacity' );
-			m.opacity = m.transparency;
+			}
 
-		}
+			if ( m.opacity !== undefined ) {
 
-		if ( m.opacity !== undefined ) {
+				mpars.opacity = m.opacity;
 
-			mpars.opacity = m.opacity;
+			}
 
-		}
+			if ( m.specularCoef ) {
 
-		if ( m.specularCoef ) {
+				mpars.shininess = m.specularCoef;
 
-			mpars.shininess = m.specularCoef;
+			}
 
-		}
+			// textures
 
-		// textures
+			if ( m.mapDiffuse && texturePath ) {
 
-		if ( m.mapDiffuse && texturePath ) {
+				create_texture( mpars, 'map', m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );
 
-			create_texture( mpars, 'map', m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );
+			}
 
-		}
+			if ( m.mapLight && texturePath ) {
 
-		if ( m.mapLight && texturePath ) {
+				create_texture( mpars, 'lightMap', m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );
 
-			create_texture( mpars, 'lightMap', m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );
+			}
 
-		}
+			if ( m.mapAO && texturePath ) {
 
-		if ( m.mapAO && texturePath ) {
+				create_texture( mpars, 'aoMap', m.mapAO, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy );
 
-			create_texture( mpars, 'aoMap', m.mapAO, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy );
+			}
 
-		}
+			if ( m.mapBump && texturePath ) {
 
-		if ( m.mapBump && texturePath ) {
+				create_texture( mpars, 'bumpMap', m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );
 
-			create_texture( mpars, 'bumpMap', m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );
+			}
 
-		}
+			if ( m.mapNormal && texturePath ) {
 
-		if ( m.mapNormal && texturePath ) {
+				create_texture( mpars, 'normalMap', m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );
 
-			create_texture( mpars, 'normalMap', m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );
+			}
 
-		}
+			if ( m.mapSpecular && texturePath ) {
 
-		if ( m.mapSpecular && texturePath ) {
+				create_texture( mpars, 'specularMap', m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );
 
-			create_texture( mpars, 'specularMap', m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );
+			}
 
-		}
+			if ( m.mapAlpha && texturePath ) {
 
-		if ( m.mapAlpha && texturePath ) {
+				create_texture( mpars, 'alphaMap', m.mapAlpha, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy );
 
-			create_texture( mpars, 'alphaMap', m.mapAlpha, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy );
+			}
 
-		}
+			//
 
-		//
+			if ( m.mapBumpScale ) {
 
-		if ( m.mapBumpScale ) {
+				mpars.bumpScale = m.mapBumpScale;
 
-			mpars.bumpScale = m.mapBumpScale;
+			}
 
-		}
+			if ( m.mapNormalFactor ) {
 
-		if ( m.mapNormalFactor ) {
+				mpars.normalScale = new THREE.Vector2( m.mapNormalFactor, m.mapNormalFactor );
 
-			mpars.normalScale = new THREE.Vector2( m.mapNormalFactor, m.mapNormalFactor );
+			}
 
-		}
+			var material = new THREE[ mtype ]( mpars );
 
-		var material = new THREE[ mtype ]( mpars );
+			if ( m.DbgName !== undefined ) material.name = m.DbgName;
 
-		if ( m.DbgName !== undefined ) material.name = m.DbgName;
+			return material;
 
-		return material;
+		};
 
-	}
+	} )()
 
 };
 

+ 24 - 6
src/loaders/LoadingManager.js

@@ -6,7 +6,7 @@ THREE.LoadingManager = function ( onLoad, onProgress, onError ) {
 
 	var scope = this;
 
-	var loaded = 0, total = 0;
+	var isLoading = false, itemsLoaded = 0, itemsTotal = 0;
 
 	this.onLoad = onLoad;
 	this.onProgress = onProgress;
@@ -14,23 +14,41 @@ THREE.LoadingManager = function ( onLoad, onProgress, onError ) {
 
 	this.itemStart = function ( url ) {
 
-		total ++;
+		itemsTotal ++;
+
+		if ( isLoading === false ) {
+
+			if ( scope.onStart !== undefined ) {
+
+				scope.onStart( url, itemsLoaded, itemsTotal );
+
+			}
+
+		}
+
+		isLoading = true;
 
 	};
 
 	this.itemEnd = function ( url ) {
 
-		loaded ++;
+		itemsLoaded ++;
 
 		if ( scope.onProgress !== undefined ) {
 
-			scope.onProgress( url, loaded, total );
+			scope.onProgress( url, itemsLoaded, itemsTotal );
 
 		}
 
-		if ( loaded === total && scope.onLoad !== undefined ) {
+		if ( itemsLoaded === itemsTotal ) {
+
+			isLoading = false;
+
+			if ( scope.onLoad !== undefined ) {
+
+				scope.onLoad();
 
-			scope.onLoad();
+			}
 
 		}
 

+ 17 - 1
src/loaders/XHRLoader.js

@@ -20,7 +20,16 @@ THREE.XHRLoader.prototype = {
 
 		if ( cached !== undefined ) {
 
-			if ( onLoad ) onLoad( cached );
+			if ( onLoad ) {
+
+				setTimeout( function () {
+
+					onLoad( cached );
+
+				}, 0 );
+
+			}
+
 			return cached;
 
 		}
@@ -60,6 +69,7 @@ THREE.XHRLoader.prototype = {
 
 		if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin;
 		if ( this.responseType !== undefined ) request.responseType = this.responseType;
+		if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
 
 		request.send( null );
 
@@ -79,6 +89,12 @@ THREE.XHRLoader.prototype = {
 
 		this.crossOrigin = value;
 
+	},
+
+	setWithCredentials: function ( value ) {
+
+		this.withCredentials = value;
+
 	}
 
 };

+ 161 - 174
src/renderers/WebGLRenderer.js

@@ -37,6 +37,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 	var opaqueObjects = [];
 	var transparentObjects = [];
 
+	var opaqueImmediateObjects = [];
+	var transparentImmediateObjects = [];
+
 	var sprites = [];
 	var lensFlares = [];
 
@@ -71,29 +74,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	this.autoScaleCubemaps = true;
 
-	// info
-
-	this.info = {
-
-		memory: {
-
-			programs: 0,
-			geometries: 0,
-			textures: 0
-
-		},
-
-		render: {
-
-			calls: 0,
-			vertices: 0,
-			faces: 0,
-			points: 0
-
-		}
-
-	};
-
 	// internal properties
 
 	var _this = this,
@@ -141,6 +121,33 @@ THREE.WebGLRenderer = function ( parameters ) {
 		spot: { length: 0, colors: [], positions: [], distances: [], directions: [], anglesCos: [], exponents: [], decays: [] },
 		hemi: { length: 0, skyColors: [], groundColors: [], positions: [] }
 
+	},
+
+	// info
+
+	_infoMemory = {
+
+		programs: 0,
+		geometries: 0,
+		textures: 0
+
+	},
+
+	_infoRender = {
+
+		calls: 0,
+		vertices: 0,
+		faces: 0,
+		points: 0
+
+	};
+
+	this.info = {
+
+		render: _infoRender,
+		memory: _infoMemory,
+		programs: _programs
+
 	};
 
 	// initialize
@@ -234,7 +241,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	//
 
-	var glClearColor = function ( r, g, b, a ) {
+	function glClearColor( r, g, b, a ) {
 
 		if ( _premultipliedAlpha === true ) {
 
@@ -244,9 +251,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		_gl.clearColor( r, g, b, a );
 
-	};
+	}
 
-	var setDefaultGLState = function () {
+	function setDefaultGLState() {
 
 		state.init();
 
@@ -254,9 +261,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
 
-	};
+	}
 
-	var resetGLState = function () {
+	function resetGLState() {
 
 		_currentProgram = null;
 		_currentCamera = null;
@@ -268,7 +275,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		state.reset();
 
-	};
+	}
 
 	setDefaultGLState();
 
@@ -523,9 +530,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	};
 
-	this.enableScissorTest = function ( enable ) {
+	this.enableScissorTest = function ( boolean ) {
 
-		enable ? _gl.enable( _gl.SCISSOR_TEST ) : _gl.disable( _gl.SCISSOR_TEST );
+		state.setScissorTest( boolean );
 
 	};
 
@@ -604,7 +611,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	// Events
 
-	var onTextureDispose = function ( event ) {
+	function onTextureDispose( event ) {
 
 		var texture = event.target;
 
@@ -612,12 +619,12 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		deallocateTexture( texture );
 
-		_this.info.memory.textures --;
+		_infoMemory.textures --;
 
 
-	};
+	}
 
-	var onRenderTargetDispose = function ( event ) {
+	function onRenderTargetDispose( event ) {
 
 		var renderTarget = event.target;
 
@@ -625,11 +632,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		deallocateRenderTarget( renderTarget );
 
-		_this.info.memory.textures --;
+		_infoMemory.textures --;
 
-	};
+	}
 
-	var onMaterialDispose = function ( event ) {
+	function onMaterialDispose( event ) {
 
 		var material = event.target;
 
@@ -637,11 +644,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		deallocateMaterial( material );
 
-	};
+	}
 
 	// Buffer deallocation
 
-	var deallocateTexture = function ( texture ) {
+	function deallocateTexture( texture ) {
 
 		var textureProperties = properties.get( texture );
 
@@ -664,9 +671,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 		// remove all webgl properties
 		properties.delete( texture );
 
-	};
+	}
 
-	var deallocateRenderTarget = function ( renderTarget ) {
+	function deallocateRenderTarget( renderTarget ) {
 
 		var renderTargetProperties = properties.get( renderTarget );
 
@@ -692,72 +699,57 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		properties.delete( renderTargetProperties );
 
-	};
-
-	var deallocateMaterial = function ( material ) {
-
-		var program = properties.get( material ).program.program;
+	}
 
-		if ( program === undefined ) return;
+	function deallocateMaterial( material ) {
 
-		material.program = undefined;
+		releaseMaterialProgramReference( material );
 
-		// only deallocate GL program if this was the last use of shared program
-		// assumed there is only single copy of any program in the _programs list
-		// (that's how it's constructed)
-
-		var i, il, programInfo;
-		var deleteProgram = false;
+		properties.delete( material );
 
-		for ( i = 0, il = _programs.length; i < il; i ++ ) {
+	}
 
-			programInfo = _programs[ i ];
 
-			if ( programInfo.program === program ) {
+	function releaseMaterialProgramReference( material ) {
 
-				programInfo.usedTimes --;
+		var program = properties.get( material ).program.program;
 
-				if ( programInfo.usedTimes === 0 ) {
+		if ( program === undefined ) return;
 
-					deleteProgram = true;
+		material.program = undefined;
 
-				}
+		for ( var i = 0, n = _programs.length; i !== n; ++ i ) {
 
-				break;
+			var programInfo = _programs[ i ];
 
-			}
-
-		}
+			if ( programInfo.program === program ) {
 
-		if ( deleteProgram === true ) {
+				var newReferenceCount = -- programInfo.usedTimes;
 
-			// avoid using array.splice, this is costlier than creating new array from scratch
+				if ( newReferenceCount === 0 ) {
 
-			var newPrograms = [];
+					// the last meterial that has been using the program let
+					// go of it, so remove it from the (unordered) _programs
+					// set and deallocate the GL resource
 
-			for ( i = 0, il = _programs.length; i < il; i ++ ) {
+					var newLength = n - 1;
 
-				programInfo = _programs[ i ];
+					_programs[ i ] = _programs[ newLength ];
+					_programs.pop();
 
-				if ( programInfo.program !== program ) {
+					_gl.deleteProgram( program );
 
-					newPrograms.push( programInfo );
+					_infoMemory.programs = newLength;
 
 				}
 
-			}
-
-			_programs = newPrograms;
-
-			_gl.deleteProgram( program );
+				break;
 
-			_this.info.memory.programs --;
+			}
 
 		}
 
-		properties.delete( material );
-
-	};
+	}
 
 	// Buffer rendering
 
@@ -929,7 +921,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 							if ( geometry.maxInstancedCount === undefined ) {
 
-								geometry.maxInstancedCount = data.meshPerAttribute * ( data.array.length / data.stride );
+								geometry.maxInstancedCount = data.meshPerAttribute * data.count;
 
 							}
 
@@ -953,7 +945,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 							if ( geometry.maxInstancedCount === undefined ) {
 
-								geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * ( geometryAttribute.array.length / geometryAttribute.itemSize );
+								geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;
 
 							}
 
@@ -1101,9 +1093,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 					_gl.drawElements( mode, index.array.length, type, 0 );
 
 				}
-				_this.info.render.calls ++;
-				_this.info.render.vertices += index.array.length; // not really true, here vertices can be shared
-				_this.info.render.faces += index.array.length / 3;
+				_infoRender.calls ++;
+				_infoRender.vertices += index.array.length; // not really true, here vertices can be shared
+				_infoRender.faces += index.array.length / 3;
 
 			} else {
 
@@ -1145,9 +1137,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					}
 
-					_this.info.render.calls ++;
-					_this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared
-					_this.info.render.faces += offsets[ i ].count / 3;
+					_infoRender.calls ++;
+					_infoRender.vertices += offsets[ i ].count; // not really true, here vertices can be shared
+					_infoRender.faces += offsets[ i ].count / 3;
 
 				}
 
@@ -1184,11 +1176,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					if ( position instanceof THREE.InterleavedBufferAttribute ) {
 
-						extension.drawArraysInstancedANGLE( mode, 0, position.data.array.length / position.data.stride, geometry.maxInstancedCount ); // Draw the instanced meshes
+						extension.drawArraysInstancedANGLE( mode, 0, position.data.count, geometry.maxInstancedCount ); // Draw the instanced meshes
 
 					} else {
 
-						extension.drawArraysInstancedANGLE( mode, 0, position.array.length / position.itemSize, geometry.maxInstancedCount ); // Draw the instanced meshes
+						extension.drawArraysInstancedANGLE( mode, 0, position.count, geometry.maxInstancedCount ); // Draw the instanced meshes
 
 					}
 
@@ -1196,19 +1188,19 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					if ( position instanceof THREE.InterleavedBufferAttribute ) {
 
-						_gl.drawArrays( mode, 0, position.data.array.length / position.data.stride );
+						_gl.drawArrays( mode, 0, position.data.count );
 
 					} else {
 
-						_gl.drawArrays( mode, 0, position.array.length / position.itemSize );
+						_gl.drawArrays( mode, 0, position.count );
 
 					}
 
 				}
 
-				_this.info.render.calls++;
-				_this.info.render.vertices += position.array.length / position.itemSize;
-				_this.info.render.faces += position.array.length / ( 3 * position.itemSize );
+				_infoRender.calls++;
+				_infoRender.vertices += position.count;
+				_infoRender.faces += position.array.length / 3;
 
 			} else {
 
@@ -1237,9 +1229,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					}
 
-					_this.info.render.calls++;
-					_this.info.render.vertices += offsets[ i ].count;
-					_this.info.render.faces += ( offsets[ i ].count  ) / 3;
+					_infoRender.calls++;
+					_infoRender.vertices += offsets[ i ].count;
+					_infoRender.faces += ( offsets[ i ].count  ) / 3;
 
 				}
 			}
@@ -1291,8 +1283,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				_gl.drawElements( mode, index.array.length, type, 0 ); // 2 bytes per Uint16Array
 
-				_this.info.render.calls ++;
-				_this.info.render.vertices += index.array.length; // not really true, here vertices can be shared
+				_infoRender.calls ++;
+				_infoRender.vertices += index.array.length; // not really true, here vertices can be shared
 
 			} else {
 
@@ -1317,8 +1309,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					_gl.drawElements( mode, offsets[ i ].count, type, offsets[ i ].start * size ); // 2 bytes per Uint16Array
 
-					_this.info.render.calls ++;
-					_this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared
+					_infoRender.calls ++;
+					_infoRender.vertices += offsets[ i ].count; // not really true, here vertices can be shared
 
 				}
 
@@ -1341,8 +1333,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				_gl.drawArrays( mode, 0, position.array.length / 3 );
 
-				_this.info.render.calls ++;
-				_this.info.render.vertices += position.array.length / 3;
+				_infoRender.calls ++;
+				_infoRender.vertices += position.array.length / 3;
 
 			} else {
 
@@ -1350,8 +1342,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					_gl.drawArrays( mode, offsets[ i ].index, offsets[ i ].count );
 
-					_this.info.render.calls ++;
-					_this.info.render.vertices += offsets[ i ].count;
+					_infoRender.calls ++;
+					_infoRender.vertices += offsets[ i ].count;
 
 				}
 
@@ -1400,8 +1392,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				_gl.drawElements( mode, index.array.length, type, 0);
 
-				_this.info.render.calls ++;
-				_this.info.render.points += index.array.length;
+				_infoRender.calls ++;
+				_infoRender.points += index.array.length;
 
 			} else {
 
@@ -1426,8 +1418,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					_gl.drawElements( mode, offsets[ i ].count, type, offsets[ i ].start * size );
 
-					_this.info.render.calls ++;
-					_this.info.render.points += offsets[ i ].count;
+					_infoRender.calls ++;
+					_infoRender.points += offsets[ i ].count;
 
 				}
 
@@ -1450,8 +1442,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 				_gl.drawArrays( mode, 0, position.array.length / 3 );
 
-				_this.info.render.calls ++;
-				_this.info.render.points += position.array.length / 3;
+				_infoRender.calls ++;
+				_infoRender.points += position.array.length / 3;
 
 			} else {
 
@@ -1459,8 +1451,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					_gl.drawArrays( mode, offsets[ i ].index, offsets[ i ].count );
 
-					_this.info.render.calls ++;
-					_this.info.render.points += offsets[ i ].count;
+					_infoRender.calls ++;
+					_infoRender.points += offsets[ i ].count;
 
 				}
 
@@ -1546,9 +1538,13 @@ THREE.WebGLRenderer = function ( parameters ) {
 		_frustum.setFromMatrix( _projScreenMatrix );
 
 		lights.length = 0;
+
 		opaqueObjects.length = 0;
 		transparentObjects.length = 0;
 
+		opaqueImmediateObjects.length = 0;
+		transparentImmediateObjects.length = 0;
+
 		sprites.length = 0;
 		lensFlares.length = 0;
 
@@ -1570,10 +1566,10 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		//
 
-		_this.info.render.calls = 0;
-		_this.info.render.vertices = 0;
-		_this.info.render.faces = 0;
-		_this.info.render.points = 0;
+		_infoRender.calls = 0;
+		_infoRender.vertices = 0;
+		_infoRender.faces = 0;
+		_infoRender.points = 0;
 
 		this.setRenderTarget( renderTarget );
 
@@ -1583,34 +1579,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		}
 
-		// set matrices for immediate objects
-
-		for ( var i = 0, il = objects.objectsImmediate.length; i < il; i ++ ) {
-
-			var webglObject = objects.objectsImmediate[ i ];
-			var object = webglObject.object;
-
-			if ( object.visible === true ) {
-
-				setupMatrices( object, camera );
-
-				var material = object.material;
-
-				if ( material.transparent ) {
-
-					webglObject.transparent = material;
-					webglObject.opaque = null;
-
-				} else {
-
-					webglObject.opaque = material;
-					webglObject.transparent = null;
-
-				}
-
-			}
-
-		}
+		//
 
 		if ( scene.overrideMaterial ) {
 
@@ -1618,7 +1587,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			renderObjects( opaqueObjects, camera, lights, fog, overrideMaterial );
 			renderObjects( transparentObjects, camera, lights, fog, overrideMaterial );
-			renderObjectsImmediate( objects.objectsImmediate, '', camera, lights, fog, overrideMaterial );
+
+			renderObjectsImmediate( opaqueImmediateObjects, camera, lights, fog, overrideMaterial );
+			renderObjectsImmediate( transparentImmediateObjects, camera, lights, fog, overrideMaterial );
 
 		} else {
 
@@ -1627,12 +1598,12 @@ THREE.WebGLRenderer = function ( parameters ) {
 			state.setBlending( THREE.NoBlending );
 
 			renderObjects( opaqueObjects, camera, lights, fog, null );
-			renderObjectsImmediate( objects.objectsImmediate, 'opaque', camera, lights, fog, null );
+			renderObjectsImmediate( opaqueImmediateObjects, camera, lights, fog, null );
 
 			// transparent pass (back-to-front order)
 
 			renderObjects( transparentObjects, camera, lights, fog, null );
-			renderObjectsImmediate( objects.objectsImmediate, 'transparent', camera, lights, fog, null );
+			renderObjectsImmediate( transparentImmediateObjects, camera, lights, fog, null );
 
 		}
 
@@ -1690,6 +1661,20 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					lensFlares.push( object );
 
+				} else if ( object instanceof THREE.ImmediateRenderObject ) {
+
+					var material = object.material;
+
+					if ( material.transparent ) {
+
+						transparentImmediateObjects.push( object );
+
+					} else {
+
+						opaqueImmediateObjects.push( object );
+
+					}
+
 				} else {
 
 					var webglObject = objects.objects[ object.id ];
@@ -1746,7 +1731,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 		for ( var i = 0, l = renderList.length; i < l; i ++ ) {
 
 			var webglObject = renderList[ i ];
-
 			var object = webglObject.object;
 
 			setupMatrices( object, camera );
@@ -1773,18 +1757,19 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	}
 
-	function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, overrideMaterial ) {
+	function renderObjectsImmediate( renderList, camera, lights, fog, overrideMaterial ) {
 
 		var material = overrideMaterial;
 
 		for ( var i = 0, l = renderList.length; i < l; i ++ ) {
 
-			var webglObject = renderList[ i ];
-			var object = webglObject.object;
+			var object = renderList[ i ];
+
+			setupMatrices( object, camera );
 
 			if ( object.visible === true ) {
 
-				if ( overrideMaterial === null ) material = webglObject[ materialType ];
+				if ( overrideMaterial === null ) material = object.material;
 
 				_this.renderImmediateObject( camera, lights, fog, material, object );
 
@@ -1802,15 +1787,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		_currentGeometryProgram = '';
 
-		if ( object.immediateRenderCallback ) {
-
-			object.immediateRenderCallback( program, _gl, _frustum );
-
-		} else {
+		object.render( function ( object ) {
 
-			object.render( function ( object ) { _this.renderBufferImmediate( object, program, material ); } );
+			_this.renderBufferImmediate( object, program, material );
 
-		}
+		} );
 
 	};
 
@@ -1930,6 +1911,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 		}
 
 		var code = chunks.join();
+		var programChange = true;
 
 		if ( !materialProperties.program ) {
 
@@ -1939,17 +1921,17 @@ THREE.WebGLRenderer = function ( parameters ) {
 		} else if ( materialProperties.program.code !== code ) {
 
 			// changed glsl or parameters
-			deallocateMaterial( material );
+			releaseMaterialProgramReference( material );
 
 		} else if ( shaderID !== undefined ) {
 
-			// same glsl
+			// same glsl and uniform list
 			return;
 
-		} else if ( materialProperties.__webglShader.uniforms === material.uniforms ) {
+		} else {
 
-			// same uniforms (container object)
-			return;
+			// only rebuild uniform list
+			programChange = false;
 
 		}
 
@@ -1986,7 +1968,12 @@ THREE.WebGLRenderer = function ( parameters ) {
 			if ( programInfo.code === code ) {
 
 				program = programInfo;
-				program.usedTimes ++;
+
+				if ( programChange ) {
+
+					program.usedTimes ++;
+
+				}
 
 				break;
 
@@ -2000,7 +1987,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 			program = new THREE.WebGLProgram( _this, code, material, parameters );
 			_programs.push( program );
 
-			_this.info.memory.programs = _programs.length;
+			_infoMemory.programs = _programs.length;
 
 		}
 
@@ -2937,7 +2924,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	}
 
-	function setupMatrices ( object, camera ) {
+	function setupMatrices( object, camera ) {
 
 		object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
 		object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
@@ -3244,7 +3231,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			textureProperties.__webglTexture = _gl.createTexture();
 
-			_this.info.memory.textures ++;
+			_infoMemory.textures ++;
 
 		}
 
@@ -3425,7 +3412,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					textureProperties.__image__webglTextureCube = _gl.createTexture();
 
-					_this.info.memory.textures ++;
+					_infoMemory.textures ++;
 
 				}
 
@@ -3586,7 +3573,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			renderTargetProperties.__webglTexture = _gl.createTexture();
 
-			_this.info.memory.textures ++;
+			_infoMemory.textures ++;
 
 			// Setup texture, create render and frame buffers
 

+ 47 - 48
src/renderers/webgl/WebGLObjects.js

@@ -5,7 +5,6 @@
 THREE.WebGLObjects = function ( gl, properties, info ) {
 
 	var objects = {};
-	var objectsImmediate = [];
 
 	var morphInfluences = new Float32Array( 8 );
 
@@ -34,10 +33,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 
 			delete objects[ object.id ];
 
-		} else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
-
-			removeInstances( objectsImmediate, object );
-
 		}
 
 		delete object._modelViewMatrix;
@@ -64,7 +59,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 	//
 
 	this.objects = objects;
-	this.objectsImmediate = objectsImmediate;
 
 	this.geometries = geometries;
 
@@ -94,16 +88,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 					z: 0
 				};
 
-			} else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
-
-				objectsImmediate.push( {
-					id: null,
-					object: object,
-					opaque: null,
-					transparent: null,
-					z: 0
-				} );
-
 			}
 
 		}
@@ -183,74 +167,90 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 
 		for ( var name in attributes ) {
 
-			var attribute = attributes[ name ];
+			updateAttribute( attributes[ name ], name );
 
-			var bufferType = ( name === 'index' ) ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
+		}
 
-			var data = ( attribute instanceof THREE.InterleavedBufferAttribute ) ? attribute.data : attribute;
+	}
 
-			var attributeProperties = properties.get( data );
+	function updateAttribute ( attribute, name ) {
 
-			if ( attributeProperties.__webglBuffer === undefined ) {
+		var bufferType = ( name === 'index' ) ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
 
-				attributeProperties.__webglBuffer = gl.createBuffer();
-				gl.bindBuffer( bufferType, attributeProperties.__webglBuffer );
+		var data = ( attribute instanceof THREE.InterleavedBufferAttribute ) ? attribute.data : attribute;
 
-				var usage = gl.STATIC_DRAW;
+		var attributeProperties = properties.get( data );
 
-				if ( data instanceof THREE.DynamicBufferAttribute
-						 || ( data instanceof THREE.InstancedBufferAttribute && data.dynamic === true )
-						 || ( data instanceof THREE.InterleavedBuffer && data.dynamic === true ) ) {
+		if ( attributeProperties.__webglBuffer === undefined ) {
 
-					usage = gl.DYNAMIC_DRAW;
+			createBuffer( attributeProperties, data, bufferType );
 
-				}
+		} else if ( attributeProperties.version !== data.version ) {
 
-				gl.bufferData( bufferType, data.array, usage );
+			updateBuffer( attributeProperties, data, bufferType );
 
-				data.needsUpdate = false;
+		}
 
-			} else if ( data.needsUpdate === true ) {
+	}
 
-				gl.bindBuffer( bufferType, attributeProperties.__webglBuffer );
+	function createBuffer ( attributeProperties, data, bufferType ) {
 
-				if ( data.updateRange === undefined || data.updateRange.count === -1 ) { // Not using update ranges
+		attributeProperties.__webglBuffer = gl.createBuffer();
+		gl.bindBuffer( bufferType, attributeProperties.__webglBuffer );
 
-					gl.bufferSubData( bufferType, 0, data.array );
+		var usage = gl.STATIC_DRAW;
 
-				} else if ( data.updateRange.count === 0 ) {
+		if ( data instanceof THREE.DynamicBufferAttribute
+			 || ( data instanceof THREE.InstancedBufferAttribute && data.dynamic === true )
+			 || ( data instanceof THREE.InterleavedBuffer && data.dynamic === true ) ) {
 
-					console.error( 'THREE.WebGLRenderer.updateObject: using updateRange for THREE.DynamicBufferAttribute and marked as needsUpdate but count is 0, ensure you are using set methods or updating manually.' );
+			usage = gl.DYNAMIC_DRAW;
 
-				} else {
+		}
 
-					gl.bufferSubData( bufferType, data.updateRange.offset * data.array.BYTES_PER_ELEMENT,
-									 data.array.subarray( data.updateRange.offset, data.updateRange.offset + data.updateRange.count ) );
+		gl.bufferData( bufferType, data.array, usage );
 
-					data.updateRange.count = 0; // reset range
+		attributeProperties.version = data.version;
 
-				}
+	}
 
-				data.needsUpdate = false;
+	function updateBuffer ( attributeProperties, data, bufferType ) {
 
-			}
+		gl.bindBuffer( bufferType, attributeProperties.__webglBuffer );
+
+		if ( data.updateRange === undefined || data.updateRange.count === -1 ) { // Not using update ranges
+
+			gl.bufferSubData( bufferType, 0, data.array );
+
+		} else if ( data.updateRange.count === 0 ) {
+
+			console.error( 'THREE.WebGLRenderer.updateObject: using updateRange for THREE.DynamicBufferAttribute and marked as needsUpdate but count is 0, ensure you are using set methods or updating manually.' );
+
+		} else {
+
+			gl.bufferSubData( bufferType, data.updateRange.offset * data.array.BYTES_PER_ELEMENT,
+							  data.array.subarray( data.updateRange.offset, data.updateRange.offset + data.updateRange.count ) );
+
+			data.updateRange.count = 0; // reset range
 
 		}
 
-	};
+		attributeProperties.version = data.version;
+
+	}
 
 	// returns the webgl buffer for a specified attribute
 	this.getAttributeBuffer = function ( attribute ) {
 
 		if ( attribute instanceof THREE.InterleavedBufferAttribute ) {
 
-			return properties.get( attribute.data ).__webglBuffer
+			return properties.get( attribute.data ).__webglBuffer;
 
 		}
 
 		return properties.get( attribute ).__webglBuffer;
 
-	}
+	};
 
 	this.update = function ( renderList ) {
 
@@ -271,7 +271,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 	this.clear = function () {
 
 		objects = {};
-		objectsImmediate = [];
 
 	};
 

+ 58 - 20
src/renderers/webgl/WebGLProgram.js

@@ -22,7 +22,6 @@ THREE.WebGLProgram = ( function () {
 
 	function fetchUniformLocations( gl, program, identifiers ) {
 
-
 		var uniforms = {};
 
 		var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );
@@ -361,8 +360,8 @@ THREE.WebGLProgram = ( function () {
 		var vertexGlsl = prefixVertex + vertexShader;
 		var fragmentGlsl = prefixFragment + fragmentShader;
 
-		var glVertexShader = new THREE.WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
-		var glFragmentShader = new THREE.WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
+		var glVertexShader = THREE.WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
+		var glFragmentShader = THREE.WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
 
 		gl.attachShader( program, glVertexShader );
 		gl.attachShader( program, glFragmentShader );
@@ -379,19 +378,53 @@ THREE.WebGLProgram = ( function () {
 
 		gl.linkProgram( program );
 
-		var programLogInfo = gl.getProgramInfoLog( program );
-		var vertexErrorLogInfo = gl.getShaderInfoLog( glVertexShader );
-		var fragmentErrorLogInfo = gl.getShaderInfoLog( glFragmentShader );
+		var programLog = gl.getProgramInfoLog( program );
+		var vertexLog = gl.getShaderInfoLog( glVertexShader );
+		var fragmentLog = gl.getShaderInfoLog( glFragmentShader );
+
+		var runnable = true;
+		var haveDiagnostics = true;
 
 		if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {
 
-			console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLogInfo, vertexErrorLogInfo, fragmentErrorLogInfo );
+			runnable = false;
+
+			console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog );
+
+		} else if ( programLog !== '' ) {
+
+			console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
+
+		} else if ( vertexLog === '' || fragmentLog === '' ) {
+
+			haveDiagnostics = false;
 
 		}
 
-		if ( programLogInfo !== '' ) {
+		if ( haveDiagnostics ) {
+
+			this.diagnostics = {
+
+				runnable: runnable,
+				material: material,
+
+				programLog: programLog,
+
+				vertexShader: {
+
+					log: vertexLog,
+					prefix: prefixVertex
+
+				},
 
-			console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLogInfo );
+				fragmentShader: {
+
+					log: fragmentLog,
+					prefix: prefixFragment
+
+				}
+
+			};
 
 		}
 
@@ -402,28 +435,33 @@ THREE.WebGLProgram = ( function () {
 
 		// set up caching for uniform locations
 
-		var getUniforms = function() { return this._cachedUniforms; };
+		var cachedUniforms;
 
 		this.getUniforms = function() {
 
-			// fetch, cache, and next time just use a dumb accessor
-			var uniforms = fetchUniformLocations( gl, program );
-			this._cachedUniforms = uniforms;
-			this.getUniforms = getUniforms;
-			return uniforms;
+			if ( cachedUniforms === undefined ) {
+
+				cachedUniforms = fetchUniformLocations( gl, program );
+
+			}
+
+			return cachedUniforms;
 
 		};
 
 		// set up caching for attribute locations
 
-		var getAttributes = function() { return this._cachedAttributes; };
+		var cachedAttributes;
 
 		this.getAttributes = function() {
 
-			var attributes = fetchAttributeLocations( gl, program );
-			this._cachedAttributes = attributes;
-			this.getAttributes = getAttributes;
-			return attributes;
+			if ( cachedAttributes === undefined ) {
+
+				cachedAttributes = fetchAttributeLocations( gl, program );
+
+			}
+
+			return cachedAttributes;
 
 		};
 

+ 7 - 3
src/renderers/webgl/WebGLProperties.js

@@ -8,13 +8,17 @@ THREE.WebGLProperties = function () {
 
 	this.get = function ( object ) {
 
-		if ( properties[ object.uuid ] === undefined ) {
+		var uuid = object.uuid;
+		var map = properties[ uuid ];
 
-			properties[ object.uuid ] = {};
+		if ( map === undefined ) {
+
+			map = {};
+			properties[ uuid ] = map;
 
 		}
 
-		return properties[ object.uuid ];
+		return map;
 
 	};
 

+ 10 - 21
src/renderers/webgl/WebGLShadowMap.js

@@ -14,7 +14,6 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	_max = new THREE.Vector3(),
 
 	_webglObjects = _objects.objects,
-	_webglObjectsImmediate = _objects.objectsImmediate,
 
 	_matrixPosition = new THREE.Vector3(),
 
@@ -63,6 +62,10 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	var scope = this;
 
 	this.enabled = false;
+
+	this.autoUpdate = true;
+	this.needsUpdate = false;
+
 	this.type = THREE.PCFShadowMap;
 	this.cullFace = THREE.CullFaceFront;
 	this.cascade = false;
@@ -70,6 +73,7 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	this.render = function ( scene, camera ) {
 
 		if ( scope.enabled === false ) return;
+		if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
 
 		var i, il, j, jl, n,
 
@@ -311,23 +315,6 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 			}
 
-			// set matrices and render immediate objects
-
-			for ( j = 0, jl = _webglObjectsImmediate.length; j < jl; j ++ ) {
-
-				webglObject = _webglObjectsImmediate[ j ];
-				object = webglObject.object;
-
-				if ( object.visible && object.castShadow ) {
-
-					object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
-
-					_renderer.renderImmediateObject( shadowCamera, _lights, fog, _depthMaterial, object );
-
-				}
-
-			}
-
 		}
 
 		// restore GL state
@@ -346,9 +333,11 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 		_renderer.resetGLState();
 
+		scope.needsUpdate = false;
+
 	};
 
-	function projectObject( object, shadowCamera ) {
+	function projectObject( object, camera ) {
 
 		if ( object.visible === true ) {
 
@@ -356,14 +345,14 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 			if ( webglObject && object.castShadow && ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) ) {
 
-				object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+				object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
 				_renderList.push( webglObject );
 
 			}
 
 			for ( var i = 0, l = object.children.length; i < l; i ++ ) {
 
-				projectObject( object.children[ i ], shadowCamera );
+				projectObject( object.children[ i ], camera );
 
 			}
 

+ 34 - 34
src/renderers/webgl/WebGLState.js

@@ -9,7 +9,7 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 	var newAttributes = new Uint8Array( 16 );
 	var enabledAttributes = new Uint8Array( 16 );
 
-	var switches = {};
+	var capabilities = {};
 
 	var currentBlending = null;
 	var currentBlendEquation = null;
@@ -20,7 +20,6 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 	var currentBlendDstAlpha = null;
 
 	var currentDepthFunc = null;
-	var currentDepthTest = null;
 	var currentDepthWrite = null;
 
 	var currentColorWrite = null;
@@ -29,7 +28,6 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	var currentLineWidth = null;
 
-	var currentPolygonOffset = null;
 	var currentPolygonOffsetFactor = null;
 	var currentPolygonOffsetUnits = null;
 
@@ -44,7 +42,7 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 		gl.clearDepth( 1 );
 		gl.clearStencil( 0 );
 
-		gl.enable( gl.DEPTH_TEST );
+		this.enable( gl.DEPTH_TEST );
 		gl.depthFunc( gl.LEQUAL );
 
 		gl.frontFace( gl.CCW );
@@ -97,10 +95,10 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	this.enable = function ( id ) {
 
-		if ( switches[ id ] !== true ) {
+		if ( capabilities[ id ] !== true ) {
 
 			gl.enable( id );
-			switches[ id ] = true;
+			capabilities[ id ] = true;
 
 		}
 
@@ -108,10 +106,10 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	this.disable = function ( id ) {
 
-		if ( switches[ id ] !== false ) {
+		if ( capabilities[ id ] !== false ) {
 
 			gl.disable( id );
-			switches[ id ] = false;
+			capabilities[ id ] = false;
 
 		}
 
@@ -270,19 +268,13 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	this.setDepthTest = function ( depthTest ) {
 
-		if ( currentDepthTest !== depthTest ) {
+		if ( depthTest ) {
 
-			if ( depthTest ) {
+			this.enable( gl.DEPTH_TEST );
 
-				gl.enable( gl.DEPTH_TEST );
-
-			} else {
-
-				gl.disable( gl.DEPTH_TEST );
-
-			}
+		} else {
 
-			currentDepthTest = depthTest;
+			this.disable( gl.DEPTH_TEST );
 
 		}
 
@@ -342,25 +334,19 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	};
 
-	this.setPolygonOffset = function ( polygonoffset, factor, units ) {
+	this.setPolygonOffset = function ( polygonOffset, factor, units ) {
 
-		if ( currentPolygonOffset !== polygonoffset ) {
+		if ( polygonOffset ) {
 
-			if ( polygonoffset ) {
+			this.enable( gl.POLYGON_OFFSET_FILL );
 
-				gl.enable( gl.POLYGON_OFFSET_FILL );
-
-			} else {
-
-				gl.disable( gl.POLYGON_OFFSET_FILL );
-
-			}
+		} else {
 
-			currentPolygonOffset = polygonoffset;
+			this.disable( gl.POLYGON_OFFSET_FILL );
 
 		}
 
-		if ( polygonoffset && ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) ) {
+		if ( polygonOffset && ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) ) {
 
 			gl.polygonOffset( factor, units );
 
@@ -371,6 +357,20 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	};
 
+	this.setScissorTest = function ( scissorTest ) {
+
+		if ( scissorTest ) {
+
+			this.enable( gl.SCISSOR_TEST );
+
+		} else {
+
+			this.disable( gl.SCISSOR_TEST );
+
+		}
+
+	};
+
 	// texture
 
 	this.activeTexture = function ( webglSlot ) {
@@ -394,12 +394,12 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 		}
 
-		var boundTexture = currentBoundTextures[currentTextureSlot];
+		var boundTexture = currentBoundTextures[ currentTextureSlot ];
 
 		if ( boundTexture === undefined ) {
 
 			boundTexture = { type: undefined, texture: undefined };
-			currentBoundTextures[currentTextureSlot] = boundTexture;
+			currentBoundTextures[ currentTextureSlot ] = boundTexture;
 
 		}
 
@@ -457,10 +457,10 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 		}
 
-		switches = {};
+		capabilities = {};
 
 		currentBlending = null;
-		currentDepthTest = null;
+
 		currentDepthWrite = null;
 		currentColorWrite = null;
 

+ 3 - 3
src/renderers/webgl/plugins/LensFlarePlugin.js

@@ -361,7 +361,7 @@ THREE.LensFlarePlugin = function ( renderer, flares ) {
 				gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z );
 
 				state.disable( gl.BLEND );
-				gl.enable( gl.DEPTH_TEST );
+				state.enable( gl.DEPTH_TEST );
 
 				gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
 
@@ -376,7 +376,7 @@ THREE.LensFlarePlugin = function ( renderer, flares ) {
 				// restore graphics
 
 				gl.uniform1i( uniforms.renderType, 1 );
-				gl.disable( gl.DEPTH_TEST );
+				state.disable( gl.DEPTH_TEST );
 
 				state.activeTexture( gl.TEXTURE1 );
 				state.bindTexture( gl.TEXTURE_2D, tempTexture );
@@ -440,7 +440,7 @@ THREE.LensFlarePlugin = function ( renderer, flares ) {
 		// restore gl
 
 		state.enable( gl.CULL_FACE );
-		gl.enable( gl.DEPTH_TEST );
+		state.enable( gl.DEPTH_TEST );
 		gl.depthMask( true );
 
 		renderer.resetGLState();

+ 85 - 0
test/unit/extras/ImageUtils.test.js

@@ -0,0 +1,85 @@
+QUnit.module( "ImageLoader", {
+
+	beforeEach: function() {
+
+		THREE.Cache.clear();
+
+	}
+
+});
+
+
+var good_url = '../../examples/textures/sprite.png';
+var bad_url = 'url_not_found';
+
+
+QUnit.test( "test load handler", function( assert ) {
+
+	var done = assert.async();
+
+	THREE.ImageUtils.loadTexture( good_url, undefined, function ( tex ) {
+
+		assert.success( "load handler should be called" );
+		assert.ok( tex, "texture is defined" );
+		assert.ok( tex.image, "texture.image is defined" );
+		done();
+
+	}, function () {
+
+		assert.fail( "error handler should not be called" );
+		done();
+
+	});
+
+});
+
+
+QUnit.test( "test error handler", function( assert ) {
+
+	var done = assert.async();
+
+	THREE.ImageUtils.loadTexture( bad_url, undefined, function () {
+
+		assert.fail( "load handler should not be called" );
+		done();
+
+	}, function ( event ) {
+
+		assert.success( "error handler should be called" );
+		assert.ok( event.type === 'error', "should have error event" );
+
+		done();
+
+	});
+
+});
+
+
+QUnit.test( "test cached texture", function( assert ) {
+
+	var done = assert.async();
+
+	var rtex1 = THREE.ImageUtils.loadTexture( good_url, undefined, function ( tex1 ) {
+
+		assert.ok( rtex1.image !== undefined, "texture 1 image is loaded" );
+		assert.equal( rtex1, tex1, "texture 1 callback is equal to return" );
+		
+		var rtex2 = THREE.ImageUtils.loadTexture( good_url, undefined, function ( tex2 ) {
+			
+			assert.ok( rtex2 !== undefined, "cached callback is async" );
+			assert.ok( rtex2.image !== undefined, "texture 2 image is loaded" );
+			assert.equal( rtex2, tex2, "texture 2 callback is equal to return" );
+			
+			done();
+
+		});
+
+		assert.ok( rtex2, "texture 2 return is defined" );
+
+	});
+	
+	assert.ok( rtex1, "texture 1 return is defined" );
+	assert.ok( rtex1.image === undefined, "texture 1 image is not loaded" );
+
+});
+

+ 7 - 5
test/unit/geometry/EdgesGeometry.js

@@ -96,7 +96,7 @@ function testEdges ( vertList, idxList, numAfter ) {
 		var numBefore = idxList.length;
 		equal( countEdges (geom), numBefore, "Edges before!" );
 
-		var egeom = new THREE.EdgesGeometry ( geom );;
+		var egeom = new THREE.EdgesGeometry( geom );
 
 		equal( countEdges (egeom), numAfter, "Edges after!" );
 		output( geom, egeom );
@@ -163,7 +163,7 @@ function createIndexedBufferGeometry ( vertList, idxList ) {
 
 function addDrawCalls ( geometry ) {
 
-	var numTris = geometry.getAttribute( 'index' ).length / 3;
+	var numTris = geometry.getAttribute( 'index' ).count / 3;
 
 	var offset = 0;
 	for ( var i = 0 ; i < numTris; i ++ ) {
@@ -183,7 +183,7 @@ function countEdges ( geom ) {
 
 	if ( geom instanceof THREE.EdgesGeometry ) {
 
-		return geom.getAttribute( 'position' ).length / 2;
+		return geom.getAttribute( 'position' ).count / 2;
 
 	}
 
@@ -196,11 +196,11 @@ function countEdges ( geom ) {
 	var indices = geom.getAttribute( 'index' );
 	if ( indices !== undefined ) {
 
-		return indices.length;
+		return indices.count;
 
 	}
 
-	return geom.getAttribute( 'position' ).length;
+	return geom.getAttribute( 'position' ).count;
 
 }
 
@@ -228,7 +228,9 @@ function output ( geom, egeom ) {
 	scene.add(edges);
 
 	if (scene.children.length % 8 === 0) {
+
 		xoffset += 2;
+
 	}
 
 }

+ 0 - 1977
test/unit/qunit-1.10.0.js

@@ -1,1977 +0,0 @@
-/**
- * QUnit v1.10.0 - A JavaScript Unit Testing Framework
- *
- * http://qunitjs.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- */
-
-(function( window ) {
-
-var QUnit,
-	config,
-	onErrorFnPrev,
-	testId = 0,
-	fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
-	toString = Object.prototype.toString,
-	hasOwn = Object.prototype.hasOwnProperty,
-	// Keep a local reference to Date (GH-283)
-	Date = window.Date,
-	defined = {
-	setTimeout: typeof window.setTimeout !== "undefined",
-	sessionStorage: (function() {
-		var x = "qunit-test-string";
-		try {
-			sessionStorage.setItem( x, x );
-			sessionStorage.removeItem( x );
-			return true;
-		} catch( e ) {
-			return false;
-		}
-	}())
-};
-
-function Test( settings ) {
-	extend( this, settings );
-	this.assertions = [];
-	this.testNumber = ++Test.count;
-}
-
-Test.count = 0;
-
-Test.prototype = {
-	init: function() {
-		var a, b, li,
-        tests = id( "qunit-tests" );
-
-		if ( tests ) {
-			b = document.createElement( "strong" );
-			b.innerHTML = this.name;
-
-			// `a` initialized at top of scope
-			a = document.createElement( "a" );
-			a.innerHTML = "Rerun";
-			a.href = QUnit.url({ testNumber: this.testNumber });
-
-			li = document.createElement( "li" );
-			li.appendChild( b );
-			li.appendChild( a );
-			li.className = "running";
-			li.id = this.id = "qunit-test-output" + testId++;
-
-			tests.appendChild( li );
-		}
-	},
-	setup: function() {
-		if ( this.module !== config.previousModule ) {
-			if ( config.previousModule ) {
-				runLoggingCallbacks( "moduleDone", QUnit, {
-					name: config.previousModule,
-					failed: config.moduleStats.bad,
-					passed: config.moduleStats.all - config.moduleStats.bad,
-					total: config.moduleStats.all
-				});
-			}
-			config.previousModule = this.module;
-			config.moduleStats = { all: 0, bad: 0 };
-			runLoggingCallbacks( "moduleStart", QUnit, {
-				name: this.module
-			});
-		} else if ( config.autorun ) {
-			runLoggingCallbacks( "moduleStart", QUnit, {
-				name: this.module
-			});
-		}
-
-		config.current = this;
-
-		this.testEnvironment = extend({
-			setup: function() {},
-			teardown: function() {}
-		}, this.moduleTestEnvironment );
-
-		runLoggingCallbacks( "testStart", QUnit, {
-			name: this.testName,
-			module: this.module
-		});
-
-		// allow utility functions to access the current test environment
-		// TODO why??
-		QUnit.current_testEnvironment = this.testEnvironment;
-
-		if ( !config.pollution ) {
-			saveGlobal();
-		}
-		if ( config.notrycatch ) {
-			this.testEnvironment.setup.call( this.testEnvironment );
-			return;
-		}
-		try {
-			this.testEnvironment.setup.call( this.testEnvironment );
-		} catch( e ) {
-			QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
-		}
-	},
-	run: function() {
-		config.current = this;
-
-		var running = id( "qunit-testresult" );
-
-		if ( running ) {
-			running.innerHTML = "Running: <br/>" + this.name;
-		}
-
-		if ( this.async ) {
-			QUnit.stop();
-		}
-
-		if ( config.notrycatch ) {
-			this.callback.call( this.testEnvironment, QUnit.assert );
-			return;
-		}
-
-		try {
-			this.callback.call( this.testEnvironment, QUnit.assert );
-		} catch( e ) {
-			QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
-			// else next test will carry the responsibility
-			saveGlobal();
-
-			// Restart the tests if they're blocking
-			if ( config.blocking ) {
-				QUnit.start();
-			}
-		}
-	},
-	teardown: function() {
-		config.current = this;
-		if ( config.notrycatch ) {
-			this.testEnvironment.teardown.call( this.testEnvironment );
-			return;
-		} else {
-			try {
-				this.testEnvironment.teardown.call( this.testEnvironment );
-			} catch( e ) {
-				QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
-			}
-		}
-		checkPollution();
-	},
-	finish: function() {
-		config.current = this;
-		if ( config.requireExpects && this.expected == null ) {
-			QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
-		} else if ( this.expected != null && this.expected != this.assertions.length ) {
-			QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
-		} else if ( this.expected == null && !this.assertions.length ) {
-			QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
-		}
-
-		var assertion, a, b, i, li, ol,
-			test = this,
-			good = 0,
-			bad = 0,
-			tests = id( "qunit-tests" );
-
-		config.stats.all += this.assertions.length;
-		config.moduleStats.all += this.assertions.length;
-
-		if ( tests ) {
-			ol = document.createElement( "ol" );
-
-			for ( i = 0; i < this.assertions.length; i++ ) {
-				assertion = this.assertions[i];
-
-				li = document.createElement( "li" );
-				li.className = assertion.result ? "pass" : "fail";
-				li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
-				ol.appendChild( li );
-
-				if ( assertion.result ) {
-					good++;
-				} else {
-					bad++;
-					config.stats.bad++;
-					config.moduleStats.bad++;
-				}
-			}
-
-			// store result when possible
-			if ( QUnit.config.reorder && defined.sessionStorage ) {
-				if ( bad ) {
-					sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
-				} else {
-					sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
-				}
-			}
-
-			if ( bad === 0 ) {
-				ol.style.display = "none";
-			}
-
-			// `b` initialized at top of scope
-			b = document.createElement( "strong" );
-			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
-
-			addEvent(b, "click", function() {
-				var next = b.nextSibling.nextSibling,
-					display = next.style.display;
-				next.style.display = display === "none" ? "block" : "none";
-			});
-
-			addEvent(b, "dblclick", function( e ) {
-				var target = e && e.target ? e.target : window.event.srcElement;
-				if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
-					target = target.parentNode;
-				}
-				if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
-					window.location = QUnit.url({ testNumber: test.testNumber });
-				}
-			});
-
-			// `li` initialized at top of scope
-			li = id( this.id );
-			li.className = bad ? "fail" : "pass";
-			li.removeChild( li.firstChild );
-			a = li.firstChild;
-			li.appendChild( b );
-			li.appendChild ( a );
-			li.appendChild( ol );
-
-		} else {
-			for ( i = 0; i < this.assertions.length; i++ ) {
-				if ( !this.assertions[i].result ) {
-					bad++;
-					config.stats.bad++;
-					config.moduleStats.bad++;
-				}
-			}
-		}
-
-		runLoggingCallbacks( "testDone", QUnit, {
-			name: this.testName,
-			module: this.module,
-			failed: bad,
-			passed: this.assertions.length - bad,
-			total: this.assertions.length
-		});
-
-		QUnit.reset();
-
-		config.current = undefined;
-	},
-
-	queue: function() {
-		var bad,
-			test = this;
-
-		synchronize(function() {
-			test.init();
-		});
-		function run() {
-			// each of these can by async
-			synchronize(function() {
-				test.setup();
-			});
-			synchronize(function() {
-				test.run();
-			});
-			synchronize(function() {
-				test.teardown();
-			});
-			synchronize(function() {
-				test.finish();
-			});
-		}
-
-		// `bad` initialized at top of scope
-		// defer when previous test run passed, if storage is available
-		bad = QUnit.config.reorder && defined.sessionStorage &&
-						+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
-
-		if ( bad ) {
-			run();
-		} else {
-			synchronize( run, true );
-		}
-	}
-};
-
-// Root QUnit object.
-// `QUnit` initialized at top of scope
-QUnit = {
-
-	// call on start of module test to prepend name to all tests
-	module: function( name, testEnvironment ) {
-		config.currentModule = name;
-		config.currentModuleTestEnvironment = testEnvironment;
-		config.modules[name] = true;
-	},
-
-	asyncTest: function( testName, expected, callback ) {
-		if ( arguments.length === 2 ) {
-			callback = expected;
-			expected = null;
-		}
-
-		QUnit.test( testName, expected, callback, true );
-	},
-
-	test: function( testName, expected, callback, async ) {
-		var test,
-			name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
-
-		if ( arguments.length === 2 ) {
-			callback = expected;
-			expected = null;
-		}
-
-		if ( config.currentModule ) {
-			name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
-		}
-
-		test = new Test({
-			name: name,
-			testName: testName,
-			expected: expected,
-			async: async,
-			callback: callback,
-			module: config.currentModule,
-			moduleTestEnvironment: config.currentModuleTestEnvironment,
-			stack: sourceFromStacktrace( 2 )
-		});
-
-		if ( !validTest( test ) ) {
-			return;
-		}
-
-		test.queue();
-	},
-
-	// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
-	expect: function( asserts ) {
-		if (arguments.length === 1) {
-			config.current.expected = asserts;
-		} else {
-			return config.current.expected;
-		}
-	},
-
-	start: function( count ) {
-		config.semaphore -= count || 1;
-		// don't start until equal number of stop-calls
-		if ( config.semaphore > 0 ) {
-			return;
-		}
-		// ignore if start is called more often then stop
-		if ( config.semaphore < 0 ) {
-			config.semaphore = 0;
-		}
-		// A slight delay, to avoid any current callbacks
-		if ( defined.setTimeout ) {
-			window.setTimeout(function() {
-				if ( config.semaphore > 0 ) {
-					return;
-				}
-				if ( config.timeout ) {
-					clearTimeout( config.timeout );
-				}
-
-				config.blocking = false;
-				process( true );
-			}, 13);
-		} else {
-			config.blocking = false;
-			process( true );
-		}
-	},
-
-	stop: function( count ) {
-		config.semaphore += count || 1;
-		config.blocking = true;
-
-		if ( config.testTimeout && defined.setTimeout ) {
-			clearTimeout( config.timeout );
-			config.timeout = window.setTimeout(function() {
-				QUnit.ok( false, "Test timed out" );
-				config.semaphore = 1;
-				QUnit.start();
-			}, config.testTimeout );
-		}
-	}
-};
-
-// Asssert helpers
-// All of these must call either QUnit.push() or manually do:
-// - runLoggingCallbacks( "log", .. );
-// - config.current.assertions.push({ .. });
-QUnit.assert = {
-	/**
-	 * Asserts rough true-ish result.
-	 * @name ok
-	 * @function
-	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
-	 */
-	ok: function( result, msg ) {
-		if ( !config.current ) {
-			throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
-		}
-		result = !!result;
-
-		var source,
-			details = {
-				module: config.current.module,
-				name: config.current.testName,
-				result: result,
-				message: msg
-			};
-
-		msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
-		msg = "<span class='test-message'>" + msg + "</span>";
-
-		if ( !result ) {
-			source = sourceFromStacktrace( 2 );
-			if ( source ) {
-				details.source = source;
-				msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
-			}
-		}
-		runLoggingCallbacks( "log", QUnit, details );
-		config.current.assertions.push({
-			result: result,
-			message: msg
-		});
-	},
-
-	/**
-	 * Assert that the first two arguments are equal, with an optional message.
-	 * Prints out both actual and expected values.
-	 * @name equal
-	 * @function
-	 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
-	 */
-	equal: function( actual, expected, message ) {
-		QUnit.push( expected == actual, actual, expected, message );
-	},
-
-	/**
-	 * @name notEqual
-	 * @function
-	 */
-	notEqual: function( actual, expected, message ) {
-		QUnit.push( expected != actual, actual, expected, message );
-	},
-
-	/**
-	 * @name deepEqual
-	 * @function
-	 */
-	deepEqual: function( actual, expected, message ) {
-		QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
-	},
-
-	/**
-	 * @name notDeepEqual
-	 * @function
-	 */
-	notDeepEqual: function( actual, expected, message ) {
-		QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
-	},
-
-	/**
-	 * @name strictEqual
-	 * @function
-	 */
-	strictEqual: function( actual, expected, message ) {
-		QUnit.push( expected === actual, actual, expected, message );
-	},
-
-	/**
-	 * @name notStrictEqual
-	 * @function
-	 */
-	notStrictEqual: function( actual, expected, message ) {
-		QUnit.push( expected !== actual, actual, expected, message );
-	},
-
-	throws: function( block, expected, message ) {
-		var actual,
-			ok = false;
-
-		// 'expected' is optional
-		if ( typeof expected === "string" ) {
-			message = expected;
-			expected = null;
-		}
-
-		config.current.ignoreGlobalErrors = true;
-		try {
-			block.call( config.current.testEnvironment );
-		} catch (e) {
-			actual = e;
-		}
-		config.current.ignoreGlobalErrors = false;
-
-		if ( actual ) {
-			// we don't want to validate thrown error
-			if ( !expected ) {
-				ok = true;
-			// expected is a regexp
-			} else if ( QUnit.objectType( expected ) === "regexp" ) {
-				ok = expected.test( actual );
-			// expected is a constructor
-			} else if ( actual instanceof expected ) {
-				ok = true;
-			// expected is a validation function which returns true is validation passed
-			} else if ( expected.call( {}, actual ) === true ) {
-				ok = true;
-			}
-
-			QUnit.push( ok, actual, null, message );
-		} else {
-			QUnit.pushFailure( message, null, 'No exception was thrown.' );
-		}
-	}
-};
-
-/**
- * @deprecate since 1.8.0
- * Kept assertion helpers in root for backwards compatibility
- */
-extend( QUnit, QUnit.assert );
-
-/**
- * @deprecated since 1.9.0
- * Kept global "raises()" for backwards compatibility
- */
-QUnit.raises = QUnit.assert.throws;
-
-/**
- * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
- * Kept to avoid TypeErrors for undefined methods.
- */
-QUnit.equals = function() {
-	QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
-};
-QUnit.same = function() {
-	QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
-};
-
-// We want access to the constructor's prototype
-(function() {
-	function F() {}
-	F.prototype = QUnit;
-	QUnit = new F();
-	// Make F QUnit's constructor so that we can add to the prototype later
-	QUnit.constructor = F;
-}());
-
-/**
- * Config object: Maintain internal state
- * Later exposed as QUnit.config
- * `config` initialized at top of scope
- */
-config = {
-	// The queue of tests to run
-	queue: [],
-
-	// block until document ready
-	blocking: true,
-
-	// when enabled, show only failing tests
-	// gets persisted through sessionStorage and can be changed in UI via checkbox
-	hidepassed: false,
-
-	// by default, run previously failed tests first
-	// very useful in combination with "Hide passed tests" checked
-	reorder: true,
-
-	// by default, modify document.title when suite is done
-	altertitle: true,
-
-	// when enabled, all tests must call expect()
-	requireExpects: false,
-
-	// add checkboxes that are persisted in the query-string
-	// when enabled, the id is set to `true` as a `QUnit.config` property
-	urlConfig: [
-		{
-			id: "noglobals",
-			label: "Check for Globals",
-			tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
-		},
-		{
-			id: "notrycatch",
-			label: "No try-catch",
-			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
-		}
-	],
-
-	// Set of all modules.
-	modules: {},
-
-	// logging callback queues
-	begin: [],
-	done: [],
-	log: [],
-	testStart: [],
-	testDone: [],
-	moduleStart: [],
-	moduleDone: []
-};
-
-// Initialize more QUnit.config and QUnit.urlParams
-(function() {
-	var i,
-		location = window.location || { search: "", protocol: "file:" },
-		params = location.search.slice( 1 ).split( "&" ),
-		length = params.length,
-		urlParams = {},
-		current;
-
-	if ( params[ 0 ] ) {
-		for ( i = 0; i < length; i++ ) {
-			current = params[ i ].split( "=" );
-			current[ 0 ] = decodeURIComponent( current[ 0 ] );
-			// allow just a key to turn on a flag, e.g., test.html?noglobals
-			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
-			urlParams[ current[ 0 ] ] = current[ 1 ];
-		}
-	}
-
-	QUnit.urlParams = urlParams;
-
-	// String search anywhere in moduleName+testName
-	config.filter = urlParams.filter;
-
-	// Exact match of the module name
-	config.module = urlParams.module;
-
-	config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
-
-	// Figure out if we're running the tests from a server or not
-	QUnit.isLocal = location.protocol === "file:";
-}());
-
-// Export global variables, unless an 'exports' object exists,
-// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
-if ( typeof exports === "undefined" ) {
-	extend( window, QUnit );
-
-	// Expose QUnit object
-	window.QUnit = QUnit;
-}
-
-// Extend QUnit object,
-// these after set here because they should not be exposed as global functions
-extend( QUnit, {
-	config: config,
-
-	// Initialize the configuration options
-	init: function() {
-		extend( config, {
-			stats: { all: 0, bad: 0 },
-			moduleStats: { all: 0, bad: 0 },
-			started: +new Date(),
-			updateRate: 1000,
-			blocking: false,
-			autostart: true,
-			autorun: false,
-			filter: "",
-			queue: [],
-			semaphore: 0
-		});
-
-		var tests, banner, result,
-			qunit = id( "qunit" );
-
-		if ( qunit ) {
-			qunit.innerHTML =
-				"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
-				"<h2 id='qunit-banner'></h2>" +
-				"<div id='qunit-testrunner-toolbar'></div>" +
-				"<h2 id='qunit-userAgent'></h2>" +
-				"<ol id='qunit-tests'></ol>";
-		}
-
-		tests = id( "qunit-tests" );
-		banner = id( "qunit-banner" );
-		result = id( "qunit-testresult" );
-
-		if ( tests ) {
-			tests.innerHTML = "";
-		}
-
-		if ( banner ) {
-			banner.className = "";
-		}
-
-		if ( result ) {
-			result.parentNode.removeChild( result );
-		}
-
-		if ( tests ) {
-			result = document.createElement( "p" );
-			result.id = "qunit-testresult";
-			result.className = "result";
-			tests.parentNode.insertBefore( result, tests );
-			result.innerHTML = "Running...<br/>&nbsp;";
-		}
-	},
-
-	// Resets the test setup. Useful for tests that modify the DOM.
-	reset: function() {
-		var fixture = id( "qunit-fixture" );
-		if ( fixture ) {
-			fixture.innerHTML = config.fixture;
-		}
-	},
-
-	// Trigger an event on an element.
-	// @example triggerEvent( document.body, "click" );
-	triggerEvent: function( elem, type, event ) {
-		if ( document.createEvent ) {
-			event = document.createEvent( "MouseEvents" );
-			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
-				0, 0, 0, 0, 0, false, false, false, false, 0, null);
-
-			elem.dispatchEvent( event );
-		} else if ( elem.fireEvent ) {
-			elem.fireEvent( "on" + type );
-		}
-	},
-
-	// Safe object type checking
-	is: function( type, obj ) {
-		return QUnit.objectType( obj ) == type;
-	},
-
-	objectType: function( obj ) {
-		if ( typeof obj === "undefined" ) {
-				return "undefined";
-		// consider: typeof null === object
-		}
-		if ( obj === null ) {
-				return "null";
-		}
-
-		var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
-
-		switch ( type ) {
-			case "Number":
-				if ( isNaN(obj) ) {
-					return "nan";
-				}
-				return "number";
-			case "String":
-			case "Boolean":
-			case "Array":
-			case "Date":
-			case "RegExp":
-			case "Function":
-				return type.toLowerCase();
-		}
-		if ( typeof obj === "object" ) {
-			return "object";
-		}
-		return undefined;
-	},
-
-	push: function( result, actual, expected, message ) {
-		if ( !config.current ) {
-			throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
-		}
-
-		var output, source,
-			details = {
-				module: config.current.module,
-				name: config.current.testName,
-				result: result,
-				message: message,
-				actual: actual,
-				expected: expected
-			};
-
-		message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
-		message = "<span class='test-message'>" + message + "</span>";
-		output = message;
-
-		if ( !result ) {
-			expected = escapeInnerText( QUnit.jsDump.parse(expected) );
-			actual = escapeInnerText( QUnit.jsDump.parse(actual) );
-			output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
-
-			if ( actual != expected ) {
-				output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
-				output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
-			}
-
-			source = sourceFromStacktrace();
-
-			if ( source ) {
-				details.source = source;
-				output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
-			}
-
-			output += "</table>";
-		}
-
-		runLoggingCallbacks( "log", QUnit, details );
-
-		config.current.assertions.push({
-			result: !!result,
-			message: output
-		});
-	},
-
-	pushFailure: function( message, source, actual ) {
-		if ( !config.current ) {
-			throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
-		}
-
-		var output,
-			details = {
-				module: config.current.module,
-				name: config.current.testName,
-				result: false,
-				message: message
-			};
-
-		message = escapeInnerText( message ) || "error";
-		message = "<span class='test-message'>" + message + "</span>";
-		output = message;
-
-		output += "<table>";
-
-		if ( actual ) {
-			output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
-		}
-
-		if ( source ) {
-			details.source = source;
-			output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
-		}
-
-		output += "</table>";
-
-		runLoggingCallbacks( "log", QUnit, details );
-
-		config.current.assertions.push({
-			result: false,
-			message: output
-		});
-	},
-
-	url: function( params ) {
-		params = extend( extend( {}, QUnit.urlParams ), params );
-		var key,
-			querystring = "?";
-
-		for ( key in params ) {
-			if ( !hasOwn.call( params, key ) ) {
-				continue;
-			}
-			querystring += encodeURIComponent( key ) + "=" +
-				encodeURIComponent( params[ key ] ) + "&";
-		}
-		return window.location.pathname + querystring.slice( 0, -1 );
-	},
-
-	extend: extend,
-	id: id,
-	addEvent: addEvent
-	// load, equiv, jsDump, diff: Attached later
-});
-
-/**
- * @deprecated: Created for backwards compatibility with test runner that set the hook function
- * into QUnit.{hook}, instead of invoking it and passing the hook function.
- * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
- * Doing this allows us to tell if the following methods have been overwritten on the actual
- * QUnit object.
- */
-extend( QUnit.constructor.prototype, {
-
-	// Logging callbacks; all receive a single argument with the listed properties
-	// run test/logs.html for any related changes
-	begin: registerLoggingCallback( "begin" ),
-
-	// done: { failed, passed, total, runtime }
-	done: registerLoggingCallback( "done" ),
-
-	// log: { result, actual, expected, message }
-	log: registerLoggingCallback( "log" ),
-
-	// testStart: { name }
-	testStart: registerLoggingCallback( "testStart" ),
-
-	// testDone: { name, failed, passed, total }
-	testDone: registerLoggingCallback( "testDone" ),
-
-	// moduleStart: { name }
-	moduleStart: registerLoggingCallback( "moduleStart" ),
-
-	// moduleDone: { name, failed, passed, total }
-	moduleDone: registerLoggingCallback( "moduleDone" )
-});
-
-if ( typeof document === "undefined" || document.readyState === "complete" ) {
-	config.autorun = true;
-}
-
-QUnit.load = function() {
-	runLoggingCallbacks( "begin", QUnit, {} );
-
-	// Initialize the config, saving the execution queue
-	var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
-	    numModules = 0,
-	    moduleFilterHtml = "",
-		urlConfigHtml = "",
-		oldconfig = extend( {}, config );
-
-	QUnit.init();
-	extend(config, oldconfig);
-
-	config.blocking = false;
-
-	len = config.urlConfig.length;
-
-	for ( i = 0; i < len; i++ ) {
-		val = config.urlConfig[i];
-		if ( typeof val === "string" ) {
-			val = {
-				id: val,
-				label: val,
-				tooltip: "[no tooltip available]"
-			};
-		}
-		config[ val.id ] = QUnit.urlParams[ val.id ];
-		urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
-	}
-
-	moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined  ? "selected" : "" ) + ">< All Modules ></option>";
-	for ( i in config.modules ) {
-		if ( config.modules.hasOwnProperty( i ) ) {
-			numModules += 1;
-			moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
-		}
-	}
-	moduleFilterHtml += "</select>";
-
-	// `userAgent` initialized at top of scope
-	userAgent = id( "qunit-userAgent" );
-	if ( userAgent ) {
-		userAgent.innerHTML = navigator.userAgent;
-	}
-
-	// `banner` initialized at top of scope
-	banner = id( "qunit-header" );
-	if ( banner ) {
-		banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
-	}
-
-	// `toolbar` initialized at top of scope
-	toolbar = id( "qunit-testrunner-toolbar" );
-	if ( toolbar ) {
-		// `filter` initialized at top of scope
-		filter = document.createElement( "input" );
-		filter.type = "checkbox";
-		filter.id = "qunit-filter-pass";
-
-		addEvent( filter, "click", function() {
-			var tmp,
-				ol = document.getElementById( "qunit-tests" );
-
-			if ( filter.checked ) {
-				ol.className = ol.className + " hidepass";
-			} else {
-				tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
-				ol.className = tmp.replace( / hidepass /, " " );
-			}
-			if ( defined.sessionStorage ) {
-				if (filter.checked) {
-					sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
-				} else {
-					sessionStorage.removeItem( "qunit-filter-passed-tests" );
-				}
-			}
-		});
-
-		if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
-			filter.checked = true;
-			// `ol` initialized at top of scope
-			ol = document.getElementById( "qunit-tests" );
-			ol.className = ol.className + " hidepass";
-		}
-		toolbar.appendChild( filter );
-
-		// `label` initialized at top of scope
-		label = document.createElement( "label" );
-		label.setAttribute( "for", "qunit-filter-pass" );
-		label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
-		label.innerHTML = "Hide passed tests";
-		toolbar.appendChild( label );
-
-		urlConfigCheckboxes = document.createElement( 'span' );
-		urlConfigCheckboxes.innerHTML = urlConfigHtml;
-		addEvent( urlConfigCheckboxes, "change", function( event ) {
-			var params = {};
-			params[ event.target.name ] = event.target.checked ? true : undefined;
-			window.location = QUnit.url( params );
-		});
-		toolbar.appendChild( urlConfigCheckboxes );
-
-		if (numModules > 1) {
-			moduleFilter = document.createElement( 'span' );
-			moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
-			moduleFilter.innerHTML = moduleFilterHtml;
-			addEvent( moduleFilter, "change", function() {
-				var selectBox = moduleFilter.getElementsByTagName("select")[0],
-				    selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
-
-				window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
-			});
-			toolbar.appendChild(moduleFilter);
-		}
-	}
-
-	// `main` initialized at top of scope
-	main = id( "qunit-fixture" );
-	if ( main ) {
-		config.fixture = main.innerHTML;
-	}
-
-	if ( config.autostart ) {
-		QUnit.start();
-	}
-};
-
-addEvent( window, "load", QUnit.load );
-
-// `onErrorFnPrev` initialized at top of scope
-// Preserve other handlers
-onErrorFnPrev = window.onerror;
-
-// Cover uncaught exceptions
-// Returning true will surpress the default browser handler,
-// returning false will let it run.
-window.onerror = function ( error, filePath, linerNr ) {
-	var ret = false;
-	if ( onErrorFnPrev ) {
-		ret = onErrorFnPrev( error, filePath, linerNr );
-	}
-
-	// Treat return value as window.onerror itself does,
-	// Only do our handling if not surpressed.
-	if ( ret !== true ) {
-		if ( QUnit.config.current ) {
-			if ( QUnit.config.current.ignoreGlobalErrors ) {
-				return true;
-			}
-			QUnit.pushFailure( error, filePath + ":" + linerNr );
-		} else {
-			QUnit.test( "global failure", extend( function() {
-				QUnit.pushFailure( error, filePath + ":" + linerNr );
-			}, { validTest: validTest } ) );
-		}
-		return false;
-	}
-
-	return ret;
-};
-
-function done() {
-	config.autorun = true;
-
-	// Log the last module results
-	if ( config.currentModule ) {
-		runLoggingCallbacks( "moduleDone", QUnit, {
-			name: config.currentModule,
-			failed: config.moduleStats.bad,
-			passed: config.moduleStats.all - config.moduleStats.bad,
-			total: config.moduleStats.all
-		});
-	}
-
-	var i, key,
-		banner = id( "qunit-banner" ),
-		tests = id( "qunit-tests" ),
-		runtime = +new Date() - config.started,
-		passed = config.stats.all - config.stats.bad,
-		html = [
-			"Tests completed in ",
-			runtime,
-			" milliseconds.<br/>",
-			"<span class='passed'>",
-			passed,
-			"</span> tests of <span class='total'>",
-			config.stats.all,
-			"</span> passed, <span class='failed'>",
-			config.stats.bad,
-			"</span> failed."
-		].join( "" );
-
-	if ( banner ) {
-		banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
-	}
-
-	if ( tests ) {
-		id( "qunit-testresult" ).innerHTML = html;
-	}
-
-	if ( config.altertitle && typeof document !== "undefined" && document.title ) {
-		// show ✖ for good, ✔ for bad suite result in title
-		// use escape sequences in case file gets loaded with non-utf-8-charset
-		document.title = [
-			( config.stats.bad ? "\u2716" : "\u2714" ),
-			document.title.replace( /^[\u2714\u2716] /i, "" )
-		].join( " " );
-	}
-
-	// clear own sessionStorage items if all tests passed
-	if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
-		// `key` & `i` initialized at top of scope
-		for ( i = 0; i < sessionStorage.length; i++ ) {
-			key = sessionStorage.key( i++ );
-			if ( key.indexOf( "qunit-test-" ) === 0 ) {
-				sessionStorage.removeItem( key );
-			}
-		}
-	}
-
-	// scroll back to top to show results
-	if ( window.scrollTo ) {
-		window.scrollTo(0, 0);
-	}
-
-	runLoggingCallbacks( "done", QUnit, {
-		failed: config.stats.bad,
-		passed: passed,
-		total: config.stats.all,
-		runtime: runtime
-	});
-}
-
-/** @return Boolean: true if this test should be ran */
-function validTest( test ) {
-	var include,
-		filter = config.filter && config.filter.toLowerCase(),
-		module = config.module && config.module.toLowerCase(),
-		fullName = (test.module + ": " + test.testName).toLowerCase();
-
-	// Internally-generated tests are always valid
-	if ( test.callback && test.callback.validTest === validTest ) {
-		delete test.callback.validTest;
-		return true;
-	}
-
-	if ( config.testNumber ) {
-		return test.testNumber === config.testNumber;
-	}
-
-	if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
-		return false;
-	}
-
-	if ( !filter ) {
-		return true;
-	}
-
-	include = filter.charAt( 0 ) !== "!";
-	if ( !include ) {
-		filter = filter.slice( 1 );
-	}
-
-	// If the filter matches, we need to honour include
-	if ( fullName.indexOf( filter ) !== -1 ) {
-		return include;
-	}
-
-	// Otherwise, do the opposite
-	return !include;
-}
-
-// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
-// Later Safari and IE10 are supposed to support error.stack as well
-// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
-function extractStacktrace( e, offset ) {
-	offset = offset === undefined ? 3 : offset;
-
-	var stack, include, i, regex;
-
-	if ( e.stacktrace ) {
-		// Opera
-		return e.stacktrace.split( "\n" )[ offset + 3 ];
-	} else if ( e.stack ) {
-		// Firefox, Chrome
-		stack = e.stack.split( "\n" );
-		if (/^error$/i.test( stack[0] ) ) {
-			stack.shift();
-		}
-		if ( fileName ) {
-			include = [];
-			for ( i = offset; i < stack.length; i++ ) {
-				if ( stack[ i ].indexOf( fileName ) != -1 ) {
-					break;
-				}
-				include.push( stack[ i ] );
-			}
-			if ( include.length ) {
-				return include.join( "\n" );
-			}
-		}
-		return stack[ offset ];
-	} else if ( e.sourceURL ) {
-		// Safari, PhantomJS
-		// hopefully one day Safari provides actual stacktraces
-		// exclude useless self-reference for generated Error objects
-		if ( /qunit.js$/.test( e.sourceURL ) ) {
-			return;
-		}
-		// for actual exceptions, this is useful
-		return e.sourceURL + ":" + e.line;
-	}
-}
-function sourceFromStacktrace( offset ) {
-	try {
-		throw new Error();
-	} catch ( e ) {
-		return extractStacktrace( e, offset );
-	}
-}
-
-function escapeInnerText( s ) {
-	if ( !s ) {
-		return "";
-	}
-	s = s + "";
-	return s.replace( /[\&<>]/g, function( s ) {
-		switch( s ) {
-			case "&": return "&amp;";
-			case "<": return "&lt;";
-			case ">": return "&gt;";
-			default: return s;
-		}
-	});
-}
-
-function synchronize( callback, last ) {
-	config.queue.push( callback );
-
-	if ( config.autorun && !config.blocking ) {
-		process( last );
-	}
-}
-
-function process( last ) {
-	function next() {
-		process( last );
-	}
-	var start = new Date().getTime();
-	config.depth = config.depth ? config.depth + 1 : 1;
-
-	while ( config.queue.length && !config.blocking ) {
-		if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
-			config.queue.shift()();
-		} else {
-			window.setTimeout( next, 13 );
-			break;
-		}
-	}
-	config.depth--;
-	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
-		done();
-	}
-}
-
-function saveGlobal() {
-	config.pollution = [];
-
-	if ( config.noglobals ) {
-		for ( var key in window ) {
-			// in Opera sometimes DOM element ids show up here, ignore them
-			if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
-				continue;
-			}
-			config.pollution.push( key );
-		}
-	}
-}
-
-function checkPollution( name ) {
-	var newGlobals,
-		deletedGlobals,
-		old = config.pollution;
-
-	saveGlobal();
-
-	newGlobals = diff( config.pollution, old );
-	if ( newGlobals.length > 0 ) {
-		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
-	}
-
-	deletedGlobals = diff( old, config.pollution );
-	if ( deletedGlobals.length > 0 ) {
-		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
-	}
-}
-
-// returns a new Array with the elements that are in a but not in b
-function diff( a, b ) {
-	var i, j,
-		result = a.slice();
-
-	for ( i = 0; i < result.length; i++ ) {
-		for ( j = 0; j < b.length; j++ ) {
-			if ( result[i] === b[j] ) {
-				result.splice( i, 1 );
-				i--;
-				break;
-			}
-		}
-	}
-	return result;
-}
-
-function extend( a, b ) {
-	for ( var prop in b ) {
-		if ( b[ prop ] === undefined ) {
-			delete a[ prop ];
-
-		// Avoid "Member not found" error in IE8 caused by setting window.constructor
-		} else if ( prop !== "constructor" || a !== window ) {
-			a[ prop ] = b[ prop ];
-		}
-	}
-
-	return a;
-}
-
-function addEvent( elem, type, fn ) {
-	if ( elem.addEventListener ) {
-		elem.addEventListener( type, fn, false );
-	} else if ( elem.attachEvent ) {
-		elem.attachEvent( "on" + type, fn );
-	} else {
-		fn();
-	}
-}
-
-function id( name ) {
-	return !!( typeof document !== "undefined" && document && document.getElementById ) &&
-		document.getElementById( name );
-}
-
-function registerLoggingCallback( key ) {
-	return function( callback ) {
-		config[key].push( callback );
-	};
-}
-
-// Supports deprecated method of completely overwriting logging callbacks
-function runLoggingCallbacks( key, scope, args ) {
-	//debugger;
-	var i, callbacks;
-	if ( QUnit.hasOwnProperty( key ) ) {
-		QUnit[ key ].call(scope, args );
-	} else {
-		callbacks = config[ key ];
-		for ( i = 0; i < callbacks.length; i++ ) {
-			callbacks[ i ].call( scope, args );
-		}
-	}
-}
-
-// Test for equality any JavaScript type.
-// Author: Philippe Rathé <[email protected]>
-QUnit.equiv = (function() {
-
-	// Call the o related callback with the given arguments.
-	function bindCallbacks( o, callbacks, args ) {
-		var prop = QUnit.objectType( o );
-		if ( prop ) {
-			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
-				return callbacks[ prop ].apply( callbacks, args );
-			} else {
-				return callbacks[ prop ]; // or undefined
-			}
-		}
-	}
-
-	// the real equiv function
-	var innerEquiv,
-		// stack to decide between skip/abort functions
-		callers = [],
-		// stack to avoiding loops from circular referencing
-		parents = [],
-
-		getProto = Object.getPrototypeOf || function ( obj ) {
-			return obj.__proto__;
-		},
-		callbacks = (function () {
-
-			// for string, boolean, number and null
-			function useStrictEquality( b, a ) {
-				if ( b instanceof a.constructor || a instanceof b.constructor ) {
-					// to catch short annotaion VS 'new' annotation of a
-					// declaration
-					// e.g. var i = 1;
-					// var j = new Number(1);
-					return a == b;
-				} else {
-					return a === b;
-				}
-			}
-
-			return {
-				"string": useStrictEquality,
-				"boolean": useStrictEquality,
-				"number": useStrictEquality,
-				"null": useStrictEquality,
-				"undefined": useStrictEquality,
-
-				"nan": function( b ) {
-					return isNaN( b );
-				},
-
-				"date": function( b, a ) {
-					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
-				},
-
-				"regexp": function( b, a ) {
-					return QUnit.objectType( b ) === "regexp" &&
-						// the regex itself
-						a.source === b.source &&
-						// and its modifers
-						a.global === b.global &&
-						// (gmi) ...
-						a.ignoreCase === b.ignoreCase &&
-						a.multiline === b.multiline &&
-						a.sticky === b.sticky;
-				},
-
-				// - skip when the property is a method of an instance (OOP)
-				// - abort otherwise,
-				// initial === would have catch identical references anyway
-				"function": function() {
-					var caller = callers[callers.length - 1];
-					return caller !== Object && typeof caller !== "undefined";
-				},
-
-				"array": function( b, a ) {
-					var i, j, len, loop;
-
-					// b could be an object literal here
-					if ( QUnit.objectType( b ) !== "array" ) {
-						return false;
-					}
-
-					len = a.length;
-					if ( len !== b.length ) {
-						// safe and faster
-						return false;
-					}
-
-					// track reference to avoid circular references
-					parents.push( a );
-					for ( i = 0; i < len; i++ ) {
-						loop = false;
-						for ( j = 0; j < parents.length; j++ ) {
-							if ( parents[j] === a[i] ) {
-								loop = true;// dont rewalk array
-							}
-						}
-						if ( !loop && !innerEquiv(a[i], b[i]) ) {
-							parents.pop();
-							return false;
-						}
-					}
-					parents.pop();
-					return true;
-				},
-
-				"object": function( b, a ) {
-					var i, j, loop,
-						// Default to true
-						eq = true,
-						aProperties = [],
-						bProperties = [];
-
-					// comparing constructors is more strict than using
-					// instanceof
-					if ( a.constructor !== b.constructor ) {
-						// Allow objects with no prototype to be equivalent to
-						// objects with Object as their constructor.
-						if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
-							( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
-								return false;
-						}
-					}
-
-					// stack constructor before traversing properties
-					callers.push( a.constructor );
-					// track reference to avoid circular references
-					parents.push( a );
-
-					for ( i in a ) { // be strict: don't ensures hasOwnProperty
-									// and go deep
-						loop = false;
-						for ( j = 0; j < parents.length; j++ ) {
-							if ( parents[j] === a[i] ) {
-								// don't go down the same path twice
-								loop = true;
-							}
-						}
-						aProperties.push(i); // collect a's properties
-
-						if (!loop && !innerEquiv( a[i], b[i] ) ) {
-							eq = false;
-							break;
-						}
-					}
-
-					callers.pop(); // unstack, we are done
-					parents.pop();
-
-					for ( i in b ) {
-						bProperties.push( i ); // collect b's properties
-					}
-
-					// Ensures identical properties name
-					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
-				}
-			};
-		}());
-
-	innerEquiv = function() { // can take multiple arguments
-		var args = [].slice.apply( arguments );
-		if ( args.length < 2 ) {
-			return true; // end transition
-		}
-
-		return (function( a, b ) {
-			if ( a === b ) {
-				return true; // catch the most you can
-			} else if ( a === null || b === null || typeof a === "undefined" ||
-					typeof b === "undefined" ||
-					QUnit.objectType(a) !== QUnit.objectType(b) ) {
-				return false; // don't lose time with error prone cases
-			} else {
-				return bindCallbacks(a, callbacks, [ b, a ]);
-			}
-
-			// apply transition with (1..n) arguments
-		}( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
-	};
-
-	return innerEquiv;
-}());
-
-/**
- * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
- * http://flesler.blogspot.com Licensed under BSD
- * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
- *
- * @projectDescription Advanced and extensible data dumping for Javascript.
- * @version 1.0.0
- * @author Ariel Flesler
- * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
- */
-QUnit.jsDump = (function() {
-	function quote( str ) {
-		return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
-	}
-	function literal( o ) {
-		return o + "";
-	}
-	function join( pre, arr, post ) {
-		var s = jsDump.separator(),
-			base = jsDump.indent(),
-			inner = jsDump.indent(1);
-		if ( arr.join ) {
-			arr = arr.join( "," + s + inner );
-		}
-		if ( !arr ) {
-			return pre + post;
-		}
-		return [ pre, inner + arr, base + post ].join(s);
-	}
-	function array( arr, stack ) {
-		var i = arr.length, ret = new Array(i);
-		this.up();
-		while ( i-- ) {
-			ret[i] = this.parse( arr[i] , undefined , stack);
-		}
-		this.down();
-		return join( "[", ret, "]" );
-	}
-
-	var reName = /^function (\w+)/,
-		jsDump = {
-			parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
-				stack = stack || [ ];
-				var inStack, res,
-					parser = this.parsers[ type || this.typeOf(obj) ];
-
-				type = typeof parser;
-				inStack = inArray( obj, stack );
-
-				if ( inStack != -1 ) {
-					return "recursion(" + (inStack - stack.length) + ")";
-				}
-				//else
-				if ( type == "function" )  {
-					stack.push( obj );
-					res = parser.call( this, obj, stack );
-					stack.pop();
-					return res;
-				}
-				// else
-				return ( type == "string" ) ? parser : this.parsers.error;
-			},
-			typeOf: function( obj ) {
-				var type;
-				if ( obj === null ) {
-					type = "null";
-				} else if ( typeof obj === "undefined" ) {
-					type = "undefined";
-				} else if ( QUnit.is( "regexp", obj) ) {
-					type = "regexp";
-				} else if ( QUnit.is( "date", obj) ) {
-					type = "date";
-				} else if ( QUnit.is( "function", obj) ) {
-					type = "function";
-				} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
-					type = "window";
-				} else if ( obj.nodeType === 9 ) {
-					type = "document";
-				} else if ( obj.nodeType ) {
-					type = "node";
-				} else if (
-					// native arrays
-					toString.call( obj ) === "[object Array]" ||
-					// NodeList objects
-					( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
-				) {
-					type = "array";
-				} else {
-					type = typeof obj;
-				}
-				return type;
-			},
-			separator: function() {
-				return this.multiline ?	this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
-			},
-			indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
-				if ( !this.multiline ) {
-					return "";
-				}
-				var chr = this.indentChar;
-				if ( this.HTML ) {
-					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
-				}
-				return new Array( this._depth_ + (extra||0) ).join(chr);
-			},
-			up: function( a ) {
-				this._depth_ += a || 1;
-			},
-			down: function( a ) {
-				this._depth_ -= a || 1;
-			},
-			setParser: function( name, parser ) {
-				this.parsers[name] = parser;
-			},
-			// The next 3 are exposed so you can use them
-			quote: quote,
-			literal: literal,
-			join: join,
-			//
-			_depth_: 1,
-			// This is the list of parsers, to modify them, use jsDump.setParser
-			parsers: {
-				window: "[Window]",
-				document: "[Document]",
-				error: "[ERROR]", //when no parser is found, shouldn"t happen
-				unknown: "[Unknown]",
-				"null": "null",
-				"undefined": "undefined",
-				"function": function( fn ) {
-					var ret = "function",
-						name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
-
-					if ( name ) {
-						ret += " " + name;
-					}
-					ret += "( ";
-
-					ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
-					return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
-				},
-				array: array,
-				nodelist: array,
-				"arguments": array,
-				object: function( map, stack ) {
-					var ret = [ ], keys, key, val, i;
-					QUnit.jsDump.up();
-					if ( Object.keys ) {
-						keys = Object.keys( map );
-					} else {
-						keys = [];
-						for ( key in map ) {
-							keys.push( key );
-						}
-					}
-					keys.sort();
-					for ( i = 0; i < keys.length; i++ ) {
-						key = keys[ i ];
-						val = map[ key ];
-						ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
-					}
-					QUnit.jsDump.down();
-					return join( "{", ret, "}" );
-				},
-				node: function( node ) {
-					var a, val,
-						open = QUnit.jsDump.HTML ? "&lt;" : "<",
-						close = QUnit.jsDump.HTML ? "&gt;" : ">",
-						tag = node.nodeName.toLowerCase(),
-						ret = open + tag;
-
-					for ( a in QUnit.jsDump.DOMAttrs ) {
-						val = node[ QUnit.jsDump.DOMAttrs[a] ];
-						if ( val ) {
-							ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
-						}
-					}
-					return ret + close + open + "/" + tag + close;
-				},
-				functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
-					var args,
-						l = fn.length;
-
-					if ( !l ) {
-						return "";
-					}
-
-					args = new Array(l);
-					while ( l-- ) {
-						args[l] = String.fromCharCode(97+l);//97 is 'a'
-					}
-					return " " + args.join( ", " ) + " ";
-				},
-				key: quote, //object calls it internally, the key part of an item in a map
-				functionCode: "[code]", //function calls it internally, it's the content of the function
-				attribute: quote, //node calls it internally, it's an html attribute value
-				string: quote,
-				date: quote,
-				regexp: literal, //regex
-				number: literal,
-				"boolean": literal
-			},
-			DOMAttrs: {
-				//attributes to dump from nodes, name=>realName
-				id: "id",
-				name: "name",
-				"class": "className"
-			},
-			HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
-			indentChar: "  ",//indentation unit
-			multiline: true //if true, items in a collection, are separated by a \n, else just a space.
-		};
-
-	return jsDump;
-}());
-
-// from Sizzle.js
-function getText( elems ) {
-	var i, elem,
-		ret = "";
-
-	for ( i = 0; elems[i]; i++ ) {
-		elem = elems[i];
-
-		// Get the text from text nodes and CDATA nodes
-		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
-			ret += elem.nodeValue;
-
-		// Traverse everything else, except comment nodes
-		} else if ( elem.nodeType !== 8 ) {
-			ret += getText( elem.childNodes );
-		}
-	}
-
-	return ret;
-}
-
-// from jquery.js
-function inArray( elem, array ) {
-	if ( array.indexOf ) {
-		return array.indexOf( elem );
-	}
-
-	for ( var i = 0, length = array.length; i < length; i++ ) {
-		if ( array[ i ] === elem ) {
-			return i;
-		}
-	}
-
-	return -1;
-}
-
-/*
- * Javascript Diff Algorithm
- *  By John Resig (http://ejohn.org/)
- *  Modified by Chu Alan "sprite"
- *
- * Released under the MIT license.
- *
- * More Info:
- *  http://ejohn.org/projects/javascript-diff-algorithm/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
- */
-QUnit.diff = (function() {
-	function diff( o, n ) {
-		var i,
-			ns = {},
-			os = {};
-
-		for ( i = 0; i < n.length; i++ ) {
-			if ( ns[ n[i] ] == null ) {
-				ns[ n[i] ] = {
-					rows: [],
-					o: null
-				};
-			}
-			ns[ n[i] ].rows.push( i );
-		}
-
-		for ( i = 0; i < o.length; i++ ) {
-			if ( os[ o[i] ] == null ) {
-				os[ o[i] ] = {
-					rows: [],
-					n: null
-				};
-			}
-			os[ o[i] ].rows.push( i );
-		}
-
-		for ( i in ns ) {
-			if ( !hasOwn.call( ns, i ) ) {
-				continue;
-			}
-			if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
-				n[ ns[i].rows[0] ] = {
-					text: n[ ns[i].rows[0] ],
-					row: os[i].rows[0]
-				};
-				o[ os[i].rows[0] ] = {
-					text: o[ os[i].rows[0] ],
-					row: ns[i].rows[0]
-				};
-			}
-		}
-
-		for ( i = 0; i < n.length - 1; i++ ) {
-			if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
-						n[ i + 1 ] == o[ n[i].row + 1 ] ) {
-
-				n[ i + 1 ] = {
-					text: n[ i + 1 ],
-					row: n[i].row + 1
-				};
-				o[ n[i].row + 1 ] = {
-					text: o[ n[i].row + 1 ],
-					row: i + 1
-				};
-			}
-		}
-
-		for ( i = n.length - 1; i > 0; i-- ) {
-			if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
-						n[ i - 1 ] == o[ n[i].row - 1 ]) {
-
-				n[ i - 1 ] = {
-					text: n[ i - 1 ],
-					row: n[i].row - 1
-				};
-				o[ n[i].row - 1 ] = {
-					text: o[ n[i].row - 1 ],
-					row: i - 1
-				};
-			}
-		}
-
-		return {
-			o: o,
-			n: n
-		};
-	}
-
-	return function( o, n ) {
-		o = o.replace( /\s+$/, "" );
-		n = n.replace( /\s+$/, "" );
-
-		var i, pre,
-			str = "",
-			out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
-			oSpace = o.match(/\s+/g),
-			nSpace = n.match(/\s+/g);
-
-		if ( oSpace == null ) {
-			oSpace = [ " " ];
-		}
-		else {
-			oSpace.push( " " );
-		}
-
-		if ( nSpace == null ) {
-			nSpace = [ " " ];
-		}
-		else {
-			nSpace.push( " " );
-		}
-
-		if ( out.n.length === 0 ) {
-			for ( i = 0; i < out.o.length; i++ ) {
-				str += "<del>" + out.o[i] + oSpace[i] + "</del>";
-			}
-		}
-		else {
-			if ( out.n[0].text == null ) {
-				for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
-					str += "<del>" + out.o[n] + oSpace[n] + "</del>";
-				}
-			}
-
-			for ( i = 0; i < out.n.length; i++ ) {
-				if (out.n[i].text == null) {
-					str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
-				}
-				else {
-					// `pre` initialized at top of scope
-					pre = "";
-
-					for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
-						pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
-					}
-					str += " " + out.n[i].text + nSpace[i] + pre;
-				}
-			}
-		}
-
-		return str;
-	};
-}());
-
-// for CommonJS enviroments, export everything
-if ( typeof exports !== "undefined" ) {
-	extend(exports, QUnit);
-}
-
-// get at whatever the global object is, like window in browsers
-}( (function() {return this;}.call()) ));

+ 106 - 50
test/unit/qunit-1.10.0.css → test/unit/qunit-1.18.0.css

@@ -1,11 +1,12 @@
-/**
- * QUnit v1.10.0 - A JavaScript Unit Testing Framework
+/*!
+ * QUnit 1.18.0
+ * http://qunitjs.com/
  *
- * http://qunitjs.com
- *
- * Copyright 2012 jQuery Foundation and other contributors
- * Released under the MIT license.
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
  * http://jquery.org/license
+ *
+ * Date: 2015-04-03T10:23Z
  */
 
 /** Font Family and Sizes */
@@ -20,7 +21,7 @@
 
 /** Resets */
 
-#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
 	margin: 0;
 	padding: 0;
 }
@@ -31,32 +32,29 @@
 #qunit-header {
 	padding: 0.5em 0 0.5em 1em;
 
-	color: #8699a4;
-	background-color: #0d3349;
+	color: #8699A4;
+	background-color: #0D3349;
 
 	font-size: 1.5em;
 	line-height: 1em;
-	font-weight: normal;
+	font-weight: 400;
 
 	border-radius: 5px 5px 0 0;
-	-moz-border-radius: 5px 5px 0 0;
-	-webkit-border-top-right-radius: 5px;
-	-webkit-border-top-left-radius: 5px;
 }
 
 #qunit-header a {
 	text-decoration: none;
-	color: #c2ccd1;
+	color: #C2CCD1;
 }
 
 #qunit-header a:hover,
 #qunit-header a:focus {
-	color: #fff;
+	color: #FFF;
 }
 
 #qunit-testrunner-toolbar label {
 	display: inline-block;
-	padding: 0 .5em 0 .1em;
+	padding: 0 0.5em 0 0.1em;
 }
 
 #qunit-banner {
@@ -64,21 +62,33 @@
 }
 
 #qunit-testrunner-toolbar {
-	padding: 0.5em 0 0.5em 2em;
+	padding: 0.5em 1em 0.5em 1em;
 	color: #5E740B;
-	background-color: #eee;
+	background-color: #EEE;
 	overflow: hidden;
 }
 
 #qunit-userAgent {
-	padding: 0.5em 0 0.5em 2.5em;
-	background-color: #2b81af;
-	color: #fff;
+	padding: 0.5em 1em 0.5em 1em;
+	background-color: #2B81AF;
+	color: #FFF;
 	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
 }
 
 #qunit-modulefilter-container {
 	float: right;
+	padding: 0.2em;
+}
+
+.qunit-url-config {
+	display: inline-block;
+	padding: 0.1em;
+}
+
+.qunit-filter {
+	display: block;
+	float: right;
+	margin-left: 1em;
 }
 
 /** Tests: Pass/Fail */
@@ -88,49 +98,83 @@
 }
 
 #qunit-tests li {
-	padding: 0.4em 0.5em 0.4em 2.5em;
-	border-bottom: 1px solid #fff;
+	padding: 0.4em 1em 0.4em 1em;
+	border-bottom: 1px solid #FFF;
 	list-style-position: inside;
 }
 
-#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
+#qunit-tests > li {
 	display: none;
 }
 
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped {
+	display: list-item;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass {
+	visibility: hidden;
+	position: absolute;
+	width:   0px;
+	height:  0px;
+	padding: 0;
+	border:  0;
+	margin:  0;
+}
+
 #qunit-tests li strong {
 	cursor: pointer;
 }
 
+#qunit-tests li.skipped strong {
+	cursor: default;
+}
+
 #qunit-tests li a {
 	padding: 0.5em;
-	color: #c2ccd1;
+	color: #C2CCD1;
 	text-decoration: none;
 }
+
+#qunit-tests li p a {
+	padding: 0.25em;
+	color: #6B6464;
+}
 #qunit-tests li a:hover,
 #qunit-tests li a:focus {
 	color: #000;
 }
 
-#qunit-tests ol {
+#qunit-tests li .runtime {
+	float: right;
+	font-size: smaller;
+}
+
+.qunit-assert-list {
 	margin-top: 0.5em;
 	padding: 0.5em;
 
-	background-color: #fff;
+	background-color: #FFF;
 
 	border-radius: 5px;
-	-moz-border-radius: 5px;
-	-webkit-border-radius: 5px;
+}
+
+.qunit-collapsed {
+	display: none;
 }
 
 #qunit-tests table {
 	border-collapse: collapse;
-	margin-top: .2em;
+	margin-top: 0.2em;
 }
 
 #qunit-tests th {
 	text-align: right;
 	vertical-align: top;
-	padding: 0 .5em 0 0;
+	padding: 0 0.5em 0 0;
 }
 
 #qunit-tests td {
@@ -144,26 +188,26 @@
 }
 
 #qunit-tests del {
-	background-color: #e0f2be;
-	color: #374e0c;
+	background-color: #E0F2BE;
+	color: #374E0C;
 	text-decoration: none;
 }
 
 #qunit-tests ins {
-	background-color: #ffcaca;
+	background-color: #FFCACA;
 	color: #500;
 	text-decoration: none;
 }
 
 /*** Test Counts */
 
-#qunit-tests b.counts                       { color: black; }
+#qunit-tests b.counts                       { color: #000; }
 #qunit-tests b.passed                       { color: #5E740B; }
 #qunit-tests b.failed                       { color: #710909; }
 
 #qunit-tests li li {
 	padding: 5px;
-	background-color: #fff;
+	background-color: #FFF;
 	border-bottom: none;
 	list-style-position: inside;
 }
@@ -171,8 +215,8 @@
 /*** Passing Styles */
 
 #qunit-tests li li.pass {
-	color: #3c510c;
-	background-color: #fff;
+	color: #3C510C;
+	background-color: #FFF;
 	border-left: 10px solid #C6E746;
 }
 
@@ -180,7 +224,7 @@
 #qunit-tests .pass .test-name               { color: #366097; }
 
 #qunit-tests .pass .test-actual,
-#qunit-tests .pass .test-expected           { color: #999999; }
+#qunit-tests .pass .test-expected           { color: #999; }
 
 #qunit-banner.qunit-pass                    { background-color: #C6E746; }
 
@@ -188,40 +232,52 @@
 
 #qunit-tests li li.fail {
 	color: #710909;
-	background-color: #fff;
+	background-color: #FFF;
 	border-left: 10px solid #EE5757;
 	white-space: pre;
 }
 
 #qunit-tests > li:last-child {
 	border-radius: 0 0 5px 5px;
-	-moz-border-radius: 0 0 5px 5px;
-	-webkit-border-bottom-right-radius: 5px;
-	-webkit-border-bottom-left-radius: 5px;
 }
 
-#qunit-tests .fail                          { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
 #qunit-tests .fail .test-name,
-#qunit-tests .fail .module-name             { color: #000000; }
+#qunit-tests .fail .module-name             { color: #000; }
 
 #qunit-tests .fail .test-actual             { color: #EE5757; }
-#qunit-tests .fail .test-expected           { color: green;   }
+#qunit-tests .fail .test-expected           { color: #008000; }
 
 #qunit-banner.qunit-fail                    { background-color: #EE5757; }
 
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+	background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-skipped-label {
+	background-color: #F4FF77;
+	display: inline-block;
+	font-style: normal;
+	color: #366097;
+	line-height: 1.8em;
+	padding: 0 0.5em;
+	margin: -0.4em 0.4em -0.4em 0;
+}
 
 /** Result */
 
 #qunit-testresult {
-	padding: 0.5em 0.5em 0.5em 2.5em;
+	padding: 0.5em 1em 0.5em 1em;
 
-	color: #2b81af;
+	color: #2B81AF;
 	background-color: #D2E0E6;
 
-	border-bottom: 1px solid white;
+	border-bottom: 1px solid #FFF;
 }
 #qunit-testresult .module-name {
-	font-weight: bold;
+	font-weight: 700;
 }
 
 /** Fixture */

+ 3829 - 0
test/unit/qunit-1.18.0.js

@@ -0,0 +1,3829 @@
+/*!
+ * QUnit 1.18.0
+ * http://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-04-03T10:23Z
+ */
+
+(function( window ) {
+
+var QUnit,
+	config,
+	onErrorFnPrev,
+	loggingCallbacks = {},
+	fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	// Keep a local reference to Date (GH-283)
+	Date = window.Date,
+	now = Date.now || function() {
+		return new Date().getTime();
+	},
+	globalStartCalled = false,
+	runStarted = false,
+	setTimeout = window.setTimeout,
+	clearTimeout = window.clearTimeout,
+	defined = {
+		document: window.document !== undefined,
+		setTimeout: window.setTimeout !== undefined,
+		sessionStorage: (function() {
+			var x = "qunit-test-string";
+			try {
+				sessionStorage.setItem( x, x );
+				sessionStorage.removeItem( x );
+				return true;
+			} catch ( e ) {
+				return false;
+			}
+		}())
+	},
+	/**
+	 * Provides a normalized error string, correcting an issue
+	 * with IE 7 (and prior) where Error.prototype.toString is
+	 * not properly implemented
+	 *
+	 * Based on http://es5.github.com/#x15.11.4.4
+	 *
+	 * @param {String|Error} error
+	 * @return {String} error message
+	 */
+	errorString = function( error ) {
+		var name, message,
+			errorString = error.toString();
+		if ( errorString.substring( 0, 7 ) === "[object" ) {
+			name = error.name ? error.name.toString() : "Error";
+			message = error.message ? error.message.toString() : "";
+			if ( name && message ) {
+				return name + ": " + message;
+			} else if ( name ) {
+				return name;
+			} else if ( message ) {
+				return message;
+			} else {
+				return "Error";
+			}
+		} else {
+			return errorString;
+		}
+	},
+	/**
+	 * Makes a clone of an object using only Array or Object as base,
+	 * and copies over the own enumerable properties.
+	 *
+	 * @param {Object} obj
+	 * @return {Object} New object with only the own properties (recursively).
+	 */
+	objectValues = function( obj ) {
+		var key, val,
+			vals = QUnit.is( "array", obj ) ? [] : {};
+		for ( key in obj ) {
+			if ( hasOwn.call( obj, key ) ) {
+				val = obj[ key ];
+				vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
+			}
+		}
+		return vals;
+	};
+
+QUnit = {};
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+	// The queue of tests to run
+	queue: [],
+
+	// block until document ready
+	blocking: true,
+
+	// by default, run previously failed tests first
+	// very useful in combination with "Hide passed tests" checked
+	reorder: true,
+
+	// by default, modify document.title when suite is done
+	altertitle: true,
+
+	// by default, scroll to top of the page when suite is done
+	scrolltop: true,
+
+	// when enabled, all tests must call expect()
+	requireExpects: false,
+
+	// depth up-to which object will be dumped
+	maxDepth: 0,
+
+	// add checkboxes that are persisted in the query-string
+	// when enabled, the id is set to `true` as a `QUnit.config` property
+	urlConfig: [
+		{
+			id: "hidepassed",
+			label: "Hide passed tests",
+			tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+		},
+		{
+			id: "noglobals",
+			label: "Check for Globals",
+			tooltip: "Enabling this will test if any test introduces new properties on the " +
+				"`window` object. Stored as query-strings."
+		},
+		{
+			id: "notrycatch",
+			label: "No try-catch",
+			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+				"exceptions in IE reasonable. Stored as query-strings."
+		}
+	],
+
+	// Set of all modules.
+	modules: [],
+
+	// The first unnamed module
+	currentModule: {
+		name: "",
+		tests: []
+	},
+
+	callbacks: {}
+};
+
+// Push a loose unnamed module to the modules collection
+config.modules.push( config.currentModule );
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+	var i, current,
+		location = window.location || { search: "", protocol: "file:" },
+		params = location.search.slice( 1 ).split( "&" ),
+		length = params.length,
+		urlParams = {};
+
+	if ( params[ 0 ] ) {
+		for ( i = 0; i < length; i++ ) {
+			current = params[ i ].split( "=" );
+			current[ 0 ] = decodeURIComponent( current[ 0 ] );
+
+			// allow just a key to turn on a flag, e.g., test.html?noglobals
+			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+			if ( urlParams[ current[ 0 ] ] ) {
+				urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
+			} else {
+				urlParams[ current[ 0 ] ] = current[ 1 ];
+			}
+		}
+	}
+
+	if ( urlParams.filter === true ) {
+		delete urlParams.filter;
+	}
+
+	QUnit.urlParams = urlParams;
+
+	// String search anywhere in moduleName+testName
+	config.filter = urlParams.filter;
+
+	if ( urlParams.maxDepth ) {
+		config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
+			Number.POSITIVE_INFINITY :
+			urlParams.maxDepth;
+	}
+
+	config.testId = [];
+	if ( urlParams.testId ) {
+
+		// Ensure that urlParams.testId is an array
+		urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
+		for ( i = 0; i < urlParams.testId.length; i++ ) {
+			config.testId.push( urlParams.testId[ i ] );
+		}
+	}
+
+	// Figure out if we're running the tests from a server or not
+	QUnit.isLocal = location.protocol === "file:";
+
+	// Expose the current QUnit version
+	QUnit.version = "1.18.0";
+}());
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+extend( QUnit, {
+
+	// call on start of module test to prepend name to all tests
+	module: function( name, testEnvironment ) {
+		var currentModule = {
+			name: name,
+			testEnvironment: testEnvironment,
+			tests: []
+		};
+
+		// DEPRECATED: handles setup/teardown functions,
+		// beforeEach and afterEach should be used instead
+		if ( testEnvironment && testEnvironment.setup ) {
+			testEnvironment.beforeEach = testEnvironment.setup;
+			delete testEnvironment.setup;
+		}
+		if ( testEnvironment && testEnvironment.teardown ) {
+			testEnvironment.afterEach = testEnvironment.teardown;
+			delete testEnvironment.teardown;
+		}
+
+		config.modules.push( currentModule );
+		config.currentModule = currentModule;
+	},
+
+	// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
+	asyncTest: function( testName, expected, callback ) {
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		QUnit.test( testName, expected, callback, true );
+	},
+
+	test: function( testName, expected, callback, async ) {
+		var test;
+
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		test = new Test({
+			testName: testName,
+			expected: expected,
+			async: async,
+			callback: callback
+		});
+
+		test.queue();
+	},
+
+	skip: function( testName ) {
+		var test = new Test({
+			testName: testName,
+			skip: true
+		});
+
+		test.queue();
+	},
+
+	// DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
+	// In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
+	start: function( count ) {
+		var globalStartAlreadyCalled = globalStartCalled;
+
+		if ( !config.current ) {
+			globalStartCalled = true;
+
+			if ( runStarted ) {
+				throw new Error( "Called start() outside of a test context while already started" );
+			} else if ( globalStartAlreadyCalled || count > 1 ) {
+				throw new Error( "Called start() outside of a test context too many times" );
+			} else if ( config.autostart ) {
+				throw new Error( "Called start() outside of a test context when " +
+					"QUnit.config.autostart was true" );
+			} else if ( !config.pageLoaded ) {
+
+				// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+				config.autostart = true;
+				return;
+			}
+		} else {
+
+			// If a test is running, adjust its semaphore
+			config.current.semaphore -= count || 1;
+
+			// Don't start until equal number of stop-calls
+			if ( config.current.semaphore > 0 ) {
+				return;
+			}
+
+			// throw an Error if start is called more often than stop
+			if ( config.current.semaphore < 0 ) {
+				config.current.semaphore = 0;
+
+				QUnit.pushFailure(
+					"Called start() while already started (test's semaphore was 0 already)",
+					sourceFromStacktrace( 2 )
+				);
+				return;
+			}
+		}
+
+		resumeProcessing();
+	},
+
+	// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
+	stop: function( count ) {
+
+		// If there isn't a test running, don't allow QUnit.stop() to be called
+		if ( !config.current ) {
+			throw new Error( "Called stop() outside of a test context" );
+		}
+
+		// If a test is running, adjust its semaphore
+		config.current.semaphore += count || 1;
+
+		pauseProcessing();
+	},
+
+	config: config,
+
+	// Safe object type checking
+	is: function( type, obj ) {
+		return QUnit.objectType( obj ) === type;
+	},
+
+	objectType: function( obj ) {
+		if ( typeof obj === "undefined" ) {
+			return "undefined";
+		}
+
+		// Consider: typeof null === object
+		if ( obj === null ) {
+			return "null";
+		}
+
+		var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
+			type = match && match[ 1 ] || "";
+
+		switch ( type ) {
+			case "Number":
+				if ( isNaN( obj ) ) {
+					return "nan";
+				}
+				return "number";
+			case "String":
+			case "Boolean":
+			case "Array":
+			case "Date":
+			case "RegExp":
+			case "Function":
+				return type.toLowerCase();
+		}
+		if ( typeof obj === "object" ) {
+			return "object";
+		}
+		return undefined;
+	},
+
+	extend: extend,
+
+	load: function() {
+		config.pageLoaded = true;
+
+		// Initialize the configuration options
+		extend( config, {
+			stats: { all: 0, bad: 0 },
+			moduleStats: { all: 0, bad: 0 },
+			started: 0,
+			updateRate: 1000,
+			autostart: true,
+			filter: ""
+		}, true );
+
+		config.blocking = false;
+
+		if ( config.autostart ) {
+			resumeProcessing();
+		}
+	}
+});
+
+// Register logging callbacks
+(function() {
+	var i, l, key,
+		callbacks = [ "begin", "done", "log", "testStart", "testDone",
+			"moduleStart", "moduleDone" ];
+
+	function registerLoggingCallback( key ) {
+		var loggingCallback = function( callback ) {
+			if ( QUnit.objectType( callback ) !== "function" ) {
+				throw new Error(
+					"QUnit logging methods require a callback function as their first parameters."
+				);
+			}
+
+			config.callbacks[ key ].push( callback );
+		};
+
+		// DEPRECATED: This will be removed on QUnit 2.0.0+
+		// Stores the registered functions allowing restoring
+		// at verifyLoggingCallbacks() if modified
+		loggingCallbacks[ key ] = loggingCallback;
+
+		return loggingCallback;
+	}
+
+	for ( i = 0, l = callbacks.length; i < l; i++ ) {
+		key = callbacks[ i ];
+
+		// Initialize key collection of logging callback
+		if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
+			config.callbacks[ key ] = [];
+		}
+
+		QUnit[ key ] = registerLoggingCallback( key );
+	}
+})();
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will suppress the default browser handler,
+// returning false will let it run.
+window.onerror = function( error, filePath, linerNr ) {
+	var ret = false;
+	if ( onErrorFnPrev ) {
+		ret = onErrorFnPrev( error, filePath, linerNr );
+	}
+
+	// Treat return value as window.onerror itself does,
+	// Only do our handling if not suppressed.
+	if ( ret !== true ) {
+		if ( QUnit.config.current ) {
+			if ( QUnit.config.current.ignoreGlobalErrors ) {
+				return true;
+			}
+			QUnit.pushFailure( error, filePath + ":" + linerNr );
+		} else {
+			QUnit.test( "global failure", extend(function() {
+				QUnit.pushFailure( error, filePath + ":" + linerNr );
+			}, { validTest: true } ) );
+		}
+		return false;
+	}
+
+	return ret;
+};
+
+function done() {
+	var runtime, passed;
+
+	config.autorun = true;
+
+	// Log the last module results
+	if ( config.previousModule ) {
+		runLoggingCallbacks( "moduleDone", {
+			name: config.previousModule.name,
+			tests: config.previousModule.tests,
+			failed: config.moduleStats.bad,
+			passed: config.moduleStats.all - config.moduleStats.bad,
+			total: config.moduleStats.all,
+			runtime: now() - config.moduleStats.started
+		});
+	}
+	delete config.previousModule;
+
+	runtime = now() - config.started;
+	passed = config.stats.all - config.stats.bad;
+
+	runLoggingCallbacks( "done", {
+		failed: config.stats.bad,
+		passed: passed,
+		total: config.stats.all,
+		runtime: runtime
+	});
+}
+
+// Doesn't support IE6 to IE9, it will return undefined on these browsers
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+	offset = offset === undefined ? 4 : offset;
+
+	var stack, include, i;
+
+	if ( e.stack ) {
+		stack = e.stack.split( "\n" );
+		if ( /^error$/i.test( stack[ 0 ] ) ) {
+			stack.shift();
+		}
+		if ( fileName ) {
+			include = [];
+			for ( i = offset; i < stack.length; i++ ) {
+				if ( stack[ i ].indexOf( fileName ) !== -1 ) {
+					break;
+				}
+				include.push( stack[ i ] );
+			}
+			if ( include.length ) {
+				return include.join( "\n" );
+			}
+		}
+		return stack[ offset ];
+
+	// Support: Safari <=6 only
+	} else if ( e.sourceURL ) {
+
+		// exclude useless self-reference for generated Error objects
+		if ( /qunit.js$/.test( e.sourceURL ) ) {
+			return;
+		}
+
+		// for actual exceptions, this is useful
+		return e.sourceURL + ":" + e.line;
+	}
+}
+
+function sourceFromStacktrace( offset ) {
+	var error = new Error();
+
+	// Support: Safari <=7 only, IE <=10 - 11 only
+	// Not all browsers generate the `stack` property for `new Error()`, see also #636
+	if ( !error.stack ) {
+		try {
+			throw error;
+		} catch ( err ) {
+			error = err;
+		}
+	}
+
+	return extractStacktrace( error, offset );
+}
+
+function synchronize( callback, last ) {
+	if ( QUnit.objectType( callback ) === "array" ) {
+		while ( callback.length ) {
+			synchronize( callback.shift() );
+		}
+		return;
+	}
+	config.queue.push( callback );
+
+	if ( config.autorun && !config.blocking ) {
+		process( last );
+	}
+}
+
+function process( last ) {
+	function next() {
+		process( last );
+	}
+	var start = now();
+	config.depth = ( config.depth || 0 ) + 1;
+
+	while ( config.queue.length && !config.blocking ) {
+		if ( !defined.setTimeout || config.updateRate <= 0 ||
+				( ( now() - start ) < config.updateRate ) ) {
+			if ( config.current ) {
+
+				// Reset async tracking for each phase of the Test lifecycle
+				config.current.usedAsync = false;
+			}
+			config.queue.shift()();
+		} else {
+			setTimeout( next, 13 );
+			break;
+		}
+	}
+	config.depth--;
+	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+		done();
+	}
+}
+
+function begin() {
+	var i, l,
+		modulesLog = [];
+
+	// If the test run hasn't officially begun yet
+	if ( !config.started ) {
+
+		// Record the time of the test run's beginning
+		config.started = now();
+
+		verifyLoggingCallbacks();
+
+		// Delete the loose unnamed module if unused.
+		if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
+			config.modules.shift();
+		}
+
+		// Avoid unnecessary information by not logging modules' test environments
+		for ( i = 0, l = config.modules.length; i < l; i++ ) {
+			modulesLog.push({
+				name: config.modules[ i ].name,
+				tests: config.modules[ i ].tests
+			});
+		}
+
+		// The test run is officially beginning now
+		runLoggingCallbacks( "begin", {
+			totalTests: Test.count,
+			modules: modulesLog
+		});
+	}
+
+	config.blocking = false;
+	process( true );
+}
+
+function resumeProcessing() {
+	runStarted = true;
+
+	// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
+	if ( defined.setTimeout ) {
+		setTimeout(function() {
+			if ( config.current && config.current.semaphore > 0 ) {
+				return;
+			}
+			if ( config.timeout ) {
+				clearTimeout( config.timeout );
+			}
+
+			begin();
+		}, 13 );
+	} else {
+		begin();
+	}
+}
+
+function pauseProcessing() {
+	config.blocking = true;
+
+	if ( config.testTimeout && defined.setTimeout ) {
+		clearTimeout( config.timeout );
+		config.timeout = setTimeout(function() {
+			if ( config.current ) {
+				config.current.semaphore = 0;
+				QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
+			} else {
+				throw new Error( "Test timed out" );
+			}
+			resumeProcessing();
+		}, config.testTimeout );
+	}
+}
+
+function saveGlobal() {
+	config.pollution = [];
+
+	if ( config.noglobals ) {
+		for ( var key in window ) {
+			if ( hasOwn.call( window, key ) ) {
+				// in Opera sometimes DOM element ids show up here, ignore them
+				if ( /^qunit-test-output/.test( key ) ) {
+					continue;
+				}
+				config.pollution.push( key );
+			}
+		}
+	}
+}
+
+function checkPollution() {
+	var newGlobals,
+		deletedGlobals,
+		old = config.pollution;
+
+	saveGlobal();
+
+	newGlobals = diff( config.pollution, old );
+	if ( newGlobals.length > 0 ) {
+		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
+	}
+
+	deletedGlobals = diff( old, config.pollution );
+	if ( deletedGlobals.length > 0 ) {
+		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
+	}
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+	var i, j,
+		result = a.slice();
+
+	for ( i = 0; i < result.length; i++ ) {
+		for ( j = 0; j < b.length; j++ ) {
+			if ( result[ i ] === b[ j ] ) {
+				result.splice( i, 1 );
+				i--;
+				break;
+			}
+		}
+	}
+	return result;
+}
+
+function extend( a, b, undefOnly ) {
+	for ( var prop in b ) {
+		if ( hasOwn.call( b, prop ) ) {
+
+			// Avoid "Member not found" error in IE8 caused by messing with window.constructor
+			if ( !( prop === "constructor" && a === window ) ) {
+				if ( b[ prop ] === undefined ) {
+					delete a[ prop ];
+				} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
+					a[ prop ] = b[ prop ];
+				}
+			}
+		}
+	}
+
+	return a;
+}
+
+function runLoggingCallbacks( key, args ) {
+	var i, l, callbacks;
+
+	callbacks = config.callbacks[ key ];
+	for ( i = 0, l = callbacks.length; i < l; i++ ) {
+		callbacks[ i ]( args );
+	}
+}
+
+// DEPRECATED: This will be removed on 2.0.0+
+// This function verifies if the loggingCallbacks were modified by the user
+// If so, it will restore it, assign the given callback and print a console warning
+function verifyLoggingCallbacks() {
+	var loggingCallback, userCallback;
+
+	for ( loggingCallback in loggingCallbacks ) {
+		if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
+
+			userCallback = QUnit[ loggingCallback ];
+
+			// Restore the callback function
+			QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
+
+			// Assign the deprecated given callback
+			QUnit[ loggingCallback ]( userCallback );
+
+			if ( window.console && window.console.warn ) {
+				window.console.warn(
+					"QUnit." + loggingCallback + " was replaced with a new value.\n" +
+					"Please, check out the documentation on how to apply logging callbacks.\n" +
+					"Reference: http://api.qunitjs.com/category/callbacks/"
+				);
+			}
+		}
+	}
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+	if ( array.indexOf ) {
+		return array.indexOf( elem );
+	}
+
+	for ( var i = 0, length = array.length; i < length; i++ ) {
+		if ( array[ i ] === elem ) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+function Test( settings ) {
+	var i, l;
+
+	++Test.count;
+
+	extend( this, settings );
+	this.assertions = [];
+	this.semaphore = 0;
+	this.usedAsync = false;
+	this.module = config.currentModule;
+	this.stack = sourceFromStacktrace( 3 );
+
+	// Register unique strings
+	for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
+		if ( this.module.tests[ i ].name === this.testName ) {
+			this.testName += " ";
+		}
+	}
+
+	this.testId = generateHash( this.module.name, this.testName );
+
+	this.module.tests.push({
+		name: this.testName,
+		testId: this.testId
+	});
+
+	if ( settings.skip ) {
+
+		// Skipped tests will fully ignore any sent callback
+		this.callback = function() {};
+		this.async = false;
+		this.expected = 0;
+	} else {
+		this.assert = new Assert( this );
+	}
+}
+
+Test.count = 0;
+
+Test.prototype = {
+	before: function() {
+		if (
+
+			// Emit moduleStart when we're switching from one module to another
+			this.module !== config.previousModule ||
+
+				// They could be equal (both undefined) but if the previousModule property doesn't
+				// yet exist it means this is the first test in a suite that isn't wrapped in a
+				// module, in which case we'll just emit a moduleStart event for 'undefined'.
+				// Without this, reporters can get testStart before moduleStart  which is a problem.
+				!hasOwn.call( config, "previousModule" )
+		) {
+			if ( hasOwn.call( config, "previousModule" ) ) {
+				runLoggingCallbacks( "moduleDone", {
+					name: config.previousModule.name,
+					tests: config.previousModule.tests,
+					failed: config.moduleStats.bad,
+					passed: config.moduleStats.all - config.moduleStats.bad,
+					total: config.moduleStats.all,
+					runtime: now() - config.moduleStats.started
+				});
+			}
+			config.previousModule = this.module;
+			config.moduleStats = { all: 0, bad: 0, started: now() };
+			runLoggingCallbacks( "moduleStart", {
+				name: this.module.name,
+				tests: this.module.tests
+			});
+		}
+
+		config.current = this;
+
+		this.testEnvironment = extend( {}, this.module.testEnvironment );
+		delete this.testEnvironment.beforeEach;
+		delete this.testEnvironment.afterEach;
+
+		this.started = now();
+		runLoggingCallbacks( "testStart", {
+			name: this.testName,
+			module: this.module.name,
+			testId: this.testId
+		});
+
+		if ( !config.pollution ) {
+			saveGlobal();
+		}
+	},
+
+	run: function() {
+		var promise;
+
+		config.current = this;
+
+		if ( this.async ) {
+			QUnit.stop();
+		}
+
+		this.callbackStarted = now();
+
+		if ( config.notrycatch ) {
+			promise = this.callback.call( this.testEnvironment, this.assert );
+			this.resolvePromise( promise );
+			return;
+		}
+
+		try {
+			promise = this.callback.call( this.testEnvironment, this.assert );
+			this.resolvePromise( promise );
+		} catch ( e ) {
+			this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
+				this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+
+			// else next test will carry the responsibility
+			saveGlobal();
+
+			// Restart the tests if they're blocking
+			if ( config.blocking ) {
+				QUnit.start();
+			}
+		}
+	},
+
+	after: function() {
+		checkPollution();
+	},
+
+	queueHook: function( hook, hookName ) {
+		var promise,
+			test = this;
+		return function runHook() {
+			config.current = test;
+			if ( config.notrycatch ) {
+				promise = hook.call( test.testEnvironment, test.assert );
+				test.resolvePromise( promise, hookName );
+				return;
+			}
+			try {
+				promise = hook.call( test.testEnvironment, test.assert );
+				test.resolvePromise( promise, hookName );
+			} catch ( error ) {
+				test.pushFailure( hookName + " failed on " + test.testName + ": " +
+					( error.message || error ), extractStacktrace( error, 0 ) );
+			}
+		};
+	},
+
+	// Currently only used for module level hooks, can be used to add global level ones
+	hooks: function( handler ) {
+		var hooks = [];
+
+		// Hooks are ignored on skipped tests
+		if ( this.skip ) {
+			return hooks;
+		}
+
+		if ( this.module.testEnvironment &&
+				QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
+			hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
+		}
+
+		return hooks;
+	},
+
+	finish: function() {
+		config.current = this;
+		if ( config.requireExpects && this.expected === null ) {
+			this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
+				"not called.", this.stack );
+		} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+			this.pushFailure( "Expected " + this.expected + " assertions, but " +
+				this.assertions.length + " were run", this.stack );
+		} else if ( this.expected === null && !this.assertions.length ) {
+			this.pushFailure( "Expected at least one assertion, but none were run - call " +
+				"expect(0) to accept zero assertions.", this.stack );
+		}
+
+		var i,
+			bad = 0;
+
+		this.runtime = now() - this.started;
+		config.stats.all += this.assertions.length;
+		config.moduleStats.all += this.assertions.length;
+
+		for ( i = 0; i < this.assertions.length; i++ ) {
+			if ( !this.assertions[ i ].result ) {
+				bad++;
+				config.stats.bad++;
+				config.moduleStats.bad++;
+			}
+		}
+
+		runLoggingCallbacks( "testDone", {
+			name: this.testName,
+			module: this.module.name,
+			skipped: !!this.skip,
+			failed: bad,
+			passed: this.assertions.length - bad,
+			total: this.assertions.length,
+			runtime: this.runtime,
+
+			// HTML Reporter use
+			assertions: this.assertions,
+			testId: this.testId,
+
+			// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
+			duration: this.runtime
+		});
+
+		// QUnit.reset() is deprecated and will be replaced for a new
+		// fixture reset function on QUnit 2.0/2.1.
+		// It's still called here for backwards compatibility handling
+		QUnit.reset();
+
+		config.current = undefined;
+	},
+
+	queue: function() {
+		var bad,
+			test = this;
+
+		if ( !this.valid() ) {
+			return;
+		}
+
+		function run() {
+
+			// each of these can by async
+			synchronize([
+				function() {
+					test.before();
+				},
+
+				test.hooks( "beforeEach" ),
+
+				function() {
+					test.run();
+				},
+
+				test.hooks( "afterEach" ).reverse(),
+
+				function() {
+					test.after();
+				},
+				function() {
+					test.finish();
+				}
+			]);
+		}
+
+		// `bad` initialized at top of scope
+		// defer when previous test run passed, if storage is available
+		bad = QUnit.config.reorder && defined.sessionStorage &&
+				+sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
+
+		if ( bad ) {
+			run();
+		} else {
+			synchronize( run, true );
+		}
+	},
+
+	push: function( result, actual, expected, message ) {
+		var source,
+			details = {
+				module: this.module.name,
+				name: this.testName,
+				result: result,
+				message: message,
+				actual: actual,
+				expected: expected,
+				testId: this.testId,
+				runtime: now() - this.started
+			};
+
+		if ( !result ) {
+			source = sourceFromStacktrace();
+
+			if ( source ) {
+				details.source = source;
+			}
+		}
+
+		runLoggingCallbacks( "log", details );
+
+		this.assertions.push({
+			result: !!result,
+			message: message
+		});
+	},
+
+	pushFailure: function( message, source, actual ) {
+		if ( !this instanceof Test ) {
+			throw new Error( "pushFailure() assertion outside test context, was " +
+				sourceFromStacktrace( 2 ) );
+		}
+
+		var details = {
+				module: this.module.name,
+				name: this.testName,
+				result: false,
+				message: message || "error",
+				actual: actual || null,
+				testId: this.testId,
+				runtime: now() - this.started
+			};
+
+		if ( source ) {
+			details.source = source;
+		}
+
+		runLoggingCallbacks( "log", details );
+
+		this.assertions.push({
+			result: false,
+			message: message
+		});
+	},
+
+	resolvePromise: function( promise, phase ) {
+		var then, message,
+			test = this;
+		if ( promise != null ) {
+			then = promise.then;
+			if ( QUnit.objectType( then ) === "function" ) {
+				QUnit.stop();
+				then.call(
+					promise,
+					QUnit.start,
+					function( error ) {
+						message = "Promise rejected " +
+							( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
+							" " + test.testName + ": " + ( error.message || error );
+						test.pushFailure( message, extractStacktrace( error, 0 ) );
+
+						// else next test will carry the responsibility
+						saveGlobal();
+
+						// Unblock
+						QUnit.start();
+					}
+				);
+			}
+		}
+	},
+
+	valid: function() {
+		var include,
+			filter = config.filter && config.filter.toLowerCase(),
+			module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+			fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
+
+		// Internally-generated tests are always valid
+		if ( this.callback && this.callback.validTest ) {
+			return true;
+		}
+
+		if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+			return false;
+		}
+
+		if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
+			return false;
+		}
+
+		if ( !filter ) {
+			return true;
+		}
+
+		include = filter.charAt( 0 ) !== "!";
+		if ( !include ) {
+			filter = filter.slice( 1 );
+		}
+
+		// If the filter matches, we need to honour include
+		if ( fullName.indexOf( filter ) !== -1 ) {
+			return include;
+		}
+
+		// Otherwise, do the opposite
+		return !include;
+	}
+
+};
+
+// Resets the test setup. Useful for tests that modify the DOM.
+/*
+DEPRECATED: Use multiple tests instead of resetting inside a test.
+Use testStart or testDone for custom cleanup.
+This method will throw an error in 2.0, and will be removed in 2.1
+*/
+QUnit.reset = function() {
+
+	// Return on non-browser environments
+	// This is necessary to not break on node tests
+	if ( typeof window === "undefined" ) {
+		return;
+	}
+
+	var fixture = defined.document && document.getElementById &&
+			document.getElementById( "qunit-fixture" );
+
+	if ( fixture ) {
+		fixture.innerHTML = config.fixture;
+	}
+};
+
+QUnit.pushFailure = function() {
+	if ( !QUnit.config.current ) {
+		throw new Error( "pushFailure() assertion outside test context, in " +
+			sourceFromStacktrace( 2 ) );
+	}
+
+	// Gets current test obj
+	var currentTest = QUnit.config.current;
+
+	return currentTest.pushFailure.apply( currentTest, arguments );
+};
+
+// Based on Java's String.hashCode, a simple but not
+// rigorously collision resistant hashing function
+function generateHash( module, testName ) {
+	var hex,
+		i = 0,
+		hash = 0,
+		str = module + "\x1C" + testName,
+		len = str.length;
+
+	for ( ; i < len; i++ ) {
+		hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
+		hash |= 0;
+	}
+
+	// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+	// strictly necessary but increases user understanding that the id is a SHA-like hash
+	hex = ( 0x100000000 + hash ).toString( 16 );
+	if ( hex.length < 8 ) {
+		hex = "0000000" + hex;
+	}
+
+	return hex.slice( -8 );
+}
+
+function Assert( testContext ) {
+	this.test = testContext;
+}
+
+// Assert helpers
+QUnit.assert = Assert.prototype = {
+
+	// Specify the number of expected assertions to guarantee that failed test
+	// (no assertions are run at all) don't slip through.
+	expect: function( asserts ) {
+		if ( arguments.length === 1 ) {
+			this.test.expected = asserts;
+		} else {
+			return this.test.expected;
+		}
+	},
+
+	// Increment this Test's semaphore counter, then return a single-use function that
+	// decrements that counter a maximum of once.
+	async: function() {
+		var test = this.test,
+			popped = false;
+
+		test.semaphore += 1;
+		test.usedAsync = true;
+		pauseProcessing();
+
+		return function done() {
+			if ( !popped ) {
+				test.semaphore -= 1;
+				popped = true;
+				resumeProcessing();
+			} else {
+				test.pushFailure( "Called the callback returned from `assert.async` more than once",
+					sourceFromStacktrace( 2 ) );
+			}
+		};
+	},
+
+	// Exports test.push() to the user API
+	push: function( /* result, actual, expected, message */ ) {
+		var assert = this,
+			currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
+
+		// Backwards compatibility fix.
+		// Allows the direct use of global exported assertions and QUnit.assert.*
+		// Although, it's use is not recommended as it can leak assertions
+		// to other tests from async tests, because we only get a reference to the current test,
+		// not exactly the test where assertion were intended to be called.
+		if ( !currentTest ) {
+			throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
+		}
+
+		if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
+			currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
+				sourceFromStacktrace( 2 ) );
+
+			// Allow this assertion to continue running anyway...
+		}
+
+		if ( !( assert instanceof Assert ) ) {
+			assert = currentTest.assert;
+		}
+		return assert.test.push.apply( assert.test, arguments );
+	},
+
+	ok: function( result, message ) {
+		message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
+			QUnit.dump.parse( result ) );
+		this.push( !!result, result, true, message );
+	},
+
+	notOk: function( result, message ) {
+		message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
+			QUnit.dump.parse( result ) );
+		this.push( !result, result, false, message );
+	},
+
+	equal: function( actual, expected, message ) {
+		/*jshint eqeqeq:false */
+		this.push( expected == actual, actual, expected, message );
+	},
+
+	notEqual: function( actual, expected, message ) {
+		/*jshint eqeqeq:false */
+		this.push( expected != actual, actual, expected, message );
+	},
+
+	propEqual: function( actual, expected, message ) {
+		actual = objectValues( actual );
+		expected = objectValues( expected );
+		this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+	},
+
+	notPropEqual: function( actual, expected, message ) {
+		actual = objectValues( actual );
+		expected = objectValues( expected );
+		this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+	},
+
+	deepEqual: function( actual, expected, message ) {
+		this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+	},
+
+	notDeepEqual: function( actual, expected, message ) {
+		this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+	},
+
+	strictEqual: function( actual, expected, message ) {
+		this.push( expected === actual, actual, expected, message );
+	},
+
+	notStrictEqual: function( actual, expected, message ) {
+		this.push( expected !== actual, actual, expected, message );
+	},
+
+	"throws": function( block, expected, message ) {
+		var actual, expectedType,
+			expectedOutput = expected,
+			ok = false,
+			currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
+
+		// 'expected' is optional unless doing string comparison
+		if ( message == null && typeof expected === "string" ) {
+			message = expected;
+			expected = null;
+		}
+
+		currentTest.ignoreGlobalErrors = true;
+		try {
+			block.call( currentTest.testEnvironment );
+		} catch (e) {
+			actual = e;
+		}
+		currentTest.ignoreGlobalErrors = false;
+
+		if ( actual ) {
+			expectedType = QUnit.objectType( expected );
+
+			// we don't want to validate thrown error
+			if ( !expected ) {
+				ok = true;
+				expectedOutput = null;
+
+			// expected is a regexp
+			} else if ( expectedType === "regexp" ) {
+				ok = expected.test( errorString( actual ) );
+
+			// expected is a string
+			} else if ( expectedType === "string" ) {
+				ok = expected === errorString( actual );
+
+			// expected is a constructor, maybe an Error constructor
+			} else if ( expectedType === "function" && actual instanceof expected ) {
+				ok = true;
+
+			// expected is an Error object
+			} else if ( expectedType === "object" ) {
+				ok = actual instanceof expected.constructor &&
+					actual.name === expected.name &&
+					actual.message === expected.message;
+
+			// expected is a validation function which returns true if validation passed
+			} else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
+				expectedOutput = null;
+				ok = true;
+			}
+		}
+
+		currentTest.assert.push( ok, actual, expectedOutput, message );
+	}
+};
+
+// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
+// Known to us are: Closure Compiler, Narwhal
+(function() {
+	/*jshint sub:true */
+	Assert.prototype.raises = Assert.prototype[ "throws" ];
+}());
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <[email protected]>
+QUnit.equiv = (function() {
+
+	// Call the o related callback with the given arguments.
+	function bindCallbacks( o, callbacks, args ) {
+		var prop = QUnit.objectType( o );
+		if ( prop ) {
+			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+				return callbacks[ prop ].apply( callbacks, args );
+			} else {
+				return callbacks[ prop ]; // or undefined
+			}
+		}
+	}
+
+	// the real equiv function
+	var innerEquiv,
+
+		// stack to decide between skip/abort functions
+		callers = [],
+
+		// stack to avoiding loops from circular referencing
+		parents = [],
+		parentsB = [],
+
+		getProto = Object.getPrototypeOf || function( obj ) {
+			/* jshint camelcase: false, proto: true */
+			return obj.__proto__;
+		},
+		callbacks = (function() {
+
+			// for string, boolean, number and null
+			function useStrictEquality( b, a ) {
+
+				/*jshint eqeqeq:false */
+				if ( b instanceof a.constructor || a instanceof b.constructor ) {
+
+					// to catch short annotation VS 'new' annotation of a
+					// declaration
+					// e.g. var i = 1;
+					// var j = new Number(1);
+					return a == b;
+				} else {
+					return a === b;
+				}
+			}
+
+			return {
+				"string": useStrictEquality,
+				"boolean": useStrictEquality,
+				"number": useStrictEquality,
+				"null": useStrictEquality,
+				"undefined": useStrictEquality,
+
+				"nan": function( b ) {
+					return isNaN( b );
+				},
+
+				"date": function( b, a ) {
+					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+				},
+
+				"regexp": function( b, a ) {
+					return QUnit.objectType( b ) === "regexp" &&
+
+						// the regex itself
+						a.source === b.source &&
+
+						// and its modifiers
+						a.global === b.global &&
+
+						// (gmi) ...
+						a.ignoreCase === b.ignoreCase &&
+						a.multiline === b.multiline &&
+						a.sticky === b.sticky;
+				},
+
+				// - skip when the property is a method of an instance (OOP)
+				// - abort otherwise,
+				// initial === would have catch identical references anyway
+				"function": function() {
+					var caller = callers[ callers.length - 1 ];
+					return caller !== Object && typeof caller !== "undefined";
+				},
+
+				"array": function( b, a ) {
+					var i, j, len, loop, aCircular, bCircular;
+
+					// b could be an object literal here
+					if ( QUnit.objectType( b ) !== "array" ) {
+						return false;
+					}
+
+					len = a.length;
+					if ( len !== b.length ) {
+						// safe and faster
+						return false;
+					}
+
+					// track reference to avoid circular references
+					parents.push( a );
+					parentsB.push( b );
+					for ( i = 0; i < len; i++ ) {
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							aCircular = parents[ j ] === a[ i ];
+							bCircular = parentsB[ j ] === b[ i ];
+							if ( aCircular || bCircular ) {
+								if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
+									loop = true;
+								} else {
+									parents.pop();
+									parentsB.pop();
+									return false;
+								}
+							}
+						}
+						if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
+							parents.pop();
+							parentsB.pop();
+							return false;
+						}
+					}
+					parents.pop();
+					parentsB.pop();
+					return true;
+				},
+
+				"object": function( b, a ) {
+
+					/*jshint forin:false */
+					var i, j, loop, aCircular, bCircular,
+						// Default to true
+						eq = true,
+						aProperties = [],
+						bProperties = [];
+
+					// comparing constructors is more strict than using
+					// instanceof
+					if ( a.constructor !== b.constructor ) {
+
+						// Allow objects with no prototype to be equivalent to
+						// objects with Object as their constructor.
+						if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
+							( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
+							return false;
+						}
+					}
+
+					// stack constructor before traversing properties
+					callers.push( a.constructor );
+
+					// track reference to avoid circular references
+					parents.push( a );
+					parentsB.push( b );
+
+					// be strict: don't ensure hasOwnProperty and go deep
+					for ( i in a ) {
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							aCircular = parents[ j ] === a[ i ];
+							bCircular = parentsB[ j ] === b[ i ];
+							if ( aCircular || bCircular ) {
+								if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
+									loop = true;
+								} else {
+									eq = false;
+									break;
+								}
+							}
+						}
+						aProperties.push( i );
+						if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
+							eq = false;
+							break;
+						}
+					}
+
+					parents.pop();
+					parentsB.pop();
+					callers.pop(); // unstack, we are done
+
+					for ( i in b ) {
+						bProperties.push( i ); // collect b's properties
+					}
+
+					// Ensures identical properties name
+					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+				}
+			};
+		}());
+
+	innerEquiv = function() { // can take multiple arguments
+		var args = [].slice.apply( arguments );
+		if ( args.length < 2 ) {
+			return true; // end transition
+		}
+
+		return ( (function( a, b ) {
+			if ( a === b ) {
+				return true; // catch the most you can
+			} else if ( a === null || b === null || typeof a === "undefined" ||
+					typeof b === "undefined" ||
+					QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
+
+				// don't lose time with error prone cases
+				return false;
+			} else {
+				return bindCallbacks( a, callbacks, [ b, a ] );
+			}
+
+			// apply transition with (1..n) arguments
+		}( args[ 0 ], args[ 1 ] ) ) &&
+			innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
+	};
+
+	return innerEquiv;
+}());
+
+// Based on jsDump by Ariel Flesler
+// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+QUnit.dump = (function() {
+	function quote( str ) {
+		return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
+	}
+	function literal( o ) {
+		return o + "";
+	}
+	function join( pre, arr, post ) {
+		var s = dump.separator(),
+			base = dump.indent(),
+			inner = dump.indent( 1 );
+		if ( arr.join ) {
+			arr = arr.join( "," + s + inner );
+		}
+		if ( !arr ) {
+			return pre + post;
+		}
+		return [ pre, inner + arr, base + post ].join( s );
+	}
+	function array( arr, stack ) {
+		var i = arr.length,
+			ret = new Array( i );
+
+		if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+			return "[object Array]";
+		}
+
+		this.up();
+		while ( i-- ) {
+			ret[ i ] = this.parse( arr[ i ], undefined, stack );
+		}
+		this.down();
+		return join( "[", ret, "]" );
+	}
+
+	var reName = /^function (\w+)/,
+		dump = {
+
+			// objType is used mostly internally, you can fix a (custom) type in advance
+			parse: function( obj, objType, stack ) {
+				stack = stack || [];
+				var res, parser, parserType,
+					inStack = inArray( obj, stack );
+
+				if ( inStack !== -1 ) {
+					return "recursion(" + ( inStack - stack.length ) + ")";
+				}
+
+				objType = objType || this.typeOf( obj  );
+				parser = this.parsers[ objType ];
+				parserType = typeof parser;
+
+				if ( parserType === "function" ) {
+					return;
+					stack.push( obj );
+					res = parser.call( this, obj, stack );
+					stack.pop();
+					return res;
+				}
+				return ( parserType === "string" ) ? parser : this.parsers.error;
+			},
+			typeOf: function( obj ) {
+				var type;
+				if ( obj === null ) {
+					type = "null";
+				} else if ( typeof obj === "undefined" ) {
+					type = "undefined";
+				} else if ( QUnit.is( "regexp", obj ) ) {
+					type = "regexp";
+				} else if ( QUnit.is( "date", obj ) ) {
+					type = "date";
+				} else if ( QUnit.is( "function", obj ) ) {
+					type = "function";
+				} else if ( obj.setInterval !== undefined &&
+						obj.document !== undefined &&
+						obj.nodeType === undefined ) {
+					type = "window";
+				} else if ( obj.nodeType === 9 ) {
+					type = "document";
+				} else if ( obj.nodeType ) {
+					type = "node";
+				} else if (
+
+					// native arrays
+					toString.call( obj ) === "[object Array]" ||
+
+					// NodeList objects
+					( typeof obj.length === "number" && obj.item !== undefined &&
+					( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
+					obj[ 0 ] === undefined ) ) )
+				) {
+					type = "array";
+				} else if ( obj.constructor === Error.prototype.constructor ) {
+					type = "error";
+				} else {
+					type = typeof obj;
+				}
+				return type;
+			},
+			separator: function() {
+				return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
+			},
+			// extra can be a number, shortcut for increasing-calling-decreasing
+			indent: function( extra ) {
+				if ( !this.multiline ) {
+					return "";
+				}
+				var chr = this.indentChar;
+				if ( this.HTML ) {
+					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
+				}
+				return new Array( this.depth + ( extra || 0 ) ).join( chr );
+			},
+			up: function( a ) {
+				this.depth += a || 1;
+			},
+			down: function( a ) {
+				this.depth -= a || 1;
+			},
+			setParser: function( name, parser ) {
+				this.parsers[ name ] = parser;
+			},
+			// The next 3 are exposed so you can use them
+			quote: quote,
+			literal: literal,
+			join: join,
+			//
+			depth: 1,
+			maxDepth: QUnit.config.maxDepth,
+
+			// This is the list of parsers, to modify them, use dump.setParser
+			parsers: {
+				window: "[Window]",
+				document: "[Document]",
+				error: function( error ) {
+					return "Error(\"" + error.message + "\")";
+				},
+				unknown: "[Unknown]",
+				"null": "null",
+				"undefined": "undefined",
+				"function": function( fn ) {
+					var ret = "function",
+
+						// functions never have name in IE
+						name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
+
+					if ( name ) {
+						ret += " " + name;
+					}
+					ret += "( ";
+
+					ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
+					return join( ret, dump.parse( fn, "functionCode" ), "}" );
+				},
+				array: array,
+				nodelist: array,
+				"arguments": array,
+				object: function( map, stack ) {
+					var keys, key, val, i, nonEnumerableProperties,
+						ret = [];
+
+					if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+						return "[object Object]";
+					}
+
+					dump.up();
+					keys = [];
+					for ( key in map ) {
+						keys.push( key );
+					}
+
+					// Some properties are not always enumerable on Error objects.
+					nonEnumerableProperties = [ "message", "name" ];
+					for ( i in nonEnumerableProperties ) {
+						key = nonEnumerableProperties[ i ];
+						if ( key in map && inArray( key, keys ) < 0 ) {
+							keys.push( key );
+						}
+					}
+					keys.sort();
+					for ( i = 0; i < keys.length; i++ ) {
+						key = keys[ i ];
+						val = map[ key ];
+						ret.push( dump.parse( key, "key" ) + ": " +
+							dump.parse( val, undefined, stack ) );
+					}
+					dump.down();
+					return join( "{", ret, "}" );
+				},
+				node: function( node ) {
+					var len, i, val,
+						open = dump.HTML ? "&lt;" : "<",
+						close = dump.HTML ? "&gt;" : ">",
+						tag = node.nodeName.toLowerCase(),
+						ret = open + tag,
+						attrs = node.attributes;
+
+					if ( attrs ) {
+						for ( i = 0, len = attrs.length; i < len; i++ ) {
+							val = attrs[ i ].nodeValue;
+
+							// IE6 includes all attributes in .attributes, even ones not explicitly
+							// set. Those have values like undefined, null, 0, false, "" or
+							// "inherit".
+							if ( val && val !== "inherit" ) {
+								ret += " " + attrs[ i ].nodeName + "=" +
+									dump.parse( val, "attribute" );
+							}
+						}
+					}
+					ret += close;
+
+					// Show content of TextNode or CDATASection
+					if ( node.nodeType === 3 || node.nodeType === 4 ) {
+						ret += node.nodeValue;
+					}
+
+					return ret + open + "/" + tag + close;
+				},
+
+				// function calls it internally, it's the arguments part of the function
+				functionArgs: function( fn ) {
+					var args,
+						l = fn.length;
+
+					if ( !l ) {
+						return "";
+					}
+
+					args = new Array( l );
+					while ( l-- ) {
+
+						// 97 is 'a'
+						args[ l ] = String.fromCharCode( 97 + l );
+					}
+					return " " + args.join( ", " ) + " ";
+				},
+				// object calls it internally, the key part of an item in a map
+				key: quote,
+				// function calls it internally, it's the content of the function
+				functionCode: "[code]",
+				// node calls it internally, it's an html attribute value
+				attribute: quote,
+				string: quote,
+				date: quote,
+				regexp: literal,
+				number: literal,
+				"boolean": literal
+			},
+			// if true, entities are escaped ( <, >, \t, space and \n )
+			HTML: false,
+			// indentation unit
+			indentChar: "  ",
+			// if true, items in a collection, are separated by a \n, else just a space.
+			multiline: true
+		};
+
+	return dump;
+}());
+
+// back compat
+QUnit.jsDump = QUnit.dump;
+
+// For browser, export only select globals
+if ( typeof window !== "undefined" ) {
+
+	// Deprecated
+	// Extend assert methods to QUnit and Global scope through Backwards compatibility
+	(function() {
+		var i,
+			assertions = Assert.prototype;
+
+		function applyCurrent( current ) {
+			return function() {
+				var assert = new Assert( QUnit.config.current );
+				current.apply( assert, arguments );
+			};
+		}
+
+		for ( i in assertions ) {
+			QUnit[ i ] = applyCurrent( assertions[ i ] );
+		}
+	})();
+
+	(function() {
+		var i, l,
+			keys = [
+				"test",
+				"module",
+				"expect",
+				"asyncTest",
+				"start",
+				"stop",
+				"ok",
+				"notOk",
+				"equal",
+				"notEqual",
+				"propEqual",
+				"notPropEqual",
+				"deepEqual",
+				"notDeepEqual",
+				"strictEqual",
+				"notStrictEqual",
+				"throws"
+			];
+
+		for ( i = 0, l = keys.length; i < l; i++ ) {
+			window[ keys[ i ] ] = QUnit[ keys[ i ] ];
+		}
+	})();
+
+	window.QUnit = QUnit;
+}
+
+// For nodejs
+if ( typeof module !== "undefined" && module && module.exports ) {
+	module.exports = QUnit;
+
+	// For consistency with CommonJS environments' exports
+	module.exports.QUnit = QUnit;
+}
+
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" && exports ) {
+	exports.QUnit = QUnit;
+}
+
+if ( typeof define === "function" && define.amd ) {
+	define( function() {
+		return QUnit;
+	} );
+	QUnit.config.autostart = false;
+}
+
+// Get a reference to the global object, like window in browsers
+}( (function() {
+	return this;
+})() ));
+
+/*istanbul ignore next */
+// jscs:disable maximumLineLength
+/*
+ * This file is a modified version of google-diff-match-patch's JavaScript implementation
+ * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+ * modifications are licensed as more fully set forth in LICENSE.txt.
+ *
+ * The original source of google-diff-match-patch is attributable and licensed as follows:
+ *
+ * Copyright 2006 Google Inc.
+ * http://code.google.com/p/google-diff-match-patch/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More Info:
+ *  https://code.google.com/p/google-diff-match-patch/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the  quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
+ */
+QUnit.diff = (function() {
+
+    function DiffMatchPatch() {
+
+        // Defaults.
+        // Redefine these in your program to override the defaults.
+
+        // Number of seconds to map a diff before giving up (0 for infinity).
+        this.DiffTimeout = 1.0;
+        // Cost of an empty edit operation in terms of edit characters.
+        this.DiffEditCost = 4;
+    }
+
+    //  DIFF FUNCTIONS
+
+    /**
+     * The data structure representing a diff is an array of tuples:
+     * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
+     * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
+     */
+    var DIFF_DELETE = -1,
+		DIFF_INSERT = 1,
+		DIFF_EQUAL = 0;
+
+    /**
+     * Find the differences between two texts.  Simplifies the problem by stripping
+     * any common prefix or suffix off the texts before diffing.
+     * @param {string} text1 Old string to be diffed.
+     * @param {string} text2 New string to be diffed.
+     * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+     *     then don't run a line-level diff first to identify the changed areas.
+     *     Defaults to true, which does a faster, slightly less optimal diff.
+     * @param {number} optDeadline Optional time when the diff should be complete
+     *     by.  Used internally for recursive calls.  Users should set DiffTimeout
+     *     instead.
+     * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+     */
+    DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
+        var deadline, checklines, commonlength,
+			commonprefix, commonsuffix, diffs;
+        // Set a deadline by which time the diff must be complete.
+        if ( typeof optDeadline === "undefined" ) {
+            if ( this.DiffTimeout <= 0 ) {
+                optDeadline = Number.MAX_VALUE;
+            } else {
+                optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
+            }
+        }
+        deadline = optDeadline;
+
+        // Check for null inputs.
+        if ( text1 === null || text2 === null ) {
+            throw new Error( "Null input. (DiffMain)" );
+        }
+
+        // Check for equality (speedup).
+        if ( text1 === text2 ) {
+            if ( text1 ) {
+                return [
+                    [ DIFF_EQUAL, text1 ]
+                ];
+            }
+            return [];
+        }
+
+        if ( typeof optChecklines === "undefined" ) {
+            optChecklines = true;
+        }
+
+        checklines = optChecklines;
+
+        // Trim off common prefix (speedup).
+        commonlength = this.diffCommonPrefix( text1, text2 );
+        commonprefix = text1.substring( 0, commonlength );
+        text1 = text1.substring( commonlength );
+        text2 = text2.substring( commonlength );
+
+        // Trim off common suffix (speedup).
+        /////////
+        commonlength = this.diffCommonSuffix( text1, text2 );
+        commonsuffix = text1.substring( text1.length - commonlength );
+        text1 = text1.substring( 0, text1.length - commonlength );
+        text2 = text2.substring( 0, text2.length - commonlength );
+
+        // Compute the diff on the middle block.
+        diffs = this.diffCompute( text1, text2, checklines, deadline );
+
+        // Restore the prefix and suffix.
+        if ( commonprefix ) {
+            diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
+        }
+        if ( commonsuffix ) {
+            diffs.push( [ DIFF_EQUAL, commonsuffix ] );
+        }
+        this.diffCleanupMerge( diffs );
+        return diffs;
+    };
+
+    /**
+     * Reduce the number of edits by eliminating operationally trivial equalities.
+     * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+     */
+    DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
+        var changes, equalities, equalitiesLength, lastequality,
+			pointer, preIns, preDel, postIns, postDel;
+        changes = false;
+        equalities = []; // Stack of indices where equalities are found.
+        equalitiesLength = 0; // Keeping our own length var is faster in JS.
+        /** @type {?string} */
+        lastequality = null;
+        // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+        pointer = 0; // Index of current position.
+        // Is there an insertion operation before the last equality.
+        preIns = false;
+        // Is there a deletion operation before the last equality.
+        preDel = false;
+        // Is there an insertion operation after the last equality.
+        postIns = false;
+        // Is there a deletion operation after the last equality.
+        postDel = false;
+        while ( pointer < diffs.length ) {
+            if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
+                if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
+                    // Candidate found.
+                    equalities[ equalitiesLength++ ] = pointer;
+                    preIns = postIns;
+                    preDel = postDel;
+                    lastequality = diffs[ pointer ][ 1 ];
+                } else {
+                    // Not a candidate, and can never become one.
+                    equalitiesLength = 0;
+                    lastequality = null;
+                }
+                postIns = postDel = false;
+            } else { // An insertion or deletion.
+                if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
+                    postDel = true;
+                } else {
+                    postIns = true;
+                }
+                /*
+                 * Five types to be split:
+                 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+                 * <ins>A</ins>X<ins>C</ins><del>D</del>
+                 * <ins>A</ins><del>B</del>X<ins>C</ins>
+                 * <ins>A</del>X<ins>C</ins><del>D</del>
+                 * <ins>A</ins><del>B</del>X<del>C</del>
+                 */
+                if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
+                        ( ( lastequality.length < this.DiffEditCost / 2 ) &&
+                            ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
+                    // Duplicate record.
+                    diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
+                    // Change second copy to insert.
+                    diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+                    equalitiesLength--; // Throw away the equality we just deleted;
+                    lastequality = null;
+                    if (preIns && preDel) {
+                        // No changes made which could affect previous entry, keep going.
+                        postIns = postDel = true;
+                        equalitiesLength = 0;
+                    } else {
+                        equalitiesLength--; // Throw away the previous equality.
+                        pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+                        postIns = postDel = false;
+                    }
+                    changes = true;
+                }
+            }
+            pointer++;
+        }
+
+        if ( changes ) {
+            this.diffCleanupMerge( diffs );
+        }
+    };
+
+    /**
+     * Convert a diff array into a pretty HTML report.
+     * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+     * @param {integer} string to be beautified.
+     * @return {string} HTML representation.
+     */
+    DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
+        var op, data, x, html = [];
+        for ( x = 0; x < diffs.length; x++ ) {
+            op = diffs[x][0]; // Operation (insert, delete, equal)
+            data = diffs[x][1]; // Text of change.
+            switch ( op ) {
+                case DIFF_INSERT:
+                    html[x] = "<ins>" + data + "</ins>";
+                    break;
+                case DIFF_DELETE:
+                    html[x] = "<del>" + data + "</del>";
+                    break;
+                case DIFF_EQUAL:
+                    html[x] = "<span>" + data + "</span>";
+                    break;
+            }
+        }
+        return html.join("");
+    };
+
+    /**
+     * Determine the common prefix of two strings.
+     * @param {string} text1 First string.
+     * @param {string} text2 Second string.
+     * @return {number} The number of characters common to the start of each
+     *     string.
+     */
+    DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
+        var pointermid, pointermax, pointermin, pointerstart;
+        // Quick check for common null cases.
+        if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
+            return 0;
+        }
+        // Binary search.
+        // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+        pointermin = 0;
+        pointermax = Math.min( text1.length, text2.length );
+        pointermid = pointermax;
+        pointerstart = 0;
+        while ( pointermin < pointermid ) {
+            if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
+                pointermin = pointermid;
+                pointerstart = pointermin;
+            } else {
+                pointermax = pointermid;
+            }
+            pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+        }
+        return pointermid;
+    };
+
+    /**
+     * Determine the common suffix of two strings.
+     * @param {string} text1 First string.
+     * @param {string} text2 Second string.
+     * @return {number} The number of characters common to the end of each string.
+     */
+    DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
+        var pointermid, pointermax, pointermin, pointerend;
+        // Quick check for common null cases.
+        if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
+            return 0;
+        }
+        // Binary search.
+        // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+        pointermin = 0;
+        pointermax = Math.min(text1.length, text2.length);
+        pointermid = pointermax;
+        pointerend = 0;
+        while ( pointermin < pointermid ) {
+            if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
+                text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
+                pointermin = pointermid;
+                pointerend = pointermin;
+            } else {
+                pointermax = pointermid;
+            }
+            pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+        }
+        return pointermid;
+    };
+
+    /**
+     * Find the differences between two texts.  Assumes that the texts do not
+     * have any common prefix or suffix.
+     * @param {string} text1 Old string to be diffed.
+     * @param {string} text2 New string to be diffed.
+     * @param {boolean} checklines Speedup flag.  If false, then don't run a
+     *     line-level diff first to identify the changed areas.
+     *     If true, then run a faster, slightly less optimal diff.
+     * @param {number} deadline Time when the diff should be complete by.
+     * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
+        var diffs, longtext, shorttext, i, hm,
+			text1A, text2A, text1B, text2B,
+			midCommon, diffsA, diffsB;
+
+        if ( !text1 ) {
+            // Just add some text (speedup).
+            return [
+                [ DIFF_INSERT, text2 ]
+            ];
+        }
+
+        if (!text2) {
+            // Just delete some text (speedup).
+            return [
+                [ DIFF_DELETE, text1 ]
+            ];
+        }
+
+        longtext = text1.length > text2.length ? text1 : text2;
+        shorttext = text1.length > text2.length ? text2 : text1;
+        i = longtext.indexOf( shorttext );
+        if ( i !== -1 ) {
+            // Shorter text is inside the longer text (speedup).
+            diffs = [
+                [ DIFF_INSERT, longtext.substring( 0, i ) ],
+                [ DIFF_EQUAL, shorttext ],
+                [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
+            ];
+            // Swap insertions for deletions if diff is reversed.
+            if ( text1.length > text2.length ) {
+                diffs[0][0] = diffs[2][0] = DIFF_DELETE;
+            }
+            return diffs;
+        }
+
+        if ( shorttext.length === 1 ) {
+            // Single character string.
+            // After the previous speedup, the character can't be an equality.
+            return [
+                [ DIFF_DELETE, text1 ],
+                [ DIFF_INSERT, text2 ]
+            ];
+        }
+
+        // Check to see if the problem can be split in two.
+        hm = this.diffHalfMatch(text1, text2);
+        if (hm) {
+            // A half-match was found, sort out the return data.
+            text1A = hm[0];
+            text1B = hm[1];
+            text2A = hm[2];
+            text2B = hm[3];
+            midCommon = hm[4];
+            // Send both pairs off for separate processing.
+            diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
+            diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
+            // Merge the results.
+            return diffsA.concat([
+                [ DIFF_EQUAL, midCommon ]
+            ], diffsB);
+        }
+
+        if (checklines && text1.length > 100 && text2.length > 100) {
+            return this.diffLineMode(text1, text2, deadline);
+        }
+
+        return this.diffBisect(text1, text2, deadline);
+    };
+
+    /**
+     * Do the two texts share a substring which is at least half the length of the
+     * longer text?
+     * This speedup can produce non-minimal diffs.
+     * @param {string} text1 First string.
+     * @param {string} text2 Second string.
+     * @return {Array.<string>} Five element Array, containing the prefix of
+     *     text1, the suffix of text1, the prefix of text2, the suffix of
+     *     text2 and the common middle.  Or null if there was no match.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
+        var longtext, shorttext, dmp,
+			text1A, text2B, text2A, text1B, midCommon,
+			hm1, hm2, hm;
+        if (this.DiffTimeout <= 0) {
+            // Don't risk returning a non-optimal diff if we have unlimited time.
+            return null;
+        }
+        longtext = text1.length > text2.length ? text1 : text2;
+        shorttext = text1.length > text2.length ? text2 : text1;
+        if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
+            return null; // Pointless.
+        }
+        dmp = this; // 'this' becomes 'window' in a closure.
+
+        /**
+         * Does a substring of shorttext exist within longtext such that the substring
+         * is at least half the length of longtext?
+         * Closure, but does not reference any external variables.
+         * @param {string} longtext Longer string.
+         * @param {string} shorttext Shorter string.
+         * @param {number} i Start index of quarter length substring within longtext.
+         * @return {Array.<string>} Five element Array, containing the prefix of
+         *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
+         *     of shorttext and the common middle.  Or null if there was no match.
+         * @private
+         */
+        function diffHalfMatchI(longtext, shorttext, i) {
+            var seed, j, bestCommon, prefixLength, suffixLength,
+				bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
+            // Start with a 1/4 length substring at position i as a seed.
+            seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
+            j = -1;
+            bestCommon = "";
+            while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
+                prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
+                    shorttext.substring(j));
+                suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
+                    shorttext.substring(0, j));
+                if (bestCommon.length < suffixLength + prefixLength) {
+                    bestCommon = shorttext.substring(j - suffixLength, j) +
+                        shorttext.substring(j, j + prefixLength);
+                    bestLongtextA = longtext.substring(0, i - suffixLength);
+                    bestLongtextB = longtext.substring(i + prefixLength);
+                    bestShorttextA = shorttext.substring(0, j - suffixLength);
+                    bestShorttextB = shorttext.substring(j + prefixLength);
+                }
+            }
+            if (bestCommon.length * 2 >= longtext.length) {
+                return [ bestLongtextA, bestLongtextB,
+                    bestShorttextA, bestShorttextB, bestCommon
+                ];
+            } else {
+                return null;
+            }
+        }
+
+        // First check if the second quarter is the seed for a half-match.
+        hm1 = diffHalfMatchI(longtext, shorttext,
+            Math.ceil(longtext.length / 4));
+        // Check again based on the third quarter.
+        hm2 = diffHalfMatchI(longtext, shorttext,
+            Math.ceil(longtext.length / 2));
+        if (!hm1 && !hm2) {
+            return null;
+        } else if (!hm2) {
+            hm = hm1;
+        } else if (!hm1) {
+            hm = hm2;
+        } else {
+            // Both matched.  Select the longest.
+            hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
+        }
+
+        // A half-match was found, sort out the return data.
+        text1A, text1B, text2A, text2B;
+        if (text1.length > text2.length) {
+            text1A = hm[0];
+            text1B = hm[1];
+            text2A = hm[2];
+            text2B = hm[3];
+        } else {
+            text2A = hm[0];
+            text2B = hm[1];
+            text1A = hm[2];
+            text1B = hm[3];
+        }
+        midCommon = hm[4];
+        return [ text1A, text1B, text2A, text2B, midCommon ];
+    };
+
+    /**
+     * Do a quick line-level diff on both strings, then rediff the parts for
+     * greater accuracy.
+     * This speedup can produce non-minimal diffs.
+     * @param {string} text1 Old string to be diffed.
+     * @param {string} text2 New string to be diffed.
+     * @param {number} deadline Time when the diff should be complete by.
+     * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
+        var a, diffs, linearray, pointer, countInsert,
+			countDelete, textInsert, textDelete, j;
+        // Scan the text on a line-by-line basis first.
+        a = this.diffLinesToChars(text1, text2);
+        text1 = a.chars1;
+        text2 = a.chars2;
+        linearray = a.lineArray;
+
+        diffs = this.DiffMain(text1, text2, false, deadline);
+
+        // Convert the diff back to original text.
+        this.diffCharsToLines(diffs, linearray);
+        // Eliminate freak matches (e.g. blank lines)
+        this.diffCleanupSemantic(diffs);
+
+        // Rediff any replacement blocks, this time character-by-character.
+        // Add a dummy entry at the end.
+        diffs.push( [ DIFF_EQUAL, "" ] );
+        pointer = 0;
+        countDelete = 0;
+        countInsert = 0;
+        textDelete = "";
+        textInsert = "";
+        while (pointer < diffs.length) {
+            switch ( diffs[pointer][0] ) {
+                case DIFF_INSERT:
+                    countInsert++;
+                    textInsert += diffs[pointer][1];
+                    break;
+                case DIFF_DELETE:
+                    countDelete++;
+                    textDelete += diffs[pointer][1];
+                    break;
+                case DIFF_EQUAL:
+                    // Upon reaching an equality, check for prior redundancies.
+                    if (countDelete >= 1 && countInsert >= 1) {
+                        // Delete the offending records and add the merged ones.
+                        diffs.splice(pointer - countDelete - countInsert,
+                            countDelete + countInsert);
+                        pointer = pointer - countDelete - countInsert;
+                        a = this.DiffMain(textDelete, textInsert, false, deadline);
+                        for (j = a.length - 1; j >= 0; j--) {
+                            diffs.splice( pointer, 0, a[j] );
+                        }
+                        pointer = pointer + a.length;
+                    }
+                    countInsert = 0;
+                    countDelete = 0;
+                    textDelete = "";
+                    textInsert = "";
+                    break;
+            }
+            pointer++;
+        }
+        diffs.pop(); // Remove the dummy entry at the end.
+
+        return diffs;
+    };
+
+    /**
+     * Find the 'middle snake' of a diff, split the problem in two
+     * and return the recursively constructed diff.
+     * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+     * @param {string} text1 Old string to be diffed.
+     * @param {string} text2 New string to be diffed.
+     * @param {number} deadline Time at which to bail if not yet complete.
+     * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
+        var text1Length, text2Length, maxD, vOffset, vLength,
+			v1, v2, x, delta, front, k1start, k1end, k2start,
+			k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
+        // Cache the text lengths to prevent multiple calls.
+        text1Length = text1.length;
+        text2Length = text2.length;
+        maxD = Math.ceil((text1Length + text2Length) / 2);
+        vOffset = maxD;
+        vLength = 2 * maxD;
+        v1 = new Array(vLength);
+        v2 = new Array(vLength);
+        // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+        // integers and undefined.
+        for (x = 0; x < vLength; x++) {
+            v1[x] = -1;
+            v2[x] = -1;
+        }
+        v1[vOffset + 1] = 0;
+        v2[vOffset + 1] = 0;
+        delta = text1Length - text2Length;
+        // If the total number of characters is odd, then the front path will collide
+        // with the reverse path.
+        front = (delta % 2 !== 0);
+        // Offsets for start and end of k loop.
+        // Prevents mapping of space beyond the grid.
+        k1start = 0;
+        k1end = 0;
+        k2start = 0;
+        k2end = 0;
+        for (d = 0; d < maxD; d++) {
+            // Bail out if deadline is reached.
+            if ((new Date()).getTime() > deadline) {
+                break;
+            }
+
+            // Walk the front path one step.
+            for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
+                k1Offset = vOffset + k1;
+                if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
+                    x1 = v1[k1Offset + 1];
+                } else {
+                    x1 = v1[k1Offset - 1] + 1;
+                }
+                y1 = x1 - k1;
+                while (x1 < text1Length && y1 < text2Length &&
+                    text1.charAt(x1) === text2.charAt(y1)) {
+                    x1++;
+                    y1++;
+                }
+                v1[k1Offset] = x1;
+                if (x1 > text1Length) {
+                    // Ran off the right of the graph.
+                    k1end += 2;
+                } else if (y1 > text2Length) {
+                    // Ran off the bottom of the graph.
+                    k1start += 2;
+                } else if (front) {
+                    k2Offset = vOffset + delta - k1;
+                    if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
+                        // Mirror x2 onto top-left coordinate system.
+                        x2 = text1Length - v2[k2Offset];
+                        if (x1 >= x2) {
+                            // Overlap detected.
+                            return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+                        }
+                    }
+                }
+            }
+
+            // Walk the reverse path one step.
+            for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
+                k2Offset = vOffset + k2;
+                if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
+                    x2 = v2[k2Offset + 1];
+                } else {
+                    x2 = v2[k2Offset - 1] + 1;
+                }
+                y2 = x2 - k2;
+                while (x2 < text1Length && y2 < text2Length &&
+                    text1.charAt(text1Length - x2 - 1) ===
+                    text2.charAt(text2Length - y2 - 1)) {
+                    x2++;
+                    y2++;
+                }
+                v2[k2Offset] = x2;
+                if (x2 > text1Length) {
+                    // Ran off the left of the graph.
+                    k2end += 2;
+                } else if (y2 > text2Length) {
+                    // Ran off the top of the graph.
+                    k2start += 2;
+                } else if (!front) {
+                    k1Offset = vOffset + delta - k2;
+                    if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
+                        x1 = v1[k1Offset];
+                        y1 = vOffset + x1 - k1Offset;
+                        // Mirror x2 onto top-left coordinate system.
+                        x2 = text1Length - x2;
+                        if (x1 >= x2) {
+                            // Overlap detected.
+                            return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+                        }
+                    }
+                }
+            }
+        }
+        // Diff took too long and hit the deadline or
+        // number of diffs equals number of characters, no commonality at all.
+        return [
+            [ DIFF_DELETE, text1 ],
+            [ DIFF_INSERT, text2 ]
+        ];
+    };
+
+    /**
+     * Given the location of the 'middle snake', split the diff in two parts
+     * and recurse.
+     * @param {string} text1 Old string to be diffed.
+     * @param {string} text2 New string to be diffed.
+     * @param {number} x Index of split point in text1.
+     * @param {number} y Index of split point in text2.
+     * @param {number} deadline Time at which to bail if not yet complete.
+     * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
+        var text1a, text1b, text2a, text2b, diffs, diffsb;
+        text1a = text1.substring(0, x);
+        text2a = text2.substring(0, y);
+        text1b = text1.substring(x);
+        text2b = text2.substring(y);
+
+        // Compute both diffs serially.
+        diffs = this.DiffMain(text1a, text2a, false, deadline);
+        diffsb = this.DiffMain(text1b, text2b, false, deadline);
+
+        return diffs.concat(diffsb);
+    };
+
+    /**
+     * Reduce the number of edits by eliminating semantically trivial equalities.
+     * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+     */
+    DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
+        var changes, equalities, equalitiesLength, lastequality,
+			pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
+			lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+        changes = false;
+        equalities = []; // Stack of indices where equalities are found.
+        equalitiesLength = 0; // Keeping our own length var is faster in JS.
+        /** @type {?string} */
+        lastequality = null;
+        // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+        pointer = 0; // Index of current position.
+        // Number of characters that changed prior to the equality.
+        lengthInsertions1 = 0;
+        lengthDeletions1 = 0;
+        // Number of characters that changed after the equality.
+        lengthInsertions2 = 0;
+        lengthDeletions2 = 0;
+        while (pointer < diffs.length) {
+            if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
+                equalities[equalitiesLength++] = pointer;
+                lengthInsertions1 = lengthInsertions2;
+                lengthDeletions1 = lengthDeletions2;
+                lengthInsertions2 = 0;
+                lengthDeletions2 = 0;
+                lastequality = diffs[pointer][1];
+            } else { // An insertion or deletion.
+                if (diffs[pointer][0] === DIFF_INSERT) {
+                    lengthInsertions2 += diffs[pointer][1].length;
+                } else {
+                    lengthDeletions2 += diffs[pointer][1].length;
+                }
+                // Eliminate an equality that is smaller or equal to the edits on both
+                // sides of it.
+                if (lastequality && (lastequality.length <=
+                        Math.max(lengthInsertions1, lengthDeletions1)) &&
+                    (lastequality.length <= Math.max(lengthInsertions2,
+                        lengthDeletions2))) {
+                    // Duplicate record.
+                    diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
+                    // Change second copy to insert.
+                    diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+                    // Throw away the equality we just deleted.
+                    equalitiesLength--;
+                    // Throw away the previous equality (it needs to be reevaluated).
+                    equalitiesLength--;
+                    pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+                    lengthInsertions1 = 0; // Reset the counters.
+                    lengthDeletions1 = 0;
+                    lengthInsertions2 = 0;
+                    lengthDeletions2 = 0;
+                    lastequality = null;
+                    changes = true;
+                }
+            }
+            pointer++;
+        }
+
+        // Normalize the diff.
+        if (changes) {
+            this.diffCleanupMerge(diffs);
+        }
+
+        // Find any overlaps between deletions and insertions.
+        // e.g: <del>abcxxx</del><ins>xxxdef</ins>
+        //   -> <del>abc</del>xxx<ins>def</ins>
+        // e.g: <del>xxxabc</del><ins>defxxx</ins>
+        //   -> <ins>def</ins>xxx<del>abc</del>
+        // Only extract an overlap if it is as big as the edit ahead or behind it.
+        pointer = 1;
+        while (pointer < diffs.length) {
+            if (diffs[pointer - 1][0] === DIFF_DELETE &&
+                diffs[pointer][0] === DIFF_INSERT) {
+                deletion = diffs[pointer - 1][1];
+                insertion = diffs[pointer][1];
+                overlapLength1 = this.diffCommonOverlap(deletion, insertion);
+                overlapLength2 = this.diffCommonOverlap(insertion, deletion);
+                if (overlapLength1 >= overlapLength2) {
+                    if (overlapLength1 >= deletion.length / 2 ||
+                        overlapLength1 >= insertion.length / 2) {
+                        // Overlap found.  Insert an equality and trim the surrounding edits.
+                        diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
+                        diffs[pointer - 1][1] =
+                            deletion.substring(0, deletion.length - overlapLength1);
+                        diffs[pointer + 1][1] = insertion.substring(overlapLength1);
+                        pointer++;
+                    }
+                } else {
+                    if (overlapLength2 >= deletion.length / 2 ||
+                        overlapLength2 >= insertion.length / 2) {
+                        // Reverse overlap found.
+                        // Insert an equality and swap and trim the surrounding edits.
+                        diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
+                        diffs[pointer - 1][0] = DIFF_INSERT;
+                        diffs[pointer - 1][1] =
+                            insertion.substring(0, insertion.length - overlapLength2);
+                        diffs[pointer + 1][0] = DIFF_DELETE;
+                        diffs[pointer + 1][1] =
+                            deletion.substring(overlapLength2);
+                        pointer++;
+                    }
+                }
+                pointer++;
+            }
+            pointer++;
+        }
+    };
+
+    /**
+     * Determine if the suffix of one string is the prefix of another.
+     * @param {string} text1 First string.
+     * @param {string} text2 Second string.
+     * @return {number} The number of characters common to the end of the first
+     *     string and the start of the second string.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
+        var text1Length, text2Length, textLength,
+			best, length, pattern, found;
+        // Cache the text lengths to prevent multiple calls.
+        text1Length = text1.length;
+        text2Length = text2.length;
+        // Eliminate the null case.
+        if (text1Length === 0 || text2Length === 0) {
+            return 0;
+        }
+        // Truncate the longer string.
+        if (text1Length > text2Length) {
+            text1 = text1.substring(text1Length - text2Length);
+        } else if (text1Length < text2Length) {
+            text2 = text2.substring(0, text1Length);
+        }
+        textLength = Math.min(text1Length, text2Length);
+        // Quick check for the worst case.
+        if (text1 === text2) {
+            return textLength;
+        }
+
+        // Start by looking for a single character match
+        // and increase length until no match is found.
+        // Performance analysis: http://neil.fraser.name/news/2010/11/04/
+        best = 0;
+        length = 1;
+        while (true) {
+            pattern = text1.substring(textLength - length);
+            found = text2.indexOf(pattern);
+            if (found === -1) {
+                return best;
+            }
+            length += found;
+            if (found === 0 || text1.substring(textLength - length) ===
+                text2.substring(0, length)) {
+                best = length;
+                length++;
+            }
+        }
+    };
+
+    /**
+     * Split two texts into an array of strings.  Reduce the texts to a string of
+     * hashes where each Unicode character represents one line.
+     * @param {string} text1 First string.
+     * @param {string} text2 Second string.
+     * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
+     *     An object containing the encoded text1, the encoded text2 and
+     *     the array of unique strings.
+     *     The zeroth element of the array of unique strings is intentionally blank.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
+        var lineArray, lineHash, chars1, chars2;
+        lineArray = []; // e.g. lineArray[4] === 'Hello\n'
+        lineHash = {}; // e.g. lineHash['Hello\n'] === 4
+
+        // '\x00' is a valid character, but various debuggers don't like it.
+        // So we'll insert a junk entry to avoid generating a null character.
+        lineArray[0] = "";
+
+        /**
+         * Split a text into an array of strings.  Reduce the texts to a string of
+         * hashes where each Unicode character represents one line.
+         * Modifies linearray and linehash through being a closure.
+         * @param {string} text String to encode.
+         * @return {string} Encoded string.
+         * @private
+         */
+        function diffLinesToCharsMunge(text) {
+            var chars, lineStart, lineEnd, lineArrayLength, line;
+            chars = "";
+            // Walk the text, pulling out a substring for each line.
+            // text.split('\n') would would temporarily double our memory footprint.
+            // Modifying text would create many large strings to garbage collect.
+            lineStart = 0;
+            lineEnd = -1;
+            // Keeping our own length variable is faster than looking it up.
+            lineArrayLength = lineArray.length;
+            while (lineEnd < text.length - 1) {
+                lineEnd = text.indexOf("\n", lineStart);
+                if (lineEnd === -1) {
+                    lineEnd = text.length - 1;
+                }
+                line = text.substring(lineStart, lineEnd + 1);
+                lineStart = lineEnd + 1;
+
+                if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
+                    (lineHash[line] !== undefined)) {
+                    chars += String.fromCharCode( lineHash[ line ] );
+                } else {
+                    chars += String.fromCharCode(lineArrayLength);
+                    lineHash[line] = lineArrayLength;
+                    lineArray[lineArrayLength++] = line;
+                }
+            }
+            return chars;
+        }
+
+        chars1 = diffLinesToCharsMunge(text1);
+        chars2 = diffLinesToCharsMunge(text2);
+        return {
+            chars1: chars1,
+            chars2: chars2,
+            lineArray: lineArray
+        };
+    };
+
+    /**
+     * Rehydrate the text in a diff from a string of line hashes to real lines of
+     * text.
+     * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+     * @param {!Array.<string>} lineArray Array of unique strings.
+     * @private
+     */
+    DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
+        var x, chars, text, y;
+        for ( x = 0; x < diffs.length; x++ ) {
+            chars = diffs[x][1];
+            text = [];
+            for ( y = 0; y < chars.length; y++ ) {
+                text[y] = lineArray[chars.charCodeAt(y)];
+            }
+            diffs[x][1] = text.join("");
+        }
+    };
+
+    /**
+     * Reorder and merge like edit sections.  Merge equalities.
+     * Any edit section can move as long as it doesn't cross an equality.
+     * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+     */
+    DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
+        var pointer, countDelete, countInsert, textInsert, textDelete,
+			commonlength, changes;
+        diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
+        pointer = 0;
+        countDelete = 0;
+        countInsert = 0;
+        textDelete = "";
+        textInsert = "";
+        commonlength;
+        while (pointer < diffs.length) {
+            switch ( diffs[ pointer ][ 0 ] ) {
+                case DIFF_INSERT:
+                    countInsert++;
+                    textInsert += diffs[pointer][1];
+                    pointer++;
+                    break;
+                case DIFF_DELETE:
+                    countDelete++;
+                    textDelete += diffs[pointer][1];
+                    pointer++;
+                    break;
+                case DIFF_EQUAL:
+                    // Upon reaching an equality, check for prior redundancies.
+                    if (countDelete + countInsert > 1) {
+                        if (countDelete !== 0 && countInsert !== 0) {
+                            // Factor out any common prefixies.
+                            commonlength = this.diffCommonPrefix(textInsert, textDelete);
+                            if (commonlength !== 0) {
+                                if ((pointer - countDelete - countInsert) > 0 &&
+                                    diffs[pointer - countDelete - countInsert - 1][0] ===
+                                    DIFF_EQUAL) {
+                                    diffs[pointer - countDelete - countInsert - 1][1] +=
+                                        textInsert.substring(0, commonlength);
+                                } else {
+                                    diffs.splice( 0, 0, [ DIFF_EQUAL,
+                                        textInsert.substring( 0, commonlength )
+                                     ] );
+                                    pointer++;
+                                }
+                                textInsert = textInsert.substring(commonlength);
+                                textDelete = textDelete.substring(commonlength);
+                            }
+                            // Factor out any common suffixies.
+                            commonlength = this.diffCommonSuffix(textInsert, textDelete);
+                            if (commonlength !== 0) {
+                                diffs[pointer][1] = textInsert.substring(textInsert.length -
+                                    commonlength) + diffs[pointer][1];
+                                textInsert = textInsert.substring(0, textInsert.length -
+                                    commonlength);
+                                textDelete = textDelete.substring(0, textDelete.length -
+                                    commonlength);
+                            }
+                        }
+                        // Delete the offending records and add the merged ones.
+                        if (countDelete === 0) {
+                            diffs.splice( pointer - countInsert,
+                                countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
+                        } else if (countInsert === 0) {
+                            diffs.splice( pointer - countDelete,
+                                countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
+                        } else {
+                            diffs.splice( pointer - countDelete - countInsert,
+                                countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
+                        }
+                        pointer = pointer - countDelete - countInsert +
+                            (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
+                    } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
+                        // Merge this equality with the previous one.
+                        diffs[pointer - 1][1] += diffs[pointer][1];
+                        diffs.splice(pointer, 1);
+                    } else {
+                        pointer++;
+                    }
+                    countInsert = 0;
+                    countDelete = 0;
+                    textDelete = "";
+                    textInsert = "";
+                    break;
+            }
+        }
+        if (diffs[diffs.length - 1][1] === "") {
+            diffs.pop(); // Remove the dummy entry at the end.
+        }
+
+        // Second pass: look for single edits surrounded on both sides by equalities
+        // which can be shifted sideways to eliminate an equality.
+        // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
+        changes = false;
+        pointer = 1;
+        // Intentionally ignore the first and last element (don't need checking).
+        while (pointer < diffs.length - 1) {
+            if (diffs[pointer - 1][0] === DIFF_EQUAL &&
+                diffs[pointer + 1][0] === DIFF_EQUAL) {
+                // This is a single edit surrounded by equalities.
+                if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
+                        diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
+                    // Shift the edit over the previous equality.
+                    diffs[pointer][1] = diffs[pointer - 1][1] +
+                        diffs[pointer][1].substring(0, diffs[pointer][1].length -
+                            diffs[pointer - 1][1].length);
+                    diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
+                    diffs.splice(pointer - 1, 1);
+                    changes = true;
+                } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
+                    diffs[ pointer + 1 ][ 1 ] ) {
+                    // Shift the edit over the next equality.
+                    diffs[pointer - 1][1] += diffs[pointer + 1][1];
+                    diffs[pointer][1] =
+                        diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
+                        diffs[pointer + 1][1];
+                    diffs.splice(pointer + 1, 1);
+                    changes = true;
+                }
+            }
+            pointer++;
+        }
+        // If shifts were made, the diff needs reordering and another shift sweep.
+        if (changes) {
+            this.diffCleanupMerge(diffs);
+        }
+    };
+
+    return function(o, n) {
+		var diff, output, text;
+        diff = new DiffMatchPatch();
+        output = diff.DiffMain(o, n);
+        //console.log(output);
+        diff.diffCleanupEfficiency(output);
+        text = diff.diffPrettyHtml(output);
+
+        return text;
+    };
+}());
+// jscs:enable
+
+(function() {
+
+// Deprecated QUnit.init - Ref #530
+// Re-initialize the configuration options
+QUnit.init = function() {
+	var tests, banner, result, qunit,
+		config = QUnit.config;
+
+	config.stats = { all: 0, bad: 0 };
+	config.moduleStats = { all: 0, bad: 0 };
+	config.started = 0;
+	config.updateRate = 1000;
+	config.blocking = false;
+	config.autostart = true;
+	config.autorun = false;
+	config.filter = "";
+	config.queue = [];
+
+	// Return on non-browser environments
+	// This is necessary to not break on node tests
+	if ( typeof window === "undefined" ) {
+		return;
+	}
+
+	qunit = id( "qunit" );
+	if ( qunit ) {
+		qunit.innerHTML =
+			"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+			"<h2 id='qunit-banner'></h2>" +
+			"<div id='qunit-testrunner-toolbar'></div>" +
+			"<h2 id='qunit-userAgent'></h2>" +
+			"<ol id='qunit-tests'></ol>";
+	}
+
+	tests = id( "qunit-tests" );
+	banner = id( "qunit-banner" );
+	result = id( "qunit-testresult" );
+
+	if ( tests ) {
+		tests.innerHTML = "";
+	}
+
+	if ( banner ) {
+		banner.className = "";
+	}
+
+	if ( result ) {
+		result.parentNode.removeChild( result );
+	}
+
+	if ( tests ) {
+		result = document.createElement( "p" );
+		result.id = "qunit-testresult";
+		result.className = "result";
+		tests.parentNode.insertBefore( result, tests );
+		result.innerHTML = "Running...<br />&#160;";
+	}
+};
+
+// Don't load the HTML Reporter on non-Browser environments
+if ( typeof window === "undefined" ) {
+	return;
+}
+
+var config = QUnit.config,
+	hasOwn = Object.prototype.hasOwnProperty,
+	defined = {
+		document: window.document !== undefined,
+		sessionStorage: (function() {
+			var x = "qunit-test-string";
+			try {
+				sessionStorage.setItem( x, x );
+				sessionStorage.removeItem( x );
+				return true;
+			} catch ( e ) {
+				return false;
+			}
+		}())
+	},
+	modulesList = [];
+
+/**
+* Escape text for attribute or text content.
+*/
+function escapeText( s ) {
+	if ( !s ) {
+		return "";
+	}
+	s = s + "";
+
+	// Both single quotes and double quotes (for attributes)
+	return s.replace( /['"<>&]/g, function( s ) {
+		switch ( s ) {
+		case "'":
+			return "&#039;";
+		case "\"":
+			return "&quot;";
+		case "<":
+			return "&lt;";
+		case ">":
+			return "&gt;";
+		case "&":
+			return "&amp;";
+		}
+	});
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+	if ( elem.addEventListener ) {
+
+		// Standards-based browsers
+		elem.addEventListener( type, fn, false );
+	} else if ( elem.attachEvent ) {
+
+		// support: IE <9
+		elem.attachEvent( "on" + type, function() {
+			var event = window.event;
+			if ( !event.target ) {
+				event.target = event.srcElement || document;
+			}
+
+			fn.call( elem, event );
+		});
+	}
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+	var i = elems.length;
+	while ( i-- ) {
+		addEvent( elems[ i ], type, fn );
+	}
+}
+
+function hasClass( elem, name ) {
+	return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
+}
+
+function addClass( elem, name ) {
+	if ( !hasClass( elem, name ) ) {
+		elem.className += ( elem.className ? " " : "" ) + name;
+	}
+}
+
+function toggleClass( elem, name ) {
+	if ( hasClass( elem, name ) ) {
+		removeClass( elem, name );
+	} else {
+		addClass( elem, name );
+	}
+}
+
+function removeClass( elem, name ) {
+	var set = " " + elem.className + " ";
+
+	// Class name may appear multiple times
+	while ( set.indexOf( " " + name + " " ) >= 0 ) {
+		set = set.replace( " " + name + " ", " " );
+	}
+
+	// trim for prettiness
+	elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
+}
+
+function id( name ) {
+	return defined.document && document.getElementById && document.getElementById( name );
+}
+
+function getUrlConfigHtml() {
+	var i, j, val,
+		escaped, escapedTooltip,
+		selection = false,
+		len = config.urlConfig.length,
+		urlConfigHtml = "";
+
+	for ( i = 0; i < len; i++ ) {
+		val = config.urlConfig[ i ];
+		if ( typeof val === "string" ) {
+			val = {
+				id: val,
+				label: val
+			};
+		}
+
+		escaped = escapeText( val.id );
+		escapedTooltip = escapeText( val.tooltip );
+
+		if ( config[ val.id ] === undefined ) {
+			config[ val.id ] = QUnit.urlParams[ val.id ];
+		}
+
+		if ( !val.value || typeof val.value === "string" ) {
+			urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
+				"' name='" + escaped + "' type='checkbox'" +
+				( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
+				( config[ val.id ] ? " checked='checked'" : "" ) +
+				" title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
+				"' title='" + escapedTooltip + "'>" + val.label + "</label>";
+		} else {
+			urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
+				"' title='" + escapedTooltip + "'>" + val.label +
+				": </label><select id='qunit-urlconfig-" + escaped +
+				"' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
+
+			if ( QUnit.is( "array", val.value ) ) {
+				for ( j = 0; j < val.value.length; j++ ) {
+					escaped = escapeText( val.value[ j ] );
+					urlConfigHtml += "<option value='" + escaped + "'" +
+						( config[ val.id ] === val.value[ j ] ?
+							( selection = true ) && " selected='selected'" : "" ) +
+						">" + escaped + "</option>";
+				}
+			} else {
+				for ( j in val.value ) {
+					if ( hasOwn.call( val.value, j ) ) {
+						urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
+							( config[ val.id ] === j ?
+								( selection = true ) && " selected='selected'" : "" ) +
+							">" + escapeText( val.value[ j ] ) + "</option>";
+					}
+				}
+			}
+			if ( config[ val.id ] && !selection ) {
+				escaped = escapeText( config[ val.id ] );
+				urlConfigHtml += "<option value='" + escaped +
+					"' selected='selected' disabled='disabled'>" + escaped + "</option>";
+			}
+			urlConfigHtml += "</select>";
+		}
+	}
+
+	return urlConfigHtml;
+}
+
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+	var updatedUrl, value,
+		field = this,
+		params = {};
+
+	// Detect if field is a select menu or a checkbox
+	if ( "selectedIndex" in field ) {
+		value = field.options[ field.selectedIndex ].value || undefined;
+	} else {
+		value = field.checked ? ( field.defaultValue || true ) : undefined;
+	}
+
+	params[ field.name ] = value;
+	updatedUrl = setUrl( params );
+
+	if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+		config[ field.name ] = value || false;
+		if ( value ) {
+			addClass( id( "qunit-tests" ), "hidepass" );
+		} else {
+			removeClass( id( "qunit-tests" ), "hidepass" );
+		}
+
+		// It is not necessary to refresh the whole page
+		window.history.replaceState( null, "", updatedUrl );
+	} else {
+		window.location = updatedUrl;
+	}
+}
+
+function setUrl( params ) {
+	var key,
+		querystring = "?";
+
+	params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+
+	for ( key in params ) {
+		if ( hasOwn.call( params, key ) ) {
+			if ( params[ key ] === undefined ) {
+				continue;
+			}
+			querystring += encodeURIComponent( key );
+			if ( params[ key ] !== true ) {
+				querystring += "=" + encodeURIComponent( params[ key ] );
+			}
+			querystring += "&";
+		}
+	}
+	return location.protocol + "//" + location.host +
+		location.pathname + querystring.slice( 0, -1 );
+}
+
+function applyUrlParams() {
+	var selectedModule,
+		modulesList = id( "qunit-modulefilter" ),
+		filter = id( "qunit-filter-input" ).value;
+
+	selectedModule = modulesList ?
+		decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
+		undefined;
+
+	window.location = setUrl({
+		module: ( selectedModule === "" ) ? undefined : selectedModule,
+		filter: ( filter === "" ) ? undefined : filter,
+
+		// Remove testId filter
+		testId: undefined
+	});
+}
+
+function toolbarUrlConfigContainer() {
+	var urlConfigContainer = document.createElement( "span" );
+
+	urlConfigContainer.innerHTML = getUrlConfigHtml();
+	addClass( urlConfigContainer, "qunit-url-config" );
+
+	// For oldIE support:
+	// * Add handlers to the individual elements instead of the container
+	// * Use "click" instead of "change" for checkboxes
+	addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+	addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+
+	return urlConfigContainer;
+}
+
+function toolbarLooseFilter() {
+	var filter = document.createElement( "form" ),
+		label = document.createElement( "label" ),
+		input = document.createElement( "input" ),
+		button = document.createElement( "button" );
+
+	addClass( filter, "qunit-filter" );
+
+	label.innerHTML = "Filter: ";
+
+	input.type = "text";
+	input.value = config.filter || "";
+	input.name = "filter";
+	input.id = "qunit-filter-input";
+
+	button.innerHTML = "Go";
+
+	label.appendChild( input );
+
+	filter.appendChild( label );
+	filter.appendChild( button );
+	addEvent( filter, "submit", function( ev ) {
+		applyUrlParams();
+
+		if ( ev && ev.preventDefault ) {
+			ev.preventDefault();
+		}
+
+		return false;
+	});
+
+	return filter;
+}
+
+function toolbarModuleFilterHtml() {
+	var i,
+		moduleFilterHtml = "";
+
+	if ( !modulesList.length ) {
+		return false;
+	}
+
+	modulesList.sort(function( a, b ) {
+		return a.localeCompare( b );
+	});
+
+	moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
+		"<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+		( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
+		">< All Modules ></option>";
+
+	for ( i = 0; i < modulesList.length; i++ ) {
+		moduleFilterHtml += "<option value='" +
+			escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+			( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+			">" + escapeText( modulesList[ i ] ) + "</option>";
+	}
+	moduleFilterHtml += "</select>";
+
+	return moduleFilterHtml;
+}
+
+function toolbarModuleFilter() {
+	var toolbar = id( "qunit-testrunner-toolbar" ),
+		moduleFilter = document.createElement( "span" ),
+		moduleFilterHtml = toolbarModuleFilterHtml();
+
+	if ( !toolbar || !moduleFilterHtml ) {
+		return false;
+	}
+
+	moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+	moduleFilter.innerHTML = moduleFilterHtml;
+
+	addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+
+	toolbar.appendChild( moduleFilter );
+}
+
+function appendToolbar() {
+	var toolbar = id( "qunit-testrunner-toolbar" );
+
+	if ( toolbar ) {
+		toolbar.appendChild( toolbarUrlConfigContainer() );
+		toolbar.appendChild( toolbarLooseFilter() );
+	}
+}
+
+function appendHeader() {
+	var header = id( "qunit-header" );
+
+	if ( header ) {
+		header.innerHTML = "<a href='" +
+			setUrl({ filter: undefined, module: undefined, testId: undefined }) +
+			"'>" + header.innerHTML + "</a> ";
+	}
+}
+
+function appendBanner() {
+	var banner = id( "qunit-banner" );
+
+	if ( banner ) {
+		banner.className = "";
+	}
+}
+
+function appendTestResults() {
+	var tests = id( "qunit-tests" ),
+		result = id( "qunit-testresult" );
+
+	if ( result ) {
+		result.parentNode.removeChild( result );
+	}
+
+	if ( tests ) {
+		tests.innerHTML = "";
+		result = document.createElement( "p" );
+		result.id = "qunit-testresult";
+		result.className = "result";
+		tests.parentNode.insertBefore( result, tests );
+		result.innerHTML = "Running...<br />&#160;";
+	}
+}
+
+function storeFixture() {
+	var fixture = id( "qunit-fixture" );
+	if ( fixture ) {
+		config.fixture = fixture.innerHTML;
+	}
+}
+
+function appendUserAgent() {
+	var userAgent = id( "qunit-userAgent" );
+
+	if ( userAgent ) {
+		userAgent.innerHTML = "";
+		userAgent.appendChild(
+			document.createTextNode(
+				"QUnit " + QUnit.version  + "; " + navigator.userAgent
+			)
+		);
+	}
+}
+
+function appendTestsList( modules ) {
+	var i, l, x, z, test, moduleObj;
+
+	for ( i = 0, l = modules.length; i < l; i++ ) {
+		moduleObj = modules[ i ];
+
+		if ( moduleObj.name ) {
+			modulesList.push( moduleObj.name );
+		}
+
+		for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+			test = moduleObj.tests[ x ];
+
+			appendTest( test.name, test.testId, moduleObj.name );
+		}
+	}
+}
+
+function appendTest( name, testId, moduleName ) {
+	var title, rerunTrigger, testBlock, assertList,
+		tests = id( "qunit-tests" );
+
+	if ( !tests ) {
+		return;
+	}
+
+	title = document.createElement( "strong" );
+	title.innerHTML = getNameHtml( name, moduleName );
+
+	rerunTrigger = document.createElement( "a" );
+	rerunTrigger.innerHTML = "Rerun";
+	rerunTrigger.href = setUrl({ testId: testId });
+
+	testBlock = document.createElement( "li" );
+	testBlock.appendChild( title );
+	testBlock.appendChild( rerunTrigger );
+	testBlock.id = "qunit-test-output-" + testId;
+
+	assertList = document.createElement( "ol" );
+	assertList.className = "qunit-assert-list";
+
+	testBlock.appendChild( assertList );
+
+	tests.appendChild( testBlock );
+}
+
+// HTML Reporter initialization and load
+QUnit.begin(function( details ) {
+	var qunit = id( "qunit" );
+
+	// Fixture is the only one necessary to run without the #qunit element
+	storeFixture();
+
+	if ( qunit ) {
+		qunit.innerHTML =
+			"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+			"<h2 id='qunit-banner'></h2>" +
+			"<div id='qunit-testrunner-toolbar'></div>" +
+			"<h2 id='qunit-userAgent'></h2>" +
+			"<ol id='qunit-tests'></ol>";
+	}
+
+	appendHeader();
+	appendBanner();
+	appendTestResults();
+	appendUserAgent();
+	appendToolbar();
+	appendTestsList( details.modules );
+	toolbarModuleFilter();
+
+	if ( qunit && config.hidepassed ) {
+		addClass( qunit.lastChild, "hidepass" );
+	}
+});
+
+QUnit.done(function( details ) {
+	var i, key,
+		banner = id( "qunit-banner" ),
+		tests = id( "qunit-tests" ),
+		html = [
+			"Tests completed in ",
+			details.runtime,
+			" milliseconds.<br />",
+			"<span class='passed'>",
+			details.passed,
+			"</span> assertions of <span class='total'>",
+			details.total,
+			"</span> passed, <span class='failed'>",
+			details.failed,
+			"</span> failed."
+		].join( "" );
+
+	if ( banner ) {
+		banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+	}
+
+	if ( tests ) {
+		id( "qunit-testresult" ).innerHTML = html;
+	}
+
+	if ( config.altertitle && defined.document && document.title ) {
+
+		// show ✖ for good, ✔ for bad suite result in title
+		// use escape sequences in case file gets loaded with non-utf-8-charset
+		document.title = [
+			( details.failed ? "\u2716" : "\u2714" ),
+			document.title.replace( /^[\u2714\u2716] /i, "" )
+		].join( " " );
+	}
+
+	// clear own sessionStorage items if all tests passed
+	if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
+		for ( i = 0; i < sessionStorage.length; i++ ) {
+			key = sessionStorage.key( i++ );
+			if ( key.indexOf( "qunit-test-" ) === 0 ) {
+				sessionStorage.removeItem( key );
+			}
+		}
+	}
+
+	// scroll back to top to show results
+	if ( config.scrolltop && window.scrollTo ) {
+		window.scrollTo( 0, 0 );
+	}
+});
+
+function getNameHtml( name, module ) {
+	var nameHtml = "";
+
+	if ( module ) {
+		nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
+	}
+
+	nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
+
+	return nameHtml;
+}
+
+QUnit.testStart(function( details ) {
+	var running, testBlock, bad;
+
+	testBlock = id( "qunit-test-output-" + details.testId );
+	if ( testBlock ) {
+		testBlock.className = "running";
+	} else {
+
+		// Report later registered tests
+		appendTest( details.name, details.testId, details.module );
+	}
+
+	running = id( "qunit-testresult" );
+	if ( running ) {
+		bad = QUnit.config.reorder && defined.sessionStorage &&
+			+sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+
+		running.innerHTML = ( bad ?
+			"Rerunning previously failed test: <br />" :
+			"Running: <br />" ) +
+			getNameHtml( details.name, details.module );
+	}
+
+});
+
+QUnit.log(function( details ) {
+	var assertList, assertLi,
+		message, expected, actual,
+		testItem = id( "qunit-test-output-" + details.testId );
+
+	if ( !testItem ) {
+		return;
+	}
+
+	message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
+	message = "<span class='test-message'>" + message + "</span>";
+	message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
+
+	// pushFailure doesn't provide details.expected
+	// when it calls, it's implicit to also not show expected and diff stuff
+	// Also, we need to check details.expected existence, as it can exist and be undefined
+	if ( !details.result && hasOwn.call( details, "expected" ) ) {
+		expected = escapeText( QUnit.dump.parse( details.expected ) );
+		actual = escapeText( QUnit.dump.parse( details.actual ) );
+		message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
+			expected +
+			"</pre></td></tr>";
+
+		if ( actual !== expected ) {
+			message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
+				actual + "</pre></td></tr>" +
+				"<tr class='test-diff'><th>Diff: </th><td><pre>" +
+				QUnit.diff( expected, actual ) + "</pre></td></tr>";
+		} else {
+			if ( expected.indexOf( "[object Array]" ) !== -1 ||
+					expected.indexOf( "[object Object]" ) !== -1 ) {
+				message += "<tr class='test-message'><th>Message: </th><td>" +
+					"Diff suppressed as the depth of object is more than current max depth (" +
+					QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
+					" run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
+					"Rerun</a> without max depth.</p></td></tr>";
+			}
+		}
+
+		if ( details.source ) {
+			message += "<tr class='test-source'><th>Source: </th><td><pre>" +
+				escapeText( details.source ) + "</pre></td></tr>";
+		}
+
+		message += "</table>";
+
+	// this occours when pushFailure is set and we have an extracted stack trace
+	} else if ( !details.result && details.source ) {
+		message += "<table>" +
+			"<tr class='test-source'><th>Source: </th><td><pre>" +
+			escapeText( details.source ) + "</pre></td></tr>" +
+			"</table>";
+	}
+
+	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+	assertLi = document.createElement( "li" );
+	assertLi.className = details.result ? "pass" : "fail";
+	assertLi.innerHTML = message;
+	assertList.appendChild( assertLi );
+});
+
+QUnit.testDone(function( details ) {
+	var testTitle, time, testItem, assertList,
+		good, bad, testCounts, skipped,
+		tests = id( "qunit-tests" );
+
+	if ( !tests ) {
+		return;
+	}
+
+	testItem = id( "qunit-test-output-" + details.testId );
+
+	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+	good = details.passed;
+	bad = details.failed;
+
+	// store result when possible
+	if ( config.reorder && defined.sessionStorage ) {
+		if ( bad ) {
+			sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
+		} else {
+			sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+		}
+	}
+
+	if ( bad === 0 ) {
+		addClass( assertList, "qunit-collapsed" );
+	}
+
+	// testItem.firstChild is the test name
+	testTitle = testItem.firstChild;
+
+	testCounts = bad ?
+		"<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
+		"";
+
+	testTitle.innerHTML += " <b class='counts'>(" + testCounts +
+		details.assertions.length + ")</b>";
+
+	if ( details.skipped ) {
+		testItem.className = "skipped";
+		skipped = document.createElement( "em" );
+		skipped.className = "qunit-skipped-label";
+		skipped.innerHTML = "skipped";
+		testItem.insertBefore( skipped, testTitle );
+	} else {
+		addEvent( testTitle, "click", function() {
+			toggleClass( assertList, "qunit-collapsed" );
+		});
+
+		testItem.className = bad ? "fail" : "pass";
+
+		time = document.createElement( "span" );
+		time.className = "runtime";
+		time.innerHTML = details.runtime + " ms";
+		testItem.insertBefore( time, assertList );
+	}
+});
+
+if ( defined.document ) {
+	if ( document.readyState === "complete" ) {
+		QUnit.load();
+	} else {
+		addEvent( window, "load", QUnit.load );
+	}
+} else {
+	config.pageLoaded = true;
+	config.autorun = true;
+}
+
+})();

+ 17 - 0
test/unit/qunit-utils.js

@@ -0,0 +1,17 @@
+//
+// Custom QUnit assertions.
+//
+
+QUnit.assert.success = function( message ) {
+
+	// Equivalent to assert( true, message );
+	QUnit.assert.push( true, undefined, undefined, message ); 
+
+};
+
+QUnit.assert.fail = function( message ) {
+
+	// Equivalent to assert( false, message );
+	QUnit.assert.push( false, undefined, undefined, message ); 
+
+};

+ 3 - 2
test/unit/unittests_sources.html

@@ -3,11 +3,12 @@
 <head>
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using Files in /src</title>
-  <link rel="stylesheet" href="qunit-1.10.0.css">
+  <link rel="stylesheet" href="qunit-1.18.0.css">
 </head>
 <body>
   <div id="qunit"></div>
-  <script src="qunit-1.10.0.js"></script>
+  <script src="qunit-1.18.0.js"></script>
+  <script src="qunit-utils.js"></script>
 
   <!-- add sources to test below -->
 

+ 3 - 2
test/unit/unittests_three-math.html

@@ -3,11 +3,12 @@
 <head>
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using build/Three-math.js</title>
-  <link rel="stylesheet" href="qunit-1.10.0.css">
+  <link rel="stylesheet" href="qunit-1.18.0.css">
 </head>
 <body>
   <div id="qunit"></div>
-  <script src="qunit-1.10.0.js"></script>
+  <script src="qunit-1.18.0.js"></script>
+  <script src="qunit-utils.js"></script>
 
   <!-- add sources to test below -->
 

+ 5 - 3
test/unit/unittests_three.html

@@ -3,12 +3,13 @@
 <head>
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using build/Three.js</title>
-  <link rel="stylesheet" href="qunit-1.10.0.css">
+  <link rel="stylesheet" href="qunit-1.18.0.css">
 </head>
 <body>
   <div id="qunit"></div>
-  <script src="qunit-1.10.0.js"></script>
-
+  <script src="qunit-1.18.0.js"></script>
+  <script src="qunit-utils.js"></script>
+  
   <!-- add sources to test below -->
 
   <script src="../../build/three.js"></script>
@@ -34,6 +35,7 @@
   <script src="math/Frustum.js"></script>
   
   <script src="geometry/EdgesGeometry.js"></script>
+  <script src="extras/ImageUtils.test.js"></script>
    
   <!-- for debug output -->
   <script src="../../examples/js/controls/OrbitControls.js"></script>

+ 3 - 2
test/unit/unittests_three.min.html

@@ -3,11 +3,12 @@
 <head>
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using build/Three.min.js</title>
-  <link rel="stylesheet" href="qunit-1.10.0.css">
+  <link rel="stylesheet" href="qunit-1.18.0.css">
 </head>
 <body>
   <div id="qunit"></div>
-  <script src="qunit-1.10.0.js"></script>
+  <script src="qunit-1.18.0.js"></script>
+  <script src="qunit-utils.js"></script>
 
   <!-- add sources to test below -->
 

Неке датотеке нису приказане због велике количине промена