Browse Source

Merge pull request #14 from mrdoob/dev

Update
Temdog007 6 years ago
parent
commit
7d38aec853
100 changed files with 16957 additions and 668 deletions
  1. 3 0
      docs/api/en/core/Object3D.html
  2. 1 0
      docs/api/en/math/Matrix4.html
  3. 7 4
      docs/api/en/renderers/WebGLRenderer.html
  4. 1 1
      docs/examples/controls/OrbitControls.html
  5. 0 20
      docs/examples/utils/SceneUtils.html
  6. 98 0
      docs/manual/en/introduction/Import-via-modules.html
  7. 3 84
      editor/js/Sidebar.Geometry.LatheGeometry.js
  8. 2 83
      editor/js/Sidebar.Geometry.TubeGeometry.js
  9. 18 1
      editor/js/Sidebar.Project.js
  10. 256 0
      editor/js/libs/ui.three.js
  11. 0 1
      examples/files.js
  12. 2 2
      examples/js/cameras/CinematicCamera.js
  13. 1 0
      examples/js/controls/TransformControls.js
  14. 8 8
      examples/js/curves/NURBSUtils.js
  15. 157 0
      examples/js/geometries/Hilbert.js
  16. 0 64
      examples/js/geometries/hilbert2D.js
  17. 0 84
      examples/js/geometries/hilbert3D.js
  18. 43 0
      examples/js/libs/basis/README.md
  19. 0 0
      examples/js/libs/basis/basis_transcoder.js
  20. BIN
      examples/js/libs/basis/basis_transcoder.wasm
  21. 3 1
      examples/js/loaders/AssimpLoader.js
  22. 402 0
      examples/js/loaders/BasisTextureLoader.js
  23. 26 17
      examples/js/loaders/EXRLoader.js
  24. 127 122
      examples/js/loaders/GCodeLoader.js
  25. 1 1
      examples/js/loaders/GLTFLoader.js
  26. 3 3
      examples/js/loaders/KTXLoader.js
  27. 167 10
      examples/js/loaders/LDrawLoader.js
  28. 2 5
      examples/js/loaders/RGBELoader.js
  29. 0 5
      examples/js/objects/Reflector.js
  30. 19 29
      examples/js/objects/Refractor.js
  31. 1 1
      examples/js/postprocessing/ClearPass.js
  32. 1 1
      examples/js/postprocessing/DotScreenPass.js
  33. 2 2
      examples/js/postprocessing/EffectComposer.js
  34. 1 1
      examples/js/postprocessing/FilmPass.js
  35. 2 2
      examples/js/postprocessing/MaskPass.js
  36. 1 1
      examples/js/postprocessing/RenderPass.js
  37. 2 1
      examples/js/postprocessing/ShaderPass.js
  38. 2 1
      examples/js/postprocessing/TexturePass.js
  39. 1 1
      examples/js/shaders/BleachBypassShader.js
  40. 2 2
      examples/js/shaders/BlendShader.js
  41. 11 11
      examples/js/shaders/BokehShader.js
  42. 21 21
      examples/js/shaders/BokehShader2.js
  43. 2 2
      examples/js/shaders/BrightnessContrastShader.js
  44. 3 3
      examples/js/shaders/ColorCorrectionShader.js
  45. 1 1
      examples/js/shaders/ColorifyShader.js
  46. 2 2
      examples/js/shaders/ConvolutionShader.js
  47. 4 4
      examples/js/shaders/DOFMipMapShader.js
  48. 4 0
      examples/js/shaders/DepthLimitedBlurShader.js
  49. 17 17
      examples/js/shaders/DigitalGlitch.js
  50. 1 1
      examples/js/shaders/FXAAShader.js
  51. 5 5
      examples/js/shaders/FilmShader.js
  52. 5 5
      examples/js/shaders/FocusShader.js
  53. 1 1
      examples/js/shaders/FreiChenShader.js
  54. 1 1
      examples/js/shaders/HorizontalBlurShader.js
  55. 2 2
      examples/js/shaders/HorizontalTiltShiftShader.js
  56. 2 2
      examples/js/shaders/HueSaturationShader.js
  57. 3 3
      examples/js/shaders/KaleidoShader.js
  58. 2 2
      examples/js/shaders/LuminosityHighPassShader.js
  59. 2 2
      examples/js/shaders/MirrorShader.js
  60. 3 3
      examples/js/shaders/NormalMapShader.js
  61. 2 2
      examples/js/shaders/RGBShiftShader.js
  62. 4 0
      examples/js/shaders/SAOShader.js
  63. 1 1
      examples/js/shaders/SepiaShader.js
  64. 1 1
      examples/js/shaders/UnpackDepthRGBAShader.js
  65. 1 1
      examples/js/shaders/VerticalBlurShader.js
  66. 2 2
      examples/js/shaders/VerticalTiltShiftShader.js
  67. 1 1
      examples/js/shaders/VignetteShader.js
  68. 0 5
      examples/js/shaders/WaterRefractionShader.js
  69. 5 6
      examples/js/utils/SceneUtils.js
  70. 32 0
      examples/jsm/cameras/CinematicCamera.d.ts
  71. 224 0
      examples/jsm/cameras/CinematicCamera.js
  72. 10 0
      examples/jsm/curves/NURBSCurve.d.ts
  73. 78 0
      examples/jsm/curves/NURBSCurve.js
  74. 11 0
      examples/jsm/curves/NURBSSurface.d.ts
  75. 60 0
      examples/jsm/curves/NURBSSurface.js
  76. 18 0
      examples/jsm/curves/NURBSUtils.d.ts
  77. 476 0
      examples/jsm/curves/NURBSUtils.js
  78. 16 0
      examples/jsm/loaders/3MFLoader.d.ts
  79. 943 0
      examples/jsm/loaders/3MFLoader.js
  80. 14 0
      examples/jsm/loaders/AMFLoader.d.ts
  81. 509 0
      examples/jsm/loaders/AMFLoader.js
  82. 18 0
      examples/jsm/loaders/AssimpJSONLoader.d.ts
  83. 309 0
      examples/jsm/loaders/AssimpJSONLoader.js
  84. 24 0
      examples/jsm/loaders/AssimpLoader.d.ts
  85. 2402 0
      examples/jsm/loaders/AssimpLoader.js
  86. 1 1
      examples/jsm/loaders/BVHLoader.d.ts
  87. 14 0
      examples/jsm/loaders/BabylonLoader.d.ts
  88. 273 0
      examples/jsm/loaders/BabylonLoader.js
  89. 28 0
      examples/jsm/loaders/ColladaLoader.d.ts
  90. 4013 0
      examples/jsm/loaders/ColladaLoader.js
  91. 22 0
      examples/jsm/loaders/DDSLoader.d.ts
  92. 283 0
      examples/jsm/loaders/DDSLoader.js
  93. 21 0
      examples/jsm/loaders/EXRLoader.d.ts
  94. 1203 0
      examples/jsm/loaders/EXRLoader.js
  95. 19 0
      examples/jsm/loaders/FBXLoader.d.ts
  96. 4186 0
      examples/jsm/loaders/FBXLoader.js
  97. 16 0
      examples/jsm/loaders/GCodeLoader.d.ts
  98. 243 0
      examples/jsm/loaders/GCodeLoader.js
  99. 2 2
      examples/jsm/loaders/GLTFLoader.d.ts
  100. 15 0
      examples/jsm/loaders/KMZLoader.d.ts

+ 3 - 0
docs/api/en/core/Object3D.html

@@ -217,6 +217,9 @@
 		<h3>[method:this applyQuaternion]( [param:Quaternion quaternion] )</h3>
 		<p>Applies the rotation represented by the quaternion to the object.</p>
 
+		<h3>[method:this attach]( [param:Object3D object] )</h3>
+		<p>Adds *object* as a child of this, while maintaining the object's world transform.</p>
+
 		<h3>[method:Object3D clone]( [param:Boolean recursive] )</h3>
 		<p>
 		recursive -- if true, descendants of the object are also cloned. Default is true.<br /><br />

+ 1 - 0
docs/api/en/math/Matrix4.html

@@ -375,6 +375,7 @@ x, y, 1, 0,
 		</p>
 
 		<h3>[method:this setPosition]( [param:Vector3 v] )</h3>
+		<h3>[method:this setPosition]( [param:Float x], [param:Float y], [param:Float z] ) // optional API</h3>
 		<p>
 			Sets the position component for this matrix from vector [page:Vector3 v], without affecting the
 			rest of the matrix - i.e. if the matrix is currently:

+ 7 - 4
docs/api/en/renderers/WebGLRenderer.html

@@ -55,7 +55,10 @@
 
 		[page:String powerPreference] - Provides a hint to the user agent indicating what configuration
 		of GPU is suitable for this WebGL context. Can be *"high-performance"*, *"low-power"* or *"default"*. Default is *"default"*.
-		See the [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.2 WebGL spec] for more information.<br />
+		See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 WebGL spec] for details.<br />
+
+		[page:Boolean failIfMajorPerformanceCaveat] - whether the renderer creation will fail upon low perfomance is detected. Default is *false*.
+		See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 WebGL spec] for details.<br />
 
 		[page:Boolean depth] - whether the drawing buffer has a
 		[link:https://en.wikipedia.org/wiki/Z-buffering depth buffer] of at least 16 bits.
@@ -394,11 +397,11 @@
 		<h3>[method:null resetGLState]( )</h3>
 		<p>Reset the GL state to default. Called internally if the WebGL context is lost.</p>
 
-		<h3>[method:null readRenderTargetPixels]( [param:WebGLRenderTarget renderTarget], [param:Float x], [param:Float y], [param:Float width], [param:Float height], [param:TypedArray buffer] )</h3>
+		<h3>[method:null readRenderTargetPixels]( [param:WebGLRenderTarget renderTarget], [param:Float x], [param:Float y], [param:Float width], [param:Float height], [param:TypedArray buffer], [param:Integer activeCubeFaceIndex] )</h3>
 		<p>buffer - Uint8Array is the only destination type supported in all cases, other types are renderTarget and platform dependent. See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 WebGL spec] for details.</p>
 		<p>Reads the pixel data from the renderTarget into the buffer you pass in. This is a wrapper around [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/readPixels WebGLRenderingContext.readPixels]().</p>
-		<p>See the [example:webgl_interactive_cubes_gpu interactive / cubes / gpu] example.
-		</p>
+		<p>See the [example:webgl_interactive_cubes_gpu interactive / cubes / gpu] example.</p>
+		<p>For reading out a [page:WebGLRenderTargetCube WebGLRenderTargetCube] use the optional parameter activeCubeFaceIndex to determine which face should be read.</p>
 
 
 		<h3>[method:null render]( [param:Scene scene], [param:Camera camera] )</h3>

+ 1 - 1
docs/examples/controls/OrbitControls.html

@@ -54,7 +54,7 @@ function animate() {
 
 		<h3>[name]( [param:Camera object], [param:HTMLDOMElement domElement] )</h3>
 		<p>
-			[page:Camera object]: (required) The camera to be controlled.<br><br>
+			[page:Camera object]: (required) The camera to be controlled. The camera must not be a child of another object, unless that object is the scene itself.<br><br>
 
 			[page:HTMLDOMElement domElement]: (optional) The HTML element used for event listeners. By default this is the whole document,
 			however if you only want the controls to work over a specific element (e.g. the canvas) you can specify that here.

+ 0 - 20
docs/examples/utils/SceneUtils.html

@@ -26,26 +26,6 @@
 		This is mostly useful for objects that need both a material and a wireframe implementation.
 		</p>
 
-		<h3>[method:null attach]( [param:Object3D child], [param:Object3D scene], [param:Object3D parent] )</h3>
-		<p>
-		child -- The object to add to the parent  <br />
-		scene -- The scene to detach the object from. <br />
-		parent -- The parent to attach the object to.
-		</p>
-		<p>
-		Attaches the object to the parent without the moving the object in the worldspace. Beware that to do this the matrixWorld needs to be updated. This can be done by calling the updateMatrixWorld method on the parent object.
-		</p>
-
-		<h3>[method:null detach]( [param:Object3D child], [param:Object3D parent], [param:Object3D scene] )</h3>
-		<p>
-		child -- The object to remove from the parent  <br />
-		scene -- The scene to attach the object on. <br />
-		parent -- The parent to detach the object from.
-		</p>
-		<p>
-		Detaches the object from the parent and adds it back to the scene without moving in worldspace. Beware that to do this the matrixWorld needs to be updated. This can be done by calling the updateMatrixWorld method on the parent object.
-		</p>
-
 		<h2>Source</h2>
 
 		[link:https://github.com/mrdoob/three.js/blob/master/examples/js/utils/SceneUtils.js examples/js/utils/SceneUtils.js]

+ 98 - 0
docs/manual/en/introduction/Import-via-modules.html

@@ -76,6 +76,11 @@
 		<p>
 			The following examples files are already available as modules:
 			<ul>
+				<li>cameras
+					<ul>
+						<li>CinematicCamera</li>
+					</ul>
+				</li>
 				<li>controls
 					<ul>
 						<li>DeviceOrientationControls</li>
@@ -91,6 +96,13 @@
 						<li>TransformControls</li>
 					</ul>
 				</li>
+				<li>curves
+					<ul>
+						<li>NURBSCurve</li>
+						<li>NURBSSurface</li>
+						<li>NURBSUtils</li>
+					</ul>
+				</li>
 				<li>exporters
 					<ul>
 						<li>ColladaExporter</li>
@@ -104,28 +116,114 @@
 				</li>
 				<li>loaders
 					<ul>
+						<li>3MFLoader</li>
+						<li>AMFLoader</li>
+						<li>AssimpJSONLoader</li>
+						<li>AssimpLoader</li>
+						<li>BabylonLoader</li>
 						<li>BVHLoader</li>
+						<li>ColladaLoader</li>
+						<li>DDSLoader</li>
+						<li>EXRLoader</li>
+						<li>FBXLoader</li>
+						<li>GCodeLoader</li>
 						<li>GLTFLoader</li>
+						<li>KMZLoader</li>
+						<li>KTXLoader</li>
 						<li>MTLLoader</li>
 						<li>OBJLoader</li>
 						<li>PCDLoader</li>
+						<li>PDBLoader</li>
+						<li>PlayCanvasLoader</li>
 						<li>PLYLoader</li>
+						<li>RGBELoader</li>
 						<li>STLLoader</li>
 						<li>SVGLoader</li>
 						<li>TGALoader</li>
 						<li>VRMLLoader</li>
 					</ul>
 				</li>
+				<li>objects
+					<ul>
+						<li>Lensflare</li>
+						<li>Reflector</li>
+						<li>Refractor</li>
+					</ul>
+				</li>
 				<li>pmrem
 					<ul>
 						<li>PMREMCubeUVPacker</li>
 						<li>PMREMGenerator</li>
 					</ul>
 				</li>
+				<li>postprocessing
+					<ul>
+						<li>BloomPass</li>
+						<li>ClearPass</li>
+						<li>DotScreenPass</li>
+						<li>EffectComposer</li>
+						<li>FilmPass</li>
+						<li>MaskPass</li>
+						<li>RenderPass</li>
+						<li>ShaderPass</li>
+						<li>TexturePass</li>
+					</ul>
+				</li>
 				<li>renderers
 					<ul>
 						<li>CSS2DRenderer</li>
 						<li>CSS3DRenderer</li>
+						<li>Projector</li>
+						<li>SoftwareRenderer</li>
+						<li>SVGRenderer</li>
+						<li>RaytracingRenderer</li>
+					</ul>
+				</li>
+				<li>shaders
+					<ul>
+						<li>AfterimageShader</li>
+						<li>BasicShader</li>
+						<li>BleachBypassShader</li>
+						<li>BlendShader</li>
+						<li>BokehShader</li>
+						<li>BokehShader2</li>
+						<li>BrightnessContrastShader</li>
+						<li>ColorCorrectionShader</li>
+						<li>ColorifyShader</li>
+						<li>ConvolutionShader</li>
+						<li>CopyShader</li>
+						<li>DepthLimitedBlurShader</li>
+						<li>DigitalGlitch</li>
+						<li>DOFMipMapShader</li>
+						<li>DotScreenShader</li>
+						<li>FilmShader</li>
+						<li>FocusShader</li>
+						<li>FreiChenShader</li>
+						<li>FresnelShader</li>
+						<li>FXAAShader</li>
+						<li>GammaCorrectionShader</li>
+						<li>HalftoneShader</li>
+						<li>HorizontalBlurShader</li>
+						<li>HorizontalTiltShiftShader</li>
+						<li>HueSaturationShader</li>
+						<li>KaleidoShader</li>
+						<li>LuminosityHighPassShader</li>
+						<li>LuminosityShader</li>
+						<li>MirrorShader</li>
+						<li>NormalMapShader</li>
+						<li>ParallaxShader</li>
+						<li>PixelShader</li>
+						<li>RGBShiftShader</li>
+						<li>SAOShader</li>
+						<li>SepiaShader</li>
+						<li>SobelOperatorShader</li>
+						<li>SSAOShader</li>
+						<li>TechnicolorShader</li>
+						<li>UnpackDepthRGBAShader</li>
+						<li>VerticalBlurShader</li>
+						<li>VerticalTiltShiftShader</li>
+						<li>VignetteShader</li>
+						<li>WaterRefractionShader</li>
 					</ul>
 				</li>
 				<li>utils

+ 3 - 84
editor/js/Sidebar.Geometry.LatheGeometry.js

@@ -2,7 +2,7 @@
  * @author rfm1201
  */
 
-Sidebar.Geometry.LatheGeometry = function( editor, object ) {
+Sidebar.Geometry.LatheGeometry = function ( editor, object ) {
 
 	var strings = editor.strings;
 
@@ -45,99 +45,18 @@ Sidebar.Geometry.LatheGeometry = function( editor, object ) {
 
 	// points
 
-	var lastPointIdx = 0;
-	var pointsUI = [];
-
 	var pointsRow = new UI.Row();
 	pointsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/lathe_geometry/points' ) ).setWidth( '90px' ) );
 
-	var points = new UI.Span().setDisplay( 'inline-block' );
+	var points = new UI.Points2().setValue( parameters.points ).onChange( update );
 	pointsRow.add( points );
 
-	var pointsList = new UI.Div();
-	points.add( pointsList );
-
-	for ( var i = 0; i < parameters.points.length; i ++ ) {
-
-		var point = parameters.points[ i ];
-		pointsList.add( createPointRow( point.x, point.y ) );
-
-	}
-
-	var addPointButton = new UI.Button( '+' ).onClick( function() {
-
-		if( pointsUI.length === 0 ){
-
-			pointsList.add( createPointRow( 0, 0 ) );
-
-		} else {
-
-			var point = pointsUI[ pointsUI.length - 1 ];
-
-			pointsList.add( createPointRow( point.x.getValue(), point.y.getValue() ) );
-
-		}
-
-		update();
-
-	} );
-	points.add( addPointButton );
-
 	container.add( pointsRow );
 
-	//
-
-	function createPointRow( x, y ) {
-
-		var pointRow = new UI.Div();
-		var lbl = new UI.Text( lastPointIdx + 1 ).setWidth( '20px' );
-		var txtX = new UI.Number( x ).setRange( 0, Infinity ).setWidth( '40px' ).onChange( update );
-		var txtY = new UI.Number( y ).setWidth( '40px' ).onChange( update );
-		var idx = lastPointIdx;
-		var btn = new UI.Button( '-' ).onClick( function() {
-
-			deletePointRow( idx );
-
-		} );
-
-		pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
-		lastPointIdx ++;
-		pointRow.add( lbl, txtX, txtY, btn );
-
-		return pointRow;
-
-	}
-
-	function deletePointRow( idx ) {
-
-		if ( ! pointsUI[ idx ] ) return;
-
-		pointsList.remove( pointsUI[ idx ].row );
-		pointsUI[ idx ] = null;
-
-		update();
-
-	}
-
 	function update() {
 
-		var points = [];
-		var count = 0;
-
-		for ( var i = 0; i < pointsUI.length; i ++ ) {
-
-			var pointUI = pointsUI[ i ];
-
-			if ( ! pointUI ) continue;
-
-			points.push( new THREE.Vector2( pointUI.x.getValue(), pointUI.y.getValue() ) );
-			count ++;
-			pointUI.lbl.setValue( count );
-
-		}
-
 		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-			points,
+			points.getValue(),
 			segments.getValue(),
 			phiStart.getValue() / 180 * Math.PI,
 			phiLength.getValue() / 180 * Math.PI

+ 2 - 83
editor/js/Sidebar.Geometry.TubeGeometry.js

@@ -15,45 +15,12 @@ Sidebar.Geometry.TubeGeometry = function ( editor, object ) {
 
 	// points
 
-	var lastPointIdx = 0;
-	var pointsUI = [];
-
 	var pointsRow = new UI.Row();
 	pointsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/tube_geometry/path' ) ).setWidth( '90px' ) );
 
-	var points = new UI.Span().setDisplay( 'inline-block' );
+	var points = new UI.Points3().setValue( parameters.path.points ).onChange( update );
 	pointsRow.add( points );
 
-	var pointsList = new UI.Div();
-	points.add( pointsList );
-
-	var parameterPoints = parameters.path.points;
-	for ( var i = 0; i < parameterPoints.length; i ++ ) {
-
-		var point = parameterPoints[ i ];
-		pointsList.add( createPointRow( point.x, point.y, point.z ) );
-
-	}
-
-	var addPointButton = new UI.Button( '+' ).onClick( function () {
-
-		if ( pointsUI.length === 0 ) {
-
-			pointsList.add( createPointRow( 0, 0, 0 ) );
-
-		} else {
-
-			var point = pointsUI[ pointsUI.length - 1 ];
-
-			pointsList.add( createPointRow( point.x.getValue(), point.y.getValue(), point.z.getValue() ) );
-
-		}
-
-		update();
-
-	} );
-	points.add( addPointButton );
-
 	container.add( pointsRow );
 
 	// radius
@@ -118,25 +85,10 @@ Sidebar.Geometry.TubeGeometry = function ( editor, object ) {
 
 	function update() {
 
-		var points = [];
-		var count = 0;
-
-		for ( var i = 0; i < pointsUI.length; i ++ ) {
-
-			var pointUI = pointsUI[ i ];
-
-			if ( ! pointUI ) continue;
-
-			points.push( new THREE.Vector3( pointUI.x.getValue(), pointUI.y.getValue(), pointUI.z.getValue() ) );
-			count ++;
-			pointUI.lbl.setValue( count );
-
-		}
-
 		tensionRow.setDisplay( curveType.getValue() == 'catmullrom' ? '' : 'none' );
 
 		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-			new THREE.CatmullRomCurve3( points, closed.getValue(), curveType.getValue(), tension.getValue() ),
+			new THREE.CatmullRomCurve3( points.getValue(), closed.getValue(), curveType.getValue(), tension.getValue() ),
 			tubularSegments.getValue(),
 			radius.getValue(),
 			radialSegments.getValue(),
@@ -145,39 +97,6 @@ Sidebar.Geometry.TubeGeometry = function ( editor, object ) {
 
 	}
 
-	function createPointRow( x, y, z ) {
-
-		var pointRow = new UI.Div();
-		var lbl = new UI.Text( lastPointIdx + 1 ).setWidth( '20px' );
-		var txtX = new UI.Number( x ).setWidth( '30px' ).onChange( update );
-		var txtY = new UI.Number( y ).setWidth( '30px' ).onChange( update );
-		var txtZ = new UI.Number( z ).setWidth( '30px' ).onChange( update );
-		var idx = lastPointIdx;
-		var btn = new UI.Button( '-' ).onClick( function () {
-
-			deletePointRow( idx );
-
-		} );
-
-		pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY, z: txtZ } );
-		lastPointIdx ++;
-		pointRow.add( lbl, txtX, txtY, txtZ, btn );
-
-		return pointRow;
-
-	}
-
-	function deletePointRow( idx ) {
-
-		if ( ! pointsUI[ idx ] ) return;
-
-		pointsList.remove( pointsUI[ idx ].row );
-		pointsUI[ idx ] = null;
-
-		update();
-
-	}
-
 	return container;
 
 };

+ 18 - 1
editor/js/Sidebar.Project.js

@@ -133,7 +133,24 @@ Sidebar.Project = function ( editor ) {
 
 		rendererPropertiesRow.setDisplay( type === 'WebGLRenderer' ? '' : 'none' );
 
-		var renderer = new rendererTypes[ type ]( { antialias: antialias } );
+		var parameters = {};
+
+		switch ( type ) {
+
+			case 'WebGLRenderer':
+				parameters.antialias = antialias;
+				break;
+
+			case 'RaytracingRenderer':
+				parameters.workers = navigator.hardwareConcurrency || 4;
+				parameters.workerPath = '../examples/js/renderers/RaytracingWorker.js';
+				parameters.randomize = true;
+				parameters.blockSize = 64;
+				break;
+
+		}
+
+		var renderer = new rendererTypes[ type ]( parameters );
 
 		if ( shadows && renderer.shadowMap ) {
 

+ 256 - 0
editor/js/libs/ui.three.js

@@ -453,6 +453,262 @@ UI.Outliner.prototype.setValue = function ( value ) {
 
 };
 
+var Points = function ( onAddClicked ) {
+
+	UI.Element.call( this );
+
+	var span = new UI.Span().setDisplay( 'inline-block' );
+
+	this.pointsList = new UI.Div();
+	span.add( this.pointsList );
+
+	var row = new UI.Row();
+	span.add( row );
+
+	var addPointButton = new UI.Button( '+' ).onClick( onAddClicked );
+	row.add( addPointButton );
+
+	this.update = function () {
+
+		if ( this.onChangeCallback !== null ) {
+
+			this.onChangeCallback();
+
+		}
+
+	}.bind( this );
+
+	this.dom = span.dom;
+	this.pointsUI = [];
+	this.lastPointIdx = 0;
+	this.onChangeCallback = null;
+	return this;
+
+};
+
+Points.prototype = Object.create( UI.Element.prototype );
+Points.prototype.constructor = Points;
+
+Points.prototype.onChange = function ( callback ) {
+
+	this.onChangeCallback = callback;
+
+	return this;
+
+};
+
+Points.prototype.clear = function () {
+
+	for ( var i = 0; i < this.pointsUI.length; ++ i ) {
+
+		if ( this.pointsUI[ i ] ) {
+
+			this.deletePointRow( i, true );
+
+		}
+
+	}
+
+	this.lastPointIdx = 0;
+
+};
+
+Points.prototype.deletePointRow = function ( idx, dontUpdate ) {
+
+	if ( ! this.pointsUI[ idx ] ) return;
+
+	this.pointsList.remove( this.pointsUI[ idx ].row );
+	this.pointsUI[ idx ] = null;
+
+	if ( dontUpdate !== true ) {
+
+		this.update();
+
+	}
+
+};
+
+UI.Points2 = function () {
+
+	Points.call( this, UI.Points2.addRow.bind( this ) );
+
+	return this;
+
+};
+
+UI.Points2.prototype = Object.create( Points.prototype );
+UI.Points2.prototype.constructor = UI.Points2;
+
+UI.Points2.addRow = function () {
+
+	if ( this.pointsUI.length === 0 ) {
+
+		this.pointsList.add( this.createPointRow( 0, 0 ) );
+
+	} else {
+
+		var point = this.pointsUI[ this.pointsUI.length - 1 ];
+
+		this.pointsList.add( this.createPointRow( point.x.getValue(), point.y.getValue() ) );
+
+	}
+
+	this.update();
+
+};
+
+UI.Points2.prototype.getValue = function () {
+
+	var points = [];
+	var count = 0;
+
+	for ( var i = 0; i < this.pointsUI.length; i ++ ) {
+
+		var pointUI = this.pointsUI[ i ];
+
+		if ( ! pointUI ) continue;
+
+		points.push( new THREE.Vector2( pointUI.x.getValue(), pointUI.y.getValue() ) );
+		++ count;
+		pointUI.lbl.setValue( count );
+
+	}
+
+	return points;
+
+};
+
+UI.Points2.prototype.setValue = function ( points ) {
+
+	this.clear();
+
+	for ( var i = 0; i < points.length; i ++ ) {
+
+		var point = points[ i ];
+		this.pointsList.add( this.createPointRow( point.x, point.y ) );
+
+	}
+
+	this.update();
+	return this;
+
+};
+
+UI.Points2.prototype.createPointRow = function ( x, y ) {
+
+	var pointRow = new UI.Div();
+	var lbl = new UI.Text( this.lastPointIdx + 1 ).setWidth( '20px' );
+	var txtX = new UI.Number( x ).setWidth( '30px' ).onChange( this.update );
+	var txtY = new UI.Number( y ).setWidth( '30px' ).onChange( this.update );
+
+	var idx = this.lastPointIdx;
+	var scope = this;
+	var btn = new UI.Button( '-' ).onClick( function () {
+
+		if ( scope.isEditing ) return;
+		scope.deletePointRow( idx );
+
+	} );
+
+	this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
+	++ this.lastPointIdx;
+	pointRow.add( lbl, txtX, txtY, btn );
+
+	return pointRow;
+
+};
+
+UI.Points3 = function () {
+
+	Points.call( this, UI.Points3.addRow.bind( this ) );
+
+	return this;
+
+};
+
+UI.Points3.prototype = Object.create( Points.prototype );
+UI.Points3.prototype.constructor = UI.Points3;
+
+UI.Points3.addRow = function () {
+
+	if ( this.pointsUI.length === 0 ) {
+
+		this.pointsList.add( this.createPointRow( 0, 0, 0 ) );
+
+	} else {
+
+		var point = this.pointsUI[ this.pointsUI.length - 1 ];
+
+		this.pointsList.add( this.createPointRow( point.x.getValue(), point.y.getValue(), point.z.getValue() ) );
+
+	}
+
+	this.update();
+
+};
+
+UI.Points3.prototype.getValue = function () {
+
+	var points = [];
+	var count = 0;
+
+	for ( var i = 0; i < this.pointsUI.length; i ++ ) {
+
+		var pointUI = this.pointsUI[ i ];
+
+		if ( ! pointUI ) continue;
+
+		points.push( new THREE.Vector3( pointUI.x.getValue(), pointUI.y.getValue(), pointUI.z.getValue() ) );
+		++ count;
+		pointUI.lbl.setValue( count );
+
+	}
+
+	return points;
+
+};
+
+UI.Points3.prototype.setValue = function ( points ) {
+
+	this.clear();
+
+	for ( var i = 0; i < points.length; i ++ ) {
+
+		var point = points[ i ];
+		this.pointsList.add( this.createPointRow( point.x, point.y, point.z ) );
+
+	}
+
+	this.update();
+	return this;
+
+};
+
+UI.Points3.prototype.createPointRow = function ( x, y, z ) {
+
+	var pointRow = new UI.Div();
+	var lbl = new UI.Text( this.lastPointIdx + 1 ).setWidth( '20px' );
+	var txtX = new UI.Number( x ).setWidth( '30px' ).onChange( this.update );
+	var txtY = new UI.Number( y ).setWidth( '30px' ).onChange( this.update );
+	var txtZ = new UI.Number( z ).setWidth( '30px' ).onChange( this.update );
+
+	var idx = this.lastPointIdx;
+	var scope = this;
+	var btn = new UI.Button( '-' ).onClick( function () {
+
+		if ( scope.isEditing ) return;
+		scope.deletePointRow( idx );
+
+	} );
+
+	this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY, z: txtZ } );
+	++ this.lastPointIdx;
+	pointRow.add( lbl, txtX, txtY, txtZ, btn );
+
+	return pointRow;
+
+};
+
 UI.THREE = {};
 
 UI.THREE.Boolean = function ( boolean, text ) {

+ 0 - 1
examples/files.js

@@ -45,7 +45,6 @@ var files = {
 		"webgl_geometry_text",
 		"webgl_geometry_text_shapes",
 		"webgl_geometry_text_stroke",
-		"webgl_hdr",
 		"webgl_helpers",
 		"webgl_interactive_buffergeometry",
 		"webgl_interactive_cubes",

+ 2 - 2
examples/js/cameras/CinematicCamera.js

@@ -186,14 +186,14 @@ THREE.CinematicCamera.prototype.renderCinematic = function ( scene, renderer ) {
 		scene.overrideMaterial = null;
 		renderer.setRenderTarget( this.postprocessing.rtTextureColor );
 		renderer.clear();
-		renderer.render( scene, camera );
+		renderer.render( scene, this );
 
 		// Render depth into texture
 
 		scene.overrideMaterial = this.materialDepth;
 		renderer.setRenderTarget( this.postprocessing.rtTextureDepth );
 		renderer.clear();
-		renderer.render( scene, camera );
+		renderer.render( scene, this );
 
 		// Render bokeh composite
 

+ 1 - 0
examples/js/controls/TransformControls.js

@@ -119,6 +119,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 		domElement.removeEventListener( "mousedown", onPointerDown );
 		domElement.removeEventListener( "touchstart", onPointerDown );
 		domElement.removeEventListener( "mousemove", onPointerHover );
+		document.removeEventListener( "mousemove", onPointerMove );
 		domElement.removeEventListener( "touchmove", onPointerHover );
 		domElement.removeEventListener( "touchmove", onPointerMove );
 		document.removeEventListener( "mouseup", onPointerUp );

+ 8 - 8
examples/js/curves/NURBSUtils.js

@@ -22,7 +22,7 @@ THREE.NURBSUtils = {
 
 	returns the span
 	*/
-	findSpan: function( p,  u,  U ) {
+	findSpan: function ( p, u, U ) {
 
 		var n = U.length - p - 1;
 
@@ -73,7 +73,7 @@ THREE.NURBSUtils = {
 
 	returns array[p+1] with basis functions values.
 	*/
-	calcBasisFunctions: function( span, u, p, U ) {
+	calcBasisFunctions: function ( span, u, p, U ) {
 
 		var N = [];
 		var left = [];
@@ -116,7 +116,7 @@ THREE.NURBSUtils = {
 
 	returns point for given u
 	*/
-	calcBSplinePoint: function( p, U, P, u ) {
+	calcBSplinePoint: function ( p, U, P, u ) {
 
 		var span = this.findSpan( p, u, U );
 		var N = this.calcBasisFunctions( span, u, p, U );
@@ -150,7 +150,7 @@ THREE.NURBSUtils = {
 
 	returns array[n+1][p+1] with basis functions derivatives
 	*/
-	calcBasisFunctionDerivatives: function( span,  u,  p,  n,  U ) {
+	calcBasisFunctionDerivatives: function ( span, u, p, n, U ) {
 
 		var zeroArr = [];
 		for ( var i = 0; i <= p; ++ i )
@@ -225,7 +225,7 @@ THREE.NURBSUtils = {
 				}
 
 				var j1 = ( rk >= - 1 ) ? 1 : - rk;
-				var j2 = ( r - 1 <= pk ) ? k - 1 :  p - r;
+				var j2 = ( r - 1 <= pk ) ? k - 1 : p - r;
 
 				for ( var j = j1; j <= j2; ++ j ) {
 
@@ -280,7 +280,7 @@ THREE.NURBSUtils = {
 
 		returns array[d+1] with derivatives
 		*/
-	calcBSplineDerivatives: function( p,  U,  P,  u,  nd ) {
+	calcBSplineDerivatives: function ( p, U, P, u, nd ) {
 
 		var du = nd < p ? nd : p;
 		var CK = [];
@@ -330,7 +330,7 @@ THREE.NURBSUtils = {
 
 	returns k!/(i!(k-i)!)
 	*/
-	calcKoverI: function( k, i ) {
+	calcKoverI: function ( k, i ) {
 
 		var nom = 1;
 
@@ -412,7 +412,7 @@ THREE.NURBSUtils = {
 
 	returns array with derivatives.
 	*/
-	calcNURBSDerivatives: function( p,  U,  P,  u,  nd ) {
+	calcNURBSDerivatives: function ( p, U, P, u, nd ) {
 
 		var Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
 		return this.calcRationalCurveDerivatives( Pders );

+ 157 - 0
examples/js/geometries/Hilbert.js

@@ -0,0 +1,157 @@
+/**
+ * Hilbert Curves.
+ *
+ * @author Dylan Grafmyre
+ */
+
+THREE.Hilbert = {
+
+	/**
+	 * Generates 2D-Coordinates in a very fast way.
+	 *
+	 * @author Dylan Grafmyre
+	 *
+	 * Based on work by:
+	 * @author Thomas Diewald
+	 * @link http://www.openprocessing.org/sketch/15493
+	 *
+	 * @param center     Center of Hilbert curve.
+	 * @param size       Total width of Hilbert curve.
+	 * @param iterations Number of subdivisions.
+	 * @param v0         Corner index -X, -Z.
+	 * @param v1         Corner index -X, +Z.
+	 * @param v2         Corner index +X, +Z.
+	 * @param v3         Corner index +X, -Z.
+	 */
+	generate2D( center, size, iterations, v0, v1, v2, v3 ) {
+
+		// Default Vars
+		var center = center !== undefined ? center : new THREE.Vector3( 0, 0, 0 ),
+			size = size !== undefined ? size : 10,
+			half = size / 2,
+			iterations = iterations !== undefined ? iterations : 1,
+			v0 = v0 !== undefined ? v0 : 0,
+			v1 = v1 !== undefined ? v1 : 1,
+			v2 = v2 !== undefined ? v2 : 2,
+			v3 = v3 !== undefined ? v3 : 3
+		;
+
+		var vec_s = [
+			new THREE.Vector3( center.x - half, center.y, center.z - half ),
+			new THREE.Vector3( center.x - half, center.y, center.z + half ),
+			new THREE.Vector3( center.x + half, center.y, center.z + half ),
+			new THREE.Vector3( center.x + half, center.y, center.z - half )
+		];
+
+		var vec = [
+			vec_s[ v0 ],
+			vec_s[ v1 ],
+			vec_s[ v2 ],
+			vec_s[ v3 ]
+		];
+
+		// Recurse iterations
+		if ( 0 <= -- iterations ) {
+
+			var tmp = [];
+
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 ) );
+
+			// Return recursive call
+			return tmp;
+
+		}
+
+		// Return complete Hilbert Curve.
+		return vec;
+
+	},
+
+	/**
+	 * Generates 3D-Coordinates in a very fast way.
+	 *
+	 * @author Dylan Grafmyre
+	 *
+	 * Based on work by:
+	 * @author Thomas Diewald
+	 * @link http://www.openprocessing.org/visuals/?visualID=15599
+	 *
+	 * @param center     Center of Hilbert curve.
+	 * @param size       Total width of Hilbert curve.
+	 * @param iterations Number of subdivisions.
+	 * @param v0         Corner index -X, +Y, -Z.
+	 * @param v1         Corner index -X, +Y, +Z.
+	 * @param v2         Corner index -X, -Y, +Z.
+	 * @param v3         Corner index -X, -Y, -Z.
+	 * @param v4         Corner index +X, -Y, -Z.
+	 * @param v5         Corner index +X, -Y, +Z.
+	 * @param v6         Corner index +X, +Y, +Z.
+	 * @param v7         Corner index +X, +Y, -Z.
+	 */
+	generate3D( center, size, iterations, v0, v1, v2, v3, v4, v5, v6, v7 ) {
+
+		// Default Vars
+		var center = center !== undefined ? center : new THREE.Vector3( 0, 0, 0 ),
+			size = size !== undefined ? size : 10,
+			half = size / 2,
+			iterations = iterations !== undefined ? iterations : 1,
+			v0 = v0 !== undefined ? v0 : 0,
+			v1 = v1 !== undefined ? v1 : 1,
+			v2 = v2 !== undefined ? v2 : 2,
+			v3 = v3 !== undefined ? v3 : 3,
+			v4 = v4 !== undefined ? v4 : 4,
+			v5 = v5 !== undefined ? v5 : 5,
+			v6 = v6 !== undefined ? v6 : 6,
+			v7 = v7 !== undefined ? v7 : 7
+		;
+
+		var vec_s = [
+			new THREE.Vector3( center.x - half, center.y + half, center.z - half ),
+			new THREE.Vector3( center.x - half, center.y + half, center.z + half ),
+			new THREE.Vector3( center.x - half, center.y - half, center.z + half ),
+			new THREE.Vector3( center.x - half, center.y - half, center.z - half ),
+			new THREE.Vector3( center.x + half, center.y - half, center.z - half ),
+			new THREE.Vector3( center.x + half, center.y - half, center.z + half ),
+			new THREE.Vector3( center.x + half, center.y + half, center.z + half ),
+			new THREE.Vector3( center.x + half, center.y + half, center.z - half )
+		];
+
+		var vec = [
+			vec_s[ v0 ],
+			vec_s[ v1 ],
+			vec_s[ v2 ],
+			vec_s[ v3 ],
+			vec_s[ v4 ],
+			vec_s[ v5 ],
+			vec_s[ v6 ],
+			vec_s[ v7 ]
+		];
+
+		// Recurse iterations
+		if ( -- iterations >= 0 ) {
+
+			var tmp = [];
+
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
+			Array.prototype.push.apply( tmp, THREE.Hilbert.generate3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 ) );
+
+			// Return recursive call
+			return tmp;
+
+		}
+
+		// Return complete Hilbert Curve.
+		return vec;
+
+	}
+
+};

+ 0 - 64
examples/js/geometries/hilbert2D.js

@@ -1,64 +0,0 @@
-/**
- * Hilbert Curve: Generates 2D-Coordinates in a very fast way.
- *
- * @author Dylan Grafmyre
- *
- * Based on work by:
- * @author Thomas Diewald
- * @link http://www.openprocessing.org/sketch/15493
- *
- * @param center     Center of Hilbert curve.
- * @param size       Total width of Hilbert curve.
- * @param iterations Number of subdivisions.
- * @param v0         Corner index -X, -Z.
- * @param v1         Corner index -X, +Z.
- * @param v2         Corner index +X, +Z.
- * @param v3         Corner index +X, -Z.
- */
-
-function hilbert2D( center, size, iterations, v0, v1, v2, v3 ) {
-
-	// Default Vars
-	var center = center !== undefined ? center : new THREE.Vector3( 0, 0, 0 ),
-		size = size !== undefined ? size : 10,
-		half = size / 2,
-		iterations = iterations !== undefined ? iterations : 1,
-		v0 = v0 !== undefined ? v0 : 0,
-		v1 = v1 !== undefined ? v1 : 1,
-		v2 = v2 !== undefined ? v2 : 2,
-		v3 = v3 !== undefined ? v3 : 3
-	;
-
-	var vec_s = [
-		new THREE.Vector3( center.x - half, center.y, center.z - half ),
-		new THREE.Vector3( center.x - half, center.y, center.z + half ),
-		new THREE.Vector3( center.x + half, center.y, center.z + half ),
-		new THREE.Vector3( center.x + half, center.y, center.z - half )
-	];
-
-	var vec = [
-		vec_s[ v0 ],
-		vec_s[ v1 ],
-		vec_s[ v2 ],
-		vec_s[ v3 ]
-	];
-
-	// Recurse iterations
-	if ( 0 <= -- iterations ) {
-
-		var tmp = [];
-
-		Array.prototype.push.apply( tmp, hilbert2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ) );
-		Array.prototype.push.apply( tmp, hilbert2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ) );
-		Array.prototype.push.apply( tmp, hilbert2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ) );
-		Array.prototype.push.apply( tmp, hilbert2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 ) );
-
-		// Return recursive call
-		return tmp;
-
-	}
-
-	// Return complete Hilbert Curve.
-	return vec;
-
-}

+ 0 - 84
examples/js/geometries/hilbert3D.js

@@ -1,84 +0,0 @@
-/**
- * Hilbert Curve: Generates 2D-Coordinates in a very fast way.
- *
- * @author Dylan Grafmyre
- *
- * Based on work by:
- * @author Thomas Diewald
- * @link http://www.openprocessing.org/visuals/?visualID=15599
- *
- * @param center     Center of Hilbert curve.
- * @param size       Total width of Hilbert curve.
- * @param iterations Number of subdivisions.
- * @param v0         Corner index -X, +Y, -Z.
- * @param v1         Corner index -X, +Y, +Z.
- * @param v2         Corner index -X, -Y, +Z.
- * @param v3         Corner index -X, -Y, -Z.
- * @param v4         Corner index +X, -Y, -Z.
- * @param v5         Corner index +X, -Y, +Z.
- * @param v6         Corner index +X, +Y, +Z.
- * @param v7         Corner index +X, +Y, -Z.
- */
-
-function hilbert3D( center, size, iterations, v0, v1, v2, v3, v4, v5, v6, v7 ) {
-
-	// Default Vars
-	var center = center !== undefined ? center : new THREE.Vector3( 0, 0, 0 ),
-		size = size !== undefined ? size : 10,
-		half = size / 2,
-		iterations = iterations !== undefined ? iterations : 1,
-		v0 = v0 !== undefined ? v0 : 0,
-		v1 = v1 !== undefined ? v1 : 1,
-		v2 = v2 !== undefined ? v2 : 2,
-		v3 = v3 !== undefined ? v3 : 3,
-		v4 = v4 !== undefined ? v4 : 4,
-		v5 = v5 !== undefined ? v5 : 5,
-		v6 = v6 !== undefined ? v6 : 6,
-		v7 = v7 !== undefined ? v7 : 7
-	;
-
-	var vec_s = [
-		new THREE.Vector3( center.x - half, center.y + half, center.z - half ),
-		new THREE.Vector3( center.x - half, center.y + half, center.z + half ),
-		new THREE.Vector3( center.x - half, center.y - half, center.z + half ),
-		new THREE.Vector3( center.x - half, center.y - half, center.z - half ),
-		new THREE.Vector3( center.x + half, center.y - half, center.z - half ),
-		new THREE.Vector3( center.x + half, center.y - half, center.z + half ),
-		new THREE.Vector3( center.x + half, center.y + half, center.z + half ),
-		new THREE.Vector3( center.x + half, center.y + half, center.z - half )
-	];
-
-	var vec = [
-		vec_s[ v0 ],
-		vec_s[ v1 ],
-		vec_s[ v2 ],
-		vec_s[ v3 ],
-		vec_s[ v4 ],
-		vec_s[ v5 ],
-		vec_s[ v6 ],
-		vec_s[ v7 ]
-	];
-
-	// Recurse iterations
-	if ( -- iterations >= 0 ) {
-
-		var tmp = [];
-
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
-		Array.prototype.push.apply( tmp, hilbert3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 ) );
-
-		// Return recursive call
-		return tmp;
-
-	}
-
-	// Return complete Hilbert Curve.
-	return vec;
-
-}

+ 43 - 0
examples/js/libs/basis/README.md

@@ -0,0 +1,43 @@
+# Basis Universal GPU Texture Compression
+
+Basis Universal is a "[supercompressed](http://gamma.cs.unc.edu/GST/gst.pdf)"
+GPU texture and texture video compression system that outputs a highly
+compressed intermediate file format (.basis) that can be quickly transcoded to
+a wide variety of GPU texture compression formats.
+
+[GitHub](https://github.com/BinomialLLC/basis_universal)
+
+## Contents
+
+This folder contains two files:
+
+* `basis_transcoder.js` — JavaScript wrapper for the WebAssembly transcoder.
+* `basis_transcoder.wasm` — WebAssembly transcoder.
+
+Both are dependencies of `THREE.BasisTextureLoader`:
+
+```js
+var basisLoader = new THREE.BasisTextureLoader();
+basisLoader.setTranscoderPath( 'examples/js/libs/basis/' );
+basisLoader.detectSupport( renderer );
+basisLoader.load( 'diffuse.basis', function ( texture ) {
+
+  var material = new THREE.MeshStandardMaterial( { map: texture } );
+
+}, function () {
+
+  console.log( 'onProgress' );
+
+}, function ( e ) {
+
+  console.error( e );
+
+} );
+```
+
+For further documentation about the Basis compressor and transcoder, refer to
+the [Basis GitHub repository](https://github.com/BinomialLLC/basis_universal).
+
+## License
+
+[Apache License 2.0](https://github.com/BinomialLLC/basis_universal/blob/master/LICENSE)

File diff suppressed because it is too large
+ 0 - 0
examples/js/libs/basis/basis_transcoder.js


BIN
examples/js/libs/basis/basis_transcoder.wasm


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

@@ -773,8 +773,10 @@ THREE.AssimpLoader.prototype = {
 					mesh = new THREE.Mesh( geometry, mat );
 
 				if ( this.mBones.length > 0 ) {
+
 					mesh = new THREE.SkinnedMesh( geometry, mat );
 					mesh.normalizeSkinWeights();
+
 				}
 
 				this.threeNode = mesh;
@@ -1853,7 +1855,7 @@ THREE.AssimpLoader.prototype = {
 
 				} else {
 
-				// else write as usual
+					// else write as usual
 
 					mesh.mTextureCoords[ n ] = [];
 					//note that assbin always writes 3d texcoords

+ 402 - 0
examples/js/loaders/BasisTextureLoader.js

@@ -0,0 +1,402 @@
+/**
+ * @author Don McCurdy / https://www.donmccurdy.com
+ * @author Austin Eng / https://github.com/austinEng
+ * @author Shrek Shao / https://github.com/shrekshao
+ */
+
+/**
+ * Loader for Basis Universal GPU Texture Codec.
+ *
+ * Basis Universal is a "supercompressed" GPU texture and texture video
+ * compression system that outputs a highly compressed intermediate file format
+ * (.basis) that can be quickly transcoded to a wide variety of GPU texture
+ * compression formats.
+ *
+ * This loader parallelizes the transcoding process across a configurable number
+ * of web workers, before transferring the transcoded compressed texture back
+ * to the main thread.
+ */
+// TODO(donmccurdy): Don't use ES6 classes.
+THREE.BasisTextureLoader = class BasisTextureLoader {
+
+	constructor ( manager ) {
+
+		// TODO(donmccurdy): Loading manager is unused.
+		this.manager = manager || THREE.DefaultLoadingManager;
+
+		this.transcoderPath = '';
+		this.transcoderBinary = null;
+		this.transcoderPending = null;
+
+		this.workerLimit = 4;
+		this.workerPool = [];
+		this.workerNextTaskID = 1;
+		this.workerSourceURL = '';
+		this.workerConfig = {
+			format: null,
+			etcSupported: false,
+			dxtSupported: false,
+			pvrtcSupported: false,
+		};
+
+	}
+
+	setTranscoderPath ( path ) {
+
+		this.transcoderPath = path;
+
+	}
+
+	detectSupport ( renderer ) {
+
+		var context = renderer.context;
+		var config = this.workerConfig;
+
+		config.etcSupported = !! context.getExtension('WEBGL_compressed_texture_etc1');
+		config.dxtSupported = !! context.getExtension('WEBGL_compressed_texture_s3tc');
+		config.pvrtcSupported = !! context.getExtension('WEBGL_compressed_texture_pvrtc')
+			|| !! context.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
+
+		if ( config.etcSupported ) {
+
+			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1;
+
+		} else if ( config.dxtSupported ) {
+
+			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1;
+
+		} else if ( config.pvrtcSupported ) {
+
+			config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY;
+
+		} else {
+
+			throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
+
+		}
+
+		return this;
+
+	}
+
+	load ( url, onLoad, onProgress, onError ) {
+
+		// TODO(donmccurdy): Use THREE.FileLoader.
+		fetch( url )
+			.then( ( res ) => res.arrayBuffer() )
+			.then( ( buffer ) => this._createTexture( buffer ) )
+			.then( onLoad )
+			.catch( onError );
+
+	}
+
+	/**
+	 * @param  {ArrayBuffer} buffer
+	 * @return {Promise<THREE.CompressedTexture>}
+	 */
+	_createTexture ( buffer ) {
+
+		return this.getWorker()
+			.then( ( worker ) => {
+
+				return new Promise( ( resolve ) => {
+
+					var taskID = this.workerNextTaskID++;
+
+					worker._callbacks[ taskID ] = resolve;
+					worker._taskCosts[ taskID ] = buffer.byteLength;
+					worker._taskLoad += worker._taskCosts[ taskID ];
+					worker._taskCount++;
+
+					worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
+
+				} );
+
+			} )
+			.then( ( message ) => {
+
+				var config = this.workerConfig;
+
+				var { data, width, height } = message;
+
+				var mipmaps = [ { data, width, height } ];
+
+				var texture;
+
+				if ( config.etcSupported ) {
+
+					texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
+
+				} else if ( config.dxtSupported ) {
+
+					texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], THREE.UnsignedByteType );
+
+				} else if ( config.pvrtcSupported ) {
+
+					texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
+
+				} else {
+
+					throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
+
+				}
+
+				texture.minFilter = THREE.LinearMipMapLinearFilter;
+				texture.magFilter = THREE.LinearFilter;
+				texture.encoding = THREE.sRGBEncoding;
+				texture.generateMipmaps = false;
+				texture.flipY = false;
+				texture.needsUpdate = true;
+
+				return texture;
+
+			});
+
+	}
+
+	_initTranscoder () {
+
+		if ( ! this.transcoderBinary ) {
+
+			// TODO(donmccurdy): Use THREE.FileLoader.
+			var jsContent = fetch( this.transcoderPath + 'basis_transcoder.js' )
+				.then( ( response ) => response.text() );
+
+			var binaryContent = fetch( this.transcoderPath + 'basis_transcoder.wasm' )
+				.then( ( response ) => response.arrayBuffer() );
+
+			this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
+				.then( ( [ jsContent, binaryContent ] ) => {
+
+					var fn = THREE.BasisTextureLoader.BasisWorker.toString();
+
+					var body = [
+						'/* basis_transcoder.js */',
+						'var Module;',
+						'function createBasisModule () {',
+						'  ' + jsContent,
+						'  return Module;',
+						'}',
+						'',
+						'/* worker */',
+						fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
+					].join( '\n' );
+
+					this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
+					this.transcoderBinary = binaryContent;
+
+				} );
+
+		}
+
+		return this.transcoderPending;
+
+	}
+
+	getWorker () {
+
+		return this._initTranscoder().then( () => {
+
+			if ( this.workerPool.length < this.workerLimit ) {
+
+				var worker = new Worker( this.workerSourceURL );
+
+				worker._callbacks = {};
+				worker._taskCosts = {};
+				worker._taskLoad = 0;
+				worker._taskCount = 0;
+
+				worker.postMessage( {
+					type: 'init',
+					config: this.workerConfig,
+					transcoderBinary: this.transcoderBinary,
+				} );
+
+				worker.onmessage = function ( e ) {
+
+					var message = e.data;
+
+					switch ( message.type ) {
+
+						case 'transcode':
+							worker._callbacks[ message.id ]( message );
+							worker._taskLoad -= worker._taskCosts[ message.id ];
+							delete worker._callbacks[ message.id ];
+							delete worker._taskCosts[ message.id ];
+							break;
+
+						default:
+							throw new Error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
+
+					}
+
+				}
+
+				this.workerPool.push( worker );
+
+			} else {
+
+				this.workerPool.sort( function ( a, b ) { return a._taskLoad > b._taskLoad ? -1 : 1; } );
+
+			}
+
+			return this.workerPool[ this.workerPool.length - 1 ];
+
+		} );
+
+	}
+
+	dispose () {
+
+		for ( var i = 0; i < this.workerPool.length; i++ ) {
+
+			this.workerPool[ i ].terminate();
+
+		}
+
+		this.workerPool.length = 0;
+
+	}
+}
+
+/* CONSTANTS */
+
+THREE.BasisTextureLoader.BASIS_FORMAT = {
+	cTFETC1: 0,
+	cTFBC1: 1,
+	cTFBC4: 2,
+	cTFPVRTC1_4_OPAQUE_ONLY: 3,
+	cTFBC7_M6_OPAQUE_ONLY: 4,
+	cTFETC2: 5,
+	cTFBC3: 6,
+	cTFBC5: 7,
+};
+
+// DXT formats, from:
+// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+THREE.BasisTextureLoader.DXT_FORMAT = {
+	COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0,
+	COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1,
+	COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2,
+	COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3,
+};
+THREE.BasisTextureLoader.DXT_FORMAT_MAP = {};
+THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] =
+	THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
+THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] =
+	THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+/* WEB WORKER */
+
+THREE.BasisTextureLoader.BasisWorker = function () {
+	var config;
+	var transcoderPending;
+	var _BasisFile;
+
+	onmessage = function ( e ) {
+
+		var message = e.data;
+
+		switch ( message.type ) {
+
+			case 'init':
+				config = message.config;
+				init( message.transcoderBinary );
+				break;
+
+			case 'transcode':
+				transcoderPending.then( () => {
+
+					var { data, width, height } = transcode( message.buffer );
+
+					self.postMessage( { type: 'transcode', id: message.id, data, width, height }, [ data.buffer ] );
+
+				} );
+				break;
+
+		}
+
+	};
+
+	function init ( wasmBinary ) {
+
+		transcoderPending = new Promise( ( resolve ) => {
+
+			// The 'Module' global is used by the Basis wrapper, which will check for
+			// the 'wasmBinary' property before trying to load the file itself.
+
+			// TODO(donmccurdy): This only works with a modified version of the
+			// emscripten-generated wrapper. The default seems to have a bug making it
+			// impossible to override the WASM binary.
+			Module = { wasmBinary, onRuntimeInitialized: resolve };
+
+		} ).then( () => {
+
+			var { BasisFile, initializeBasis } = Module;
+
+			_BasisFile = BasisFile;
+
+			initializeBasis();
+
+		} );
+
+		createBasisModule();
+
+	}
+
+	function transcode ( buffer ) {
+
+		var basisFile = new _BasisFile( new Uint8Array( buffer ) );
+
+		var width = basisFile.getImageWidth( 0, 0 );
+		var height = basisFile.getImageHeight( 0, 0 );
+		var images = basisFile.getNumImages();
+		var levels = basisFile.getNumLevels( 0 );
+
+		function cleanup () {
+
+			basisFile.close();
+			basisFile.delete();
+
+		}
+
+		if ( ! width || ! height || ! images || ! levels ) {
+
+			cleanup();
+			throw new Error( 'THREE.BasisTextureLoader:  Invalid .basis file' );
+
+		}
+
+		if ( ! basisFile.startTranscoding() ) {
+
+			cleanup();
+			throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
+
+		}
+
+		var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, 0, config.format ) );
+
+		var startTime = performance.now();
+
+		var status = basisFile.transcodeImage(
+			dst,
+			0,
+			0,
+			config.format,
+			config.etcSupported ? 0 : ( config.dxtSupported ? 1 : 0 ),
+			0
+		);
+
+		cleanup();
+
+		if ( ! status ) {
+
+			throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
+
+		}
+
+		return { data: dst, width, height };
+
+	}
+
+};

+ 26 - 17
examples/js/loaders/EXRLoader.js

@@ -83,14 +83,14 @@ THREE.EXRLoader.prototype = Object.create( THREE.DataTextureLoader.prototype );
 
 THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
-	const USHORT_RANGE = (1 << 16);
-	const BITMAP_SIZE = (USHORT_RANGE >> 3);
+	const USHORT_RANGE = ( 1 << 16 );
+	const BITMAP_SIZE = ( USHORT_RANGE >> 3 );
 
-	const HUF_ENCBITS = 16;  // literal (value) bit length
-	const HUF_DECBITS = 14;  // decoding bit size (>= 8)
+	const HUF_ENCBITS = 16; // literal (value) bit length
+	const HUF_DECBITS = 14; // decoding bit size (>= 8)
 
-	const HUF_ENCSIZE = (1 << HUF_ENCBITS) + 1;  // encoding table size
-	const HUF_DECSIZE = 1 << HUF_DECBITS;        // decoding table size
+	const HUF_ENCSIZE = ( 1 << HUF_ENCBITS ) + 1; // encoding table size
+	const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
 	const HUF_DECMASK = HUF_DECSIZE - 1;
 
 	const SHORT_ZEROCODE_RUN = 59;
@@ -157,6 +157,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 		getBitsReturn.l = ( c >> lc ) & ( ( 1 << nBits ) - 1 );
 		getBitsReturn.c = c;
 		getBitsReturn.lc = lc;
+
 	}
 
 	const hufTableBuffer = new Array( 59 );
@@ -249,9 +250,17 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	}
 
-	function hufLength( code ) { return code & 63; }
+	function hufLength( code ) {
+
+		return code & 63;
+
+	}
 
-	function hufCode( code ) { return code >> 6; }
+	function hufCode( code ) {
+
+		return code >> 6;
+
+	}
 
 	function hufBuildDecTable( hcode, im, iM, hdecod ) {
 
@@ -355,7 +364,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 			lc -= 8;
 
 			var cs = ( c >> lc );
-			var cs = new Uint8Array([cs])[0];
+			var cs = new Uint8Array( [ cs ] )[ 0 ];
 
 			if ( outBufferOffset.value + cs > outBufferEndOffset ) {
 
@@ -365,7 +374,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 			var s = outBuffer[ outBufferOffset.value - 1 ];
 
-			while ( cs-- > 0 ) {
+			while ( cs -- > 0 ) {
 
 				outBuffer[ outBufferOffset.value ++ ] = s;
 
@@ -805,7 +814,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	function parseUint32( dataView, offset ) {
 
-		var Uint32 = dataView.getUint32(offset.value, true);
+		var Uint32 = dataView.getUint32( offset.value, true );
 
 		offset.value = offset.value + INT32_SIZE;
 
@@ -815,7 +824,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	function parseUint8Array( uInt8Array, offset ) {
 
-		var Uint8 = uInt8Array[offset.value];
+		var Uint8 = uInt8Array[ offset.value ];
 
 		offset.value = offset.value + INT8_SIZE;
 
@@ -825,7 +834,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	function parseUint8( dataView, offset ) {
 
-		var Uint8 = dataView.getUint8(offset.value);
+		var Uint8 = dataView.getUint8( offset.value );
 
 		offset.value = offset.value + INT8_SIZE;
 
@@ -835,7 +844,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	function parseFloat32( dataView, offset ) {
 
-		var float = dataView.getFloat32(offset.value, true);
+		var float = dataView.getFloat32( offset.value, true );
 
 		offset.value += FLOAT32_SIZE;
 
@@ -873,7 +882,7 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	function parseFloat16( buffer, offset ) {
 
-		return decodeFloat16( parseUint16( buffer, offset) );
+		return decodeFloat16( parseUint16( buffer, offset ) );
 
 	}
 
@@ -1021,8 +1030,8 @@ THREE.EXRLoader.prototype._parser = function ( buffer ) {
 
 	}
 
-	var bufferDataView = new DataView(buffer);
-	var uInt8Array = new Uint8Array(buffer);
+	var bufferDataView = new DataView( buffer );
+	var uInt8Array = new Uint8Array( buffer );
 
 	var EXRHeader = {};
 

+ 127 - 122
examples/js/loaders/GCodeLoader.js

@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * THREE.GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications.
  *
@@ -10,6 +8,7 @@
  * @author tentone
  * @author joewalnes
  */
+
 THREE.GCodeLoader = function ( manager ) {
 
 	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
@@ -18,208 +17,214 @@ THREE.GCodeLoader = function ( manager ) {
 
 };
 
-THREE.GCodeLoader.prototype.load = function ( url, onLoad, onProgress, onError ) {
+THREE.GCodeLoader.prototype = {
 
-	var self = this;
+	constructor: THREE.GCodeLoader,
 
-	var loader = new THREE.FileLoader( self.manager );
-	loader.setPath( self.path );
-	loader.load( url, function ( text ) {
+	load: function ( url, onLoad, onProgress, onError ) {
 
-		onLoad( self.parse( text ) );
+		var self = this;
 
-	}, onProgress, onError );
+		var loader = new THREE.FileLoader( self.manager );
+		loader.setPath( self.path );
+		loader.load( url, function ( text ) {
 
-};
+			onLoad( self.parse( text ) );
 
-THREE.GCodeLoader.prototype.setPath = function ( value ) {
+		}, onProgress, onError );
 
-	this.path = value;
-	return this;
+	},
 
-};
+	setPath: function ( value ) {
 
-THREE.GCodeLoader.prototype.parse = function ( data ) {
+		this.path = value;
+		return this;
 
-	var state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
-	var layers = [];
+	},
 
-	var currentLayer = undefined;
+	parse: function ( data ) {
 
-	var pathMaterial = new THREE.LineBasicMaterial( { color: 0xFF0000 } );
-	pathMaterial.name = 'path';
+		var state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
+		var layers = [];
 
-	var extrudingMaterial = new THREE.LineBasicMaterial( { color: 0x00FF00 } );
-	extrudingMaterial.name = 'extruded';
+		var currentLayer = undefined;
 
-	function newLayer( line ) {
+		var pathMaterial = new THREE.LineBasicMaterial( { color: 0xFF0000 } );
+		pathMaterial.name = 'path';
 
-		currentLayer = { vertex: [], pathVertex: [], z: line.z };
-		layers.push( currentLayer );
+		var extrudingMaterial = new THREE.LineBasicMaterial( { color: 0x00FF00 } );
+		extrudingMaterial.name = 'extruded';
 
-	}
+		function newLayer( line ) {
 
-	//Create lie segment between p1 and p2
-	function addSegment( p1, p2 ) {
+			currentLayer = { vertex: [], pathVertex: [], z: line.z };
+			layers.push( currentLayer );
 
-		if ( currentLayer === undefined ) {
+		}
 
-			newLayer( p1 );
+		//Create lie segment between p1 and p2
+		function addSegment( p1, p2 ) {
 
-		}
+			if ( currentLayer === undefined ) {
 
-		if ( line.extruding ) {
+				newLayer( p1 );
 
-			currentLayer.vertex.push( p1.x, p1.y, p1.z );
-			currentLayer.vertex.push( p2.x, p2.y, p2.z );
+			}
 
-		} else {
+			if ( line.extruding ) {
 
-			currentLayer.pathVertex.push( p1.x, p1.y, p1.z );
-			currentLayer.pathVertex.push( p2.x, p2.y, p2.z );
+				currentLayer.vertex.push( p1.x, p1.y, p1.z );
+				currentLayer.vertex.push( p2.x, p2.y, p2.z );
+
+			} else {
+
+				currentLayer.pathVertex.push( p1.x, p1.y, p1.z );
+				currentLayer.pathVertex.push( p2.x, p2.y, p2.z );
+
+			}
 
 		}
 
-	}
+		function delta( v1, v2 ) {
 
-	function delta( v1, v2 ) {
+			return state.relative ? v2 : v2 - v1;
 
-		return state.relative ? v2 : v2 - v1;
+		}
 
-	}
+		function absolute( v1, v2 ) {
 
-	function absolute( v1, v2 ) {
+			return state.relative ? v1 + v2 : v2;
 
-		return state.relative ? v1 + v2 : v2;
+		}
 
-	}
+		var lines = data.replace( /;.+/g, '' ).split( '\n' );
 
-	var lines = data.replace( /;.+/g, '' ).split( '\n' );
+		for ( var i = 0; i < lines.length; i ++ ) {
 
-	for ( var i = 0; i < lines.length; i ++ ) {
+			var tokens = lines[ i ].split( ' ' );
+			var cmd = tokens[ 0 ].toUpperCase();
 
-		var tokens = lines[ i ].split( ' ' );
-		var cmd = tokens[ 0 ].toUpperCase();
+			//Argumments
+			var args = {};
+			tokens.splice( 1 ).forEach( function ( token ) {
 
-		//Argumments
-		var args = {};
-		tokens.splice( 1 ).forEach( function ( token ) {
+				if ( token[ 0 ] !== undefined ) {
 
-			if ( token[ 0 ] !== undefined ) {
+					var key = token[ 0 ].toLowerCase();
+					var value = parseFloat( token.substring( 1 ) );
+					args[ key ] = value;
 
-				var key = token[ 0 ].toLowerCase();
-				var value = parseFloat( token.substring( 1 ) );
-				args[ key ] = value;
+				}
 
-			}
+			} );
 
-		} );
+			//Process commands
+			//G0/G1 – Linear Movement
+			if ( cmd === 'G0' || cmd === 'G1' ) {
 
-		//Process commands
-		//G0/G1 – Linear Movement
-		if ( cmd === 'G0' || cmd === 'G1' ) {
+				var line = {
+					x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
+					y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
+					z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
+					e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
+					f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
+				};
 
-			var line = {
-				x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
-				y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
-				z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
-				e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
-				f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
-			};
+				//Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
+				if ( delta( state.e, line.e ) > 0 ) {
 
-			//Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
-			if ( delta( state.e, line.e ) > 0 ) {
+					line.extruding = delta( state.e, line.e ) > 0;
 
-				line.extruding = delta( state.e, line.e ) > 0;
+					if ( currentLayer == undefined || line.z != currentLayer.z ) {
 
-				if ( currentLayer == undefined || line.z != currentLayer.z ) {
+						newLayer( line );
 
-					newLayer( line );
+					}
 
 				}
 
-			}
+				addSegment( state, line );
+				state = line;
 
-			addSegment( state, line );
-			state = line;
+			} else if ( cmd === 'G2' || cmd === 'G3' ) {
 
-		} else if ( cmd === 'G2' || cmd === 'G3' ) {
+				//G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
+				//console.warn( 'THREE.GCodeLoader: Arc command not supported' );
 
-			//G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
-			//console.warn( 'THREE.GCodeLoader: Arc command not supported' );
+			} else if ( cmd === 'G90' ) {
 
-		} else if ( cmd === 'G90' ) {
+				//G90: Set to Absolute Positioning
+				state.relative = false;
 
-			//G90: Set to Absolute Positioning
-			state.relative = false;
+			} else if ( cmd === 'G91' ) {
 
-		} else if ( cmd === 'G91' ) {
+				//G91: Set to state.relative Positioning
+				state.relative = true;
 
-			//G91: Set to state.relative Positioning
-			state.relative = true;
+			} else if ( cmd === 'G92' ) {
 
-		} else if ( cmd === 'G92' ) {
+				//G92: Set Position
+				var line = state;
+				line.x = args.x !== undefined ? args.x : line.x;
+				line.y = args.y !== undefined ? args.y : line.y;
+				line.z = args.z !== undefined ? args.z : line.z;
+				line.e = args.e !== undefined ? args.e : line.e;
+				state = line;
 
-			//G92: Set Position
-			var line = state;
-			line.x = args.x !== undefined ? args.x : line.x;
-			line.y = args.y !== undefined ? args.y : line.y;
-			line.z = args.z !== undefined ? args.z : line.z;
-			line.e = args.e !== undefined ? args.e : line.e;
-			state = line;
+			} else {
 
-		} else {
+				//console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
 
-			//console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
+			}
 
 		}
 
-	}
+		function addObject( vertex, extruding ) {
 
-	function addObject( vertex, extruding ) {
+			var geometry = new THREE.BufferGeometry();
+			geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertex, 3 ) );
 
-		var geometry = new THREE.BufferGeometry();
-		geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertex, 3 ) );
+			var segments = new THREE.LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
+			segments.name = 'layer' + i;
+			object.add( segments );
 
-		var segments = new THREE.LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
-		segments.name = 'layer' + i;
-		object.add( segments );
+		}
 
-	}
+		var object = new THREE.Group();
+		object.name = 'gcode';
 
-	var object = new THREE.Group();
-	object.name = 'gcode';
+		if ( this.splitLayer ) {
 
-	if ( this.splitLayer ) {
+			for ( var i = 0; i < layers.length; i ++ ) {
 
-		for ( var i = 0; i < layers.length; i ++ ) {
+				var layer = layers[ i ];
+				addObject( layer.vertex, true );
+				addObject( layer.pathVertex, false );
 
-			var layer = layers[ i ];
-			addObject( layer.vertex, true );
-			addObject( layer.pathVertex, false );
+			}
 
-		}
+		} else {
+
+			var vertex = [], pathVertex = [];
 
-	} else {
+			for ( var i = 0; i < layers.length; i ++ ) {
 
-		var vertex = [], pathVertex = [];
+				var layer = layers[ i ];
 
-		for ( var i = 0; i < layers.length; i ++ ) {
+				vertex = vertex.concat( layer.vertex );
+				pathVertex = pathVertex.concat( layer.pathVertex );
 
-			var layer = layers[ i ];
+			}
 
-			vertex = vertex.concat( layer.vertex );
-			pathVertex = pathVertex.concat( layer.pathVertex );
+			addObject( vertex, true );
+			addObject( pathVertex, false );
 
 		}
 
-		addObject( vertex, true );
-		addObject( pathVertex, false );
-
-	}
+		object.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) );
 
-	object.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) );
+		return object;
 
-	return object;
+	}
 
 };

+ 1 - 1
examples/js/loaders/GLTFLoader.js

@@ -842,7 +842,7 @@ THREE.GLTFLoader = ( function () {
 				for ( var i = 0, il = params.length; i < il; i ++ ) {
 
 					var value = source[ params[ i ] ];
-					target[ params[ i ] ] = value.isColor ? value.clone() : value;
+					target[ params[ i ] ] = ( value && value.isColor ) ? value.clone() : value;
 
 				}
 

+ 3 - 3
examples/js/loaders/KTXLoader.js

@@ -42,7 +42,7 @@ var KhronosTextureContainer = ( function () {
 	 * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented
 	 * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented
 	 */
-	function KhronosTextureContainer( arrayBuffer, facesExpected, threeDExpected, textureArrayExpected ) {
+	function KhronosTextureContainer( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) {
 
 		this.arrayBuffer = arrayBuffer;
 
@@ -138,13 +138,13 @@ var KhronosTextureContainer = ( function () {
 
 			var imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps
 			dataOffset += 4; // size of the image + 4 for the imageSize field
-			
+
 			for ( var face = 0; face < this.numberOfFaces; face ++ ) {
 
 				var byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize );
 
 				mipmaps.push( { "data": byteArray, "width": width, "height": height } );
-				
+
 				dataOffset += imageSize;
 				dataOffset += 3 - ( ( imageSize + 3 ) % 4 ); // add padding for odd sized image
 

+ 167 - 10
examples/js/loaders/LDrawLoader.js

@@ -7,6 +7,84 @@
 
 THREE.LDrawLoader = ( function () {
 
+	var conditionalLineVertShader = /* glsl */`
+	attribute vec3 control0;
+	attribute vec3 control1;
+	attribute vec3 direction;
+	varying float discardFlag;
+
+	#include <common>
+	#include <color_pars_vertex>
+	#include <fog_pars_vertex>
+	#include <logdepthbuf_pars_vertex>
+	#include <clipping_planes_pars_vertex>
+	void main() {
+		#include <color_vertex>
+
+		vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
+		gl_Position = projectionMatrix * mvPosition;
+
+		// Transform the line segment ends and control points into camera clip space
+		vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 );
+		vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 );
+		vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+		vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 );
+
+		c0.xy /= c0.w;
+		c1.xy /= c1.w;
+		p0.xy /= p0.w;
+		p1.xy /= p1.w;
+
+		// Get the direction of the segment and an orthogonal vector
+		vec2 dir = p1.xy - p0.xy;
+		vec2 norm = vec2( -dir.y, dir.x );
+
+		// Get control point directions from the line
+		vec2 c0dir = c0.xy - p1.xy;
+		vec2 c1dir = c1.xy - p1.xy;
+
+		// If the vectors to the controls points are pointed in different directions away
+		// from the line segment then the line should not be drawn.
+		float d0 = dot( normalize( norm ), normalize( c0dir ) );
+		float d1 = dot( normalize( norm ), normalize( c1dir ) );
+		discardFlag = float( sign( d0 ) != sign( d1 ) );
+
+		#include <logdepthbuf_vertex>
+		#include <clipping_planes_vertex>
+		#include <fog_vertex>
+	}
+	`;
+
+	var conditionalLineFragShader = /* glsl */`
+	uniform vec3 diffuse;
+	uniform float opacity;
+	varying float discardFlag;
+
+	#include <common>
+	#include <color_pars_fragment>
+	#include <fog_pars_fragment>
+	#include <logdepthbuf_pars_fragment>
+	#include <clipping_planes_pars_fragment>
+	void main() {
+
+		if ( discardFlag > 0.5 ) discard;
+
+		#include <clipping_planes_fragment>
+		vec3 outgoingLight = vec3( 0.0 );
+		vec4 diffuseColor = vec4( diffuse, opacity );
+		#include <logdepthbuf_fragment>
+		#include <color_fragment>
+		outgoingLight = diffuseColor.rgb; // simple shader
+		gl_FragColor = vec4( outgoingLight, diffuseColor.a );
+		#include <premultiplied_alpha_fragment>
+		#include <tonemapping_fragment>
+		#include <encodings_fragment>
+		#include <fog_fragment>
+	}
+	`;
+
+
+
 	var tempVec0 = new THREE.Vector3();
 	var tempVec1 = new THREE.Vector3();
 	function smoothNormals( triangles, lineSegments ) {
@@ -306,7 +384,7 @@ THREE.LDrawLoader = ( function () {
 
 	}
 
-	function createObject( elements, elementSize ) {
+	function createObject( elements, elementSize, isConditionalSegments ) {
 
 		// Creates a THREE.LineSegments (elementSize = 2) or a THREE.Mesh (elementSize = 3 )
 		// With per face / segment material, implemented with mesh groups and materials array
@@ -391,6 +469,50 @@ THREE.LDrawLoader = ( function () {
 
 		}
 
+		if ( isConditionalSegments ) {
+
+			object3d.isConditionalLine = true;
+
+			var controlArray0 = new Float32Array( elements.length * 3 * 2 );
+			var controlArray1 = new Float32Array( elements.length * 3 * 2 );
+			var directionArray = new Float32Array( elements.length * 3 * 2 );
+			for ( var i = 0, l = elements.length; i < l; i ++ ) {
+
+				var os = elements[ i ];
+				var c0 = os.c0;
+				var c1 = os.c1;
+				var v0 = os.v0;
+				var v1 = os.v1;
+				var index = i * 3 * 2;
+				controlArray0[ index + 0 ] = c0.x;
+				controlArray0[ index + 1 ] = c0.y;
+				controlArray0[ index + 2 ] = c0.z;
+				controlArray0[ index + 3 ] = c0.x;
+				controlArray0[ index + 4 ] = c0.y;
+				controlArray0[ index + 5 ] = c0.z;
+
+				controlArray1[ index + 0 ] = c1.x;
+				controlArray1[ index + 1 ] = c1.y;
+				controlArray1[ index + 2 ] = c1.z;
+				controlArray1[ index + 3 ] = c1.x;
+				controlArray1[ index + 4 ] = c1.y;
+				controlArray1[ index + 5 ] = c1.z;
+
+				directionArray[ index + 0 ] = v1.x - v0.x;
+				directionArray[ index + 1 ] = v1.y - v0.y;
+				directionArray[ index + 2 ] = v1.z - v0.z;
+				directionArray[ index + 3 ] = v1.x - v0.x;
+				directionArray[ index + 4 ] = v1.y - v0.y;
+				directionArray[ index + 5 ] = v1.z - v0.z;
+
+			}
+
+			bufferGeometry.addAttribute( 'control0', new THREE.BufferAttribute( controlArray0, 3, false ) );
+			bufferGeometry.addAttribute( 'control1', new THREE.BufferAttribute( controlArray1, 3, false ) );
+			bufferGeometry.addAttribute( 'direction', new THREE.BufferAttribute( directionArray, 3, false ) );
+
+		}
+
 		return object3d;
 
 	}
@@ -566,10 +688,7 @@ THREE.LDrawLoader = ( function () {
 
 						if ( parseScope.conditionalSegments.length > 0 ) {
 
-							var lines = createObject( parseScope.conditionalSegments, 2 );
-							lines.isConditionalLine = true;
-							lines.visible = false;
-							objGroup.add( lines );
+							objGroup.add( createObject( parseScope.conditionalSegments, 2, true ) );
 
 						}
 
@@ -615,6 +734,8 @@ THREE.LDrawLoader = ( function () {
 
 								os.v0.applyMatrix4( parseScope.matrix );
 								os.v1.applyMatrix4( parseScope.matrix );
+								os.c0.applyMatrix4( parseScope.matrix );
+								os.c1.applyMatrix4( parseScope.matrix );
 
 							}
 							parentConditionalSegments.push( os );
@@ -1150,8 +1271,12 @@ THREE.LDrawLoader = ( function () {
 			}
 
 			material.transparent = isTransparent;
+			material.premultipliedAlpha = true;
 			material.opacity = alpha;
 
+			material.polygonOffset = true;
+			material.polygonOffsetFactor = 1;
+
 			material.userData.canHaveEnvMap = canHaveEnvMap;
 
 			if ( luminance !== 0 ) {
@@ -1168,6 +1293,21 @@ THREE.LDrawLoader = ( function () {
 				edgeMaterial.name = name + " - Edge";
 				edgeMaterial.userData.canHaveEnvMap = false;
 
+				// This is the material used for conditional edges
+				edgeMaterial.userData.conditionalEdgeMaterial = new THREE.ShaderMaterial( {
+					vertexShader: conditionalLineVertShader,
+					fragmentShader: conditionalLineFragShader,
+					uniforms: {
+						diffuse: {
+							value: new THREE.Color( edgeColour )
+						},
+						opacity: {
+							value: alpha
+						}
+					}
+				} );
+				edgeMaterial.userData.conditionalEdgeMaterial.userData.canHaveEnvMap = false;
+
 			}
 
 			material.userData.code = code;
@@ -1541,19 +1681,36 @@ THREE.LDrawLoader = ( function () {
 						break;
 
 					// Line type 2: Line segment
-					// Line type 5: Conditional Line segment
 					case '2':
-					case '5':
 
 						var material = parseColourCode( lp, true );
-						var arr = lineType === '2' ? lineSegments : conditionalSegments;
 
-						arr.push( {
+						var segment = {
 							material: material.userData.edgeMaterial,
 							colourCode: material.userData.code,
 							v0: parseVector( lp ),
 							v1: parseVector( lp )
-						} );
+						};
+
+						lineSegments.push( segment );
+
+						break;
+
+					// Line type 5: Conditional Line segment
+					case '5':
+
+						var material = parseColourCode( lp, true );
+
+						var segment = {
+							material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
+							colourCode: material.userData.code,
+							v0: parseVector( lp ),
+							v1: parseVector( lp ),
+							c0: parseVector( lp ),
+							c1: parseVector( lp )
+						};
+
+						conditionalSegments.push( segment );
 
 						break;
 

+ 2 - 5
examples/js/loaders/RGBELoader.js

@@ -5,7 +5,7 @@
 // https://github.com/mrdoob/three.js/issues/5552
 // http://en.wikipedia.org/wiki/RGBE_image_format
 
-THREE.HDRLoader = THREE.RGBELoader = function ( manager ) {
+THREE.RGBELoader = function ( manager ) {
 
 	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
 	this.type = THREE.UnsignedByteType;
@@ -316,8 +316,7 @@ THREE.RGBELoader.prototype._parser = function ( buffer ) {
 		}
 	;
 
-	var byteArray = new Uint8Array( buffer ),
-		byteLength = byteArray.byteLength;
+	var byteArray = new Uint8Array( buffer );
 	byteArray.pos = 0;
 	var rgbe_header_info = RGBE_ReadHeader( byteArray );
 
@@ -392,5 +391,3 @@ THREE.RGBELoader.prototype.setType = function ( value ) {
 	return this;
 
 };
-
-

+ 0 - 5
examples/js/objects/Reflector.js

@@ -28,12 +28,10 @@ THREE.Reflector = function ( geometry, options ) {
 	var rotationMatrix = new THREE.Matrix4();
 	var lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
 	var clipPlane = new THREE.Vector4();
-	var viewport = new THREE.Vector4();
 
 	var view = new THREE.Vector3();
 	var target = new THREE.Vector3();
 	var q = new THREE.Vector4();
-	var size = new THREE.Vector2();
 
 	var textureMatrix = new THREE.Matrix4();
 	var virtualCamera = new THREE.PerspectiveCamera();
@@ -198,17 +196,14 @@ THREE.Reflector.ReflectorShader = {
 	uniforms: {
 
 		'color': {
-			type: 'c',
 			value: null
 		},
 
 		'tDiffuse': {
-			type: 't',
 			value: null
 		},
 
 		'textureMatrix': {
-			type: 'm4',
 			value: null
 		}
 

+ 19 - 29
examples/js/objects/Refractor.js

@@ -184,43 +184,36 @@ THREE.Refractor = function ( geometry, options ) {
 
 	//
 
-	var render = ( function () {
+	function render( renderer, scene, camera ) {
 
-		var viewport = new THREE.Vector4();
-		var size = new THREE.Vector2();
+		scope.visible = false;
 
-		return function render( renderer, scene, camera ) {
+		var currentRenderTarget = renderer.getRenderTarget();
+		var currentVrEnabled = renderer.vr.enabled;
+		var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
 
-			scope.visible = false;
+		renderer.vr.enabled = false; // avoid camera modification
+		renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows
 
-			var currentRenderTarget = renderer.getRenderTarget();
-			var currentVrEnabled = renderer.vr.enabled;
-			var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
+		renderer.setRenderTarget( renderTarget );
+		renderer.clear();
+		renderer.render( scene, virtualCamera );
 
-			renderer.vr.enabled = false; // avoid camera modification
-			renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows
+		renderer.vr.enabled = currentVrEnabled;
+		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+		renderer.setRenderTarget( currentRenderTarget );
 
-			renderer.setRenderTarget( renderTarget );
-			renderer.clear();
-			renderer.render( scene, virtualCamera );
+		// restore viewport
 
-			renderer.vr.enabled = currentVrEnabled;
-			renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
-			renderer.setRenderTarget( currentRenderTarget );
+		if ( camera.isArrayCamera ) {
 
-			// restore viewport
+			renderer.state.viewport( camera.viewport );
 
-			if ( camera.isArrayCamera ) {
-
-				renderer.state.viewport( camera.viewport );
-
-			}
+		}
 
-			scope.visible = true;
+		scope.visible = true;
 
-		};
-
-	} )();
+	}
 
 	//
 
@@ -262,17 +255,14 @@ THREE.Refractor.RefractorShader = {
 	uniforms: {
 
 		'color': {
-			type: 'c',
 			value: null
 		},
 
 		'tDiffuse': {
-			type: 't',
 			value: null
 		},
 
 		'textureMatrix': {
-			type: 'm4',
 			value: null
 		}
 

+ 1 - 1
examples/js/postprocessing/ClearPass.js

@@ -17,7 +17,7 @@ THREE.ClearPass.prototype = Object.assign( Object.create( THREE.Pass.prototype )
 
 	constructor: THREE.ClearPass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 		var oldClearColor, oldClearAlpha;
 

+ 1 - 1
examples/js/postprocessing/DotScreenPass.js

@@ -33,7 +33,7 @@ THREE.DotScreenPass.prototype = Object.assign( Object.create( THREE.Pass.prototy
 
 	constructor: THREE.DotScreenPass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 		this.uniforms[ "tDiffuse" ].value = readBuffer.texture;
 		this.uniforms[ "tSize" ].value.set( readBuffer.width, readBuffer.height );

+ 2 - 2
examples/js/postprocessing/EffectComposer.js

@@ -240,9 +240,9 @@ THREE.Pass = function () {
 
 Object.assign( THREE.Pass.prototype, {
 
-	setSize: function ( width, height ) {},
+	setSize: function ( /* width, height */ ) {},
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
 
 		console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
 

+ 1 - 1
examples/js/postprocessing/FilmPass.js

@@ -34,7 +34,7 @@ THREE.FilmPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ),
 
 	constructor: THREE.FilmPass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer, deltaTime /*, maskActive */ ) {
 
 		this.uniforms[ "tDiffuse" ].value = readBuffer.texture;
 		this.uniforms[ "time" ].value += deltaTime;

+ 2 - 2
examples/js/postprocessing/MaskPass.js

@@ -20,7 +20,7 @@ THREE.MaskPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ),
 
 	constructor: THREE.MaskPass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 		var context = renderer.context;
 		var state = renderer.state;
@@ -93,7 +93,7 @@ THREE.ClearMaskPass.prototype = Object.create( THREE.Pass.prototype );
 
 Object.assign( THREE.ClearMaskPass.prototype, {
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
 
 		renderer.state.buffers.stencil.setTest( false );
 

+ 1 - 1
examples/js/postprocessing/RenderPass.js

@@ -24,7 +24,7 @@ THREE.RenderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype
 
 	constructor: THREE.RenderPass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 		var oldAutoClear = renderer.autoClear;
 		renderer.autoClear = false;

+ 2 - 1
examples/js/postprocessing/ShaderPass.js

@@ -30,13 +30,14 @@ THREE.ShaderPass = function ( shader, textureID ) {
 	}
 
 	this.fsQuad = new THREE.Pass.FullScreenQuad( this.material );
+
 };
 
 THREE.ShaderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), {
 
 	constructor: THREE.ShaderPass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 		if ( this.uniforms[ this.textureID ] ) {
 

+ 2 - 1
examples/js/postprocessing/TexturePass.js

@@ -36,7 +36,7 @@ THREE.TexturePass.prototype = Object.assign( Object.create( THREE.Pass.prototype
 
 	constructor: THREE.TexturePass,
 
-	render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) {
+	render: function ( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 		var oldAutoClear = renderer.autoClear;
 		renderer.autoClear = false;
@@ -52,6 +52,7 @@ THREE.TexturePass.prototype = Object.assign( Object.create( THREE.Pass.prototype
 		this.fsQuad.render( renderer );
 
 		renderer.autoClear = oldAutoClear;
+
 	}
 
 } );

+ 1 - 1
examples/js/shaders/BleachBypassShader.js

@@ -11,7 +11,7 @@ THREE.BleachBypassShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"opacity":  { value: 1.0 }
+		"opacity": { value: 1.0 }
 
 	},
 

+ 2 - 2
examples/js/shaders/BlendShader.js

@@ -10,8 +10,8 @@ THREE.BlendShader = {
 
 		"tDiffuse1": { value: null },
 		"tDiffuse2": { value: null },
-		"mixRatio":  { value: 0.5 },
-		"opacity":   { value: 1.0 }
+		"mixRatio": { value: 0.5 },
+		"opacity": { value: 1.0 }
 
 	},
 

+ 11 - 11
examples/js/shaders/BokehShader.js

@@ -15,14 +15,14 @@ THREE.BokehShader = {
 
 	uniforms: {
 
-		"tColor":   { value: null },
-		"tDepth":   { value: null },
-		"focus":    { value: 1.0 },
-		"aspect":   { value: 1.0 },
+		"tColor": { value: null },
+		"tDepth": { value: null },
+		"focus": { value: 1.0 },
+		"aspect": { value: 1.0 },
 		"aperture": { value: 0.025 },
-		"maxblur":  { value: 1.0 },
-		"nearClip":  { value: 1.0 },
-		"farClip":  { value: 1000.0 },
+		"maxblur": { value: 1.0 },
+		"nearClip": { value: 1.0 },
+		"farClip": { value: 1000.0 },
 
 	},
 
@@ -47,7 +47,7 @@ THREE.BokehShader = {
 		"uniform sampler2D tColor;",
 		"uniform sampler2D tDepth;",
 
-		"uniform float maxblur;",  // max blur amount
+		"uniform float maxblur;", // max blur amount
 		"uniform float aperture;", // aperture - bigger values for shallower depth of field
 
 		"uniform float nearClip;",
@@ -73,15 +73,15 @@ THREE.BokehShader = {
 		"	return orthographicDepthToViewZ( depth, nearClip, farClip );",
 		"	#endif",
 		"}",
-		
+
 
 		"void main() {",
 
 			"vec2 aspectcorrect = vec2( 1.0, aspect );",
-	
+
 			"float viewZ = getViewZ( getDepth( vUv ) );",
 
-			"float factor = ( focus + viewZ );",  // viewZ is <= 0, so this is a difference equation
+			"float factor = ( focus + viewZ );", // viewZ is <= 0, so this is a difference equation
 
 			"vec2 dofblur = vec2 ( clamp( factor * aperture, -maxblur, maxblur ) );",
 

+ 21 - 21
examples/js/shaders/BokehShader2.js

@@ -14,37 +14,37 @@ THREE.BokehShader = {
 
 	uniforms: {
 
-		"textureWidth":  { value: 1.0 },
-		"textureHeight":  { value: 1.0 },
+		"textureWidth": { value: 1.0 },
+		"textureHeight": { value: 1.0 },
 
-		"focalDepth":   { value: 1.0 },
-		"focalLength":   { value: 24.0 },
+		"focalDepth": { value: 1.0 },
+		"focalLength": { value: 24.0 },
 		"fstop": { value: 0.9 },
 
-		"tColor":   { value: null },
-		"tDepth":   { value: null },
+		"tColor": { value: null },
+		"tDepth": { value: null },
 
-		"maxblur":  { value: 1.0 },
+		"maxblur": { value: 1.0 },
 
-		"showFocus":   { value: 0 },
-		"manualdof":   { value: 0 },
-		"vignetting":   { value: 0 },
-		"depthblur":   { value: 0 },
+		"showFocus": { value: 0 },
+		"manualdof": { value: 0 },
+		"vignetting": { value: 0 },
+		"depthblur": { value: 0 },
 
-		"threshold":  { value: 0.5 },
-		"gain":  { value: 2.0 },
-		"bias":  { value: 0.5 },
-		"fringe":  { value: 0.7 },
+		"threshold": { value: 0.5 },
+		"gain": { value: 2.0 },
+		"bias": { value: 0.5 },
+		"fringe": { value: 0.7 },
 
-		"znear":  { value: 0.1 },
-		"zfar":  { value: 100 },
+		"znear": { value: 0.1 },
+		"zfar": { value: 100 },
 
-		"noise":  { value: 1 },
-		"dithering":  { value: 0.0001 },
+		"noise": { value: 1 },
+		"dithering": { value: 0.0001 },
 		"pentagon": { value: 0 },
 
-		"shaderFocus":  { value: 1 },
-		"focusCoords":  { value: new THREE.Vector2() }
+		"shaderFocus": { value: 1 },
+		"focusCoords": { value: new THREE.Vector2() }
 
 
 	},

+ 2 - 2
examples/js/shaders/BrightnessContrastShader.js

@@ -11,9 +11,9 @@ THREE.BrightnessContrastShader = {
 
 	uniforms: {
 
-		"tDiffuse":   { value: null },
+		"tDiffuse": { value: null },
 		"brightness": { value: 0 },
-		"contrast":   { value: 0 }
+		"contrast": { value: 0 }
 
 	},
 

+ 3 - 3
examples/js/shaders/ColorCorrectionShader.js

@@ -9,9 +9,9 @@ THREE.ColorCorrectionShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"powRGB":   { value: new THREE.Vector3( 2, 2, 2 ) },
-		"mulRGB":   { value: new THREE.Vector3( 1, 1, 1 ) },
-		"addRGB":   { value: new THREE.Vector3( 0, 0, 0 ) }
+		"powRGB": { value: new THREE.Vector3( 2, 2, 2 ) },
+		"mulRGB": { value: new THREE.Vector3( 1, 1, 1 ) },
+		"addRGB": { value: new THREE.Vector3( 0, 0, 0 ) }
 
 	},
 

+ 1 - 1
examples/js/shaders/ColorifyShader.js

@@ -9,7 +9,7 @@ THREE.ColorifyShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"color":    { value: new THREE.Color( 0xffffff ) }
+		"color": { value: new THREE.Color( 0xffffff ) }
 
 	},
 

+ 2 - 2
examples/js/shaders/ConvolutionShader.js

@@ -17,9 +17,9 @@ THREE.ConvolutionShader = {
 
 	uniforms: {
 
-		"tDiffuse":        { value: null },
+		"tDiffuse": { value: null },
 		"uImageIncrement": { value: new THREE.Vector2( 0.001953125, 0.0 ) },
-		"cKernel":         { value: [] }
+		"cKernel": { value: [] }
 
 	},
 

+ 4 - 4
examples/js/shaders/DOFMipMapShader.js

@@ -10,10 +10,10 @@ THREE.DOFMipMapShader = {
 
 	uniforms: {
 
-		"tColor":   { value: null },
-		"tDepth":   { value: null },
-		"focus":    { value: 1.0 },
-		"maxblur":  { value: 1.0 }
+		"tColor": { value: null },
+		"tDepth": { value: null },
+		"focus": { value: 1.0 },
+		"maxblur": { value: 1.0 }
 
 	},
 

+ 4 - 0
examples/js/shaders/DepthLimitedBlurShader.js

@@ -1,3 +1,7 @@
+/**
+ * TODO
+ */
+
 THREE.DepthLimitedBlurShader = {
 	defines: {
 		'KERNEL_RADIUS': 4,

+ 17 - 17
examples/js/shaders/DigitalGlitch.js

@@ -14,17 +14,17 @@ THREE.DigitalGlitch = {
 
 	uniforms: {
 
-		"tDiffuse":		{ value: null },//diffuse texture
-		"tDisp":		{ value: null },//displacement texture for digital glitch squares
-		"byp":			{ value: 0 },//apply the glitch ?
-		"amount":		{ value: 0.08 },
-		"angle":		{ value: 0.02 },
-		"seed":			{ value: 0.02 },
-		"seed_x":		{ value: 0.02 },//-1,1
-		"seed_y":		{ value: 0.02 },//-1,1
-		"distortion_x":	{ value: 0.5 },
-		"distortion_y":	{ value: 0.6 },
-		"col_s":		{ value: 0.05 }
+		"tDiffuse": { value: null }, //diffuse texture
+		"tDisp": { value: null }, //displacement texture for digital glitch squares
+		"byp": { value: 0 }, //apply the glitch ?
+		"amount": { value: 0.08 },
+		"angle": { value: 0.02 },
+		"seed": { value: 0.02 },
+		"seed_x": { value: 0.02 }, //-1,1
+		"seed_y": { value: 0.02 }, //-1,1
+		"distortion_x": { value: 0.5 },
+		"distortion_y": { value: 0.6 },
+		"col_s": { value: 0.05 }
 	},
 
 	vertexShader: [
@@ -38,10 +38,10 @@ THREE.DigitalGlitch = {
 
 	fragmentShader: [
 		"uniform int byp;",//should we apply the glitch ?
-		
+
 		"uniform sampler2D tDiffuse;",
 		"uniform sampler2D tDisp;",
-		
+
 		"uniform float amount;",
 		"uniform float angle;",
 		"uniform float seed;",
@@ -50,14 +50,14 @@ THREE.DigitalGlitch = {
 		"uniform float distortion_x;",
 		"uniform float distortion_y;",
 		"uniform float col_s;",
-			
+
 		"varying vec2 vUv;",
-		
-		
+
+
 		"float rand(vec2 co){",
 			"return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);",
 		"}",
-				
+
 		"void main() {",
 			"if(byp<1) {",
 				"vec2 p = vUv;",

+ 1 - 1
examples/js/shaders/FXAAShader.js

@@ -12,7 +12,7 @@ THREE.FXAAShader = {
 
 	uniforms: {
 
-		"tDiffuse":   { value: null },
+		"tDiffuse": { value: null },
 		"resolution": { value: new THREE.Vector2( 1 / 1024, 1 / 512 ) }
 
 	},

+ 5 - 5
examples/js/shaders/FilmShader.js

@@ -24,12 +24,12 @@ THREE.FilmShader = {
 
 	uniforms: {
 
-		"tDiffuse":   { value: null },
-		"time":       { value: 0.0 },
+		"tDiffuse": { value: null },
+		"time": { value: 0.0 },
 		"nIntensity": { value: 0.5 },
 		"sIntensity": { value: 0.05 },
-		"sCount":     { value: 4096 },
-		"grayscale":  { value: 1 }
+		"sCount": { value: 4096 },
+		"grayscale": { value: 1 }
 
 	},
 
@@ -49,7 +49,7 @@ THREE.FilmShader = {
 	fragmentShader: [
 
 		"#include <common>",
-		
+
 		// control parameter
 		"uniform float time;",
 

+ 5 - 5
examples/js/shaders/FocusShader.js

@@ -8,13 +8,13 @@
 
 THREE.FocusShader = {
 
-	uniforms : {
+	uniforms: {
 
-		"tDiffuse":       { value: null },
-		"screenWidth":    { value: 1024 },
-		"screenHeight":   { value: 1024 },
+		"tDiffuse": { value: null },
+		"screenWidth": { value: 1024 },
+		"screenHeight": { value: 1024 },
 		"sampleDistance": { value: 0.94 },
-		"waveFactor":     { value: 0.00125 }
+		"waveFactor": { value: 0.00125 }
 
 	},
 

+ 1 - 1
examples/js/shaders/FreiChenShader.js

@@ -12,7 +12,7 @@ THREE.FreiChenShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"aspect":    { value: new THREE.Vector2( 512, 512 ) }
+		"aspect": { value: new THREE.Vector2( 512, 512 ) }
 	},
 
 	vertexShader: [

+ 1 - 1
examples/js/shaders/HorizontalBlurShader.js

@@ -15,7 +15,7 @@ THREE.HorizontalBlurShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"h":        { value: 1.0 / 512.0 }
+		"h": { value: 1.0 / 512.0 }
 
 	},
 

+ 2 - 2
examples/js/shaders/HorizontalTiltShiftShader.js

@@ -14,8 +14,8 @@ THREE.HorizontalTiltShiftShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"h":        { value: 1.0 / 512.0 },
-		"r":        { value: 0.35 }
+		"h": { value: 1.0 / 512.0 },
+		"r": { value: 0.35 }
 
 	},
 

+ 2 - 2
examples/js/shaders/HueSaturationShader.js

@@ -11,8 +11,8 @@ THREE.HueSaturationShader = {
 
 	uniforms: {
 
-		"tDiffuse":   { value: null },
-		"hue":        { value: 0 },
+		"tDiffuse": { value: null },
+		"hue": { value: 0 },
 		"saturation": { value: 0 }
 
 	},

+ 3 - 3
examples/js/shaders/KaleidoShader.js

@@ -15,8 +15,8 @@ THREE.KaleidoShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"sides":    { value: 6.0 },
-		"angle":    { value: 0.0 }
+		"sides": { value: 6.0 },
+		"angle": { value: 0.0 }
 
 	},
 
@@ -38,7 +38,7 @@ THREE.KaleidoShader = {
 		"uniform sampler2D tDiffuse;",
 		"uniform float sides;",
 		"uniform float angle;",
-		
+
 		"varying vec2 vUv;",
 
 		"void main() {",

+ 2 - 2
examples/js/shaders/LuminosityHighPassShader.js

@@ -7,7 +7,7 @@
 
 THREE.LuminosityHighPassShader = {
 
-  shaderID: "luminosityHighPass",
+	shaderID: "luminosityHighPass",
 
 	uniforms: {
 
@@ -15,7 +15,7 @@ THREE.LuminosityHighPassShader = {
 		"luminosityThreshold": { value: 1.0 },
 		"smoothWidth": { value: 1.0 },
 		"defaultColor": { value: new THREE.Color( 0x000000 ) },
-		"defaultOpacity":  { value: 0.0 }
+		"defaultOpacity": { value: 0.0 }
 
 	},
 

+ 2 - 2
examples/js/shaders/MirrorShader.js

@@ -12,7 +12,7 @@ THREE.MirrorShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"side":     { value: 1 }
+		"side": { value: 1 }
 
 	},
 
@@ -33,7 +33,7 @@ THREE.MirrorShader = {
 
 		"uniform sampler2D tDiffuse;",
 		"uniform int side;",
-		
+
 		"varying vec2 vUv;",
 
 		"void main() {",

+ 3 - 3
examples/js/shaders/NormalMapShader.js

@@ -9,10 +9,10 @@ THREE.NormalMapShader = {
 
 	uniforms: {
 
-		"heightMap":  { value: null },
+		"heightMap": { value: null },
 		"resolution": { value: new THREE.Vector2( 512, 512 ) },
-		"scale":      { value: new THREE.Vector2( 1, 1 ) },
-		"height":     { value: 0.05 }
+		"scale": { value: new THREE.Vector2( 1, 1 ) },
+		"height": { value: 0.05 }
 
 	},
 

+ 2 - 2
examples/js/shaders/RGBShiftShader.js

@@ -15,8 +15,8 @@ THREE.RGBShiftShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"amount":   { value: 0.005 },
-		"angle":    { value: 0.0 }
+		"amount": { value: 0.005 },
+		"angle": { value: 0.0 }
 
 	},
 

+ 4 - 0
examples/js/shaders/SAOShader.js

@@ -1,3 +1,7 @@
+/**
+ * TODO
+ */
+
 THREE.SAOShader = {
 	defines: {
 		'NUM_SAMPLES': 7,

+ 1 - 1
examples/js/shaders/SepiaShader.js

@@ -11,7 +11,7 @@ THREE.SepiaShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"amount":   { value: 1.0 }
+		"amount": { value: 1.0 }
 
 	},
 

+ 1 - 1
examples/js/shaders/UnpackDepthRGBAShader.js

@@ -10,7 +10,7 @@ THREE.UnpackDepthRGBAShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"opacity":  { value: 1.0 }
+		"opacity": { value: 1.0 }
 
 	},
 

+ 1 - 1
examples/js/shaders/VerticalBlurShader.js

@@ -15,7 +15,7 @@ THREE.VerticalBlurShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"v":        { value: 1.0 / 512.0 }
+		"v": { value: 1.0 / 512.0 }
 
 	},
 

+ 2 - 2
examples/js/shaders/VerticalTiltShiftShader.js

@@ -14,8 +14,8 @@ THREE.VerticalTiltShiftShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"v":        { value: 1.0 / 512.0 },
-		"r":        { value: 0.35 }
+		"v": { value: 1.0 / 512.0 },
+		"r": { value: 0.35 }
 
 	},
 

+ 1 - 1
examples/js/shaders/VignetteShader.js

@@ -11,7 +11,7 @@ THREE.VignetteShader = {
 	uniforms: {
 
 		"tDiffuse": { value: null },
-		"offset":   { value: 1.0 },
+		"offset": { value: 1.0 },
 		"darkness": { value: 1.0 }
 
 	},

+ 0 - 5
examples/js/shaders/WaterRefractionShader.js

@@ -8,27 +8,22 @@ THREE.WaterRefractionShader = {
 	uniforms: {
 
 		'color': {
-			type: 'c',
 			value: null
 		},
 
 		'time': {
-			type: 'f',
 			value: 0
 		},
 
 		'tDiffuse': {
-			type: 't',
 			value: null
 		},
 
 		'tDudv': {
-			type: 't',
 			value: null
 		},
 
 		'textureMatrix': {
-			type: 'm4',
 			value: null
 		}
 

+ 5 - 6
examples/js/utils/SceneUtils.js

@@ -20,18 +20,17 @@ THREE.SceneUtils = {
 
 	detach: function ( child, parent, scene ) {
 
-		child.applyMatrix( parent.matrixWorld );
-		parent.remove( child );
-		scene.add( child );
+		console.warn( 'THREE.SceneUtils: detach() has been deprecated. Use scene.attach( child ) instead.' );
+
+		scene.attach( child );
 
 	},
 
 	attach: function ( child, scene, parent ) {
 
-		child.applyMatrix( new THREE.Matrix4().getInverse( parent.matrixWorld ) );
+		console.warn( 'THREE.SceneUtils: attach() has been deprecated. Use parent.attach( child ) instead.' );
 
-		scene.remove( child );
-		parent.add( child );
+		parent.attach( child );
 
 	}
 

+ 32 - 0
examples/jsm/cameras/CinematicCamera.d.ts

@@ -0,0 +1,32 @@
+import {
+  PerspectiveCamera,
+  ShaderMaterial,
+  Scene,
+  WebGLRenderer
+} from '../../../src/Three';
+
+export class CinematicCamera extends PerspectiveCamera {
+  constructor(fov: number, aspect: number, near: number, far: number);
+
+  postprocessing: {
+    enabled: boolean;
+  };
+  shaderSettings: {
+    rings: number;
+    samples: number;
+  };
+  materialDepth: ShaderMaterial;
+  coc: number;
+  aperture: number;
+  fNumber: number;
+  hyperFocal: number;
+  filmGauge: number;
+
+  linearize(depth: number): number;
+  smoothstep(near: number, far: number, depth: number): number;
+  saturate(x: number): number;
+  focusAt(focusDistance: number): void;
+  initPostProcessing(): void;
+  renderCinematic(scene: Scene, renderer: WebGLRenderer): void;
+
+}

+ 224 - 0
examples/jsm/cameras/CinematicCamera.js

@@ -0,0 +1,224 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author greggman / http://games.greggman.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author kaypiKun
+ */
+
+import {
+	LinearFilter,
+	Mesh,
+	OrthographicCamera,
+	PerspectiveCamera,
+	PlaneBufferGeometry,
+	RGBFormat,
+	Scene,
+	ShaderMaterial,
+	UniformsUtils,
+	WebGLRenderTarget
+} from "../../../build/three.module.js";
+import { BokehShader } from "../shaders/BokehShader2.js";
+import { BokehDepthShader } from "../shaders/BokehShader2.js";
+
+var CinematicCamera = function ( fov, aspect, near, far ) {
+
+	PerspectiveCamera.call( this, fov, aspect, near, far );
+
+	this.type = 'CinematicCamera';
+
+	this.postprocessing = { enabled: true };
+	this.shaderSettings = {
+		rings: 3,
+		samples: 4
+	};
+
+	var depthShader = BokehDepthShader;
+
+	this.materialDepth = new ShaderMaterial( {
+		uniforms: depthShader.uniforms,
+		vertexShader: depthShader.vertexShader,
+		fragmentShader: depthShader.fragmentShader
+	} );
+
+	this.materialDepth.uniforms[ 'mNear' ].value = near;
+	this.materialDepth.uniforms[ 'mFar' ].value = far;
+
+	// In case of cinematicCamera, having a default lens set is important
+	this.setLens();
+
+	this.initPostProcessing();
+
+};
+
+CinematicCamera.prototype = Object.create( PerspectiveCamera.prototype );
+CinematicCamera.prototype.constructor = CinematicCamera;
+
+
+// providing fnumber and coc(Circle of Confusion) as extra arguments
+CinematicCamera.prototype.setLens = function ( focalLength, filmGauge, fNumber, coc ) {
+
+	// In case of cinematicCamera, having a default lens set is important
+	if ( focalLength === undefined ) focalLength = 35;
+	if ( filmGauge !== undefined ) this.filmGauge = filmGauge;
+
+	this.setFocalLength( focalLength );
+
+	// if fnumber and coc are not provided, cinematicCamera tries to act as a basic PerspectiveCamera
+	if ( fNumber === undefined ) fNumber = 8;
+	if ( coc === undefined ) coc = 0.019;
+
+	this.fNumber = fNumber;
+	this.coc = coc;
+
+	// fNumber is focalLength by aperture
+	this.aperture = focalLength / this.fNumber;
+
+	// hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength
+	this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc );
+
+};
+
+CinematicCamera.prototype.linearize = function ( depth ) {
+
+	var zfar = this.far;
+	var znear = this.near;
+	return - zfar * znear / ( depth * ( zfar - znear ) - zfar );
+
+};
+
+CinematicCamera.prototype.smoothstep = function ( near, far, depth ) {
+
+	var x = this.saturate( ( depth - near ) / ( far - near ) );
+	return x * x * ( 3 - 2 * x );
+
+};
+
+CinematicCamera.prototype.saturate = function ( x ) {
+
+	return Math.max( 0, Math.min( 1, x ) );
+
+};
+
+// function for focusing at a distance from the camera
+CinematicCamera.prototype.focusAt = function ( focusDistance ) {
+
+	if ( focusDistance === undefined ) focusDistance = 20;
+
+	var focalLength = this.getFocalLength();
+
+	// distance from the camera (normal to frustrum) to focus on
+	this.focus = focusDistance;
+
+	// the nearest point from the camera which is in focus (unused)
+	this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) );
+
+	// the farthest point from the camera which is in focus (unused)
+	this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) );
+
+	// the gap or width of the space in which is everything is in focus (unused)
+	this.depthOfField = this.farPoint - this.nearPoint;
+
+	// Considering minimum distance of focus for a standard lens (unused)
+	if ( this.depthOfField < 0 ) this.depthOfField = 0;
+
+	this.sdistance = this.smoothstep( this.near, this.far, this.focus );
+
+	this.ldistance = this.linearize( 1 -	this.sdistance );
+
+	this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance;
+
+};
+
+CinematicCamera.prototype.initPostProcessing = function () {
+
+	if ( this.postprocessing.enabled ) {
+
+		this.postprocessing.scene = new Scene();
+
+		this.postprocessing.camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2,	window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 );
+
+		this.postprocessing.scene.add( this.postprocessing.camera );
+
+		var pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBFormat };
+		this.postprocessing.rtTextureDepth = new WebGLRenderTarget( window.innerWidth, window.innerHeight, pars );
+		this.postprocessing.rtTextureColor = new WebGLRenderTarget( window.innerWidth, window.innerHeight, pars );
+
+		var bokeh_shader = BokehShader;
+
+		this.postprocessing.bokeh_uniforms = UniformsUtils.clone( bokeh_shader.uniforms );
+
+		this.postprocessing.bokeh_uniforms[ "tColor" ].value = this.postprocessing.rtTextureColor.texture;
+		this.postprocessing.bokeh_uniforms[ "tDepth" ].value = this.postprocessing.rtTextureDepth.texture;
+
+		this.postprocessing.bokeh_uniforms[ "manualdof" ].value = 0;
+		this.postprocessing.bokeh_uniforms[ "shaderFocus" ].value = 0;
+
+		this.postprocessing.bokeh_uniforms[ "fstop" ].value = 2.8;
+
+		this.postprocessing.bokeh_uniforms[ "showFocus" ].value = 1;
+
+		this.postprocessing.bokeh_uniforms[ "focalDepth" ].value = 0.1;
+
+		//console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value );
+
+		this.postprocessing.bokeh_uniforms[ "znear" ].value = this.near;
+		this.postprocessing.bokeh_uniforms[ "zfar" ].value = this.near;
+
+
+		this.postprocessing.bokeh_uniforms[ "textureWidth" ].value = window.innerWidth;
+
+		this.postprocessing.bokeh_uniforms[ "textureHeight" ].value = window.innerHeight;
+
+		this.postprocessing.materialBokeh = new ShaderMaterial( {
+			uniforms: this.postprocessing.bokeh_uniforms,
+			vertexShader: bokeh_shader.vertexShader,
+			fragmentShader: bokeh_shader.fragmentShader,
+			defines: {
+				RINGS: this.shaderSettings.rings,
+				SAMPLES: this.shaderSettings.samples,
+				DEPTH_PACKING: 1
+			}
+		} );
+
+		this.postprocessing.quad = new Mesh( new PlaneBufferGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh );
+		this.postprocessing.quad.position.z = - 500;
+		this.postprocessing.scene.add( this.postprocessing.quad );
+
+	}
+
+};
+
+CinematicCamera.prototype.renderCinematic = function ( scene, renderer ) {
+
+	if ( this.postprocessing.enabled ) {
+
+		var currentRenderTarget = renderer.getRenderTarget();
+
+		renderer.clear();
+
+		// Render scene into texture
+
+		scene.overrideMaterial = null;
+		renderer.setRenderTarget( this.postprocessing.rtTextureColor );
+		renderer.clear();
+		renderer.render( scene, this );
+
+		// Render depth into texture
+
+		scene.overrideMaterial = this.materialDepth;
+		renderer.setRenderTarget( this.postprocessing.rtTextureDepth );
+		renderer.clear();
+		renderer.render( scene, this );
+
+		// Render bokeh composite
+
+		renderer.setRenderTarget( null );
+		renderer.render( this.postprocessing.scene, this.postprocessing.camera );
+
+		renderer.setRenderTarget( currentRenderTarget );
+
+	}
+
+};
+
+export { CinematicCamera };

+ 10 - 0
examples/jsm/curves/NURBSCurve.d.ts

@@ -0,0 +1,10 @@
+import {
+  Curve,
+  Vector2,
+  Vector3,
+  Vector4
+} from '../../../src/Three';
+
+export class NURBSCurve extends Curve<Vector3> {
+  constructor(degree: number, knots: number[], controlPoints: Vector2[] | Vector3[] | Vector4[], startKnot: number, endKnot: number);
+}

+ 78 - 0
examples/jsm/curves/NURBSCurve.js

@@ -0,0 +1,78 @@
+/**
+ * @author renej
+ * NURBS curve object
+ *
+ * Derives from Curve, overriding getPoint and getTangent.
+ *
+ * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
+ *
+ **/
+
+import {
+	Curve,
+	Vector3,
+	Vector4
+} from "../../../build/three.module.js";
+import { NURBSUtils } from "../curves/NURBSUtils.js";
+
+
+/**************************************************************
+ *	NURBS curve
+ **************************************************************/
+
+var NURBSCurve = function ( degree, knots /* array of reals */, controlPoints /* array of Vector(2|3|4) */, startKnot /* index in knots */, endKnot /* index in knots */ ) {
+
+	Curve.call( this );
+
+	this.degree = degree;
+	this.knots = knots;
+	this.controlPoints = [];
+	// Used by periodic NURBS to remove hidden spans
+	this.startKnot = startKnot || 0;
+	this.endKnot = endKnot || ( this.knots.length - 1 );
+	for ( var i = 0; i < controlPoints.length; ++ i ) {
+
+		// ensure Vector4 for control points
+		var point = controlPoints[ i ];
+		this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w );
+
+	}
+
+};
+
+
+NURBSCurve.prototype = Object.create( Curve.prototype );
+NURBSCurve.prototype.constructor = NURBSCurve;
+
+
+NURBSCurve.prototype.getPoint = function ( t ) {
+
+	var u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
+
+	// following results in (wx, wy, wz, w) homogeneous point
+	var hpoint = NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
+
+	if ( hpoint.w != 1.0 ) {
+
+		// project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
+		hpoint.divideScalar( hpoint.w );
+
+	}
+
+	return new Vector3( hpoint.x, hpoint.y, hpoint.z );
+
+};
+
+
+NURBSCurve.prototype.getTangent = function ( t ) {
+
+	var u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
+	var ders = NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
+	var tangent = ders[ 1 ].clone();
+	tangent.normalize();
+
+	return tangent;
+
+};
+
+export { NURBSCurve };

+ 11 - 0
examples/jsm/curves/NURBSSurface.d.ts

@@ -0,0 +1,11 @@
+import {
+  Vector2,
+  Vector3,
+  Vector4
+} from '../../../src/Three';
+
+export class NURBSSurface {
+  constructor(degree1: number, degree2: number, knots1: number[], knots2: number[], controlPoints: Vector2[][] | Vector3[][] | Vector4[][]);
+
+  getPoint(t1: number, t2: number, target: Vector3): void;
+}

+ 60 - 0
examples/jsm/curves/NURBSSurface.js

@@ -0,0 +1,60 @@
+/**
+ * @author renej
+ * NURBS surface object
+ *
+ * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
+ *
+ **/
+
+import {
+	Vector4
+} from "../../../build/three.module.js";
+import { NURBSUtils } from "../curves/NURBSUtils.js";
+
+
+/**************************************************************
+ *	NURBS surface
+ **************************************************************/
+
+var NURBSSurface = function ( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) {
+
+	this.degree1 = degree1;
+	this.degree2 = degree2;
+	this.knots1 = knots1;
+	this.knots2 = knots2;
+	this.controlPoints = [];
+
+	var len1 = knots1.length - degree1 - 1;
+	var len2 = knots2.length - degree2 - 1;
+
+	// ensure Vector4 for control points
+	for ( var i = 0; i < len1; ++ i ) {
+
+		this.controlPoints[ i ] = [];
+		for ( var j = 0; j < len2; ++ j ) {
+
+			var point = controlPoints[ i ][ j ];
+			this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w );
+
+		}
+
+	}
+
+};
+
+
+NURBSSurface.prototype = {
+
+	constructor: NURBSSurface,
+
+	getPoint: function ( t1, t2, target ) {
+
+		var u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
+		var v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u
+
+		NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target );
+
+	}
+};
+
+export { NURBSSurface };

+ 18 - 0
examples/jsm/curves/NURBSUtils.d.ts

@@ -0,0 +1,18 @@
+import {
+  Vector3,
+  Vector4
+} from '../../../src/Three';
+
+export namespace NURBSUtils {
+
+  export function findSpan(p: number, u: number, U: number[]): number;
+  export function calcBasisFunctions(span: number, u: number, p: number, U: number[]): number[];
+  export function calcBSplinePoint(p: number, U: number[], P: Vector4[], u: number): Vector4;
+  export function calcBasisFunctionDerivatives(span: number,u: number, p: number, n: number, U: number[]): number[][];
+  export function calcBSplineDerivatives(p: number, U: number[], P: Vector4[], u: number, nd: number): Vector4[];
+  export function calcKoverI(k: number, i: number): number;
+  export function calcRationalCurveDerivatives(Pders: Vector4[]): Vector3[];
+  export function calcNURBSDerivatives(p: number, U: number[], P: Vector4[], u: number, nd: number): Vector3[];
+  export function calcSurfacePoint(p: number, q: number, U: number[], V: number[], P: Vector4[], u: number, v: number, target: Vector3): Vector3;
+
+}

+ 476 - 0
examples/jsm/curves/NURBSUtils.js

@@ -0,0 +1,476 @@
+/**
+ * @author renej
+ * NURBS utils
+ *
+ * See NURBSCurve and NURBSSurface.
+ *
+ **/
+
+import {
+	Vector3,
+	Vector4
+} from "../../../build/three.module.js";
+
+
+/**************************************************************
+ *	NURBS Utils
+ **************************************************************/
+
+var NURBSUtils = {
+
+	/*
+	Finds knot vector span.
+
+	p : degree
+	u : parametric value
+	U : knot vector
+
+	returns the span
+	*/
+	findSpan: function ( p, u, U ) {
+
+		var n = U.length - p - 1;
+
+		if ( u >= U[ n ] ) {
+
+			return n - 1;
+
+		}
+
+		if ( u <= U[ p ] ) {
+
+			return p;
+
+		}
+
+		var low = p;
+		var high = n;
+		var mid = Math.floor( ( low + high ) / 2 );
+
+		while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
+
+			if ( u < U[ mid ] ) {
+
+				high = mid;
+
+			} else {
+
+				low = mid;
+
+			}
+
+			mid = Math.floor( ( low + high ) / 2 );
+
+		}
+
+		return mid;
+
+	},
+
+
+	/*
+	Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
+
+	span : span in which u lies
+	u    : parametric point
+	p    : degree
+	U    : knot vector
+
+	returns array[p+1] with basis functions values.
+	*/
+	calcBasisFunctions: function ( span, u, p, U ) {
+
+		var N = [];
+		var left = [];
+		var right = [];
+		N[ 0 ] = 1.0;
+
+		for ( var j = 1; j <= p; ++ j ) {
+
+			left[ j ] = u - U[ span + 1 - j ];
+			right[ j ] = U[ span + j ] - u;
+
+			var saved = 0.0;
+
+			for ( var r = 0; r < j; ++ r ) {
+
+				var rv = right[ r + 1 ];
+				var lv = left[ j - r ];
+				var temp = N[ r ] / ( rv + lv );
+				N[ r ] = saved + rv * temp;
+				saved = lv * temp;
+
+			 }
+
+			 N[ j ] = saved;
+
+		 }
+
+		 return N;
+
+	},
+
+
+	/*
+	Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
+
+	p : degree of B-Spline
+	U : knot vector
+	P : control points (x, y, z, w)
+	u : parametric point
+
+	returns point for given u
+	*/
+	calcBSplinePoint: function ( p, U, P, u ) {
+
+		var span = this.findSpan( p, u, U );
+		var N = this.calcBasisFunctions( span, u, p, U );
+		var C = new Vector4( 0, 0, 0, 0 );
+
+		for ( var j = 0; j <= p; ++ j ) {
+
+			var point = P[ span - p + j ];
+			var Nj = N[ j ];
+			var wNj = point.w * Nj;
+			C.x += point.x * wNj;
+			C.y += point.y * wNj;
+			C.z += point.z * wNj;
+			C.w += point.w * Nj;
+
+		}
+
+		return C;
+
+	},
+
+
+	/*
+	Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
+
+	span : span in which u lies
+	u    : parametric point
+	p    : degree
+	n    : number of derivatives to calculate
+	U    : knot vector
+
+	returns array[n+1][p+1] with basis functions derivatives
+	*/
+	calcBasisFunctionDerivatives: function ( span, u, p, n, U ) {
+
+		var zeroArr = [];
+		for ( var i = 0; i <= p; ++ i )
+			zeroArr[ i ] = 0.0;
+
+		var ders = [];
+		for ( var i = 0; i <= n; ++ i )
+			ders[ i ] = zeroArr.slice( 0 );
+
+		var ndu = [];
+		for ( var i = 0; i <= p; ++ i )
+			ndu[ i ] = zeroArr.slice( 0 );
+
+		ndu[ 0 ][ 0 ] = 1.0;
+
+		var left = zeroArr.slice( 0 );
+		var right = zeroArr.slice( 0 );
+
+		for ( var j = 1; j <= p; ++ j ) {
+
+			left[ j ] = u - U[ span + 1 - j ];
+			right[ j ] = U[ span + j ] - u;
+
+			var saved = 0.0;
+
+			for ( var r = 0; r < j; ++ r ) {
+
+				var rv = right[ r + 1 ];
+				var lv = left[ j - r ];
+				ndu[ j ][ r ] = rv + lv;
+
+				var temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
+				ndu[ r ][ j ] = saved + rv * temp;
+				saved = lv * temp;
+
+			}
+
+			ndu[ j ][ j ] = saved;
+
+		}
+
+		for ( var j = 0; j <= p; ++ j ) {
+
+			ders[ 0 ][ j ] = ndu[ j ][ p ];
+
+		}
+
+		for ( var r = 0; r <= p; ++ r ) {
+
+			var s1 = 0;
+			var s2 = 1;
+
+			var a = [];
+			for ( var i = 0; i <= p; ++ i ) {
+
+				a[ i ] = zeroArr.slice( 0 );
+
+			}
+			a[ 0 ][ 0 ] = 1.0;
+
+			for ( var k = 1; k <= n; ++ k ) {
+
+				var d = 0.0;
+				var rk = r - k;
+				var pk = p - k;
+
+				if ( r >= k ) {
+
+					a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
+					d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
+
+				}
+
+				var j1 = ( rk >= - 1 ) ? 1 : - rk;
+				var j2 = ( r - 1 <= pk ) ? k - 1 : p - r;
+
+				for ( var j = j1; j <= j2; ++ j ) {
+
+					a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
+					d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
+
+				}
+
+				if ( r <= pk ) {
+
+					a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
+					d += a[ s2 ][ k ] * ndu[ r ][ pk ];
+
+				}
+
+				ders[ k ][ r ] = d;
+
+				var j = s1;
+				s1 = s2;
+				s2 = j;
+
+			}
+
+		}
+
+		var r = p;
+
+		for ( var k = 1; k <= n; ++ k ) {
+
+			for ( var j = 0; j <= p; ++ j ) {
+
+				ders[ k ][ j ] *= r;
+
+			}
+			r *= p - k;
+
+		}
+
+		return ders;
+
+	},
+
+
+	/*
+		Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
+
+		p  : degree
+		U  : knot vector
+		P  : control points
+		u  : Parametric points
+		nd : number of derivatives
+
+		returns array[d+1] with derivatives
+		*/
+	calcBSplineDerivatives: function ( p, U, P, u, nd ) {
+
+		var du = nd < p ? nd : p;
+		var CK = [];
+		var span = this.findSpan( p, u, U );
+		var nders = this.calcBasisFunctionDerivatives( span, u, p, du, U );
+		var Pw = [];
+
+		for ( var i = 0; i < P.length; ++ i ) {
+
+			var point = P[ i ].clone();
+			var w = point.w;
+
+			point.x *= w;
+			point.y *= w;
+			point.z *= w;
+
+			Pw[ i ] = point;
+
+		}
+		for ( var k = 0; k <= du; ++ k ) {
+
+			var point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
+
+			for ( var j = 1; j <= p; ++ j ) {
+
+				point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
+
+			}
+
+			CK[ k ] = point;
+
+		}
+
+		for ( var k = du + 1; k <= nd + 1; ++ k ) {
+
+			CK[ k ] = new Vector4( 0, 0, 0 );
+
+		}
+
+		return CK;
+
+	},
+
+
+	/*
+	Calculate "K over I"
+
+	returns k!/(i!(k-i)!)
+	*/
+	calcKoverI: function ( k, i ) {
+
+		var nom = 1;
+
+		for ( var j = 2; j <= k; ++ j ) {
+
+			nom *= j;
+
+		}
+
+		var denom = 1;
+
+		for ( var j = 2; j <= i; ++ j ) {
+
+			denom *= j;
+
+		}
+
+		for ( var j = 2; j <= k - i; ++ j ) {
+
+			denom *= j;
+
+		}
+
+		return nom / denom;
+
+	},
+
+
+	/*
+	Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
+
+	Pders : result of function calcBSplineDerivatives
+
+	returns array with derivatives for rational curve.
+	*/
+	calcRationalCurveDerivatives: function ( Pders ) {
+
+		var nd = Pders.length;
+		var Aders = [];
+		var wders = [];
+
+		for ( var i = 0; i < nd; ++ i ) {
+
+			var point = Pders[ i ];
+			Aders[ i ] = new Vector3( point.x, point.y, point.z );
+			wders[ i ] = point.w;
+
+		}
+
+		var CK = [];
+
+		for ( var k = 0; k < nd; ++ k ) {
+
+			var v = Aders[ k ].clone();
+
+			for ( var i = 1; i <= k; ++ i ) {
+
+				v.sub( CK[ k - i ].clone().multiplyScalar( this.calcKoverI( k, i ) * wders[ i ] ) );
+
+			}
+
+			CK[ k ] = v.divideScalar( wders[ 0 ] );
+
+		}
+
+		return CK;
+
+	},
+
+
+	/*
+	Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
+
+	p  : degree
+	U  : knot vector
+	P  : control points in homogeneous space
+	u  : parametric points
+	nd : number of derivatives
+
+	returns array with derivatives.
+	*/
+	calcNURBSDerivatives: function ( p, U, P, u, nd ) {
+
+		var Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
+		return this.calcRationalCurveDerivatives( Pders );
+
+	},
+
+
+	/*
+	Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
+
+	p1, p2 : degrees of B-Spline surface
+	U1, U2 : knot vectors
+	P      : control points (x, y, z, w)
+	u, v   : parametric values
+
+	returns point for given (u, v)
+	*/
+	calcSurfacePoint: function ( p, q, U, V, P, u, v, target ) {
+
+		var uspan = this.findSpan( p, u, U );
+		var vspan = this.findSpan( q, v, V );
+		var Nu = this.calcBasisFunctions( uspan, u, p, U );
+		var Nv = this.calcBasisFunctions( vspan, v, q, V );
+		var temp = [];
+
+		for ( var l = 0; l <= q; ++ l ) {
+
+			temp[ l ] = new Vector4( 0, 0, 0, 0 );
+			for ( var k = 0; k <= p; ++ k ) {
+
+				var point = P[ uspan - p + k ][ vspan - q + l ].clone();
+				var w = point.w;
+				point.x *= w;
+				point.y *= w;
+				point.z *= w;
+				temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
+
+			}
+
+		}
+
+		var Sw = new Vector4( 0, 0, 0, 0 );
+		for ( var l = 0; l <= q; ++ l ) {
+
+			Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
+
+		}
+
+		Sw.divideScalar( Sw.w );
+		target.set( Sw.x, Sw.y, Sw.z );
+
+	}
+
+};
+
+export { NURBSUtils };

+ 16 - 0
examples/jsm/loaders/3MFLoader.d.ts

@@ -0,0 +1,16 @@
+import {
+  LoadingManager,
+  Group
+} from '../../../src/Three';
+
+export class ThreeMFLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+  availableExtensions: object[];
+
+  load(url: string, onLoad: (object: Group) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  setPath(value: string): this;
+  parse(data: ArrayBuffer): Group;
+  addExtension(extension: object):void
+}

+ 943 - 0
examples/jsm/loaders/3MFLoader.js

@@ -0,0 +1,943 @@
+/**
+ * @author technohippy / https://github.com/technohippy
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/
+ *
+ * The following features from the core specification are supported:
+ *
+ * - 3D Models
+ * - Object Resources (Meshes and Components)
+ * - Material Resources (Base Materials)
+ *
+ * 3MF Materials and Properties Extension (e.g. textures) are not yet supported.
+ *
+ */
+
+import {
+	BufferAttribute,
+	BufferGeometry,
+	DefaultLoadingManager,
+	FileLoader,
+	Group,
+	LoaderUtils,
+	Matrix4,
+	Mesh,
+	MeshPhongMaterial
+} from "../../../build/three.module.js";
+
+var ThreeMFLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+	this.availableExtensions = [];
+
+};
+
+ThreeMFLoader.prototype = {
+
+	constructor: ThreeMFLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( buffer ) {
+
+			onLoad( scope.parse( buffer ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( data ) {
+
+		var scope = this;
+
+		function loadDocument( data ) {
+
+			var zip = null;
+			var file = null;
+
+			var relsName;
+			var modelPartNames = [];
+			var printTicketPartNames = [];
+			var texturesPartNames = [];
+			var otherPartNames = [];
+
+			var rels;
+			var modelParts = {};
+			var printTicketParts = {};
+			var texturesParts = {};
+			var otherParts = {};
+
+			try {
+
+				zip = new JSZip( data ); // eslint-disable-line no-undef
+
+			} catch ( e ) {
+
+				if ( e instanceof ReferenceError ) {
+
+					console.error( 'THREE.3MFLoader: jszip missing and file is compressed.' );
+					return null;
+
+				}
+
+			}
+
+			for ( file in zip.files ) {
+
+				if ( file.match( /\_rels\/.rels$/ ) ) {
+
+					relsName = file;
+
+				} else if ( file.match( /^3D\/.*\.model$/ ) ) {
+
+					modelPartNames.push( file );
+
+				} else if ( file.match( /^3D\/Metadata\/.*\.xml$/ ) ) {
+
+					printTicketPartNames.push( file );
+
+				} else if ( file.match( /^3D\/Textures\/.*/ ) ) {
+
+					texturesPartNames.push( file );
+
+				} else if ( file.match( /^3D\/Other\/.*/ ) ) {
+
+					otherPartNames.push( file );
+
+				}
+
+			}
+
+			var relsView = new Uint8Array( zip.file( relsName ).asArrayBuffer() );
+			var relsFileText = LoaderUtils.decodeText( relsView );
+			rels = parseRelsXml( relsFileText );
+
+			for ( var i = 0; i < modelPartNames.length; i ++ ) {
+
+				var modelPart = modelPartNames[ i ];
+				var view = new Uint8Array( zip.file( modelPart ).asArrayBuffer() );
+
+				var fileText = LoaderUtils.decodeText( view );
+				var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
+
+				if ( xmlData.documentElement.nodeName.toLowerCase() !== 'model' ) {
+
+					console.error( 'THREE.3MFLoader: Error loading 3MF - no 3MF document found: ', modelPart );
+
+				}
+
+				var modelNode = xmlData.querySelector( 'model' );
+				var extensions = {};
+
+				for ( var i = 0; i < modelNode.attributes.length; i ++ ) {
+
+					var attr = modelNode.attributes[ i ];
+					if ( attr.name.match( /^xmlns:(.+)$/ ) ) {
+
+						extensions[ attr.value ] = RegExp.$1;
+
+					}
+
+				}
+
+				var modelData = parseModelNode( modelNode );
+				modelData[ 'xml' ] = modelNode;
+
+				if ( 0 < Object.keys( extensions ).length ) {
+
+					modelData[ 'extensions' ] = extensions;
+
+				}
+
+				modelParts[ modelPart ] = modelData;
+
+			}
+
+			for ( var i = 0; i < texturesPartNames.length; i ++ ) {
+
+				var texturesPartName = texturesPartNames[ i ];
+				texturesParts[ texturesPartName ] = zip.file( texturesPartName ).asBinary();
+
+			}
+
+			return {
+				rels: rels,
+				model: modelParts,
+				printTicket: printTicketParts,
+				texture: texturesParts,
+				other: otherParts
+			};
+
+		}
+
+		function parseRelsXml( relsFileText ) {
+
+			var relsXmlData = new DOMParser().parseFromString( relsFileText, 'application/xml' );
+			var relsNode = relsXmlData.querySelector( 'Relationship' );
+			var target = relsNode.getAttribute( 'Target' );
+			var id = relsNode.getAttribute( 'Id' );
+			var type = relsNode.getAttribute( 'Type' );
+
+			return {
+				target: target,
+				id: id,
+				type: type
+			};
+
+		}
+
+		function parseMetadataNodes( metadataNodes ) {
+
+			var metadataData = {};
+
+			for ( var i = 0; i < metadataNodes.length; i ++ ) {
+
+				var metadataNode = metadataNodes[ i ];
+				var name = metadataNode.getAttribute( 'name' );
+				var validNames = [
+					'Title',
+					'Designer',
+					'Description',
+					'Copyright',
+					'LicenseTerms',
+					'Rating',
+					'CreationDate',
+					'ModificationDate'
+				];
+
+				if ( 0 <= validNames.indexOf( name ) ) {
+
+					metadataData[ name ] = metadataNode.textContent;
+
+				}
+
+			}
+
+			return metadataData;
+
+		}
+
+		function parseBasematerialsNode( basematerialsNode ) {
+
+			var basematerialsData = {
+				id: basematerialsNode.getAttribute( 'id' ), // required
+				basematerials: []
+			};
+
+			var basematerialNodes = basematerialsNode.querySelectorAll( 'base' );
+
+			for ( var i = 0; i < basematerialNodes.length; i ++ ) {
+
+				var basematerialNode = basematerialNodes[ i ];
+				var basematerialData = parseBasematerialNode( basematerialNode );
+				basematerialsData.basematerials.push( basematerialData );
+
+			}
+
+			return basematerialsData;
+
+		}
+
+		function parseBasematerialNode( basematerialNode ) {
+
+			var basematerialData = {};
+
+			basematerialData[ 'name' ] = basematerialNode.getAttribute( 'name' ); // required
+			basematerialData[ 'displaycolor' ] = basematerialNode.getAttribute( 'displaycolor' ); // required
+
+			return basematerialData;
+
+		}
+
+		function parseMeshNode( meshNode ) {
+
+			var meshData = {};
+
+			var vertices = [];
+			var vertexNodes = meshNode.querySelectorAll( 'vertices vertex' );
+
+			for ( var i = 0; i < vertexNodes.length; i ++ ) {
+
+				var vertexNode = vertexNodes[ i ];
+				var x = vertexNode.getAttribute( 'x' );
+				var y = vertexNode.getAttribute( 'y' );
+				var z = vertexNode.getAttribute( 'z' );
+
+				vertices.push( parseFloat( x ), parseFloat( y ), parseFloat( z ) );
+
+			}
+
+			meshData[ 'vertices' ] = new Float32Array( vertices.length );
+
+			for ( var i = 0; i < vertices.length; i ++ ) {
+
+				meshData[ 'vertices' ][ i ] = vertices[ i ];
+
+			}
+
+			var triangleProperties = [];
+			var triangles = [];
+			var triangleNodes = meshNode.querySelectorAll( 'triangles triangle' );
+
+			for ( var i = 0; i < triangleNodes.length; i ++ ) {
+
+				var triangleNode = triangleNodes[ i ];
+				var v1 = triangleNode.getAttribute( 'v1' );
+				var v2 = triangleNode.getAttribute( 'v2' );
+				var v3 = triangleNode.getAttribute( 'v3' );
+				var p1 = triangleNode.getAttribute( 'p1' );
+				var p2 = triangleNode.getAttribute( 'p2' );
+				var p3 = triangleNode.getAttribute( 'p3' );
+				var pid = triangleNode.getAttribute( 'pid' );
+
+				triangles.push( parseInt( v1, 10 ), parseInt( v2, 10 ), parseInt( v3, 10 ) );
+
+				var triangleProperty = {};
+
+				if ( p1 ) {
+
+					triangleProperty[ 'p1' ] = parseInt( p1, 10 );
+
+				}
+
+				if ( p2 ) {
+
+					triangleProperty[ 'p2' ] = parseInt( p2, 10 );
+
+				}
+
+				if ( p3 ) {
+
+					triangleProperty[ 'p3' ] = parseInt( p3, 10 );
+
+				}
+
+				if ( pid ) {
+
+					triangleProperty[ 'pid' ] = pid;
+
+				}
+
+				if ( 0 < Object.keys( triangleProperty ).length ) {
+
+					triangleProperties.push( triangleProperty );
+
+				}
+
+			}
+
+			meshData[ 'triangleProperties' ] = triangleProperties;
+			meshData[ 'triangles' ] = new Uint32Array( triangles.length );
+
+			for ( var i = 0; i < triangles.length; i ++ ) {
+
+				meshData[ 'triangles' ][ i ] = triangles[ i ];
+
+			}
+
+			return meshData;
+
+		}
+
+		function parseComponentsNode( componentsNode ) {
+
+			var components = [];
+
+			var componentNodes = componentsNode.querySelectorAll( 'component' );
+
+			for ( var i = 0; i < componentNodes.length; i ++ ) {
+
+				var componentNode = componentNodes[ i ];
+				var componentData = parseComponentNode( componentNode );
+				components.push( componentData );
+
+			}
+
+			return components;
+
+		}
+
+		function parseComponentNode( componentNode ) {
+
+			var componentData = {};
+
+			componentData[ 'objectId' ] = componentNode.getAttribute( 'objectid' ); // required
+
+			var transform = componentNode.getAttribute( 'transform' );
+
+			if ( transform ) {
+
+				componentData[ 'transform' ] = parseTransform( transform );
+
+			}
+
+			return componentData;
+
+		}
+
+		function parseTransform( transform ) {
+
+			var t = [];
+			transform.split( ' ' ).forEach( function ( s ) {
+
+				t.push( parseFloat( s ) );
+
+			} );
+
+			var matrix = new Matrix4();
+			matrix.set(
+				t[ 0 ], t[ 3 ], t[ 6 ], t[ 9 ],
+				t[ 1 ], t[ 4 ], t[ 7 ], t[ 10 ],
+				t[ 2 ], t[ 5 ], t[ 8 ], t[ 11 ],
+				 0.0, 0.0, 0.0, 1.0
+			);
+
+			return matrix;
+
+		}
+
+		function parseObjectNode( objectNode ) {
+
+			var objectData = {
+				type: objectNode.getAttribute( 'type' )
+			};
+
+			var id = objectNode.getAttribute( 'id' );
+
+			if ( id ) {
+
+				objectData[ 'id' ] = id;
+
+			}
+
+			var pid = objectNode.getAttribute( 'pid' );
+
+			if ( pid ) {
+
+				objectData[ 'pid' ] = pid;
+
+			}
+
+			var pindex = objectNode.getAttribute( 'pindex' );
+
+			if ( pindex ) {
+
+				objectData[ 'pindex' ] = pindex;
+
+			}
+
+			var thumbnail = objectNode.getAttribute( 'thumbnail' );
+
+			if ( thumbnail ) {
+
+				objectData[ 'thumbnail' ] = thumbnail;
+
+			}
+
+			var partnumber = objectNode.getAttribute( 'partnumber' );
+
+			if ( partnumber ) {
+
+				objectData[ 'partnumber' ] = partnumber;
+
+			}
+
+			var name = objectNode.getAttribute( 'name' );
+
+			if ( name ) {
+
+				objectData[ 'name' ] = name;
+
+			}
+
+			var meshNode = objectNode.querySelector( 'mesh' );
+
+			if ( meshNode ) {
+
+				objectData[ 'mesh' ] = parseMeshNode( meshNode );
+
+			}
+
+			var componentsNode = objectNode.querySelector( 'components' );
+
+			if ( componentsNode ) {
+
+				objectData[ 'components' ] = parseComponentsNode( componentsNode );
+
+			}
+
+			return objectData;
+
+		}
+
+		function parseResourcesNode( resourcesNode ) {
+
+			var resourcesData = {};
+			var basematerialsNode = resourcesNode.querySelector( 'basematerials' );
+
+			if ( basematerialsNode ) {
+
+				resourcesData[ 'basematerials' ] = parseBasematerialsNode( basematerialsNode );
+
+			}
+
+			resourcesData[ 'object' ] = {};
+			var objectNodes = resourcesNode.querySelectorAll( 'object' );
+
+			for ( var i = 0; i < objectNodes.length; i ++ ) {
+
+				var objectNode = objectNodes[ i ];
+				var objectData = parseObjectNode( objectNode );
+				resourcesData[ 'object' ][ objectData[ 'id' ] ] = objectData;
+
+			}
+
+			return resourcesData;
+
+		}
+
+		function parseBuildNode( buildNode ) {
+
+			var buildData = [];
+			var itemNodes = buildNode.querySelectorAll( 'item' );
+
+			for ( var i = 0; i < itemNodes.length; i ++ ) {
+
+				var itemNode = itemNodes[ i ];
+				var buildItem = {
+					objectId: itemNode.getAttribute( 'objectid' )
+				};
+				var transform = itemNode.getAttribute( 'transform' );
+
+				if ( transform ) {
+
+					buildItem[ 'transform' ] = parseTransform( transform );
+
+				}
+
+				buildData.push( buildItem );
+
+			}
+
+			return buildData;
+
+		}
+
+		function parseModelNode( modelNode ) {
+
+			var modelData = { unit: modelNode.getAttribute( 'unit' ) || 'millimeter' };
+			var metadataNodes = modelNode.querySelectorAll( 'metadata' );
+
+			if ( metadataNodes ) {
+
+				modelData[ 'metadata' ] = parseMetadataNodes( metadataNodes );
+
+			}
+
+			var resourcesNode = modelNode.querySelector( 'resources' );
+
+			if ( resourcesNode ) {
+
+				modelData[ 'resources' ] = parseResourcesNode( resourcesNode );
+
+			}
+
+			var buildNode = modelNode.querySelector( 'build' );
+
+			if ( buildNode ) {
+
+				modelData[ 'build' ] = parseBuildNode( buildNode );
+
+			}
+
+			return modelData;
+
+		}
+
+		function buildMesh( meshData, objects, modelData, objectData ) {
+
+			// geometry
+
+			var geometry = new BufferGeometry();
+			geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) );
+			geometry.addAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) );
+
+			// groups
+
+			var basematerialsData = modelData[ 'resources' ][ 'basematerials' ];
+			var triangleProperties = meshData[ 'triangleProperties' ];
+
+			var start = 0;
+			var count = 0;
+			var currentMaterialIndex = - 1;
+
+			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+
+				var triangleProperty = triangleProperties[ i ];
+				var pid = triangleProperty.pid;
+
+				// only proceed if the triangle refers to a basematerials definition
+
+				if ( basematerialsData && ( basematerialsData.id === pid ) ) {
+
+					if ( currentMaterialIndex === - 1 ) currentMaterialIndex = triangleProperty.p1;
+
+					if ( currentMaterialIndex === triangleProperty.p1 ) {
+
+						count += 3; // primitves per triangle
+
+					} else {
+
+						geometry.addGroup( start, count, currentMaterialIndex );
+
+						start += count;
+						count = 3;
+						currentMaterialIndex = triangleProperty.p1;
+
+					}
+
+				}
+
+			}
+
+			if ( geometry.groups.length > 0 ) mergeGroups( geometry );
+
+			geometry.computeBoundingSphere();
+
+			// material
+
+			var material;
+
+			// add material if an object-level definition is present
+
+			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
+
+				var materialIndex = objectData.pindex;
+				var basematerialData = basematerialsData.basematerials[ materialIndex ];
+
+				material = getBuild( basematerialData, objects, modelData, objectData, buildBasematerial );
+
+			}
+
+			// add/overwrite material if definitions on triangles are present
+
+			if ( geometry.groups.length > 0 ) {
+
+				var groups = geometry.groups;
+				material = [];
+
+				for ( var i = 0, l = groups.length; i < l; i ++ ) {
+
+					var group = groups[ i ];
+					var basematerialData = basematerialsData.basematerials[ group.materialIndex ];
+
+					material.push( getBuild( basematerialData, objects, modelData, objectData, buildBasematerial ) );
+
+				}
+
+			}
+
+			// default material
+
+			if ( material === undefined ) material = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+
+			return new Mesh( geometry, material );
+
+		}
+
+		function mergeGroups( geometry ) {
+
+			// sort by material index
+
+			var groups = geometry.groups.sort( function ( a, b ) {
+
+				if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex;
+
+				return a.start - b.start;
+
+			} );
+
+			// reorganize index buffer
+
+			var index = geometry.index;
+
+			var itemSize = index.itemSize;
+			var srcArray = index.array;
+
+			var targetOffset = 0;
+
+			var targetArray = new srcArray.constructor( srcArray.length );
+
+			for ( var i = 0; i < groups.length; i ++ ) {
+
+				var group = groups[ i ];
+
+				var groupLength = group.count * itemSize;
+				var groupStart = group.start * itemSize;
+
+				var sub = srcArray.subarray( groupStart, groupStart + groupLength );
+
+				targetArray.set( sub, targetOffset );
+
+				targetOffset += groupLength;
+
+			}
+
+			srcArray.set( targetArray );
+
+			// update groups
+
+			var start = 0;
+
+			for ( i = 0; i < groups.length; i ++ ) {
+
+				group = groups[ i ];
+
+				group.start = start;
+				start += group.count;
+
+			}
+
+			// merge groups
+
+			var lastGroup = groups[ 0 ];
+
+			geometry.groups = [ lastGroup ];
+
+			for ( i = 1; i < groups.length; i ++ ) {
+
+				group = groups[ i ];
+
+				if ( lastGroup.materialIndex === group.materialIndex ) {
+
+					lastGroup.count += group.count;
+
+				} else {
+
+					lastGroup = group;
+					geometry.groups.push( lastGroup );
+
+				}
+
+			}
+
+		}
+
+		function applyExtensions( extensions, meshData, modelXml ) {
+
+			if ( ! extensions ) {
+
+				return;
+
+			}
+
+			var availableExtensions = [];
+			var keys = Object.keys( extensions );
+
+			for ( var i = 0; i < keys.length; i ++ ) {
+
+				var ns = keys[ i ];
+
+				for ( var j = 0; j < scope.availableExtensions.length; j ++ ) {
+
+					var extension = scope.availableExtensions[ j ];
+
+					if ( extension.ns === ns ) {
+
+						availableExtensions.push( extension );
+
+					}
+
+				}
+
+			}
+
+			for ( var i = 0; i < availableExtensions.length; i ++ ) {
+
+				var extension = availableExtensions[ i ];
+				extension.apply( modelXml, extensions[ extension[ 'ns' ] ], meshData );
+
+			}
+
+		}
+
+		function getBuild( data, objects, modelData, objectData, builder ) {
+
+			if ( data.build !== undefined ) return data.build;
+
+			data.build = builder( data, objects, modelData, objectData );
+
+			return data.build;
+
+		}
+
+		function buildBasematerial( materialData ) {
+
+			var material = new MeshPhongMaterial( { flatShading: true } );
+
+			material.name = materialData.name;
+
+			// displaycolor MUST be specified with a value of a 6 or 8 digit hexadecimal number, e.g. "#RRGGBB" or "#RRGGBBAA"
+
+			var displaycolor = materialData.displaycolor;
+
+			var color = displaycolor.substring( 0, 7 );
+			material.color.setStyle( color );
+			material.color.convertSRGBToLinear(); // displaycolor is in sRGB
+
+			// process alpha if set
+
+			if ( displaycolor.length === 9 ) {
+
+				material.opacity = parseInt( displaycolor.charAt( 7 ) + displaycolor.charAt( 8 ), 16 ) / 255;
+
+			}
+
+			return material;
+
+		}
+
+		function buildComposite( compositeData, objects, modelData ) {
+
+			var composite = new Group();
+
+			for ( var j = 0; j < compositeData.length; j ++ ) {
+
+				var component = compositeData[ j ];
+				var build = objects[ component.objectId ];
+
+				if ( build === undefined ) {
+
+					buildObject( component.objectId, objects, modelData );
+					build = objects[ component.objectId ];
+
+				}
+
+				var object3D = build.clone();
+
+				// apply component transfrom
+
+				var transform = component.transform;
+
+				if ( transform ) {
+
+					object3D.applyMatrix( transform );
+
+				}
+
+				composite.add( object3D );
+
+			}
+
+			return composite;
+
+		}
+
+		function buildObject( objectId, objects, modelData ) {
+
+			var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
+
+			if ( objectData[ 'mesh' ] ) {
+
+				var meshData = objectData[ 'mesh' ];
+
+				var extensions = modelData[ 'extensions' ];
+				var modelXml = modelData[ 'xml' ];
+
+				applyExtensions( extensions, meshData, modelXml );
+
+				objects[ objectData.id ] = getBuild( meshData, objects, modelData, objectData, buildMesh );
+
+			} else {
+
+				var compositeData = objectData[ 'components' ];
+
+				objects[ objectData.id ] = getBuild( compositeData, objects, modelData, objectData, buildComposite );
+
+			}
+
+		}
+
+		function buildObjects( data3mf ) {
+
+			var modelsData = data3mf.model;
+			var objects = {};
+			var modelsKeys = Object.keys( modelsData );
+
+			for ( var i = 0; i < modelsKeys.length; i ++ ) {
+
+				var modelsKey = modelsKeys[ i ];
+				var modelData = modelsData[ modelsKey ];
+
+				var objectIds = Object.keys( modelData[ 'resources' ][ 'object' ] );
+
+				for ( var j = 0; j < objectIds.length; j ++ ) {
+
+					var objectId = objectIds[ j ];
+
+					buildObject( objectId, objects, modelData );
+
+				}
+
+			}
+
+			return objects;
+
+		}
+
+		function build( objects, refs, data3mf ) {
+
+			var group = new Group();
+			var buildData = data3mf.model[ refs[ 'target' ].substring( 1 ) ][ 'build' ];
+
+			for ( var i = 0; i < buildData.length; i ++ ) {
+
+				var buildItem = buildData[ i ];
+				var object3D = objects[ buildItem[ 'objectId' ] ];
+
+				// apply transform
+
+				var transform = buildItem[ 'transform' ];
+
+				if ( transform ) {
+
+					object3D.applyMatrix( transform );
+
+				}
+
+				group.add( object3D );
+
+			}
+
+			return group;
+
+		}
+
+		var data3mf = loadDocument( data );
+		var objects = buildObjects( data3mf );
+
+		return build( objects, data3mf[ 'rels' ], data3mf );
+
+	},
+
+	addExtension: function ( extension ) {
+
+		this.availableExtensions.push( extension );
+
+	}
+
+};
+
+export { ThreeMFLoader };

+ 14 - 0
examples/jsm/loaders/AMFLoader.d.ts

@@ -0,0 +1,14 @@
+import {
+  LoadingManager,
+  Group
+} from '../../../src/Three';
+
+export class AMFLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  load(url: string, onLoad: (object: Group) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  setPath(value: string): this;
+  parse(data: ArrayBuffer): Group;
+}

+ 509 - 0
examples/jsm/loaders/AMFLoader.js

@@ -0,0 +1,509 @@
+/*
+ * @author tamarintech / https://tamarintech.com
+ *
+ * Description: Early release of an AMF Loader following the pattern of the
+ * example loaders in the three.js project.
+ *
+ * More information about the AMF format: http://amf.wikispaces.com
+ *
+ * Usage:
+ *	var loader = new AMFLoader();
+ *	loader.load('/path/to/project.amf', function(objecttree) {
+ *		scene.add(objecttree);
+ *	});
+ *
+ * Materials now supported, material colors supported
+ * Zip support, requires jszip
+ * No constellation support (yet)!
+ *
+ */
+
+import {
+	BufferGeometry,
+	Color,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	LoaderUtils,
+	Mesh,
+	MeshPhongMaterial
+} from "../../../build/three.module.js";
+
+var AMFLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+AMFLoader.prototype = {
+
+	constructor: AMFLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( data ) {
+
+		function loadDocument( data ) {
+
+			var view = new DataView( data );
+			var magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
+
+			if ( magic === 'PK' ) {
+
+				var zip = null;
+				var file = null;
+
+				console.log( 'THREE.AMFLoader: Loading Zip' );
+
+				try {
+
+					zip = new JSZip( data ); // eslint-disable-line no-undef
+
+				} catch ( e ) {
+
+					if ( e instanceof ReferenceError ) {
+
+						console.log( 'THREE.AMFLoader: jszip missing and file is compressed.' );
+						return null;
+
+					}
+
+				}
+
+				for ( file in zip.files ) {
+
+					if ( file.toLowerCase().substr( - 4 ) === '.amf' ) {
+
+						break;
+
+					}
+
+				}
+
+				console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
+				view = new DataView( zip.file( file ).asArrayBuffer() );
+
+			}
+
+			var fileText = LoaderUtils.decodeText( view );
+			var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
+
+			if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {
+
+				console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
+				return null;
+
+			}
+
+			return xmlData;
+
+		}
+
+		function loadDocumentScale( node ) {
+
+			var scale = 1.0;
+			var unit = 'millimeter';
+
+			if ( node.documentElement.attributes.unit !== undefined ) {
+
+				unit = node.documentElement.attributes.unit.value.toLowerCase();
+
+			}
+
+			var scaleUnits = {
+				millimeter: 1.0,
+				inch: 25.4,
+				feet: 304.8,
+				meter: 1000.0,
+				micron: 0.001
+			};
+
+			if ( scaleUnits[ unit ] !== undefined ) {
+
+				scale = scaleUnits[ unit ];
+
+			}
+
+			console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
+			return scale;
+
+		}
+
+		function loadMaterials( node ) {
+
+			var matName = 'AMF Material';
+			var matId = node.attributes.id.textContent;
+			var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+
+			var loadedMaterial = null;
+
+			for ( var i = 0; i < node.childNodes.length; i ++ ) {
+
+				var matChildEl = node.childNodes[ i ];
+
+				if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {
+
+					if ( matChildEl.attributes.type.value === 'name' ) {
+
+						matName = matChildEl.textContent;
+
+					}
+
+				} else if ( matChildEl.nodeName === 'color' ) {
+
+					color = loadColor( matChildEl );
+
+				}
+
+			}
+
+			loadedMaterial = new MeshPhongMaterial( {
+				flatShading: true,
+				color: new Color( color.r, color.g, color.b ),
+				name: matName
+			} );
+
+			if ( color.a !== 1.0 ) {
+
+				loadedMaterial.transparent = true;
+				loadedMaterial.opacity = color.a;
+
+			}
+
+			return { id: matId, material: loadedMaterial };
+
+		}
+
+		function loadColor( node ) {
+
+			var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+
+			for ( var i = 0; i < node.childNodes.length; i ++ ) {
+
+				var matColor = node.childNodes[ i ];
+
+				if ( matColor.nodeName === 'r' ) {
+
+					color.r = matColor.textContent;
+
+				} else if ( matColor.nodeName === 'g' ) {
+
+					color.g = matColor.textContent;
+
+				} else if ( matColor.nodeName === 'b' ) {
+
+					color.b = matColor.textContent;
+
+				} else if ( matColor.nodeName === 'a' ) {
+
+					color.a = matColor.textContent;
+
+				}
+
+			}
+
+			return color;
+
+		}
+
+		function loadMeshVolume( node ) {
+
+			var volume = { name: '', triangles: [], materialid: null };
+
+			var currVolumeNode = node.firstElementChild;
+
+			if ( node.attributes.materialid !== undefined ) {
+
+				volume.materialId = node.attributes.materialid.nodeValue;
+
+			}
+
+			while ( currVolumeNode ) {
+
+				if ( currVolumeNode.nodeName === 'metadata' ) {
+
+					if ( currVolumeNode.attributes.type !== undefined ) {
+
+						if ( currVolumeNode.attributes.type.value === 'name' ) {
+
+							volume.name = currVolumeNode.textContent;
+
+						}
+
+					}
+
+				} else if ( currVolumeNode.nodeName === 'triangle' ) {
+
+					var v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
+					var v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
+					var v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
+
+					volume.triangles.push( v1, v2, v3 );
+
+				}
+
+				currVolumeNode = currVolumeNode.nextElementSibling;
+
+			}
+
+			return volume;
+
+		}
+
+		function loadMeshVertices( node ) {
+
+			var vertArray = [];
+			var normalArray = [];
+			var currVerticesNode = node.firstElementChild;
+
+			while ( currVerticesNode ) {
+
+				if ( currVerticesNode.nodeName === 'vertex' ) {
+
+					var vNode = currVerticesNode.firstElementChild;
+
+					while ( vNode ) {
+
+						if ( vNode.nodeName === 'coordinates' ) {
+
+							var x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
+							var y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
+							var z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
+
+							vertArray.push( x, y, z );
+
+						} else if ( vNode.nodeName === 'normal' ) {
+
+							var nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
+							var ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
+							var nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
+
+							normalArray.push( nx, ny, nz );
+
+						}
+
+						vNode = vNode.nextElementSibling;
+
+					}
+
+				}
+				currVerticesNode = currVerticesNode.nextElementSibling;
+
+			}
+
+			return { 'vertices': vertArray, 'normals': normalArray };
+
+		}
+
+		function loadObject( node ) {
+
+			var objId = node.attributes.id.textContent;
+			var loadedObject = { name: 'amfobject', meshes: [] };
+			var currColor = null;
+			var currObjNode = node.firstElementChild;
+
+			while ( currObjNode ) {
+
+				if ( currObjNode.nodeName === 'metadata' ) {
+
+					if ( currObjNode.attributes.type !== undefined ) {
+
+						if ( currObjNode.attributes.type.value === 'name' ) {
+
+							loadedObject.name = currObjNode.textContent;
+
+						}
+
+					}
+
+				} else if ( currObjNode.nodeName === 'color' ) {
+
+					currColor = loadColor( currObjNode );
+
+				} else if ( currObjNode.nodeName === 'mesh' ) {
+
+					var currMeshNode = currObjNode.firstElementChild;
+					var mesh = { vertices: [], normals: [], volumes: [], color: currColor };
+
+					while ( currMeshNode ) {
+
+						if ( currMeshNode.nodeName === 'vertices' ) {
+
+							var loadedVertices = loadMeshVertices( currMeshNode );
+
+							mesh.normals = mesh.normals.concat( loadedVertices.normals );
+							mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );
+
+						} else if ( currMeshNode.nodeName === 'volume' ) {
+
+							mesh.volumes.push( loadMeshVolume( currMeshNode ) );
+
+						}
+
+						currMeshNode = currMeshNode.nextElementSibling;
+
+					}
+
+					loadedObject.meshes.push( mesh );
+
+				}
+
+				currObjNode = currObjNode.nextElementSibling;
+
+			}
+
+			return { 'id': objId, 'obj': loadedObject };
+
+		}
+
+		var xmlData = loadDocument( data );
+		var amfName = '';
+		var amfAuthor = '';
+		var amfScale = loadDocumentScale( xmlData );
+		var amfMaterials = {};
+		var amfObjects = {};
+		var childNodes = xmlData.documentElement.childNodes;
+
+		var i, j;
+
+		for ( i = 0; i < childNodes.length; i ++ ) {
+
+			var child = childNodes[ i ];
+
+			if ( child.nodeName === 'metadata' ) {
+
+				if ( child.attributes.type !== undefined ) {
+
+					if ( child.attributes.type.value === 'name' ) {
+
+						amfName = child.textContent;
+
+					} else if ( child.attributes.type.value === 'author' ) {
+
+						amfAuthor = child.textContent;
+
+					}
+
+				}
+
+			} else if ( child.nodeName === 'material' ) {
+
+				var loadedMaterial = loadMaterials( child );
+
+				amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
+
+			} else if ( child.nodeName === 'object' ) {
+
+				var loadedObject = loadObject( child );
+
+				amfObjects[ loadedObject.id ] = loadedObject.obj;
+
+			}
+
+		}
+
+		var sceneObject = new Group();
+		var defaultMaterial = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+
+		sceneObject.name = amfName;
+		sceneObject.userData.author = amfAuthor;
+		sceneObject.userData.loader = 'AMF';
+
+		for ( var id in amfObjects ) {
+
+			var part = amfObjects[ id ];
+			var meshes = part.meshes;
+			var newObject = new Group();
+			newObject.name = part.name || '';
+
+			for ( i = 0; i < meshes.length; i ++ ) {
+
+				var objDefaultMaterial = defaultMaterial;
+				var mesh = meshes[ i ];
+				var vertices = new Float32BufferAttribute( mesh.vertices, 3 );
+				var normals = null;
+
+				if ( mesh.normals.length ) {
+
+					normals = new Float32BufferAttribute( mesh.normals, 3 );
+
+				}
+
+				if ( mesh.color ) {
+
+					var color = mesh.color;
+
+					objDefaultMaterial = defaultMaterial.clone();
+					objDefaultMaterial.color = new Color( color.r, color.g, color.b );
+
+					if ( color.a !== 1.0 ) {
+
+						objDefaultMaterial.transparent = true;
+						objDefaultMaterial.opacity = color.a;
+
+					}
+
+				}
+
+				var volumes = mesh.volumes;
+
+				for ( j = 0; j < volumes.length; j ++ ) {
+
+					var volume = volumes[ j ];
+					var newGeometry = new BufferGeometry();
+					var material = objDefaultMaterial;
+
+					newGeometry.setIndex( volume.triangles );
+					newGeometry.addAttribute( 'position', vertices.clone() );
+
+					if ( normals ) {
+
+						newGeometry.addAttribute( 'normal', normals.clone() );
+
+					}
+
+					if ( amfMaterials[ volume.materialId ] !== undefined ) {
+
+						material = amfMaterials[ volume.materialId ];
+
+					}
+
+					newGeometry.scale( amfScale, amfScale, amfScale );
+					newObject.add( new Mesh( newGeometry, material.clone() ) );
+
+				}
+
+			}
+
+			sceneObject.add( newObject );
+
+		}
+
+		return sceneObject;
+
+	}
+
+};
+
+export { AMFLoader };

+ 18 - 0
examples/jsm/loaders/AssimpJSONLoader.d.ts

@@ -0,0 +1,18 @@
+import {
+  Object3D,
+  LoadingManager
+} from '../../../src/Three';
+
+export class AssimpJSONLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  crossOrigin: string;
+  path: string;
+  resourcePath: string;
+
+  load(url: string, onLoad: (object: Object3D) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+  setResourcePath(path: string) : this;
+  setCrossOrigin(value: string): this;
+  parse(json: object, path: string) : Object3D;
+}

+ 309 - 0
examples/jsm/loaders/AssimpJSONLoader.js

@@ -0,0 +1,309 @@
+/**
+ * @author Alexander Gessler / http://www.greentoken.de/
+ * https://github.com/acgessler
+ *
+ * Loader for models imported with Open Asset Import Library (http://assimp.sf.net)
+ * through assimp2json (https://github.com/acgessler/assimp2json).
+ *
+ * Supports any input format that assimp supports, including 3ds, obj, dae, blend,
+ * fbx, x, ms3d, lwo (and many more).
+ *
+ * See webgl_loader_assimp2json example.
+ */
+
+import {
+	BufferGeometry,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	LoaderUtils,
+	Matrix4,
+	Mesh,
+	MeshPhongMaterial,
+	Object3D,
+	RepeatWrapping,
+	TextureLoader
+} from "../../../build/three.module.js";
+
+var AssimpJSONLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+AssimpJSONLoader.prototype = {
+
+	constructor: AssimpJSONLoader,
+
+	crossOrigin: 'anonymous',
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var path = ( scope.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : scope.path;
+
+		var loader = new FileLoader( this.manager );
+		loader.setPath( scope.path );
+		loader.load( url, function ( text ) {
+
+			var json = JSON.parse( text );
+			var metadata = json.__metadata__;
+
+			// check if __metadata__ meta header is present
+			// this header is used to disambiguate between different JSON-based file formats
+
+			if ( typeof metadata !== 'undefined' ) {
+
+				// check if assimp2json at all
+
+				if ( metadata.format !== 'assimp2json' ) {
+
+					onError( 'THREE.AssimpJSONLoader: Not an assimp2json scene.' );
+					return;
+
+					// check major format version
+
+				} else if ( metadata.version < 100 && metadata.version >= 200 ) {
+
+					onError( 'THREE.AssimpJSONLoader: Unsupported assimp2json file format version.' );
+					return;
+
+				}
+
+			}
+
+			onLoad( scope.parse( json, path ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	setResourcePath: function ( value ) {
+
+		this.resourcePath = value;
+		return this;
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	parse: function ( json, path ) {
+
+		function parseList( json, handler ) {
+
+			var meshes = new Array( json.length );
+
+			for ( var i = 0; i < json.length; ++ i ) {
+
+				meshes[ i ] = handler.call( this, json[ i ] );
+
+			}
+
+			return meshes;
+
+		}
+
+		function parseMesh( json ) {
+
+			var geometry = new BufferGeometry();
+
+			var i, l, face;
+
+			var indices = [];
+
+			var vertices = json.vertices || [];
+			var normals = json.normals || [];
+			var uvs = json.texturecoords || [];
+			var colors = json.colors || [];
+
+			uvs = uvs[ 0 ] || []; // only support for a single set of uvs
+
+			for ( i = 0, l = json.faces.length; i < l; i ++ ) {
+
+				face = json.faces[ i ];
+				indices.push( face[ 0 ], face[ 1 ], face[ 2 ] );
+
+			}
+
+			geometry.setIndex( indices );
+			geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+
+			if ( normals.length > 0 ) {
+
+				geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+
+			}
+
+			if ( uvs.length > 0 ) {
+
+				geometry.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+			}
+
+			if ( colors.length > 0 ) {
+
+				geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
+
+			}
+
+			geometry.computeBoundingSphere();
+
+			return geometry;
+
+		}
+
+		function parseMaterial( json ) {
+
+			var material = new MeshPhongMaterial();
+
+			for ( var i in json.properties ) {
+
+				var property = json.properties[ i ];
+				var key = property.key;
+				var value = property.value;
+
+				switch ( key ) {
+
+					case '$tex.file': {
+
+						var semantic = property.semantic;
+
+						// prop.semantic gives the type of the texture
+						// 1: diffuse
+						// 2: specular map
+						// 4: emissive map
+						// 5: height map (bumps)
+						// 6: normal map
+						// more values (i.e. environment, etc) are known by assimp and may be relevant
+
+						if ( semantic === 1 || semantic === 2 || semantic === 4 || semantic === 5 || semantic === 6 ) {
+
+							var keyname;
+
+							switch ( semantic ) {
+
+								case 1:
+									keyname = 'map';
+									break;
+								case 2:
+									keyname = 'specularMap';
+									break;
+								case 4:
+									keyname = 'emissiveMap';
+									break;
+								case 5:
+									keyname = 'bumpMap';
+									break;
+								case 6:
+									keyname = 'normalMap';
+									break;
+
+							}
+
+							var texture = textureLoader.load( value );
+
+							// TODO: read texture settings from assimp.
+							// Wrapping is the default, though.
+
+							texture.wrapS = texture.wrapT = RepeatWrapping;
+
+							material[ keyname ] = texture;
+
+						}
+
+						break;
+
+					}
+
+					case '?mat.name':
+						material.name = value;
+						break;
+
+					case '$clr.diffuse':
+						material.color.fromArray( value );
+						break;
+
+					case '$clr.specular':
+						material.specular.fromArray( value );
+						break;
+
+					case '$clr.emissive':
+						material.emissive.fromArray( value );
+						break;
+
+					case '$mat.shininess':
+						material.shininess = value;
+						break;
+
+					case '$mat.shadingm':
+						// aiShadingMode_Flat
+						material.flatShading = ( value === 1 ) ? true : false;
+						break;
+
+					case '$mat.opacity':
+						if ( value < 1 ) {
+
+							material.opacity = value;
+							material.transparent = true;
+
+						}
+						break;
+
+				}
+
+			}
+
+			return material;
+
+		}
+
+		function parseObject( json, node, meshes, materials ) {
+
+			var obj = new Object3D(),	i, idx;
+
+			obj.name = node.name || '';
+			obj.matrix = new Matrix4().fromArray( node.transformation ).transpose();
+			obj.matrix.decompose( obj.position, obj.quaternion, obj.scale );
+
+			for ( i = 0; node.meshes && i < node.meshes.length; i ++ ) {
+
+				idx = node.meshes[ i ];
+				obj.add( new Mesh( meshes[ idx ], materials[ json.meshes[ idx ].materialindex ] ) );
+
+			}
+
+			for ( i = 0; node.children && i < node.children.length; i ++ ) {
+
+				obj.add( parseObject( json, node.children[ i ], meshes, materials ) );
+
+			}
+
+			return obj;
+
+		}
+
+		var textureLoader = new TextureLoader( this.manager );
+		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+
+		var meshes = parseList( json.meshes, parseMesh );
+		var materials = parseList( json.materials, parseMaterial );
+		return parseObject( json, json.rootnode, meshes, materials );
+
+	}
+
+};
+
+export { AssimpJSONLoader };

+ 24 - 0
examples/jsm/loaders/AssimpLoader.d.ts

@@ -0,0 +1,24 @@
+import {
+  Object3D,
+  LoadingManager
+} from '../../../src/Three';
+
+
+export interface Assimp {
+  animation: any;
+  object: Object3D;
+}
+
+export class AssimpLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  crossOrigin: string;
+  path: string;
+  resourcePath: string;
+
+  load(url: string, onLoad: (result: Assimp) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+  setResourcePath(path: string) : this;
+  setCrossOrigin(value: string): this;
+  parse(buffer: ArrayBuffer, path: string) : Assimp;
+}

+ 2402 - 0
examples/jsm/loaders/AssimpLoader.js

@@ -0,0 +1,2402 @@
+/**
+ * @author Virtulous / https://virtulo.us/
+ */
+
+import {
+	Bone,
+	BufferAttribute,
+	BufferGeometry,
+	Color,
+	DefaultLoadingManager,
+	FileLoader,
+	LoaderUtils,
+	Matrix4,
+	Mesh,
+	MeshLambertMaterial,
+	MeshPhongMaterial,
+	Object3D,
+	Quaternion,
+	Skeleton,
+	SkinnedMesh,
+	TextureLoader,
+	Vector2,
+	Vector3,
+	Vector4
+} from "../../../build/three.module.js";
+
+var AssimpLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+AssimpLoader.prototype = {
+
+	constructor: AssimpLoader,
+
+	crossOrigin: 'anonymous',
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var path = ( scope.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : scope.path;
+
+		var loader = new FileLoader( this.manager );
+		loader.setPath( scope.path );
+		loader.setResponseType( 'arraybuffer' );
+
+		loader.load( url, function ( buffer ) {
+
+			onLoad( scope.parse( buffer, path ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	setResourcePath: function ( value ) {
+
+		this.resourcePath = value;
+		return this;
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	parse: function ( buffer, path ) {
+
+		var textureLoader = new TextureLoader( this.manager );
+		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+
+		var Virtulous = {};
+
+		Virtulous.KeyFrame = function ( time, matrix ) {
+
+			this.time = time;
+			this.matrix = matrix.clone();
+			this.position = new Vector3();
+			this.quaternion = new Quaternion();
+			this.scale = new Vector3( 1, 1, 1 );
+			this.matrix.decompose( this.position, this.quaternion, this.scale );
+			this.clone = function () {
+
+				var n = new Virtulous.KeyFrame( this.time, this.matrix );
+				return n;
+
+			};
+			this.lerp = function ( nextKey, time ) {
+
+				time -= this.time;
+				var dist = ( nextKey.time - this.time );
+				var l = time / dist;
+				var l2 = 1 - l;
+				var keypos = this.position;
+				var keyrot = this.quaternion;
+				//      var keyscl =  key.parentspaceScl || key.scl;
+				var key2pos = nextKey.position;
+				var key2rot = nextKey.quaternion;
+				//  var key2scl =  key2.parentspaceScl || key2.scl;
+				Virtulous.KeyFrame.tempAniPos.x = keypos.x * l2 + key2pos.x * l;
+				Virtulous.KeyFrame.tempAniPos.y = keypos.y * l2 + key2pos.y * l;
+				Virtulous.KeyFrame.tempAniPos.z = keypos.z * l2 + key2pos.z * l;
+				//     tempAniScale.x = keyscl[0] * l2 + key2scl[0] * l;
+				//     tempAniScale.y = keyscl[1] * l2 + key2scl[1] * l;
+				//     tempAniScale.z = keyscl[2] * l2 + key2scl[2] * l;
+				Virtulous.KeyFrame.tempAniQuat.set( keyrot.x, keyrot.y, keyrot.z, keyrot.w );
+				Virtulous.KeyFrame.tempAniQuat.slerp( key2rot, l );
+				return Virtulous.KeyFrame.tempAniMatrix.compose( Virtulous.KeyFrame.tempAniPos, Virtulous.KeyFrame.tempAniQuat, Virtulous.KeyFrame.tempAniScale );
+
+			};
+
+		};
+
+		Virtulous.KeyFrame.tempAniPos = new Vector3();
+		Virtulous.KeyFrame.tempAniQuat = new Quaternion();
+		Virtulous.KeyFrame.tempAniScale = new Vector3( 1, 1, 1 );
+		Virtulous.KeyFrame.tempAniMatrix = new Matrix4();
+		Virtulous.KeyFrameTrack = function () {
+
+			this.keys = [];
+			this.target = null;
+			this.time = 0;
+			this.length = 0;
+			this._accelTable = {};
+			this.fps = 20;
+			this.addKey = function ( key ) {
+
+				this.keys.push( key );
+
+			};
+			this.init = function () {
+
+				this.sortKeys();
+
+				if ( this.keys.length > 0 )
+					this.length = this.keys[ this.keys.length - 1 ].time;
+				else
+					this.length = 0;
+
+				if ( ! this.fps ) return;
+
+				for ( var j = 0; j < this.length * this.fps; j ++ ) {
+
+					for ( var i = 0; i < this.keys.length; i ++ ) {
+
+						if ( this.keys[ i ].time == j ) {
+
+							this._accelTable[ j ] = i;
+							break;
+
+						} else if ( this.keys[ i ].time < j / this.fps && this.keys[ i + 1 ] && this.keys[ i + 1 ].time >= j / this.fps ) {
+
+							this._accelTable[ j ] = i;
+							break;
+
+						}
+
+					}
+
+				}
+
+			};
+
+			this.parseFromThree = function ( data ) {
+
+				var fps = data.fps;
+				this.target = data.node;
+				var track = data.hierarchy[ 0 ].keys;
+				for ( var i = 0; i < track.length; i ++ ) {
+
+					this.addKey( new Virtulous.KeyFrame( i / fps || track[ i ].time, track[ i ].targets[ 0 ].data ) );
+
+				}
+				this.init();
+
+			};
+
+			this.parseFromCollada = function ( data ) {
+
+				var track = data.keys;
+				var fps = this.fps;
+
+				for ( var i = 0; i < track.length; i ++ ) {
+
+					this.addKey( new Virtulous.KeyFrame( i / fps || track[ i ].time, track[ i ].matrix ) );
+
+				}
+
+				this.init();
+
+			};
+
+			this.sortKeys = function () {
+
+				this.keys.sort( this.keySortFunc );
+
+			};
+
+			this.keySortFunc = function ( a, b ) {
+
+				return a.time - b.time;
+
+			};
+
+			this.clone = function () {
+
+				var t = new Virtulous.KeyFrameTrack();
+				t.target = this.target;
+				t.time = this.time;
+				t.length = this.length;
+
+				for ( var i = 0; i < this.keys.length; i ++ ) {
+
+					t.addKey( this.keys[ i ].clone() );
+
+				}
+
+				t.init();
+				return t;
+
+			};
+
+			this.reTarget = function ( root, compareitor ) {
+
+				if ( ! compareitor ) compareitor = Virtulous.TrackTargetNodeNameCompare;
+				this.target = compareitor( root, this.target );
+
+			};
+
+			this.keySearchAccel = function ( time ) {
+
+				time *= this.fps;
+				time = Math.floor( time );
+				return this._accelTable[ time ] || 0;
+
+			};
+
+			this.setTime = function ( time ) {
+
+				time = Math.abs( time );
+				if ( this.length )
+					time = time % this.length + .05;
+				var key0 = null;
+				var key1 = null;
+
+				for ( var i = this.keySearchAccel( time ); i < this.keys.length; i ++ ) {
+
+					if ( this.keys[ i ].time == time ) {
+
+						key0 = this.keys[ i ];
+						key1 = this.keys[ i ];
+						break;
+
+					} else if ( this.keys[ i ].time < time && this.keys[ i + 1 ] && this.keys[ i + 1 ].time > time ) {
+
+						key0 = this.keys[ i ];
+						key1 = this.keys[ i + 1 ];
+						break;
+
+					} else if ( this.keys[ i ].time < time && i == this.keys.length - 1 ) {
+
+						key0 = this.keys[ i ];
+						key1 = this.keys[ 0 ].clone();
+						key1.time += this.length + .05;
+						break;
+
+					}
+
+				}
+
+				if ( key0 && key1 && key0 !== key1 ) {
+
+					this.target.matrixAutoUpdate = false;
+					this.target.matrix.copy( key0.lerp( key1, time ) );
+					this.target.matrixWorldNeedsUpdate = true;
+					return;
+
+				}
+
+				if ( key0 && key1 && key0 == key1 ) {
+
+					this.target.matrixAutoUpdate = false;
+					this.target.matrix.copy( key0.matrix );
+					this.target.matrixWorldNeedsUpdate = true;
+					return;
+
+				}
+
+			};
+
+		};
+
+		Virtulous.TrackTargetNodeNameCompare = function ( root, target ) {
+
+			function find( node, name ) {
+
+				if ( node.name == name )
+					return node;
+
+				for ( var i = 0; i < node.children.length; i ++ ) {
+
+					var r = find( node.children[ i ], name );
+					if ( r ) return r;
+
+				}
+
+				return null;
+
+			}
+
+			return find( root, target.name );
+
+		};
+
+		Virtulous.Animation = function () {
+
+			this.tracks = [];
+			this.length = 0;
+
+			this.addTrack = function ( track ) {
+
+				this.tracks.push( track );
+				this.length = Math.max( track.length, this.length );
+
+			};
+
+			this.setTime = function ( time ) {
+
+				this.time = time;
+
+				for ( var i = 0; i < this.tracks.length; i ++ )
+					this.tracks[ i ].setTime( time );
+
+			};
+
+			this.clone = function ( target, compareitor ) {
+
+				if ( ! compareitor ) compareitor = Virtulous.TrackTargetNodeNameCompare;
+				var n = new Virtulous.Animation();
+				n.target = target;
+				for ( var i = 0; i < this.tracks.length; i ++ ) {
+
+					var track = this.tracks[ i ].clone();
+					track.reTarget( target, compareitor );
+					n.addTrack( track );
+
+				}
+
+				return n;
+
+			};
+
+		};
+
+		var ASSBIN_CHUNK_AICAMERA = 0x1234;
+		var ASSBIN_CHUNK_AILIGHT = 0x1235;
+		var ASSBIN_CHUNK_AITEXTURE = 0x1236;
+		var ASSBIN_CHUNK_AIMESH = 0x1237;
+		var ASSBIN_CHUNK_AINODEANIM = 0x1238;
+		var ASSBIN_CHUNK_AISCENE = 0x1239;
+		var ASSBIN_CHUNK_AIBONE = 0x123a;
+		var ASSBIN_CHUNK_AIANIMATION = 0x123b;
+		var ASSBIN_CHUNK_AINODE = 0x123c;
+		var ASSBIN_CHUNK_AIMATERIAL = 0x123d;
+		var ASSBIN_CHUNK_AIMATERIALPROPERTY = 0x123e;
+		var ASSBIN_MESH_HAS_POSITIONS = 0x1;
+		var ASSBIN_MESH_HAS_NORMALS = 0x2;
+		var ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS = 0x4;
+		var ASSBIN_MESH_HAS_TEXCOORD_BASE = 0x100;
+		var ASSBIN_MESH_HAS_COLOR_BASE = 0x10000;
+		var AI_MAX_NUMBER_OF_COLOR_SETS = 1;
+		var AI_MAX_NUMBER_OF_TEXTURECOORDS = 4;
+		var aiLightSource_UNDEFINED = 0x0;
+		//! A directional light source has a well-defined direction
+		//! but is infinitely far away. That's quite a good
+		//! approximation for sun light.
+		var aiLightSource_DIRECTIONAL = 0x1;
+		//! A point light source has a well-defined position
+		//! in space but no direction - it emits light in all
+		//! directions. A normal bulb is a point light.
+		var aiLightSource_POINT = 0x2;
+		//! A spot light source emits light in a specific
+		//! angle. It has a position and a direction it is pointing to.
+		//! A good example for a spot light is a light spot in
+		//! sport arenas.
+		var aiLightSource_SPOT = 0x3;
+		//! The generic light level of the world, including the bounces
+		//! of all other lightsources.
+		//! Typically, there's at most one ambient light in a scene.
+		//! This light type doesn't have a valid position, direction, or
+		//! other properties, just a color.
+		var aiLightSource_AMBIENT = 0x4;
+		/** Flat shading. Shading is done on per-face base,
+		 *  diffuse only. Also known as 'faceted shading'.
+		 */
+		var aiShadingMode_Flat = 0x1;
+		/** Simple Gouraud shading.
+		 */
+		var aiShadingMode_Gouraud = 0x2;
+		/** Phong-Shading -
+		 */
+		var aiShadingMode_Phong = 0x3;
+		/** Phong-Blinn-Shading
+		 */
+		var aiShadingMode_Blinn = 0x4;
+		/** Toon-Shading per pixel
+		 *
+		 *  Also known as 'comic' shader.
+		 */
+		var aiShadingMode_Toon = 0x5;
+		/** OrenNayar-Shading per pixel
+		 *
+		 *  Extension to standard Lambertian shading, taking the
+		 *  roughness of the material into account
+		 */
+		var aiShadingMode_OrenNayar = 0x6;
+		/** Minnaert-Shading per pixel
+		 *
+		 *  Extension to standard Lambertian shading, taking the
+		 *  "darkness" of the material into account
+		 */
+		var aiShadingMode_Minnaert = 0x7;
+		/** CookTorrance-Shading per pixel
+		 *
+		 *  Special shader for metallic surfaces.
+		 */
+		var aiShadingMode_CookTorrance = 0x8;
+		/** No shading at all. Constant light influence of 1.0.
+		 */
+		var aiShadingMode_NoShading = 0x9;
+		/** Fresnel shading
+		 */
+		var aiShadingMode_Fresnel = 0xa;
+		var aiTextureType_NONE = 0x0;
+		/** The texture is combined with the result of the diffuse
+		 *  lighting equation.
+		 */
+		var aiTextureType_DIFFUSE = 0x1;
+		/** The texture is combined with the result of the specular
+		 *  lighting equation.
+		 */
+		var aiTextureType_SPECULAR = 0x2;
+		/** The texture is combined with the result of the ambient
+		 *  lighting equation.
+		 */
+		var aiTextureType_AMBIENT = 0x3;
+		/** The texture is added to the result of the lighting
+		 *  calculation. It isn't influenced by incoming light.
+		 */
+		var aiTextureType_EMISSIVE = 0x4;
+		/** The texture is a height map.
+		 *
+		 *  By convention, higher gray-scale values stand for
+		 *  higher elevations from the base height.
+		 */
+		var aiTextureType_HEIGHT = 0x5;
+		/** The texture is a (tangent space) normal-map.
+		 *
+		 *  Again, there are several conventions for tangent-space
+		 *  normal maps. Assimp does (intentionally) not
+		 *  distinguish here.
+		 */
+		var aiTextureType_NORMALS = 0x6;
+		/** The texture defines the glossiness of the material.
+		 *
+		 *  The glossiness is in fact the exponent of the specular
+		 *  (phong) lighting equation. Usually there is a conversion
+		 *  function defined to map the linear color values in the
+		 *  texture to a suitable exponent. Have fun.
+		 */
+		var aiTextureType_SHININESS = 0x7;
+		/** The texture defines per-pixel opacity.
+		 *
+		 *  Usually 'white' means opaque and 'black' means
+		 *  'transparency'. Or quite the opposite. Have fun.
+		 */
+		var aiTextureType_OPACITY = 0x8;
+		/** Displacement texture
+		 *
+		 *  The exact purpose and format is application-dependent.
+		 *  Higher color values stand for higher vertex displacements.
+		 */
+		var aiTextureType_DISPLACEMENT = 0x9;
+		/** Lightmap texture (aka Ambient Occlusion)
+		 *
+		 *  Both 'Lightmaps' and dedicated 'ambient occlusion maps' are
+		 *  covered by this material property. The texture contains a
+		 *  scaling value for the final color value of a pixel. Its
+		 *  intensity is not affected by incoming light.
+		 */
+		var aiTextureType_LIGHTMAP = 0xA;
+		/** Reflection texture
+		 *
+		 * Contains the color of a perfect mirror reflection.
+		 * Rarely used, almost never for real-time applications.
+		 */
+		var aiTextureType_REFLECTION = 0xB;
+		/** Unknown texture
+		 *
+		 *  A texture reference that does not match any of the definitions
+		 *  above is considered to be 'unknown'. It is still imported,
+		 *  but is excluded from any further postprocessing.
+		 */
+		var aiTextureType_UNKNOWN = 0xC;
+		var BONESPERVERT = 4;
+
+		function ASSBIN_MESH_HAS_TEXCOORD( n ) {
+
+			return ASSBIN_MESH_HAS_TEXCOORD_BASE << n;
+
+		}
+
+		function ASSBIN_MESH_HAS_COLOR( n ) {
+
+			return ASSBIN_MESH_HAS_COLOR_BASE << n;
+
+		}
+
+		function markBones( scene ) {
+
+			for ( var i in scene.mMeshes ) {
+
+				var mesh = scene.mMeshes[ i ];
+				for ( var k in mesh.mBones ) {
+
+					var boneNode = scene.findNode( mesh.mBones[ k ].mName );
+					if ( boneNode )
+						boneNode.isBone = true;
+
+				}
+
+			}
+
+		}
+		function cloneTreeToBones( root, scene ) {
+
+			var rootBone = new Bone();
+			rootBone.matrix.copy( root.matrix );
+			rootBone.matrixWorld.copy( root.matrixWorld );
+			rootBone.position.copy( root.position );
+			rootBone.quaternion.copy( root.quaternion );
+			rootBone.scale.copy( root.scale );
+			scene.nodeCount ++;
+			rootBone.name = "bone_" + root.name + scene.nodeCount.toString();
+
+			if ( ! scene.nodeToBoneMap[ root.name ] )
+				scene.nodeToBoneMap[ root.name ] = [];
+			scene.nodeToBoneMap[ root.name ].push( rootBone );
+			for ( var i in root.children ) {
+
+				var child = cloneTreeToBones( root.children[ i ], scene );
+				if ( child )
+					rootBone.add( child );
+
+			}
+
+			return rootBone;
+
+		}
+
+		function sortWeights( indexes, weights ) {
+
+			var pairs = [];
+
+			for ( var i = 0; i < indexes.length; i ++ ) {
+
+				pairs.push( {
+					i: indexes[ i ],
+					w: weights[ i ]
+				} );
+
+			}
+
+			pairs.sort( function ( a, b ) {
+
+				return b.w - a.w;
+
+			 } );
+
+			while ( pairs.length < 4 ) {
+
+				pairs.push( {
+					i: 0,
+					w: 0
+				} );
+
+			}
+
+			if ( pairs.length > 4 )
+				pairs.length = 4;
+			var sum = 0;
+
+			for ( var i = 0; i < 4; i ++ ) {
+
+				sum += pairs[ i ].w * pairs[ i ].w;
+
+			}
+
+			sum = Math.sqrt( sum );
+
+			for ( var i = 0; i < 4; i ++ ) {
+
+				pairs[ i ].w = pairs[ i ].w / sum;
+				indexes[ i ] = pairs[ i ].i;
+				weights[ i ] = pairs[ i ].w;
+
+			}
+
+		}
+
+		function findMatchingBone( root, name ) {
+
+			if ( root.name.indexOf( "bone_" + name ) == 0 )
+				return root;
+
+			for ( var i in root.children ) {
+
+				var ret = findMatchingBone( root.children[ i ], name );
+
+				if ( ret )
+					return ret;
+
+			}
+
+			return undefined;
+
+		}
+
+		function aiMesh() {
+
+			this.mPrimitiveTypes = 0;
+			this.mNumVertices = 0;
+			this.mNumFaces = 0;
+			this.mNumBones = 0;
+			this.mMaterialIndex = 0;
+			this.mVertices = [];
+			this.mNormals = [];
+			this.mTangents = [];
+			this.mBitangents = [];
+			this.mColors = [
+				[]
+			];
+			this.mTextureCoords = [
+				[]
+			];
+			this.mFaces = [];
+			this.mBones = [];
+			this.hookupSkeletons = function ( scene, threeScene ) {
+
+				if ( this.mBones.length == 0 ) return;
+
+				var allBones = [];
+				var offsetMatrix = [];
+				var skeletonRoot = scene.findNode( this.mBones[ 0 ].mName );
+
+				while ( skeletonRoot.mParent && skeletonRoot.mParent.isBone ) {
+
+					skeletonRoot = skeletonRoot.mParent;
+
+				}
+
+				var threeSkeletonRoot = skeletonRoot.toTHREE( scene );
+				var threeSkeletonRootBone = cloneTreeToBones( threeSkeletonRoot, scene );
+				this.threeNode.add( threeSkeletonRootBone );
+
+				for ( var i = 0; i < this.mBones.length; i ++ ) {
+
+					var bone = findMatchingBone( threeSkeletonRootBone, this.mBones[ i ].mName );
+
+					if ( bone ) {
+
+						var tbone = bone;
+						allBones.push( tbone );
+						//tbone.matrixAutoUpdate = false;
+						offsetMatrix.push( this.mBones[ i ].mOffsetMatrix.toTHREE() );
+
+					} else {
+
+						var skeletonRoot = scene.findNode( this.mBones[ i ].mName );
+						if ( ! skeletonRoot ) return;
+						var threeSkeletonRoot = skeletonRoot.toTHREE( scene );
+						var threeSkeletonRootParent = threeSkeletonRoot.parent;
+						var threeSkeletonRootBone = cloneTreeToBones( threeSkeletonRoot, scene );
+						this.threeNode.add( threeSkeletonRootBone );
+						var bone = findMatchingBone( threeSkeletonRootBone, this.mBones[ i ].mName );
+						var tbone = bone;
+						allBones.push( tbone );
+						//tbone.matrixAutoUpdate = false;
+						offsetMatrix.push( this.mBones[ i ].mOffsetMatrix.toTHREE() );
+
+					}
+
+				}
+				var skeleton = new Skeleton( allBones, offsetMatrix );
+
+				this.threeNode.bind( skeleton, new Matrix4() );
+				this.threeNode.material.skinning = true;
+
+			};
+
+			this.toTHREE = function ( scene ) {
+
+				if ( this.threeNode ) return this.threeNode;
+				var geometry = new BufferGeometry();
+				var mat;
+				if ( scene.mMaterials[ this.mMaterialIndex ] )
+					mat = scene.mMaterials[ this.mMaterialIndex ].toTHREE( scene );
+				else
+					mat = new MeshLambertMaterial();
+				geometry.setIndex( new BufferAttribute( new Uint32Array( this.mIndexArray ), 1 ) );
+				geometry.addAttribute( 'position', new BufferAttribute( this.mVertexBuffer, 3 ) );
+				if ( this.mNormalBuffer && this.mNormalBuffer.length > 0 )
+					geometry.addAttribute( 'normal', new BufferAttribute( this.mNormalBuffer, 3 ) );
+				if ( this.mColorBuffer && this.mColorBuffer.length > 0 )
+					geometry.addAttribute( 'color', new BufferAttribute( this.mColorBuffer, 4 ) );
+				if ( this.mTexCoordsBuffers[ 0 ] && this.mTexCoordsBuffers[ 0 ].length > 0 )
+					geometry.addAttribute( 'uv', new BufferAttribute( new Float32Array( this.mTexCoordsBuffers[ 0 ] ), 2 ) );
+				if ( this.mTexCoordsBuffers[ 1 ] && this.mTexCoordsBuffers[ 1 ].length > 0 )
+					geometry.addAttribute( 'uv1', new BufferAttribute( new Float32Array( this.mTexCoordsBuffers[ 1 ] ), 2 ) );
+				if ( this.mTangentBuffer && this.mTangentBuffer.length > 0 )
+					geometry.addAttribute( 'tangents', new BufferAttribute( this.mTangentBuffer, 3 ) );
+				if ( this.mBitangentBuffer && this.mBitangentBuffer.length > 0 )
+					geometry.addAttribute( 'bitangents', new BufferAttribute( this.mBitangentBuffer, 3 ) );
+				if ( this.mBones.length > 0 ) {
+
+					var weights = [];
+					var bones = [];
+
+					for ( var i = 0; i < this.mBones.length; i ++ ) {
+
+						for ( var j = 0; j < this.mBones[ i ].mWeights.length; j ++ ) {
+
+							var weight = this.mBones[ i ].mWeights[ j ];
+							if ( weight ) {
+
+								if ( ! weights[ weight.mVertexId ] ) weights[ weight.mVertexId ] = [];
+								if ( ! bones[ weight.mVertexId ] ) bones[ weight.mVertexId ] = [];
+								weights[ weight.mVertexId ].push( weight.mWeight );
+								bones[ weight.mVertexId ].push( parseInt( i ) );
+
+							}
+
+						}
+
+					}
+
+					for ( var i in bones ) {
+
+						sortWeights( bones[ i ], weights[ i ] );
+
+					}
+
+					var _weights = [];
+					var _bones = [];
+
+					for ( var i = 0; i < weights.length; i ++ ) {
+
+						for ( var j = 0; j < 4; j ++ ) {
+
+							if ( weights[ i ] && bones[ i ] ) {
+
+								_weights.push( weights[ i ][ j ] );
+								_bones.push( bones[ i ][ j ] );
+
+							} else {
+
+								_weights.push( 0 );
+								_bones.push( 0 );
+
+							}
+
+						}
+
+					}
+
+					geometry.addAttribute( 'skinWeight', new BufferAttribute( new Float32Array( _weights ), BONESPERVERT ) );
+					geometry.addAttribute( 'skinIndex', new BufferAttribute( new Float32Array( _bones ), BONESPERVERT ) );
+
+				}
+
+				var mesh;
+
+				if ( this.mBones.length == 0 )
+					mesh = new Mesh( geometry, mat );
+
+				if ( this.mBones.length > 0 ) {
+
+					mesh = new SkinnedMesh( geometry, mat );
+					mesh.normalizeSkinWeights();
+
+				}
+
+				this.threeNode = mesh;
+				//mesh.matrixAutoUpdate = false;
+				return mesh;
+
+			};
+
+		}
+
+		function aiFace() {
+
+			this.mNumIndices = 0;
+			this.mIndices = [];
+
+		}
+
+		function aiVector3D() {
+
+			this.x = 0;
+			this.y = 0;
+			this.z = 0;
+
+			this.toTHREE = function () {
+
+				return new Vector3( this.x, this.y, this.z );
+
+			};
+
+		}
+
+		function aiVector2D() {
+
+			this.x = 0;
+			this.y = 0;
+			this.toTHREE = function () {
+
+				return new Vector2( this.x, this.y );
+
+			};
+
+		}
+
+		function aiVector4D() {
+
+			this.w = 0;
+			this.x = 0;
+			this.y = 0;
+			this.z = 0;
+			this.toTHREE = function () {
+
+				return new Vector4( this.w, this.x, this.y, this.z );
+
+			};
+
+		}
+
+		function aiColor4D() {
+
+			this.r = 0;
+			this.g = 0;
+			this.b = 0;
+			this.a = 0;
+			this.toTHREE = function () {
+
+				return new Color( this.r, this.g, this.b, this.a );
+
+			};
+
+		}
+
+		function aiColor3D() {
+
+			this.r = 0;
+			this.g = 0;
+			this.b = 0;
+			this.a = 0;
+			this.toTHREE = function () {
+
+				return new Color( this.r, this.g, this.b, 1 );
+
+			};
+
+		}
+
+		function aiQuaternion() {
+
+			this.x = 0;
+			this.y = 0;
+			this.z = 0;
+			this.w = 0;
+			this.toTHREE = function () {
+
+				return new Quaternion( this.x, this.y, this.z, this.w );
+
+			};
+
+		}
+
+		function aiVertexWeight() {
+
+			this.mVertexId = 0;
+			this.mWeight = 0;
+
+		}
+
+		function aiString() {
+
+			this.data = [];
+			this.toString = function () {
+
+				var str = '';
+				this.data.forEach( function ( i ) {
+
+					str += ( String.fromCharCode( i ) );
+
+				} );
+				return str.replace( /[^\x20-\x7E]+/g, '' );
+
+			};
+
+		}
+
+		function aiVectorKey() {
+
+			this.mTime = 0;
+			this.mValue = null;
+
+		}
+
+		function aiQuatKey() {
+
+			this.mTime = 0;
+			this.mValue = null;
+
+		}
+
+		function aiNode() {
+
+			this.mName = '';
+			this.mTransformation = [];
+			this.mNumChildren = 0;
+			this.mNumMeshes = 0;
+			this.mMeshes = [];
+			this.mChildren = [];
+			this.toTHREE = function ( scene ) {
+
+				if ( this.threeNode ) return this.threeNode;
+				var o = new Object3D();
+				o.name = this.mName;
+				o.matrix = this.mTransformation.toTHREE();
+
+				for ( var i = 0; i < this.mChildren.length; i ++ ) {
+
+					o.add( this.mChildren[ i ].toTHREE( scene ) );
+
+				}
+
+				for ( var i = 0; i < this.mMeshes.length; i ++ ) {
+
+					o.add( scene.mMeshes[ this.mMeshes[ i ] ].toTHREE( scene ) );
+
+				}
+
+				this.threeNode = o;
+				//o.matrixAutoUpdate = false;
+				o.matrix.decompose( o.position, o.quaternion, o.scale );
+				return o;
+
+			};
+
+		}
+
+		function aiBone() {
+
+			this.mName = '';
+			this.mNumWeights = 0;
+			this.mOffsetMatrix = 0;
+
+		}
+
+		function aiMaterialProperty() {
+
+			this.mKey = "";
+			this.mSemantic = 0;
+			this.mIndex = 0;
+			this.mData = [];
+			this.mDataLength = 0;
+			this.mType = 0;
+			this.dataAsColor = function () {
+
+				var array = ( new Uint8Array( this.mData ) ).buffer;
+				var reader = new DataView( array );
+				var r = reader.getFloat32( 0, true );
+				var g = reader.getFloat32( 4, true );
+				var b = reader.getFloat32( 8, true );
+				//var a = reader.getFloat32(12, true);
+				return new Color( r, g, b );
+
+			};
+
+			this.dataAsFloat = function () {
+
+				var array = ( new Uint8Array( this.mData ) ).buffer;
+				var reader = new DataView( array );
+				var r = reader.getFloat32( 0, true );
+				return r;
+
+			};
+
+			this.dataAsBool = function () {
+
+				var array = ( new Uint8Array( this.mData ) ).buffer;
+				var reader = new DataView( array );
+				var r = reader.getFloat32( 0, true );
+				return !! r;
+
+			};
+
+			this.dataAsString = function () {
+
+				var s = new aiString();
+				s.data = this.mData;
+				return s.toString();
+
+			};
+
+			this.dataAsMap = function () {
+
+				var s = new aiString();
+				s.data = this.mData;
+				var path = s.toString();
+				path = path.replace( /\\/g, '/' );
+
+				if ( path.indexOf( '/' ) != - 1 ) {
+
+					path = path.substr( path.lastIndexOf( '/' ) + 1 );
+
+				}
+
+				return textureLoader.load( path );
+
+			};
+
+		}
+		var namePropMapping = {
+
+			"?mat.name": "name",
+			"$mat.shadingm": "shading",
+			"$mat.twosided": "twoSided",
+			"$mat.wireframe": "wireframe",
+			"$clr.ambient": "ambient",
+			"$clr.diffuse": "color",
+			"$clr.specular": "specular",
+			"$clr.emissive": "emissive",
+			"$clr.transparent": "transparent",
+			"$clr.reflective": "reflect",
+			"$mat.shininess": "shininess",
+			"$mat.reflectivity": "reflectivity",
+			"$mat.refracti": "refraction",
+			"$tex.file": "map"
+
+		};
+
+		var nameTypeMapping = {
+
+			"?mat.name": "string",
+			"$mat.shadingm": "bool",
+			"$mat.twosided": "bool",
+			"$mat.wireframe": "bool",
+			"$clr.ambient": "color",
+			"$clr.diffuse": "color",
+			"$clr.specular": "color",
+			"$clr.emissive": "color",
+			"$clr.transparent": "color",
+			"$clr.reflective": "color",
+			"$mat.shininess": "float",
+			"$mat.reflectivity": "float",
+			"$mat.refracti": "float",
+			"$tex.file": "map"
+
+		};
+
+		function aiMaterial() {
+
+			this.mNumAllocated = 0;
+			this.mNumProperties = 0;
+			this.mProperties = [];
+			this.toTHREE = function ( scene ) {
+
+				var name = this.mProperties[ 0 ].dataAsString();
+				var mat = new MeshPhongMaterial();
+
+				for ( var i = 0; i < this.mProperties.length; i ++ ) {
+
+					if ( nameTypeMapping[ this.mProperties[ i ].mKey ] == 'float' )
+						mat[ namePropMapping[ this.mProperties[ i ].mKey ] ] = this.mProperties[ i ].dataAsFloat();
+					if ( nameTypeMapping[ this.mProperties[ i ].mKey ] == 'color' )
+						mat[ namePropMapping[ this.mProperties[ i ].mKey ] ] = this.mProperties[ i ].dataAsColor();
+					if ( nameTypeMapping[ this.mProperties[ i ].mKey ] == 'bool' )
+						mat[ namePropMapping[ this.mProperties[ i ].mKey ] ] = this.mProperties[ i ].dataAsBool();
+					if ( nameTypeMapping[ this.mProperties[ i ].mKey ] == 'string' )
+						mat[ namePropMapping[ this.mProperties[ i ].mKey ] ] = this.mProperties[ i ].dataAsString();
+					if ( nameTypeMapping[ this.mProperties[ i ].mKey ] == 'map' ) {
+
+						var prop = this.mProperties[ i ];
+						if ( prop.mSemantic == aiTextureType_DIFFUSE )
+							mat.map = this.mProperties[ i ].dataAsMap();
+						if ( prop.mSemantic == aiTextureType_NORMALS )
+							mat.normalMap = this.mProperties[ i ].dataAsMap();
+						if ( prop.mSemantic == aiTextureType_LIGHTMAP )
+							mat.lightMap = this.mProperties[ i ].dataAsMap();
+						if ( prop.mSemantic == aiTextureType_OPACITY )
+							mat.alphaMap = this.mProperties[ i ].dataAsMap();
+
+					}
+
+				}
+
+				mat.ambient.r = .53;
+				mat.ambient.g = .53;
+				mat.ambient.b = .53;
+				mat.color.r = 1;
+				mat.color.g = 1;
+				mat.color.b = 1;
+				return mat;
+
+			};
+
+		}
+
+
+		function veclerp( v1, v2, l ) {
+
+			var v = new Vector3();
+			var lm1 = 1 - l;
+			v.x = v1.x * l + v2.x * lm1;
+			v.y = v1.y * l + v2.y * lm1;
+			v.z = v1.z * l + v2.z * lm1;
+			return v;
+
+		}
+
+		function quatlerp( q1, q2, l ) {
+
+			return q1.clone().slerp( q2, 1 - l );
+
+		}
+
+		function sampleTrack( keys, time, lne, lerp ) {
+
+			if ( keys.length == 1 ) return keys[ 0 ].mValue.toTHREE();
+
+			var dist = Infinity;
+			var key = null;
+			var nextKey = null;
+
+			for ( var i = 0; i < keys.length; i ++ ) {
+
+				var timeDist = Math.abs( keys[ i ].mTime - time );
+
+				if ( timeDist < dist && keys[ i ].mTime <= time ) {
+
+					dist = timeDist;
+					key = keys[ i ];
+					nextKey = keys[ i + 1 ];
+
+				}
+
+			}
+
+			if ( ! key ) {
+
+				return null;
+
+			} else if ( nextKey ) {
+
+				var dT = nextKey.mTime - key.mTime;
+				var T = key.mTime - time;
+				var l = T / dT;
+
+				return lerp( key.mValue.toTHREE(), nextKey.mValue.toTHREE(), l );
+
+			} else {
+
+				nextKey = keys[ 0 ].clone();
+				nextKey.mTime += lne;
+
+				var dT = nextKey.mTime - key.mTime;
+				var T = key.mTime - time;
+				var l = T / dT;
+
+				return lerp( key.mValue.toTHREE(), nextKey.mValue.toTHREE(), l );
+
+			}
+
+		}
+
+		function aiNodeAnim() {
+
+			this.mNodeName = "";
+			this.mNumPositionKeys = 0;
+			this.mNumRotationKeys = 0;
+			this.mNumScalingKeys = 0;
+			this.mPositionKeys = [];
+			this.mRotationKeys = [];
+			this.mScalingKeys = [];
+			this.mPreState = "";
+			this.mPostState = "";
+			this.init = function ( tps ) {
+
+				if ( ! tps ) tps = 1;
+
+				function t( t ) {
+
+					t.mTime /= tps;
+
+				}
+
+				this.mPositionKeys.forEach( t );
+				this.mRotationKeys.forEach( t );
+				this.mScalingKeys.forEach( t );
+
+			};
+
+			this.sortKeys = function () {
+
+				function comp( a, b ) {
+
+					return a.mTime - b.mTime;
+
+				}
+
+				this.mPositionKeys.sort( comp );
+				this.mRotationKeys.sort( comp );
+				this.mScalingKeys.sort( comp );
+
+			};
+
+			this.getLength = function () {
+
+				return Math.max(
+					Math.max.apply( null, this.mPositionKeys.map( function ( a ) {
+
+						return a.mTime;
+
+					} ) ),
+					Math.max.apply( null, this.mRotationKeys.map( function ( a ) {
+
+						return a.mTime;
+
+					} ) ),
+					Math.max.apply( null, this.mScalingKeys.map( function ( a ) {
+
+						return a.mTime;
+
+				 } ) )
+				);
+
+			};
+
+			this.toTHREE = function ( o, tps ) {
+
+				this.sortKeys();
+				var length = this.getLength();
+				var track = new Virtulous.KeyFrameTrack();
+
+				for ( var i = 0; i < length; i += .05 ) {
+
+					var matrix = new Matrix4();
+					var time = i;
+					var pos = sampleTrack( this.mPositionKeys, time, length, veclerp );
+					var scale = sampleTrack( this.mScalingKeys, time, length, veclerp );
+					var rotation = sampleTrack( this.mRotationKeys, time, length, quatlerp );
+					matrix.compose( pos, rotation, scale );
+
+					var key = new Virtulous.KeyFrame( time, matrix );
+					track.addKey( key );
+
+				}
+
+				track.target = o.findNode( this.mNodeName ).toTHREE();
+
+				var tracks = [ track ];
+
+				if ( o.nodeToBoneMap[ this.mNodeName ] ) {
+
+					for ( var i = 0; i < o.nodeToBoneMap[ this.mNodeName ].length; i ++ ) {
+
+						var t2 = track.clone();
+						t2.target = o.nodeToBoneMap[ this.mNodeName ][ i ];
+						tracks.push( t2 );
+
+					}
+
+				}
+
+				return tracks;
+
+			};
+
+		}
+
+		function aiAnimation() {
+
+			this.mName = "";
+			this.mDuration = 0;
+			this.mTicksPerSecond = 0;
+			this.mNumChannels = 0;
+			this.mChannels = [];
+			this.toTHREE = function ( root ) {
+
+				var animationHandle = new Virtulous.Animation();
+
+				for ( var i in this.mChannels ) {
+
+					this.mChannels[ i ].init( this.mTicksPerSecond );
+
+					var tracks = this.mChannels[ i ].toTHREE( root );
+
+					for ( var j in tracks ) {
+
+						tracks[ j ].init();
+						animationHandle.addTrack( tracks[ j ] );
+
+					}
+
+				}
+
+				animationHandle.length = Math.max.apply( null, animationHandle.tracks.map( function ( e ) {
+
+					return e.length;
+
+				} ) );
+				return animationHandle;
+
+			};
+
+		}
+
+		function aiTexture() {
+
+			this.mWidth = 0;
+			this.mHeight = 0;
+			this.texAchFormatHint = [];
+			this.pcData = [];
+
+		}
+
+		function aiLight() {
+
+			this.mName = '';
+			this.mType = 0;
+			this.mAttenuationConstant = 0;
+			this.mAttenuationLinear = 0;
+			this.mAttenuationQuadratic = 0;
+			this.mAngleInnerCone = 0;
+			this.mAngleOuterCone = 0;
+			this.mColorDiffuse = null;
+			this.mColorSpecular = null;
+			this.mColorAmbient = null;
+
+		}
+
+		function aiCamera() {
+
+			this.mName = '';
+			this.mPosition = null;
+			this.mLookAt = null;
+			this.mUp = null;
+			this.mHorizontalFOV = 0;
+			this.mClipPlaneNear = 0;
+			this.mClipPlaneFar = 0;
+			this.mAspect = 0;
+
+		}
+
+		function aiScene() {
+
+			this.mFlags = 0;
+			this.mNumMeshes = 0;
+			this.mNumMaterials = 0;
+			this.mNumAnimations = 0;
+			this.mNumTextures = 0;
+			this.mNumLights = 0;
+			this.mNumCameras = 0;
+			this.mRootNode = null;
+			this.mMeshes = [];
+			this.mMaterials = [];
+			this.mAnimations = [];
+			this.mLights = [];
+			this.mCameras = [];
+			this.nodeToBoneMap = {};
+			this.findNode = function ( name, root ) {
+
+				if ( ! root ) {
+
+					root = this.mRootNode;
+
+				}
+
+				if ( root.mName == name ) {
+
+					return root;
+
+				}
+
+				for ( var i = 0; i < root.mChildren.length; i ++ ) {
+
+					var ret = this.findNode( name, root.mChildren[ i ] );
+					if ( ret ) return ret;
+
+				}
+
+				return null;
+
+			};
+
+			this.toTHREE = function () {
+
+				this.nodeCount = 0;
+
+				markBones( this );
+
+				var o = this.mRootNode.toTHREE( this );
+
+				for ( var i in this.mMeshes )
+					this.mMeshes[ i ].hookupSkeletons( this, o );
+
+				if ( this.mAnimations.length > 0 ) {
+
+					var a = this.mAnimations[ 0 ].toTHREE( this );
+
+				}
+
+				return { object: o, animation: a };
+
+			};
+
+		}
+
+		function aiMatrix4() {
+
+			this.elements = [
+				[],
+				[],
+				[],
+				[]
+			];
+			this.toTHREE = function () {
+
+				var m = new Matrix4();
+
+				for ( var i = 0; i < 4; ++ i ) {
+
+					for ( var i2 = 0; i2 < 4; ++ i2 ) {
+
+						m.elements[ i * 4 + i2 ] = this.elements[ i2 ][ i ];
+
+					}
+
+				}
+
+				return m;
+
+			};
+
+		}
+
+		var littleEndian = true;
+
+		function readFloat( dataview ) {
+
+			var val = dataview.getFloat32( dataview.readOffset, littleEndian );
+			dataview.readOffset += 4;
+			return val;
+
+		}
+
+		function Read_double( dataview ) {
+
+			var val = dataview.getFloat64( dataview.readOffset, littleEndian );
+			dataview.readOffset += 8;
+			return val;
+
+		}
+
+		function Read_uint8_t( dataview ) {
+
+			var val = dataview.getUint8( dataview.readOffset );
+			dataview.readOffset += 1;
+			return val;
+
+		}
+
+		function Read_uint16_t( dataview ) {
+
+			var val = dataview.getUint16( dataview.readOffset, littleEndian );
+			dataview.readOffset += 2;
+			return val;
+
+		}
+
+		function Read_unsigned_int( dataview ) {
+
+			var val = dataview.getUint32( dataview.readOffset, littleEndian );
+			dataview.readOffset += 4;
+			return val;
+
+		}
+
+		function Read_uint32_t( dataview ) {
+
+			var val = dataview.getUint32( dataview.readOffset, littleEndian );
+			dataview.readOffset += 4;
+			return val;
+
+		}
+
+		function Read_aiVector3D( stream ) {
+
+			var v = new aiVector3D();
+			v.x = readFloat( stream );
+			v.y = readFloat( stream );
+			v.z = readFloat( stream );
+			return v;
+
+		}
+
+		function Read_aiVector2D( stream ) {
+
+			var v = new aiVector2D();
+			v.x = readFloat( stream );
+			v.y = readFloat( stream );
+			return v;
+
+		}
+
+		function Read_aiVector4D( stream ) {
+
+			var v = new aiVector4D();
+			v.w = readFloat( stream );
+			v.x = readFloat( stream );
+			v.y = readFloat( stream );
+			v.z = readFloat( stream );
+			return v;
+
+		}
+
+		function Read_aiColor3D( stream ) {
+
+			var c = new aiColor3D();
+			c.r = readFloat( stream );
+			c.g = readFloat( stream );
+			c.b = readFloat( stream );
+			return c;
+
+		}
+
+		function Read_aiColor4D( stream ) {
+
+			var c = new aiColor4D();
+			c.r = readFloat( stream );
+			c.g = readFloat( stream );
+			c.b = readFloat( stream );
+			c.a = readFloat( stream );
+			return c;
+
+		}
+
+		function Read_aiQuaternion( stream ) {
+
+			var v = new aiQuaternion();
+			v.w = readFloat( stream );
+			v.x = readFloat( stream );
+			v.y = readFloat( stream );
+			v.z = readFloat( stream );
+			return v;
+
+		}
+
+		function Read_aiString( stream ) {
+
+			var s = new aiString();
+			var stringlengthbytes = Read_unsigned_int( stream );
+			stream.ReadBytes( s.data, 1, stringlengthbytes );
+			return s.toString();
+
+		}
+
+		function Read_aiVertexWeight( stream ) {
+
+			var w = new aiVertexWeight();
+			w.mVertexId = Read_unsigned_int( stream );
+			w.mWeight = readFloat( stream );
+			return w;
+
+		}
+
+		function Read_aiMatrix4x4( stream ) {
+
+			var m = new aiMatrix4();
+
+			for ( var i = 0; i < 4; ++ i ) {
+
+				for ( var i2 = 0; i2 < 4; ++ i2 ) {
+
+					m.elements[ i ][ i2 ] = readFloat( stream );
+
+				}
+
+			}
+
+			return m;
+
+		}
+
+		function Read_aiVectorKey( stream ) {
+
+			var v = new aiVectorKey();
+			v.mTime = Read_double( stream );
+			v.mValue = Read_aiVector3D( stream );
+			return v;
+
+		}
+
+		function Read_aiQuatKey( stream ) {
+
+			var v = new aiQuatKey();
+			v.mTime = Read_double( stream );
+			v.mValue = Read_aiQuaternion( stream );
+			return v;
+
+		}
+
+		function ReadArray( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read( stream );
+
+		}
+
+		function ReadArray_aiVector2D( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiVector2D( stream );
+
+		}
+
+		function ReadArray_aiVector3D( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiVector3D( stream );
+
+		}
+
+		function ReadArray_aiVector4D( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiVector4D( stream );
+
+		}
+
+		function ReadArray_aiVertexWeight( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiVertexWeight( stream );
+
+		}
+
+		function ReadArray_aiColor4D( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiColor4D( stream );
+
+		}
+
+		function ReadArray_aiVectorKey( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiVectorKey( stream );
+
+		}
+
+		function ReadArray_aiQuatKey( stream, data, size ) {
+
+			for ( var i = 0; i < size; i ++ ) data[ i ] = Read_aiQuatKey( stream );
+
+		}
+
+		function ReadBounds( stream, T /*p*/, n ) {
+
+			// not sure what to do here, the data isn't really useful.
+			return stream.Seek( sizeof( T ) * n, aiOrigin_CUR );
+
+		}
+
+		function ai_assert( bool ) {
+
+			if ( ! bool )
+				throw ( "asset failed" );
+
+		}
+
+		function ReadBinaryNode( stream, parent, depth ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AINODE );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			var node = new aiNode();
+			node.mParent = parent;
+			node.mDepth = depth;
+			node.mName = Read_aiString( stream );
+			node.mTransformation = Read_aiMatrix4x4( stream );
+			node.mNumChildren = Read_unsigned_int( stream );
+			node.mNumMeshes = Read_unsigned_int( stream );
+
+			if ( node.mNumMeshes ) {
+
+				node.mMeshes = [];
+
+				for ( var i = 0; i < node.mNumMeshes; ++ i ) {
+
+					node.mMeshes[ i ] = Read_unsigned_int( stream );
+
+				}
+
+			}
+
+			if ( node.mNumChildren ) {
+
+				node.mChildren = [];
+
+				for ( var i = 0; i < node.mNumChildren; ++ i ) {
+
+					var node2 = ReadBinaryNode( stream, node, depth ++ );
+					node.mChildren[ i ] = node2;
+
+				}
+
+			}
+
+			return node;
+
+		}
+
+		// -----------------------------------------------------------------------------------
+
+		function ReadBinaryBone( stream, b ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AIBONE );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			b.mName = Read_aiString( stream );
+			b.mNumWeights = Read_unsigned_int( stream );
+			b.mOffsetMatrix = Read_aiMatrix4x4( stream );
+			// for the moment we write dumb min/max values for the bones, too.
+			// maybe I'll add a better, hash-like solution later
+			if ( shortened ) {
+
+				ReadBounds( stream, b.mWeights, b.mNumWeights );
+
+			} else {
+
+				// else write as usual
+
+				b.mWeights = [];
+				ReadArray_aiVertexWeight( stream, b.mWeights, b.mNumWeights );
+
+			}
+
+			return b;
+
+		}
+
+		function ReadBinaryMesh( stream, mesh ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AIMESH );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			mesh.mPrimitiveTypes = Read_unsigned_int( stream );
+			mesh.mNumVertices = Read_unsigned_int( stream );
+			mesh.mNumFaces = Read_unsigned_int( stream );
+			mesh.mNumBones = Read_unsigned_int( stream );
+			mesh.mMaterialIndex = Read_unsigned_int( stream );
+			mesh.mNumUVComponents = [];
+			// first of all, write bits for all existent vertex components
+			var c = Read_unsigned_int( stream );
+
+			if ( c & ASSBIN_MESH_HAS_POSITIONS ) {
+
+				if ( shortened ) {
+
+					ReadBounds( stream, mesh.mVertices, mesh.mNumVertices );
+
+				} else {
+
+					// else write as usual
+
+					mesh.mVertices = [];
+					mesh.mVertexBuffer = stream.subArray32( stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4 );
+					stream.Seek( mesh.mNumVertices * 3 * 4, aiOrigin_CUR );
+
+				}
+
+			}
+
+			if ( c & ASSBIN_MESH_HAS_NORMALS ) {
+
+				if ( shortened ) {
+
+					ReadBounds( stream, mesh.mNormals, mesh.mNumVertices );
+
+				} else {
+
+					// else write as usual
+
+					mesh.mNormals = [];
+					mesh.mNormalBuffer = stream.subArray32( stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4 );
+					stream.Seek( mesh.mNumVertices * 3 * 4, aiOrigin_CUR );
+
+				}
+
+			}
+
+			if ( c & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS ) {
+
+				if ( shortened ) {
+
+					ReadBounds( stream, mesh.mTangents, mesh.mNumVertices );
+					ReadBounds( stream, mesh.mBitangents, mesh.mNumVertices );
+
+				} else {
+
+					// else write as usual
+
+					mesh.mTangents = [];
+					mesh.mTangentBuffer = stream.subArray32( stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4 );
+					stream.Seek( mesh.mNumVertices * 3 * 4, aiOrigin_CUR );
+					mesh.mBitangents = [];
+					mesh.mBitangentBuffer = stream.subArray32( stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4 );
+					stream.Seek( mesh.mNumVertices * 3 * 4, aiOrigin_CUR );
+
+				}
+
+			}
+
+			for ( var n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++ n ) {
+
+				if ( ! ( c & ASSBIN_MESH_HAS_COLOR( n ) ) ) break;
+
+				if ( shortened ) {
+
+					ReadBounds( stream, mesh.mColors[ n ], mesh.mNumVertices );
+
+				} else {
+
+					// else write as usual
+
+					mesh.mColors[ n ] = [];
+					mesh.mColorBuffer = stream.subArray32( stream.readOffset, stream.readOffset + mesh.mNumVertices * 4 * 4 );
+					stream.Seek( mesh.mNumVertices * 4 * 4, aiOrigin_CUR );
+
+				}
+
+			}
+
+			mesh.mTexCoordsBuffers = [];
+
+			for ( var n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++ n ) {
+
+				if ( ! ( c & ASSBIN_MESH_HAS_TEXCOORD( n ) ) ) break;
+
+				// write number of UV components
+				mesh.mNumUVComponents[ n ] = Read_unsigned_int( stream );
+
+				if ( shortened ) {
+
+					ReadBounds( stream, mesh.mTextureCoords[ n ], mesh.mNumVertices );
+
+				} else {
+
+					// else write as usual
+
+					mesh.mTextureCoords[ n ] = [];
+					//note that assbin always writes 3d texcoords
+					mesh.mTexCoordsBuffers[ n ] = [];
+
+					for ( var uv = 0; uv < mesh.mNumVertices; uv ++ ) {
+
+						mesh.mTexCoordsBuffers[ n ].push( readFloat( stream ) );
+						mesh.mTexCoordsBuffers[ n ].push( readFloat( stream ) );
+						readFloat( stream );
+
+					}
+
+				}
+
+			}
+			// write faces. There are no floating-point calculations involved
+			// in these, so we can write a simple hash over the face data
+			// to the dump file. We generate a single 32 Bit hash for 512 faces
+			// using Assimp's standard hashing function.
+			if ( shortened ) {
+
+				Read_unsigned_int( stream );
+
+			} else {
+
+				// else write as usual
+
+				// if there are less than 2^16 vertices, we can simply use 16 bit integers ...
+				mesh.mFaces = [];
+
+				var indexCounter = 0;
+				mesh.mIndexArray = [];
+
+				for ( var i = 0; i < mesh.mNumFaces; ++ i ) {
+
+					var f = mesh.mFaces[ i ] = new aiFace();
+					// BOOST_STATIC_ASSERT(AI_MAX_FACE_INDICES <= 0xffff);
+					f.mNumIndices = Read_uint16_t( stream );
+					f.mIndices = [];
+
+					for ( var a = 0; a < f.mNumIndices; ++ a ) {
+
+						if ( mesh.mNumVertices < ( 1 << 16 ) ) {
+
+							f.mIndices[ a ] = Read_uint16_t( stream );
+
+						} else {
+
+							f.mIndices[ a ] = Read_unsigned_int( stream );
+
+						}
+
+
+
+					}
+
+					if ( f.mNumIndices === 3 ) {
+
+						mesh.mIndexArray.push( f.mIndices[ 0 ] );
+						mesh.mIndexArray.push( f.mIndices[ 1 ] );
+						mesh.mIndexArray.push( f.mIndices[ 2 ] );
+
+					} else if ( f.mNumIndices === 4 ) {
+
+						mesh.mIndexArray.push( f.mIndices[ 0 ] );
+						mesh.mIndexArray.push( f.mIndices[ 1 ] );
+						mesh.mIndexArray.push( f.mIndices[ 2 ] );
+						mesh.mIndexArray.push( f.mIndices[ 2 ] );
+						mesh.mIndexArray.push( f.mIndices[ 3 ] );
+						mesh.mIndexArray.push( f.mIndices[ 0 ] );
+
+					} else {
+
+						throw ( new Error( "Sorry, can't currently triangulate polys. Use the triangulate preprocessor in Assimp." ) );
+
+					}
+
+
+
+				}
+
+			}
+			// write bones
+			if ( mesh.mNumBones ) {
+
+				mesh.mBones = [];
+
+				for ( var a = 0; a < mesh.mNumBones; ++ a ) {
+
+					mesh.mBones[ a ] = new aiBone();
+					ReadBinaryBone( stream, mesh.mBones[ a ] );
+
+				}
+
+			}
+
+		}
+
+		function ReadBinaryMaterialProperty( stream, prop ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AIMATERIALPROPERTY );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			prop.mKey = Read_aiString( stream );
+			prop.mSemantic = Read_unsigned_int( stream );
+			prop.mIndex = Read_unsigned_int( stream );
+			prop.mDataLength = Read_unsigned_int( stream );
+			prop.mType = Read_unsigned_int( stream );
+			prop.mData = [];
+			stream.ReadBytes( prop.mData, 1, prop.mDataLength );
+
+		}
+
+		// -----------------------------------------------------------------------------------
+
+		function ReadBinaryMaterial( stream, mat ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AIMATERIAL );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			mat.mNumAllocated = mat.mNumProperties = Read_unsigned_int( stream );
+
+			if ( mat.mNumProperties ) {
+
+				if ( mat.mProperties ) {
+
+					delete mat.mProperties;
+
+				}
+
+				mat.mProperties = [];
+
+				for ( var i = 0; i < mat.mNumProperties; ++ i ) {
+
+					mat.mProperties[ i ] = new aiMaterialProperty();
+					ReadBinaryMaterialProperty( stream, mat.mProperties[ i ] );
+
+				}
+
+			}
+
+		}
+		// -----------------------------------------------------------------------------------
+		function ReadBinaryNodeAnim( stream, nd ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AINODEANIM );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			nd.mNodeName = Read_aiString( stream );
+			nd.mNumPositionKeys = Read_unsigned_int( stream );
+			nd.mNumRotationKeys = Read_unsigned_int( stream );
+			nd.mNumScalingKeys = Read_unsigned_int( stream );
+			nd.mPreState = Read_unsigned_int( stream );
+			nd.mPostState = Read_unsigned_int( stream );
+
+			if ( nd.mNumPositionKeys ) {
+
+				if ( shortened ) {
+
+					ReadBounds( stream, nd.mPositionKeys, nd.mNumPositionKeys );
+
+				} else {
+
+					// else write as usual
+
+					nd.mPositionKeys = [];
+					ReadArray_aiVectorKey( stream, nd.mPositionKeys, nd.mNumPositionKeys );
+
+				}
+
+			}
+
+			if ( nd.mNumRotationKeys ) {
+
+				if ( shortened ) {
+
+					ReadBounds( stream, nd.mRotationKeys, nd.mNumRotationKeys );
+
+				} else {
+
+		 			// else write as usual
+
+					nd.mRotationKeys = [];
+					ReadArray_aiQuatKey( stream, nd.mRotationKeys, nd.mNumRotationKeys );
+
+				}
+
+			}
+
+			if ( nd.mNumScalingKeys ) {
+
+				if ( shortened ) {
+
+					ReadBounds( stream, nd.mScalingKeys, nd.mNumScalingKeys );
+
+				} else {
+
+	 				// else write as usual
+
+					nd.mScalingKeys = [];
+					ReadArray_aiVectorKey( stream, nd.mScalingKeys, nd.mNumScalingKeys );
+
+				}
+
+			}
+
+		}
+		// -----------------------------------------------------------------------------------
+		function ReadBinaryAnim( stream, anim ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AIANIMATION );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			anim.mName = Read_aiString( stream );
+			anim.mDuration = Read_double( stream );
+			anim.mTicksPerSecond = Read_double( stream );
+			anim.mNumChannels = Read_unsigned_int( stream );
+
+			if ( anim.mNumChannels ) {
+
+				anim.mChannels = [];
+
+				for ( var a = 0; a < anim.mNumChannels; ++ a ) {
+
+					anim.mChannels[ a ] = new aiNodeAnim();
+					ReadBinaryNodeAnim( stream, anim.mChannels[ a ] );
+
+				}
+
+			}
+
+		}
+
+		function ReadBinaryTexture( stream, tex ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AITEXTURE );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			tex.mWidth = Read_unsigned_int( stream );
+			tex.mHeight = Read_unsigned_int( stream );
+			stream.ReadBytes( tex.achFormatHint, 1, 4 );
+
+			if ( ! shortened ) {
+
+				if ( ! tex.mHeight ) {
+
+					tex.pcData = [];
+					stream.ReadBytes( tex.pcData, 1, tex.mWidth );
+
+				} else {
+
+					tex.pcData = [];
+					stream.ReadBytes( tex.pcData, 1, tex.mWidth * tex.mHeight * 4 );
+
+				}
+
+			}
+
+		}
+		// -----------------------------------------------------------------------------------
+		function ReadBinaryLight( stream, l ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AILIGHT );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			l.mName = Read_aiString( stream );
+			l.mType = Read_unsigned_int( stream );
+
+			if ( l.mType != aiLightSource_DIRECTIONAL ) {
+
+				l.mAttenuationConstant = readFloat( stream );
+				l.mAttenuationLinear = readFloat( stream );
+				l.mAttenuationQuadratic = readFloat( stream );
+
+			}
+
+			l.mColorDiffuse = Read_aiColor3D( stream );
+			l.mColorSpecular = Read_aiColor3D( stream );
+			l.mColorAmbient = Read_aiColor3D( stream );
+
+			if ( l.mType == aiLightSource_SPOT ) {
+
+				l.mAngleInnerCone = readFloat( stream );
+				l.mAngleOuterCone = readFloat( stream );
+
+			}
+
+		}
+		// -----------------------------------------------------------------------------------
+		function ReadBinaryCamera( stream, cam ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AICAMERA );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			cam.mName = Read_aiString( stream );
+			cam.mPosition = Read_aiVector3D( stream );
+			cam.mLookAt = Read_aiVector3D( stream );
+			cam.mUp = Read_aiVector3D( stream );
+			cam.mHorizontalFOV = readFloat( stream );
+			cam.mClipPlaneNear = readFloat( stream );
+			cam.mClipPlaneFar = readFloat( stream );
+			cam.mAspect = readFloat( stream );
+
+		}
+
+		function ReadBinaryScene( stream, scene ) {
+
+			var chunkID = Read_uint32_t( stream );
+			ai_assert( chunkID == ASSBIN_CHUNK_AISCENE );
+			/*uint32_t size =*/
+			Read_uint32_t( stream );
+			scene.mFlags = Read_unsigned_int( stream );
+			scene.mNumMeshes = Read_unsigned_int( stream );
+			scene.mNumMaterials = Read_unsigned_int( stream );
+			scene.mNumAnimations = Read_unsigned_int( stream );
+			scene.mNumTextures = Read_unsigned_int( stream );
+			scene.mNumLights = Read_unsigned_int( stream );
+			scene.mNumCameras = Read_unsigned_int( stream );
+			// Read node graph
+			scene.mRootNode = new aiNode();
+			scene.mRootNode = ReadBinaryNode( stream, null, 0 );
+			// Read all meshes
+			if ( scene.mNumMeshes ) {
+
+				scene.mMeshes = [];
+
+				for ( var i = 0; i < scene.mNumMeshes; ++ i ) {
+
+					scene.mMeshes[ i ] = new aiMesh();
+					ReadBinaryMesh( stream, scene.mMeshes[ i ] );
+
+				}
+
+			}
+			// Read materials
+			if ( scene.mNumMaterials ) {
+
+				scene.mMaterials = [];
+
+				for ( var i = 0; i < scene.mNumMaterials; ++ i ) {
+
+					scene.mMaterials[ i ] = new aiMaterial();
+					ReadBinaryMaterial( stream, scene.mMaterials[ i ] );
+
+				}
+
+			}
+			// Read all animations
+			if ( scene.mNumAnimations ) {
+
+				scene.mAnimations = [];
+
+				for ( var i = 0; i < scene.mNumAnimations; ++ i ) {
+
+					scene.mAnimations[ i ] = new aiAnimation();
+					ReadBinaryAnim( stream, scene.mAnimations[ i ] );
+
+				}
+
+			}
+			// Read all textures
+			if ( scene.mNumTextures ) {
+
+				scene.mTextures = [];
+
+				for ( var i = 0; i < scene.mNumTextures; ++ i ) {
+
+					scene.mTextures[ i ] = new aiTexture();
+					ReadBinaryTexture( stream, scene.mTextures[ i ] );
+
+				}
+
+			}
+			// Read lights
+			if ( scene.mNumLights ) {
+
+				scene.mLights = [];
+
+				for ( var i = 0; i < scene.mNumLights; ++ i ) {
+
+					scene.mLights[ i ] = new aiLight();
+					ReadBinaryLight( stream, scene.mLights[ i ] );
+
+				}
+
+			}
+			// Read cameras
+			if ( scene.mNumCameras ) {
+
+				scene.mCameras = [];
+
+				for ( var i = 0; i < scene.mNumCameras; ++ i ) {
+
+					scene.mCameras[ i ] = new aiCamera();
+					ReadBinaryCamera( stream, scene.mCameras[ i ] );
+
+				}
+
+			}
+
+		}
+		var aiOrigin_CUR = 0;
+		var aiOrigin_BEG = 1;
+
+		function extendStream( stream ) {
+
+			stream.readOffset = 0;
+			stream.Seek = function ( off, ori ) {
+
+				if ( ori == aiOrigin_CUR ) {
+
+					stream.readOffset += off;
+
+				}
+				if ( ori == aiOrigin_BEG ) {
+
+					stream.readOffset = off;
+
+				}
+
+			};
+
+			stream.ReadBytes = function ( buff, size, n ) {
+
+				var bytes = size * n;
+				for ( var i = 0; i < bytes; i ++ )
+					buff[ i ] = Read_uint8_t( this );
+
+			};
+
+			stream.subArray32 = function ( start, end ) {
+
+				var buff = this.buffer;
+				var newbuff = buff.slice( start, end );
+				return new Float32Array( newbuff );
+
+			};
+
+			stream.subArrayUint16 = function ( start, end ) {
+
+				var buff = this.buffer;
+				var newbuff = buff.slice( start, end );
+				return new Uint16Array( newbuff );
+
+			};
+
+			stream.subArrayUint8 = function ( start, end ) {
+
+				var buff = this.buffer;
+				var newbuff = buff.slice( start, end );
+				return new Uint8Array( newbuff );
+
+			};
+
+			stream.subArrayUint32 = function ( start, end ) {
+
+				var buff = this.buffer;
+				var newbuff = buff.slice( start, end );
+				return new Uint32Array( newbuff );
+
+			};
+
+		}
+
+		var shortened, compressed;
+
+		function InternReadFile( pFiledata ) {
+
+			var pScene = new aiScene();
+			var stream = new DataView( pFiledata );
+			extendStream( stream );
+			stream.Seek( 44, aiOrigin_CUR ); // signature
+			/*unsigned int versionMajor =*/
+			var versionMajor = Read_unsigned_int( stream );
+			/*unsigned int versionMinor =*/
+			var versionMinor = Read_unsigned_int( stream );
+			/*unsigned int versionRevision =*/
+			var versionRevision = Read_unsigned_int( stream );
+			/*unsigned int compileFlags =*/
+			var compileFlags = Read_unsigned_int( stream );
+			shortened = Read_uint16_t( stream ) > 0;
+			compressed = Read_uint16_t( stream ) > 0;
+			if ( shortened )
+				throw "Shortened binaries are not supported!";
+			stream.Seek( 256, aiOrigin_CUR ); // original filename
+			stream.Seek( 128, aiOrigin_CUR ); // options
+			stream.Seek( 64, aiOrigin_CUR ); // padding
+			if ( compressed ) {
+
+				var uncompressedSize = Read_uint32_t( stream );
+				var compressedSize = stream.FileSize() - stream.Tell();
+				var compressedData = [];
+				stream.Read( compressedData, 1, compressedSize );
+				var uncompressedData = [];
+				uncompress( uncompressedData, uncompressedSize, compressedData, compressedSize );
+				var buff = new ArrayBuffer( uncompressedData );
+				ReadBinaryScene( buff, pScene );
+
+			} else {
+
+				ReadBinaryScene( stream, pScene );
+				return pScene.toTHREE();
+
+			}
+
+		}
+
+		return InternReadFile( buffer );
+
+	}
+
+};
+
+export { AssimpLoader };

+ 1 - 1
examples/jsm/loaders/BVHLoader.d.ts

@@ -6,7 +6,7 @@ import {
 
 
 export interface BVH {
-  clip: AnimationClip,
+  clip: AnimationClip;
   skeleton: Skeleton;
 }
 

+ 14 - 0
examples/jsm/loaders/BabylonLoader.d.ts

@@ -0,0 +1,14 @@
+import {
+  LoadingManager,
+  Scene
+} from '../../../src/Three';
+
+export class BabylonLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  load(url: string, onLoad: (scene: Scene) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  parse(json: object): Scene;
+  setPath(value: string): this;
+}

+ 273 - 0
examples/jsm/loaders/BabylonLoader.js

@@ -0,0 +1,273 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Mugen87 / https://github.com/Mugen87
+ */
+
+import {
+	BufferGeometry,
+	DefaultLoadingManager,
+	DirectionalLight,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	HemisphereLight,
+	Mesh,
+	MeshPhongMaterial,
+	PerspectiveCamera,
+	PointLight,
+	Scene,
+	SpotLight
+} from "../../../build/three.module.js";
+
+var BabylonLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+BabylonLoader.prototype = {
+
+	constructor: BabylonLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( JSON.parse( text ) ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( json ) {
+
+		function parseMaterials( json ) {
+
+			var materials = {};
+
+			for ( var i = 0, l = json.materials.length; i < l; i ++ ) {
+
+				var data = json.materials[ i ];
+
+				var material = new MeshPhongMaterial();
+				material.name = data.name;
+				material.color.fromArray( data.diffuse );
+				material.emissive.fromArray( data.emissive );
+				material.specular.fromArray( data.specular );
+				material.shininess = data.specularPower;
+				material.opacity = data.alpha;
+
+				materials[ data.id ] = material;
+
+			}
+
+			if ( json.multiMaterials ) {
+
+				for ( var i = 0, l = json.multiMaterials.length; i < l; i ++ ) {
+
+					var data = json.multiMaterials[ i ];
+
+					console.warn( 'THREE.BabylonLoader: Multi materials not yet supported.' );
+
+					materials[ data.id ] = new MeshPhongMaterial();
+
+				}
+
+			}
+
+			return materials;
+
+		}
+
+		function parseGeometry( json ) {
+
+			var geometry = new BufferGeometry();
+
+			var indices = json.indices;
+			var positions = json.positions;
+			var normals = json.normals;
+			var uvs = json.uvs;
+
+			// indices
+
+			geometry.setIndex( indices );
+
+			// positions
+
+			for ( var j = 2, jl = positions.length; j < jl; j += 3 ) {
+
+				positions[ j ] = - positions[ j ];
+
+			}
+
+			geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
+
+			// normals
+
+			if ( normals ) {
+
+				for ( var j = 2, jl = normals.length; j < jl; j += 3 ) {
+
+					normals[ j ] = - normals[ j ];
+
+				}
+
+				geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+
+			}
+
+			// uvs
+
+			if ( uvs ) {
+
+				geometry.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+			}
+
+			// offsets
+
+			var subMeshes = json.subMeshes;
+
+			if ( subMeshes ) {
+
+				for ( var j = 0, jl = subMeshes.length; j < jl; j ++ ) {
+
+					var subMesh = subMeshes[ j ];
+
+					geometry.addGroup( subMesh.indexStart, subMesh.indexCount );
+
+				}
+
+			}
+
+			return geometry;
+
+		}
+
+		function parseObjects( json, materials ) {
+
+			var objects = {};
+			var scene = new Scene();
+
+			var cameras = json.cameras;
+
+			for ( var i = 0, l = cameras.length; i < l; i ++ ) {
+
+				var data = cameras[ i ];
+
+				var camera = new PerspectiveCamera( ( data.fov / Math.PI ) * 180, 1.33, data.minZ, data.maxZ );
+
+				camera.name = data.name;
+				camera.position.fromArray( data.position );
+				if ( data.rotation ) camera.rotation.fromArray( data.rotation );
+
+				objects[ data.id ] = camera;
+
+			}
+
+			var lights = json.lights;
+
+			for ( var i = 0, l = lights.length; i < l; i ++ ) {
+
+				var data = lights[ i ];
+
+				var light;
+
+				switch ( data.type ) {
+
+					case 0:
+						light = new PointLight();
+						break;
+
+					case 1:
+						light = new DirectionalLight();
+						break;
+
+					case 2:
+						light = new SpotLight();
+						break;
+
+					case 3:
+						light = new HemisphereLight();
+						break;
+
+				}
+
+				light.name = data.name;
+				if ( data.position ) light.position.set( data.position[ 0 ], data.position[ 1 ], - data.position[ 2 ] );
+				light.color.fromArray( data.diffuse );
+				if ( data.groundColor ) light.groundColor.fromArray( data.groundColor );
+				if ( data.intensity ) light.intensity = data.intensity;
+
+				objects[ data.id ] = light;
+
+				scene.add( light );
+
+			}
+
+			var meshes = json.meshes;
+
+			for ( var i = 0, l = meshes.length; i < l; i ++ ) {
+
+				var data = meshes[ i ];
+
+				var object;
+
+				if ( data.indices ) {
+
+					var geometry = parseGeometry( data );
+
+					object = new Mesh( geometry, materials[ data.materialId ] );
+
+				} else {
+
+					object = new Group();
+
+				}
+
+				object.name = data.name;
+				object.position.set( data.position[ 0 ], data.position[ 1 ], - data.position[ 2 ] );
+				object.rotation.fromArray( data.rotation );
+				if ( data.rotationQuaternion ) object.quaternion.fromArray( data.rotationQuaternion );
+				object.scale.fromArray( data.scaling );
+				// object.visible = data.isVisible;
+
+				if ( data.parentId ) {
+
+					objects[ data.parentId ].add( object );
+
+				} else {
+
+					scene.add( object );
+
+				}
+
+				objects[ data.id ] = object;
+
+			}
+
+			return scene;
+
+		}
+
+		var materials = parseMaterials( json );
+		var scene = parseObjects( json, materials );
+
+		return scene;
+
+	}
+
+};
+
+export { BabylonLoader };

+ 28 - 0
examples/jsm/loaders/ColladaLoader.d.ts

@@ -0,0 +1,28 @@
+import {
+  AnimationClip,
+  LoadingManager,
+  Scene
+} from '../../../src/Three';
+
+
+export interface Collada {
+  animations: AnimationClip[];
+  kinematics: object;
+  library: object;
+  scene: Scene;
+}
+
+export class Collada {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  crossOrigin: string;
+  path: string;
+  resourcePath: string;
+
+  load(url: string, onLoad: (collada: Collada) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+  setResourcePath(path: string) : this;
+  setCrossOrigin(value: string): this;
+
+  parse(text: string, path: string) : Collada;
+}

+ 4013 - 0
examples/jsm/loaders/ColladaLoader.js

@@ -0,0 +1,4013 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Mugen87 / https://github.com/Mugen87
+ */
+
+import {
+	AmbientLight,
+	AnimationClip,
+	Bone,
+	BufferGeometry,
+	ClampToEdgeWrapping,
+	Color,
+	DefaultLoadingManager,
+	DirectionalLight,
+	DoubleSide,
+	Euler,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	Line,
+	LineBasicMaterial,
+	LineSegments,
+	LoaderUtils,
+	Math as _Math,
+	Matrix4,
+	Mesh,
+	MeshBasicMaterial,
+	MeshLambertMaterial,
+	MeshPhongMaterial,
+	OrthographicCamera,
+	PerspectiveCamera,
+	PointLight,
+	Quaternion,
+	QuaternionKeyframeTrack,
+	RepeatWrapping,
+	Scene,
+	Skeleton,
+	SkinnedMesh,
+	SpotLight,
+	TextureLoader,
+	Vector3,
+	VectorKeyframeTrack
+} from "../../../build/three.module.js";
+import { TGALoader } from "../loaders/TGALoader.js";
+
+var ColladaLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+ColladaLoader.prototype = {
+
+	constructor: ColladaLoader,
+
+	crossOrigin: 'anonymous',
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var path = ( scope.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : scope.path;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text, path ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	setResourcePath: function ( value ) {
+
+		this.resourcePath = value;
+		return this;
+
+	},
+
+	options: {
+
+		set convertUpAxis( value ) {
+
+			console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' );
+
+		}
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	parse: function ( text, path ) {
+
+		function getElementsByTagName( xml, name ) {
+
+			// Non recursive xml.getElementsByTagName() ...
+
+			var array = [];
+			var childNodes = xml.childNodes;
+
+			for ( var i = 0, l = childNodes.length; i < l; i ++ ) {
+
+				var child = childNodes[ i ];
+
+				if ( child.nodeName === name ) {
+
+					array.push( child );
+
+				}
+
+			}
+
+			return array;
+
+		}
+
+		function parseStrings( text ) {
+
+			if ( text.length === 0 ) return [];
+
+			var parts = text.trim().split( /\s+/ );
+			var array = new Array( parts.length );
+
+			for ( var i = 0, l = parts.length; i < l; i ++ ) {
+
+				array[ i ] = parts[ i ];
+
+			}
+
+			return array;
+
+		}
+
+		function parseFloats( text ) {
+
+			if ( text.length === 0 ) return [];
+
+			var parts = text.trim().split( /\s+/ );
+			var array = new Array( parts.length );
+
+			for ( var i = 0, l = parts.length; i < l; i ++ ) {
+
+				array[ i ] = parseFloat( parts[ i ] );
+
+			}
+
+			return array;
+
+		}
+
+		function parseInts( text ) {
+
+			if ( text.length === 0 ) return [];
+
+			var parts = text.trim().split( /\s+/ );
+			var array = new Array( parts.length );
+
+			for ( var i = 0, l = parts.length; i < l; i ++ ) {
+
+				array[ i ] = parseInt( parts[ i ] );
+
+			}
+
+			return array;
+
+		}
+
+		function parseId( text ) {
+
+			return text.substring( 1 );
+
+		}
+
+		function generateId() {
+
+			return 'three_default_' + ( count ++ );
+
+		}
+
+		function isEmpty( object ) {
+
+			return Object.keys( object ).length === 0;
+
+		}
+
+		// asset
+
+		function parseAsset( xml ) {
+
+			return {
+				unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ),
+				upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] )
+			};
+
+		}
+
+		function parseAssetUnit( xml ) {
+
+			if ( ( xml !== undefined ) && ( xml.hasAttribute( 'meter' ) === true ) ) {
+
+				return parseFloat( xml.getAttribute( 'meter' ) );
+
+			} else {
+
+				return 1; // default 1 meter
+
+			}
+
+		}
+
+		function parseAssetUpAxis( xml ) {
+
+			return xml !== undefined ? xml.textContent : 'Y_UP';
+
+		}
+
+		// library
+
+		function parseLibrary( xml, libraryName, nodeName, parser ) {
+
+			var library = getElementsByTagName( xml, libraryName )[ 0 ];
+
+			if ( library !== undefined ) {
+
+				var elements = getElementsByTagName( library, nodeName );
+
+				for ( var i = 0; i < elements.length; i ++ ) {
+
+					parser( elements[ i ] );
+
+				}
+
+			}
+
+		}
+
+		function buildLibrary( data, builder ) {
+
+			for ( var name in data ) {
+
+				var object = data[ name ];
+				object.build = builder( data[ name ] );
+
+			}
+
+		}
+
+		// get
+
+		function getBuild( data, builder ) {
+
+			if ( data.build !== undefined ) return data.build;
+
+			data.build = builder( data );
+
+			return data.build;
+
+		}
+
+		// animation
+
+		function parseAnimation( xml ) {
+
+			var data = {
+				sources: {},
+				samplers: {},
+				channels: {}
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				var id;
+
+				switch ( child.nodeName ) {
+
+					case 'source':
+						id = child.getAttribute( 'id' );
+						data.sources[ id ] = parseSource( child );
+						break;
+
+					case 'sampler':
+						id = child.getAttribute( 'id' );
+						data.samplers[ id ] = parseAnimationSampler( child );
+						break;
+
+					case 'channel':
+						id = child.getAttribute( 'target' );
+						data.channels[ id ] = parseAnimationChannel( child );
+						break;
+
+					default:
+						console.log( child );
+
+				}
+
+			}
+
+			library.animations[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parseAnimationSampler( xml ) {
+
+			var data = {
+				inputs: {},
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'input':
+						var id = parseId( child.getAttribute( 'source' ) );
+						var semantic = child.getAttribute( 'semantic' );
+						data.inputs[ semantic ] = id;
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseAnimationChannel( xml ) {
+
+			var data = {};
+
+			var target = xml.getAttribute( 'target' );
+
+			// parsing SID Addressing Syntax
+
+			var parts = target.split( '/' );
+
+			var id = parts.shift();
+			var sid = parts.shift();
+
+			// check selection syntax
+
+			var arraySyntax = ( sid.indexOf( '(' ) !== - 1 );
+			var memberSyntax = ( sid.indexOf( '.' ) !== - 1 );
+
+			if ( memberSyntax ) {
+
+				//  member selection access
+
+				parts = sid.split( '.' );
+				sid = parts.shift();
+				data.member = parts.shift();
+
+			} else if ( arraySyntax ) {
+
+				// array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.
+
+				var indices = sid.split( '(' );
+				sid = indices.shift();
+
+				for ( var i = 0; i < indices.length; i ++ ) {
+
+					indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) );
+
+				}
+
+				data.indices = indices;
+
+			}
+
+			data.id = id;
+			data.sid = sid;
+
+			data.arraySyntax = arraySyntax;
+			data.memberSyntax = memberSyntax;
+
+			data.sampler = parseId( xml.getAttribute( 'source' ) );
+
+			return data;
+
+		}
+
+		function buildAnimation( data ) {
+
+			var tracks = [];
+
+			var channels = data.channels;
+			var samplers = data.samplers;
+			var sources = data.sources;
+
+			for ( var target in channels ) {
+
+				if ( channels.hasOwnProperty( target ) ) {
+
+					var channel = channels[ target ];
+					var sampler = samplers[ channel.sampler ];
+
+					var inputId = sampler.inputs.INPUT;
+					var outputId = sampler.inputs.OUTPUT;
+
+					var inputSource = sources[ inputId ];
+					var outputSource = sources[ outputId ];
+
+					var animation = buildAnimationChannel( channel, inputSource, outputSource );
+
+					createKeyframeTracks( animation, tracks );
+
+				}
+
+			}
+
+			return tracks;
+
+		}
+
+		function getAnimation( id ) {
+
+			return getBuild( library.animations[ id ], buildAnimation );
+
+		}
+
+		function buildAnimationChannel( channel, inputSource, outputSource ) {
+
+			var node = library.nodes[ channel.id ];
+			var object3D = getNode( node.id );
+
+			var transform = node.transforms[ channel.sid ];
+			var defaultMatrix = node.matrix.clone().transpose();
+
+			var time, stride;
+			var i, il, j, jl;
+
+			var data = {};
+
+			// the collada spec allows the animation of data in various ways.
+			// depending on the transform type (matrix, translate, rotate, scale), we execute different logic
+
+			switch ( transform ) {
+
+				case 'matrix':
+
+					for ( i = 0, il = inputSource.array.length; i < il; i ++ ) {
+
+						time = inputSource.array[ i ];
+						stride = i * outputSource.stride;
+
+						if ( data[ time ] === undefined ) data[ time ] = {};
+
+						if ( channel.arraySyntax === true ) {
+
+							var value = outputSource.array[ stride ];
+							var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ];
+
+							data[ time ][ index ] = value;
+
+						} else {
+
+							for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) {
+
+								data[ time ][ j ] = outputSource.array[ stride + j ];
+
+							}
+
+						}
+
+					}
+
+					break;
+
+				case 'translate':
+					console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
+					break;
+
+				case 'rotate':
+					console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
+					break;
+
+				case 'scale':
+					console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
+					break;
+
+			}
+
+			var keyframes = prepareAnimationData( data, defaultMatrix );
+
+			var animation = {
+				name: object3D.uuid,
+				keyframes: keyframes
+			};
+
+			return animation;
+
+		}
+
+		function prepareAnimationData( data, defaultMatrix ) {
+
+			var keyframes = [];
+
+			// transfer data into a sortable array
+
+			for ( var time in data ) {
+
+				keyframes.push( { time: parseFloat( time ), value: data[ time ] } );
+
+			}
+
+			// ensure keyframes are sorted by time
+
+			keyframes.sort( ascending );
+
+			// now we clean up all animation data, so we can use them for keyframe tracks
+
+			for ( var i = 0; i < 16; i ++ ) {
+
+				transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] );
+
+			}
+
+			return keyframes;
+
+			// array sort function
+
+			function ascending( a, b ) {
+
+				return a.time - b.time;
+
+			}
+
+		}
+
+		var position = new Vector3();
+		var scale = new Vector3();
+		var quaternion = new Quaternion();
+
+		function createKeyframeTracks( animation, tracks ) {
+
+			var keyframes = animation.keyframes;
+			var name = animation.name;
+
+			var times = [];
+			var positionData = [];
+			var quaternionData = [];
+			var scaleData = [];
+
+			for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
+
+				var keyframe = keyframes[ i ];
+
+				var time = keyframe.time;
+				var value = keyframe.value;
+
+				matrix.fromArray( value ).transpose();
+				matrix.decompose( position, quaternion, scale );
+
+				times.push( time );
+				positionData.push( position.x, position.y, position.z );
+				quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
+				scaleData.push( scale.x, scale.y, scale.z );
+
+			}
+
+			if ( positionData.length > 0 ) tracks.push( new VectorKeyframeTrack( name + '.position', times, positionData ) );
+			if ( quaternionData.length > 0 ) tracks.push( new QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) );
+			if ( scaleData.length > 0 ) tracks.push( new VectorKeyframeTrack( name + '.scale', times, scaleData ) );
+
+			return tracks;
+
+		}
+
+		function transformAnimationData( keyframes, property, defaultValue ) {
+
+			var keyframe;
+
+			var empty = true;
+			var i, l;
+
+			// check, if values of a property are missing in our keyframes
+
+			for ( i = 0, l = keyframes.length; i < l; i ++ ) {
+
+				keyframe = keyframes[ i ];
+
+				if ( keyframe.value[ property ] === undefined ) {
+
+					keyframe.value[ property ] = null; // mark as missing
+
+				} else {
+
+					empty = false;
+
+				}
+
+			}
+
+			if ( empty === true ) {
+
+				// no values at all, so we set a default value
+
+				for ( i = 0, l = keyframes.length; i < l; i ++ ) {
+
+					keyframe = keyframes[ i ];
+
+					keyframe.value[ property ] = defaultValue;
+
+				}
+
+			} else {
+
+				// filling gaps
+
+				createMissingKeyframes( keyframes, property );
+
+			}
+
+		}
+
+		function createMissingKeyframes( keyframes, property ) {
+
+			var prev, next;
+
+			for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
+
+				var keyframe = keyframes[ i ];
+
+				if ( keyframe.value[ property ] === null ) {
+
+					prev = getPrev( keyframes, i, property );
+					next = getNext( keyframes, i, property );
+
+					if ( prev === null ) {
+
+						keyframe.value[ property ] = next.value[ property ];
+						continue;
+
+					}
+
+					if ( next === null ) {
+
+						keyframe.value[ property ] = prev.value[ property ];
+						continue;
+
+					}
+
+					interpolate( keyframe, prev, next, property );
+
+				}
+
+			}
+
+		}
+
+		function getPrev( keyframes, i, property ) {
+
+			while ( i >= 0 ) {
+
+				var keyframe = keyframes[ i ];
+
+				if ( keyframe.value[ property ] !== null ) return keyframe;
+
+				i --;
+
+			}
+
+			return null;
+
+		}
+
+		function getNext( keyframes, i, property ) {
+
+			while ( i < keyframes.length ) {
+
+				var keyframe = keyframes[ i ];
+
+				if ( keyframe.value[ property ] !== null ) return keyframe;
+
+				i ++;
+
+			}
+
+			return null;
+
+		}
+
+		function interpolate( key, prev, next, property ) {
+
+			if ( ( next.time - prev.time ) === 0 ) {
+
+				key.value[ property ] = prev.value[ property ];
+				return;
+
+			}
+
+			key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ];
+
+		}
+
+		// animation clips
+
+		function parseAnimationClip( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'id' ) || 'default',
+				start: parseFloat( xml.getAttribute( 'start' ) || 0 ),
+				end: parseFloat( xml.getAttribute( 'end' ) || 0 ),
+				animations: []
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'instance_animation':
+						data.animations.push( parseId( child.getAttribute( 'url' ) ) );
+						break;
+
+				}
+
+			}
+
+			library.clips[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function buildAnimationClip( data ) {
+
+			var tracks = [];
+
+			var name = data.name;
+			var duration = ( data.end - data.start ) || - 1;
+			var animations = data.animations;
+
+			for ( var i = 0, il = animations.length; i < il; i ++ ) {
+
+				var animationTracks = getAnimation( animations[ i ] );
+
+				for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) {
+
+					tracks.push( animationTracks[ j ] );
+
+				}
+
+			}
+
+			return new AnimationClip( name, duration, tracks );
+
+		}
+
+		function getAnimationClip( id ) {
+
+			return getBuild( library.clips[ id ], buildAnimationClip );
+
+		}
+
+		// controller
+
+		function parseController( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'skin':
+						// there is exactly one skin per controller
+						data.id = parseId( child.getAttribute( 'source' ) );
+						data.skin = parseSkin( child );
+						break;
+
+					case 'morph':
+						data.id = parseId( child.getAttribute( 'source' ) );
+						console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' );
+						break;
+
+				}
+
+			}
+
+			library.controllers[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parseSkin( xml ) {
+
+			var data = {
+				sources: {}
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'bind_shape_matrix':
+						data.bindShapeMatrix = parseFloats( child.textContent );
+						break;
+
+					case 'source':
+						var id = child.getAttribute( 'id' );
+						data.sources[ id ] = parseSource( child );
+						break;
+
+					case 'joints':
+						data.joints = parseJoints( child );
+						break;
+
+					case 'vertex_weights':
+						data.vertexWeights = parseVertexWeights( child );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseJoints( xml ) {
+
+			var data = {
+				inputs: {}
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'input':
+						var semantic = child.getAttribute( 'semantic' );
+						var id = parseId( child.getAttribute( 'source' ) );
+						data.inputs[ semantic ] = id;
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseVertexWeights( xml ) {
+
+			var data = {
+				inputs: {}
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'input':
+						var semantic = child.getAttribute( 'semantic' );
+						var id = parseId( child.getAttribute( 'source' ) );
+						var offset = parseInt( child.getAttribute( 'offset' ) );
+						data.inputs[ semantic ] = { id: id, offset: offset };
+						break;
+
+					case 'vcount':
+						data.vcount = parseInts( child.textContent );
+						break;
+
+					case 'v':
+						data.v = parseInts( child.textContent );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function buildController( data ) {
+
+			var build = {
+				id: data.id
+			};
+
+			var geometry = library.geometries[ build.id ];
+
+			if ( data.skin !== undefined ) {
+
+				build.skin = buildSkin( data.skin );
+
+				// we enhance the 'sources' property of the corresponding geometry with our skin data
+
+				geometry.sources.skinIndices = build.skin.indices;
+				geometry.sources.skinWeights = build.skin.weights;
+
+			}
+
+			return build;
+
+		}
+
+		function buildSkin( data ) {
+
+			var BONE_LIMIT = 4;
+
+			var build = {
+				joints: [], // this must be an array to preserve the joint order
+				indices: {
+					array: [],
+					stride: BONE_LIMIT
+				},
+				weights: {
+					array: [],
+					stride: BONE_LIMIT
+				}
+			};
+
+			var sources = data.sources;
+			var vertexWeights = data.vertexWeights;
+
+			var vcount = vertexWeights.vcount;
+			var v = vertexWeights.v;
+			var jointOffset = vertexWeights.inputs.JOINT.offset;
+			var weightOffset = vertexWeights.inputs.WEIGHT.offset;
+
+			var jointSource = data.sources[ data.joints.inputs.JOINT ];
+			var inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ];
+
+			var weights = sources[ vertexWeights.inputs.WEIGHT.id ].array;
+			var stride = 0;
+
+			var i, j, l;
+
+			// procces skin data for each vertex
+
+			for ( i = 0, l = vcount.length; i < l; i ++ ) {
+
+				var jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex
+				var vertexSkinData = [];
+
+				for ( j = 0; j < jointCount; j ++ ) {
+
+					var skinIndex = v[ stride + jointOffset ];
+					var weightId = v[ stride + weightOffset ];
+					var skinWeight = weights[ weightId ];
+
+					vertexSkinData.push( { index: skinIndex, weight: skinWeight } );
+
+					stride += 2;
+
+				}
+
+				// we sort the joints in descending order based on the weights.
+				// this ensures, we only procced the most important joints of the vertex
+
+				vertexSkinData.sort( descending );
+
+				// now we provide for each vertex a set of four index and weight values.
+				// the order of the skin data matches the order of vertices
+
+				for ( j = 0; j < BONE_LIMIT; j ++ ) {
+
+					var d = vertexSkinData[ j ];
+
+					if ( d !== undefined ) {
+
+						build.indices.array.push( d.index );
+						build.weights.array.push( d.weight );
+
+					} else {
+
+						build.indices.array.push( 0 );
+						build.weights.array.push( 0 );
+
+					}
+
+				}
+
+			}
+
+			// setup bind matrix
+
+			if ( data.bindShapeMatrix ) {
+
+				build.bindMatrix = new Matrix4().fromArray( data.bindShapeMatrix ).transpose();
+
+			} else {
+
+				build.bindMatrix = new Matrix4().identity();
+
+			}
+
+			// process bones and inverse bind matrix data
+
+			for ( i = 0, l = jointSource.array.length; i < l; i ++ ) {
+
+				var name = jointSource.array[ i ];
+				var boneInverse = new Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose();
+
+				build.joints.push( { name: name, boneInverse: boneInverse } );
+
+			}
+
+			return build;
+
+			// array sort function
+
+			function descending( a, b ) {
+
+				return b.weight - a.weight;
+
+			}
+
+		}
+
+		function getController( id ) {
+
+			return getBuild( library.controllers[ id ], buildController );
+
+		}
+
+		// image
+
+		function parseImage( xml ) {
+
+			var data = {
+				init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent
+			};
+
+			library.images[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function buildImage( data ) {
+
+			if ( data.build !== undefined ) return data.build;
+
+			return data.init_from;
+
+		}
+
+		function getImage( id ) {
+
+			var data = library.images[ id ];
+
+			if ( data !== undefined ) {
+
+				return getBuild( data, buildImage );
+
+			}
+
+			console.warn( 'THREE.ColladaLoader: Couldn\'t find image with ID:', id );
+
+			return null;
+
+		}
+
+		// effect
+
+		function parseEffect( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'profile_COMMON':
+						data.profile = parseEffectProfileCOMMON( child );
+						break;
+
+				}
+
+			}
+
+			library.effects[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parseEffectProfileCOMMON( xml ) {
+
+			var data = {
+				surfaces: {},
+				samplers: {}
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'newparam':
+						parseEffectNewparam( child, data );
+						break;
+
+					case 'technique':
+						data.technique = parseEffectTechnique( child );
+						break;
+
+					case 'extra':
+						data.extra = parseEffectExtra( child );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectNewparam( xml, data ) {
+
+			var sid = xml.getAttribute( 'sid' );
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'surface':
+						data.surfaces[ sid ] = parseEffectSurface( child );
+						break;
+
+					case 'sampler2D':
+						data.samplers[ sid ] = parseEffectSampler( child );
+						break;
+
+				}
+
+			}
+
+		}
+
+		function parseEffectSurface( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'init_from':
+						data.init_from = child.textContent;
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectSampler( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'source':
+						data.source = child.textContent;
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectTechnique( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'constant':
+					case 'lambert':
+					case 'blinn':
+					case 'phong':
+						data.type = child.nodeName;
+						data.parameters = parseEffectParameters( child );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectParameters( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'emission':
+					case 'diffuse':
+					case 'specular':
+					case 'bump':
+					case 'ambient':
+					case 'shininess':
+					case 'transparency':
+						data[ child.nodeName ] = parseEffectParameter( child );
+						break;
+					case 'transparent':
+						data[ child.nodeName ] = {
+							opaque: child.getAttribute( 'opaque' ),
+							data: parseEffectParameter( child )
+						};
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectParameter( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'color':
+						data[ child.nodeName ] = parseFloats( child.textContent );
+						break;
+
+					case 'float':
+						data[ child.nodeName ] = parseFloat( child.textContent );
+						break;
+
+					case 'texture':
+						data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) };
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectParameterTexture( xml ) {
+
+			var data = {
+				technique: {}
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'extra':
+						parseEffectParameterTextureExtra( child, data );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectParameterTextureExtra( xml, data ) {
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'technique':
+						parseEffectParameterTextureExtraTechnique( child, data );
+						break;
+
+				}
+
+			}
+
+		}
+
+		function parseEffectParameterTextureExtraTechnique( xml, data ) {
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'repeatU':
+					case 'repeatV':
+					case 'offsetU':
+					case 'offsetV':
+						data.technique[ child.nodeName ] = parseFloat( child.textContent );
+						break;
+
+					case 'wrapU':
+					case 'wrapV':
+
+						// some files have values for wrapU/wrapV which become NaN via parseInt
+
+						if ( child.textContent.toUpperCase() === 'TRUE' ) {
+
+							data.technique[ child.nodeName ] = 1;
+
+						} else if ( child.textContent.toUpperCase() === 'FALSE' ) {
+
+							data.technique[ child.nodeName ] = 0;
+
+						} else {
+
+							data.technique[ child.nodeName ] = parseInt( child.textContent );
+
+						}
+
+						break;
+
+				}
+
+			}
+
+		}
+
+		function parseEffectExtra( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'technique':
+						data.technique = parseEffectExtraTechnique( child );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseEffectExtraTechnique( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'double_sided':
+						data[ child.nodeName ] = parseInt( child.textContent );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function buildEffect( data ) {
+
+			return data;
+
+		}
+
+		function getEffect( id ) {
+
+			return getBuild( library.effects[ id ], buildEffect );
+
+		}
+
+		// material
+
+		function parseMaterial( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' )
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'instance_effect':
+						data.url = parseId( child.getAttribute( 'url' ) );
+						break;
+
+				}
+
+			}
+
+			library.materials[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function getTextureLoader( image ) {
+
+			var loader;
+
+			var extension = image.slice( ( image.lastIndexOf( '.' ) - 1 >>> 0 ) + 2 ); // http://www.jstips.co/en/javascript/get-file-extension/
+			extension = extension.toLowerCase();
+
+			switch ( extension ) {
+
+				case 'tga':
+					loader = tgaLoader;
+					break;
+
+				default:
+					loader = textureLoader;
+
+			}
+
+			return loader;
+
+		}
+
+		function buildMaterial( data ) {
+
+			var effect = getEffect( data.url );
+			var technique = effect.profile.technique;
+			var extra = effect.profile.extra;
+
+			var material;
+
+			switch ( technique.type ) {
+
+				case 'phong':
+				case 'blinn':
+					material = new MeshPhongMaterial();
+					break;
+
+				case 'lambert':
+					material = new MeshLambertMaterial();
+					break;
+
+				default:
+					material = new MeshBasicMaterial();
+					break;
+
+			}
+
+			material.name = data.name || '';
+
+			function getTexture( textureObject ) {
+
+				var sampler = effect.profile.samplers[ textureObject.id ];
+				var image = null;
+
+				// get image
+
+				if ( sampler !== undefined ) {
+
+					var surface = effect.profile.surfaces[ sampler.source ];
+					image = getImage( surface.init_from );
+
+				} else {
+
+					console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' );
+					image = getImage( textureObject.id );
+
+				}
+
+				// create texture if image is avaiable
+
+				if ( image !== null ) {
+
+					var loader = getTextureLoader( image );
+
+					if ( loader !== undefined ) {
+
+						var texture = loader.load( image );
+
+						var extra = textureObject.extra;
+
+						if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) {
+
+							var technique = extra.technique;
+
+							texture.wrapS = technique.wrapU ? RepeatWrapping : ClampToEdgeWrapping;
+							texture.wrapT = technique.wrapV ? RepeatWrapping : ClampToEdgeWrapping;
+
+							texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 );
+							texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 );
+
+						} else {
+
+							texture.wrapS = RepeatWrapping;
+							texture.wrapT = RepeatWrapping;
+
+						}
+
+						return texture;
+
+					} else {
+
+						console.warn( 'THREE.ColladaLoader: Loader for texture %s not found.', image );
+
+						return null;
+
+					}
+
+				} else {
+
+					console.warn( 'THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id );
+
+					return null;
+
+				}
+
+			}
+
+			var parameters = technique.parameters;
+
+			for ( var key in parameters ) {
+
+				var parameter = parameters[ key ];
+
+				switch ( key ) {
+
+					case 'diffuse':
+						if ( parameter.color ) material.color.fromArray( parameter.color );
+						if ( parameter.texture ) material.map = getTexture( parameter.texture );
+						break;
+					case 'specular':
+						if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color );
+						if ( parameter.texture ) material.specularMap = getTexture( parameter.texture );
+						break;
+					case 'bump':
+						if ( parameter.texture ) material.normalMap = getTexture( parameter.texture );
+						break;
+					case 'ambient':
+						if ( parameter.texture ) material.lightMap = getTexture( parameter.texture );
+						break;
+					case 'shininess':
+						if ( parameter.float && material.shininess ) material.shininess = parameter.float;
+						break;
+					case 'emission':
+						if ( parameter.color && material.emissive ) material.emissive.fromArray( parameter.color );
+						if ( parameter.texture ) material.emissiveMap = getTexture( parameter.texture );
+						break;
+
+				}
+
+			}
+
+			//
+
+			var transparent = parameters[ 'transparent' ];
+			var transparency = parameters[ 'transparency' ];
+
+			// <transparency> does not exist but <transparent>
+
+			if ( transparency === undefined && transparent ) {
+
+				transparency = {
+					float: 1
+				};
+
+			}
+
+			// <transparent> does not exist but <transparency>
+
+			if ( transparent === undefined && transparency ) {
+
+				transparent = {
+					opaque: 'A_ONE',
+					data: {
+						color: [ 1, 1, 1, 1 ]
+					} };
+
+			}
+
+			if ( transparent && transparency ) {
+
+				// handle case if a texture exists but no color
+
+				if ( transparent.data.texture ) {
+
+					// we do not set an alpha map (see #13792)
+
+					material.transparent = true;
+
+				} else {
+
+					var color = transparent.data.color;
+
+					switch ( transparent.opaque ) {
+
+						case 'A_ONE':
+							material.opacity = color[ 3 ] * transparency.float;
+							break;
+						case 'RGB_ZERO':
+							material.opacity = 1 - ( color[ 0 ] * transparency.float );
+							break;
+						case 'A_ZERO':
+							material.opacity = 1 - ( color[ 3 ] * transparency.float );
+							break;
+						case 'RGB_ONE':
+							material.opacity = color[ 0 ] * transparency.float;
+							break;
+						default:
+							console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque );
+
+					}
+
+					if ( material.opacity < 1 ) material.transparent = true;
+
+				}
+
+			}
+
+			//
+
+			if ( extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1 ) {
+
+				material.side = DoubleSide;
+
+			}
+
+			return material;
+
+		}
+
+		function getMaterial( id ) {
+
+			return getBuild( library.materials[ id ], buildMaterial );
+
+		}
+
+		// camera
+
+		function parseCamera( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' )
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'optics':
+						data.optics = parseCameraOptics( child );
+						break;
+
+				}
+
+			}
+
+			library.cameras[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parseCameraOptics( xml ) {
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				switch ( child.nodeName ) {
+
+					case 'technique_common':
+						return parseCameraTechnique( child );
+
+				}
+
+			}
+
+			return {};
+
+		}
+
+		function parseCameraTechnique( xml ) {
+
+			var data = {};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				switch ( child.nodeName ) {
+
+					case 'perspective':
+					case 'orthographic':
+
+						data.technique = child.nodeName;
+						data.parameters = parseCameraParameters( child );
+
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseCameraParameters( xml ) {
+
+			var data = {};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				switch ( child.nodeName ) {
+
+					case 'xfov':
+					case 'yfov':
+					case 'xmag':
+					case 'ymag':
+					case 'znear':
+					case 'zfar':
+					case 'aspect_ratio':
+						data[ child.nodeName ] = parseFloat( child.textContent );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function buildCamera( data ) {
+
+			var camera;
+
+			switch ( data.optics.technique ) {
+
+				case 'perspective':
+					camera = new PerspectiveCamera(
+						data.optics.parameters.yfov,
+						data.optics.parameters.aspect_ratio,
+						data.optics.parameters.znear,
+						data.optics.parameters.zfar
+					);
+					break;
+
+				case 'orthographic':
+					var ymag = data.optics.parameters.ymag;
+					var xmag = data.optics.parameters.xmag;
+					var aspectRatio = data.optics.parameters.aspect_ratio;
+
+					xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag;
+					ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag;
+
+					xmag *= 0.5;
+					ymag *= 0.5;
+
+					camera = new OrthographicCamera(
+						- xmag, xmag, ymag, - ymag, // left, right, top, bottom
+						data.optics.parameters.znear,
+						data.optics.parameters.zfar
+					);
+					break;
+
+				default:
+					camera = new PerspectiveCamera();
+					break;
+
+			}
+
+			camera.name = data.name || '';
+
+			return camera;
+
+		}
+
+		function getCamera( id ) {
+
+			var data = library.cameras[ id ];
+
+			if ( data !== undefined ) {
+
+				return getBuild( data, buildCamera );
+
+			}
+
+			console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id );
+
+			return null;
+
+		}
+
+		// light
+
+		function parseLight( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'technique_common':
+						data = parseLightTechnique( child );
+						break;
+
+				}
+
+			}
+
+			library.lights[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parseLightTechnique( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'directional':
+					case 'point':
+					case 'spot':
+					case 'ambient':
+
+						data.technique = child.nodeName;
+						data.parameters = parseLightParameters( child );
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseLightParameters( xml ) {
+
+			var data = {};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'color':
+						var array = parseFloats( child.textContent );
+						data.color = new Color().fromArray( array );
+						break;
+
+					case 'falloff_angle':
+						data.falloffAngle = parseFloat( child.textContent );
+						break;
+
+					case 'quadratic_attenuation':
+						var f = parseFloat( child.textContent );
+						data.distance = f ? Math.sqrt( 1 / f ) : 0;
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function buildLight( data ) {
+
+			var light;
+
+			switch ( data.technique ) {
+
+				case 'directional':
+					light = new DirectionalLight();
+					break;
+
+				case 'point':
+					light = new PointLight();
+					break;
+
+				case 'spot':
+					light = new SpotLight();
+					break;
+
+				case 'ambient':
+					light = new AmbientLight();
+					break;
+
+			}
+
+			if ( data.parameters.color ) light.color.copy( data.parameters.color );
+			if ( data.parameters.distance ) light.distance = data.parameters.distance;
+
+			return light;
+
+		}
+
+		function getLight( id ) {
+
+			var data = library.lights[ id ];
+
+			if ( data !== undefined ) {
+
+				return getBuild( data, buildLight );
+
+			}
+
+			console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id );
+
+			return null;
+
+		}
+
+		// geometry
+
+		function parseGeometry( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' ),
+				sources: {},
+				vertices: {},
+				primitives: []
+			};
+
+			var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ];
+
+			// the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep
+			if ( mesh === undefined ) return;
+
+			for ( var i = 0; i < mesh.childNodes.length; i ++ ) {
+
+				var child = mesh.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				var id = child.getAttribute( 'id' );
+
+				switch ( child.nodeName ) {
+
+					case 'source':
+						data.sources[ id ] = parseSource( child );
+						break;
+
+					case 'vertices':
+						// data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];
+						data.vertices = parseGeometryVertices( child );
+						break;
+
+					case 'polygons':
+						console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName );
+						break;
+
+					case 'lines':
+					case 'linestrips':
+					case 'polylist':
+					case 'triangles':
+						data.primitives.push( parseGeometryPrimitive( child ) );
+						break;
+
+					default:
+						console.log( child );
+
+				}
+
+			}
+
+			library.geometries[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parseSource( xml ) {
+
+			var data = {
+				array: [],
+				stride: 3
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'float_array':
+						data.array = parseFloats( child.textContent );
+						break;
+
+					case 'Name_array':
+						data.array = parseStrings( child.textContent );
+						break;
+
+					case 'technique_common':
+						var accessor = getElementsByTagName( child, 'accessor' )[ 0 ];
+
+						if ( accessor !== undefined ) {
+
+							data.stride = parseInt( accessor.getAttribute( 'stride' ) );
+
+						}
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseGeometryVertices( xml ) {
+
+			var data = {};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) );
+
+			}
+
+			return data;
+
+		}
+
+		function parseGeometryPrimitive( xml ) {
+
+			var primitive = {
+				type: xml.nodeName,
+				material: xml.getAttribute( 'material' ),
+				count: parseInt( xml.getAttribute( 'count' ) ),
+				inputs: {},
+				stride: 0,
+				hasUV: false
+			};
+
+			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'input':
+						var id = parseId( child.getAttribute( 'source' ) );
+						var semantic = child.getAttribute( 'semantic' );
+						var offset = parseInt( child.getAttribute( 'offset' ) );
+						var set = parseInt( child.getAttribute( 'set' ) );
+						var inputname = ( set > 0 ? semantic + set : semantic );
+						primitive.inputs[ inputname ] = { id: id, offset: offset };
+						primitive.stride = Math.max( primitive.stride, offset + 1 );
+						if ( semantic === 'TEXCOORD' ) primitive.hasUV = true;
+						break;
+
+					case 'vcount':
+						primitive.vcount = parseInts( child.textContent );
+						break;
+
+					case 'p':
+						primitive.p = parseInts( child.textContent );
+						break;
+
+				}
+
+			}
+
+			return primitive;
+
+		}
+
+		function groupPrimitives( primitives ) {
+
+			var build = {};
+
+			for ( var i = 0; i < primitives.length; i ++ ) {
+
+				var primitive = primitives[ i ];
+
+				if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = [];
+
+				build[ primitive.type ].push( primitive );
+
+			}
+
+			return build;
+
+		}
+
+		function checkUVCoordinates( primitives ) {
+
+			var count = 0;
+
+			for ( var i = 0, l = primitives.length; i < l; i ++ ) {
+
+				var primitive = primitives[ i ];
+
+				if ( primitive.hasUV === true ) {
+
+					count ++;
+
+				}
+
+			}
+
+			if ( count > 0 && count < primitives.length ) {
+
+				primitives.uvsNeedsFix = true;
+
+			}
+
+		}
+
+		function buildGeometry( data ) {
+
+			var build = {};
+
+			var sources = data.sources;
+			var vertices = data.vertices;
+			var primitives = data.primitives;
+
+			if ( primitives.length === 0 ) return {};
+
+			// our goal is to create one buffer geometry for a single type of primitives
+			// first, we group all primitives by their type
+
+			var groupedPrimitives = groupPrimitives( primitives );
+
+			for ( var type in groupedPrimitives ) {
+
+				var primitiveType = groupedPrimitives[ type ];
+
+				// second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines)
+
+				checkUVCoordinates( primitiveType );
+
+				// third, create a buffer geometry for each type of primitives
+
+				build[ type ] = buildGeometryType( primitiveType, sources, vertices );
+
+			}
+
+			return build;
+
+		}
+
+		function buildGeometryType( primitives, sources, vertices ) {
+
+			var build = {};
+
+			var position = { array: [], stride: 0 };
+			var normal = { array: [], stride: 0 };
+			var uv = { array: [], stride: 0 };
+			var uv2 = { array: [], stride: 0 };
+			var color = { array: [], stride: 0 };
+
+			var skinIndex = { array: [], stride: 4 };
+			var skinWeight = { array: [], stride: 4 };
+
+			var geometry = new BufferGeometry();
+
+			var materialKeys = [];
+
+			var start = 0;
+
+			for ( var p = 0; p < primitives.length; p ++ ) {
+
+				var primitive = primitives[ p ];
+				var inputs = primitive.inputs;
+
+				// groups
+
+				var count = 0;
+
+				switch ( primitive.type ) {
+
+					case 'lines':
+					case 'linestrips':
+						count = primitive.count * 2;
+						break;
+
+					case 'triangles':
+						count = primitive.count * 3;
+						break;
+
+					case 'polylist':
+
+						for ( var g = 0; g < primitive.count; g ++ ) {
+
+							var vc = primitive.vcount[ g ];
+
+							switch ( vc ) {
+
+								case 3:
+									count += 3; // single triangle
+									break;
+
+								case 4:
+									count += 6; // quad, subdivided into two triangles
+									break;
+
+								default:
+									count += ( vc - 2 ) * 3; // polylist with more than four vertices
+									break;
+
+							}
+
+						}
+
+						break;
+
+					default:
+						console.warn( 'THREE.ColladaLoader: Unknow primitive type:', primitive.type );
+
+				}
+
+				geometry.addGroup( start, count, p );
+				start += count;
+
+				// material
+
+				if ( primitive.material ) {
+
+					materialKeys.push( primitive.material );
+
+				}
+
+				// geometry data
+
+				for ( var name in inputs ) {
+
+					var input = inputs[ name ];
+
+					switch ( name )	{
+
+						case 'VERTEX':
+							for ( var key in vertices ) {
+
+								var id = vertices[ key ];
+
+								switch ( key ) {
+
+									case 'POSITION':
+										var prevLength = position.array.length;
+										buildGeometryData( primitive, sources[ id ], input.offset, position.array );
+										position.stride = sources[ id ].stride;
+
+										if ( sources.skinWeights && sources.skinIndices ) {
+
+											buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array );
+											buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array );
+
+										}
+
+										// see #3803
+
+										if ( primitive.hasUV === false && primitives.uvsNeedsFix === true ) {
+
+											var count = ( position.array.length - prevLength ) / position.stride;
+
+											for ( var i = 0; i < count; i ++ ) {
+
+												// fill missing uv coordinates
+
+												uv.array.push( 0, 0 );
+
+											}
+
+										}
+										break;
+
+									case 'NORMAL':
+										buildGeometryData( primitive, sources[ id ], input.offset, normal.array );
+										normal.stride = sources[ id ].stride;
+										break;
+
+									case 'COLOR':
+										buildGeometryData( primitive, sources[ id ], input.offset, color.array );
+										color.stride = sources[ id ].stride;
+										break;
+
+									case 'TEXCOORD':
+										buildGeometryData( primitive, sources[ id ], input.offset, uv.array );
+										uv.stride = sources[ id ].stride;
+										break;
+
+									case 'TEXCOORD1':
+										buildGeometryData( primitive, sources[ id ], input.offset, uv2.array );
+										uv.stride = sources[ id ].stride;
+										break;
+
+									default:
+										console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key );
+
+								}
+
+							}
+							break;
+
+						case 'NORMAL':
+							buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array );
+							normal.stride = sources[ input.id ].stride;
+							break;
+
+						case 'COLOR':
+							buildGeometryData( primitive, sources[ input.id ], input.offset, color.array );
+							color.stride = sources[ input.id ].stride;
+							break;
+
+						case 'TEXCOORD':
+							buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array );
+							uv.stride = sources[ input.id ].stride;
+							break;
+
+						case 'TEXCOORD1':
+							buildGeometryData( primitive, sources[ input.id ], input.offset, uv2.array );
+							uv2.stride = sources[ input.id ].stride;
+							break;
+
+					}
+
+				}
+
+			}
+
+			// build geometry
+
+			if ( position.array.length > 0 ) geometry.addAttribute( 'position', new Float32BufferAttribute( position.array, position.stride ) );
+			if ( normal.array.length > 0 ) geometry.addAttribute( 'normal', new Float32BufferAttribute( normal.array, normal.stride ) );
+			if ( color.array.length > 0 ) geometry.addAttribute( 'color', new Float32BufferAttribute( color.array, color.stride ) );
+			if ( uv.array.length > 0 ) geometry.addAttribute( 'uv', new Float32BufferAttribute( uv.array, uv.stride ) );
+			if ( uv2.array.length > 0 ) geometry.addAttribute( 'uv2', new Float32BufferAttribute( uv2.array, uv2.stride ) );
+
+			if ( skinIndex.array.length > 0 ) geometry.addAttribute( 'skinIndex', new Float32BufferAttribute( skinIndex.array, skinIndex.stride ) );
+			if ( skinWeight.array.length > 0 ) geometry.addAttribute( 'skinWeight', new Float32BufferAttribute( skinWeight.array, skinWeight.stride ) );
+
+			build.data = geometry;
+			build.type = primitives[ 0 ].type;
+			build.materialKeys = materialKeys;
+
+			return build;
+
+		}
+
+		function buildGeometryData( primitive, source, offset, array ) {
+
+			var indices = primitive.p;
+			var stride = primitive.stride;
+			var vcount = primitive.vcount;
+
+			function pushVector( i ) {
+
+				var index = indices[ i + offset ] * sourceStride;
+				var length = index + sourceStride;
+
+				for ( ; index < length; index ++ ) {
+
+					array.push( sourceArray[ index ] );
+
+				}
+
+			}
+
+			var sourceArray = source.array;
+			var sourceStride = source.stride;
+
+			if ( primitive.vcount !== undefined ) {
+
+				var index = 0;
+
+				for ( var i = 0, l = vcount.length; i < l; i ++ ) {
+
+					var count = vcount[ i ];
+
+					if ( count === 4 ) {
+
+						var a = index + stride * 0;
+						var b = index + stride * 1;
+						var c = index + stride * 2;
+						var d = index + stride * 3;
+
+						pushVector( a ); pushVector( b ); pushVector( d );
+						pushVector( b ); pushVector( c ); pushVector( d );
+
+					} else if ( count === 3 ) {
+
+						var a = index + stride * 0;
+						var b = index + stride * 1;
+						var c = index + stride * 2;
+
+						pushVector( a ); pushVector( b ); pushVector( c );
+
+					} else if ( count > 4 ) {
+
+						for ( var k = 1, kl = ( count - 2 ); k <= kl; k ++ ) {
+
+							var a = index + stride * 0;
+							var b = index + stride * k;
+							var c = index + stride * ( k + 1 );
+
+							pushVector( a ); pushVector( b ); pushVector( c );
+
+						}
+
+					}
+
+					index += stride * count;
+
+				}
+
+			} else {
+
+				for ( var i = 0, l = indices.length; i < l; i += stride ) {
+
+					pushVector( i );
+
+				}
+
+			}
+
+		}
+
+		function getGeometry( id ) {
+
+			return getBuild( library.geometries[ id ], buildGeometry );
+
+		}
+
+		// kinematics
+
+		function parseKinematicsModel( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' ) || '',
+				joints: {},
+				links: []
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'technique_common':
+						parseKinematicsTechniqueCommon( child, data );
+						break;
+
+				}
+
+			}
+
+			library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function buildKinematicsModel( data ) {
+
+			if ( data.build !== undefined ) return data.build;
+
+			return data;
+
+		}
+
+		function getKinematicsModel( id ) {
+
+			return getBuild( library.kinematicsModels[ id ], buildKinematicsModel );
+
+		}
+
+		function parseKinematicsTechniqueCommon( xml, data ) {
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'joint':
+						data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child );
+						break;
+
+					case 'link':
+						data.links.push( parseKinematicsLink( child ) );
+						break;
+
+				}
+
+			}
+
+		}
+
+		function parseKinematicsJoint( xml ) {
+
+			var data;
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'prismatic':
+					case 'revolute':
+						data = parseKinematicsJointParameter( child );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseKinematicsJointParameter( xml, data ) {
+
+			var data = {
+				sid: xml.getAttribute( 'sid' ),
+				name: xml.getAttribute( 'name' ) || '',
+				axis: new Vector3(),
+				limits: {
+					min: 0,
+					max: 0
+				},
+				type: xml.nodeName,
+				static: false,
+				zeroPosition: 0,
+				middlePosition: 0
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'axis':
+						var array = parseFloats( child.textContent );
+						data.axis.fromArray( array );
+						break;
+					case 'limits':
+						var max = child.getElementsByTagName( 'max' )[ 0 ];
+						var min = child.getElementsByTagName( 'min' )[ 0 ];
+
+						data.limits.max = parseFloat( max.textContent );
+						data.limits.min = parseFloat( min.textContent );
+						break;
+
+				}
+
+			}
+
+			// if min is equal to or greater than max, consider the joint static
+
+			if ( data.limits.min >= data.limits.max ) {
+
+				data.static = true;
+
+			}
+
+			// calculate middle position
+
+			data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0;
+
+			return data;
+
+		}
+
+		function parseKinematicsLink( xml ) {
+
+			var data = {
+				sid: xml.getAttribute( 'sid' ),
+				name: xml.getAttribute( 'name' ) || '',
+				attachments: [],
+				transforms: []
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'attachment_full':
+						data.attachments.push( parseKinematicsAttachment( child ) );
+						break;
+
+					case 'matrix':
+					case 'translate':
+					case 'rotate':
+						data.transforms.push( parseKinematicsTransform( child ) );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseKinematicsAttachment( xml ) {
+
+			var data = {
+				joint: xml.getAttribute( 'joint' ).split( '/' ).pop(),
+				transforms: [],
+				links: []
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'link':
+						data.links.push( parseKinematicsLink( child ) );
+						break;
+
+					case 'matrix':
+					case 'translate':
+					case 'rotate':
+						data.transforms.push( parseKinematicsTransform( child ) );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function parseKinematicsTransform( xml ) {
+
+			var data = {
+				type: xml.nodeName
+			};
+
+			var array = parseFloats( xml.textContent );
+
+			switch ( data.type ) {
+
+				case 'matrix':
+					data.obj = new Matrix4();
+					data.obj.fromArray( array ).transpose();
+					break;
+
+				case 'translate':
+					data.obj = new Vector3();
+					data.obj.fromArray( array );
+					break;
+
+				case 'rotate':
+					data.obj = new Vector3();
+					data.obj.fromArray( array );
+					data.angle = _Math.degToRad( array[ 3 ] );
+					break;
+
+			}
+
+			return data;
+
+		}
+
+		// physics
+
+		function parsePhysicsModel( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' ) || '',
+				rigidBodies: {}
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'rigid_body':
+						data.rigidBodies[ child.getAttribute( 'name' ) ] = {};
+						parsePhysicsRigidBody( child, data.rigidBodies[ child.getAttribute( 'name' ) ] );
+						break;
+
+				}
+
+			}
+
+			library.physicsModels[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function parsePhysicsRigidBody( xml, data ) {
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'technique_common':
+						parsePhysicsTechniqueCommon( child, data );
+						break;
+
+				}
+
+			}
+
+		}
+
+		function parsePhysicsTechniqueCommon( xml, data ) {
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'inertia':
+						data.inertia = parseFloats( child.textContent );
+						break;
+
+					case 'mass':
+						data.mass = parseFloats( child.textContent )[ 0 ];
+						break;
+
+				}
+
+			}
+
+		}
+
+		// scene
+
+		function parseKinematicsScene( xml ) {
+
+			var data = {
+				bindJointAxis: []
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'bind_joint_axis':
+						data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) );
+						break;
+
+				}
+
+			}
+
+			library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data;
+
+		}
+
+		function parseKinematicsBindJointAxis( xml ) {
+
+			var data = {
+				target: xml.getAttribute( 'target' ).split( '/' ).pop()
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'axis':
+						var param = child.getElementsByTagName( 'param' )[ 0 ];
+						data.axis = param.textContent;
+						var tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ];
+						data.jointIndex = tmpJointIndex.substr( 0, tmpJointIndex.length - 1 );
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function buildKinematicsScene( data ) {
+
+			if ( data.build !== undefined ) return data.build;
+
+			return data;
+
+		}
+
+		function getKinematicsScene( id ) {
+
+			return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene );
+
+		}
+
+		function setupKinematics() {
+
+			var kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ];
+			var kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ];
+			var visualSceneId = Object.keys( library.visualScenes )[ 0 ];
+
+			if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return;
+
+			var kinematicsModel = getKinematicsModel( kinematicsModelId );
+			var kinematicsScene = getKinematicsScene( kinematicsSceneId );
+			var visualScene = getVisualScene( visualSceneId );
+
+			var bindJointAxis = kinematicsScene.bindJointAxis;
+			var jointMap = {};
+
+			for ( var i = 0, l = bindJointAxis.length; i < l; i ++ ) {
+
+				var axis = bindJointAxis[ i ];
+
+				// the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix'
+
+				var targetElement = collada.querySelector( '[sid="' + axis.target + '"]' );
+
+				if ( targetElement ) {
+
+					// get the parent of the transfrom element
+
+					var parentVisualElement = targetElement.parentElement;
+
+					// connect the joint of the kinematics model with the element in the visual scene
+
+					connect( axis.jointIndex, parentVisualElement );
+
+				}
+
+			}
+
+			function connect( jointIndex, visualElement ) {
+
+				var visualElementName = visualElement.getAttribute( 'name' );
+				var joint = kinematicsModel.joints[ jointIndex ];
+
+				visualScene.traverse( function ( object ) {
+
+					if ( object.name === visualElementName ) {
+
+						jointMap[ jointIndex ] = {
+							object: object,
+							transforms: buildTransformList( visualElement ),
+							joint: joint,
+							position: joint.zeroPosition
+						};
+
+					}
+
+				} );
+
+			}
+
+			var m0 = new Matrix4();
+
+			kinematics = {
+
+				joints: kinematicsModel && kinematicsModel.joints,
+
+				getJointValue: function ( jointIndex ) {
+
+					var jointData = jointMap[ jointIndex ];
+
+					if ( jointData ) {
+
+						return jointData.position;
+
+					} else {
+
+						console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' );
+
+					}
+
+				},
+
+				setJointValue: function ( jointIndex, value ) {
+
+					var jointData = jointMap[ jointIndex ];
+
+					if ( jointData ) {
+
+						var joint = jointData.joint;
+
+						if ( value > joint.limits.max || value < joint.limits.min ) {
+
+							console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' );
+
+						} else if ( joint.static ) {
+
+							console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' );
+
+						} else {
+
+							var object = jointData.object;
+							var axis = joint.axis;
+							var transforms = jointData.transforms;
+
+							matrix.identity();
+
+							// each update, we have to apply all transforms in the correct order
+
+							for ( var i = 0; i < transforms.length; i ++ ) {
+
+								var transform = transforms[ i ];
+
+								// if there is a connection of the transform node with a joint, apply the joint value
+
+								if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) {
+
+									switch ( joint.type ) {
+
+										case 'revolute':
+											matrix.multiply( m0.makeRotationAxis( axis, _Math.degToRad( value ) ) );
+											break;
+
+										case 'prismatic':
+											matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) );
+											break;
+
+										default:
+											console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type );
+											break;
+
+									}
+
+								} else {
+
+									switch ( transform.type ) {
+
+										case 'matrix':
+											matrix.multiply( transform.obj );
+											break;
+
+										case 'translate':
+											matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) );
+											break;
+
+										case 'scale':
+											matrix.scale( transform.obj );
+											break;
+
+										case 'rotate':
+											matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) );
+											break;
+
+									}
+
+								}
+
+							}
+
+							object.matrix.copy( matrix );
+							object.matrix.decompose( object.position, object.quaternion, object.scale );
+
+							jointMap[ jointIndex ].position = value;
+
+						}
+
+					} else {
+
+						console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' );
+
+					}
+
+				}
+
+			};
+
+		}
+
+		function buildTransformList( node ) {
+
+			var transforms = [];
+
+			var xml = collada.querySelector( '[id="' + node.id + '"]' );
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'matrix':
+						var array = parseFloats( child.textContent );
+						var matrix = new Matrix4().fromArray( array ).transpose();
+						transforms.push( {
+							sid: child.getAttribute( 'sid' ),
+							type: child.nodeName,
+							obj: matrix
+						} );
+						break;
+
+					case 'translate':
+					case 'scale':
+						var array = parseFloats( child.textContent );
+						var vector = new Vector3().fromArray( array );
+						transforms.push( {
+							sid: child.getAttribute( 'sid' ),
+							type: child.nodeName,
+							obj: vector
+						} );
+						break;
+
+					case 'rotate':
+						var array = parseFloats( child.textContent );
+						var vector = new Vector3().fromArray( array );
+						var angle = _Math.degToRad( array[ 3 ] );
+						transforms.push( {
+							sid: child.getAttribute( 'sid' ),
+							type: child.nodeName,
+							obj: vector,
+							angle: angle
+						} );
+						break;
+
+				}
+
+			}
+
+			return transforms;
+
+		}
+
+		// nodes
+
+		function prepareNodes( xml ) {
+
+			var elements = xml.getElementsByTagName( 'node' );
+
+			// ensure all node elements have id attributes
+
+			for ( var i = 0; i < elements.length; i ++ ) {
+
+				var element = elements[ i ];
+
+				if ( element.hasAttribute( 'id' ) === false ) {
+
+					element.setAttribute( 'id', generateId() );
+
+				}
+
+			}
+
+		}
+
+		var matrix = new Matrix4();
+		var vector = new Vector3();
+
+		function parseNode( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' ) || '',
+				type: xml.getAttribute( 'type' ),
+				id: xml.getAttribute( 'id' ),
+				sid: xml.getAttribute( 'sid' ),
+				matrix: new Matrix4(),
+				nodes: [],
+				instanceCameras: [],
+				instanceControllers: [],
+				instanceLights: [],
+				instanceGeometries: [],
+				instanceNodes: [],
+				transforms: {}
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				if ( child.nodeType !== 1 ) continue;
+
+				switch ( child.nodeName ) {
+
+					case 'node':
+						data.nodes.push( child.getAttribute( 'id' ) );
+						parseNode( child );
+						break;
+
+					case 'instance_camera':
+						data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) );
+						break;
+
+					case 'instance_controller':
+						data.instanceControllers.push( parseNodeInstance( child ) );
+						break;
+
+					case 'instance_light':
+						data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) );
+						break;
+
+					case 'instance_geometry':
+						data.instanceGeometries.push( parseNodeInstance( child ) );
+						break;
+
+					case 'instance_node':
+						data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) );
+						break;
+
+					case 'matrix':
+						var array = parseFloats( child.textContent );
+						data.matrix.multiply( matrix.fromArray( array ).transpose() );
+						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+						break;
+
+					case 'translate':
+						var array = parseFloats( child.textContent );
+						vector.fromArray( array );
+						data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) );
+						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+						break;
+
+					case 'rotate':
+						var array = parseFloats( child.textContent );
+						var angle = _Math.degToRad( array[ 3 ] );
+						data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) );
+						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+						break;
+
+					case 'scale':
+						var array = parseFloats( child.textContent );
+						data.matrix.scale( vector.fromArray( array ) );
+						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+						break;
+
+					case 'extra':
+						break;
+
+					default:
+						console.log( child );
+
+				}
+
+			}
+
+			if ( hasNode( data.id ) ) {
+
+				console.warn( 'THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id );
+
+			} else {
+
+				library.nodes[ data.id ] = data;
+
+			}
+
+			return data;
+
+		}
+
+		function parseNodeInstance( xml ) {
+
+			var data = {
+				id: parseId( xml.getAttribute( 'url' ) ),
+				materials: {},
+				skeletons: []
+			};
+
+			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+
+				var child = xml.childNodes[ i ];
+
+				switch ( child.nodeName ) {
+
+					case 'bind_material':
+						var instances = child.getElementsByTagName( 'instance_material' );
+
+						for ( var j = 0; j < instances.length; j ++ ) {
+
+							var instance = instances[ j ];
+							var symbol = instance.getAttribute( 'symbol' );
+							var target = instance.getAttribute( 'target' );
+
+							data.materials[ symbol ] = parseId( target );
+
+						}
+
+						break;
+
+					case 'skeleton':
+						data.skeletons.push( parseId( child.textContent ) );
+						break;
+
+					default:
+						break;
+
+				}
+
+			}
+
+			return data;
+
+		}
+
+		function buildSkeleton( skeletons, joints ) {
+
+			var boneData = [];
+			var sortedBoneData = [];
+
+			var i, j, data;
+
+			// a skeleton can have multiple root bones. collada expresses this
+			// situtation with multiple "skeleton" tags per controller instance
+
+			for ( i = 0; i < skeletons.length; i ++ ) {
+
+				var skeleton = skeletons[ i ];
+
+				var root;
+
+				if ( hasNode( skeleton ) ) {
+
+					root = getNode( skeleton );
+					buildBoneHierarchy( root, joints, boneData );
+
+				} else if ( hasVisualScene( skeleton ) ) {
+
+					// handle case where the skeleton refers to the visual scene (#13335)
+
+					var visualScene = library.visualScenes[ skeleton ];
+					var children = visualScene.children;
+
+					for ( var j = 0; j < children.length; j ++ ) {
+
+						var child = children[ j ];
+
+						if ( child.type === 'JOINT' ) {
+
+							var root = getNode( child.id );
+							buildBoneHierarchy( root, joints, boneData );
+
+						}
+
+					}
+
+				} else {
+
+					console.error( 'THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton );
+
+				}
+
+			}
+
+			// sort bone data (the order is defined in the corresponding controller)
+
+			for ( i = 0; i < joints.length; i ++ ) {
+
+				for ( j = 0; j < boneData.length; j ++ ) {
+
+					data = boneData[ j ];
+
+					if ( data.bone.name === joints[ i ].name ) {
+
+						sortedBoneData[ i ] = data;
+						data.processed = true;
+						break;
+
+					}
+
+				}
+
+			}
+
+			// add unprocessed bone data at the end of the list
+
+			for ( i = 0; i < boneData.length; i ++ ) {
+
+				data = boneData[ i ];
+
+				if ( data.processed === false ) {
+
+					sortedBoneData.push( data );
+					data.processed = true;
+
+				}
+
+			}
+
+			// setup arrays for skeleton creation
+
+			var bones = [];
+			var boneInverses = [];
+
+			for ( i = 0; i < sortedBoneData.length; i ++ ) {
+
+				data = sortedBoneData[ i ];
+
+				bones.push( data.bone );
+				boneInverses.push( data.boneInverse );
+
+			}
+
+			return new Skeleton( bones, boneInverses );
+
+		}
+
+		function buildBoneHierarchy( root, joints, boneData ) {
+
+			// setup bone data from visual scene
+
+			root.traverse( function ( object ) {
+
+				if ( object.isBone === true ) {
+
+					var boneInverse;
+
+					// retrieve the boneInverse from the controller data
+
+					for ( var i = 0; i < joints.length; i ++ ) {
+
+						var joint = joints[ i ];
+
+						if ( joint.name === object.name ) {
+
+							boneInverse = joint.boneInverse;
+							break;
+
+						}
+
+					}
+
+					if ( boneInverse === undefined ) {
+
+						// Unfortunately, there can be joints in the visual scene that are not part of the
+						// corresponding controller. In this case, we have to create a dummy boneInverse matrix
+						// for the respective bone. This bone won't affect any vertices, because there are no skin indices
+						// and weights defined for it. But we still have to add the bone to the sorted bone list in order to
+						// ensure a correct animation of the model.
+
+						boneInverse = new Matrix4();
+
+					}
+
+					boneData.push( { bone: object, boneInverse: boneInverse, processed: false } );
+
+				}
+
+			} );
+
+		}
+
+		function buildNode( data ) {
+
+			var objects = [];
+
+			var matrix = data.matrix;
+			var nodes = data.nodes;
+			var type = data.type;
+			var instanceCameras = data.instanceCameras;
+			var instanceControllers = data.instanceControllers;
+			var instanceLights = data.instanceLights;
+			var instanceGeometries = data.instanceGeometries;
+			var instanceNodes = data.instanceNodes;
+
+			// nodes
+
+			for ( var i = 0, l = nodes.length; i < l; i ++ ) {
+
+				objects.push( getNode( nodes[ i ] ) );
+
+			}
+
+			// instance cameras
+
+			for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) {
+
+				var instanceCamera = getCamera( instanceCameras[ i ] );
+
+				if ( instanceCamera !== null ) {
+
+					objects.push( instanceCamera.clone() );
+
+				}
+
+			}
+
+			// instance controllers
+
+			for ( var i = 0, l = instanceControllers.length; i < l; i ++ ) {
+
+				var instance = instanceControllers[ i ];
+				var controller = getController( instance.id );
+				var geometries = getGeometry( controller.id );
+				var newObjects = buildObjects( geometries, instance.materials );
+
+				var skeletons = instance.skeletons;
+				var joints = controller.skin.joints;
+
+				var skeleton = buildSkeleton( skeletons, joints );
+
+				for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
+
+					var object = newObjects[ j ];
+
+					if ( object.isSkinnedMesh ) {
+
+						object.bind( skeleton, controller.skin.bindMatrix );
+						object.normalizeSkinWeights();
+
+					}
+
+					objects.push( object );
+
+				}
+
+			}
+
+			// instance lights
+
+			for ( var i = 0, l = instanceLights.length; i < l; i ++ ) {
+
+				var instanceLight = getLight( instanceLights[ i ] );
+
+				if ( instanceLight !== null ) {
+
+					objects.push( instanceLight.clone() );
+
+				}
+
+			}
+
+			// instance geometries
+
+			for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) {
+
+				var instance = instanceGeometries[ i ];
+
+				// a single geometry instance in collada can lead to multiple object3Ds.
+				// this is the case when primitives are combined like triangles and lines
+
+				var geometries = getGeometry( instance.id );
+				var newObjects = buildObjects( geometries, instance.materials );
+
+				for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
+
+					objects.push( newObjects[ j ] );
+
+				}
+
+			}
+
+			// instance nodes
+
+			for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) {
+
+				objects.push( getNode( instanceNodes[ i ] ).clone() );
+
+			}
+
+			var object;
+
+			if ( nodes.length === 0 && objects.length === 1 ) {
+
+				object = objects[ 0 ];
+
+			} else {
+
+				object = ( type === 'JOINT' ) ? new Bone() : new Group();
+
+				for ( var i = 0; i < objects.length; i ++ ) {
+
+					object.add( objects[ i ] );
+
+				}
+
+			}
+
+			if ( object.name === '' ) {
+
+				object.name = ( type === 'JOINT' ) ? data.sid : data.name;
+
+			}
+
+			object.matrix.copy( matrix );
+			object.matrix.decompose( object.position, object.quaternion, object.scale );
+
+			return object;
+
+		}
+
+		var fallbackMaterial = new MeshBasicMaterial( { color: 0xff00ff } );
+
+		function resolveMaterialBinding( keys, instanceMaterials ) {
+
+			var materials = [];
+
+			for ( var i = 0, l = keys.length; i < l; i ++ ) {
+
+				var id = instanceMaterials[ keys[ i ] ];
+
+				if ( id === undefined ) {
+
+					console.warn( 'THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[ i ] );
+					materials.push( fallbackMaterial );
+
+				} else {
+
+					materials.push( getMaterial( id ) );
+
+				}
+
+			}
+
+			return materials;
+
+		}
+
+		function buildObjects( geometries, instanceMaterials ) {
+
+			var objects = [];
+
+			for ( var type in geometries ) {
+
+				var geometry = geometries[ type ];
+
+				var materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials );
+
+				// handle case if no materials are defined
+
+				if ( materials.length === 0 ) {
+
+					if ( type === 'lines' || type === 'linestrips' ) {
+
+						materials.push( new LineBasicMaterial() );
+
+					} else {
+
+						materials.push( new MeshPhongMaterial() );
+
+					}
+
+				}
+
+				// regard skinning
+
+				var skinning = ( geometry.data.attributes.skinIndex !== undefined );
+
+				if ( skinning ) {
+
+					for ( var i = 0, l = materials.length; i < l; i ++ ) {
+
+						materials[ i ].skinning = true;
+
+					}
+
+				}
+
+				// choose between a single or multi materials (material array)
+
+				var material = ( materials.length === 1 ) ? materials[ 0 ] : materials;
+
+				// now create a specific 3D object
+
+				var object;
+
+				switch ( type ) {
+
+					case 'lines':
+						object = new LineSegments( geometry.data, material );
+						break;
+
+					case 'linestrips':
+						object = new Line( geometry.data, material );
+						break;
+
+					case 'triangles':
+					case 'polylist':
+						if ( skinning ) {
+
+							object = new SkinnedMesh( geometry.data, material );
+
+						} else {
+
+							object = new Mesh( geometry.data, material );
+
+						}
+						break;
+
+				}
+
+				objects.push( object );
+
+			}
+
+			return objects;
+
+		}
+
+		function hasNode( id ) {
+
+			return library.nodes[ id ] !== undefined;
+
+		}
+
+		function getNode( id ) {
+
+			return getBuild( library.nodes[ id ], buildNode );
+
+		}
+
+		// visual scenes
+
+		function parseVisualScene( xml ) {
+
+			var data = {
+				name: xml.getAttribute( 'name' ),
+				children: []
+			};
+
+			prepareNodes( xml );
+
+			var elements = getElementsByTagName( xml, 'node' );
+
+			for ( var i = 0; i < elements.length; i ++ ) {
+
+				data.children.push( parseNode( elements[ i ] ) );
+
+			}
+
+			library.visualScenes[ xml.getAttribute( 'id' ) ] = data;
+
+		}
+
+		function buildVisualScene( data ) {
+
+			var group = new Group();
+			group.name = data.name;
+
+			var children = data.children;
+
+			for ( var i = 0; i < children.length; i ++ ) {
+
+				var child = children[ i ];
+
+				group.add( getNode( child.id ) );
+
+			}
+
+			return group;
+
+		}
+
+		function hasVisualScene( id ) {
+
+			return library.visualScenes[ id ] !== undefined;
+
+		}
+
+		function getVisualScene( id ) {
+
+			return getBuild( library.visualScenes[ id ], buildVisualScene );
+
+		}
+
+		// scenes
+
+		function parseScene( xml ) {
+
+			var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ];
+			return getVisualScene( parseId( instance.getAttribute( 'url' ) ) );
+
+		}
+
+		function setupAnimations() {
+
+			var clips = library.clips;
+
+			if ( isEmpty( clips ) === true ) {
+
+				if ( isEmpty( library.animations ) === false ) {
+
+					// if there are animations but no clips, we create a default clip for playback
+
+					var tracks = [];
+
+					for ( var id in library.animations ) {
+
+						var animationTracks = getAnimation( id );
+
+						for ( var i = 0, l = animationTracks.length; i < l; i ++ ) {
+
+							tracks.push( animationTracks[ i ] );
+
+						}
+
+					}
+
+					animations.push( new AnimationClip( 'default', - 1, tracks ) );
+
+				}
+
+			} else {
+
+				for ( var id in clips ) {
+
+					animations.push( getAnimationClip( id ) );
+
+				}
+
+			}
+
+		}
+
+		// convert the parser error element into text with each child elements text
+		// separated by new lines.
+
+		function parserErrorToText( parserError ) {
+
+			var result = '';
+			var stack = [ parserError ];
+
+			while ( stack.length ) {
+
+				var node = stack.shift();
+
+				if ( node.nodeType === Node.TEXT_NODE ) {
+
+					result += node.textContent;
+
+				} else {
+
+					result += '\n';
+					stack.push.apply( stack, node.childNodes );
+
+				}
+
+			}
+
+			return result.trim();
+
+		}
+
+		if ( text.length === 0 ) {
+
+			return { scene: new Scene() };
+
+		}
+
+		var xml = new DOMParser().parseFromString( text, 'application/xml' );
+
+		var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
+
+		var parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ];
+		if ( parserError !== undefined ) {
+
+			// Chrome will return parser error with a div in it
+
+			var errorElement = getElementsByTagName( parserError, 'div' )[ 0 ];
+			var errorText;
+
+			if ( errorElement ) {
+
+				errorText = errorElement.textContent;
+
+			} else {
+
+				errorText = parserErrorToText( parserError );
+
+			}
+
+			console.error( 'THREE.ColladaLoader: Failed to parse collada file.\n', errorText );
+
+			return null;
+
+		}
+
+		// metadata
+
+		var version = collada.getAttribute( 'version' );
+		console.log( 'THREE.ColladaLoader: File version', version );
+
+		var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] );
+		var textureLoader = new TextureLoader( this.manager );
+		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+
+		var tgaLoader;
+
+		if ( TGALoader ) {
+
+			tgaLoader = new TGALoader( this.manager );
+			tgaLoader.setPath( this.resourcePath || path );
+
+		}
+
+		//
+
+		var animations = [];
+		var kinematics = {};
+		var count = 0;
+
+		//
+
+		var library = {
+			animations: {},
+			clips: {},
+			controllers: {},
+			images: {},
+			effects: {},
+			materials: {},
+			cameras: {},
+			lights: {},
+			geometries: {},
+			nodes: {},
+			visualScenes: {},
+			kinematicsModels: {},
+			physicsModels: {},
+			kinematicsScenes: {}
+		};
+
+		parseLibrary( collada, 'library_animations', 'animation', parseAnimation );
+		parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip );
+		parseLibrary( collada, 'library_controllers', 'controller', parseController );
+		parseLibrary( collada, 'library_images', 'image', parseImage );
+		parseLibrary( collada, 'library_effects', 'effect', parseEffect );
+		parseLibrary( collada, 'library_materials', 'material', parseMaterial );
+		parseLibrary( collada, 'library_cameras', 'camera', parseCamera );
+		parseLibrary( collada, 'library_lights', 'light', parseLight );
+		parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry );
+		parseLibrary( collada, 'library_nodes', 'node', parseNode );
+		parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene );
+		parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel );
+		parseLibrary( collada, 'library_physics_models', 'physics_model', parsePhysicsModel );
+		parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene );
+
+		buildLibrary( library.animations, buildAnimation );
+		buildLibrary( library.clips, buildAnimationClip );
+		buildLibrary( library.controllers, buildController );
+		buildLibrary( library.images, buildImage );
+		buildLibrary( library.effects, buildEffect );
+		buildLibrary( library.materials, buildMaterial );
+		buildLibrary( library.cameras, buildCamera );
+		buildLibrary( library.lights, buildLight );
+		buildLibrary( library.geometries, buildGeometry );
+		buildLibrary( library.visualScenes, buildVisualScene );
+
+		setupAnimations();
+		setupKinematics();
+
+		var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] );
+
+		if ( asset.upAxis === 'Z_UP' ) {
+
+			scene.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) );
+
+		}
+
+		scene.scale.multiplyScalar( asset.unit );
+
+		return {
+			animations: animations,
+			kinematics: kinematics,
+			library: library,
+			scene: scene
+		};
+
+	}
+
+};
+
+export { ColladaLoader };

+ 22 - 0
examples/jsm/loaders/DDSLoader.d.ts

@@ -0,0 +1,22 @@
+import {
+  LoadingManager,
+  CompressedTextureLoader,
+  PixelFormat,
+  CompressedPixelFormat
+} from '../../../src/Three';
+
+export interface DDS {
+  mipmaps: object[];
+  width: number;
+  height: number;
+  format: PixelFormat | CompressedPixelFormat;
+  mipmapCount: number;
+  isCubemap: boolean;
+}
+
+export class DDSLoader extends CompressedTextureLoader {
+  constructor(manager?: LoadingManager);
+
+  parse(buffer: ArrayBuffer, loadMipmaps: boolean) : DDS;
+  _parser(buffer: ArrayBuffer, loadMipmaps: boolean) : DDS;
+}

+ 283 - 0
examples/jsm/loaders/DDSLoader.js

@@ -0,0 +1,283 @@
+/*
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+import {
+	CompressedTextureLoader,
+	RGBAFormat,
+	RGBA_S3TC_DXT3_Format,
+	RGBA_S3TC_DXT5_Format,
+	RGB_ETC1_Format,
+	RGB_S3TC_DXT1_Format
+} from "../../../build/three.module.js";
+
+var DDSLoader = function ( manager ) {
+
+	CompressedTextureLoader.call( this, manager );
+
+	this._parser = DDSLoader.parse;
+
+};
+
+DDSLoader.prototype = Object.create( CompressedTextureLoader.prototype );
+DDSLoader.prototype.constructor = DDSLoader;
+
+DDSLoader.parse = function ( buffer, loadMipmaps ) {
+
+	var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
+
+	// Adapted from @toji's DDS utils
+	// https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
+
+	// All values and structures referenced from:
+	// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
+
+	var DDS_MAGIC = 0x20534444;
+
+	var DDSD_CAPS = 0x1,
+		DDSD_HEIGHT = 0x2,
+		DDSD_WIDTH = 0x4,
+		DDSD_PITCH = 0x8,
+		DDSD_PIXELFORMAT = 0x1000,
+		DDSD_MIPMAPCOUNT = 0x20000,
+		DDSD_LINEARSIZE = 0x80000,
+		DDSD_DEPTH = 0x800000;
+
+	var DDSCAPS_COMPLEX = 0x8,
+		DDSCAPS_MIPMAP = 0x400000,
+		DDSCAPS_TEXTURE = 0x1000;
+
+	var DDSCAPS2_CUBEMAP = 0x200,
+		DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
+		DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
+		DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
+		DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
+		DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
+		DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
+		DDSCAPS2_VOLUME = 0x200000;
+
+	var DDPF_ALPHAPIXELS = 0x1,
+		DDPF_ALPHA = 0x2,
+		DDPF_FOURCC = 0x4,
+		DDPF_RGB = 0x40,
+		DDPF_YUV = 0x200,
+		DDPF_LUMINANCE = 0x20000;
+
+	function fourCCToInt32( value ) {
+
+		return value.charCodeAt( 0 ) +
+			( value.charCodeAt( 1 ) << 8 ) +
+			( value.charCodeAt( 2 ) << 16 ) +
+			( value.charCodeAt( 3 ) << 24 );
+
+	}
+
+	function int32ToFourCC( value ) {
+
+		return String.fromCharCode(
+			value & 0xff,
+			( value >> 8 ) & 0xff,
+			( value >> 16 ) & 0xff,
+			( value >> 24 ) & 0xff
+		);
+
+	}
+
+	function loadARGBMip( buffer, dataOffset, width, height ) {
+
+		var dataLength = width * height * 4;
+		var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
+		var byteArray = new Uint8Array( dataLength );
+		var dst = 0;
+		var src = 0;
+		for ( var y = 0; y < height; y ++ ) {
+
+			for ( var x = 0; x < width; x ++ ) {
+
+				var b = srcBuffer[ src ]; src ++;
+				var g = srcBuffer[ src ]; src ++;
+				var r = srcBuffer[ src ]; src ++;
+				var a = srcBuffer[ src ]; src ++;
+				byteArray[ dst ] = r; dst ++;	//r
+				byteArray[ dst ] = g; dst ++;	//g
+				byteArray[ dst ] = b; dst ++;	//b
+				byteArray[ dst ] = a; dst ++;	//a
+
+			}
+
+		}
+		return byteArray;
+
+	}
+
+	var FOURCC_DXT1 = fourCCToInt32( "DXT1" );
+	var FOURCC_DXT3 = fourCCToInt32( "DXT3" );
+	var FOURCC_DXT5 = fourCCToInt32( "DXT5" );
+	var FOURCC_ETC1 = fourCCToInt32( "ETC1" );
+
+	var headerLengthInt = 31; // The header length in 32 bit ints
+
+	// Offsets into the header array
+
+	var off_magic = 0;
+
+	var off_size = 1;
+	var off_flags = 2;
+	var off_height = 3;
+	var off_width = 4;
+
+	var off_mipmapCount = 7;
+
+	var off_pfFlags = 20;
+	var off_pfFourCC = 21;
+	var off_RGBBitCount = 22;
+	var off_RBitMask = 23;
+	var off_GBitMask = 24;
+	var off_BBitMask = 25;
+	var off_ABitMask = 26;
+
+	var off_caps = 27;
+	var off_caps2 = 28;
+	var off_caps3 = 29;
+	var off_caps4 = 30;
+
+	// Parse header
+
+	var header = new Int32Array( buffer, 0, headerLengthInt );
+
+	if ( header[ off_magic ] !== DDS_MAGIC ) {
+
+		console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' );
+		return dds;
+
+	}
+
+	if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
+
+		console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' );
+		return dds;
+
+	}
+
+	var blockBytes;
+
+	var fourCC = header[ off_pfFourCC ];
+
+	var isRGBAUncompressed = false;
+
+	switch ( fourCC ) {
+
+		case FOURCC_DXT1:
+
+			blockBytes = 8;
+			dds.format = RGB_S3TC_DXT1_Format;
+			break;
+
+		case FOURCC_DXT3:
+
+			blockBytes = 16;
+			dds.format = RGBA_S3TC_DXT3_Format;
+			break;
+
+		case FOURCC_DXT5:
+
+			blockBytes = 16;
+			dds.format = RGBA_S3TC_DXT5_Format;
+			break;
+
+		case FOURCC_ETC1:
+
+			blockBytes = 8;
+			dds.format = RGB_ETC1_Format;
+			break;
+
+		default:
+
+			if ( header[ off_RGBBitCount ] === 32
+				&& header[ off_RBitMask ] & 0xff0000
+				&& header[ off_GBitMask ] & 0xff00
+				&& header[ off_BBitMask ] & 0xff
+				&& header[ off_ABitMask ] & 0xff000000 ) {
+
+				isRGBAUncompressed = true;
+				blockBytes = 64;
+				dds.format = RGBAFormat;
+
+			} else {
+
+				console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) );
+				return dds;
+
+			}
+
+	}
+
+	dds.mipmapCount = 1;
+
+	if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
+
+		dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
+
+	}
+
+	var caps2 = header[ off_caps2 ];
+	dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false;
+	if ( dds.isCubemap && (
+		! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) ||
+		! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) ||
+		! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) ||
+		! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) ||
+		! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) ||
+		! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ )
+	) ) {
+
+		console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' );
+		return dds;
+
+	}
+
+	dds.width = header[ off_width ];
+	dds.height = header[ off_height ];
+
+	var dataOffset = header[ off_size ] + 4;
+
+	// Extract mipmaps buffers
+
+	var faces = dds.isCubemap ? 6 : 1;
+
+	for ( var face = 0; face < faces; face ++ ) {
+
+		var width = dds.width;
+		var height = dds.height;
+
+		for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+			if ( isRGBAUncompressed ) {
+
+				var byteArray = loadARGBMip( buffer, dataOffset, width, height );
+				var dataLength = byteArray.length;
+
+			} else {
+
+				var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
+				var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
+
+			}
+
+			var mipmap = { "data": byteArray, "width": width, "height": height };
+			dds.mipmaps.push( mipmap );
+
+			dataOffset += dataLength;
+
+			width = Math.max( width >> 1, 1 );
+			height = Math.max( height >> 1, 1 );
+
+		}
+
+	}
+
+	return dds;
+
+};
+
+export { DDSLoader };

+ 21 - 0
examples/jsm/loaders/EXRLoader.d.ts

@@ -0,0 +1,21 @@
+import {
+  LoadingManager,
+  DataTextureLoader,
+  TextureDataType,
+  PixelFormat
+} from '../../../src/Three';
+
+export interface EXR {
+  header: object;
+  width: number;
+  height: number;
+  data: Float32Array;
+  format: PixelFormat;
+  type: TextureDataType;
+}
+
+export class EXRLoader extends DataTextureLoader {
+  constructor(manager?: LoadingManager);
+
+  _parser(buffer: ArrayBuffer) : EXR;
+}

+ 1203 - 0
examples/jsm/loaders/EXRLoader.js

@@ -0,0 +1,1203 @@
+/**
+ * @author Richard M. / https://github.com/richardmonette
+ *
+ * OpenEXR loader which, currently, supports reading 16 bit half data, in either
+ * uncompressed or PIZ wavelet compressed form.
+ *
+ * Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita
+ * implementation, so I have preserved their copyright notices.
+ */
+
+import {
+	DataTextureLoader,
+	DefaultLoadingManager,
+	FloatType,
+	RGBAFormat,
+	RGBFormat
+} from "../../../build/three.module.js";
+
+// /*
+// Copyright (c) 2014 - 2017, Syoyo Fujita
+// All rights reserved.
+
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//     * Neither the name of the Syoyo Fujita nor the
+//       names of its contributors may be used to endorse or promote products
+//       derived from this software without specific prior written permission.
+
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// */
+
+// // TinyEXR contains some OpenEXR code, which is licensed under ------------
+
+// ///////////////////////////////////////////////////////////////////////////
+// //
+// // Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
+// // Digital Ltd. LLC
+// //
+// // All rights reserved.
+// //
+// // Redistribution and use in source and binary forms, with or without
+// // modification, are permitted provided that the following conditions are
+// // met:
+// // *       Redistributions of source code must retain the above copyright
+// // notice, this list of conditions and the following disclaimer.
+// // *       Redistributions in binary form must reproduce the above
+// // copyright notice, this list of conditions and the following disclaimer
+// // in the documentation and/or other materials provided with the
+// // distribution.
+// // *       Neither the name of Industrial Light & Magic nor the names of
+// // its contributors may be used to endorse or promote products derived
+// // from this software without specific prior written permission.
+// //
+// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// //
+// ///////////////////////////////////////////////////////////////////////////
+
+// // End of OpenEXR license -------------------------------------------------
+
+var EXRLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+EXRLoader.prototype = Object.create( DataTextureLoader.prototype );
+
+EXRLoader.prototype._parser = function ( buffer ) {
+
+	const USHORT_RANGE = ( 1 << 16 );
+	const BITMAP_SIZE = ( USHORT_RANGE >> 3 );
+
+	const HUF_ENCBITS = 16; // literal (value) bit length
+	const HUF_DECBITS = 14; // decoding bit size (>= 8)
+
+	const HUF_ENCSIZE = ( 1 << HUF_ENCBITS ) + 1; // encoding table size
+	const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
+	const HUF_DECMASK = HUF_DECSIZE - 1;
+
+	const SHORT_ZEROCODE_RUN = 59;
+	const LONG_ZEROCODE_RUN = 63;
+	const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
+	const LONGEST_LONG_RUN = 255 + SHORTEST_LONG_RUN;
+
+	const BYTES_PER_HALF = 2;
+
+	const ULONG_SIZE = 8;
+	const FLOAT32_SIZE = 4;
+	const INT32_SIZE = 4;
+	const INT16_SIZE = 2;
+	const INT8_SIZE = 1;
+
+	function reverseLutFromBitmap( bitmap, lut ) {
+
+		var k = 0;
+
+		for ( var i = 0; i < USHORT_RANGE; ++ i ) {
+
+			if ( ( i == 0 ) || ( bitmap[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) ) {
+
+				lut[ k ++ ] = i;
+
+			}
+
+		}
+
+		var n = k - 1;
+
+		while ( k < USHORT_RANGE ) lut[ k ++ ] = 0;
+
+		return n;
+
+	}
+
+	function hufClearDecTable( hdec ) {
+
+		for ( var i = 0; i < HUF_DECSIZE; i ++ ) {
+
+			hdec[ i ] = {};
+			hdec[ i ].len = 0;
+			hdec[ i ].lit = 0;
+			hdec[ i ].p = null;
+
+		}
+
+	}
+
+	const getBitsReturn = { l: 0, c: 0, lc: 0 };
+
+	function getBits( nBits, c, lc, uInt8Array, inOffset ) {
+
+		while ( lc < nBits ) {
+
+			c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset );
+			lc += 8;
+
+		}
+
+		lc -= nBits;
+
+		getBitsReturn.l = ( c >> lc ) & ( ( 1 << nBits ) - 1 );
+		getBitsReturn.c = c;
+		getBitsReturn.lc = lc;
+
+	}
+
+	const hufTableBuffer = new Array( 59 );
+
+	function hufCanonicalCodeTable( hcode ) {
+
+		for ( var i = 0; i <= 58; ++ i ) hufTableBuffer[ i ] = 0;
+		for ( var i = 0; i < HUF_ENCSIZE; ++ i ) hufTableBuffer[ hcode[ i ] ] += 1;
+
+		var c = 0;
+
+		for ( var i = 58; i > 0; -- i ) {
+
+			var nc = ( ( c + hufTableBuffer[ i ] ) >> 1 );
+			hufTableBuffer[ i ] = c;
+			c = nc;
+
+		}
+
+		for ( var i = 0; i < HUF_ENCSIZE; ++ i ) {
+
+			var l = hcode[ i ];
+			if ( l > 0 ) hcode[ i ] = l | ( hufTableBuffer[ l ] ++ << 6 );
+
+		}
+
+	}
+
+	function hufUnpackEncTable( uInt8Array, inDataView, inOffset, ni, im, iM, hcode ) {
+
+		var p = inOffset;
+		var c = 0;
+		var lc = 0;
+
+		for ( ; im <= iM; im ++ ) {
+
+			if ( p.value - inOffset.value > ni ) return false;
+
+			getBits( 6, c, lc, uInt8Array, p );
+
+			var l = getBitsReturn.l;
+			c = getBitsReturn.c;
+			lc = getBitsReturn.lc;
+
+			hcode[ im ] = l;
+
+			if ( l == LONG_ZEROCODE_RUN ) {
+
+				if ( p.value - inOffset.value > ni ) {
+
+					throw 'Something wrong with hufUnpackEncTable';
+
+				}
+
+				getBits( 8, c, lc, uInt8Array, p );
+
+				var zerun = getBitsReturn.l + SHORTEST_LONG_RUN;
+				c = getBitsReturn.c;
+				lc = getBitsReturn.lc;
+
+				if ( im + zerun > iM + 1 ) {
+
+					throw 'Something wrong with hufUnpackEncTable';
+
+				}
+
+				while ( zerun -- ) hcode[ im ++ ] = 0;
+
+				im --;
+
+			} else if ( l >= SHORT_ZEROCODE_RUN ) {
+
+				var zerun = l - SHORT_ZEROCODE_RUN + 2;
+
+				if ( im + zerun > iM + 1 ) {
+
+					throw 'Something wrong with hufUnpackEncTable';
+
+				}
+
+				while ( zerun -- ) hcode[ im ++ ] = 0;
+
+				im --;
+
+			}
+
+		}
+
+		hufCanonicalCodeTable( hcode );
+
+	}
+
+	function hufLength( code ) {
+
+		return code & 63;
+
+	}
+
+	function hufCode( code ) {
+
+		return code >> 6;
+
+	}
+
+	function hufBuildDecTable( hcode, im, iM, hdecod ) {
+
+		for ( ; im <= iM; im ++ ) {
+
+			var c = hufCode( hcode[ im ] );
+			var l = hufLength( hcode[ im ] );
+
+			if ( c >> l ) {
+
+				throw 'Invalid table entry';
+
+			}
+
+			if ( l > HUF_DECBITS ) {
+
+				var pl = hdecod[ ( c >> ( l - HUF_DECBITS ) ) ];
+
+				if ( pl.len ) {
+
+					throw 'Invalid table entry';
+
+				}
+
+				pl.lit ++;
+
+				if ( pl.p ) {
+
+					var p = pl.p;
+					pl.p = new Array( pl.lit );
+
+					for ( var i = 0; i < pl.lit - 1; ++ i ) {
+
+						pl.p[ i ] = p[ i ];
+
+					}
+
+				} else {
+
+					pl.p = new Array( 1 );
+
+				}
+
+				pl.p[ pl.lit - 1 ] = im;
+
+			} else if ( l ) {
+
+				var plOffset = 0;
+
+				for ( var i = 1 << ( HUF_DECBITS - l ); i > 0; i -- ) {
+
+					var pl = hdecod[ ( c << ( HUF_DECBITS - l ) ) + plOffset ];
+
+					if ( pl.len || pl.p ) {
+
+						throw 'Invalid table entry';
+
+					}
+
+					pl.len = l;
+					pl.lit = im;
+
+					plOffset ++;
+
+				}
+
+			}
+
+		}
+
+		return true;
+
+	}
+
+	const getCharReturn = { c: 0, lc: 0 };
+
+	function getChar( c, lc, uInt8Array, inOffset ) {
+
+		c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset );
+		lc += 8;
+
+		getCharReturn.c = c;
+		getCharReturn.lc = lc;
+
+	}
+
+	const getCodeReturn = { c: 0, lc: 0 };
+
+	function getCode( po, rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outBufferOffset, outBufferEndOffset ) {
+
+		if ( po == rlc ) {
+
+			if ( lc < 8 ) {
+
+				getChar( c, lc, uInt8Array, inOffset );
+				c = getCharReturn.c;
+				lc = getCharReturn.lc;
+
+			}
+
+			lc -= 8;
+
+			var cs = ( c >> lc );
+			var cs = new Uint8Array( [ cs ] )[ 0 ];
+
+			if ( outBufferOffset.value + cs > outBufferEndOffset ) {
+
+				return false;
+
+			}
+
+			var s = outBuffer[ outBufferOffset.value - 1 ];
+
+			while ( cs -- > 0 ) {
+
+				outBuffer[ outBufferOffset.value ++ ] = s;
+
+			}
+
+		} else if ( outBufferOffset.value < outBufferEndOffset ) {
+
+			outBuffer[ outBufferOffset.value ++ ] = po;
+
+		} else {
+
+			return false;
+
+		}
+
+		getCodeReturn.c = c;
+		getCodeReturn.lc = lc;
+
+	}
+
+	var NBITS = 16;
+	var A_OFFSET = 1 << ( NBITS - 1 );
+	var M_OFFSET = 1 << ( NBITS - 1 );
+	var MOD_MASK = ( 1 << NBITS ) - 1;
+
+	function UInt16( value ) {
+
+		return ( value & 0xFFFF );
+
+	}
+
+	function Int16( value ) {
+
+		var ref = UInt16( value );
+		return ( ref > 0x7FFF ) ? ref - 0x10000 : ref;
+
+	}
+
+	const wdec14Return = { a: 0, b: 0 };
+
+	function wdec14( l, h ) {
+
+		var ls = Int16( l );
+		var hs = Int16( h );
+
+		var hi = hs;
+		var ai = ls + ( hi & 1 ) + ( hi >> 1 );
+
+		var as = ai;
+		var bs = ai - hi;
+
+		wdec14Return.a = as;
+		wdec14Return.b = bs;
+
+	}
+
+	function wav2Decode( j, buffer, nx, ox, ny, oy, mx ) {
+
+		var n = ( nx > ny ) ? ny : nx;
+		var p = 1;
+		var p2;
+
+		while ( p <= n ) p <<= 1;
+
+		p >>= 1;
+		p2 = p;
+		p >>= 1;
+
+		while ( p >= 1 ) {
+
+			var py = 0;
+			var ey = py + oy * ( ny - p2 );
+			var oy1 = oy * p;
+			var oy2 = oy * p2;
+			var ox1 = ox * p;
+			var ox2 = ox * p2;
+			var i00, i01, i10, i11;
+
+			for ( ; py <= ey; py += oy2 ) {
+
+				var px = py;
+				var ex = py + ox * ( nx - p2 );
+
+				for ( ; px <= ex; px += ox2 ) {
+
+					var p01 = px + ox1;
+					var p10 = px + oy1;
+					var p11 = p10 + ox1;
+
+					wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+
+					i00 = wdec14Return.a;
+					i10 = wdec14Return.b;
+
+					wdec14( buffer[ p01 + j ], buffer[ p11 + j ] );
+
+					i01 = wdec14Return.a;
+					i11 = wdec14Return.b;
+
+					wdec14( i00, i01 );
+
+					buffer[ px + j ] = wdec14Return.a;
+					buffer[ p01 + j ] = wdec14Return.b;
+
+					wdec14( i10, i11 );
+
+					buffer[ p10 + j ] = wdec14Return.a;
+					buffer[ p11 + j ] = wdec14Return.b;
+
+				}
+
+				if ( nx & p ) {
+
+					var p10 = px + oy1;
+
+					wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+
+					i00 = wdec14Return.a;
+					buffer[ p10 + j ] = wdec14Return.b;
+
+					buffer[ px + j ] = i00;
+
+				}
+
+			}
+
+			if ( ny & p ) {
+
+				var px = py;
+				var ex = py + ox * ( nx - p2 );
+
+				for ( ; px <= ex; px += ox2 ) {
+
+					var p01 = px + ox1;
+
+					wdec14( buffer[ px + j ], buffer[ p01 + j ] );
+
+					i00 = wdec14Return.a;
+					buffer[ p01 + j ] = wdec14Return.b;
+
+					buffer[ px + j ] = i00;
+
+				}
+
+			}
+
+			p2 = p;
+			p >>= 1;
+
+		}
+
+		return py;
+
+	}
+
+	function hufDecode( encodingTable, decodingTable, uInt8Array, inDataView, inOffset, ni, rlc, no, outBuffer, outOffset ) {
+
+		var c = 0;
+		var lc = 0;
+		var outBufferEndOffset = no;
+		var inOffsetEnd = Math.trunc( inOffset.value + ( ni + 7 ) / 8 );
+
+		while ( inOffset.value < inOffsetEnd ) {
+
+			getChar( c, lc, uInt8Array, inOffset );
+
+			c = getCharReturn.c;
+			lc = getCharReturn.lc;
+
+			while ( lc >= HUF_DECBITS ) {
+
+				var index = ( c >> ( lc - HUF_DECBITS ) ) & HUF_DECMASK;
+				var pl = decodingTable[ index ];
+
+				if ( pl.len ) {
+
+					lc -= pl.len;
+
+					getCode( pl.lit, rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outOffset, outBufferEndOffset );
+
+					c = getCodeReturn.c;
+					lc = getCodeReturn.lc;
+
+				} else {
+
+					if ( ! pl.p ) {
+
+						throw 'hufDecode issues';
+
+					}
+
+					var j;
+
+					for ( j = 0; j < pl.lit; j ++ ) {
+
+						var l = hufLength( encodingTable[ pl.p[ j ] ] );
+
+						while ( lc < l && inOffset.value < inOffsetEnd ) {
+
+							getChar( c, lc, uInt8Array, inOffset );
+
+							c = getCharReturn.c;
+							lc = getCharReturn.lc;
+
+						}
+
+						if ( lc >= l ) {
+
+							if ( hufCode( encodingTable[ pl.p[ j ] ] ) == ( ( c >> ( lc - l ) ) & ( ( 1 << l ) - 1 ) ) ) {
+
+								lc -= l;
+
+								getCode( pl.p[ j ], rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outOffset, outBufferEndOffset );
+
+								c = getCodeReturn.c;
+								lc = getCodeReturn.lc;
+
+								break;
+
+							}
+
+						}
+
+					}
+
+					if ( j == pl.lit ) {
+
+						throw 'hufDecode issues';
+
+					}
+
+				}
+
+			}
+
+		}
+
+		var i = ( 8 - ni ) & 7;
+
+		c >>= i;
+		lc -= i;
+
+		while ( lc > 0 ) {
+
+			var pl = decodingTable[ ( c << ( HUF_DECBITS - lc ) ) & HUF_DECMASK ];
+
+			if ( pl.len ) {
+
+				lc -= pl.len;
+
+				getCode( pl.lit, rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outOffset, outBufferEndOffset );
+
+				c = getCodeReturn.c;
+				lc = getCodeReturn.lc;
+
+			} else {
+
+				throw 'hufDecode issues';
+
+			}
+
+		}
+
+		return true;
+
+	}
+
+	function hufUncompress( uInt8Array, inDataView, inOffset, nCompressed, outBuffer, outOffset, nRaw ) {
+
+		var initialInOffset = inOffset.value;
+
+		var im = parseUint32( inDataView, inOffset );
+		var iM = parseUint32( inDataView, inOffset );
+
+		inOffset.value += 4;
+
+		var nBits = parseUint32( inDataView, inOffset );
+
+		inOffset.value += 4;
+
+		if ( im < 0 || im >= HUF_ENCSIZE || iM < 0 || iM >= HUF_ENCSIZE ) {
+
+			throw 'Something wrong with HUF_ENCSIZE';
+
+		}
+
+		var freq = new Array( HUF_ENCSIZE );
+		var hdec = new Array( HUF_DECSIZE );
+
+		hufClearDecTable( hdec );
+
+		var ni = nCompressed - ( inOffset.value - initialInOffset );
+
+		hufUnpackEncTable( uInt8Array, inDataView, inOffset, ni, im, iM, freq );
+
+		if ( nBits > 8 * ( nCompressed - ( inOffset.value - initialInOffset ) ) ) {
+
+			throw 'Something wrong with hufUncompress';
+
+		}
+
+		hufBuildDecTable( freq, im, iM, hdec );
+
+		hufDecode( freq, hdec, uInt8Array, inDataView, inOffset, nBits, iM, nRaw, outBuffer, outOffset );
+
+	}
+
+	function applyLut( lut, data, nData ) {
+
+		for ( var i = 0; i < nData; ++ i ) {
+
+			data[ i ] = lut[ data[ i ] ];
+
+		}
+
+	}
+
+	function decompressPIZ( outBuffer, outOffset, uInt8Array, inDataView, inOffset, tmpBufSize, num_channels, exrChannelInfos, dataWidth, num_lines ) {
+
+		var bitmap = new Uint8Array( BITMAP_SIZE );
+
+		var minNonZero = parseUint16( inDataView, inOffset );
+		var maxNonZero = parseUint16( inDataView, inOffset );
+
+		if ( maxNonZero >= BITMAP_SIZE ) {
+
+			throw 'Something is wrong with PIZ_COMPRESSION BITMAP_SIZE';
+
+		}
+
+		if ( minNonZero <= maxNonZero ) {
+
+			for ( var i = 0; i < maxNonZero - minNonZero + 1; i ++ ) {
+
+				bitmap[ i + minNonZero ] = parseUint8( inDataView, inOffset );
+
+			}
+
+		}
+
+		var lut = new Uint16Array( USHORT_RANGE );
+		var maxValue = reverseLutFromBitmap( bitmap, lut );
+
+		var length = parseUint32( inDataView, inOffset );
+
+		hufUncompress( uInt8Array, inDataView, inOffset, length, outBuffer, outOffset, tmpBufSize );
+
+		var pizChannelData = new Array( num_channels );
+
+		var outBufferEnd = 0;
+
+		for ( var i = 0; i < num_channels; i ++ ) {
+
+			var exrChannelInfo = exrChannelInfos[ i ];
+
+			var pixelSize = 2; // assumes HALF_FLOAT
+
+			pizChannelData[ i ] = {};
+			pizChannelData[ i ][ 'start' ] = outBufferEnd;
+			pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ];
+			pizChannelData[ i ][ 'nx' ] = dataWidth;
+			pizChannelData[ i ][ 'ny' ] = num_lines;
+			pizChannelData[ i ][ 'size' ] = 1;
+
+			outBufferEnd += pizChannelData[ i ].nx * pizChannelData[ i ].ny * pizChannelData[ i ].size;
+
+		}
+
+		var fooOffset = 0;
+
+		for ( var i = 0; i < num_channels; i ++ ) {
+
+			for ( var j = 0; j < pizChannelData[ i ].size; ++ j ) {
+
+				fooOffset += wav2Decode(
+					j + fooOffset,
+					outBuffer,
+					pizChannelData[ i ].nx,
+					pizChannelData[ i ].size,
+					pizChannelData[ i ].ny,
+					pizChannelData[ i ].nx * pizChannelData[ i ].size,
+					maxValue
+				);
+
+			}
+
+		}
+
+		applyLut( lut, outBuffer, outBufferEnd );
+
+		return true;
+
+	}
+
+	function parseNullTerminatedString( buffer, offset ) {
+
+		var uintBuffer = new Uint8Array( buffer );
+		var endOffset = 0;
+
+		while ( uintBuffer[ offset.value + endOffset ] != 0 ) {
+
+			endOffset += 1;
+
+		}
+
+		var stringValue = new TextDecoder().decode(
+			uintBuffer.slice( offset.value, offset.value + endOffset )
+		);
+
+		offset.value = offset.value + endOffset + 1;
+
+		return stringValue;
+
+	}
+
+	function parseFixedLengthString( buffer, offset, size ) {
+
+		var stringValue = new TextDecoder().decode(
+			new Uint8Array( buffer ).slice( offset.value, offset.value + size )
+		);
+
+		offset.value = offset.value + size;
+
+		return stringValue;
+
+	}
+
+	function parseUlong( dataView, offset ) {
+
+		var uLong = dataView.getUint32( 0, true );
+
+		offset.value = offset.value + ULONG_SIZE;
+
+		return uLong;
+
+	}
+
+	function parseUint32( dataView, offset ) {
+
+		var Uint32 = dataView.getUint32( offset.value, true );
+
+		offset.value = offset.value + INT32_SIZE;
+
+		return Uint32;
+
+	}
+
+	function parseUint8Array( uInt8Array, offset ) {
+
+		var Uint8 = uInt8Array[ offset.value ];
+
+		offset.value = offset.value + INT8_SIZE;
+
+		return Uint8;
+
+	}
+
+	function parseUint8( dataView, offset ) {
+
+		var Uint8 = dataView.getUint8( offset.value );
+
+		offset.value = offset.value + INT8_SIZE;
+
+		return Uint8;
+
+	}
+
+	function parseFloat32( dataView, offset ) {
+
+		var float = dataView.getFloat32( offset.value, true );
+
+		offset.value += FLOAT32_SIZE;
+
+		return float;
+
+	}
+
+	// https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript
+	function decodeFloat16( binary ) {
+
+		var exponent = ( binary & 0x7C00 ) >> 10,
+			fraction = binary & 0x03FF;
+
+		return ( binary >> 15 ? - 1 : 1 ) * (
+			exponent ?
+				(
+					exponent === 0x1F ?
+						fraction ? NaN : Infinity :
+						Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 )
+				) :
+				6.103515625e-5 * ( fraction / 0x400 )
+		);
+
+	}
+
+	function parseUint16( dataView, offset ) {
+
+		var Uint16 = dataView.getUint16( offset.value, true );
+
+		offset.value += INT16_SIZE;
+
+		return Uint16;
+
+	}
+
+	function parseFloat16( buffer, offset ) {
+
+		return decodeFloat16( parseUint16( buffer, offset ) );
+
+	}
+
+	function parseChlist( dataView, buffer, offset, size ) {
+
+		var startOffset = offset.value;
+		var channels = [];
+
+		while ( offset.value < ( startOffset + size - 1 ) ) {
+
+			var name = parseNullTerminatedString( buffer, offset );
+			var pixelType = parseUint32( dataView, offset ); // TODO: Cast this to UINT, HALF or FLOAT
+			var pLinear = parseUint8( dataView, offset );
+			offset.value += 3; // reserved, three chars
+			var xSampling = parseUint32( dataView, offset );
+			var ySampling = parseUint32( dataView, offset );
+
+			channels.push( {
+				name: name,
+				pixelType: pixelType,
+				pLinear: pLinear,
+				xSampling: xSampling,
+				ySampling: ySampling
+			} );
+
+		}
+
+		offset.value += 1;
+
+		return channels;
+
+	}
+
+	function parseChromaticities( dataView, offset ) {
+
+		var redX = parseFloat32( dataView, offset );
+		var redY = parseFloat32( dataView, offset );
+		var greenX = parseFloat32( dataView, offset );
+		var greenY = parseFloat32( dataView, offset );
+		var blueX = parseFloat32( dataView, offset );
+		var blueY = parseFloat32( dataView, offset );
+		var whiteX = parseFloat32( dataView, offset );
+		var whiteY = parseFloat32( dataView, offset );
+
+		return { redX: redX, redY: redY, greenX: greenX, greenY: greenY, blueX: blueX, blueY: blueY, whiteX: whiteX, whiteY: whiteY };
+
+	}
+
+	function parseCompression( dataView, offset ) {
+
+		var compressionCodes = [
+			'NO_COMPRESSION',
+			'RLE_COMPRESSION',
+			'ZIPS_COMPRESSION',
+			'ZIP_COMPRESSION',
+			'PIZ_COMPRESSION',
+			'PXR24_COMPRESSION',
+			'B44_COMPRESSION',
+			'B44A_COMPRESSION',
+			'DWAA_COMPRESSION',
+			'DWAB_COMPRESSION'
+		];
+
+		var compression = parseUint8( dataView, offset );
+
+		return compressionCodes[ compression ];
+
+	}
+
+	function parseBox2i( dataView, offset ) {
+
+		var xMin = parseUint32( dataView, offset );
+		var yMin = parseUint32( dataView, offset );
+		var xMax = parseUint32( dataView, offset );
+		var yMax = parseUint32( dataView, offset );
+
+		return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax };
+
+	}
+
+	function parseLineOrder( dataView, offset ) {
+
+		var lineOrders = [
+			'INCREASING_Y'
+		];
+
+		var lineOrder = parseUint8( dataView, offset );
+
+		return lineOrders[ lineOrder ];
+
+	}
+
+	function parseV2f( dataView, offset ) {
+
+		var x = parseFloat32( dataView, offset );
+		var y = parseFloat32( dataView, offset );
+
+		return [ x, y ];
+
+	}
+
+	function parseValue( dataView, buffer, offset, type, size ) {
+
+		if ( type === 'string' || type === 'iccProfile' ) {
+
+			return parseFixedLengthString( buffer, offset, size );
+
+		} else if ( type === 'chlist' ) {
+
+			return parseChlist( dataView, buffer, offset, size );
+
+		} else if ( type === 'chromaticities' ) {
+
+			return parseChromaticities( dataView, offset );
+
+		} else if ( type === 'compression' ) {
+
+			return parseCompression( dataView, offset );
+
+		} else if ( type === 'box2i' ) {
+
+			return parseBox2i( dataView, offset );
+
+		} else if ( type === 'lineOrder' ) {
+
+			return parseLineOrder( dataView, offset );
+
+		} else if ( type === 'float' ) {
+
+			return parseFloat32( dataView, offset );
+
+		} else if ( type === 'v2f' ) {
+
+			return parseV2f( dataView, offset );
+
+		} else if ( type === 'int' ) {
+
+			return parseUint32( dataView, offset );
+
+		} else {
+
+			throw 'Cannot parse value for unsupported type: ' + type;
+
+		}
+
+	}
+
+	var bufferDataView = new DataView( buffer );
+	var uInt8Array = new Uint8Array( buffer );
+
+	var EXRHeader = {};
+
+	var magic = bufferDataView.getUint32( 0, true );
+	var versionByteZero = bufferDataView.getUint8( 4, true );
+	var fullMask = bufferDataView.getUint8( 5, true );
+
+	// start of header
+
+	var offset = { value: 8 }; // start at 8, after magic stuff
+
+	var keepReading = true;
+
+	while ( keepReading ) {
+
+		var attributeName = parseNullTerminatedString( buffer, offset );
+
+		if ( attributeName == 0 ) {
+
+			keepReading = false;
+
+		} else {
+
+			var attributeType = parseNullTerminatedString( buffer, offset );
+			var attributeSize = parseUint32( bufferDataView, offset );
+			var attributeValue = parseValue( bufferDataView, buffer, offset, attributeType, attributeSize );
+
+			EXRHeader[ attributeName ] = attributeValue;
+
+		}
+
+	}
+
+	// offsets
+
+	var dataWindowHeight = EXRHeader.dataWindow.yMax + 1;
+	var scanlineBlockSize = 1; // 1 for NO_COMPRESSION
+
+	if ( EXRHeader.compression === 'PIZ_COMPRESSION' ) {
+
+		scanlineBlockSize = 32;
+
+	}
+
+	var numBlocks = dataWindowHeight / scanlineBlockSize;
+
+	for ( var i = 0; i < numBlocks; i ++ ) {
+
+		var scanlineOffset = parseUlong( bufferDataView, offset );
+
+	}
+
+	// we should be passed the scanline offset table, start reading pixel data
+
+	var width = EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1;
+	var height = EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1;
+	var numChannels = EXRHeader.channels.length;
+
+	var byteArray = new Float32Array( width * height * numChannels );
+
+	var channelOffsets = {
+		R: 0,
+		G: 1,
+		B: 2,
+		A: 3
+	};
+
+	if ( EXRHeader.compression === 'NO_COMPRESSION' ) {
+
+		for ( var y = 0; y < height; y ++ ) {
+
+			var y_scanline = parseUint32( bufferDataView, offset );
+			var dataSize = parseUint32( bufferDataView, offset );
+
+			for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) {
+
+				var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
+
+				if ( EXRHeader.channels[ channelID ].pixelType === 1 ) {
+
+					// HALF
+					for ( var x = 0; x < width; x ++ ) {
+
+						var val = parseFloat16( bufferDataView, offset );
+
+						byteArray[ ( ( ( height - y_scanline ) * ( width * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val;
+
+					}
+
+				} else {
+
+					throw 'EXRLoader._parser: unsupported pixelType ' + EXRHeader.channels[ channelID ].pixelType + '. Only pixelType is 1 (HALF) is supported.';
+
+				}
+
+			}
+
+		}
+
+	} else if ( EXRHeader.compression === 'PIZ_COMPRESSION' ) {
+
+		for ( var scanlineBlockIdx = 0; scanlineBlockIdx < height / scanlineBlockSize; scanlineBlockIdx ++ ) {
+
+			var line_no = parseUint32( bufferDataView, offset );
+			var data_len = parseUint32( bufferDataView, offset );
+
+			var tmpBufferSize = width * scanlineBlockSize * ( EXRHeader.channels.length * BYTES_PER_HALF );
+			var tmpBuffer = new Uint16Array( tmpBufferSize );
+			var tmpOffset = { value: 0 };
+
+			decompressPIZ( tmpBuffer, tmpOffset, uInt8Array, bufferDataView, offset, tmpBufferSize, numChannels, EXRHeader.channels, width, scanlineBlockSize );
+
+			for ( var line_y = 0; line_y < scanlineBlockSize; line_y ++ ) {
+
+				for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) {
+
+					var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
+
+					if ( EXRHeader.channels[ channelID ].pixelType === 1 ) {
+
+						// HALF
+						for ( var x = 0; x < width; x ++ ) {
+
+							var val = decodeFloat16( tmpBuffer[ ( channelID * ( scanlineBlockSize * width ) ) + ( line_y * width ) + x ] );
+
+							var true_y = line_y + ( scanlineBlockIdx * scanlineBlockSize );
+
+							byteArray[ ( ( ( height - true_y ) * ( width * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val;
+
+						}
+
+					} else {
+
+						throw 'EXRLoader._parser: unsupported pixelType ' + EXRHeader.channels[ channelID ].pixelType + '. Only pixelType is 1 (HALF) is supported.';
+
+					}
+
+				}
+
+			}
+
+		}
+
+	} else {
+
+		throw 'EXRLoader._parser: ' + EXRHeader.compression + ' is unsupported';
+
+	}
+
+	return {
+		header: EXRHeader,
+		width: width,
+		height: height,
+		data: byteArray,
+		format: EXRHeader.channels.length == 4 ? RGBAFormat : RGBFormat,
+		type: FloatType
+	};
+
+};
+
+export { EXRLoader };

+ 19 - 0
examples/jsm/loaders/FBXLoader.d.ts

@@ -0,0 +1,19 @@
+import {
+  Group,
+  LoadingManager
+} from '../../../src/Three';
+
+export class FBXLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  crossOrigin: string;
+  path: string;
+  resourcePath: string;
+
+  load(url: string, onLoad: (object: Group) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+  setResourcePath(path: string) : this;
+  setCrossOrigin(value: string): this;
+
+  parse(FBXBuffer: ArrayBuffer | string, path: string) : Group;
+}

+ 4186 - 0
examples/jsm/loaders/FBXLoader.js

@@ -0,0 +1,4186 @@
+/**
+ * @author Kyle-Larson https://github.com/Kyle-Larson
+ * @author Takahiro https://github.com/takahirox
+ * @author Lewy Blue https://github.com/looeee
+ *
+ * Loader loads FBX file and generates Group representing FBX scene.
+ * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format
+ * Versions lower than this may load but will probably have errors
+ *
+ * Needs Support:
+ *  Morph normals / blend shape normals
+ *
+ * FBX format references:
+ * 	https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
+ * 	http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference)
+ *
+ * 	Binary format specification:
+ *		https://code.blender.org/2013/08/fbx-binary-file-format-specification/
+ */
+
+import {
+	AmbientLight,
+	AnimationClip,
+	Bone,
+	BufferAttribute,
+	BufferGeometry,
+	ClampToEdgeWrapping,
+	Color,
+	DefaultLoadingManager,
+	DirectionalLight,
+	EquirectangularReflectionMapping,
+	Euler,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	Line,
+	LineBasicMaterial,
+	Loader,
+	LoaderUtils,
+	Math as _Math,
+	Matrix3,
+	Matrix4,
+	Mesh,
+	MeshLambertMaterial,
+	MeshPhongMaterial,
+	NumberKeyframeTrack,
+	Object3D,
+	OrthographicCamera,
+	PerspectiveCamera,
+	PointLight,
+	PropertyBinding,
+	Quaternion,
+	QuaternionKeyframeTrack,
+	RepeatWrapping,
+	Skeleton,
+	SkinnedMesh,
+	SpotLight,
+	Texture,
+	TextureLoader,
+	Uint16BufferAttribute,
+	Vector3,
+	Vector4,
+	VectorKeyframeTrack,
+	VertexColors
+} from "../../../build/three.module.js";
+import { TGALoader } from "../loaders/TGALoader.js";
+import { NURBSCurve } from "../curves/NURBSCurve.js";
+
+
+var FBXLoader = ( function () {
+
+	var fbxTree;
+	var connections;
+	var sceneGraph;
+
+	function FBXLoader( manager ) {
+
+		this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+	}
+
+	FBXLoader.prototype = {
+
+		constructor: FBXLoader,
+
+		crossOrigin: 'anonymous',
+
+		load: function ( url, onLoad, onProgress, onError ) {
+
+			var self = this;
+
+			var path = ( self.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : self.path;
+
+			var loader = new FileLoader( this.manager );
+			loader.setPath( self.path );
+			loader.setResponseType( 'arraybuffer' );
+
+			loader.load( url, function ( buffer ) {
+
+				try {
+
+					onLoad( self.parse( buffer, path ) );
+
+				} catch ( error ) {
+
+					setTimeout( function () {
+
+						if ( onError ) onError( error );
+
+						self.manager.itemError( url );
+
+					}, 0 );
+
+				}
+
+			}, onProgress, onError );
+
+		},
+
+		setPath: function ( value ) {
+
+			this.path = value;
+			return this;
+
+		},
+
+		setResourcePath: function ( value ) {
+
+			this.resourcePath = value;
+			return this;
+
+		},
+
+		setCrossOrigin: function ( value ) {
+
+			this.crossOrigin = value;
+			return this;
+
+		},
+
+		parse: function ( FBXBuffer, path ) {
+
+			if ( isFbxFormatBinary( FBXBuffer ) ) {
+
+				fbxTree = new BinaryParser().parse( FBXBuffer );
+
+			} else {
+
+				var FBXText = convertArrayBufferToString( FBXBuffer );
+
+				if ( ! isFbxFormatASCII( FBXText ) ) {
+
+					throw new Error( 'THREE.FBXLoader: Unknown format.' );
+
+				}
+
+				if ( getFbxVersion( FBXText ) < 7000 ) {
+
+					throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) );
+
+				}
+
+				fbxTree = new TextParser().parse( FBXText );
+
+			}
+
+			// console.log( fbxTree );
+
+			var textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+
+			return new FBXTreeParser( textureLoader ).parse( fbxTree );
+
+		}
+
+	};
+
+	// Parse the FBXTree object returned by the BinaryParser or TextParser and return a Group
+	function FBXTreeParser( textureLoader ) {
+
+		this.textureLoader = textureLoader;
+
+	}
+
+	FBXTreeParser.prototype = {
+
+		constructor: FBXTreeParser,
+
+		parse: function () {
+
+			connections = this.parseConnections();
+
+			var images = this.parseImages();
+			var textures = this.parseTextures( images );
+			var materials = this.parseMaterials( textures );
+			var deformers = this.parseDeformers();
+			var geometryMap = new GeometryParser().parse( deformers );
+
+			this.parseScene( deformers, geometryMap, materials );
+
+			return sceneGraph;
+
+		},
+
+		// Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry )
+		// and details the connection type
+		parseConnections: function () {
+
+			var connectionMap = new Map();
+
+			if ( 'Connections' in fbxTree ) {
+
+				var rawConnections = fbxTree.Connections.connections;
+
+				rawConnections.forEach( function ( rawConnection ) {
+
+					var fromID = rawConnection[ 0 ];
+					var toID = rawConnection[ 1 ];
+					var relationship = rawConnection[ 2 ];
+
+					if ( ! connectionMap.has( fromID ) ) {
+
+						connectionMap.set( fromID, {
+							parents: [],
+							children: []
+						} );
+
+					}
+
+					var parentRelationship = { ID: toID, relationship: relationship };
+					connectionMap.get( fromID ).parents.push( parentRelationship );
+
+					if ( ! connectionMap.has( toID ) ) {
+
+						connectionMap.set( toID, {
+							parents: [],
+							children: []
+						} );
+
+					}
+
+					var childRelationship = { ID: fromID, relationship: relationship };
+					connectionMap.get( toID ).children.push( childRelationship );
+
+				} );
+
+			}
+
+			return connectionMap;
+
+		},
+
+		// Parse FBXTree.Objects.Video for embedded image data
+		// These images are connected to textures in FBXTree.Objects.Textures
+		// via FBXTree.Connections.
+		parseImages: function () {
+
+			var images = {};
+			var blobs = {};
+
+			if ( 'Video' in fbxTree.Objects ) {
+
+				var videoNodes = fbxTree.Objects.Video;
+
+				for ( var nodeID in videoNodes ) {
+
+					var videoNode = videoNodes[ nodeID ];
+
+					var id = parseInt( nodeID );
+
+					images[ id ] = videoNode.RelativeFilename || videoNode.Filename;
+
+					// raw image data is in videoNode.Content
+					if ( 'Content' in videoNode ) {
+
+						var arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 );
+						var base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' );
+
+						if ( arrayBufferContent || base64Content ) {
+
+							var image = this.parseImage( videoNodes[ nodeID ] );
+
+							blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image;
+
+						}
+
+					}
+
+				}
+
+			}
+
+			for ( var id in images ) {
+
+				var filename = images[ id ];
+
+				if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ];
+				else images[ id ] = images[ id ].split( '\\' ).pop();
+
+			}
+
+			return images;
+
+		},
+
+		// Parse embedded image data in FBXTree.Video.Content
+		parseImage: function ( videoNode ) {
+
+			var content = videoNode.Content;
+			var fileName = videoNode.RelativeFilename || videoNode.Filename;
+			var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase();
+
+			var type;
+
+			switch ( extension ) {
+
+				case 'bmp':
+
+					type = 'image/bmp';
+					break;
+
+				case 'jpg':
+				case 'jpeg':
+
+					type = 'image/jpeg';
+					break;
+
+				case 'png':
+
+					type = 'image/png';
+					break;
+
+				case 'tif':
+
+					type = 'image/tiff';
+					break;
+
+				case 'tga':
+
+					if ( typeof TGALoader !== 'function' ) {
+
+						console.warn( 'FBXLoader: TGALoader is required to load TGA textures' );
+						return;
+
+					} else {
+
+						if ( Loader.Handlers.get( '.tga' ) === null ) {
+
+							var tgaLoader = new TGALoader();
+							tgaLoader.setPath( this.textureLoader.path );
+
+							Loader.Handlers.add( /\.tga$/i, tgaLoader );
+
+						}
+
+						type = 'image/tga';
+						break;
+
+					}
+
+				default:
+
+					console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' );
+					return;
+
+			}
+
+			if ( typeof content === 'string' ) { // ASCII format
+
+				return 'data:' + type + ';base64,' + content;
+
+			} else { // Binary Format
+
+				var array = new Uint8Array( content );
+				return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) );
+
+			}
+
+		},
+
+		// Parse nodes in FBXTree.Objects.Texture
+		// These contain details such as UV scaling, cropping, rotation etc and are connected
+		// to images in FBXTree.Objects.Video
+		parseTextures: function ( images ) {
+
+			var textureMap = new Map();
+
+			if ( 'Texture' in fbxTree.Objects ) {
+
+				var textureNodes = fbxTree.Objects.Texture;
+				for ( var nodeID in textureNodes ) {
+
+					var texture = this.parseTexture( textureNodes[ nodeID ], images );
+					textureMap.set( parseInt( nodeID ), texture );
+
+				}
+
+			}
+
+			return textureMap;
+
+		},
+
+		// Parse individual node in FBXTree.Objects.Texture
+		parseTexture: function ( textureNode, images ) {
+
+			var texture = this.loadTexture( textureNode, images );
+
+			texture.ID = textureNode.id;
+
+			texture.name = textureNode.attrName;
+
+			var wrapModeU = textureNode.WrapModeU;
+			var wrapModeV = textureNode.WrapModeV;
+
+			var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
+			var valueV = wrapModeV !== undefined ? wrapModeV.value : 0;
+
+			// http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a
+			// 0: repeat(default), 1: clamp
+
+			texture.wrapS = valueU === 0 ? RepeatWrapping : ClampToEdgeWrapping;
+			texture.wrapT = valueV === 0 ? RepeatWrapping : ClampToEdgeWrapping;
+
+			if ( 'Scaling' in textureNode ) {
+
+				var values = textureNode.Scaling.value;
+
+				texture.repeat.x = values[ 0 ];
+				texture.repeat.y = values[ 1 ];
+
+			}
+
+			return texture;
+
+		},
+
+		// load a texture specified as a blob or data URI, or via an external URL using TextureLoader
+		loadTexture: function ( textureNode, images ) {
+
+			var fileName;
+
+			var currentPath = this.textureLoader.path;
+
+			var children = connections.get( textureNode.id ).children;
+
+			if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) {
+
+				fileName = images[ children[ 0 ].ID ];
+
+				if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) {
+
+					this.textureLoader.setPath( undefined );
+
+				}
+
+			}
+
+			var texture;
+
+			var extension = textureNode.FileName.slice( - 3 ).toLowerCase();
+
+			if ( extension === 'tga' ) {
+
+				var loader = Loader.Handlers.get( '.tga' );
+
+				if ( loader === null ) {
+
+					console.warn( 'FBXLoader: TGALoader not found, creating empty placeholder texture for', fileName );
+					texture = new Texture();
+
+				} else {
+
+					texture = loader.load( fileName );
+
+				}
+
+			} else if ( extension === 'psd' ) {
+
+				console.warn( 'FBXLoader: PSD textures are not supported, creating empty placeholder texture for', fileName );
+				texture = new Texture();
+
+			} else {
+
+				texture = this.textureLoader.load( fileName );
+
+			}
+
+			this.textureLoader.setPath( currentPath );
+
+			return texture;
+
+		},
+
+		// Parse nodes in FBXTree.Objects.Material
+		parseMaterials: function ( textureMap ) {
+
+			var materialMap = new Map();
+
+			if ( 'Material' in fbxTree.Objects ) {
+
+				var materialNodes = fbxTree.Objects.Material;
+
+				for ( var nodeID in materialNodes ) {
+
+					var material = this.parseMaterial( materialNodes[ nodeID ], textureMap );
+
+					if ( material !== null ) materialMap.set( parseInt( nodeID ), material );
+
+				}
+
+			}
+
+			return materialMap;
+
+		},
+
+		// Parse single node in FBXTree.Objects.Material
+		// Materials are connected to texture maps in FBXTree.Objects.Textures
+		// FBX format currently only supports Lambert and Phong shading models
+		parseMaterial: function ( materialNode, textureMap ) {
+
+			var ID = materialNode.id;
+			var name = materialNode.attrName;
+			var type = materialNode.ShadingModel;
+
+			// Case where FBX wraps shading model in property object.
+			if ( typeof type === 'object' ) {
+
+				type = type.value;
+
+			}
+
+			// Ignore unused materials which don't have any connections.
+			if ( ! connections.has( ID ) ) return null;
+
+			var parameters = this.parseParameters( materialNode, textureMap, ID );
+
+			var material;
+
+			switch ( type.toLowerCase() ) {
+
+				case 'phong':
+					material = new MeshPhongMaterial();
+					break;
+				case 'lambert':
+					material = new MeshLambertMaterial();
+					break;
+				default:
+					console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type );
+					material = new MeshPhongMaterial();
+					break;
+
+			}
+
+			material.setValues( parameters );
+			material.name = name;
+
+			return material;
+
+		},
+
+		// Parse FBX material and return parameters suitable for a three.js material
+		// Also parse the texture map and return any textures associated with the material
+		parseParameters: function ( materialNode, textureMap, ID ) {
+
+			var parameters = {};
+
+			if ( materialNode.BumpFactor ) {
+
+				parameters.bumpScale = materialNode.BumpFactor.value;
+
+			}
+			if ( materialNode.Diffuse ) {
+
+				parameters.color = new Color().fromArray( materialNode.Diffuse.value );
+
+			} else if ( materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color' ) {
+
+				// The blender exporter exports diffuse here instead of in materialNode.Diffuse
+				parameters.color = new Color().fromArray( materialNode.DiffuseColor.value );
+
+			}
+
+			if ( materialNode.DisplacementFactor ) {
+
+				parameters.displacementScale = materialNode.DisplacementFactor.value;
+
+			}
+
+			if ( materialNode.Emissive ) {
+
+				parameters.emissive = new Color().fromArray( materialNode.Emissive.value );
+
+			} else if ( materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color' ) {
+
+				// The blender exporter exports emissive color here instead of in materialNode.Emissive
+				parameters.emissive = new Color().fromArray( materialNode.EmissiveColor.value );
+
+			}
+
+			if ( materialNode.EmissiveFactor ) {
+
+				parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value );
+
+			}
+
+			if ( materialNode.Opacity ) {
+
+				parameters.opacity = parseFloat( materialNode.Opacity.value );
+
+			}
+
+			if ( parameters.opacity < 1.0 ) {
+
+				parameters.transparent = true;
+
+			}
+
+			if ( materialNode.ReflectionFactor ) {
+
+				parameters.reflectivity = materialNode.ReflectionFactor.value;
+
+			}
+
+			if ( materialNode.Shininess ) {
+
+				parameters.shininess = materialNode.Shininess.value;
+
+			}
+
+			if ( materialNode.Specular ) {
+
+				parameters.specular = new Color().fromArray( materialNode.Specular.value );
+
+			} else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) {
+
+				// The blender exporter exports specular color here instead of in materialNode.Specular
+				parameters.specular = new Color().fromArray( materialNode.SpecularColor.value );
+
+			}
+
+			var self = this;
+			connections.get( ID ).children.forEach( function ( child ) {
+
+				var type = child.relationship;
+
+				switch ( type ) {
+
+					case 'Bump':
+						parameters.bumpMap = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'Maya|TEX_ao_map':
+						parameters.aoMap = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'DiffuseColor':
+					case 'Maya|TEX_color_map':
+						parameters.map = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'DisplacementColor':
+						parameters.displacementMap = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'EmissiveColor':
+						parameters.emissiveMap = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'NormalMap':
+					case 'Maya|TEX_normal_map':
+						parameters.normalMap = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'ReflectionColor':
+						parameters.envMap = self.getTexture( textureMap, child.ID );
+						parameters.envMap.mapping = EquirectangularReflectionMapping;
+						break;
+
+					case 'SpecularColor':
+						parameters.specularMap = self.getTexture( textureMap, child.ID );
+						break;
+
+					case 'TransparentColor':
+						parameters.alphaMap = self.getTexture( textureMap, child.ID );
+						parameters.transparent = true;
+						break;
+
+					case 'AmbientColor':
+					case 'ShininessExponent': // AKA glossiness map
+					case 'SpecularFactor': // AKA specularLevel
+					case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor
+					default:
+						console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type );
+						break;
+
+				}
+
+			} );
+
+			return parameters;
+
+		},
+
+		// get a texture from the textureMap for use by a material.
+		getTexture: function ( textureMap, id ) {
+
+			// if the texture is a layered texture, just use the first layer and issue a warning
+			if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) {
+
+				console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' );
+				id = connections.get( id ).children[ 0 ].ID;
+
+			}
+
+			return textureMap.get( id );
+
+		},
+
+		// Parse nodes in FBXTree.Objects.Deformer
+		// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
+		// Generates map of Skeleton-like objects for use later when generating and binding skeletons.
+		parseDeformers: function () {
+
+			var skeletons = {};
+			var morphTargets = {};
+
+			if ( 'Deformer' in fbxTree.Objects ) {
+
+				var DeformerNodes = fbxTree.Objects.Deformer;
+
+				for ( var nodeID in DeformerNodes ) {
+
+					var deformerNode = DeformerNodes[ nodeID ];
+
+					var relationships = connections.get( parseInt( nodeID ) );
+
+					if ( deformerNode.attrType === 'Skin' ) {
+
+						var skeleton = this.parseSkeleton( relationships, DeformerNodes );
+						skeleton.ID = nodeID;
+
+						if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' );
+						skeleton.geometryID = relationships.parents[ 0 ].ID;
+
+						skeletons[ nodeID ] = skeleton;
+
+					} else if ( deformerNode.attrType === 'BlendShape' ) {
+
+						var morphTarget = {
+							id: nodeID,
+						};
+
+						morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes );
+						morphTarget.id = nodeID;
+
+						if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' );
+
+						morphTargets[ nodeID ] = morphTarget;
+
+					}
+
+				}
+
+			}
+
+			return {
+
+				skeletons: skeletons,
+				morphTargets: morphTargets,
+
+			};
+
+		},
+
+		// Parse single nodes in FBXTree.Objects.Deformer
+		// The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
+		// Each skin node represents a skeleton and each cluster node represents a bone
+		parseSkeleton: function ( relationships, deformerNodes ) {
+
+			var rawBones = [];
+
+			relationships.children.forEach( function ( child ) {
+
+				var boneNode = deformerNodes[ child.ID ];
+
+				if ( boneNode.attrType !== 'Cluster' ) return;
+
+				var rawBone = {
+
+					ID: child.ID,
+					indices: [],
+					weights: [],
+					transformLink: new Matrix4().fromArray( boneNode.TransformLink.a ),
+					// transform: new Matrix4().fromArray( boneNode.Transform.a ),
+					// linkMode: boneNode.Mode,
+
+				};
+
+				if ( 'Indexes' in boneNode ) {
+
+					rawBone.indices = boneNode.Indexes.a;
+					rawBone.weights = boneNode.Weights.a;
+
+				}
+
+				rawBones.push( rawBone );
+
+			} );
+
+			return {
+
+				rawBones: rawBones,
+				bones: []
+
+			};
+
+		},
+
+		// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
+		parseMorphTargets: function ( relationships, deformerNodes ) {
+
+			var rawMorphTargets = [];
+
+			for ( var i = 0; i < relationships.children.length; i ++ ) {
+
+				var child = relationships.children[ i ];
+
+				var morphTargetNode = deformerNodes[ child.ID ];
+
+				var rawMorphTarget = {
+
+					name: morphTargetNode.attrName,
+					initialWeight: morphTargetNode.DeformPercent,
+					id: morphTargetNode.id,
+					fullWeights: morphTargetNode.FullWeights.a
+
+				};
+
+				if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return;
+
+				rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) {
+
+					return child.relationship === undefined;
+
+				} )[ 0 ].ID;
+
+				rawMorphTargets.push( rawMorphTarget );
+
+			}
+
+			return rawMorphTargets;
+
+		},
+
+		// create the main Group() to be returned by the loader
+		parseScene: function ( deformers, geometryMap, materialMap ) {
+
+			sceneGraph = new Group();
+
+			var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap );
+
+			var modelNodes = fbxTree.Objects.Model;
+
+			var self = this;
+			modelMap.forEach( function ( model ) {
+
+				var modelNode = modelNodes[ model.ID ];
+				self.setLookAtProperties( model, modelNode );
+
+				var parentConnections = connections.get( model.ID ).parents;
+
+				parentConnections.forEach( function ( connection ) {
+
+					var parent = modelMap.get( connection.ID );
+					if ( parent !== undefined ) parent.add( model );
+
+				} );
+
+				if ( model.parent === null ) {
+
+					sceneGraph.add( model );
+
+				}
+
+
+			} );
+
+			this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
+
+			this.createAmbientLight();
+
+			this.setupMorphMaterials();
+
+			sceneGraph.traverse( function ( node ) {
+
+				if ( node.userData.transformData ) {
+
+					if ( node.parent ) node.userData.transformData.parentMatrixWorld = node.parent.matrix;
+
+					var transform = generateTransform( node.userData.transformData );
+
+					node.applyMatrix( transform );
+
+				}
+
+			} );
+
+			var animations = new AnimationParser().parse();
+
+			// if all the models where already combined in a single group, just return that
+			if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) {
+
+				sceneGraph.children[ 0 ].animations = animations;
+				sceneGraph = sceneGraph.children[ 0 ];
+
+			}
+
+			sceneGraph.animations = animations;
+
+		},
+
+		// parse nodes in FBXTree.Objects.Model
+		parseModels: function ( skeletons, geometryMap, materialMap ) {
+
+			var modelMap = new Map();
+			var modelNodes = fbxTree.Objects.Model;
+
+			for ( var nodeID in modelNodes ) {
+
+				var id = parseInt( nodeID );
+				var node = modelNodes[ nodeID ];
+				var relationships = connections.get( id );
+
+				var model = this.buildSkeleton( relationships, skeletons, id, node.attrName );
+
+				if ( ! model ) {
+
+					switch ( node.attrType ) {
+
+						case 'Camera':
+							model = this.createCamera( relationships );
+							break;
+						case 'Light':
+							model = this.createLight( relationships );
+							break;
+						case 'Mesh':
+							model = this.createMesh( relationships, geometryMap, materialMap );
+							break;
+						case 'NurbsCurve':
+							model = this.createCurve( relationships, geometryMap );
+							break;
+						case 'LimbNode':
+						case 'Root':
+							model = new Bone();
+							break;
+						case 'Null':
+						default:
+							model = new Group();
+							break;
+
+					}
+
+					model.name = PropertyBinding.sanitizeNodeName( node.attrName );
+					model.ID = id;
+
+				}
+
+				this.getTransformData( model, node );
+				modelMap.set( id, model );
+
+			}
+
+			return modelMap;
+
+		},
+
+		buildSkeleton: function ( relationships, skeletons, id, name ) {
+
+			var bone = null;
+
+			relationships.parents.forEach( function ( parent ) {
+
+				for ( var ID in skeletons ) {
+
+					var skeleton = skeletons[ ID ];
+
+					skeleton.rawBones.forEach( function ( rawBone, i ) {
+
+						if ( rawBone.ID === parent.ID ) {
+
+							var subBone = bone;
+							bone = new Bone();
+
+							bone.matrixWorld.copy( rawBone.transformLink );
+
+							// set name and id here - otherwise in cases where "subBone" is created it will not have a name / id
+							bone.name = PropertyBinding.sanitizeNodeName( name );
+							bone.ID = id;
+
+							skeleton.bones[ i ] = bone;
+
+							// In cases where a bone is shared between multiple meshes
+							// duplicate the bone here and and it as a child of the first bone
+							if ( subBone !== null ) {
+
+								bone.add( subBone );
+
+							}
+
+						}
+
+					} );
+
+				}
+
+			} );
+
+			return bone;
+
+		},
+
+		// create a PerspectiveCamera or OrthographicCamera
+		createCamera: function ( relationships ) {
+
+			var model;
+			var cameraAttribute;
+
+			relationships.children.forEach( function ( child ) {
+
+				var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
+
+				if ( attr !== undefined ) {
+
+					cameraAttribute = attr;
+
+				}
+
+			} );
+
+			if ( cameraAttribute === undefined ) {
+
+				model = new Object3D();
+
+			} else {
+
+				var type = 0;
+				if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) {
+
+					type = 1;
+
+				}
+
+				var nearClippingPlane = 1;
+				if ( cameraAttribute.NearPlane !== undefined ) {
+
+					nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
+
+				}
+
+				var farClippingPlane = 1000;
+				if ( cameraAttribute.FarPlane !== undefined ) {
+
+					farClippingPlane = cameraAttribute.FarPlane.value / 1000;
+
+				}
+
+
+				var width = window.innerWidth;
+				var height = window.innerHeight;
+
+				if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) {
+
+					width = cameraAttribute.AspectWidth.value;
+					height = cameraAttribute.AspectHeight.value;
+
+				}
+
+				var aspect = width / height;
+
+				var fov = 45;
+				if ( cameraAttribute.FieldOfView !== undefined ) {
+
+					fov = cameraAttribute.FieldOfView.value;
+
+				}
+
+				var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null;
+
+				switch ( type ) {
+
+					case 0: // Perspective
+						model = new PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane );
+						if ( focalLength !== null ) model.setFocalLength( focalLength );
+						break;
+
+					case 1: // Orthographic
+						model = new OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane );
+						break;
+
+					default:
+						console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' );
+						model = new Object3D();
+						break;
+
+				}
+
+			}
+
+			return model;
+
+		},
+
+		// Create a DirectionalLight, PointLight or SpotLight
+		createLight: function ( relationships ) {
+
+			var model;
+			var lightAttribute;
+
+			relationships.children.forEach( function ( child ) {
+
+				var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
+
+				if ( attr !== undefined ) {
+
+					lightAttribute = attr;
+
+				}
+
+			} );
+
+			if ( lightAttribute === undefined ) {
+
+				model = new Object3D();
+
+			} else {
+
+				var type;
+
+				// LightType can be undefined for Point lights
+				if ( lightAttribute.LightType === undefined ) {
+
+					type = 0;
+
+				} else {
+
+					type = lightAttribute.LightType.value;
+
+				}
+
+				var color = 0xffffff;
+
+				if ( lightAttribute.Color !== undefined ) {
+
+					color = new Color().fromArray( lightAttribute.Color.value );
+
+				}
+
+				var intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100;
+
+				// light disabled
+				if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) {
+
+					intensity = 0;
+
+				}
+
+				var distance = 0;
+				if ( lightAttribute.FarAttenuationEnd !== undefined ) {
+
+					if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) {
+
+						distance = 0;
+
+					} else {
+
+						distance = lightAttribute.FarAttenuationEnd.value;
+
+					}
+
+				}
+
+				// TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd?
+				var decay = 1;
+
+				switch ( type ) {
+
+					case 0: // Point
+						model = new PointLight( color, intensity, distance, decay );
+						break;
+
+					case 1: // Directional
+						model = new DirectionalLight( color, intensity );
+						break;
+
+					case 2: // Spot
+						var angle = Math.PI / 3;
+
+						if ( lightAttribute.InnerAngle !== undefined ) {
+
+							angle = _Math.degToRad( lightAttribute.InnerAngle.value );
+
+						}
+
+						var penumbra = 0;
+						if ( lightAttribute.OuterAngle !== undefined ) {
+
+							// TODO: this is not correct - FBX calculates outer and inner angle in degrees
+							// with OuterAngle > InnerAngle && OuterAngle <= Math.PI
+							// while three.js uses a penumbra between (0, 1) to attenuate the inner angle
+							penumbra = _Math.degToRad( lightAttribute.OuterAngle.value );
+							penumbra = Math.max( penumbra, 1 );
+
+						}
+
+						model = new SpotLight( color, intensity, distance, angle, penumbra, decay );
+						break;
+
+					default:
+						console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a PointLight.' );
+						model = new PointLight( color, intensity );
+						break;
+
+				}
+
+				if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) {
+
+					model.castShadow = true;
+
+				}
+
+			}
+
+			return model;
+
+		},
+
+		createMesh: function ( relationships, geometryMap, materialMap ) {
+
+			var model;
+			var geometry = null;
+			var material = null;
+			var materials = [];
+
+			// get geometry and materials(s) from connections
+			relationships.children.forEach( function ( child ) {
+
+				if ( geometryMap.has( child.ID ) ) {
+
+					geometry = geometryMap.get( child.ID );
+
+				}
+
+				if ( materialMap.has( child.ID ) ) {
+
+					materials.push( materialMap.get( child.ID ) );
+
+				}
+
+			} );
+
+			if ( materials.length > 1 ) {
+
+				material = materials;
+
+			} else if ( materials.length > 0 ) {
+
+				material = materials[ 0 ];
+
+			} else {
+
+				material = new MeshPhongMaterial( { color: 0xcccccc } );
+				materials.push( material );
+
+			}
+
+			if ( 'color' in geometry.attributes ) {
+
+				materials.forEach( function ( material ) {
+
+					material.vertexColors = VertexColors;
+
+				} );
+
+			}
+
+			if ( geometry.FBX_Deformer ) {
+
+				materials.forEach( function ( material ) {
+
+					material.skinning = true;
+
+				} );
+
+				model = new SkinnedMesh( geometry, material );
+				model.normalizeSkinWeights();
+
+			} else {
+
+				model = new Mesh( geometry, material );
+
+			}
+
+			return model;
+
+		},
+
+		createCurve: function ( relationships, geometryMap ) {
+
+			var geometry = relationships.children.reduce( function ( geo, child ) {
+
+				if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID );
+
+				return geo;
+
+			}, null );
+
+			// FBX does not list materials for Nurbs lines, so we'll just put our own in here.
+			var material = new LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } );
+			return new Line( geometry, material );
+
+		},
+
+		// parse the model node for transform data
+		getTransformData: function ( model, modelNode ) {
+
+			var transformData = {};
+
+			if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+
+			if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
+			else transformData.eulerOrder = 'ZYX';
+
+			if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
+
+			if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
+			if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
+			if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
+
+			if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
+
+			if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value;
+			if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value;
+
+			if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
+			if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value;
+
+			model.userData.transformData = transformData;
+
+		},
+
+		setLookAtProperties: function ( model, modelNode ) {
+
+			if ( 'LookAtProperty' in modelNode ) {
+
+				var children = connections.get( model.ID ).children;
+
+				children.forEach( function ( child ) {
+
+					if ( child.relationship === 'LookAtProperty' ) {
+
+						var lookAtTarget = fbxTree.Objects.Model[ child.ID ];
+
+						if ( 'Lcl_Translation' in lookAtTarget ) {
+
+							var pos = lookAtTarget.Lcl_Translation.value;
+
+							// DirectionalLight, SpotLight
+							if ( model.target !== undefined ) {
+
+								model.target.position.fromArray( pos );
+								sceneGraph.add( model.target );
+
+							} else { // Cameras and other Object3Ds
+
+								model.lookAt( new Vector3().fromArray( pos ) );
+
+							}
+
+						}
+
+					}
+
+				} );
+
+			}
+
+		},
+
+		bindSkeleton: function ( skeletons, geometryMap, modelMap ) {
+
+			var bindMatrices = this.parsePoseNodes();
+
+			for ( var ID in skeletons ) {
+
+				var skeleton = skeletons[ ID ];
+
+				var parents = connections.get( parseInt( skeleton.ID ) ).parents;
+
+				parents.forEach( function ( parent ) {
+
+					if ( geometryMap.has( parent.ID ) ) {
+
+						var geoID = parent.ID;
+						var geoRelationships = connections.get( geoID );
+
+						geoRelationships.parents.forEach( function ( geoConnParent ) {
+
+							if ( modelMap.has( geoConnParent.ID ) ) {
+
+								var model = modelMap.get( geoConnParent.ID );
+
+								model.bind( new Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
+
+							}
+
+						} );
+
+					}
+
+				} );
+
+			}
+
+		},
+
+		parsePoseNodes: function () {
+
+			var bindMatrices = {};
+
+			if ( 'Pose' in fbxTree.Objects ) {
+
+				var BindPoseNode = fbxTree.Objects.Pose;
+
+				for ( var nodeID in BindPoseNode ) {
+
+					if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
+
+						var poseNodes = BindPoseNode[ nodeID ].PoseNode;
+
+						if ( Array.isArray( poseNodes ) ) {
+
+							poseNodes.forEach( function ( poseNode ) {
+
+								bindMatrices[ poseNode.Node ] = new Matrix4().fromArray( poseNode.Matrix.a );
+
+							} );
+
+						} else {
+
+							bindMatrices[ poseNodes.Node ] = new Matrix4().fromArray( poseNodes.Matrix.a );
+
+						}
+
+					}
+
+				}
+
+			}
+
+			return bindMatrices;
+
+		},
+
+		// Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
+		createAmbientLight: function () {
+
+			if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
+
+				var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
+				var r = ambientColor[ 0 ];
+				var g = ambientColor[ 1 ];
+				var b = ambientColor[ 2 ];
+
+				if ( r !== 0 || g !== 0 || b !== 0 ) {
+
+					var color = new Color( r, g, b );
+					sceneGraph.add( new AmbientLight( color, 1 ) );
+
+				}
+
+			}
+
+		},
+
+		setupMorphMaterials: function () {
+
+			var self = this;
+			sceneGraph.traverse( function ( child ) {
+
+				if ( child.isMesh ) {
+
+					if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) {
+
+						if ( Array.isArray( child.material ) ) {
+
+							child.material.forEach( function ( material, i ) {
+
+								self.setupMorphMaterial( child, material, i );
+
+							} );
+
+						} else {
+
+							self.setupMorphMaterial( child, child.material );
+
+						}
+
+					}
+
+				}
+
+			} );
+
+		},
+
+		setupMorphMaterial: function ( child, material, index ) {
+
+			var uuid = child.uuid;
+			var matUuid = material.uuid;
+
+			// if a geometry has morph targets, it cannot share the material with other geometries
+			var sharedMat = false;
+
+			sceneGraph.traverse( function ( node ) {
+
+				if ( node.isMesh ) {
+
+					if ( Array.isArray( node.material ) ) {
+
+						node.material.forEach( function ( mat ) {
+
+							if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+
+						} );
+
+					} else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+
+				}
+
+			} );
+
+			if ( sharedMat === true ) {
+
+				var clonedMat = material.clone();
+				clonedMat.morphTargets = true;
+
+				if ( index === undefined ) child.material = clonedMat;
+				else child.material[ index ] = clonedMat;
+
+			} else material.morphTargets = true;
+
+		}
+
+	};
+
+	// parse Geometry data from FBXTree and return map of BufferGeometries
+	function GeometryParser() {}
+
+	GeometryParser.prototype = {
+
+		constructor: GeometryParser,
+
+		// Parse nodes in FBXTree.Objects.Geometry
+		parse: function ( deformers ) {
+
+			var geometryMap = new Map();
+
+			if ( 'Geometry' in fbxTree.Objects ) {
+
+				var geoNodes = fbxTree.Objects.Geometry;
+
+				for ( var nodeID in geoNodes ) {
+
+					var relationships = connections.get( parseInt( nodeID ) );
+					var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers );
+
+					geometryMap.set( parseInt( nodeID ), geo );
+
+				}
+
+			}
+
+			return geometryMap;
+
+		},
+
+		// Parse single node in FBXTree.Objects.Geometry
+		parseGeometry: function ( relationships, geoNode, deformers ) {
+
+			switch ( geoNode.attrType ) {
+
+				case 'Mesh':
+					return this.parseMeshGeometry( relationships, geoNode, deformers );
+					break;
+
+				case 'NurbsCurve':
+					return this.parseNurbsGeometry( geoNode );
+					break;
+
+			}
+
+		},
+
+		// Parse single node mesh geometry in FBXTree.Objects.Geometry
+		parseMeshGeometry: function ( relationships, geoNode, deformers ) {
+
+			var skeletons = deformers.skeletons;
+			var morphTargets = deformers.morphTargets;
+
+			var modelNodes = relationships.parents.map( function ( parent ) {
+
+				return fbxTree.Objects.Model[ parent.ID ];
+
+			} );
+
+			// don't create geometry if it is not associated with any models
+			if ( modelNodes.length === 0 ) return;
+
+			var skeleton = relationships.children.reduce( function ( skeleton, child ) {
+
+				if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ];
+
+				return skeleton;
+
+			}, null );
+
+			var morphTarget = relationships.children.reduce( function ( morphTarget, child ) {
+
+				if ( morphTargets[ child.ID ] !== undefined ) morphTarget = morphTargets[ child.ID ];
+
+				return morphTarget;
+
+			}, null );
+
+			// Assume one model and get the preRotation from that
+			// if there is more than one model associated with the geometry this may cause problems
+			var modelNode = modelNodes[ 0 ];
+
+			var transformData = {};
+
+			if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
+			if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+
+			if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
+			if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
+			if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
+
+			var transform = generateTransform( transformData );
+
+			return this.genGeometry( geoNode, skeleton, morphTarget, transform );
+
+		},
+
+		// Generate a BufferGeometry from a node in FBXTree.Objects.Geometry
+		genGeometry: function ( geoNode, skeleton, morphTarget, preTransform ) {
+
+			var geo = new BufferGeometry();
+			if ( geoNode.attrName ) geo.name = geoNode.attrName;
+
+			var geoInfo = this.parseGeoNode( geoNode, skeleton );
+			var buffers = this.genBuffers( geoInfo );
+
+			var positionAttribute = new Float32BufferAttribute( buffers.vertex, 3 );
+
+			preTransform.applyToBufferAttribute( positionAttribute );
+
+			geo.addAttribute( 'position', positionAttribute );
+
+			if ( buffers.colors.length > 0 ) {
+
+				geo.addAttribute( 'color', new Float32BufferAttribute( buffers.colors, 3 ) );
+
+			}
+
+			if ( skeleton ) {
+
+				geo.addAttribute( 'skinIndex', new Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
+
+				geo.addAttribute( 'skinWeight', new Float32BufferAttribute( buffers.vertexWeights, 4 ) );
+
+				// used later to bind the skeleton to the model
+				geo.FBX_Deformer = skeleton;
+
+			}
+
+			if ( buffers.normal.length > 0 ) {
+
+				var normalAttribute = new Float32BufferAttribute( buffers.normal, 3 );
+
+				var normalMatrix = new Matrix3().getNormalMatrix( preTransform );
+				normalMatrix.applyToBufferAttribute( normalAttribute );
+
+				geo.addAttribute( 'normal', normalAttribute );
+
+			}
+
+			buffers.uvs.forEach( function ( uvBuffer, i ) {
+
+				// subsequent uv buffers are called 'uv1', 'uv2', ...
+				var name = 'uv' + ( i + 1 ).toString();
+
+				// the first uv buffer is just called 'uv'
+				if ( i === 0 ) {
+
+					name = 'uv';
+
+				}
+
+				geo.addAttribute( name, new Float32BufferAttribute( buffers.uvs[ i ], 2 ) );
+
+			} );
+
+			if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+				// Convert the material indices of each vertex into rendering groups on the geometry.
+				var prevMaterialIndex = buffers.materialIndex[ 0 ];
+				var startIndex = 0;
+
+				buffers.materialIndex.forEach( function ( currentIndex, i ) {
+
+					if ( currentIndex !== prevMaterialIndex ) {
+
+						geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
+
+						prevMaterialIndex = currentIndex;
+						startIndex = i;
+
+					}
+
+				} );
+
+				// the loop above doesn't add the last group, do that here.
+				if ( geo.groups.length > 0 ) {
+
+					var lastGroup = geo.groups[ geo.groups.length - 1 ];
+					var lastIndex = lastGroup.start + lastGroup.count;
+
+					if ( lastIndex !== buffers.materialIndex.length ) {
+
+						geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex );
+
+					}
+
+				}
+
+				// case where there are multiple materials but the whole geometry is only
+				// using one of them
+				if ( geo.groups.length === 0 ) {
+
+					geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] );
+
+				}
+
+			}
+
+			this.addMorphTargets( geo, geoNode, morphTarget, preTransform );
+
+			return geo;
+
+		},
+
+		parseGeoNode: function ( geoNode, skeleton ) {
+
+			var geoInfo = {};
+
+			geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : [];
+			geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : [];
+
+			if ( geoNode.LayerElementColor ) {
+
+				geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] );
+
+			}
+
+			if ( geoNode.LayerElementMaterial ) {
+
+				geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] );
+
+			}
+
+			if ( geoNode.LayerElementNormal ) {
+
+				geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] );
+
+			}
+
+			if ( geoNode.LayerElementUV ) {
+
+				geoInfo.uv = [];
+
+				var i = 0;
+				while ( geoNode.LayerElementUV[ i ] ) {
+
+					geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) );
+					i ++;
+
+				}
+
+			}
+
+			geoInfo.weightTable = {};
+
+			if ( skeleton !== null ) {
+
+				geoInfo.skeleton = skeleton;
+
+				skeleton.rawBones.forEach( function ( rawBone, i ) {
+
+					// loop over the bone's vertex indices and weights
+					rawBone.indices.forEach( function ( index, j ) {
+
+						if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = [];
+
+						geoInfo.weightTable[ index ].push( {
+
+							id: i,
+							weight: rawBone.weights[ j ],
+
+						} );
+
+					} );
+
+				} );
+
+			}
+
+			return geoInfo;
+
+		},
+
+		genBuffers: function ( geoInfo ) {
+
+			var buffers = {
+				vertex: [],
+				normal: [],
+				colors: [],
+				uvs: [],
+				materialIndex: [],
+				vertexWeights: [],
+				weightsIndices: [],
+			};
+
+			var polygonIndex = 0;
+			var faceLength = 0;
+			var displayedWeightsWarning = false;
+
+			// these will hold data for a single face
+			var facePositionIndexes = [];
+			var faceNormals = [];
+			var faceColors = [];
+			var faceUVs = [];
+			var faceWeights = [];
+			var faceWeightIndices = [];
+
+			var self = this;
+			geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
+
+				var endOfFace = false;
+
+				// Face index and vertex index arrays are combined in a single array
+				// A cube with quad faces looks like this:
+				// PolygonVertexIndex: *24 {
+				//  a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
+				//  }
+				// Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
+				// to find index of last vertex bit shift the index: ^ - 1
+				if ( vertexIndex < 0 ) {
+
+					vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1
+					endOfFace = true;
+
+				}
+
+				var weightIndices = [];
+				var weights = [];
+
+				facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
+
+				if ( geoInfo.color ) {
+
+					var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color );
+
+					faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+
+				}
+
+				if ( geoInfo.skeleton ) {
+
+					if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) {
+
+						geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) {
+
+							weights.push( wt.weight );
+							weightIndices.push( wt.id );
+
+						} );
+
+
+					}
+
+					if ( weights.length > 4 ) {
+
+						if ( ! displayedWeightsWarning ) {
+
+							console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' );
+							displayedWeightsWarning = true;
+
+						}
+
+						var wIndex = [ 0, 0, 0, 0 ];
+						var Weight = [ 0, 0, 0, 0 ];
+
+						weights.forEach( function ( weight, weightIndex ) {
+
+							var currentWeight = weight;
+							var currentIndex = weightIndices[ weightIndex ];
+
+							Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) {
+
+								if ( currentWeight > comparedWeight ) {
+
+									comparedWeightArray[ comparedWeightIndex ] = currentWeight;
+									currentWeight = comparedWeight;
+
+									var tmp = wIndex[ comparedWeightIndex ];
+									wIndex[ comparedWeightIndex ] = currentIndex;
+									currentIndex = tmp;
+
+								}
+
+							} );
+
+						} );
+
+						weightIndices = wIndex;
+						weights = Weight;
+
+					}
+
+					// if the weight array is shorter than 4 pad with 0s
+					while ( weights.length < 4 ) {
+
+						weights.push( 0 );
+						weightIndices.push( 0 );
+
+					}
+
+					for ( var i = 0; i < 4; ++ i ) {
+
+						faceWeights.push( weights[ i ] );
+						faceWeightIndices.push( weightIndices[ i ] );
+
+					}
+
+				}
+
+				if ( geoInfo.normal ) {
+
+					var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
+
+					faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+
+				}
+
+				if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+					var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
+
+				}
+
+				if ( geoInfo.uv ) {
+
+					geoInfo.uv.forEach( function ( uv, i ) {
+
+						var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv );
+
+						if ( faceUVs[ i ] === undefined ) {
+
+							faceUVs[ i ] = [];
+
+						}
+
+						faceUVs[ i ].push( data[ 0 ] );
+						faceUVs[ i ].push( data[ 1 ] );
+
+					} );
+
+				}
+
+				faceLength ++;
+
+				if ( endOfFace ) {
+
+					self.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
+
+					polygonIndex ++;
+					faceLength = 0;
+
+					// reset arrays for the next face
+					facePositionIndexes = [];
+					faceNormals = [];
+					faceColors = [];
+					faceUVs = [];
+					faceWeights = [];
+					faceWeightIndices = [];
+
+				}
+
+			} );
+
+			return buffers;
+
+		},
+
+		// Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
+		genFace: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
+
+			for ( var i = 2; i < faceLength; i ++ ) {
+
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
+
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
+
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
+				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
+
+				if ( geoInfo.skeleton ) {
+
+					buffers.vertexWeights.push( faceWeights[ 0 ] );
+					buffers.vertexWeights.push( faceWeights[ 1 ] );
+					buffers.vertexWeights.push( faceWeights[ 2 ] );
+					buffers.vertexWeights.push( faceWeights[ 3 ] );
+
+					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
+					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
+					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
+					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
+
+					buffers.vertexWeights.push( faceWeights[ i * 4 ] );
+					buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
+					buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
+					buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
+
+					buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
+
+					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
+
+					buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
+					buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
+
+				}
+
+				if ( geoInfo.color ) {
+
+					buffers.colors.push( faceColors[ 0 ] );
+					buffers.colors.push( faceColors[ 1 ] );
+					buffers.colors.push( faceColors[ 2 ] );
+
+					buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
+					buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
+					buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
+
+					buffers.colors.push( faceColors[ i * 3 ] );
+					buffers.colors.push( faceColors[ i * 3 + 1 ] );
+					buffers.colors.push( faceColors[ i * 3 + 2 ] );
+
+				}
+
+				if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+					buffers.materialIndex.push( materialIndex );
+					buffers.materialIndex.push( materialIndex );
+					buffers.materialIndex.push( materialIndex );
+
+				}
+
+				if ( geoInfo.normal ) {
+
+					buffers.normal.push( faceNormals[ 0 ] );
+					buffers.normal.push( faceNormals[ 1 ] );
+					buffers.normal.push( faceNormals[ 2 ] );
+
+					buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
+					buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
+					buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
+
+					buffers.normal.push( faceNormals[ i * 3 ] );
+					buffers.normal.push( faceNormals[ i * 3 + 1 ] );
+					buffers.normal.push( faceNormals[ i * 3 + 2 ] );
+
+				}
+
+				if ( geoInfo.uv ) {
+
+					geoInfo.uv.forEach( function ( uv, j ) {
+
+						if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
+
+						buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
+						buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
+
+						buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
+						buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
+
+						buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
+						buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
+
+					} );
+
+				}
+
+			}
+
+		},
+
+		addMorphTargets: function ( parentGeo, parentGeoNode, morphTarget, preTransform ) {
+
+			if ( morphTarget === null ) return;
+
+			parentGeo.morphAttributes.position = [];
+			// parentGeo.morphAttributes.normal = []; // not implemented
+
+			var self = this;
+			morphTarget.rawTargets.forEach( function ( rawTarget ) {
+
+				var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ];
+
+				if ( morphGeoNode !== undefined ) {
+
+					self.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name );
+
+				}
+
+			} );
+
+		},
+
+		// a morph geometry node is similar to a standard  node, and the node is also contained
+		// in FBXTree.Objects.Geometry, however it can only have attributes for position, normal
+		// and a special attribute Index defining which vertices of the original geometry are affected
+		// Normal and position attributes only have data for the vertices that are affected by the morph
+		genMorphGeometry: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {
+
+			var morphGeo = new BufferGeometry();
+			if ( morphGeoNode.attrName ) morphGeo.name = morphGeoNode.attrName;
+
+			var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
+
+			// make a copy of the parent's vertex positions
+			var vertexPositions = ( parentGeoNode.Vertices !== undefined ) ? parentGeoNode.Vertices.a.slice() : [];
+
+			var morphPositions = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
+			var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
+
+			for ( var i = 0; i < indices.length; i ++ ) {
+
+				var morphIndex = indices[ i ] * 3;
+
+				// FBX format uses blend shapes rather than morph targets. This can be converted
+				// by additively combining the blend shape positions with the original geometry's positions
+				vertexPositions[ morphIndex ] += morphPositions[ i * 3 ];
+				vertexPositions[ morphIndex + 1 ] += morphPositions[ i * 3 + 1 ];
+				vertexPositions[ morphIndex + 2 ] += morphPositions[ i * 3 + 2 ];
+
+			}
+
+			// TODO: add morph normal support
+			var morphGeoInfo = {
+				vertexIndices: vertexIndices,
+				vertexPositions: vertexPositions,
+			};
+
+			var morphBuffers = this.genBuffers( morphGeoInfo );
+
+			var positionAttribute = new Float32BufferAttribute( morphBuffers.vertex, 3 );
+			positionAttribute.name = name || morphGeoNode.attrName;
+
+			preTransform.applyToBufferAttribute( positionAttribute );
+
+			parentGeo.morphAttributes.position.push( positionAttribute );
+
+		},
+
+		// Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
+		parseNormals: function ( NormalNode ) {
+
+			var mappingType = NormalNode.MappingInformationType;
+			var referenceType = NormalNode.ReferenceInformationType;
+			var buffer = NormalNode.Normals.a;
+			var indexBuffer = [];
+			if ( referenceType === 'IndexToDirect' ) {
+
+				if ( 'NormalIndex' in NormalNode ) {
+
+					indexBuffer = NormalNode.NormalIndex.a;
+
+				} else if ( 'NormalsIndex' in NormalNode ) {
+
+					indexBuffer = NormalNode.NormalsIndex.a;
+
+				}
+
+			}
+
+			return {
+				dataSize: 3,
+				buffer: buffer,
+				indices: indexBuffer,
+				mappingType: mappingType,
+				referenceType: referenceType
+			};
+
+		},
+
+		// Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists
+		parseUVs: function ( UVNode ) {
+
+			var mappingType = UVNode.MappingInformationType;
+			var referenceType = UVNode.ReferenceInformationType;
+			var buffer = UVNode.UV.a;
+			var indexBuffer = [];
+			if ( referenceType === 'IndexToDirect' ) {
+
+				indexBuffer = UVNode.UVIndex.a;
+
+			}
+
+			return {
+				dataSize: 2,
+				buffer: buffer,
+				indices: indexBuffer,
+				mappingType: mappingType,
+				referenceType: referenceType
+			};
+
+		},
+
+		// Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists
+		parseVertexColors: function ( ColorNode ) {
+
+			var mappingType = ColorNode.MappingInformationType;
+			var referenceType = ColorNode.ReferenceInformationType;
+			var buffer = ColorNode.Colors.a;
+			var indexBuffer = [];
+			if ( referenceType === 'IndexToDirect' ) {
+
+				indexBuffer = ColorNode.ColorIndex.a;
+
+			}
+
+			return {
+				dataSize: 4,
+				buffer: buffer,
+				indices: indexBuffer,
+				mappingType: mappingType,
+				referenceType: referenceType
+			};
+
+		},
+
+		// Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists
+		parseMaterialIndices: function ( MaterialNode ) {
+
+			var mappingType = MaterialNode.MappingInformationType;
+			var referenceType = MaterialNode.ReferenceInformationType;
+
+			if ( mappingType === 'NoMappingInformation' ) {
+
+				return {
+					dataSize: 1,
+					buffer: [ 0 ],
+					indices: [ 0 ],
+					mappingType: 'AllSame',
+					referenceType: referenceType
+				};
+
+			}
+
+			var materialIndexBuffer = MaterialNode.Materials.a;
+
+			// Since materials are stored as indices, there's a bit of a mismatch between FBX and what
+			// we expect.So we create an intermediate buffer that points to the index in the buffer,
+			// for conforming with the other functions we've written for other data.
+			var materialIndices = [];
+
+			for ( var i = 0; i < materialIndexBuffer.length; ++ i ) {
+
+				materialIndices.push( i );
+
+			}
+
+			return {
+				dataSize: 1,
+				buffer: materialIndexBuffer,
+				indices: materialIndices,
+				mappingType: mappingType,
+				referenceType: referenceType
+			};
+
+		},
+
+		// Generate a NurbGeometry from a node in FBXTree.Objects.Geometry
+		parseNurbsGeometry: function ( geoNode ) {
+
+			if ( NURBSCurve === undefined ) {
+
+				console.error( 'THREE.FBXLoader: The loader relies on NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' );
+				return new BufferGeometry();
+
+			}
+
+			var order = parseInt( geoNode.Order );
+
+			if ( isNaN( order ) ) {
+
+				console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id );
+				return new BufferGeometry();
+
+			}
+
+			var degree = order - 1;
+
+			var knots = geoNode.KnotVector.a;
+			var controlPoints = [];
+			var pointsValues = geoNode.Points.a;
+
+			for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
+
+				controlPoints.push( new Vector4().fromArray( pointsValues, i ) );
+
+			}
+
+			var startKnot, endKnot;
+
+			if ( geoNode.Form === 'Closed' ) {
+
+				controlPoints.push( controlPoints[ 0 ] );
+
+			} else if ( geoNode.Form === 'Periodic' ) {
+
+				startKnot = degree;
+				endKnot = knots.length - 1 - startKnot;
+
+				for ( var i = 0; i < degree; ++ i ) {
+
+					controlPoints.push( controlPoints[ i ] );
+
+				}
+
+			}
+
+			var curve = new NURBSCurve( degree, knots, controlPoints, startKnot, endKnot );
+			var vertices = curve.getPoints( controlPoints.length * 7 );
+
+			var positions = new Float32Array( vertices.length * 3 );
+
+			vertices.forEach( function ( vertex, i ) {
+
+				vertex.toArray( positions, i * 3 );
+
+			} );
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( 'position', new BufferAttribute( positions, 3 ) );
+
+			return geometry;
+
+		},
+
+	};
+
+	// parse animation data from FBXTree
+	function AnimationParser() {}
+
+	AnimationParser.prototype = {
+
+		constructor: AnimationParser,
+
+		// take raw animation clips and turn them into three.js animation clips
+		parse: function () {
+
+			var animationClips = [];
+
+			var rawClips = this.parseClips();
+
+			if ( rawClips !== undefined ) {
+
+				for ( var key in rawClips ) {
+
+					var rawClip = rawClips[ key ];
+
+					var clip = this.addClip( rawClip );
+
+					animationClips.push( clip );
+
+				}
+
+			}
+
+			return animationClips;
+
+		},
+
+		parseClips: function () {
+
+			// since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
+			// if this is undefined we can safely assume there are no animations
+			if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined;
+
+			var curveNodesMap = this.parseAnimationCurveNodes();
+
+			this.parseAnimationCurves( curveNodesMap );
+
+			var layersMap = this.parseAnimationLayers( curveNodesMap );
+			var rawClips = this.parseAnimStacks( layersMap );
+
+			return rawClips;
+
+		},
+
+		// parse nodes in FBXTree.Objects.AnimationCurveNode
+		// each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
+		// and is referenced by an AnimationLayer
+		parseAnimationCurveNodes: function () {
+
+			var rawCurveNodes = fbxTree.Objects.AnimationCurveNode;
+
+			var curveNodesMap = new Map();
+
+			for ( var nodeID in rawCurveNodes ) {
+
+				var rawCurveNode = rawCurveNodes[ nodeID ];
+
+				if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
+
+					var curveNode = {
+
+						id: rawCurveNode.id,
+						attr: rawCurveNode.attrName,
+						curves: {},
+
+					};
+
+					curveNodesMap.set( curveNode.id, curveNode );
+
+				}
+
+			}
+
+			return curveNodesMap;
+
+		},
+
+		// parse nodes in FBXTree.Objects.AnimationCurve and connect them up to
+		// previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated
+		// axis ( e.g. times and values of x rotation)
+		parseAnimationCurves: function ( curveNodesMap ) {
+
+			var rawCurves = fbxTree.Objects.AnimationCurve;
+
+			// TODO: Many values are identical up to roundoff error, but won't be optimised
+			// e.g. position times: [0, 0.4, 0. 8]
+			// position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809]
+			// clearly, this should be optimised to
+			// times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809]
+			// this shows up in nearly every FBX file, and generally time array is length > 100
+
+			for ( var nodeID in rawCurves ) {
+
+				var animationCurve = {
+
+					id: rawCurves[ nodeID ].id,
+					times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
+					values: rawCurves[ nodeID ].KeyValueFloat.a,
+
+				};
+
+				var relationships = connections.get( animationCurve.id );
+
+				if ( relationships !== undefined ) {
+
+					var animationCurveID = relationships.parents[ 0 ].ID;
+					var animationCurveRelationship = relationships.parents[ 0 ].relationship;
+
+					if ( animationCurveRelationship.match( /X/ ) ) {
+
+						curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
+
+					} else if ( animationCurveRelationship.match( /Y/ ) ) {
+
+						curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
+
+					} else if ( animationCurveRelationship.match( /Z/ ) ) {
+
+						curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
+
+					} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
+
+						curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
+
+					}
+
+				}
+
+			}
+
+		},
+
+		// parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
+		// to various AnimationCurveNodes and is referenced by an AnimationStack node
+		// note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
+		parseAnimationLayers: function ( curveNodesMap ) {
+
+			var rawLayers = fbxTree.Objects.AnimationLayer;
+
+			var layersMap = new Map();
+
+			for ( var nodeID in rawLayers ) {
+
+				var layerCurveNodes = [];
+
+				var connection = connections.get( parseInt( nodeID ) );
+
+				if ( connection !== undefined ) {
+
+					// all the animationCurveNodes used in the layer
+					var children = connection.children;
+
+					children.forEach( function ( child, i ) {
+
+						if ( curveNodesMap.has( child.ID ) ) {
+
+							var curveNode = curveNodesMap.get( child.ID );
+
+							// check that the curves are defined for at least one axis, otherwise ignore the curveNode
+							if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
+
+								if ( layerCurveNodes[ i ] === undefined ) {
+
+									var modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
+
+										return parent.relationship !== undefined;
+
+									} )[ 0 ].ID;
+
+									if ( modelID !== undefined ) {
+
+										var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
+
+										var node = {
+
+											modelName: PropertyBinding.sanitizeNodeName( rawModel.attrName ),
+											ID: rawModel.id,
+											initialPosition: [ 0, 0, 0 ],
+											initialRotation: [ 0, 0, 0 ],
+											initialScale: [ 1, 1, 1 ],
+
+										};
+
+										sceneGraph.traverse( function ( child ) {
+
+											if ( child.ID === rawModel.id ) {
+
+												node.transform = child.matrix;
+
+												if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
+
+											}
+
+										} );
+
+										if ( ! node.transform ) node.transform = new Matrix4();
+
+										// if the animated model is pre rotated, we'll have to apply the pre rotations to every
+										// animation value as well
+										if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value;
+										if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value;
+
+										layerCurveNodes[ i ] = node;
+
+									}
+
+								}
+
+								if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+
+							} else if ( curveNode.curves.morph !== undefined ) {
+
+								if ( layerCurveNodes[ i ] === undefined ) {
+
+									var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
+
+										return parent.relationship !== undefined;
+
+									} )[ 0 ].ID;
+
+									var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
+									var geoID = connections.get( morpherID ).parents[ 0 ].ID;
+
+									// assuming geometry is not used in more than one model
+									var modelID = connections.get( geoID ).parents[ 0 ].ID;
+
+									var rawModel = fbxTree.Objects.Model[ modelID ];
+
+									var node = {
+
+										modelName: PropertyBinding.sanitizeNodeName( rawModel.attrName ),
+										morphName: fbxTree.Objects.Deformer[ deformerID ].attrName,
+
+									};
+
+									layerCurveNodes[ i ] = node;
+
+								}
+
+								layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+
+							}
+
+						}
+
+					} );
+
+					layersMap.set( parseInt( nodeID ), layerCurveNodes );
+
+				}
+
+			}
+
+			return layersMap;
+
+		},
+
+		// parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
+		// hierarchy. Each Stack node will be used to create a AnimationClip
+		parseAnimStacks: function ( layersMap ) {
+
+			var rawStacks = fbxTree.Objects.AnimationStack;
+
+			// connect the stacks (clips) up to the layers
+			var rawClips = {};
+
+			for ( var nodeID in rawStacks ) {
+
+				var children = connections.get( parseInt( nodeID ) ).children;
+
+				if ( children.length > 1 ) {
+
+					// it seems like stacks will always be associated with a single layer. But just in case there are files
+					// where there are multiple layers per stack, we'll display a warning
+					console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' );
+
+				}
+
+				var layer = layersMap.get( children[ 0 ].ID );
+
+				rawClips[ nodeID ] = {
+
+					name: rawStacks[ nodeID ].attrName,
+					layer: layer,
+
+				};
+
+			}
+
+			return rawClips;
+
+		},
+
+		addClip: function ( rawClip ) {
+
+			var tracks = [];
+
+			var self = this;
+			rawClip.layer.forEach( function ( rawTracks ) {
+
+				tracks = tracks.concat( self.generateTracks( rawTracks ) );
+
+			} );
+
+			return new AnimationClip( rawClip.name, - 1, tracks );
+
+		},
+
+		generateTracks: function ( rawTracks ) {
+
+			var tracks = [];
+
+			var initialPosition = new Vector3();
+			var initialRotation = new Quaternion();
+			var initialScale = new Vector3();
+
+			if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
+
+			initialPosition = initialPosition.toArray();
+			initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
+			initialScale = initialScale.toArray();
+
+			if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
+
+				var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
+				if ( positionTrack !== undefined ) tracks.push( positionTrack );
+
+			}
+
+			if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
+
+				var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
+				if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
+
+			}
+
+			if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
+
+				var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
+				if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
+
+			}
+
+			if ( rawTracks.DeformPercent !== undefined ) {
+
+				var morphTrack = this.generateMorphTrack( rawTracks );
+				if ( morphTrack !== undefined ) tracks.push( morphTrack );
+
+			}
+
+			return tracks;
+
+		},
+
+		generateVectorTrack: function ( modelName, curves, initialValue, type ) {
+
+			var times = this.getTimesForAllAxes( curves );
+			var values = this.getKeyframeTrackValues( times, curves, initialValue );
+
+			return new VectorKeyframeTrack( modelName + '.' + type, times, values );
+
+		},
+
+		generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
+
+			if ( curves.x !== undefined ) {
+
+				this.interpolateRotations( curves.x );
+				curves.x.values = curves.x.values.map( _Math.degToRad );
+
+			}
+			if ( curves.y !== undefined ) {
+
+				this.interpolateRotations( curves.y );
+				curves.y.values = curves.y.values.map( _Math.degToRad );
+
+			}
+			if ( curves.z !== undefined ) {
+
+				this.interpolateRotations( curves.z );
+				curves.z.values = curves.z.values.map( _Math.degToRad );
+
+			}
+
+			var times = this.getTimesForAllAxes( curves );
+			var values = this.getKeyframeTrackValues( times, curves, initialValue );
+
+			if ( preRotation !== undefined ) {
+
+				preRotation = preRotation.map( _Math.degToRad );
+				preRotation.push( eulerOrder );
+
+				preRotation = new Euler().fromArray( preRotation );
+				preRotation = new Quaternion().setFromEuler( preRotation );
+
+			}
+
+			if ( postRotation !== undefined ) {
+
+				postRotation = postRotation.map( _Math.degToRad );
+				postRotation.push( eulerOrder );
+
+				postRotation = new Euler().fromArray( postRotation );
+				postRotation = new Quaternion().setFromEuler( postRotation ).inverse();
+
+			}
+
+			var quaternion = new Quaternion();
+			var euler = new Euler();
+
+			var quaternionValues = [];
+
+			for ( var i = 0; i < values.length; i += 3 ) {
+
+				euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
+
+				quaternion.setFromEuler( euler );
+
+				if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
+				if ( postRotation !== undefined ) quaternion.multiply( postRotation );
+
+				quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
+
+			}
+
+			return new QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
+
+		},
+
+		generateMorphTrack: function ( rawTracks ) {
+
+			var curves = rawTracks.DeformPercent.curves.morph;
+			var values = curves.values.map( function ( val ) {
+
+				return val / 100;
+
+			} );
+
+			var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
+
+			return new NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
+
+		},
+
+		// For all animated objects, times are defined separately for each axis
+		// Here we'll combine the times into one sorted array without duplicates
+		getTimesForAllAxes: function ( curves ) {
+
+			var times = [];
+
+			// first join together the times for each axis, if defined
+			if ( curves.x !== undefined ) times = times.concat( curves.x.times );
+			if ( curves.y !== undefined ) times = times.concat( curves.y.times );
+			if ( curves.z !== undefined ) times = times.concat( curves.z.times );
+
+			// then sort them and remove duplicates
+			times = times.sort( function ( a, b ) {
+
+				return a - b;
+
+			} ).filter( function ( elem, index, array ) {
+
+				return array.indexOf( elem ) == index;
+
+			} );
+
+			return times;
+
+		},
+
+		getKeyframeTrackValues: function ( times, curves, initialValue ) {
+
+			var prevValue = initialValue;
+
+			var values = [];
+
+			var xIndex = - 1;
+			var yIndex = - 1;
+			var zIndex = - 1;
+
+			times.forEach( function ( time ) {
+
+				if ( curves.x ) xIndex = curves.x.times.indexOf( time );
+				if ( curves.y ) yIndex = curves.y.times.indexOf( time );
+				if ( curves.z ) zIndex = curves.z.times.indexOf( time );
+
+				// if there is an x value defined for this frame, use that
+				if ( xIndex !== - 1 ) {
+
+					var xValue = curves.x.values[ xIndex ];
+					values.push( xValue );
+					prevValue[ 0 ] = xValue;
+
+				} else {
+
+					// otherwise use the x value from the previous frame
+					values.push( prevValue[ 0 ] );
+
+				}
+
+				if ( yIndex !== - 1 ) {
+
+					var yValue = curves.y.values[ yIndex ];
+					values.push( yValue );
+					prevValue[ 1 ] = yValue;
+
+				} else {
+
+					values.push( prevValue[ 1 ] );
+
+				}
+
+				if ( zIndex !== - 1 ) {
+
+					var zValue = curves.z.values[ zIndex ];
+					values.push( zValue );
+					prevValue[ 2 ] = zValue;
+
+				} else {
+
+					values.push( prevValue[ 2 ] );
+
+				}
+
+			} );
+
+			return values;
+
+		},
+
+		// Rotations are defined as Euler angles which can have values  of any size
+		// These will be converted to quaternions which don't support values greater than
+		// PI, so we'll interpolate large rotations
+		interpolateRotations: function ( curve ) {
+
+			for ( var i = 1; i < curve.values.length; i ++ ) {
+
+				var initialValue = curve.values[ i - 1 ];
+				var valuesSpan = curve.values[ i ] - initialValue;
+
+				var absoluteSpan = Math.abs( valuesSpan );
+
+				if ( absoluteSpan >= 180 ) {
+
+					var numSubIntervals = absoluteSpan / 180;
+
+					var step = valuesSpan / numSubIntervals;
+					var nextValue = initialValue + step;
+
+					var initialTime = curve.times[ i - 1 ];
+					var timeSpan = curve.times[ i ] - initialTime;
+					var interval = timeSpan / numSubIntervals;
+					var nextTime = initialTime + interval;
+
+					var interpolatedTimes = [];
+					var interpolatedValues = [];
+
+					while ( nextTime < curve.times[ i ] ) {
+
+						interpolatedTimes.push( nextTime );
+						nextTime += interval;
+
+						interpolatedValues.push( nextValue );
+						nextValue += step;
+
+					}
+
+					curve.times = inject( curve.times, i, interpolatedTimes );
+					curve.values = inject( curve.values, i, interpolatedValues );
+
+				}
+
+			}
+
+		},
+
+	};
+
+	// parse an FBX file in ASCII format
+	function TextParser() {}
+
+	TextParser.prototype = {
+
+		constructor: TextParser,
+
+		getPrevNode: function () {
+
+			return this.nodeStack[ this.currentIndent - 2 ];
+
+		},
+
+		getCurrentNode: function () {
+
+			return this.nodeStack[ this.currentIndent - 1 ];
+
+		},
+
+		getCurrentProp: function () {
+
+			return this.currentProp;
+
+		},
+
+		pushStack: function ( node ) {
+
+			this.nodeStack.push( node );
+			this.currentIndent += 1;
+
+		},
+
+		popStack: function () {
+
+			this.nodeStack.pop();
+			this.currentIndent -= 1;
+
+		},
+
+		setCurrentProp: function ( val, name ) {
+
+			this.currentProp = val;
+			this.currentPropName = name;
+
+		},
+
+		parse: function ( text ) {
+
+			this.currentIndent = 0;
+
+			this.allNodes = new FBXTree();
+			this.nodeStack = [];
+			this.currentProp = [];
+			this.currentPropName = '';
+
+			var self = this;
+
+			var split = text.split( /[\r\n]+/ );
+
+			split.forEach( function ( line, i ) {
+
+				var matchComment = line.match( /^[\s\t]*;/ );
+				var matchEmpty = line.match( /^[\s\t]*$/ );
+
+				if ( matchComment || matchEmpty ) return;
+
+				var matchBeginning = line.match( '^\\t{' + self.currentIndent + '}(\\w+):(.*){', '' );
+				var matchProperty = line.match( '^\\t{' + ( self.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' );
+				var matchEnd = line.match( '^\\t{' + ( self.currentIndent - 1 ) + '}}' );
+
+				if ( matchBeginning ) {
+
+					self.parseNodeBegin( line, matchBeginning );
+
+				} else if ( matchProperty ) {
+
+					self.parseNodeProperty( line, matchProperty, split[ ++ i ] );
+
+				} else if ( matchEnd ) {
+
+					self.popStack();
+
+				} else if ( line.match( /^[^\s\t}]/ ) ) {
+
+					// large arrays are split over multiple lines terminated with a ',' character
+					// if this is encountered the line needs to be joined to the previous line
+					self.parseNodePropertyContinued( line );
+
+				}
+
+			} );
+
+			return this.allNodes;
+
+		},
+
+		parseNodeBegin: function ( line, property ) {
+
+			var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+			var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) {
+
+				return attr.trim().replace( /^"/, '' ).replace( /"$/, '' );
+
+			} );
+
+			var node = { name: nodeName };
+			var attrs = this.parseNodeAttr( nodeAttrs );
+
+			var currentNode = this.getCurrentNode();
+
+			// a top node
+			if ( this.currentIndent === 0 ) {
+
+				this.allNodes.add( nodeName, node );
+
+			} else { // a subnode
+
+				// if the subnode already exists, append it
+				if ( nodeName in currentNode ) {
+
+					// special case Pose needs PoseNodes as an array
+					if ( nodeName === 'PoseNode' ) {
+
+						currentNode.PoseNode.push( node );
+
+					} else if ( currentNode[ nodeName ].id !== undefined ) {
+
+						currentNode[ nodeName ] = {};
+						currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ];
+
+					}
+
+					if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node;
+
+				} else if ( typeof attrs.id === 'number' ) {
+
+					currentNode[ nodeName ] = {};
+					currentNode[ nodeName ][ attrs.id ] = node;
+
+				} else if ( nodeName !== 'Properties70' ) {
+
+					if ( nodeName === 'PoseNode' )	currentNode[ nodeName ] = [ node ];
+					else currentNode[ nodeName ] = node;
+
+				}
+
+			}
+
+			if ( typeof attrs.id === 'number' ) node.id = attrs.id;
+			if ( attrs.name !== '' ) node.attrName = attrs.name;
+			if ( attrs.type !== '' ) node.attrType = attrs.type;
+
+			this.pushStack( node );
+
+		},
+
+		parseNodeAttr: function ( attrs ) {
+
+			var id = attrs[ 0 ];
+
+			if ( attrs[ 0 ] !== '' ) {
+
+				id = parseInt( attrs[ 0 ] );
+
+				if ( isNaN( id ) ) {
+
+					id = attrs[ 0 ];
+
+				}
+
+			}
+
+			var name = '', type = '';
+
+			if ( attrs.length > 1 ) {
+
+				name = attrs[ 1 ].replace( /^(\w+)::/, '' );
+				type = attrs[ 2 ];
+
+			}
+
+			return { id: id, name: name, type: type };
+
+		},
+
+		parseNodeProperty: function ( line, property, contentLine ) {
+
+			var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+			var propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+
+			// for special case: base64 image data follows "Content: ," line
+			//	Content: ,
+			//	 "/9j/4RDaRXhpZgAATU0A..."
+			if ( propName === 'Content' && propValue === ',' ) {
+
+				propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim();
+
+			}
+
+			var currentNode = this.getCurrentNode();
+			var parentName = currentNode.name;
+
+			if ( parentName === 'Properties70' ) {
+
+				this.parseNodeSpecialProperty( line, propName, propValue );
+				return;
+
+			}
+
+			// Connections
+			if ( propName === 'C' ) {
+
+				var connProps = propValue.split( ',' ).slice( 1 );
+				var from = parseInt( connProps[ 0 ] );
+				var to = parseInt( connProps[ 1 ] );
+
+				var rest = propValue.split( ',' ).slice( 3 );
+
+				rest = rest.map( function ( elem ) {
+
+					return elem.trim().replace( /^"/, '' );
+
+				} );
+
+				propName = 'connections';
+				propValue = [ from, to ];
+				append( propValue, rest );
+
+				if ( currentNode[ propName ] === undefined ) {
+
+					currentNode[ propName ] = [];
+
+				}
+
+			}
+
+			// Node
+			if ( propName === 'Node' ) currentNode.id = propValue;
+
+			// connections
+			if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) {
+
+				currentNode[ propName ].push( propValue );
+
+			} else {
+
+				if ( propName !== 'a' ) currentNode[ propName ] = propValue;
+				else currentNode.a = propValue;
+
+			}
+
+			this.setCurrentProp( currentNode, propName );
+
+			// convert string to array, unless it ends in ',' in which case more will be added to it
+			if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) {
+
+				currentNode.a = parseNumberArray( propValue );
+
+			}
+
+		},
+
+		parseNodePropertyContinued: function ( line ) {
+
+			var currentNode = this.getCurrentNode();
+
+			currentNode.a += line;
+
+			// if the line doesn't end in ',' we have reached the end of the property value
+			// so convert the string to an array
+			if ( line.slice( - 1 ) !== ',' ) {
+
+				currentNode.a = parseNumberArray( currentNode.a );
+
+			}
+
+		},
+
+		// parse "Property70"
+		parseNodeSpecialProperty: function ( line, propName, propValue ) {
+
+			// split this
+			// P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
+			// into array like below
+			// ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
+			var props = propValue.split( '",' ).map( function ( prop ) {
+
+				return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
+
+			} );
+
+			var innerPropName = props[ 0 ];
+			var innerPropType1 = props[ 1 ];
+			var innerPropType2 = props[ 2 ];
+			var innerPropFlag = props[ 3 ];
+			var innerPropValue = props[ 4 ];
+
+			// cast values where needed, otherwise leave as strings
+			switch ( innerPropType1 ) {
+
+				case 'int':
+				case 'enum':
+				case 'bool':
+				case 'ULongLong':
+				case 'double':
+				case 'Number':
+				case 'FieldOfView':
+					innerPropValue = parseFloat( innerPropValue );
+					break;
+
+				case 'Color':
+				case 'ColorRGB':
+				case 'Vector3D':
+				case 'Lcl_Translation':
+				case 'Lcl_Rotation':
+				case 'Lcl_Scaling':
+					innerPropValue = parseNumberArray( innerPropValue );
+					break;
+
+			}
+
+			// CAUTION: these props must append to parent's parent
+			this.getPrevNode()[ innerPropName ] = {
+
+				'type': innerPropType1,
+				'type2': innerPropType2,
+				'flag': innerPropFlag,
+				'value': innerPropValue
+
+			};
+
+			this.setCurrentProp( this.getPrevNode(), innerPropName );
+
+		},
+
+	};
+
+	// Parse an FBX file in Binary format
+	function BinaryParser() {}
+
+	BinaryParser.prototype = {
+
+		constructor: BinaryParser,
+
+		parse: function ( buffer ) {
+
+			var reader = new BinaryReader( buffer );
+			reader.skip( 23 ); // skip magic 23 bytes
+
+			var version = reader.getUint32();
+
+			console.log( 'THREE.FBXLoader: FBX binary version: ' + version );
+
+			var allNodes = new FBXTree();
+
+			while ( ! this.endOfContent( reader ) ) {
+
+				var node = this.parseNode( reader, version );
+				if ( node !== null ) allNodes.add( node.name, node );
+
+			}
+
+			return allNodes;
+
+		},
+
+		// Check if reader has reached the end of content.
+		endOfContent: function ( reader ) {
+
+			// footer size: 160bytes + 16-byte alignment padding
+			// - 16bytes: magic
+			// - padding til 16-byte alignment (at least 1byte?)
+			//	(seems like some exporters embed fixed 15 or 16bytes?)
+			// - 4bytes: magic
+			// - 4bytes: version
+			// - 120bytes: zero
+			// - 16bytes: magic
+			if ( reader.size() % 16 === 0 ) {
+
+				return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size();
+
+			} else {
+
+				return reader.getOffset() + 160 + 16 >= reader.size();
+
+			}
+
+		},
+
+		// recursively parse nodes until the end of the file is reached
+		parseNode: function ( reader, version ) {
+
+			var node = {};
+
+			// The first three data sizes depends on version.
+			var endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+			var numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+
+			// note: do not remove this even if you get a linter warning as it moves the buffer forward
+			var propertyListLen = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+
+			var nameLen = reader.getUint8();
+			var name = reader.getString( nameLen );
+
+			// Regards this node as NULL-record if endOffset is zero
+			if ( endOffset === 0 ) return null;
+
+			var propertyList = [];
+
+			for ( var i = 0; i < numProperties; i ++ ) {
+
+				propertyList.push( this.parseProperty( reader ) );
+
+			}
+
+			// Regards the first three elements in propertyList as id, attrName, and attrType
+			var id = propertyList.length > 0 ? propertyList[ 0 ] : '';
+			var attrName = propertyList.length > 1 ? propertyList[ 1 ] : '';
+			var attrType = propertyList.length > 2 ? propertyList[ 2 ] : '';
+
+			// check if this node represents just a single property
+			// like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]}
+			node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false;
+
+			while ( endOffset > reader.getOffset() ) {
+
+				var subNode = this.parseNode( reader, version );
+
+				if ( subNode !== null ) this.parseSubNode( name, node, subNode );
+
+			}
+
+			node.propertyList = propertyList; // raw property list used by parent
+
+			if ( typeof id === 'number' ) node.id = id;
+			if ( attrName !== '' ) node.attrName = attrName;
+			if ( attrType !== '' ) node.attrType = attrType;
+			if ( name !== '' ) node.name = name;
+
+			return node;
+
+		},
+
+		parseSubNode: function ( name, node, subNode ) {
+
+			// special case: child node is single property
+			if ( subNode.singleProperty === true ) {
+
+				var value = subNode.propertyList[ 0 ];
+
+				if ( Array.isArray( value ) ) {
+
+					node[ subNode.name ] = subNode;
+
+					subNode.a = value;
+
+				} else {
+
+					node[ subNode.name ] = value;
+
+				}
+
+			} else if ( name === 'Connections' && subNode.name === 'C' ) {
+
+				var array = [];
+
+				subNode.propertyList.forEach( function ( property, i ) {
+
+					// first Connection is FBX type (OO, OP, etc.). We'll discard these
+					if ( i !== 0 ) array.push( property );
+
+				} );
+
+				if ( node.connections === undefined ) {
+
+					node.connections = [];
+
+				}
+
+				node.connections.push( array );
+
+			} else if ( subNode.name === 'Properties70' ) {
+
+				var keys = Object.keys( subNode );
+
+				keys.forEach( function ( key ) {
+
+					node[ key ] = subNode[ key ];
+
+				} );
+
+			} else if ( name === 'Properties70' && subNode.name === 'P' ) {
+
+				var innerPropName = subNode.propertyList[ 0 ];
+				var innerPropType1 = subNode.propertyList[ 1 ];
+				var innerPropType2 = subNode.propertyList[ 2 ];
+				var innerPropFlag = subNode.propertyList[ 3 ];
+				var innerPropValue;
+
+				if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' );
+				if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' );
+
+				if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) {
+
+					innerPropValue = [
+						subNode.propertyList[ 4 ],
+						subNode.propertyList[ 5 ],
+						subNode.propertyList[ 6 ]
+					];
+
+				} else {
+
+					innerPropValue = subNode.propertyList[ 4 ];
+
+				}
+
+				// this will be copied to parent, see above
+				node[ innerPropName ] = {
+
+					'type': innerPropType1,
+					'type2': innerPropType2,
+					'flag': innerPropFlag,
+					'value': innerPropValue
+
+				};
+
+			} else if ( node[ subNode.name ] === undefined ) {
+
+				if ( typeof subNode.id === 'number' ) {
+
+					node[ subNode.name ] = {};
+					node[ subNode.name ][ subNode.id ] = subNode;
+
+				} else {
+
+					node[ subNode.name ] = subNode;
+
+				}
+
+			} else {
+
+				if ( subNode.name === 'PoseNode' ) {
+
+					if ( ! Array.isArray( node[ subNode.name ] ) ) {
+
+						node[ subNode.name ] = [ node[ subNode.name ] ];
+
+					}
+
+					node[ subNode.name ].push( subNode );
+
+				} else if ( node[ subNode.name ][ subNode.id ] === undefined ) {
+
+					node[ subNode.name ][ subNode.id ] = subNode;
+
+				}
+
+			}
+
+		},
+
+		parseProperty: function ( reader ) {
+
+			var type = reader.getString( 1 );
+
+			switch ( type ) {
+
+				case 'C':
+					return reader.getBoolean();
+
+				case 'D':
+					return reader.getFloat64();
+
+				case 'F':
+					return reader.getFloat32();
+
+				case 'I':
+					return reader.getInt32();
+
+				case 'L':
+					return reader.getInt64();
+
+				case 'R':
+					var length = reader.getUint32();
+					return reader.getArrayBuffer( length );
+
+				case 'S':
+					var length = reader.getUint32();
+					return reader.getString( length );
+
+				case 'Y':
+					return reader.getInt16();
+
+				case 'b':
+				case 'c':
+				case 'd':
+				case 'f':
+				case 'i':
+				case 'l':
+
+					var arrayLength = reader.getUint32();
+					var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
+					var compressedLength = reader.getUint32();
+
+					if ( encoding === 0 ) {
+
+						switch ( type ) {
+
+							case 'b':
+							case 'c':
+								return reader.getBooleanArray( arrayLength );
+
+							case 'd':
+								return reader.getFloat64Array( arrayLength );
+
+							case 'f':
+								return reader.getFloat32Array( arrayLength );
+
+							case 'i':
+								return reader.getInt32Array( arrayLength );
+
+							case 'l':
+								return reader.getInt64Array( arrayLength );
+
+						}
+
+					}
+
+					if ( typeof Zlib === 'undefined' ) {
+
+						console.error( 'THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js' );
+
+					}
+
+					var inflate = new Zlib.Inflate( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef
+					var reader2 = new BinaryReader( inflate.decompress().buffer );
+
+					switch ( type ) {
+
+						case 'b':
+						case 'c':
+							return reader2.getBooleanArray( arrayLength );
+
+						case 'd':
+							return reader2.getFloat64Array( arrayLength );
+
+						case 'f':
+							return reader2.getFloat32Array( arrayLength );
+
+						case 'i':
+							return reader2.getInt32Array( arrayLength );
+
+						case 'l':
+							return reader2.getInt64Array( arrayLength );
+
+					}
+
+				default:
+					throw new Error( 'THREE.FBXLoader: Unknown property type ' + type );
+
+			}
+
+		}
+
+	};
+
+	function BinaryReader( buffer, littleEndian ) {
+
+		this.dv = new DataView( buffer );
+		this.offset = 0;
+		this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true;
+
+	}
+
+	BinaryReader.prototype = {
+
+		constructor: BinaryReader,
+
+		getOffset: function () {
+
+			return this.offset;
+
+		},
+
+		size: function () {
+
+			return this.dv.buffer.byteLength;
+
+		},
+
+		skip: function ( length ) {
+
+			this.offset += length;
+
+		},
+
+		// seems like true/false representation depends on exporter.
+		// true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54)
+		// then sees LSB.
+		getBoolean: function () {
+
+			return ( this.getUint8() & 1 ) === 1;
+
+		},
+
+		getBooleanArray: function ( size ) {
+
+			var a = [];
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a.push( this.getBoolean() );
+
+			}
+
+			return a;
+
+		},
+
+		getUint8: function () {
+
+			var value = this.dv.getUint8( this.offset );
+			this.offset += 1;
+			return value;
+
+		},
+
+		getInt16: function () {
+
+			var value = this.dv.getInt16( this.offset, this.littleEndian );
+			this.offset += 2;
+			return value;
+
+		},
+
+		getInt32: function () {
+
+			var value = this.dv.getInt32( this.offset, this.littleEndian );
+			this.offset += 4;
+			return value;
+
+		},
+
+		getInt32Array: function ( size ) {
+
+			var a = [];
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a.push( this.getInt32() );
+
+			}
+
+			return a;
+
+		},
+
+		getUint32: function () {
+
+			var value = this.dv.getUint32( this.offset, this.littleEndian );
+			this.offset += 4;
+			return value;
+
+		},
+
+		// JavaScript doesn't support 64-bit integer so calculate this here
+		// 1 << 32 will return 1 so using multiply operation instead here.
+		// There's a possibility that this method returns wrong value if the value
+		// is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER.
+		// TODO: safely handle 64-bit integer
+		getInt64: function () {
+
+			var low, high;
+
+			if ( this.littleEndian ) {
+
+				low = this.getUint32();
+				high = this.getUint32();
+
+			} else {
+
+				high = this.getUint32();
+				low = this.getUint32();
+
+			}
+
+			// calculate negative value
+			if ( high & 0x80000000 ) {
+
+				high = ~ high & 0xFFFFFFFF;
+				low = ~ low & 0xFFFFFFFF;
+
+				if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF;
+
+				low = ( low + 1 ) & 0xFFFFFFFF;
+
+				return - ( high * 0x100000000 + low );
+
+			}
+
+			return high * 0x100000000 + low;
+
+		},
+
+		getInt64Array: function ( size ) {
+
+			var a = [];
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a.push( this.getInt64() );
+
+			}
+
+			return a;
+
+		},
+
+		// Note: see getInt64() comment
+		getUint64: function () {
+
+			var low, high;
+
+			if ( this.littleEndian ) {
+
+				low = this.getUint32();
+				high = this.getUint32();
+
+			} else {
+
+				high = this.getUint32();
+				low = this.getUint32();
+
+			}
+
+			return high * 0x100000000 + low;
+
+		},
+
+		getFloat32: function () {
+
+			var value = this.dv.getFloat32( this.offset, this.littleEndian );
+			this.offset += 4;
+			return value;
+
+		},
+
+		getFloat32Array: function ( size ) {
+
+			var a = [];
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a.push( this.getFloat32() );
+
+			}
+
+			return a;
+
+		},
+
+		getFloat64: function () {
+
+			var value = this.dv.getFloat64( this.offset, this.littleEndian );
+			this.offset += 8;
+			return value;
+
+		},
+
+		getFloat64Array: function ( size ) {
+
+			var a = [];
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a.push( this.getFloat64() );
+
+			}
+
+			return a;
+
+		},
+
+		getArrayBuffer: function ( size ) {
+
+			var value = this.dv.buffer.slice( this.offset, this.offset + size );
+			this.offset += size;
+			return value;
+
+		},
+
+		getString: function ( size ) {
+
+			// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
+			var a = [];
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a[ i ] = this.getUint8();
+
+			}
+
+			var nullByte = a.indexOf( 0 );
+			if ( nullByte >= 0 ) a = a.slice( 0, nullByte );
+
+			return LoaderUtils.decodeText( new Uint8Array( a ) );
+
+		}
+
+	};
+
+	// FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format)
+	// and BinaryParser( FBX Binary format)
+	function FBXTree() {}
+
+	FBXTree.prototype = {
+
+		constructor: FBXTree,
+
+		add: function ( key, val ) {
+
+			this[ key ] = val;
+
+		},
+
+	};
+
+	// ************** UTILITY FUNCTIONS **************
+
+	function isFbxFormatBinary( buffer ) {
+
+		var CORRECT = 'Kaydara FBX Binary  \0';
+
+		return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length );
+
+	}
+
+	function isFbxFormatASCII( text ) {
+
+		var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
+
+		var cursor = 0;
+
+		function read( offset ) {
+
+			var result = text[ offset - 1 ];
+			text = text.slice( cursor + offset );
+			cursor ++;
+			return result;
+
+		}
+
+		for ( var i = 0; i < CORRECT.length; ++ i ) {
+
+			var num = read( 1 );
+			if ( num === CORRECT[ i ] ) {
+
+				return false;
+
+			}
+
+		}
+
+		return true;
+
+	}
+
+	function getFbxVersion( text ) {
+
+		var versionRegExp = /FBXVersion: (\d+)/;
+		var match = text.match( versionRegExp );
+		if ( match ) {
+
+			var version = parseInt( match[ 1 ] );
+			return version;
+
+		}
+		throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' );
+
+	}
+
+	// Converts FBX ticks into real time seconds.
+	function convertFBXTimeToSeconds( time ) {
+
+		return time / 46186158000;
+
+	}
+
+	var dataArray = [];
+
+	// extracts the data from the correct position in the FBX array based on indexing type
+	function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+
+		var index;
+
+		switch ( infoObject.mappingType ) {
+
+			case 'ByPolygonVertex' :
+				index = polygonVertexIndex;
+				break;
+			case 'ByPolygon' :
+				index = polygonIndex;
+				break;
+			case 'ByVertice' :
+				index = vertexIndex;
+				break;
+			case 'AllSame' :
+				index = infoObject.indices[ 0 ];
+				break;
+			default :
+				console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType );
+
+		}
+
+		if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ];
+
+		var from = index * infoObject.dataSize;
+		var to = from + infoObject.dataSize;
+
+		return slice( dataArray, infoObject.buffer, from, to );
+
+	}
+
+	var tempEuler = new Euler();
+	var tempVec = new Vector3();
+
+	// generate transformation from FBX transform data
+	// ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
+	// ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e
+	function generateTransform( transformData ) {
+
+		var lTranslationM = new Matrix4();
+		var lPreRotationM = new Matrix4();
+		var lRotationM = new Matrix4();
+		var lPostRotationM = new Matrix4();
+
+		var lScalingM = new Matrix4();
+		var lScalingPivotM = new Matrix4();
+		var lScalingOffsetM = new Matrix4();
+		var lRotationOffsetM = new Matrix4();
+		var lRotationPivotM = new Matrix4();
+
+		var lParentGX = new Matrix4();
+		var lGlobalT = new Matrix4();
+
+		var inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0;
+
+		if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) );
+
+		if ( transformData.preRotation ) {
+
+			var array = transformData.preRotation.map( _Math.degToRad );
+			array.push( transformData.eulerOrder );
+			lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+		}
+
+		if ( transformData.rotation ) {
+
+			var array = transformData.rotation.map( _Math.degToRad );
+			array.push( transformData.eulerOrder );
+			lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+		}
+
+		if ( transformData.postRotation ) {
+
+			var array = transformData.postRotation.map( _Math.degToRad );
+			array.push( transformData.eulerOrder );
+			lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+
+		}
+
+		if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) );
+
+		// Pivots and offsets
+		if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) );
+		if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) );
+		if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) );
+		if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) );
+
+		// parent transform
+		if ( transformData.parentMatrixWorld ) lParentGX = transformData.parentMatrixWorld;
+
+		// Global Rotation
+		var lLRM = lPreRotationM.multiply( lRotationM ).multiply( lPostRotationM );
+		var lParentGRM = new Matrix4();
+		lParentGX.extractRotation( lParentGRM );
+
+		// Global Shear*Scaling
+		var lParentTM = new Matrix4();
+		var lLSM;
+		var lParentGSM;
+		var lParentGRSM;
+
+		lParentTM.copyPosition( lParentGX );
+		lParentGRSM = lParentTM.getInverse( lParentTM ).multiply( lParentGX );
+		lParentGSM = lParentGRM.getInverse( lParentGRM ).multiply( lParentGRSM );
+		lLSM = lScalingM;
+
+		var lGlobalRS;
+		if ( inheritType === 0 ) {
+
+			lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM );
+
+		} else if ( inheritType === 1 ) {
+
+			lGlobalRS = lParentGRM.multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM );
+
+		} else {
+
+			var lParentLSM = new Matrix4().copy( lScalingM );
+
+			var lParentGSM_noLocal = lParentGSM.multiply( lParentLSM.getInverse( lParentLSM ) );
+
+			lGlobalRS = lParentGRM.multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM );
+
+		}
+
+		// Calculate the local transform matrix
+		var lTransform = lTranslationM.multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM.getInverse( lRotationPivotM ) ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM.getInverse( lScalingPivotM ) );
+
+		var lLocalTWithAllPivotAndOffsetInfo = new Matrix4().copyPosition( lTransform );
+
+		var lGlobalTranslation = lParentGX.multiply( lLocalTWithAllPivotAndOffsetInfo );
+		lGlobalT.copyPosition( lGlobalTranslation );
+
+		lTransform = lGlobalT.multiply( lGlobalRS );
+
+		return lTransform;
+
+	}
+
+	// Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
+	// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
+	function getEulerOrder( order ) {
+
+		order = order || 0;
+
+		var enums = [
+			'ZYX', // -> XYZ extrinsic
+			'YZX', // -> XZY extrinsic
+			'XZY', // -> YZX extrinsic
+			'ZXY', // -> YXZ extrinsic
+			'YXZ', // -> ZXY extrinsic
+			'XYZ', // -> ZYX extrinsic
+			//'SphericXYZ', // not possible to support
+		];
+
+		if ( order === 6 ) {
+
+			console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
+			return enums[ 0 ];
+
+		}
+
+		return enums[ order ];
+
+	}
+
+	// Parses comma separated list of numbers and returns them an array.
+	// Used internally by the TextParser
+	function parseNumberArray( value ) {
+
+		var array = value.split( ',' ).map( function ( val ) {
+
+			return parseFloat( val );
+
+		} );
+
+		return array;
+
+	}
+
+	function convertArrayBufferToString( buffer, from, to ) {
+
+		if ( from === undefined ) from = 0;
+		if ( to === undefined ) to = buffer.byteLength;
+
+		return LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) );
+
+	}
+
+	function append( a, b ) {
+
+		for ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) {
+
+			a[ j ] = b[ i ];
+
+		}
+
+	}
+
+	function slice( a, b, from, to ) {
+
+		for ( var i = from, j = 0; i < to; i ++, j ++ ) {
+
+			a[ j ] = b[ i ];
+
+		}
+
+		return a;
+
+	}
+
+	// inject array a2 into array a1 at index
+	function inject( a1, index, a2 ) {
+
+		return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
+
+	}
+
+	return FBXLoader;
+
+} )();
+
+export { FBXLoader };

+ 16 - 0
examples/jsm/loaders/GCodeLoader.d.ts

@@ -0,0 +1,16 @@
+import {
+  Group
+  LoadingManager
+} from '../../../src/Three';
+
+export class GCodeLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+  splitLayer: boolean;
+
+  load(url: string, onLoad: (object: Group) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+
+  parse(data: string) : Group;
+}

+ 243 - 0
examples/jsm/loaders/GCodeLoader.js

@@ -0,0 +1,243 @@
+/**
+ * GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications.
+ *
+ * Gcode files are composed by commands used by machines to create objects.
+ *
+ * @class GCodeLoader
+ * @param {Manager} manager Loading manager.
+ * @author tentone
+ * @author joewalnes
+ */
+
+import {
+	BufferGeometry,
+	DefaultLoadingManager,
+	Euler,
+	FileLoader,
+	Float32BufferAttribute,
+	Group,
+	LineBasicMaterial,
+	LineSegments
+} from "../../../build/three.module.js";
+
+var GCodeLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+	this.splitLayer = false;
+
+};
+
+GCodeLoader.prototype = {
+
+	constructor: GCodeLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var self = this;
+
+		var loader = new FileLoader( self.manager );
+		loader.setPath( self.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( self.parse( text ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( data ) {
+
+		var state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
+		var layers = [];
+
+		var currentLayer = undefined;
+
+		var pathMaterial = new LineBasicMaterial( { color: 0xFF0000 } );
+		pathMaterial.name = 'path';
+
+		var extrudingMaterial = new LineBasicMaterial( { color: 0x00FF00 } );
+		extrudingMaterial.name = 'extruded';
+
+		function newLayer( line ) {
+
+			currentLayer = { vertex: [], pathVertex: [], z: line.z };
+			layers.push( currentLayer );
+
+		}
+
+		//Create lie segment between p1 and p2
+		function addSegment( p1, p2 ) {
+
+			if ( currentLayer === undefined ) {
+
+				newLayer( p1 );
+
+			}
+
+			if ( line.extruding ) {
+
+				currentLayer.vertex.push( p1.x, p1.y, p1.z );
+				currentLayer.vertex.push( p2.x, p2.y, p2.z );
+
+			} else {
+
+				currentLayer.pathVertex.push( p1.x, p1.y, p1.z );
+				currentLayer.pathVertex.push( p2.x, p2.y, p2.z );
+
+			}
+
+		}
+
+		function delta( v1, v2 ) {
+
+			return state.relative ? v2 : v2 - v1;
+
+		}
+
+		function absolute( v1, v2 ) {
+
+			return state.relative ? v1 + v2 : v2;
+
+		}
+
+		var lines = data.replace( /;.+/g, '' ).split( '\n' );
+
+		for ( var i = 0; i < lines.length; i ++ ) {
+
+			var tokens = lines[ i ].split( ' ' );
+			var cmd = tokens[ 0 ].toUpperCase();
+
+			//Argumments
+			var args = {};
+			tokens.splice( 1 ).forEach( function ( token ) {
+
+				if ( token[ 0 ] !== undefined ) {
+
+					var key = token[ 0 ].toLowerCase();
+					var value = parseFloat( token.substring( 1 ) );
+					args[ key ] = value;
+
+				}
+
+			} );
+
+			//Process commands
+			//G0/G1 – Linear Movement
+			if ( cmd === 'G0' || cmd === 'G1' ) {
+
+				var line = {
+					x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
+					y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
+					z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
+					e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
+					f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
+				};
+
+				//Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
+				if ( delta( state.e, line.e ) > 0 ) {
+
+					line.extruding = delta( state.e, line.e ) > 0;
+
+					if ( currentLayer == undefined || line.z != currentLayer.z ) {
+
+						newLayer( line );
+
+					}
+
+				}
+
+				addSegment( state, line );
+				state = line;
+
+			} else if ( cmd === 'G2' || cmd === 'G3' ) {
+
+				//G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
+				//console.warn( 'THREE.GCodeLoader: Arc command not supported' );
+
+			} else if ( cmd === 'G90' ) {
+
+				//G90: Set to Absolute Positioning
+				state.relative = false;
+
+			} else if ( cmd === 'G91' ) {
+
+				//G91: Set to state.relative Positioning
+				state.relative = true;
+
+			} else if ( cmd === 'G92' ) {
+
+				//G92: Set Position
+				var line = state;
+				line.x = args.x !== undefined ? args.x : line.x;
+				line.y = args.y !== undefined ? args.y : line.y;
+				line.z = args.z !== undefined ? args.z : line.z;
+				line.e = args.e !== undefined ? args.e : line.e;
+				state = line;
+
+			} else {
+
+				//console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
+
+			}
+
+		}
+
+		function addObject( vertex, extruding ) {
+
+			var geometry = new BufferGeometry();
+			geometry.addAttribute( 'position', new Float32BufferAttribute( vertex, 3 ) );
+
+			var segments = new LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
+			segments.name = 'layer' + i;
+			object.add( segments );
+
+		}
+
+		var object = new Group();
+		object.name = 'gcode';
+
+		if ( this.splitLayer ) {
+
+			for ( var i = 0; i < layers.length; i ++ ) {
+
+				var layer = layers[ i ];
+				addObject( layer.vertex, true );
+				addObject( layer.pathVertex, false );
+
+			}
+
+		} else {
+
+			var vertex = [], pathVertex = [];
+
+			for ( var i = 0; i < layers.length; i ++ ) {
+
+				var layer = layers[ i ];
+
+				vertex = vertex.concat( layer.vertex );
+				pathVertex = pathVertex.concat( layer.pathVertex );
+
+			}
+
+			addObject( vertex, true );
+			addObject( pathVertex, false );
+
+		}
+
+		object.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) );
+
+		return object;
+
+	}
+
+};
+
+export { GCodeLoader };

+ 2 - 2
examples/jsm/loaders/GLTFLoader.d.ts

@@ -21,7 +21,7 @@ export class GLTFLoader {
   load(url: string, onLoad: (gltf: GLTF) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
   setPath(path: string) : GLTFLoader;
   setResourcePath(path: string) : GLTFLoader;
-  setCrossOrigin(value: string): void;
-  setDRACOLoader(dracoLoader: object): void;
+  setCrossOrigin(value: string): GLTFLoader;
+  setDRACOLoader(dracoLoader: object): GLTFLoader;
   parse(data: ArrayBuffer | string, path: string, onLoad: (gltf: GLTF) => void, onError?: (event: ErrorEvent) => void) : void;
 }

+ 15 - 0
examples/jsm/loaders/KMZLoader.d.ts

@@ -0,0 +1,15 @@
+import {
+  LoadingManager
+} from '../../../src/Three';
+
+import { Collada } from './ColladaLoader';
+
+export class KMZLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  load(url: string, onLoad: (kmz: Collada) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  setPath(value: string): this;
+  parse(data: ArrayBuffer): Collada;
+}

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