Browse Source

Merge branch 'dev' into drawcalls

Conflicts:
	examples/js/loaders/AWDLoader.js
dubejf 10 years ago
parent
commit
10049e7c02
97 changed files with 8962 additions and 6389 deletions
  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

File diff suppressed because it is too large
+ 519 - 522
build/three.js


File diff suppressed because it is too large
+ 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.
 		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>
 		</div>
 
 
+		<h3>[property:Integer version]</h3>
+		<div>
+		A version number, incremented every time the needsUpdate property is set to true.
+		</div>
 
 
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
-		
+
 		<h3>[method:null copyAt] ( [page:Integer index1], attribute, [page:Integer index2] ) </h3>
 		<h3>[method:null copyAt] ( [page:Integer index1], attribute, [page:Integer index2] ) </h3>
 		<div>
 		<div>
 		Copies itemSize values in the array from the vertex at index2 to the vertex at index1.
 		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>
 		<h2>Constructor</h2>
 
 
-		<h3>[name]( [page:Boolean showStatus] )</h3>
-		<div>
-		[page:Boolean showStatus] — Show the status of loading div.
-		</div>
+		<h3>[name]()</h3>
 		<div>
 		<div>
 		Creates a new [name].
 		Creates a new [name].
 		</div>
 		</div>
@@ -32,29 +29,6 @@
 		</div>
 		</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>
 		<h2>Methods</h2>
 
 
 		<h3>[method:null load]( [page:String url], [page:Function callback], [page:String texturePath] )</h3>
 		<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].
 		Parse a <em>JSON</em> structure and return an [page:Object] containing the parsed .[page:Geometry] and .[page:Array materials].
 		</div>
 		</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>
 		<h2>Example</h2>
 
 

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

@@ -15,10 +15,7 @@
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>
 
 
 
 
-		<h3>[name]( [page:Boolean showStatus] )</h3>
-		<div>
-		[page:Boolean showStatus] — Show the status of loading div.
-		</div>
+		<h3>[name]()</h3>
 		<div>
 		<div>
 		Creates a new [name]. This should be called as base class.
 		Creates a new [name]. This should be called as base class.
 		</div>
 		</div>
@@ -26,12 +23,6 @@
 
 
 		<h2>Properties</h2>
 		<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>
 		<h3>[property:Function onLoadStart]</h3>
 		<div>Will be called when load starts.</div>
 		<div>Will be called when load starts.</div>
 		<div>The default is a function with empty body.</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.
 		Checks if the loaded object needs tangents based on its materials.
 		</div>
 		</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>
 		<h3>[method:Material createMaterial]( [page:object m], [page:string texturePath] )</h3>
 		<div>
 		<div>
 		[page:Object m] — The parameters to create the material. <br />
 		[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>
 		<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>
 		<h3>[method:null setTexture]( [page:Texture texture], [page:number slot] )</h3>
 		<div>
 		<div>
 		texture -- The [page:Texture texture] that needs to be set.<br />
 		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": [
 		"Renderers / WebGL": [
 			[ "WebGLProgram", "api/renderers/webgl/WebGLProgram" ],
 			[ "WebGLProgram", "api/renderers/webgl/WebGLProgram" ],
-			[ "WebGLShader", "api/renderers/webgl/WebGLShader" ]
+			[ "WebGLShader", "api/renderers/webgl/WebGLShader" ],
+			[ "WebGLState", "api/renderers/webgl/WebGLState" ]
 		],
 		],
 
 
 		"Renderers / WebGL / Plugins": [
 		"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/codemirror/mode/javascript.js"></script>
 		<script src="js/libs/esprima.js"></script>
 		<script src="js/libs/esprima.js"></script>
 		<script src="js/libs/jsonlint.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>
 		<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 );
 	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;
 	return container;
 
 
 };
 };

+ 96 - 33
editor/js/Script.js

@@ -42,6 +42,16 @@ var Script = function ( editor ) {
 	} );
 	} );
 	header.add( close );
 	header.add( close );
 
 
+
+	var renderer;
+
+	signals.rendererChanged.add( function ( newRenderer ) {
+
+		renderer = newRenderer;
+
+	} );
+
+
 	var delay;
 	var delay;
 	var currentMode;
 	var currentMode;
 	var currentScript;
 	var currentScript;
@@ -75,31 +85,17 @@ var Script = function ( editor ) {
 				return;
 				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;
 			currentObject.needsUpdate = true;
 			signals.materialChanged.dispatch( currentObject );
 			signals.materialChanged.dispatch( currentObject );
 
 
-		}, 200 );
+		}, 300 );
 
 
 	});
 	});
 
 
@@ -118,7 +114,8 @@ var Script = function ( editor ) {
 
 
 	var validate = function ( string ) {
 	var validate = function ( string ) {
 
 
-		var errors;
+		var valid;
+		var errors = [];
 
 
 		return codemirror.operation( function () {
 		return codemirror.operation( function () {
 
 
@@ -147,10 +144,12 @@ var Script = function ( editor ) {
 
 
 					} catch ( error ) {
 					} 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];
 						message = message.split('\n')[3];
 
 
-						errors.push({
-							lineNumber: info.loc.first_line,
+						errors.push( {
+
+							lineNumber: info.loc.first_line - 1,
 							message: message
 							message: message
-						});
+
+						} );
 
 
 					};
 					};
 
 
@@ -192,13 +193,75 @@ var Script = function ( editor ) {
 
 
 				case 'glsl':
 				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 ++ ) {
 			for ( var i = 0; i < errors.length; i ++ ) {
 
 
@@ -208,7 +271,7 @@ var Script = function ( editor ) {
 				message.className = 'esprima-error';
 				message.className = 'esprima-error';
 				message.textContent = error.message;
 				message.textContent = error.message;
 
 
-				var lineNumber = error.lineNumber - 1;
+				var lineNumber = Math.max( error.lineNumber, 0 );
 				errorLines.push( lineNumber );
 				errorLines.push( lineNumber );
 
 
 				codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
 				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 () {
 	var rendererAntialias = new UI.Checkbox( editor.config.getKey( 'project/renderer/antialias' ) ).setLeft( '100px' ).onChange( function () {
 
 
 		editor.config.setKey( 'project/renderer/antialias', this.getValue() );
 		editor.config.setKey( 'project/renderer/antialias', this.getValue() );
-		// updateRenderer();
+		updateRenderer();
 
 
 	} );
 	} );
 
 
@@ -92,10 +92,25 @@ Sidebar.Project = function ( editor ) {
 
 
 	function updateRenderer() {
 	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;
 	return container;
 
 
 }
 }

+ 15 - 24
editor/js/Viewport.js

@@ -232,6 +232,8 @@ var Viewport = function ( editor ) {
 
 
 	} );
 	} );
 
 
+	var clearColor;
+
 	signals.themeChanged.add( function ( value ) {
 	signals.themeChanged.add( function ( value ) {
 
 
 		switch ( 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 );
 		renderer.setSize( container.dom.offsetWidth, container.dom.offsetHeight );
 
 
 		container.dom.appendChild( renderer.domElement );
 		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();
 	animate();
 
 

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

@@ -33,6 +33,9 @@ var APP = {
 			this.setCamera( loader.parse( json.camera ) );
 			this.setCamera( loader.parse( json.camera ) );
 
 
 			events = {
 			events = {
+				init: [],
+				start: [],
+				stop: [],
 				keydown: [],
 				keydown: [],
 				keyup: [],
 				keyup: [],
 				mousedown: [],
 				mousedown: [],
@@ -44,6 +47,15 @@ var APP = {
 				update: []
 				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 ) {
 			for ( var uuid in json.scripts ) {
 
 
 				var object = scene.getObjectByProperty( 'uuid', uuid, true );
 				var object = scene.getObjectByProperty( 'uuid', uuid, true );
@@ -54,7 +66,8 @@ var APP = {
 
 
 					var script = scripts[ i ];
 					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 ) {
 					for ( var name in functions ) {
 
 
@@ -75,6 +88,8 @@ var APP = {
 
 
 			}
 			}
 
 
+			dispatch( events.init, arguments );
+
 		};
 		};
 
 
 		this.setCamera = function ( value ) {
 		this.setCamera = function ( value ) {
@@ -146,7 +161,15 @@ var APP = {
 
 
 			for ( var i = 0, l = array.length; i < l; i ++ ) {
 			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( 'touchend', onDocumentTouchEnd );
 			document.addEventListener( 'touchmove', onDocumentTouchMove );
 			document.addEventListener( 'touchmove', onDocumentTouchMove );
 
 
+			dispatch( events.start, arguments );
+
 			request = requestAnimationFrame( animate );
 			request = requestAnimationFrame( animate );
 			prevTime = performance.now();
 			prevTime = performance.now();
-
 		};
 		};
 
 
 		this.stop = function () {
 		this.stop = function () {
@@ -202,8 +226,9 @@ var APP = {
 			document.removeEventListener( 'touchend', onDocumentTouchEnd );
 			document.removeEventListener( 'touchend', onDocumentTouchEnd );
 			document.removeEventListener( 'touchmove', onDocumentTouchMove );
 			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();
 				light.position.set( -1, -1, -1 ).normalize();
 				scene.add( light );
 				scene.add( light );
 
 
-				var loader = new THREE.JSONLoader( true );
+				var loader = new THREE.JSONLoader();
 				loader.load( "models/animated/horse.js", function ( geometry ) {
 				loader.load( "models/animated/horse.js", function ( geometry ) {
 
 
 					mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x606060, morphTargets: true, overdraw: 0.5 } ) );
 					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_godrays",
 				"webgl_postprocessing_crossfade",
 				"webgl_postprocessing_crossfade",
 				"webgl_postprocessing_glitch",
 				"webgl_postprocessing_glitch",
+				"webgl_postprocessing_ssao",
 				"webgl_rtt",
 				"webgl_rtt",
 				"webgl_sandbox",
 				"webgl_sandbox",
 				"webgl_shader",
 				"webgl_shader",

+ 3 - 3
examples/js/MarchingCubes.js

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

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

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

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

@@ -3,98 +3,79 @@
  * Date: 09/12/2013 17:21
  * Date: 09/12/2013 17:21
  */
  */
 
 
-THREE.AWDLoader = (function () {
-
-
+(function (){
 
 
 	var UNCOMPRESSED  = 0,
 	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;
 	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.id = 0;
 		this.data = null;
 		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.trunk = new THREE.Object3D();
 
 
 		this.materialFactory = undefined;
 		this.materialFactory = undefined;
 
 
-		this._resourceLoader = null;
-		this._url = '';
+		this._url     = '';
+		this._baseDir = '';
 
 
 		this._data;
 		this._data;
 		this._ptr = 0;
 		this._ptr = 0;
@@ -105,1118 +86,1031 @@ THREE.AWDLoader = (function () {
 		this._compression = 0;
 		this._compression = 0;
 		this._bodylen = 0xFFFFFFFF;
 		this._bodylen = 0xFFFFFFFF;
 
 
-
 		this._blocks = [ new Block() ];
 		this._blocks = [ new Block() ];
 
 
 		this._accuracyMatrix  = false;
 		this._accuracyMatrix  = false;
 		this._accuracyGeo     = false;
 		this._accuracyGeo     = false;
 		this._accuracyProps   = 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.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();
 			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 {
 				} 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,
 	constructor: THREE.AssimpJSONLoader,
 
 
-	texturePath : '',
-
-	load: function ( url, onLoad, onProgress, onError, texturePath ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 
 		var scope = this;
 		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 );
 		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.setCrossOrigin( this.crossOrigin );
@@ -60,6 +58,10 @@ THREE.AssimpJSONLoader.prototype = {
 		this.crossOrigin = value;
 		this.crossOrigin = value;
 	},
 	},
 
 
+	setTexturePath: function ( value ) {
+		this.texturePath = value;
+	},
+
 	extractUrlBase: function ( url ) { // from three/src/loaders/Loader.js
 	extractUrlBase: function ( url ) { // from three/src/loaders/Loader.js
 		var parts = url.split( '/' );
 		var parts = url.split( '/' );
 		parts.pop();
 		parts.pop();

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

@@ -2,751 +2,694 @@
  * @author alteredq / http://alteredqualia.com/
  * @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 );
+	}
 
 
 };
 };

File diff suppressed because it is too large
+ 457 - 404
examples/js/loaders/ColladaLoader.js


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

@@ -4,11 +4,9 @@
  * @author angelxuanchang
  * @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 scope = this;
 
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.load( url, function ( text ) {
 		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
 	 * Parses loaded MTL file
 	 * @param text - Content of 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 );
 		materialCreator.setMaterials( materialsInfo );
 		return materialCreator;
 		return materialCreator;
 
 
@@ -130,6 +147,18 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 
 	constructor: THREE.MTLLoader.MaterialCreator,
 	constructor: THREE.MTLLoader.MaterialCreator,
 
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
+	setManager: function ( value ) {
+
+		this.manager = value;
+
+	},
+
 	setMaterials: function( materialsInfo ) {
 	setMaterials: function( materialsInfo ) {
 
 
 		this.materialsInfo = this.convert( 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 texture;
 		var loader = THREE.Loader.Handlers.get( url );
 		var loader = THREE.Loader.Handlers.get( url );
+		var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
 
 
 		if ( loader !== null ) {
 		if ( loader !== null ) {
 
 
@@ -385,8 +415,8 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 
 			texture = new THREE.Texture();
 			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 ) {
 			loader.load( url, function ( image ) {
 
 
 				texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image );
 				texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image );
@@ -394,7 +424,7 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 
 				if ( onLoad ) onLoad( texture );
 				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 ) {
 	parse: function ( text ) {
 
 
 		console.time( 'OBJLoader' );
 		console.time( 'OBJLoader' );

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

@@ -19,8 +19,9 @@ THREE.OBJMTLLoader.prototype = {
 
 
 		var scope = this;
 		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 ) {
 		mtlLoader.load( mtlurl, function ( materials ) {
 
 
 			var materialsCreator = materials;
 			var materialsCreator = materials;
@@ -56,6 +57,12 @@ THREE.OBJMTLLoader.prototype = {
 
 
 	},
 	},
 
 
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
 	/**
 	/**
 	 * Parses loaded .obj file
 	 * Parses loaded .obj file
 	 * @param data - content of .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
 	// Based on CanvasMol PDB parser
 
 
 	parsePDB: function ( text ) {
 	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 = {};
 	this.propertyNameMapping = {};
 
 
@@ -37,42 +39,30 @@ THREE.PLYLoader.prototype = {
 
 
 	constructor: THREE.PLYLoader,
 	constructor: THREE.PLYLoader,
 
 
-	setPropertyNameMapping: function ( mapping ) {
-
-		this.propertyNameMapping = mapping;
-
-	},
-
-	load: function ( url, callback ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 
 		var scope = this;
 		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
  *   TODO : implement loadMipmaps option
  */
  */
 
 
+THREE.PVRLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 
 
-THREE.PVRLoader = function () {
 	this._parser = THREE.PVRLoader.parse;
 	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 );
 		var loader = new THREE.XHRLoader( scope.manager );
 		loader.setCrossOrigin( this.crossOrigin );
 		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 ) );
 			onLoad( scope.parse( text ) );
 
 
 		}, onProgress, onError );
 		}, onProgress, onError );
 
 
-		return request;
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
 	},
 	},
 
 
 	parse: function ( data ) {
 	parse: function ( data ) {

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

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

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

@@ -2,7 +2,11 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-THREE.VRMLLoader = function () {};
+THREE.VRMLLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
 
 
 THREE.VRMLLoader.prototype = {
 THREE.VRMLLoader.prototype = {
 
 
@@ -22,35 +26,23 @@ THREE.VRMLLoader.prototype = {
 
 
 	recordingFieldname: null,
 	recordingFieldname: null,
 
 
-	load: function ( url, callback ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 
 		var scope = this;
 		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 ) {
 	parse: function ( data ) {
 
 
 		var indices = [];
 		var indices = [];

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

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

File diff suppressed because it is too large
+ 149 - 149
examples/js/loaders/gltf/glTFLoader.js


+ 1 - 1
examples/misc_controls_orbit.html

@@ -77,7 +77,7 @@
 				// world
 				// world
 
 
 				var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
 				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 ++ ) {
 				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 );
 				scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );
 
 
 				var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
 				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 ++ ) {
 				for ( var i = 0; i < 500; i ++ ) {
 
 

+ 1 - 1
examples/misc_controls_transform.html

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

+ 17 - 15
examples/webgl_animation_cloth.html

@@ -128,7 +128,7 @@
 
 
 			var clothGeometry;
 			var clothGeometry;
 			var sphere;
 			var sphere;
-			var object, arrow;
+			var object;
 
 
 			var rotate = true;
 			var rotate = true;
 
 
@@ -143,7 +143,6 @@
 				// scene
 				// scene
 
 
 				scene = new THREE.Scene();
 				scene = new THREE.Scene();
-
 				scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 );
 				scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 );
 
 
 				// camera
 				// camera
@@ -164,7 +163,7 @@
 				light.position.multiplyScalar( 1.3 );
 				light.position.multiplyScalar( 1.3 );
 
 
 				light.castShadow = true;
 				light.castShadow = true;
-				//light.shadowCameraVisible = true;
+				// light.shadowCameraVisible = true;
 
 
 				light.shadowMapWidth = 1024;
 				light.shadowMapWidth = 1024;
 				light.shadowMapHeight = 1024;
 				light.shadowMapHeight = 1024;
@@ -187,7 +186,14 @@
 				clothTexture.wrapS = clothTexture.wrapT = THREE.RepeatWrapping;
 				clothTexture.wrapS = clothTexture.wrapT = THREE.RepeatWrapping;
 				clothTexture.anisotropy = 16;
 				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
 				// cloth geometry
 				clothGeometry = new THREE.ParametricGeometry( clothFunction, cloth.w, cloth.h );
 				clothGeometry = new THREE.ParametricGeometry( clothFunction, cloth.w, cloth.h );
@@ -202,27 +208,25 @@
 				object = new THREE.Mesh( clothGeometry, clothMaterial );
 				object = new THREE.Mesh( clothGeometry, clothMaterial );
 				object.position.set( 0, 0, 0 );
 				object.position.set( 0, 0, 0 );
 				object.castShadow = true;
 				object.castShadow = true;
-				object.receiveShadow = true;
 				scene.add( object );
 				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
 				// sphere
 
 
 				var ballGeo = new THREE.SphereGeometry( ballSize, 20, 20 );
 				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 = new THREE.Mesh( ballGeo, ballMaterial );
 				sphere.castShadow = true;
 				sphere.castShadow = true;
 				sphere.receiveShadow = true;
 				sphere.receiveShadow = true;
 				scene.add( sphere );
 				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
 				// ground
 
 
 				var groundTexture = THREE.ImageUtils.loadTexture( "textures/terrain/grasslight-big.jpg" );
 				var groundTexture = THREE.ImageUtils.loadTexture( "textures/terrain/grasslight-big.jpg" );
@@ -327,8 +331,6 @@
 
 
 				windStrength = Math.cos( time / 7000 ) * 20 + 40;
 				windStrength = Math.cos( time / 7000 ) * 20 + 40;
 				windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) ).normalize().multiplyScalar( windStrength );
 				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);
 				simulate(time);
 				render();
 				render();

+ 4 - 4
examples/webgl_buffergeometry_instancing.html

@@ -145,7 +145,7 @@
 
 
             var offsets = new THREE.InstancedBufferAttribute( new Float32Array( instances * 3 ), 3, 1, false );
             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 );
                 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 );
             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() );
                 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 );
             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.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
                 vector.normalize();
                 vector.normalize();
@@ -180,7 +180,7 @@
 
 
             var orientationsEnd = new THREE.InstancedBufferAttribute( new Float32Array( instances * 4 ), 4, 1, false );
             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.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
                 vector.normalize();
                 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 offsets = new THREE.InstancedBufferAttribute( new Float32Array( instances * 3 ), 3, 1, false );
 
 
             var vector = new THREE.Vector4();
             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 x = Math.random() * 100 - 50;
                 var y = Math.random() * 100 - 50;
                 var y = Math.random() * 100 - 50;
                 var z = 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 );
             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.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
                 vector.normalize();
                 vector.normalize();
@@ -318,7 +318,7 @@
             var delta = ( time - lastTime ) / 5000;
             var delta = ( time - lastTime ) / 5000;
             tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize();
             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;
                 var index = i * 4;
                 currentQ.set( orientations.array[index], orientations.array[index + 1], orientations.array[index + 2], orientations.array[index + 3] );
                 currentQ.set( orientations.array[index], orientations.array[index + 1], orientations.array[index + 2], orientations.array[index + 3] );
                 currentQ.multiply( tmpQ );
                 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 offsets = new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 );
 
 
         var vector = new THREE.Vector4();
         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 x = Math.random() * 100 - 50;
             var y = Math.random() * 100 - 50;
             var y = Math.random() * 100 - 50;
             var z = Math.random() * 100 - 50;
             var z = Math.random() * 100 - 50;
@@ -199,7 +199,7 @@
 
 
         orientations = new THREE.InterleavedBufferAttribute( instanceBuffer, 4, 4 );
         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.set( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
             vector.normalize();
             vector.normalize();
@@ -290,7 +290,7 @@
         var delta = ( time - lastTime ) / 5000;
         var delta = ( time - lastTime ) / 5000;
         tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize();
         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;
             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.set( instanceBuffer.array[index], instanceBuffer.array[index + 1], instanceBuffer.array[index + 2], instanceBuffer.array[index + 3] );
             currentQ.multiply( tmpQ );
             currentQ.multiply( tmpQ );

+ 1 - 8
examples/webgl_effects_parallaxbarrier.html

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

+ 1 - 4
examples/webgl_geometry_large_mesh.html

@@ -162,8 +162,7 @@
 				bcanvas.addEventListener( "click", toggleCanvas, false );
 				bcanvas.addEventListener( "click", toggleCanvas, false );
 				bwebgl.addEventListener( "click", toggleWebGL, false );
 				bwebgl.addEventListener( "click", toggleWebGL, false );
 
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
+				loader = new THREE.BinaryLoader();
 
 
 				var start = Date.now();
 				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, -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 } ) );
 					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.vertices: " + geometry.vertices.length );
 					log( "geometry.faces: " + geometry.faces.length );
 					log( "geometry.faces: " + geometry.faces.length );
 
 

+ 14 - 5
examples/webgl_interactive_draggablecubes.html

@@ -162,7 +162,13 @@
 				if ( SELECTED ) {
 				if ( SELECTED ) {
 
 
 					var intersects = raycaster.intersectObject( plane );
 					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;
 					return;
 
 
 				}
 				}
@@ -201,9 +207,7 @@
 
 
 				event.preventDefault();
 				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 );
 				var intersects = raycaster.intersectObjects( objects );
 
 
@@ -214,7 +218,12 @@
 					SELECTED = intersects[ 0 ].object;
 					SELECTED = intersects[ 0 ].object;
 
 
 					var intersects = raycaster.intersectObject( plane );
 					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';
 					container.style.cursor = 'move';
 
 

+ 1 - 4
examples/webgl_lights_pointlights.html

@@ -68,8 +68,7 @@
 
 
 				scene = new THREE.Scene();
 				scene = new THREE.Scene();
 
 
-				loader = new THREE.BinaryLoader( true );
-				document.body.appendChild( loader.statusDomElement );
+				loader = new THREE.BinaryLoader();
 
 
 				var callback = function( geometry ) {
 				var callback = function( geometry ) {
 
 
@@ -77,8 +76,6 @@
 					object.scale.x = object.scale.y = object.scale.z = 0.80;
 					object.scale.x = object.scale.y = object.scale.z = 0.80;
 					scene.add( object );
 					scene.add( object );
 
 
-					loader.statusDomElement.style.display = "none";
-
 				};
 				};
 
 
 				loader.load( "obj/walt/WaltHead_bin.js", callback );
 				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
 				// 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 position = new THREE.Vector3( -105, -78, -40 );
 				var scale = new THREE.Vector3( 30, 30, 30 );
 				var scale = new THREE.Vector3( 30, 30, 30 );
@@ -197,8 +196,6 @@
 
 
 					}
 					}
 
 
-					loaderCTM.statusDomElement.style.display = "none";
-
 					var end = Date.now();
 					var end = Date.now();
 
 
 					console.log( "load time:", end - start, "ms" );
 					console.log( "load time:", end - start, "ms" );
@@ -269,10 +266,8 @@
 
 
 			function createScene( geometry, materials, x, y, z, s ) {
 			function createScene( geometry, materials, x, y, z, s ) {
 
 
-				loader.statusDomElement.style.display = "none";
-
 				geometry.center();
 				geometry.center();
-				
+
 				hackMaterials( materials );
 				hackMaterials( materials );
 
 
 				var material = new THREE.MeshFaceMaterial( materials );
 				var material = new THREE.MeshFaceMaterial( materials );

+ 1 - 3
examples/webgl_loader_ply.html

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

+ 2 - 3
examples/webgl_loader_vrml.html

@@ -79,12 +79,11 @@
 				camera.add( dirLight.target );
 				camera.add( dirLight.target );
 
 
 				var loader = new THREE.VRMLLoader();
 				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
 				// 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 } );
 				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 ) } );
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100, material ) } );
 
 
 				renderer = new THREE.WebGLRenderer( { antialias: false } );
 				renderer = new THREE.WebGLRenderer( { antialias: false } );
@@ -218,8 +216,6 @@
 
 
 				scene.add( mesh );
 				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 ) } );
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100 ) } );
 
 
 				//
 				//
@@ -294,8 +292,6 @@
 
 
 				scene.add( mesh );
 				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 windowHalfX = window.innerWidth / 2;
 			var windowHalfY = window.innerHeight / 2;
 			var windowHalfY = window.innerHeight / 2;
 
 
-			var loader = new THREE.BinaryLoader( true );
-			document.body.appendChild( loader.statusDomElement );
+			var loader = new THREE.BinaryLoader();
 
 
 			init();
 			init();
 			animate();
 			animate();
@@ -494,7 +493,6 @@
 
 
 					if ( ! CARS[ car ].object ) {
 					if ( ! CARS[ car ].object ) {
 
 
-						loader.statusDomElement.style.display = "block";
 						loader.load( CARS[ car ].url, function( geometry ) { createScene( geometry, car ) } );
 						loader.load( CARS[ car ].url, function( geometry ) { createScene( geometry, car ) } );
 
 
 					} else {
 					} else {
@@ -570,8 +568,6 @@
 
 
 			function createScene( geometry, car ) {
 			function createScene( geometry, car ) {
 
 
-				loader.statusDomElement.innerHTML = "Creating model ...";
-
 				var m = new THREE.MeshFaceMaterial(),
 				var m = new THREE.MeshFaceMaterial(),
 					s = CARS[ car ].scale * 1,
 					s = CARS[ car ].scale * 1,
 					r = CARS[ car ].init_rotation,
 					r = CARS[ car ].init_rotation,
@@ -602,9 +598,6 @@
 
 
 				switchCar( car );
 				switchCar( car );
 
 
-				loader.statusDomElement.style.display = "none";
-				loader.statusDomElement.innerHTML = "Loading model ...";
-
 			}
 			}
 
 
 			function onDocumentMouseMove(event) {
 			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 ) } );
 				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;
 				mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
 				scene.add( mesh );
 				scene.add( mesh );
 
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 			}
 
 
 			function onDocumentMouseMove(event) {
 			function onDocumentMouseMove(event) {

+ 1 - 5
examples/webgl_materials_cubemap_refraction.html

@@ -140,9 +140,7 @@
 				stats.domElement.style.zIndex = 100;
 				stats.domElement.style.zIndex = 100;
 				container.appendChild( stats.domElement );
 				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 ) } );
 				loader.load( 'obj/lucy/Lucy100k_bin.js', function( geometry ) { createScene( geometry, cubeMaterial1, cubeMaterial2, cubeMaterial3 ) } );
 
 
 				document.addEventListener('mousemove', onDocumentMouseMove, false);
 				document.addEventListener('mousemove', onDocumentMouseMove, false);
@@ -189,8 +187,6 @@
 				mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
 				mesh.scale.x = mesh.scale.y = mesh.scale.z = s;
 				scene.add( mesh );
 				scene.add( mesh );
 
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 			}
 
 
 			function onDocumentMouseMove(event) {
 			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 ) } );
 				loader.load( "obj/ninja/NinjaLo_bin.js", function( geometry ) { createScene( geometry, scale, material1, material2 ) } );
 
 
 				//
 				//
@@ -272,8 +270,6 @@
 				mesh2.receiveShadow = true;
 				mesh2.receiveShadow = true;
 				scene.add( mesh2 );
 				scene.add( mesh2 );
 
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 			}
 
 
 			function onDocumentMouseMove(event) {
 			function onDocumentMouseMove(event) {

+ 1 - 5
examples/webgl_materials_normalmap.html

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

+ 1 - 5
examples/webgl_materials_skin.html

@@ -148,9 +148,7 @@
 
 
 				// LOADER
 				// 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 ) } );
 				loader.load(  "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createScene( geometry, 100, material ) } );
 
 
 				// RENDERER
 				// RENDERER
@@ -283,8 +281,6 @@
 
 
 				scene.add( mesh );
 				scene.add( mesh );
 
 
-				loader.statusDomElement.style.display = "none";
-
 			}
 			}
 
 
 			function onDocumentMouseMove( event ) {
 			function onDocumentMouseMove( event ) {

+ 1 - 1
examples/webgl_morphtargets_horse.html

@@ -59,7 +59,7 @@
 				light.position.set( -1, -1, -1 ).normalize();
 				light.position.set( -1, -1, -1 ).normalize();
 				scene.add( light );
 				scene.add( light );
 
 
-				var loader = new THREE.JSONLoader( true );
+				var loader = new THREE.JSONLoader();
 				loader.load( "models/animated/horse.js", function( geometry ) {
 				loader.load( "models/animated/horse.js", function( geometry ) {
 
 
 					mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x606060, morphTargets: true } ) );
 					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() {
 			function animate() {
 
 
+				updateScene();
+
 				for ( var i = 0; i < apps.length; ++i ) {
 				for ( var i = 0; i < apps.length; ++i ) {
 
 
 					apps[ i ].animate();
 					apps[ i ].animate();
@@ -223,7 +225,10 @@
 				group2.rotation.x = 0;
 				group2.rotation.x = 0;
 				scene.add( group2 );
 				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.position.x = 0;
 				group3.rotation.x = 0;
 				group3.rotation.x = 0;
 				scene.add( group3 );
 				scene.add( group3 );
@@ -231,6 +236,24 @@
 				return scene;
 				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 ) {
 			function App( container, fullWidth, fullHeight ) {
 
 
 				var container, stats;
 				var container, stats;

+ 1 - 1
examples/webgl_nearestneighbour.html

@@ -70,7 +70,7 @@
 		<script>		
 		<script>		
 		
 		
 			var camera, scene, renderer;
 			var camera, scene, renderer;
-			var geometry, material, mesh;
+			var geometry, mesh;
 			var controls;
 			var controls;
 
 
 			var objects = [];
 			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 ) {
 				aloader.load( "obj/terrain.js", function( geometry ) {
 
 
 					createMesh( geometry, scene, 16.8, -11000, -200,  -5000, 0x00ff44, false );
 					createMesh( geometry, scene, 16.8, -11000, -200,  -5000, 0x00ff44, false );
@@ -265,8 +262,6 @@
 
 
 				}
 				}
 
 
-				bloader.statusDomElement.style.display = "none";
-
 				meshes.push( {
 				meshes.push( {
 					mesh: mesh, vertices: geometry.vertices, vertices_tmp: vertices_tmp, vl: vl,
 					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() ),
 					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();
 				directionalLight.position.set( 0, -0.1, 1 ).normalize();
 				sceneModel.add( directionalLight );
 				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 ) } );
 				loader.load( "obj/leeperrysmith/LeePerrySmith.js", function( geometry ) { createMesh( geometry, sceneModel, 100 ) } );
 
 
 				//
 				//
@@ -349,8 +348,6 @@
 
 
 				scene.add( mesh );
 				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
 // 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 ) {
 if ( Math.sign === undefined ) {
 
 
 	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign
 	// 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.array = array;
 	this.itemSize = itemSize;
 	this.itemSize = itemSize;
 
 
-	this.needsUpdate = false;
+	this.version = 0;
 
 
 };
 };
 
 
@@ -19,8 +19,8 @@ THREE.BufferAttribute.prototype = {
 
 
 	get length () {
 	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 ) {
 	copyAt: function ( index1, attribute, index2 ) {
 
 
 		index1 *= this.itemSize;
 		index1 *= this.itemSize;

+ 2 - 2
src/core/BufferGeometry.js

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

+ 13 - 1
src/core/InterleavedBuffer.js

@@ -9,7 +9,7 @@ THREE.InterleavedBuffer = function ( array, stride, dynamic ) {
 	this.array = array;
 	this.array = array;
 	this.stride = stride;
 	this.stride = stride;
 
 
-	this.needsUpdate = false;
+	this.version = 0;
 
 
 	this.dynamic = dynamic || false;
 	this.dynamic = dynamic || false;
 	this.updateRange = { offset: 0, count: -1 };
 	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 ) {
 	copyAt: function ( index1, attribute, index2 ) {
 
 
 		index1 *= this.stride;
 		index1 *= this.stride;

+ 2 - 2
src/core/InterleavedBufferAttribute.js

@@ -19,8 +19,8 @@ THREE.InterleavedBufferAttribute.prototype = {
 
 
 	get length() {
 	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 thresholdDot = Math.cos( THREE.Math.degToRad( thresholdAngle ) );
 
 
 	var edge = [ 0, 0 ], hash = {};
 	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' ];
 	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;
 	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 );
 	heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );
 
 
 	phiStart = phiStart !== undefined ? phiStart : 0;
 	phiStart = phiStart !== undefined ? phiStart : 0;
@@ -30,10 +30,12 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 	thetaStart = thetaStart !== undefined ? thetaStart : 0;
 	thetaStart = thetaStart !== undefined ? thetaStart : 0;
 	thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;
 	thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;
 
 
+	var thetaEnd = thetaStart + thetaLength;
+
 	var vertexCount = ( ( widthSegments + 1 ) * ( heightSegments + 1 ) );
 	var vertexCount = ( ( widthSegments + 1 ) * ( heightSegments + 1 ) );
 
 
 	var positions = new THREE.BufferAttribute( new Float32Array( vertexCount * 3 ), 3 );
 	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 uvs = new THREE.BufferAttribute( new Float32Array( vertexCount * 2 ), 2 );
 
 
 	var index = 0, vertices = [], normal = new THREE.Vector3();
 	var index = 0, vertices = [], normal = new THREE.Vector3();
@@ -60,7 +62,7 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 
 
 			verticesRow.push( index );
 			verticesRow.push( index );
 
 
-			index++;
+			index ++;
 
 
 		}
 		}
 
 
@@ -74,13 +76,13 @@ THREE.SphereBufferGeometry = function ( radius, widthSegments, heightSegments, p
 
 
 		for ( var x = 0; x < widthSegments; x ++ ) {
 		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 ];
 			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;
 	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 );
 	heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );
 
 
 	phiStart = phiStart !== undefined ? phiStart : 0;
 	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, ...)
  * 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
 	// override in sub classes
 	this._parser = null;
 	this._parser = null;
@@ -21,7 +23,8 @@ THREE.BinaryTextureLoader.prototype = {
 
 
 		var texture = new THREE.DataTexture( );
 		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.setResponseType( 'arraybuffer' );
 
 
 		loader.load( url, function ( buffer ) {
 		loader.load( url, function ( buffer ) {
@@ -82,6 +85,12 @@ THREE.BinaryTextureLoader.prototype = {
 
 
 		return texture;
 		return texture;
 
 
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
 	}
 	}
 
 
 };
 };

+ 1 - 1
src/loaders/Cache.js

@@ -30,7 +30,7 @@ THREE.Cache = {
 
 
 	clear: function () {
 	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, ...)
  * 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
 	// override in sub classes
 	this._parser = null;
 	this._parser = null;
@@ -16,7 +18,7 @@ THREE.CompressedTextureLoader.prototype = {
 
 
 	constructor: THREE.CompressedTextureLoader,
 	constructor: THREE.CompressedTextureLoader,
 
 
-	load: function ( url, onLoad, onError ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
 
 		var scope = this;
 		var scope = this;
 
 
@@ -25,7 +27,8 @@ THREE.CompressedTextureLoader.prototype = {
 		var texture = new THREE.CompressedTexture();
 		var texture = new THREE.CompressedTexture();
 		texture.image = images;
 		texture.image = images;
 
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
+		loader.setCrossOrigin( this.crossOrigin );
 		loader.setResponseType( 'arraybuffer' );
 		loader.setResponseType( 'arraybuffer' );
 
 
 		if ( Array.isArray( url ) ) {
 		if ( Array.isArray( url ) ) {
@@ -59,7 +62,7 @@ THREE.CompressedTextureLoader.prototype = {
 
 
 					}
 					}
 
 
-				} );
+				}, onProgress, onError );
 
 
 			};
 			};
 
 
@@ -115,12 +118,18 @@ THREE.CompressedTextureLoader.prototype = {
 
 
 				if ( onLoad ) onLoad( texture );
 				if ( onLoad ) onLoad( texture );
 
 
-			} );
+			}, onProgress, onError );
 
 
 		}
 		}
 
 
 		return texture;
 		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 scope = this;
 
 
-		var loader = new THREE.XHRLoader();
+		var loader = new THREE.XHRLoader( this.manager );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.setCrossOrigin( this.crossOrigin );
 		loader.load( url, function ( text ) {
 		loader.load( url, function ( text ) {
 
 

+ 10 - 1
src/loaders/ImageLoader.js

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

+ 265 - 309
src/loaders/JSONLoader.js

@@ -3,548 +3,504 @@
  * @author alteredq / http://alteredqualia.com/
  * @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;
 	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,
 		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,
 		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 ++;
 					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 ] = [];
 						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;
 						normalIndex = faces[ offset ++ ] * 3;
 
 
-						normal = new THREE.Vector3(
+						faceA.normal.set(
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ++ ],
 							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 ++ ];
 						colorIndex = faces[ offset ++ ];
 						hex = colors[ colorIndex ];
 						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;
 						normalIndex = faces[ offset ++ ] * 3;
 
 
-						normal = new THREE.Vector3(
+						face.normal.set(
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ++ ],
 							normals[ normalIndex ++ ],
 							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 ++ ];
 						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/
  * @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.onLoadStart = function () {};
 	this.onLoadProgress = function () {};
 	this.onLoadProgress = function () {};
@@ -21,46 +16,6 @@ THREE.Loader.prototype = {
 
 
 	crossOrigin: undefined,
 	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 ) {
 	extractUrlBase: function ( url ) {
 
 
 		var parts = url.split( '/' );
 		var parts = url.split( '/' );
@@ -73,13 +28,13 @@ THREE.Loader.prototype = {
 
 
 	},
 	},
 
 
-	initMaterials: function ( materials, texturePath ) {
+	initMaterials: function ( materials, texturePath, crossOrigin ) {
 
 
 		var array = [];
 		var array = [];
 
 
 		for ( var i = 0; i < materials.length; ++ i ) {
 		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 ||
 					if ( THREE.Math.isPowerOfTwo( image.width ) === false ||
 						 THREE.Math.isPowerOfTwo( image.height ) === 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 scope = this;
 
 
-	var loaded = 0, total = 0;
+	var isLoading = false, itemsLoaded = 0, itemsTotal = 0;
 
 
 	this.onLoad = onLoad;
 	this.onLoad = onLoad;
 	this.onProgress = onProgress;
 	this.onProgress = onProgress;
@@ -14,23 +14,41 @@ THREE.LoadingManager = function ( onLoad, onProgress, onError ) {
 
 
 	this.itemStart = function ( url ) {
 	this.itemStart = function ( url ) {
 
 
-		total ++;
+		itemsTotal ++;
+
+		if ( isLoading === false ) {
+
+			if ( scope.onStart !== undefined ) {
+
+				scope.onStart( url, itemsLoaded, itemsTotal );
+
+			}
+
+		}
+
+		isLoading = true;
 
 
 	};
 	};
 
 
 	this.itemEnd = function ( url ) {
 	this.itemEnd = function ( url ) {
 
 
-		loaded ++;
+		itemsLoaded ++;
 
 
 		if ( scope.onProgress !== undefined ) {
 		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 ( cached !== undefined ) {
 
 
-			if ( onLoad ) onLoad( cached );
+			if ( onLoad ) {
+
+				setTimeout( function () {
+
+					onLoad( cached );
+
+				}, 0 );
+
+			}
+
 			return cached;
 			return cached;
 
 
 		}
 		}
@@ -60,6 +69,7 @@ THREE.XHRLoader.prototype = {
 
 
 		if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin;
 		if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin;
 		if ( this.responseType !== undefined ) request.responseType = this.responseType;
 		if ( this.responseType !== undefined ) request.responseType = this.responseType;
+		if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
 
 
 		request.send( null );
 		request.send( null );
 
 
@@ -79,6 +89,12 @@ THREE.XHRLoader.prototype = {
 
 
 		this.crossOrigin = value;
 		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 opaqueObjects = [];
 	var transparentObjects = [];
 	var transparentObjects = [];
 
 
+	var opaqueImmediateObjects = [];
+	var transparentImmediateObjects = [];
+
 	var sprites = [];
 	var sprites = [];
 	var lensFlares = [];
 	var lensFlares = [];
 
 
@@ -71,29 +74,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 	this.autoScaleCubemaps = true;
 	this.autoScaleCubemaps = true;
 
 
-	// info
-
-	this.info = {
-
-		memory: {
-
-			programs: 0,
-			geometries: 0,
-			textures: 0
-
-		},
-
-		render: {
-
-			calls: 0,
-			vertices: 0,
-			faces: 0,
-			points: 0
-
-		}
-
-	};
-
 	// internal properties
 	// internal properties
 
 
 	var _this = this,
 	var _this = this,
@@ -141,6 +121,33 @@ THREE.WebGLRenderer = function ( parameters ) {
 		spot: { length: 0, colors: [], positions: [], distances: [], directions: [], anglesCos: [], exponents: [], decays: [] },
 		spot: { length: 0, colors: [], positions: [], distances: [], directions: [], anglesCos: [], exponents: [], decays: [] },
 		hemi: { length: 0, skyColors: [], groundColors: [], positions: [] }
 		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
 	// 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 ) {
 		if ( _premultipliedAlpha === true ) {
 
 
@@ -244,9 +251,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		_gl.clearColor( r, g, b, a );
 		_gl.clearColor( r, g, b, a );
 
 
-	};
+	}
 
 
-	var setDefaultGLState = function () {
+	function setDefaultGLState() {
 
 
 		state.init();
 		state.init();
 
 
@@ -254,9 +261,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
 		glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
 
 
-	};
+	}
 
 
-	var resetGLState = function () {
+	function resetGLState() {
 
 
 		_currentProgram = null;
 		_currentProgram = null;
 		_currentCamera = null;
 		_currentCamera = null;
@@ -268,7 +275,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		state.reset();
 		state.reset();
 
 
-	};
+	}
 
 
 	setDefaultGLState();
 	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
 	// Events
 
 
-	var onTextureDispose = function ( event ) {
+	function onTextureDispose( event ) {
 
 
 		var texture = event.target;
 		var texture = event.target;
 
 
@@ -612,12 +619,12 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		deallocateTexture( texture );
 		deallocateTexture( texture );
 
 
-		_this.info.memory.textures --;
+		_infoMemory.textures --;
 
 
 
 
-	};
+	}
 
 
-	var onRenderTargetDispose = function ( event ) {
+	function onRenderTargetDispose( event ) {
 
 
 		var renderTarget = event.target;
 		var renderTarget = event.target;
 
 
@@ -625,11 +632,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		deallocateRenderTarget( renderTarget );
 		deallocateRenderTarget( renderTarget );
 
 
-		_this.info.memory.textures --;
+		_infoMemory.textures --;
 
 
-	};
+	}
 
 
-	var onMaterialDispose = function ( event ) {
+	function onMaterialDispose( event ) {
 
 
 		var material = event.target;
 		var material = event.target;
 
 
@@ -637,11 +644,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		deallocateMaterial( material );
 		deallocateMaterial( material );
 
 
-	};
+	}
 
 
 	// Buffer deallocation
 	// Buffer deallocation
 
 
-	var deallocateTexture = function ( texture ) {
+	function deallocateTexture( texture ) {
 
 
 		var textureProperties = properties.get( texture );
 		var textureProperties = properties.get( texture );
 
 
@@ -664,9 +671,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 		// remove all webgl properties
 		// remove all webgl properties
 		properties.delete( texture );
 		properties.delete( texture );
 
 
-	};
+	}
 
 
-	var deallocateRenderTarget = function ( renderTarget ) {
+	function deallocateRenderTarget( renderTarget ) {
 
 
 		var renderTargetProperties = properties.get( renderTarget );
 		var renderTargetProperties = properties.get( renderTarget );
 
 
@@ -692,72 +699,57 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		properties.delete( renderTargetProperties );
 		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
 	// Buffer rendering
 
 
@@ -929,7 +921,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 							if ( geometry.maxInstancedCount === undefined ) {
 							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 ) {
 							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 );
 					_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 {
 			} 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 ) {
 					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 {
 					} 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 ) {
 					if ( position instanceof THREE.InterleavedBufferAttribute ) {
 
 
-						_gl.drawArrays( mode, 0, position.data.array.length / position.data.stride );
+						_gl.drawArrays( mode, 0, position.data.count );
 
 
 					} else {
 					} 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 {
 			} 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
 				_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 {
 			} else {
 
 
@@ -1317,8 +1309,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 					_gl.drawElements( mode, offsets[ i ].count, type, offsets[ i ].start * size ); // 2 bytes per Uint16Array
 					_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 );
 				_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 {
 			} else {
 
 
@@ -1350,8 +1342,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 					_gl.drawArrays( mode, offsets[ i ].index, offsets[ i ].count );
 					_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);
 				_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 {
 			} else {
 
 
@@ -1426,8 +1418,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 					_gl.drawElements( mode, offsets[ i ].count, type, offsets[ i ].start * size );
 					_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 );
 				_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 {
 			} else {
 
 
@@ -1459,8 +1451,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 					_gl.drawArrays( mode, offsets[ i ].index, offsets[ i ].count );
 					_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 );
 		_frustum.setFromMatrix( _projScreenMatrix );
 
 
 		lights.length = 0;
 		lights.length = 0;
+
 		opaqueObjects.length = 0;
 		opaqueObjects.length = 0;
 		transparentObjects.length = 0;
 		transparentObjects.length = 0;
 
 
+		opaqueImmediateObjects.length = 0;
+		transparentImmediateObjects.length = 0;
+
 		sprites.length = 0;
 		sprites.length = 0;
 		lensFlares.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 );
 		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 ) {
 		if ( scene.overrideMaterial ) {
 
 
@@ -1618,7 +1587,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 			renderObjects( opaqueObjects, camera, lights, fog, overrideMaterial );
 			renderObjects( opaqueObjects, camera, lights, fog, overrideMaterial );
 			renderObjects( transparentObjects, 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 {
 		} else {
 
 
@@ -1627,12 +1598,12 @@ THREE.WebGLRenderer = function ( parameters ) {
 			state.setBlending( THREE.NoBlending );
 			state.setBlending( THREE.NoBlending );
 
 
 			renderObjects( opaqueObjects, camera, lights, fog, null );
 			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)
 			// transparent pass (back-to-front order)
 
 
 			renderObjects( transparentObjects, camera, lights, fog, null );
 			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 );
 					lensFlares.push( object );
 
 
+				} else if ( object instanceof THREE.ImmediateRenderObject ) {
+
+					var material = object.material;
+
+					if ( material.transparent ) {
+
+						transparentImmediateObjects.push( object );
+
+					} else {
+
+						opaqueImmediateObjects.push( object );
+
+					}
+
 				} else {
 				} else {
 
 
 					var webglObject = objects.objects[ object.id ];
 					var webglObject = objects.objects[ object.id ];
@@ -1746,7 +1731,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 		for ( var i = 0, l = renderList.length; i < l; i ++ ) {
 		for ( var i = 0, l = renderList.length; i < l; i ++ ) {
 
 
 			var webglObject = renderList[ i ];
 			var webglObject = renderList[ i ];
-
 			var object = webglObject.object;
 			var object = webglObject.object;
 
 
 			setupMatrices( object, camera );
 			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;
 		var material = overrideMaterial;
 
 
 		for ( var i = 0, l = renderList.length; i < l; i ++ ) {
 		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 ( object.visible === true ) {
 
 
-				if ( overrideMaterial === null ) material = webglObject[ materialType ];
+				if ( overrideMaterial === null ) material = object.material;
 
 
 				_this.renderImmediateObject( camera, lights, fog, material, object );
 				_this.renderImmediateObject( camera, lights, fog, material, object );
 
 
@@ -1802,15 +1787,11 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 		_currentGeometryProgram = '';
 		_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 code = chunks.join();
+		var programChange = true;
 
 
 		if ( !materialProperties.program ) {
 		if ( !materialProperties.program ) {
 
 
@@ -1939,17 +1921,17 @@ THREE.WebGLRenderer = function ( parameters ) {
 		} else if ( materialProperties.program.code !== code ) {
 		} else if ( materialProperties.program.code !== code ) {
 
 
 			// changed glsl or parameters
 			// changed glsl or parameters
-			deallocateMaterial( material );
+			releaseMaterialProgramReference( material );
 
 
 		} else if ( shaderID !== undefined ) {
 		} else if ( shaderID !== undefined ) {
 
 
-			// same glsl
+			// same glsl and uniform list
 			return;
 			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 ) {
 			if ( programInfo.code === code ) {
 
 
 				program = programInfo;
 				program = programInfo;
-				program.usedTimes ++;
+
+				if ( programChange ) {
+
+					program.usedTimes ++;
+
+				}
 
 
 				break;
 				break;
 
 
@@ -2000,7 +1987,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 			program = new THREE.WebGLProgram( _this, code, material, parameters );
 			program = new THREE.WebGLProgram( _this, code, material, parameters );
 			_programs.push( program );
 			_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._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
 		object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
 		object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
@@ -3244,7 +3231,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 			textureProperties.__webglTexture = _gl.createTexture();
 			textureProperties.__webglTexture = _gl.createTexture();
 
 
-			_this.info.memory.textures ++;
+			_infoMemory.textures ++;
 
 
 		}
 		}
 
 
@@ -3425,7 +3412,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 					textureProperties.__image__webglTextureCube = _gl.createTexture();
 					textureProperties.__image__webglTextureCube = _gl.createTexture();
 
 
-					_this.info.memory.textures ++;
+					_infoMemory.textures ++;
 
 
 				}
 				}
 
 
@@ -3586,7 +3573,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 
 			renderTargetProperties.__webglTexture = _gl.createTexture();
 			renderTargetProperties.__webglTexture = _gl.createTexture();
 
 
-			_this.info.memory.textures ++;
+			_infoMemory.textures ++;
 
 
 			// Setup texture, create render and frame buffers
 			// Setup texture, create render and frame buffers
 
 

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

@@ -5,7 +5,6 @@
 THREE.WebGLObjects = function ( gl, properties, info ) {
 THREE.WebGLObjects = function ( gl, properties, info ) {
 
 
 	var objects = {};
 	var objects = {};
-	var objectsImmediate = [];
 
 
 	var morphInfluences = new Float32Array( 8 );
 	var morphInfluences = new Float32Array( 8 );
 
 
@@ -34,10 +33,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 
 
 			delete objects[ object.id ];
 			delete objects[ object.id ];
 
 
-		} else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
-
-			removeInstances( objectsImmediate, object );
-
 		}
 		}
 
 
 		delete object._modelViewMatrix;
 		delete object._modelViewMatrix;
@@ -64,7 +59,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 	//
 	//
 
 
 	this.objects = objects;
 	this.objects = objects;
-	this.objectsImmediate = objectsImmediate;
 
 
 	this.geometries = geometries;
 	this.geometries = geometries;
 
 
@@ -94,16 +88,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 					z: 0
 					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 ) {
 		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
 	// returns the webgl buffer for a specified attribute
 	this.getAttributeBuffer = function ( attribute ) {
 	this.getAttributeBuffer = function ( attribute ) {
 
 
 		if ( attribute instanceof THREE.InterleavedBufferAttribute ) {
 		if ( attribute instanceof THREE.InterleavedBufferAttribute ) {
 
 
-			return properties.get( attribute.data ).__webglBuffer
+			return properties.get( attribute.data ).__webglBuffer;
 
 
 		}
 		}
 
 
 		return properties.get( attribute ).__webglBuffer;
 		return properties.get( attribute ).__webglBuffer;
 
 
-	}
+	};
 
 
 	this.update = function ( renderList ) {
 	this.update = function ( renderList ) {
 
 
@@ -271,7 +271,6 @@ THREE.WebGLObjects = function ( gl, properties, info ) {
 	this.clear = function () {
 	this.clear = function () {
 
 
 		objects = {};
 		objects = {};
-		objectsImmediate = [];
 
 
 	};
 	};
 
 

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

@@ -22,7 +22,6 @@ THREE.WebGLProgram = ( function () {
 
 
 	function fetchUniformLocations( gl, program, identifiers ) {
 	function fetchUniformLocations( gl, program, identifiers ) {
 
 
-
 		var uniforms = {};
 		var uniforms = {};
 
 
 		var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );
 		var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );
@@ -361,8 +360,8 @@ THREE.WebGLProgram = ( function () {
 		var vertexGlsl = prefixVertex + vertexShader;
 		var vertexGlsl = prefixVertex + vertexShader;
 		var fragmentGlsl = prefixFragment + fragmentShader;
 		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, glVertexShader );
 		gl.attachShader( program, glFragmentShader );
 		gl.attachShader( program, glFragmentShader );
@@ -379,19 +378,53 @@ THREE.WebGLProgram = ( function () {
 
 
 		gl.linkProgram( program );
 		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 ) {
 		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
 		// set up caching for uniform locations
 
 
-		var getUniforms = function() { return this._cachedUniforms; };
+		var cachedUniforms;
 
 
 		this.getUniforms = function() {
 		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
 		// set up caching for attribute locations
 
 
-		var getAttributes = function() { return this._cachedAttributes; };
+		var cachedAttributes;
 
 
 		this.getAttributes = function() {
 		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 ) {
 	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(),
 	_max = new THREE.Vector3(),
 
 
 	_webglObjects = _objects.objects,
 	_webglObjects = _objects.objects,
-	_webglObjectsImmediate = _objects.objectsImmediate,
 
 
 	_matrixPosition = new THREE.Vector3(),
 	_matrixPosition = new THREE.Vector3(),
 
 
@@ -63,6 +62,10 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	var scope = this;
 	var scope = this;
 
 
 	this.enabled = false;
 	this.enabled = false;
+
+	this.autoUpdate = true;
+	this.needsUpdate = false;
+
 	this.type = THREE.PCFShadowMap;
 	this.type = THREE.PCFShadowMap;
 	this.cullFace = THREE.CullFaceFront;
 	this.cullFace = THREE.CullFaceFront;
 	this.cascade = false;
 	this.cascade = false;
@@ -70,6 +73,7 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 	this.render = function ( scene, camera ) {
 	this.render = function ( scene, camera ) {
 
 
 		if ( scope.enabled === false ) return;
 		if ( scope.enabled === false ) return;
+		if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
 
 
 		var i, il, j, jl, n,
 		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
 		// restore GL state
@@ -346,9 +333,11 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 
 		_renderer.resetGLState();
 		_renderer.resetGLState();
 
 
+		scope.needsUpdate = false;
+
 	};
 	};
 
 
-	function projectObject( object, shadowCamera ) {
+	function projectObject( object, camera ) {
 
 
 		if ( object.visible === true ) {
 		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 ) ) {
 			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 );
 				_renderList.push( webglObject );
 
 
 			}
 			}
 
 
 			for ( var i = 0, l = object.children.length; i < l; i ++ ) {
 			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 newAttributes = new Uint8Array( 16 );
 	var enabledAttributes = new Uint8Array( 16 );
 	var enabledAttributes = new Uint8Array( 16 );
 
 
-	var switches = {};
+	var capabilities = {};
 
 
 	var currentBlending = null;
 	var currentBlending = null;
 	var currentBlendEquation = null;
 	var currentBlendEquation = null;
@@ -20,7 +20,6 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 	var currentBlendDstAlpha = null;
 	var currentBlendDstAlpha = null;
 
 
 	var currentDepthFunc = null;
 	var currentDepthFunc = null;
-	var currentDepthTest = null;
 	var currentDepthWrite = null;
 	var currentDepthWrite = null;
 
 
 	var currentColorWrite = null;
 	var currentColorWrite = null;
@@ -29,7 +28,6 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 
 	var currentLineWidth = null;
 	var currentLineWidth = null;
 
 
-	var currentPolygonOffset = null;
 	var currentPolygonOffsetFactor = null;
 	var currentPolygonOffsetFactor = null;
 	var currentPolygonOffsetUnits = null;
 	var currentPolygonOffsetUnits = null;
 
 
@@ -44,7 +42,7 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 		gl.clearDepth( 1 );
 		gl.clearDepth( 1 );
 		gl.clearStencil( 0 );
 		gl.clearStencil( 0 );
 
 
-		gl.enable( gl.DEPTH_TEST );
+		this.enable( gl.DEPTH_TEST );
 		gl.depthFunc( gl.LEQUAL );
 		gl.depthFunc( gl.LEQUAL );
 
 
 		gl.frontFace( gl.CCW );
 		gl.frontFace( gl.CCW );
@@ -97,10 +95,10 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 
 	this.enable = function ( id ) {
 	this.enable = function ( id ) {
 
 
-		if ( switches[ id ] !== true ) {
+		if ( capabilities[ id ] !== true ) {
 
 
 			gl.enable( id );
 			gl.enable( id );
-			switches[ id ] = true;
+			capabilities[ id ] = true;
 
 
 		}
 		}
 
 
@@ -108,10 +106,10 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 
 	this.disable = function ( id ) {
 	this.disable = function ( id ) {
 
 
-		if ( switches[ id ] !== false ) {
+		if ( capabilities[ id ] !== false ) {
 
 
 			gl.disable( id );
 			gl.disable( id );
-			switches[ id ] = false;
+			capabilities[ id ] = false;
 
 
 		}
 		}
 
 
@@ -270,19 +268,13 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 
 	this.setDepthTest = function ( depthTest ) {
 	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 );
 			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
 	// texture
 
 
 	this.activeTexture = function ( webglSlot ) {
 	this.activeTexture = function ( webglSlot ) {
@@ -394,12 +394,12 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 
 		}
 		}
 
 
-		var boundTexture = currentBoundTextures[currentTextureSlot];
+		var boundTexture = currentBoundTextures[ currentTextureSlot ];
 
 
 		if ( boundTexture === undefined ) {
 		if ( boundTexture === undefined ) {
 
 
 			boundTexture = { type: undefined, texture: 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;
 		currentBlending = null;
-		currentDepthTest = null;
+
 		currentDepthWrite = null;
 		currentDepthWrite = null;
 		currentColorWrite = 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 );
 				gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z );
 
 
 				state.disable( gl.BLEND );
 				state.disable( gl.BLEND );
-				gl.enable( gl.DEPTH_TEST );
+				state.enable( gl.DEPTH_TEST );
 
 
 				gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
 				gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
 
 
@@ -376,7 +376,7 @@ THREE.LensFlarePlugin = function ( renderer, flares ) {
 				// restore graphics
 				// restore graphics
 
 
 				gl.uniform1i( uniforms.renderType, 1 );
 				gl.uniform1i( uniforms.renderType, 1 );
-				gl.disable( gl.DEPTH_TEST );
+				state.disable( gl.DEPTH_TEST );
 
 
 				state.activeTexture( gl.TEXTURE1 );
 				state.activeTexture( gl.TEXTURE1 );
 				state.bindTexture( gl.TEXTURE_2D, tempTexture );
 				state.bindTexture( gl.TEXTURE_2D, tempTexture );
@@ -440,7 +440,7 @@ THREE.LensFlarePlugin = function ( renderer, flares ) {
 		// restore gl
 		// restore gl
 
 
 		state.enable( gl.CULL_FACE );
 		state.enable( gl.CULL_FACE );
-		gl.enable( gl.DEPTH_TEST );
+		state.enable( gl.DEPTH_TEST );
 		gl.depthMask( true );
 		gl.depthMask( true );
 
 
 		renderer.resetGLState();
 		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;
 		var numBefore = idxList.length;
 		equal( countEdges (geom), numBefore, "Edges before!" );
 		equal( countEdges (geom), numBefore, "Edges before!" );
 
 
-		var egeom = new THREE.EdgesGeometry ( geom );;
+		var egeom = new THREE.EdgesGeometry( geom );
 
 
 		equal( countEdges (egeom), numAfter, "Edges after!" );
 		equal( countEdges (egeom), numAfter, "Edges after!" );
 		output( geom, egeom );
 		output( geom, egeom );
@@ -163,7 +163,7 @@ function createIndexedBufferGeometry ( vertList, idxList ) {
 
 
 function addDrawCalls ( geometry ) {
 function addDrawCalls ( geometry ) {
 
 
-	var numTris = geometry.getAttribute( 'index' ).length / 3;
+	var numTris = geometry.getAttribute( 'index' ).count / 3;
 
 
 	var offset = 0;
 	var offset = 0;
 	for ( var i = 0 ; i < numTris; i ++ ) {
 	for ( var i = 0 ; i < numTris; i ++ ) {
@@ -183,7 +183,7 @@ function countEdges ( geom ) {
 
 
 	if ( geom instanceof THREE.EdgesGeometry ) {
 	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' );
 	var indices = geom.getAttribute( 'index' );
 	if ( indices !== undefined ) {
 	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);
 	scene.add(edges);
 
 
 	if (scene.children.length % 8 === 0) {
 	if (scene.children.length % 8 === 0) {
+
 		xoffset += 2;
 		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
  * http://jquery.org/license
+ *
+ * Date: 2015-04-03T10:23Z
  */
  */
 
 
 /** Font Family and Sizes */
 /** Font Family and Sizes */
@@ -20,7 +21,7 @@
 
 
 /** Resets */
 /** 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;
 	margin: 0;
 	padding: 0;
 	padding: 0;
 }
 }
@@ -31,32 +32,29 @@
 #qunit-header {
 #qunit-header {
 	padding: 0.5em 0 0.5em 1em;
 	padding: 0.5em 0 0.5em 1em;
 
 
-	color: #8699a4;
-	background-color: #0d3349;
+	color: #8699A4;
+	background-color: #0D3349;
 
 
 	font-size: 1.5em;
 	font-size: 1.5em;
 	line-height: 1em;
 	line-height: 1em;
-	font-weight: normal;
+	font-weight: 400;
 
 
 	border-radius: 5px 5px 0 0;
 	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 {
 #qunit-header a {
 	text-decoration: none;
 	text-decoration: none;
-	color: #c2ccd1;
+	color: #C2CCD1;
 }
 }
 
 
 #qunit-header a:hover,
 #qunit-header a:hover,
 #qunit-header a:focus {
 #qunit-header a:focus {
-	color: #fff;
+	color: #FFF;
 }
 }
 
 
 #qunit-testrunner-toolbar label {
 #qunit-testrunner-toolbar label {
 	display: inline-block;
 	display: inline-block;
-	padding: 0 .5em 0 .1em;
+	padding: 0 0.5em 0 0.1em;
 }
 }
 
 
 #qunit-banner {
 #qunit-banner {
@@ -64,21 +62,33 @@
 }
 }
 
 
 #qunit-testrunner-toolbar {
 #qunit-testrunner-toolbar {
-	padding: 0.5em 0 0.5em 2em;
+	padding: 0.5em 1em 0.5em 1em;
 	color: #5E740B;
 	color: #5E740B;
-	background-color: #eee;
+	background-color: #EEE;
 	overflow: hidden;
 	overflow: hidden;
 }
 }
 
 
 #qunit-userAgent {
 #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;
 	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
 }
 }
 
 
 #qunit-modulefilter-container {
 #qunit-modulefilter-container {
 	float: right;
 	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 */
 /** Tests: Pass/Fail */
@@ -88,49 +98,83 @@
 }
 }
 
 
 #qunit-tests li {
 #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;
 	list-style-position: inside;
 }
 }
 
 
-#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
+#qunit-tests > li {
 	display: none;
 	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 {
 #qunit-tests li strong {
 	cursor: pointer;
 	cursor: pointer;
 }
 }
 
 
+#qunit-tests li.skipped strong {
+	cursor: default;
+}
+
 #qunit-tests li a {
 #qunit-tests li a {
 	padding: 0.5em;
 	padding: 0.5em;
-	color: #c2ccd1;
+	color: #C2CCD1;
 	text-decoration: none;
 	text-decoration: none;
 }
 }
+
+#qunit-tests li p a {
+	padding: 0.25em;
+	color: #6B6464;
+}
 #qunit-tests li a:hover,
 #qunit-tests li a:hover,
 #qunit-tests li a:focus {
 #qunit-tests li a:focus {
 	color: #000;
 	color: #000;
 }
 }
 
 
-#qunit-tests ol {
+#qunit-tests li .runtime {
+	float: right;
+	font-size: smaller;
+}
+
+.qunit-assert-list {
 	margin-top: 0.5em;
 	margin-top: 0.5em;
 	padding: 0.5em;
 	padding: 0.5em;
 
 
-	background-color: #fff;
+	background-color: #FFF;
 
 
 	border-radius: 5px;
 	border-radius: 5px;
-	-moz-border-radius: 5px;
-	-webkit-border-radius: 5px;
+}
+
+.qunit-collapsed {
+	display: none;
 }
 }
 
 
 #qunit-tests table {
 #qunit-tests table {
 	border-collapse: collapse;
 	border-collapse: collapse;
-	margin-top: .2em;
+	margin-top: 0.2em;
 }
 }
 
 
 #qunit-tests th {
 #qunit-tests th {
 	text-align: right;
 	text-align: right;
 	vertical-align: top;
 	vertical-align: top;
-	padding: 0 .5em 0 0;
+	padding: 0 0.5em 0 0;
 }
 }
 
 
 #qunit-tests td {
 #qunit-tests td {
@@ -144,26 +188,26 @@
 }
 }
 
 
 #qunit-tests del {
 #qunit-tests del {
-	background-color: #e0f2be;
-	color: #374e0c;
+	background-color: #E0F2BE;
+	color: #374E0C;
 	text-decoration: none;
 	text-decoration: none;
 }
 }
 
 
 #qunit-tests ins {
 #qunit-tests ins {
-	background-color: #ffcaca;
+	background-color: #FFCACA;
 	color: #500;
 	color: #500;
 	text-decoration: none;
 	text-decoration: none;
 }
 }
 
 
 /*** Test Counts */
 /*** Test Counts */
 
 
-#qunit-tests b.counts                       { color: black; }
+#qunit-tests b.counts                       { color: #000; }
 #qunit-tests b.passed                       { color: #5E740B; }
 #qunit-tests b.passed                       { color: #5E740B; }
 #qunit-tests b.failed                       { color: #710909; }
 #qunit-tests b.failed                       { color: #710909; }
 
 
 #qunit-tests li li {
 #qunit-tests li li {
 	padding: 5px;
 	padding: 5px;
-	background-color: #fff;
+	background-color: #FFF;
 	border-bottom: none;
 	border-bottom: none;
 	list-style-position: inside;
 	list-style-position: inside;
 }
 }
@@ -171,8 +215,8 @@
 /*** Passing Styles */
 /*** Passing Styles */
 
 
 #qunit-tests li li.pass {
 #qunit-tests li li.pass {
-	color: #3c510c;
-	background-color: #fff;
+	color: #3C510C;
+	background-color: #FFF;
 	border-left: 10px solid #C6E746;
 	border-left: 10px solid #C6E746;
 }
 }
 
 
@@ -180,7 +224,7 @@
 #qunit-tests .pass .test-name               { color: #366097; }
 #qunit-tests .pass .test-name               { color: #366097; }
 
 
 #qunit-tests .pass .test-actual,
 #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; }
 #qunit-banner.qunit-pass                    { background-color: #C6E746; }
 
 
@@ -188,40 +232,52 @@
 
 
 #qunit-tests li li.fail {
 #qunit-tests li li.fail {
 	color: #710909;
 	color: #710909;
-	background-color: #fff;
+	background-color: #FFF;
 	border-left: 10px solid #EE5757;
 	border-left: 10px solid #EE5757;
 	white-space: pre;
 	white-space: pre;
 }
 }
 
 
 #qunit-tests > li:last-child {
 #qunit-tests > li:last-child {
 	border-radius: 0 0 5px 5px;
 	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 .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-actual             { color: #EE5757; }
-#qunit-tests .fail .test-expected           { color: green;   }
+#qunit-tests .fail .test-expected           { color: #008000; }
 
 
 #qunit-banner.qunit-fail                    { background-color: #EE5757; }
 #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 */
 /** Result */
 
 
 #qunit-testresult {
 #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;
 	background-color: #D2E0E6;
 
 
-	border-bottom: 1px solid white;
+	border-bottom: 1px solid #FFF;
 }
 }
 #qunit-testresult .module-name {
 #qunit-testresult .module-name {
-	font-weight: bold;
+	font-weight: 700;
 }
 }
 
 
 /** Fixture */
 /** 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>
 <head>
   <meta charset="utf-8">
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using Files in /src</title>
   <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>
 </head>
 <body>
 <body>
   <div id="qunit"></div>
   <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 -->
   <!-- add sources to test below -->
 
 

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

@@ -3,11 +3,12 @@
 <head>
 <head>
   <meta charset="utf-8">
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using build/Three-math.js</title>
   <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>
 </head>
 <body>
 <body>
   <div id="qunit"></div>
   <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 -->
   <!-- add sources to test below -->
 
 

+ 5 - 3
test/unit/unittests_three.html

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

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

@@ -3,11 +3,12 @@
 <head>
 <head>
   <meta charset="utf-8">
   <meta charset="utf-8">
   <title>ThreeJS Unit Tests - Using build/Three.min.js</title>
   <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>
 </head>
 <body>
 <body>
   <div id="qunit"></div>
   <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 -->
   <!-- add sources to test below -->
 
 

Some files were not shown because too many files changed in this diff