Browse Source

Merge pull request #4 from mrdoob/dev

Update dev branch
Temdog007 6 years ago
parent
commit
8e50ec0b35
93 changed files with 5971 additions and 2222 deletions
  1. 15 0
      .editorconfig
  2. 189 38
      build/three.js
  3. 264 262
      build/three.min.js
  4. 188 38
      build/three.module.js
  5. 4 5
      docs/api/en/loaders/AnimationLoader.html
  6. 10 0
      docs/api/en/loaders/ImageBitmapLoader.html
  7. 1 1
      docs/api/en/loaders/ImageLoader.html
  8. 24 0
      docs/api/en/loaders/Loader.html
  9. 1 1
      docs/api/en/materials/Material.html
  10. 1 1
      docs/api/en/math/Matrix3.html
  11. 1 1
      docs/api/en/math/Matrix4.html
  12. 7 0
      docs/api/en/math/Quaternion.html
  13. 7 0
      docs/api/en/textures/Texture.html
  14. 4 7
      docs/api/zh/loaders/AnimationLoader.html
  15. 10 0
      docs/api/zh/loaders/ImageBitmapLoader.html
  16. 20 0
      docs/api/zh/loaders/Loader.html
  17. 1 1
      docs/api/zh/materials/Material.html
  18. 3 3
      docs/api/zh/math/Matrix3.html
  19. 5 5
      docs/api/zh/math/Matrix4.html
  20. 8 1
      docs/api/zh/math/Quaternion.html
  21. 8 0
      docs/api/zh/textures/Texture.html
  22. 1 0
      docs/examples/exporters/GLTFExporter.html
  23. 3 0
      docs/examples/loaders/GLTFLoader.html
  24. 48 0
      editor/css/dark.css
  25. 48 0
      editor/css/light.css
  26. BIN
      editor/images/icon.png
  27. BIN
      editor/images/icon.xcf
  28. 20 5
      editor/index.html
  29. 1 1
      editor/js/Editor.js
  30. 0 55
      editor/main.js
  31. 13 0
      editor/manifest.json
  32. 197 0
      editor/sw.js
  33. 44 39
      examples/files.js
  34. 73 0
      examples/js/MarchingCubes.js
  35. 41 19
      examples/js/exporters/GLTFExporter.js
  36. 0 1
      examples/js/loaders/GLTFLoader.js
  37. 1 4
      examples/js/loaders/ctm/CTMLoader.js
  38. 1 2
      examples/js/loaders/sea3d/SEA3D.js
  39. 26 0
      examples/js/nodes/materials/NodeMaterial.js
  40. 1 1
      examples/js/objects/Fire.js
  41. 32 34
      examples/js/objects/LightningStorm.js
  42. 1 2
      examples/js/objects/Sky.js
  43. 2 1
      examples/js/renderers/CSS2DRenderer.js
  44. 3 3
      examples/js/renderers/Projector.js
  45. 71 0
      examples/jsm/controls/MapControls.d.ts
  46. 70 0
      examples/jsm/controls/OrbitControls.d.ts
  47. 24 5
      examples/jsm/controls/OrbitControls.js
  48. 47 0
      examples/jsm/controls/TrackballControls.d.ts
  49. 1 1
      examples/jsm/controls/TrackballControls.js
  50. 7 0
      examples/jsm/exporters/GLTFExporter.d.ts
  51. 2256 0
      examples/jsm/exporters/GLTFExporter.js
  52. 27 0
      examples/jsm/loaders/GLTFLoader.d.ts
  53. 222 444
      examples/jsm/loaders/GLTFLoader.js
  54. 104 0
      examples/jsm/loaders/MTLLoader.d.ts
  55. 602 0
      examples/jsm/loaders/MTLLoader.js
  56. 19 0
      examples/jsm/loaders/OBJLoader.d.ts
  57. 0 2
      examples/misc_lookat.html
  58. 212 0
      examples/webgl2_materials_texture2darray.html
  59. 188 190
      examples/webgl2_materials_texture3d.html
  60. 0 212
      examples/webgl2_materials_texture3d_volume.html
  61. 24 46
      examples/webgl2_sandbox.html
  62. 6 6
      examples/webgl_gpgpu_birds.html
  63. 2 4
      examples/webgl_lights_rectarealight.html
  64. 3 7
      examples/webgl_loader_svg.html
  65. 492 0
      examples/webgl_materials_envmaps_parallax.html
  66. 0 2
      examples/webgl_materials_shaders_fresnel.html
  67. 0 2
      examples/webgl_postprocessing_dof.html
  68. 13 13
      examples/webgl_shaders_ocean.html
  69. 4 694
      package-lock.json
  70. 1 3
      package.json
  71. 1 0
      rollup.config.js
  72. 2 0
      src/Three.d.ts
  73. 1 0
      src/Three.js
  74. 3 0
      src/audio/Audio.d.ts
  75. 6 5
      src/audio/PositionalAudio.d.ts
  76. 80 17
      src/core/BufferGeometry.js
  77. 2 1
      src/core/InstancedBufferAttribute.d.ts
  78. 2 1
      src/loaders/AnimationLoader.d.ts
  79. 2 2
      src/loaders/AnimationLoader.js
  80. 10 1
      src/loaders/ImageBitmapLoader.js
  81. 9 0
      src/materials/Material.d.ts
  82. 4 4
      src/math/Matrix3.d.ts
  83. 2 2
      src/math/Matrix4.d.ts
  84. 3 0
      src/math/Quaternion.d.ts
  85. 1 12
      src/objects/Line.js
  86. 12 2
      src/renderers/WebGLRenderer.js
  87. 11 2
      src/renderers/webgl/WebGLBackground.js
  88. 29 4
      src/renderers/webgl/WebGLTextures.js
  89. 19 0
      src/renderers/webgl/WebGLUniforms.js
  90. 1 1
      src/renderers/webvr/WebXRManager.js
  91. 12 0
      src/textures/DataTexture2DArray.d.ts
  92. 28 0
      src/textures/DataTexture2DArray.js
  93. 9 6
      utils/modularize.js

+ 15 - 0
.editorconfig

@@ -0,0 +1,15 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.{js,ts,html}]
+charset = utf-8
+indent_style = tab
+indent_size = 2
+
+[*.{js,ts}]
+trim_trailing_whitespace = true

+ 189 - 38
build/three.js

@@ -11780,35 +11780,59 @@
 
 		computeBoundingBox: function () {
 
-			if ( this.boundingBox === null ) {
+			var box = new Box3();
 
-				this.boundingBox = new Box3();
+			return function computeBoundingBox() {
 
-			}
+				if ( this.boundingBox === null ) {
 
-			var position = this.attributes.position;
+					this.boundingBox = new Box3();
 
-			if ( position !== undefined ) {
+				}
 
-				this.boundingBox.setFromBufferAttribute( position );
+				var position = this.attributes.position;
+				var morphAttributesPosition = this.morphAttributes.position;
 
-			} else {
+				if ( position !== undefined ) {
 
-				this.boundingBox.makeEmpty();
+					this.boundingBox.setFromBufferAttribute( position );
 
-			}
+					// process morph attributes if present
 
-			if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
+					if ( morphAttributesPosition ) {
 
-				console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
+						for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
 
-			}
+							var morphAttribute = morphAttributesPosition[ i ];
+							box.setFromBufferAttribute( morphAttribute );
 
-		},
+							this.boundingBox.expandByPoint( box.min );
+							this.boundingBox.expandByPoint( box.max );
+
+						}
+
+					}
+
+				} else {
+
+					this.boundingBox.makeEmpty();
+
+				}
+
+				if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
+
+					console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
+
+				}
+
+			};
+
+		}(),
 
 		computeBoundingSphere: function () {
 
 			var box = new Box3();
+			var boxMorphTargets = new Box3();
 			var vector = new Vector3();
 
 			return function computeBoundingSphere() {
@@ -11820,28 +11844,67 @@
 				}
 
 				var position = this.attributes.position;
+				var morphAttributesPosition = this.morphAttributes.position;
 
 				if ( position ) {
 
+					// first, find the center of the bounding sphere
+
 					var center = this.boundingSphere.center;
 
 					box.setFromBufferAttribute( position );
+
+					// process morph attributes if present
+
+					if ( morphAttributesPosition ) {
+
+						for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+							var morphAttribute = morphAttributesPosition[ i ];
+							boxMorphTargets.setFromBufferAttribute( morphAttribute );
+
+							box.expandByPoint( boxMorphTargets.min );
+							box.expandByPoint( boxMorphTargets.max );
+
+						}
+
+					}
+
 					box.getCenter( center );
 
-					// hoping to find a boundingSphere with a radius smaller than the
+					// second, try to find a boundingSphere with a radius smaller than the
 					// boundingSphere of the boundingBox: sqrt(3) smaller in the best case
 
 					var maxRadiusSq = 0;
 
 					for ( var i = 0, il = position.count; i < il; i ++ ) {
 
-						vector.x = position.getX( i );
-						vector.y = position.getY( i );
-						vector.z = position.getZ( i );
+						vector.fromBufferAttribute( position, i );
+
 						maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
 
 					}
 
+					// process morph attributes if present
+
+					if ( morphAttributesPosition ) {
+
+						for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+							var morphAttribute = morphAttributesPosition[ i ];
+
+							for ( var j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
+
+								vector.fromBufferAttribute( morphAttribute, i );
+
+								maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+
+							}
+
+						}
+
+					}
+
 					this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
 
 					if ( isNaN( this.boundingSphere.radius ) ) {
@@ -14750,6 +14813,18 @@
 
 			var background = scene.background;
 
+			// Ignore background in AR
+			// TODO: Reconsider this.
+
+			var vr = renderer.vr;
+			var session = vr.getSession && vr.getSession();
+
+			if ( session && session.environmentBlendMode === 'additive' ) {
+
+				background = null;
+
+			}
+
 			if ( background === null ) {
 
 				setClear( clearColor, clearAlpha );
@@ -15845,6 +15920,30 @@
 
 	} );
 
+	/**
+	 * @author Takahiro https://github.com/takahirox
+	 */
+
+	function DataTexture2DArray( data, width, height, depth ) {
+
+		Texture.call( this, null );
+
+		this.image = { data: data, width: width, height: height, depth: depth };
+
+		this.magFilter = NearestFilter;
+		this.minFilter = NearestFilter;
+
+		this.wrapR = ClampToEdgeWrapping;
+
+		this.generateMipmaps = false;
+		this.flipY = false;
+
+	}
+
+	DataTexture2DArray.prototype = Object.create( Texture.prototype );
+	DataTexture2DArray.prototype.constructor = DataTexture2DArray;
+	DataTexture2DArray.prototype.isDataTexture2DArray = true;
+
 	/**
 	 * @author Artur Trzesiok
 	 */
@@ -15929,6 +16028,7 @@
 	 */
 
 	var emptyTexture = new Texture();
+	var emptyTexture2dArray = new DataTexture2DArray();
 	var emptyTexture3d = new DataTexture3D();
 	var emptyCubeTexture = new CubeTexture();
 
@@ -16265,6 +16365,22 @@
 
 	}
 
+	function setValueT2DArray1( gl, v, renderer ) {
+
+		var cache = this.cache;
+		var unit = renderer.allocTextureUnit();
+
+		if ( cache[ 0 ] !== unit ) {
+
+			gl.uniform1i( this.addr, unit );
+			cache[ 0 ] = unit;
+
+		}
+
+		renderer.setTexture2DArray( v || emptyTexture2dArray, unit );
+
+	}
+
 	function setValueT3D1( gl, v, renderer ) {
 
 		var cache = this.cache;
@@ -16353,6 +16469,7 @@
 			case 0x8b5e: case 0x8d66: return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES
 			case 0x8b5f: return setValueT3D1; // SAMPLER_3D
 			case 0x8b60: return setValueT6; // SAMPLER_CUBE
+			case 0x8DC1: return setValueT2DArray1; // SAMPLER_2D_ARRAY
 
 			case 0x1404: case 0x8b56: return setValue1i; // INT, BOOL
 			case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2
@@ -20161,7 +20278,7 @@
 
 					console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
 
-					return useOffscreenCanvas ? canvas.transferToImageBitmap() : canvas;
+					return canvas;
 
 				} else {
 
@@ -20395,6 +20512,22 @@
 
 		}
 
+		function setTexture2DArray( texture, slot ) {
+
+			var textureProperties = properties.get( texture );
+
+			if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+				uploadTexture( textureProperties, texture, slot );
+				return;
+
+			}
+
+			state.activeTexture( 33984 + slot );
+			state.bindTexture( 35866, textureProperties.__webglTexture );
+
+		}
+
 		function setTexture3D( texture, slot ) {
 
 			var textureProperties = properties.get( texture );
@@ -20547,7 +20680,7 @@
 				_gl.texParameteri( textureType, 10242, utils.convert( texture.wrapS ) );
 				_gl.texParameteri( textureType, 10243, utils.convert( texture.wrapT ) );
 
-				if ( textureType === 32879 ) {
+				if ( textureType === 32879 || textureType === 35866 ) {
 
 					_gl.texParameteri( textureType, 32882, utils.convert( texture.wrapR ) );
 
@@ -20561,7 +20694,7 @@
 				_gl.texParameteri( textureType, 10242, 33071 );
 				_gl.texParameteri( textureType, 10243, 33071 );
 
-				if ( textureType === 32879 ) {
+				if ( textureType === 32879 || textureType === 35866 ) {
 
 					_gl.texParameteri( textureType, 32882, 33071 );
 
@@ -20620,7 +20753,10 @@
 
 		function uploadTexture( textureProperties, texture, slot ) {
 
-			var textureType = ( texture.isDataTexture3D ) ? 32879 : 3553;
+			var textureType = 3553;
+
+			if ( texture.isDataTexture2DArray ) textureType = 35866;
+			if ( texture.isDataTexture3D ) textureType = 32879;
 
 			initTexture( textureProperties, texture );
 
@@ -20752,6 +20888,11 @@
 
 				textureProperties.__maxMipLevel = mipmaps.length - 1;
 
+			} else if ( texture.isDataTexture2DArray ) {
+
+				state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+				textureProperties.__maxMipLevel = 0;
+
 			} else if ( texture.isDataTexture3D ) {
 
 				state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
@@ -21150,6 +21291,7 @@
 		}
 
 		this.setTexture2D = setTexture2D;
+		this.setTexture2DArray = setTexture2DArray;
 		this.setTexture3D = setTexture3D;
 		this.setTextureCube = setTextureCube;
 		this.setTextureCubeDynamic = setTextureCubeDynamic;
@@ -23663,7 +23805,11 @@
 						var geometry = objects.update( object );
 						var material = object.material;
 
-						currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+						if ( material.visible ) {
+
+							currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+						}
 
 					}
 
@@ -24849,6 +24995,12 @@
 
 		}() );
 
+		this.setTexture2DArray = function ( texture, slot ) {
+
+			textures.setTexture2DArray( texture, slot );
+
+		};
+
 		this.setTexture3D = function ( texture, slot ) {
 
 			textures.setTexture3D( texture, slot );
@@ -24988,7 +25140,7 @@
 			if ( isCube ) {
 
 				var textureProperties = properties.get( renderTarget.texture );
-				_gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace || 0, textureProperties.__webglTexture, activeMipMapLevel || 0 );
+				_gl.framebufferTexture2D( 36160, 36064, 34069 + ( activeCubeFace || 0 ), textureProperties.__webglTexture, activeMipMapLevel || 0 );
 
 			}
 
@@ -26470,20 +26622,9 @@
 
 		}() ),
 
-		copy: function ( source ) {
-
-			Object3D.prototype.copy.call( this, source );
-
-			this.geometry.copy( source.geometry );
-			this.material.copy( source.material );
-
-			return this;
-
-		},
-
 		clone: function () {
 
-			return new this.constructor().copy( this );
+			return new this.constructor( this.geometry, this.material ).copy( this );
 
 		}
 
@@ -34579,7 +34720,7 @@
 
 		},
 
-		parse: function ( json, onLoad ) {
+		parse: function ( json ) {
 
 			var animations = [];
 
@@ -34591,7 +34732,7 @@
 
 			}
 
-			onLoad( animations );
+			return animations;
 
 		},
 
@@ -39097,7 +39238,16 @@
 
 			} ).then( function ( blob ) {
 
-				return createImageBitmap( blob, scope.options );
+				if ( scope.options === undefined ) {
+
+					// Workaround for FireFox. It causes an error if you pass options.
+					return createImageBitmap( blob );
+
+				} else {
+
+					return createImageBitmap( blob, scope.options );
+
+				}
 
 			} ).then( function ( imageBitmap ) {
 
@@ -47966,6 +48116,7 @@
 	exports.Group = Group;
 	exports.VideoTexture = VideoTexture;
 	exports.DataTexture = DataTexture;
+	exports.DataTexture2DArray = DataTexture2DArray;
 	exports.DataTexture3D = DataTexture3D;
 	exports.CompressedTexture = CompressedTexture;
 	exports.CubeTexture = CubeTexture;

File diff suppressed because it is too large
+ 264 - 262
build/three.min.js


File diff suppressed because it is too large
+ 188 - 38
build/three.module.js


+ 4 - 5
docs/api/en/loaders/AnimationLoader.html

@@ -65,19 +65,18 @@
 		<p>
 		[page:String url] — the path or URL to the file. This can also be a
 			[link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].<br />
-		[page:Function onLoad] — Will be called when load completes. The argument will be the loaded [page:Animation animation].<br />
+		[page:Function onLoad] — Will be called when load completes. The argument will be the loaded [page:AnimationClip animation clips].<br />
 		[page:Function onProgress] — Will be called while load progresses. The argument will be the XMLHttpRequest instance, which contains .[page:Integer total] and .[page:Integer loaded] bytes.<br />
 		[page:Function onError] — Will be called if load errors.<br /><br />
 
 		Begin loading from url and pass the loaded animation to onLoad.
 		</p>
 
-		<h3>[method:null parse]( [param:JSON json], [param:Function onLoad] )</h3>
+		<h3>[method:Array parse]( [param:JSON json] )</h3>
 		<p>
-		[page:JSON json] — required<br />
-		[page:Function onLoad] — Will be called when parsing completes. <br /><br />
+		[page:JSON json] — required<br /><br />
 
-		Parse the JSON object and pass the result to onLoad. Individual clips in the object will
+		Parse the JSON object and return an array of animation clips. Individual clips in the object will
 		be parsed with [page:AnimationClip.parse].
 		</p>
 

+ 10 - 0
docs/api/en/loaders/ImageBitmapLoader.html

@@ -16,6 +16,13 @@
 			Unlike [page:FileLoader], [name] does not avoid multiple concurrent requests to the same URL.
 		</p>
 
+		<p>
+			Note that [page:Texture.flipY] and [page:Texture.premultiplyAlpha] with [link:https://developer.mozilla.org/de/docs/Web/API/ImageBitmap ImageBitmap] are ignored.
+			[link:https://developer.mozilla.org/de/docs/Web/API/ImageBitmap ImageBitmap] needs these configuration on bitmap creation
+			unlike regular images need them on uploading to GPU. You need to set the equivalent options via [page:ImageBitmapLoader.setOptions]
+			instead. Refer to [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.10 WebGL specification] for the detail.
+		</p>
+
 		<h2>Example</h2>
 
 		<p>
@@ -26,6 +33,9 @@
 		// instantiate a loader
 		var loader = new THREE.ImageBitmapLoader();
 
+		// set options if needed
+		loader.setOptions( { imageOrientation: 'flipY' } );
+
 		// load a image resource
 		loader.load(
 			// resource URL

+ 1 - 1
docs/api/en/loaders/ImageLoader.html

@@ -12,7 +12,7 @@
 
 		<p class="desc">
 			A loader for loading an [page:Image].
-			This uses the [page:FileLoader] internally for loading files, and is used internally by the
+			This is used internally by the
 			[page:CubeTextureLoader], [page:ObjectLoader] and [page:TextureLoader].
 		</p>
 

+ 24 - 0
docs/api/en/loaders/Loader.html

@@ -62,6 +62,30 @@
 		Creates an array of [page:Material] based on the array of parameters m. The index of the parameters decide the correct index of the materials.
 		</p>
 
+		<h2>Handlers</h2>
+
+		<p>
+		*[name].Handlers* is a special object normally used by other loaders like [page:GLTFLoader] or [page:MTLLoader]. It provides an
+		API that allows the definition of special mappings: What loaders should be used in order to load specific files. A typical use case
+		is to overwrite the default loader for textures.<br /><br />
+
+		Note: It's only possible to use *[name].Handlers* if the respective loader support the usage.
+		</p>
+
+		<h3>[method:null add]( [param:Object regex], [param:Loader loader] )</h3>
+		<p>
+		[page:Object regex] — A regular expression.<br />
+		[page:Loader loader] — The loader.
+		<p>
+		Registers a loader with the given regular expression.
+		</p>
+
+		<h3>[method:null get]( [param:String file] )</h3>
+		<p>
+		[page:String file] — The file path.
+		<p>
+		Can be used to retrieve the registered loader for the given file path.
+		</p>
 
 		<h2>Source</h2>
 

+ 1 - 1
docs/api/en/materials/Material.html

@@ -295,7 +295,7 @@
 		These needs to be disposed by [page:Texture Texture].
 		</p>
 
-		<h3>[method:null onBeforeCompile]( [param:Object shader], [param:WebGLRenderer renderer] )</h3>
+		<h3>[method:null onBeforeCompile]( [param:Shader shader], [param:WebGLRenderer renderer] )</h3>
 		<p>
 		An optional callback that is executed immediately before the shader program is compiled.
 		This function is called with the shader source code as a parameter. Useful for the modification of built-in materials.

+ 1 - 1
docs/api/en/math/Matrix3.html

@@ -55,7 +55,7 @@ m.elements = [ 11, 21, 31,
 
 		<h2>Properties</h2>
 
-		<h3>[property:Float32Array elements]</h3>
+		<h3>[property:Array elements]</h3>
 		<p>
 		A [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order column-major]
 		 list of matrix values.

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

@@ -90,7 +90,7 @@ m.elements = [ 11, 21, 31, 41,
 
 		<h2>Properties</h2>
 
-		<h3>[property:Float32Array elements]</h3>
+		<h3>[property:Array elements]</h3>
 		<p>
 		A [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order column-major]
 		 list of matrix values.

+ 7 - 0
docs/api/en/math/Quaternion.html

@@ -44,6 +44,13 @@
 
 		<h2>Properties</h2>
 
+		<h3>[property:Boolean isQuaternion]</h3>
+		<p>
+			Used to check whether this or derived classes are Quaternions. Default is *true*.<br /><br />
+
+			You should not change this, as it is used internally for optimisation.
+		</p>
+
 		<h3>[property:Float x]</h3>
 		<p>Changing this property will result in [page:.onChangeCallback onChangeCallback] being called.</p>
 

+ 7 - 0
docs/api/en/textures/Texture.html

@@ -55,6 +55,13 @@
 		as long as video is playing - the [page:VideoTexture VideoTexture] class handles this automatically.
 		</p>
 
+		<h3>[property:Boolean isTexture]</h3>
+		<p>
+			Used to check whether this or derived classes are Textures. Default is *true*.<br /><br />
+
+			You should not change this, as it is used internally for optimisation.
+		</p>
+
 		<h3>[property:array mipmaps]</h3>
 		<p>
 		Array of user-specified mipmaps (optional).

+ 4 - 7
docs/api/zh/loaders/AnimationLoader.html

@@ -65,20 +65,17 @@
 		<p>
 		[page:String url] — 文件的URL或者路径,也可以为
 			[link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].<br />
-		[page:Function onLoad] — 加载完成时将调用。回调参数为将要加载的[page:Animation animation].<br />
+		[page:Function onLoad] — 加载完成时将调用。回调参数为将要加载的[page:AnimationClip animation clips].<br />
 		[page:Function onProgress] —  将在加载过程中进行调用。参数为XMLHttpRequest实例,实例包含[page:Integer total]和[page:Integer loaded]字节。<br />
 		[page:Function onError] — 在加载错误时被调用。<br /><br />
 
             从URL中进行加载并将动画传递给onLoad。
 		</p>
 
-		<h3>[method:null parse]( [param:JSON json], [param:Function onLoad] )</h3>
+		<h3>[method:Array parse]( [param:JSON json] )</h3>
 		<p>
-		[page:JSON json] — 请求<br />
-		[page:Function onLoad] — 当解析完成时,将被调用 <br /><br />
-
-            解析JSON对象并将结果传递给onLoad。对象中的单个片段将
-            用[page [page:AnimationClip.parse]进行解析。
+		[page:JSON json] — 请求 <br /><br />
+			TODO
 		</p>
 
 		<h2>源</h2>

+ 10 - 0
docs/api/zh/loaders/ImageBitmapLoader.html

@@ -16,6 +16,13 @@
 			不像[page:FileLoader], [name]无需避免对同一的URL进行多次请求。
 		</p>
 
+		<p>
+			Note that [page:Texture.flipY] and [page:Texture.premultiplyAlpha] with [link:https://developer.mozilla.org/de/docs/Web/API/ImageBitmap ImageBitmap] are ignored.
+			[link:https://developer.mozilla.org/de/docs/Web/API/ImageBitmap ImageBitmap] needs these configuration on bitmap creation
+			unlike regular images need them on uploading to GPU. You need to set the equivalent options via [page:ImageBitmapLoader.setOptions]
+			instead. Refer to [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.10 WebGL specification] for the detail.
+		</p>
+
 		<h2>例子</h2>
 
 		<p>
@@ -26,6 +33,9 @@
 		// 初始化一个加载器
 		var loader = new THREE.ImageBitmapLoader();
 
+		// set options if needed
+		loader.setOptions( { imageOrientation: 'flipY' } );
+
 		// 加载一个图片资源
 		loader.load(
 			// 资源的URL

+ 20 - 0
docs/api/zh/loaders/Loader.html

@@ -61,6 +61,26 @@
 		基于参数数组m,来创建 [page:Material] 数组. 参数索引与材质的索引一致。
 		</p>
 
+		<h2>Handlers</h2>
+
+		<p>
+		TODO
+		</p>
+
+		<h3>[method:null add]( [param:Object regex], [param:Loader loader] )</h3>
+		<p>
+		[page:Object regex] — TODO<br />
+		[page:Loader loader] — TODO
+		<p>
+		TODO
+		</p>
+
+		<h3>[method:null get]( [param:String file] )</h3>
+		<p>
+		[page:String file] — TODO
+		<p>
+		TODO
+		</p>
 
 		<h2>Source</h2>
 

+ 1 - 1
docs/api/zh/materials/Material.html

@@ -243,7 +243,7 @@
 <p> 处理材质。材质的纹理不会被处理。需要通过[page:Texture Texture]处理。
 </p>
 
-<h3>[method:null onBeforeCompile]( [param:Object shader], [param:WebGLRenderer renderer] )</h3>
+<h3>[method:null onBeforeCompile]( [param:Shader shader], [param:WebGLRenderer renderer] )</h3>
 <p> 在编译shader程序之前立即执行的可选回调。此函数使用shader源码作为参数。用于修改内置材质。
 </p>
 

+ 3 - 3
docs/api/zh/math/Matrix3.html

@@ -53,7 +53,7 @@ m.elements = [ 11, 21, 31,
 
 		<h2>属性(Properties)</h2>
 
-		<h3>[property:Float32Array elements]</h3>
+		<h3>[property:Array elements]</h3>
 		<p>
 			矩阵列优先[link:https://en.wikipedia.org/wiki/Row-_and_column-major_order column-major]列表。
 		</p>
@@ -61,7 +61,7 @@ m.elements = [ 11, 21, 31,
 		<h3>[property:Boolean isMatrix3]</h3>
 		<p>
 				用于判定此对象或者此类的派生对象是否是三维矩阵。默认值为 *true*。<br /><br />
-			
+
 				不应该改变该值,因为它在内部用于优化。
 		</p>
 
@@ -103,7 +103,7 @@ m.elements = [ 11, 21, 31,
 		<p>
 		[page:Matrix3 m] - 取逆的矩阵。<br />
 		[page:Boolean throwOnDegenerate] - (optional) 如果设置为true,如果矩阵是退化的(如果不可逆的话),则会抛出一个错误。<br /><br />
-		
+
 		使用逆矩阵计算方法[link:https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution analytic method],
 		将当前矩阵设置为给定矩阵的逆矩阵[link:https://en.wikipedia.org/wiki/Invertible_matrix inverse],如果[page:Boolean throwOnDegenerate]
 		参数没有设置且给定矩阵不可逆,那么将当前矩阵设置为3X3单位矩阵。

+ 5 - 5
docs/api/zh/math/Matrix4.html

@@ -83,7 +83,7 @@ m.elements = [ 11, 21, 31, 41,
 
 		<h2>属性(Properties)</h2>
 
-		<h3>[property:Float32Array elements]</h3>
+		<h3>[property:Array elements]</h3>
 		<p>
 		矩阵列优先[link:https://en.wikipedia.org/wiki/Row-_and_column-major_order column-major]列表。
 		</p>
@@ -91,7 +91,7 @@ m.elements = [ 11, 21, 31, 41,
 		<h3>[property:Boolean isMatrix4]</h3>
 		<p>
 			用于判定此对象或者此类的派生对象是否是三维矩阵。默认值为 *true*。<br /><br />
-		
+
 			不应该改变该值,因为它在内部用于优化。
 		</p>
 
@@ -168,7 +168,7 @@ zAxis = (c, g, k)
 		<p>
 			[page:Array array] - 用来存储设置元素数据的数组<br />
 			[page:Integer offset] - (可选参数) 数组的偏移量,默认值为 0。<br /><br />
-	
+
 			使用基于列优先格式[link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order column-major]的数组来设置该矩阵。
 			</p>
 
@@ -176,7 +176,7 @@ zAxis = (c, g, k)
 		<p>
 			[page:Matrix3 m] - 取逆的矩阵。<br />
 			[page:Boolean throwOnDegenerate] - (optional) 如果设置为true,如果矩阵是退化的(如果不可逆的话),则会抛出一个错误。<br /><br />
-			
+
 			使用逆矩阵计算方法[link:https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution analytic method],
 			将当前矩阵设置为给定矩阵的逆矩阵[link:https://en.wikipedia.org/wiki/Invertible_matrix inverse],如果[page:Boolean throwOnDegenerate]
 			参数没有设置且给定矩阵不可逆,那么将当前矩阵设置为3X3单位矩阵。
@@ -251,7 +251,7 @@ xAxis.z, yAxis.z, zAxis.z, 0,
 		<h3>[method:this makeRotationX]( [param:Float theta] )</h3>
 		<p>
 		[page:Float theta] — Rotation angle in radians.<br /><br />
-		
+
 		把该矩阵设置为绕x轴旋转弧度[page:Float theta] (&theta;)大小的矩阵。
 		结果如下:
 		<code>

+ 8 - 1
docs/api/zh/math/Quaternion.html

@@ -44,6 +44,13 @@
 
 		<h2>Properties</h2>
 
+		<h3>[property:Boolean isQuaternion]</h3>
+		<p>
+			Used to check whether this or derived classes are Quaternions. Default is *true*.<br /><br />
+
+			You should not change this, as it is used internally for optimisation.
+		</p>
+
 		<h3>[property:Float x]</h3>
 		<p>Changing this property will result in [page:.onChangeCallback onChangeCallback] being called.</p>
 
@@ -290,4 +297,4 @@ q.slerp( qb, t )
 
 		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
 	</body>
-</html>
+</html>

+ 8 - 0
docs/api/zh/textures/Texture.html

@@ -54,6 +54,14 @@
 		并在视频播放时不断地更新这个纹理贴图。——[page:VideoTexture VideoTexture] 类会对此自动进行处理。
 		</p>
 
+		<h3>[property:Boolean isTexture]</h3>
+		<p>
+			用于测试这个类或者派生类是否为Texture,默认为*true*。<br /><br />
+
+			你不应当对这个属性进行改变,因为它在内部使用,以用于优化。
+		</p>
+
+
 		<h3>[property:array mipmaps]</h3>
 		<p>
 		用户所给定的mipmap数组(可选)。

+ 1 - 0
docs/examples/exporters/GLTFExporter.html

@@ -97,6 +97,7 @@
 			<li>animations - Array<[page:AnimationClip AnimationClip]>. List of animations to be included in the export.</li>
 			<li>forceIndices - bool. Generate indices for non-index geometry and export with them. Default is false.</li>
 			<li>forcePowerOfTwoTextures - bool. Export with images resized to POT size. This option works only if embedImages is true. Default is false.</li>
+			<li>includeCustomExtensions - bool. Export custom glTF extensions defined on an object's <em>userData.gltfExtensions</em> property. Default is false.</li>
 		</ul>
 		</p>
 		<p>

+ 3 - 0
docs/examples/loaders/GLTFLoader.html

@@ -58,6 +58,9 @@
 		// Optional: Provide a DRACOLoader instance to decode compressed mesh data
 		THREE.DRACOLoader.setDecoderPath( '/examples/js/libs/draco' );
 		loader.setDRACOLoader( new THREE.DRACOLoader() );
+			
+		// Optional: Pre-fetch Draco WASM/JS module, to save time while parsing.
+		THREE.DRACOLoader.getDecoderModule();
 
 		// Load a glTF resource
 		loader.load(

+ 48 - 0
editor/css/dark.css

@@ -250,3 +250,51 @@ select {
 	.Outliner .option.active {
 		background-color: rgba(21,60,94,1);
 	}
+
+/* */
+
+@media all and ( max-width: 600px ) {
+
+	#menubar .menu .options {
+		max-height: calc(100% - 372px);
+	}
+
+	#menubar .menu.right {
+		display: none;
+	}
+
+	#viewport {
+		left: 0;
+		right: 0;
+		top: 32px;
+		height: calc(100% - 352px);
+	}
+
+	#script {
+		left: 0;
+		right: 0;
+		top: 32px;
+		height: calc(100% - 352px);
+	}
+
+	#player {
+		left: 0;
+		right: 0;
+		top: 32px;
+		height: calc(100% - 352px);
+	}
+
+	#sidebar {
+		left: 0;
+		width: 100%;
+		top: calc(100% - 320px);
+		bottom: 0;
+	}
+
+	#toolbar {
+		left: calc(50% - 140px);
+		width: 280px;
+		top: 52px;
+	}
+
+}

+ 48 - 0
editor/css/light.css

@@ -243,3 +243,51 @@ select {
 	.Outliner .option.active {
 		background-color: rgba(0,0,0,0.04);
 	}
+
+/* */
+
+@media all and ( max-width: 600px ) {
+
+	#menubar .menu .options {
+		max-height: calc(100% - 372px);
+	}
+
+	#menubar .menu.right {
+		display: none;
+	}
+
+	#viewport {
+		left: 0;
+		right: 0;
+		top: 32px;
+		height: calc(100% - 352px);
+	}
+
+	#script {
+		left: 0;
+		right: 0;
+		top: 32px;
+		height: calc(100% - 352px);
+	}
+
+	#player {
+		left: 0;
+		right: 0;
+		top: 32px;
+		height: calc(100% - 352px);
+	}
+
+	#sidebar {
+		left: 0;
+		width: 100%;
+		top: calc(100% - 320px);
+		bottom: 0;
+	}
+
+	#toolbar {
+		left: calc(50% - 140px);
+		width: 280px;
+		top: 52px;
+	}
+
+}

BIN
editor/images/icon.png


BIN
editor/images/icon.xcf


+ 20 - 5
editor/index.html

@@ -6,10 +6,11 @@
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<!-- Origin Trial Token, feature = WebXR Device API (For Chrome M69+), origin = https://threejs.org, expires = 2019-03-06 -->
 		<meta http-equiv="origin-trial" data-feature="WebXR Device API (For Chrome M69+)" data-expires="2019-03-06" content="AvDjbxYpoTgOL1PS0JEra7KFCehfTlKnXpU/ORSwNdCQ35cX70cTUkXOnQ26A5XJi3eXHSKpBPchdt5lbcxDuAIAAABTeyJvcmlnaW4iOiJodHRwczovL3RocmVlanMub3JnOjQ0MyIsImZlYXR1cmUiOiJXZWJYUkRldmljZU02OSIsImV4cGlyeSI6MTU1MTgzMDM5OX0=">
+		<link rel="manifest" href="manifest.json">
 	</head>
 	<body ontouchstart="">
-		<link href="css/main.css" rel="stylesheet" />
-		<link id="theme" href="css/light.css" rel="stylesheet" />
+		<link rel="stylesheet" href="css/main.css">
+		<link rel="stylesheet" id="theme" href="css/light.css">
 
 		<script src="../build/three.js"></script>
 		<script src="../examples/js/libs/system.min.js"></script>
@@ -86,6 +87,9 @@
 		<script src="js/libs/ui.js"></script>
 		<script src="js/libs/ui.three.js"></script>
 
+		<script src="js/libs/html2canvas.js"></script>
+		<script src="js/libs/three.html.js"></script>
+
 		<script src="js/libs/app.js"></script>
 		<script src="js/Player.js"></script>
 		<script src="js/Script.js"></script>
@@ -162,9 +166,6 @@
 		<script src="js/commands/SetMaterialMapCommand.js"></script>
 		<script src="js/commands/SetSceneCommand.js"></script>
 
-		<script src="js/libs/html2canvas.js"></script>
-		<script src="js/libs/three.html.js"></script>
-
 		<script>
 
 			window.URL = window.URL || window.webkitURL;
@@ -329,6 +330,20 @@
 
 			}
 
+			// ServiceWorker
+
+			if ( 'serviceWorker' in navigator ) {
+
+				try {
+
+					navigator.serviceWorker.register( 'sw.js' );
+
+				} catch ( error ) {
+
+				}
+
+			}
+
 			/*
 			window.addEventListener( 'message', function ( event ) {
 

+ 1 - 1
editor/js/Editor.js

@@ -285,7 +285,7 @@ Editor.prototype = {
 
 			} else if ( object.isSkinnedMesh ) {
 
-				helper = new THREE.SkeletonHelper( object );
+				helper = new THREE.SkeletonHelper( object.skeleton.bones[ 0 ] );
 
 			} else {
 

+ 0 - 55
editor/main.js

@@ -1,55 +0,0 @@
-const electron = require( 'electron' );
-const app = electron.app;
-const BrowserWindow = electron.BrowserWindow;
-
-const path = require( 'path' );
-const url = require( 'url' );
-
-// Keep a global reference of the window object, if you don't, the window will
-// be closed automatically when the JavaScript object is garbage collected.
-let mainWindow;
-
-function createWindow() {
-
-	mainWindow = new BrowserWindow( { webPreferences: {
-		nodeIntegration: false
-	} } );
-
-	mainWindow.maximize();
-	mainWindow.setMenu( null );
-
-	mainWindow.loadURL( url.format( {
-		pathname: path.join( __dirname, 'index.html' ),
-		protocol: 'file:',
-		slashes: true
-	} ) );
-
-	mainWindow.on( 'closed', function () {
-
-		mainWindow = null;
-
-	} );
-
-}
-
-app.on( 'ready', createWindow );
-
-app.on( 'window-all-closed', function () {
-
-	if ( process.platform !== 'darwin' ) {
-
-		app.quit();
-
-	}
-
-} );
-
-app.on( 'activate', function () {
-
-	if ( mainWindow === null ) {
-
-		createWindow();
-
-	}
-
-} );

+ 13 - 0
editor/manifest.json

@@ -0,0 +1,13 @@
+{
+  "short_name": "Editor",
+  "name": "Three.js Editor",
+  "icons": [
+    {
+      "src": "./images/icon.png",
+      "type": "image/png",
+      "sizes": "144x144"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone"
+}

+ 197 - 0
editor/sw.js

@@ -0,0 +1,197 @@
+// r102.1
+
+const staticAssets = [
+	'./',
+
+	'../build/three.js',
+	'../examples/js/libs/system.min.js',
+
+	'../examples/js/controls/EditorControls.js',
+	'../examples/js/controls/TransformControls.js',
+
+	'../examples/js/libs/jszip.min.js',
+	'../examples/js/libs/inflate.min.js',
+
+	'../examples/js/loaders/AMFLoader.js',
+	'../examples/js/loaders/AWDLoader.js',
+	'../examples/js/loaders/BabylonLoader.js',
+	'../examples/js/loaders/ColladaLoader.js',
+	'../examples/js/loaders/DRACOLoader.js',
+	'../examples/js/loaders/FBXLoader.js',
+	'../examples/js/loaders/GLTFLoader.js',
+	'../examples/js/loaders/deprecated/LegacyGLTFLoader.js',
+	'../examples/js/loaders/KMZLoader.js',
+	'../examples/js/loaders/MD2Loader.js',
+	'../examples/js/loaders/OBJLoader.js',
+	'../examples/js/loaders/MTLLoader.js',
+	'../examples/js/loaders/PlayCanvasLoader.js',
+	'../examples/js/loaders/PLYLoader.js',
+	'../examples/js/loaders/STLLoader.js',
+	'../examples/js/loaders/SVGLoader.js',
+	'../examples/js/loaders/TGALoader.js',
+	'../examples/js/loaders/TDSLoader.js',
+	'../examples/js/loaders/VRMLLoader.js',
+	'../examples/js/loaders/VTKLoader.js',
+	'../examples/js/loaders/ctm/lzma.js',
+	'../examples/js/loaders/ctm/ctm.js',
+	'../examples/js/loaders/ctm/CTMLoader.js',
+
+	'../examples/js/exporters/ColladaExporter.js',
+	'../examples/js/exporters/GLTFExporter.js',
+	'../examples/js/exporters/OBJExporter.js',
+	'../examples/js/exporters/STLExporter.js',
+
+	'../examples/js/renderers/Projector.js',
+	'../examples/js/renderers/RaytracingRenderer.js',
+	'../examples/js/renderers/SoftwareRenderer.js',
+	'../examples/js/renderers/SVGRenderer.js',
+
+	'./js/libs/codemirror/codemirror.css',
+	'./js/libs/codemirror/theme/monokai.css',
+
+	'./js/libs/codemirror/codemirror.js',
+	'./js/libs/codemirror/mode/javascript.js',
+	'./js/libs/codemirror/mode/glsl.js',
+
+	'./js/libs/esprima.js',
+	'./js/libs/jsonlint.js',
+	'./js/libs/glslprep.min.js',
+
+	'./js/libs/codemirror/addon/dialog.css',
+	'./js/libs/codemirror/addon/show-hint.css',
+	'./js/libs/codemirror/addon/tern.css',
+
+	'./js/libs/codemirror/addon/dialog.js',
+	'./js/libs/codemirror/addon/show-hint.js',
+	'./js/libs/codemirror/addon/tern.js',
+	'./js/libs/acorn/acorn.js',
+	'./js/libs/acorn/acorn_loose.js',
+	'./js/libs/acorn/walk.js',
+	'./js/libs/ternjs/polyfill.js',
+	'./js/libs/ternjs/signal.js',
+	'./js/libs/ternjs/tern.js',
+	'./js/libs/ternjs/def.js',
+	'./js/libs/ternjs/comment.js',
+	'./js/libs/ternjs/infer.js',
+	'./js/libs/ternjs/doc_comment.js',
+	'./js/libs/tern-threejs/threejs.js',
+
+	'./js/libs/signals.min.js',
+	'./js/libs/ui.js',
+	'./js/libs/ui.three.js',
+
+	'./js/libs/html2canvas.js',
+	'./js/libs/three.html.js',
+
+	'./js/libs/app.js',
+	'./js/Player.js',
+	'./js/Script.js',
+
+	'../examples/js/vr/WebVR.js',
+
+	//
+
+	'./css/main.css',
+	'./css/dark.css',
+	'./css/light.css',
+
+	'./js/Storage.js',
+
+	'./js/Editor.js',
+	'./js/Config.js',
+	'./js/History.js',
+	'./js/Loader.js',
+	'./js/Menubar.js',
+	'./js/Menubar.File.js',
+	'./js/Menubar.Edit.js',
+	'./js/Menubar.Add.js',
+	'./js/Menubar.Play.js',
+	// './js/Menubar.View.js',
+	'./js/Menubar.Examples.js',
+	'./js/Menubar.Help.js',
+	'./js/Menubar.Status.js',
+	'./js/Sidebar.js',
+	'./js/Sidebar.Scene.js',
+	'./js/Sidebar.Project.js',
+	'./js/Sidebar.Settings.js',
+	'./js/Sidebar.Settings.Shortcuts.js',
+	'./js/Sidebar.Settings.Viewport.js',
+	'./js/Sidebar.Properties.js',
+	'./js/Sidebar.Object.js',
+	'./js/Sidebar.Geometry.js',
+	'./js/Sidebar.Geometry.Geometry.js',
+	'./js/Sidebar.Geometry.BufferGeometry.js',
+	'./js/Sidebar.Geometry.Modifiers.js',
+	'./js/Sidebar.Geometry.BoxGeometry.js',
+	'./js/Sidebar.Geometry.CircleGeometry.js',
+	'./js/Sidebar.Geometry.CylinderGeometry.js',
+	'./js/Sidebar.Geometry.IcosahedronGeometry.js',
+	'./js/Sidebar.Geometry.PlaneGeometry.js',
+	'./js/Sidebar.Geometry.SphereGeometry.js',
+	'./js/Sidebar.Geometry.TorusGeometry.js',
+	'./js/Sidebar.Geometry.TorusKnotGeometry.js',
+	'./js/Sidebar.Geometry.TubeGeometry.js',
+	'../examples/js/geometries/TeapotBufferGeometry.js',
+	'./js/Sidebar.Geometry.TeapotBufferGeometry.js',
+	'./js/Sidebar.Geometry.LatheGeometry.js',
+	'./js/Sidebar.Material.js',
+	'./js/Sidebar.Animation.js',
+	'./js/Sidebar.Script.js',
+	'./js/Sidebar.History.js',
+	'./js/Strings.js',
+	'./js/Toolbar.js',
+	'./js/Viewport.js',
+	'./js/Viewport.Info.js',
+
+	'./js/Command.js',
+	'./js/commands/AddObjectCommand.js',
+	'./js/commands/RemoveObjectCommand.js',
+	'./js/commands/MoveObjectCommand.js',
+	'./js/commands/SetPositionCommand.js',
+	'./js/commands/SetRotationCommand.js',
+	'./js/commands/SetScaleCommand.js',
+	'./js/commands/SetValueCommand.js',
+	'./js/commands/SetUuidCommand.js',
+	'./js/commands/SetColorCommand.js',
+	'./js/commands/SetGeometryCommand.js',
+	'./js/commands/SetGeometryValueCommand.js',
+	'./js/commands/MultiCmdsCommand.js',
+	'./js/commands/AddScriptCommand.js',
+	'./js/commands/RemoveScriptCommand.js',
+	'./js/commands/SetScriptValueCommand.js',
+	'./js/commands/SetMaterialCommand.js',
+	'./js/commands/SetMaterialValueCommand.js',
+	'./js/commands/SetMaterialColorCommand.js',
+	'./js/commands/SetMaterialMapCommand.js',
+	'./js/commands/SetSceneCommand.js',
+
+	//
+
+	'./examples/arkanoid.app.json',
+	'./examples/camera.app.json',
+	'./examples/particles.app.json',
+	'./examples/pong.app.json',
+	'./examples/shaders.app.json'
+
+];
+
+self.addEventListener( 'install', async function ( event ) {
+
+	const cache = await caches.open( 'threejs-editor' );
+	cache.addAll( staticAssets );
+
+} );
+
+self.addEventListener( 'fetch', async function ( event ) {
+
+	const request = event.request;
+	event.respondWith( cacheFirst( request ) );
+
+} );
+
+async function cacheFirst( request ) {
+
+	const cachedResponse = await caches.match( request );
+	return cachedResponse || fetch( request );
+
+}

+ 44 - 39
examples/files.js

@@ -19,7 +19,6 @@ var files = {
 		"webgl_effects_peppersghost",
 		"webgl_effects_stereo",
 		"webgl_framebuffer_texture",
-		"webgl_furnace_test",
 		"webgl_geometries",
 		"webgl_geometries_parametric",
 		"webgl_geometry_colors",
@@ -155,6 +154,7 @@ var files = {
 		"webgl_materials_envmaps",
 		"webgl_materials_envmaps_exr",
 		"webgl_materials_envmaps_hdr",
+		"webgl_materials_envmaps_parallax",
 		"webgl_materials_grass",
 		"webgl_materials_lightmap",
 		"webgl_materials_matcap",
@@ -208,40 +208,10 @@ var files = {
 		"webgl_performance_doublesided",
 		"webgl_performance_nodes",
 		"webgl_performance_static",
-		"webgl_physics_cloth",
-		"webgl_physics_convex_break",
-		"webgl_physics_rope",
-		"webgl_physics_terrain",
-		"webgl_physics_volume",
 		"webgl_points_billboards",
 		"webgl_points_dynamic",
 		"webgl_points_sprites",
 		"webgl_points_waves",
-		"webgl_postprocessing",
-		"webgl_postprocessing_advanced",
-		"webgl_postprocessing_afterimage",
-		"webgl_postprocessing_backgrounds",
-		"webgl_postprocessing_crossfade",
-		"webgl_postprocessing_dof",
-		"webgl_postprocessing_dof2",
-		"webgl_postprocessing_fxaa",
-		"webgl_postprocessing_glitch",
-		"webgl_postprocessing_godrays",
-		"webgl_postprocessing_rgb_halftone",
-		"webgl_postprocessing_masking",
-		"webgl_postprocessing_ssaa",
-		"webgl_postprocessing_ssaa_unbiased",
-		"webgl_postprocessing_nodes",
-		"webgl_postprocessing_nodes_pass",
-		"webgl_postprocessing_outline",
-		"webgl_postprocessing_pixel",
-		"webgl_postprocessing_procedural",
-		"webgl_postprocessing_sao",
-		"webgl_postprocessing_smaa",
-		"webgl_postprocessing_sobel",
-		"webgl_postprocessing_ssao",
-		"webgl_postprocessing_taa",
-		"webgl_postprocessing_unreal_bloom",
 		"webgl_raycast_sprite",
 		"webgl_raycast_texture",
 		"webgl_read_float_buffer",
@@ -274,6 +244,33 @@ var files = {
 		"webgl_water",
 		"webgl_water_flowmap"
 	],
+	"webgl / postprocessing": [
+		"webgl_postprocessing",
+		"webgl_postprocessing_advanced",
+		"webgl_postprocessing_afterimage",
+		"webgl_postprocessing_backgrounds",
+		"webgl_postprocessing_crossfade",
+		"webgl_postprocessing_dof",
+		"webgl_postprocessing_dof2",
+		"webgl_postprocessing_fxaa",
+		"webgl_postprocessing_glitch",
+		"webgl_postprocessing_godrays",
+		"webgl_postprocessing_rgb_halftone",
+		"webgl_postprocessing_masking",
+		"webgl_postprocessing_ssaa",
+		"webgl_postprocessing_ssaa_unbiased",
+		"webgl_postprocessing_nodes",
+		"webgl_postprocessing_nodes_pass",
+		"webgl_postprocessing_outline",
+		"webgl_postprocessing_pixel",
+		"webgl_postprocessing_procedural",
+		"webgl_postprocessing_sao",
+		"webgl_postprocessing_smaa",
+		"webgl_postprocessing_sobel",
+		"webgl_postprocessing_ssao",
+		"webgl_postprocessing_taa",
+		"webgl_postprocessing_unreal_bloom"
+	],
 	"webgl / advanced": [
 		"webgl_buffergeometry",
 		"webgl_buffergeometry_constructed_from_geometry",
@@ -309,14 +306,12 @@ var files = {
 		"webgl_shadowmap_pcss",
 		"webgl_simple_gi",
 		"webgl_tiled_forward",
-		"webgl_worker_offscreencanvas"
-	],
-	"webgl deferred": [
+		"webgl_worker_offscreencanvas",
 		"webgldeferred_animation"
 	],
 	"webgl2": [
+		"webgl2_materials_texture2darray",
 		"webgl2_materials_texture3d",
-		"webgl2_materials_texture3d_volume",
 		"webgl2_multisampled_renderbuffers",
 		"webgl2_sandbox"
 	],
@@ -340,6 +335,13 @@ var files = {
 		"webvr_vive_paint",
 		"webvr_vive_sculpt"
 	],
+	"physics": [
+		"webgl_physics_cloth",
+		"webgl_physics_convex_break",
+		"webgl_physics_rope",
+		"webgl_physics_terrain",
+		"webgl_physics_volume"
+	],
 	"misc": [
 		"misc_animation_authoring",
 		"misc_animation_groups",
@@ -358,7 +360,9 @@ var files = {
 		"misc_exporter_stl",
 		"misc_fps",
 		"misc_lookat",
-		"misc_uv_tests"
+	],
+	"css2d": [
+		"css2d_label"
 	],
 	"css3d": [
 		"css3d_molecules",
@@ -370,9 +374,6 @@ var files = {
 		"css3d_sprites",
 		"css3d_youtube"
 	],
-	"css2d": [
-		"css2d_label"
-	],
 	"raytracing": [
 		"raytracing_sandbox"
 	],
@@ -384,5 +385,9 @@ var files = {
 	"svg": [
 		"svg_lines",
 		"svg_sandbox"
+	],
+	"tests": [
+		"webgl_furnace_test",
+		"misc_uv_tests"
 	]
 };

+ 73 - 0
examples/js/MarchingCubes.js

@@ -772,6 +772,79 @@ THREE.MarchingCubes = function ( resolution, material, enableUvs, enableColors )
 	// Updates
 	/////////////////////////////////////
 
+	this.setCell = function ( x, y, z, value ) {
+
+		var index = this.size2 * z + this.size * y + x;
+		this.field[ index ] = value;
+
+	};
+
+	this.getCell = function ( x, y, z ) {
+
+		var index = this.size2 * z + this.size * y + x;
+		return this.field[ index ];
+
+	};
+
+	this.blur = function ( intensity ) {
+
+		if ( intensity === undefined ) {
+
+			intensity = 1;
+
+		}
+
+		var field = this.field;
+		var fieldCopy = field.slice();
+		var size = this.size;
+		var size2 = this.size2;
+		for ( var x = 0; x < size; x ++ ) {
+
+			for ( var y = 0; y < size; y ++ ) {
+
+				for ( var z = 0; z < size; z ++ ) {
+
+					var index = size2 * z + size * y + x;
+					var val = fieldCopy[ index ];
+					var count = 1;
+
+					for ( var x2 = - 1; x2 <= 1; x2 += 2 ) {
+
+						var x3 = x2 + x;
+						if ( x3 < 0 || x3 >= size ) continue;
+
+						for ( var y2 = - 1; y2 <= 1; y2 += 2 ) {
+
+							var y3 = y2 + y;
+							if ( y3 < 0 || y3 >= size ) continue;
+
+							for ( var z2 = - 1; z2 <= 1; z2 += 2 ) {
+
+								var z3 = z2 + z;
+								if ( z3 < 0 || z3 >= size ) continue;
+
+								var index2 = size2 * z3 + size * y3 + x3;
+								var val2 = fieldCopy[ index2 ];
+
+								count ++;
+								val += intensity * ( val2 - val ) / count;
+
+							}
+
+						}
+
+					}
+
+					field[ index ] = val;
+
+				}
+
+			}
+
+		}
+
+	};
+
 	this.reset = function () {
 
 		var i;

+ 41 - 19
examples/js/exporters/GLTFExporter.js

@@ -80,7 +80,8 @@ THREE.GLTFExporter.prototype = {
 			embedImages: true,
 			animations: [],
 			forceIndices: false,
-			forcePowerOfTwoTextures: false
+			forcePowerOfTwoTextures: false,
+			includeCustomExtensions: false
 		};
 
 		options = Object.assign( {}, DEFAULT_OPTIONS, options );
@@ -340,21 +341,50 @@ THREE.GLTFExporter.prototype = {
 		 * Serializes a userData.
 		 *
 		 * @param {THREE.Object3D|THREE.Material} object
-		 * @returns {Object}
+		 * @param {Object} gltfProperty
 		 */
-		function serializeUserData( object ) {
+		function serializeUserData( object, gltfProperty ) {
+
+			if ( Object.keys( object.userData ).length === 0 ) {
+
+				return;
+
+			}
 
 			try {
 
-				return JSON.parse( JSON.stringify( object.userData ) );
+				var json = JSON.parse( JSON.stringify( object.userData ) );
+
+				if ( options.includeCustomExtensions && json.gltfExtensions ) {
+
+					if ( gltfProperty.extensions === undefined ) {
+
+						gltfProperty.extensions = {};
+
+					}
+
+					for ( var extensionName in json.gltfExtensions ) {
+
+						gltfProperty.extensions[ extensionName ] = json.gltfExtensions[ extensionName ];
+						extensionsUsed[ extensionName ] = true;
+
+					}
+
+					delete json.gltfExtensions;
+
+				}
+
+				if ( Object.keys( json ).length > 0 ) {
+
+					gltfProperty.extras = json;
+
+				}
 
 			} catch ( error ) {
 
 				console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' +
 					'won\'t be serialized because of JSON.stringify error - ' + error.message );
 
-				return {};
-
 			}
 
 		}
@@ -1025,11 +1055,7 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
-			if ( Object.keys( material.userData ).length > 0 ) {
-
-				gltfMaterial.extras = serializeUserData( material );
-
-			}
+			serializeUserData( material, gltfMaterial );
 
 			outputJSON.materials.push( gltfMaterial );
 
@@ -1276,8 +1302,6 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
-			var extras = ( Object.keys( geometry.userData ).length > 0 ) ? serializeUserData( geometry ) : undefined;
-
 			var forceIndices = options.forceIndices;
 			var isMultiMaterial = Array.isArray( mesh.material );
 
@@ -1319,7 +1343,7 @@ THREE.GLTFExporter.prototype = {
 					attributes: attributes,
 				};
 
-				if ( extras ) primitive.extras = extras;
+				serializeUserData( geometry, primitive );
 
 				if ( targets.length > 0 ) primitive.targets = targets;
 
@@ -1699,11 +1723,7 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
-			if ( object.userData && Object.keys( object.userData ).length > 0 ) {
-
-				gltfNode.extras = serializeUserData( object );
-
-			}
+			serializeUserData( object, gltfNode );
 
 			if ( object.isMesh || object.isLine || object.isPoints ) {
 
@@ -1844,6 +1864,8 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
+			serializeUserData( scene, gltfScene );
+
 		}
 
 		/**

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

@@ -496,7 +496,6 @@ THREE.GLTFLoader = ( function () {
 		this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
 		this.json = json;
 		this.dracoLoader = dracoLoader;
-		THREE.DRACOLoader.getDecoderModule();
 
 	}
 

+ 1 - 4
examples/js/loaders/ctm/CTMLoader.js

@@ -10,11 +10,8 @@
 
 THREE.CTMLoader = function () {
 
-	THREE.Loader.call( this );
-
 };
 
-THREE.CTMLoader.prototype = Object.create( THREE.Loader.prototype );
 THREE.CTMLoader.prototype.constructor = THREE.CTMLoader;
 
 // Load multiple CTM parts defined in JSON
@@ -58,7 +55,7 @@ THREE.CTMLoader.prototype.loadParts = function ( url, callback, parameters ) {
 
 				for ( var i = 0; i < jsonObject.materials.length; i ++ ) {
 
-					materials[ i ] = scope.createMaterial( jsonObject.materials[ i ], basePath );
+					materials[ i ] = THREE.Loader.prototype.createMaterial( jsonObject.materials[ i ], basePath );
 
 				}
 

+ 1 - 2
examples/js/loaders/sea3d/SEA3D.js

@@ -3240,9 +3240,8 @@ SEA3D.File.prototype.load = function ( url ) {
 
 	xhr.open( "GET", url, true );
 
-	if (!this.config.path) {
+	if ( ! this.config.path ) {
 
-//		this.config.path = THREE.Loader.prototype.extractUrlBase( url );
 		this.config.path = THREE.LoaderUtils.extractUrlBase( url );
 
 	}

+ 26 - 0
examples/js/nodes/materials/NodeMaterial.js

@@ -11,11 +11,21 @@ function NodeMaterial( vertex, fragment ) {
 
 	THREE.ShaderMaterial.call( this );
 
+	var self = this;
+
 	this.vertex = vertex || new RawNode( new PositionNode( PositionNode.PROJECTION ) );
 	this.fragment = fragment || new RawNode( new ColorNode( 0xFF0000 ) );
 
 	this.updaters = [];
 
+	// it fix the programCache and share the code with others materials
+
+	this.onBeforeCompile.toString = function() {
+
+		return self.needsCompile;
+
+	};
+
 }
 
 NodeMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
@@ -34,6 +44,22 @@ Object.defineProperties( NodeMaterial.prototype, {
 
 		}
 
+	},
+
+	needsUpdate: {
+
+		set: function ( value ) {
+
+			this.needsCompile = value;
+
+		},
+
+		get: function () {
+
+			return this.needsCompile;
+
+		}
+
 	}
 
 } );

+ 1 - 1
examples/js/objects/Fire.js

@@ -431,7 +431,7 @@ THREE.Fire = function ( geometry, options ) {
 
 	};
 
-	this.onBeforeRender = function ( renderer, scene, camera ) {
+	this.onBeforeRender = function ( renderer ) {
 
 		var delta = this.clock.getDelta();
 		if ( delta > 0.1 ) {

+ 32 - 34
examples/js/objects/LightningStorm.js

@@ -3,53 +3,53 @@
  *
  * @fileoverview Lightning strike object generator
  *
- * 
+ *
  * Usage
- * 
+ *
  * var myStorm = new THREE.LightningStorm( paramsObject );
  * myStorm.position.set( ... );
  * scene.add( myStorm );
  * ...
  * myStorm.update( currentTime );
- * 
+ *
  * The "currentTime" can only go forwards or be stopped.
- * 
- * 
+ *
+ *
  * LightningStorm parameters:
  *
  * @param {double} size Size of the storm. If no 'onRayPosition' parameter is defined, it means the side of the rectangle the storm covers.
  *
  * @param {double} minHeight Minimum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0.
- * 
+ *
  * @param {double} maxHeight Maximum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0.
- * 
+ *
  * @param {double} maxSlope The maximum inclination slope of a ray. If no 'onRayPosition' parameter is defined, it means the slope relative to plane y = 0.
- * 
+ *
  * @param {integer} maxLightnings Greater than 0. The maximum number of simultaneous rays.
- * 
+ *
  * @param {double} lightningMinPeriod minimum time between two consecutive rays.
- * 
+ *
  * @param {double} lightningMaxPeriod maximum time between two consecutive rays.
- * 
+ *
  * @param {double} lightningMinDuration The minimum time a ray can last.
- * 
+ *
  * @param {double} lightningMaxDuration The maximum time a ray can last.
- * 
+ *
  * @param {Object} lightningParameters The parameters for created rays. See THREE.LightningStrike (geometry)
- * 
+ *
  * @param {Material} lightningMaterial The THREE.Material used for the created rays.
- * 
+ *
  * @param {function} onRayPosition Optional callback with two Vector3 parameters (source, dest). You can set here the start and end points for each created ray, using the standard size, minHeight, etc parameters and other values in your algorithm.
- * 
+ *
  * @param {function} onLightningDown This optional callback is called with one parameter (lightningStrike) when a ray ends propagating, so it has hit the ground.
- * 
+ *
  *
 */
 
 THREE.LightningStorm = function ( stormParams ) {
 
 	THREE.Object3D.call( this );
-	
+
 	// Parameters
 
 	stormParams = stormParams || {};
@@ -71,21 +71,20 @@ THREE.LightningStorm = function ( stormParams ) {
 	this.lightningParameters = THREE.LightningStrike.copyParameters( stormParams.lightningParameters, stormParams.lightningParameters );
 
 	this.lightningParameters.isEternal = false;
-	
+
 	this.lightningMaterial = stormParams.lightningMaterial !== undefined ? stormParams.lightningMaterial : new THREE.MeshBasicMaterial( { color: 0xB0FFFF } );
 
 	if ( stormParams.onRayPosition !== undefined ) {
 
 		this.onRayPosition = stormParams.onRayPosition;
 
-	}
-	else {
+	} else {
 
-		this.onRayPosition = function( source, dest ) {
+		this.onRayPosition = function ( source, dest ) {
 
 			dest.set( ( Math.random() - 0.5 ) * stormParams.size, 0, ( Math.random() - 0.5 ) * stormParams.size );
-			
-			var height = THREE.Math.lerp( stormParams.minHeight, stormParams.maxHeight, Math.random() );;
+
+			var height = THREE.Math.lerp( stormParams.minHeight, stormParams.maxHeight, Math.random() );
 
 			source.set( stormParams.maxSlope * ( 2 * Math.random() - 1 ), 1, stormParams.maxSlope * ( 2 * Math.random() - 1 ) ).multiplyScalar( height ).add( dest );
 
@@ -102,7 +101,7 @@ THREE.LightningStorm = function ( stormParams ) {
 	this.lightningsMeshes = [];
 	this.deadLightningsMeshes = [];
 
-	for ( var i = 0; i < this.stormParams.maxLightnings; i++ ) {
+	for ( var i = 0; i < this.stormParams.maxLightnings; i ++ ) {
 
 		var lightning = new THREE.LightningStrike( THREE.LightningStrike.copyParameters( {}, this.lightningParameters ) );
 		var mesh = new THREE.Mesh( lightning, this.lightningMaterial );
@@ -155,9 +154,9 @@ THREE.LightningStorm.prototype.update = function ( time ) {
 
 	}
 
-	var i = 0; il = this.lightningsMeshes.length;
+	var i = 0, il = this.lightningsMeshes.length;
 
-	while ( i < il ){
+	while ( i < il ) {
 
 		var mesh = this.lightningsMeshes[ i ];
 
@@ -181,18 +180,17 @@ THREE.LightningStorm.prototype.update = function ( time ) {
 
 			// Lightning is to be destroyed
 
-			this.lightningsMeshes.splice( this.lightningsMeshes.indexOf( mesh ), 1 ); 
+			this.lightningsMeshes.splice( this.lightningsMeshes.indexOf( mesh ), 1 );
 
 			this.deadLightningsMeshes.push( mesh );
 
 			this.remove( mesh );
 
-			il--;
+			il --;
 
-		}
-		else {
+		} else {
 
-			i++;
+			i ++;
 
 		}
 
@@ -207,8 +205,8 @@ THREE.LightningStorm.prototype.getNextLightningTime = function ( currentTime ) {
 };
 
 THREE.LightningStorm.prototype.copy = function ( source ) {
-	
-	Object3D.prototype.copy.call( this, source );
+
+	THREE.Object3D.prototype.copy.call( this, source );
 
 	this.stormParams.size = source.stormParams.size;
 	this.stormParams.minHeight = source.stormParams.minHeight;

+ 1 - 2
examples/js/objects/Sky.js

@@ -133,8 +133,7 @@ THREE.Sky.SkyShader = {
 		'const float pi = 3.141592653589793238462643383279502884197169;',
 
 		'const float n = 1.0003;', // refractive index of air
-		'const float N = 2.545E25;', // number of molecules per unit volume for air at
-									// 288.15K and 1013mb (sea level -45 celsius)
+		'const float N = 2.545E25;', // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius)
 
 		// optical length at zenith for molecules
 		'const float rayleighZenithLength = 8.4E3;',

+ 2 - 1
examples/js/renderers/CSS2DRenderer.js

@@ -9,7 +9,7 @@ THREE.CSS2DObject = function ( element ) {
 	this.element = element;
 	this.element.style.position = 'absolute';
 
-	this.addEventListener( 'removed', function ( event ) {
+	this.addEventListener( 'removed', function () {
 
 		if ( this.element.parentNode !== null ) {
 
@@ -82,6 +82,7 @@ THREE.CSS2DRenderer = function () {
 			element.style.MozTransform = style;
 			element.style.oTransform = style;
 			element.style.transform = style;
+			element.style.display = ( vector.z < - 1 || vector.z > 1 ) ? 'none' : '';
 
 			var objectData = {
 				distanceToCameraSquared: getDistanceToSquared( camera, object )

+ 3 - 3
examples/js/renderers/Projector.js

@@ -322,7 +322,7 @@ THREE.Projector = function () {
 
 				_face.material = material;
 
-				if ( material.vertexColors === THREE.FaceColors ||  material.vertexColors === THREE.VertexColors ) {
+				if ( material.vertexColors === THREE.FaceColors || material.vertexColors === THREE.VertexColors ) {
 
 					_face.color.fromArray( colors, a * 3 );
 
@@ -1038,8 +1038,8 @@ THREE.Projector = function () {
 
 		var alpha1 = 0, alpha2 = 1,
 
-		// Calculate the boundary coordinate of each vertex for the near and far clip planes,
-		// Z = -1 and Z = +1, respectively.
+			// Calculate the boundary coordinate of each vertex for the near and far clip planes,
+			// Z = -1 and Z = +1, respectively.
 
 			bc1near = s1.z + s1.w,
 			bc2near = s2.z + s2.w,

+ 71 - 0
examples/jsm/controls/MapControls.d.ts

@@ -0,0 +1,71 @@
+import {
+  Camera,
+  EventDispatcher,
+  MOUSE,
+  Object3D,
+  Vector3
+} from '../../../src/Three';
+
+export class MapControls extends EventDispatcher {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  // API
+  enabled: boolean;
+  target: Vector3;
+
+  enableZoom: boolean;
+  zoomSpeed: number;
+  minDistance: number;
+  maxDistance: number;
+  enableRotate: boolean;
+  rotateSpeed: number;
+  enablePan: boolean;
+  keyPanSpeed: number;
+  maxZoom: number;
+  minZoom: number;
+  panSpeed: number;
+  autoRotate: boolean;
+  autoRotateSpeed: number;
+  minPolarAngle: number;
+  maxPolarAngle: number;
+  minAzimuthAngle: number;
+  maxAzimuthAngle: number;
+  enableKeys: boolean;
+  screenSpacePanning: boolean;
+  keys: { LEFT: number; UP: number; RIGHT: number; BOTTOM: number };
+  mouseButtons: { LEFT: MOUSE; MIDDLE: MOUSE; RIGHT: MOUSE };
+  enableDamping: boolean;
+  dampingFactor: number;
+  target0: Vector3;
+  position0: Vector3;
+  zoom0: number;
+
+  rotateLeft(angle?: number): void;
+
+  rotateUp(angle?: number): void;
+
+  panLeft(distance?: number): void;
+
+  panUp(distance?: number): void;
+
+  pan(deltaX: number, deltaY: number): void;
+
+  dollyIn(dollyScale: number): void;
+
+  dollyOut(dollyScale: number): void;
+
+  saveState(): void;
+
+  update(): boolean;
+
+  reset(): void;
+
+  dispose(): void;
+
+  getPolarAngle(): number;
+
+  getAzimuthalAngle(): number;
+}

+ 70 - 0
examples/jsm/controls/OrbitControls.d.ts

@@ -0,0 +1,70 @@
+import { Camera, MOUSE, Object3D, Vector3 } from '../../../src/Three';
+
+export class OrbitControls {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  // API
+  enabled: boolean;
+  target: Vector3;
+
+  // deprecated
+  center: Vector3;
+
+  enableZoom: boolean;
+  zoomSpeed: number;
+  minDistance: number;
+  maxDistance: number;
+  enableRotate: boolean;
+  rotateSpeed: number;
+  enablePan: boolean;
+  keyPanSpeed: number;
+  autoRotate: boolean;
+  autoRotateSpeed: number;
+  minPolarAngle: number;
+  maxPolarAngle: number;
+  minAzimuthAngle: number;
+  maxAzimuthAngle: number;
+  enableKeys: boolean;
+  keys: {LEFT: number; UP: number; RIGHT: number; BOTTOM: number;};
+  mouseButtons: {ORBIT: MOUSE; ZOOM: MOUSE; PAN: MOUSE;};
+  enableDamping: boolean;
+  dampingFactor: number;
+  screenSpacePanning: boolean;
+
+
+  rotateLeft(angle?: number): void;
+
+  rotateUp(angle?: number): void;
+
+  panLeft(distance?: number): void;
+
+  panUp(distance?: number): void;
+
+  pan(deltaX: number, deltaY: number): void;
+
+  dollyIn(dollyScale: number): void;
+
+  dollyOut(dollyScale: number): void;
+
+  update(): void;
+
+  reset(): void;
+
+  dispose(): void;
+
+  getPolarAngle(): number;
+
+  getAzimuthalAngle(): number;
+
+  // EventDispatcher mixins
+  addEventListener(type: string, listener: (event: any) => void): void;
+
+  hasEventListener(type: string, listener: (event: any) => void): boolean;
+
+  removeEventListener(type: string, listener: (event: any) => void): void;
+
+  dispatchEvent(event: {type: string; target: any;}): void;
+}

+ 24 - 5
examples/jsm/controls/OrbitControls.js

@@ -546,32 +546,44 @@ var OrbitControls = function ( object, domElement ) {
 
 	function handleKeyDown( event ) {
 
-		//console.log( 'handleKeyDown' );
+		// console.log( 'handleKeyDown' );
+
+		var needsUpdate = false;
 
 		switch ( event.keyCode ) {
 
 			case scope.keys.UP:
 				pan( 0, scope.keyPanSpeed );
-				scope.update();
+				needsUpdate = true;
 				break;
 
 			case scope.keys.BOTTOM:
 				pan( 0, - scope.keyPanSpeed );
-				scope.update();
+				needsUpdate = true;
 				break;
 
 			case scope.keys.LEFT:
 				pan( scope.keyPanSpeed, 0 );
-				scope.update();
+				needsUpdate = true;
 				break;
 
 			case scope.keys.RIGHT:
 				pan( - scope.keyPanSpeed, 0 );
-				scope.update();
+				needsUpdate = true;
 				break;
 
 		}
 
+		if ( needsUpdate ) {
+
+			// prevent the browser from scrolling on cursor keys
+			event.preventDefault();
+
+			scope.update();
+
+		}
+
+
 	}
 
 	function handleTouchStartRotate( event ) {
@@ -682,8 +694,15 @@ var OrbitControls = function ( object, domElement ) {
 
 		if ( scope.enabled === false ) return;
 
+		// Prevent the browser from scrolling.
+
 		event.preventDefault();
 
+		// Manually set the focus since calling preventDefault above
+		// prevents the browser from setting it automatically.
+
+		scope.domElement.focus ? scope.domElement.focus() : window.focus();
+
 		switch ( event.button ) {
 
 			case scope.mouseButtons.LEFT:

+ 47 - 0
examples/jsm/controls/TrackballControls.d.ts

@@ -0,0 +1,47 @@
+import { Camera, EventDispatcher, Vector3 } from '../../../src/Three';
+
+export class TrackballControls extends EventDispatcher {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement;
+
+  // API
+  enabled: boolean;
+  screen: {left: number; top: number; width: number; height: number};
+  rotateSpeed: number;
+  zoomSpeed: number;
+  panSpeed: number;
+  noRotate: boolean;
+  noZoom: boolean;
+  noPan: boolean;
+  noRoll: boolean;
+  staticMoving: boolean;
+  dynamicDampingFactor: number;
+  minDistance: number;
+  maxDistance: number;
+  keys: number[];
+
+  target: Vector3;
+  position0: Vector3;
+  target0: Vector3;
+  up0: Vector3;
+
+  update(): void;
+
+  reset(): void;
+
+  dispose(): void;
+
+  checkDistances(): void;
+
+  zoomCamera(): void;
+
+  panCamera(): void;
+
+  rotateCamera(): void;
+
+  handleResize(): void;
+
+  handleEvent(event: any): void;
+}

+ 1 - 1
examples/jsm/controls/TrackballControls.js

@@ -499,7 +499,7 @@ var TrackballControls = function ( object, domElement ) {
 	function touchstart( event ) {
 
 		if ( _this.enabled === false ) return;
-
+		
 		event.preventDefault();
 
 		switch ( event.touches.length ) {

+ 7 - 0
examples/jsm/exporters/GLTFExporter.d.ts

@@ -0,0 +1,7 @@
+import { Object3D } from '../../../src/Three';
+
+export class GLTFExporter {
+  constructor();
+
+  parse(input: Object3D, onCompleted: (gltf: object) => void, options: object): null;
+}

+ 2256 - 0
examples/jsm/exporters/GLTFExporter.js

@@ -0,0 +1,2256 @@
+/**
+ * @author fernandojsg / http://fernandojsg.com
+ * @author Don McCurdy / https://www.donmccurdy.com
+ * @author Takahiro / https://github.com/takahirox
+ */
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+var WEBGL_CONSTANTS = {
+	POINTS: 0x0000,
+	LINES: 0x0001,
+	LINE_LOOP: 0x0002,
+	LINE_STRIP: 0x0003,
+	TRIANGLES: 0x0004,
+	TRIANGLE_STRIP: 0x0005,
+	TRIANGLE_FAN: 0x0006,
+
+	UNSIGNED_BYTE: 0x1401,
+	UNSIGNED_SHORT: 0x1403,
+	FLOAT: 0x1406,
+	UNSIGNED_INT: 0x1405,
+	ARRAY_BUFFER: 0x8892,
+	ELEMENT_ARRAY_BUFFER: 0x8893,
+
+	NEAREST: 0x2600,
+	LINEAR: 0x2601,
+	NEAREST_MIPMAP_NEAREST: 0x2700,
+	LINEAR_MIPMAP_NEAREST: 0x2701,
+	NEAREST_MIPMAP_LINEAR: 0x2702,
+	LINEAR_MIPMAP_LINEAR: 0x2703,
+
+	CLAMP_TO_EDGE: 33071,
+	MIRRORED_REPEAT: 33648,
+	REPEAT: 10497
+};
+
+var THREE_TO_WEBGL = {};
+
+THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST;
+THREE_TO_WEBGL[ NearestMipMapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
+THREE_TO_WEBGL[ NearestMipMapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
+THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR;
+THREE_TO_WEBGL[ LinearMipMapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
+THREE_TO_WEBGL[ LinearMipMapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
+
+THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
+THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT;
+THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
+
+var PATH_PROPERTIES = {
+	scale: 'scale',
+	position: 'translation',
+	quaternion: 'rotation',
+	morphTargetInfluences: 'weights'
+};
+
+//------------------------------------------------------------------------------
+// GLTF Exporter
+//------------------------------------------------------------------------------
+import {
+	AnimationClip,
+	BufferAttribute,
+	BufferGeometry,
+	Camera,
+	ClampToEdgeWrapping,
+	DoubleSide,
+	Geometry,
+	InterpolateDiscrete,
+	LinearFilter,
+	LinearMipMapLinearFilter,
+	LinearMipMapNearestFilter,
+	Material,
+	Math,
+	Mesh,
+	MirroredRepeatWrapping,
+	NearestFilter,
+	NearestMipMapLinearFilter,
+	NearestMipMapNearestFilter,
+	Object3D,
+	PropertyBinding,
+	RGBAFormat,
+	RGBFormat,
+	RepeatWrapping,
+	Scene,
+	Scenes,
+	ShaderMaterial,
+	TriangleFanDrawMode,
+	TriangleStripDrawMode,
+	Vector3,
+	VertexColors
+} from "../../../build/three.module.js";
+
+var GLTFExporter = function () {};
+
+GLTFExporter.prototype = {
+
+	constructor: GLTFExporter,
+
+	/**
+	 * Parse scenes and generate GLTF output
+	 * @param  {Scene or [Scenes]} input   Scene or Array of Scenes
+	 * @param  {Function} onDone  Callback on completed
+	 * @param  {Object} options options
+	 */
+	parse: function ( input, onDone, options ) {
+
+		var DEFAULT_OPTIONS = {
+			binary: false,
+			trs: false,
+			onlyVisible: true,
+			truncateDrawRange: true,
+			embedImages: true,
+			animations: [],
+			forceIndices: false,
+			forcePowerOfTwoTextures: false,
+			includeCustomExtensions: false
+		};
+
+		options = Object.assign( {}, DEFAULT_OPTIONS, options );
+
+		if ( options.animations.length > 0 ) {
+
+			// Only TRS properties, and not matrices, may be targeted by animation.
+			options.trs = true;
+
+		}
+
+		var outputJSON = {
+
+			asset: {
+
+				version: "2.0",
+				generator: "GLTFExporter"
+
+			}
+
+		};
+
+		var byteOffset = 0;
+		var buffers = [];
+		var pending = [];
+		var nodeMap = new Map();
+		var skins = [];
+		var extensionsUsed = {};
+		var cachedData = {
+
+			meshes: new Map(),
+			attributes: new Map(),
+			attributesNormalized: new Map(),
+			materials: new Map(),
+			textures: new Map(),
+			images: new Map()
+
+		};
+
+		var cachedCanvas;
+
+		/**
+		 * Compare two arrays
+		 */
+		/**
+		 * Compare two arrays
+		 * @param  {Array} array1 Array 1 to compare
+		 * @param  {Array} array2 Array 2 to compare
+		 * @return {Boolean}        Returns true if both arrays are equal
+		 */
+		function equalArray( array1, array2 ) {
+
+			return ( array1.length === array2.length ) && array1.every( function ( element, index ) {
+
+				return element === array2[ index ];
+
+			} );
+
+		}
+
+		/**
+		 * Converts a string to an ArrayBuffer.
+		 * @param  {string} text
+		 * @return {ArrayBuffer}
+		 */
+		function stringToArrayBuffer( text ) {
+
+			if ( window.TextEncoder !== undefined ) {
+
+				return new TextEncoder().encode( text ).buffer;
+
+			}
+
+			var array = new Uint8Array( new ArrayBuffer( text.length ) );
+
+			for ( var i = 0, il = text.length; i < il; i ++ ) {
+
+				var value = text.charCodeAt( i );
+
+				// Replacing multi-byte character with space(0x20).
+				array[ i ] = value > 0xFF ? 0x20 : value;
+
+			}
+
+			return array.buffer;
+
+		}
+
+		/**
+		 * Get the min and max vectors from the given attribute
+		 * @param  {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count
+		 * @param  {Integer} start
+		 * @param  {Integer} count
+		 * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
+		 */
+		function getMinMax( attribute, start, count ) {
+
+			var output = {
+
+				min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
+				max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
+
+			};
+
+			for ( var i = start; i < start + count; i ++ ) {
+
+				for ( var a = 0; a < attribute.itemSize; a ++ ) {
+
+					var value = attribute.array[ i * attribute.itemSize + a ];
+					output.min[ a ] = Math.min( output.min[ a ], value );
+					output.max[ a ] = Math.max( output.max[ a ], value );
+
+				}
+
+			}
+
+			return output;
+
+		}
+
+		/**
+		 * Checks if image size is POT.
+		 *
+		 * @param {Image} image The image to be checked.
+		 * @returns {Boolean} Returns true if image size is POT.
+		 *
+		 */
+		function isPowerOfTwo( image ) {
+
+			return Math.isPowerOfTwo( image.width ) && Math.isPowerOfTwo( image.height );
+
+		}
+
+		/**
+		 * Checks if normal attribute values are normalized.
+		 *
+		 * @param {BufferAttribute} normal
+		 * @returns {Boolean}
+		 *
+		 */
+		function isNormalizedNormalAttribute( normal ) {
+
+			if ( cachedData.attributesNormalized.has( normal ) ) {
+
+				return false;
+
+			}
+
+			var v = new Vector3();
+
+			for ( var i = 0, il = normal.count; i < il; i ++ ) {
+
+				// 0.0005 is from glTF-validator
+				if ( Math.abs( v.fromArray( normal.array, i * 3 ).length() - 1.0 ) > 0.0005 ) return false;
+
+			}
+
+			return true;
+
+		}
+
+		/**
+		 * Creates normalized normal buffer attribute.
+		 *
+		 * @param {BufferAttribute} normal
+		 * @returns {BufferAttribute}
+		 *
+		 */
+		function createNormalizedNormalAttribute( normal ) {
+
+			if ( cachedData.attributesNormalized.has( normal ) ) {
+
+				return cachedData.attributesNormalized.get( normal );
+
+			}
+
+			var attribute = normal.clone();
+
+			var v = new Vector3();
+
+			for ( var i = 0, il = attribute.count; i < il; i ++ ) {
+
+				v.fromArray( attribute.array, i * 3 );
+
+				if ( v.x === 0 && v.y === 0 && v.z === 0 ) {
+
+					// if values can't be normalized set (1, 0, 0)
+					v.setX( 1.0 );
+
+				} else {
+
+					v.normalize();
+
+				}
+
+				v.toArray( attribute.array, i * 3 );
+
+			}
+
+			cachedData.attributesNormalized.set( normal, attribute );
+
+			return attribute;
+
+		}
+
+		/**
+		 * Get the required size + padding for a buffer, rounded to the next 4-byte boundary.
+		 * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
+		 *
+		 * @param {Integer} bufferSize The size the original buffer.
+		 * @returns {Integer} new buffer size with required padding.
+		 *
+		 */
+		function getPaddedBufferSize( bufferSize ) {
+
+			return Math.ceil( bufferSize / 4 ) * 4;
+
+		}
+
+		/**
+		 * Returns a buffer aligned to 4-byte boundary.
+		 *
+		 * @param {ArrayBuffer} arrayBuffer Buffer to pad
+		 * @param {Integer} paddingByte (Optional)
+		 * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
+		 */
+		function getPaddedArrayBuffer( arrayBuffer, paddingByte ) {
+
+			paddingByte = paddingByte || 0;
+
+			var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength );
+
+			if ( paddedLength !== arrayBuffer.byteLength ) {
+
+				var array = new Uint8Array( paddedLength );
+				array.set( new Uint8Array( arrayBuffer ) );
+
+				if ( paddingByte !== 0 ) {
+
+					for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) {
+
+						array[ i ] = paddingByte;
+
+					}
+
+				}
+
+				return array.buffer;
+
+			}
+
+			return arrayBuffer;
+
+		}
+
+		/**
+		 * Serializes a userData.
+		 *
+		 * @param {Object3D|Material} object
+		 * @param {Object} gltfProperty
+		 */
+		function serializeUserData( object, gltfProperty ) {
+
+			if ( Object.keys( object.userData ).length === 0 ) {
+
+				return;
+
+			}
+
+			try {
+
+				var json = JSON.parse( JSON.stringify( object.userData ) );
+
+				if ( options.includeCustomExtensions && json.gltfExtensions ) {
+
+					if ( gltfProperty.extensions === undefined ) {
+
+						gltfProperty.extensions = {};
+
+					}
+
+					for ( var extensionName in json.gltfExtensions ) {
+
+						gltfProperty.extensions[ extensionName ] = json.gltfExtensions[ extensionName ];
+						extensionsUsed[ extensionName ] = true;
+
+					}
+
+					delete json.gltfExtensions;
+
+				}
+
+				if ( Object.keys( json ).length > 0 ) {
+
+					gltfProperty.extras = json;
+
+				}
+
+			} catch ( error ) {
+
+				console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' +
+					'won\'t be serialized because of JSON.stringify error - ' + error.message );
+
+			}
+
+		}
+
+		/**
+		 * Applies a texture transform, if present, to the map definition. Requires
+		 * the KHR_texture_transform extension.
+		 */
+		function applyTextureTransform( mapDef, texture ) {
+
+			var didTransform = false;
+			var transformDef = {};
+
+			if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) {
+
+				transformDef.offset = texture.offset.toArray();
+				didTransform = true;
+
+			}
+
+			if ( texture.rotation !== 0 ) {
+
+				transformDef.rotation = texture.rotation;
+				didTransform = true;
+
+			}
+
+			if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) {
+
+				transformDef.scale = texture.repeat.toArray();
+				didTransform = true;
+
+			}
+
+			if ( didTransform ) {
+
+				mapDef.extensions = mapDef.extensions || {};
+				mapDef.extensions[ 'KHR_texture_transform' ] = transformDef;
+				extensionsUsed[ 'KHR_texture_transform' ] = true;
+
+			}
+
+		}
+
+		/**
+		 * Process a buffer to append to the default one.
+		 * @param  {ArrayBuffer} buffer
+		 * @return {Integer}
+		 */
+		function processBuffer( buffer ) {
+
+			if ( ! outputJSON.buffers ) {
+
+				outputJSON.buffers = [ { byteLength: 0 } ];
+
+			}
+
+			// All buffers are merged before export.
+			buffers.push( buffer );
+
+			return 0;
+
+		}
+
+		/**
+		 * Process and generate a BufferView
+		 * @param  {BufferAttribute} attribute
+		 * @param  {number} componentType
+		 * @param  {number} start
+		 * @param  {number} count
+		 * @param  {number} target (Optional) Target usage of the BufferView
+		 * @return {Object}
+		 */
+		function processBufferView( attribute, componentType, start, count, target ) {
+
+			if ( ! outputJSON.bufferViews ) {
+
+				outputJSON.bufferViews = [];
+
+			}
+
+			// Create a new dataview and dump the attribute's array into it
+
+			var componentSize;
+
+			if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {
+
+				componentSize = 1;
+
+			} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
+
+				componentSize = 2;
+
+			} else {
+
+				componentSize = 4;
+
+			}
+
+			var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize );
+			var dataView = new DataView( new ArrayBuffer( byteLength ) );
+			var offset = 0;
+
+			for ( var i = start; i < start + count; i ++ ) {
+
+				for ( var a = 0; a < attribute.itemSize; a ++ ) {
+
+					// @TODO Fails on InterleavedBufferAttribute, and could probably be
+					// optimized for normal BufferAttribute.
+					var value = attribute.array[ i * attribute.itemSize + a ];
+
+					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
+
+						dataView.setFloat32( offset, value, true );
+
+					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) {
+
+						dataView.setUint32( offset, value, true );
+
+					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
+
+						dataView.setUint16( offset, value, true );
+
+					} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {
+
+						dataView.setUint8( offset, value );
+
+					}
+
+					offset += componentSize;
+
+				}
+
+			}
+
+			var gltfBufferView = {
+
+				buffer: processBuffer( dataView.buffer ),
+				byteOffset: byteOffset,
+				byteLength: byteLength
+
+			};
+
+			if ( target !== undefined ) gltfBufferView.target = target;
+
+			if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {
+
+				// Only define byteStride for vertex attributes.
+				gltfBufferView.byteStride = attribute.itemSize * componentSize;
+
+			}
+
+			byteOffset += byteLength;
+
+			outputJSON.bufferViews.push( gltfBufferView );
+
+			// @TODO Merge bufferViews where possible.
+			var output = {
+
+				id: outputJSON.bufferViews.length - 1,
+				byteLength: 0
+
+			};
+
+			return output;
+
+		}
+
+		/**
+		 * Process and generate a BufferView from an image Blob.
+		 * @param {Blob} blob
+		 * @return {Promise<Integer>}
+		 */
+		function processBufferViewImage( blob ) {
+
+			if ( ! outputJSON.bufferViews ) {
+
+				outputJSON.bufferViews = [];
+
+			}
+
+			return new Promise( function ( resolve ) {
+
+				var reader = new window.FileReader();
+				reader.readAsArrayBuffer( blob );
+				reader.onloadend = function () {
+
+					var buffer = getPaddedArrayBuffer( reader.result );
+
+					var bufferView = {
+						buffer: processBuffer( buffer ),
+						byteOffset: byteOffset,
+						byteLength: buffer.byteLength
+					};
+
+					byteOffset += buffer.byteLength;
+
+					outputJSON.bufferViews.push( bufferView );
+
+					resolve( outputJSON.bufferViews.length - 1 );
+
+				};
+
+			} );
+
+		}
+
+		/**
+		 * Process attribute to generate an accessor
+		 * @param  {BufferAttribute} attribute Attribute to process
+		 * @param  {BufferGeometry} geometry (Optional) Geometry used for truncated draw range
+		 * @param  {Integer} start (Optional)
+		 * @param  {Integer} count (Optional)
+		 * @return {Integer}           Index of the processed accessor on the "accessors" array
+		 */
+		function processAccessor( attribute, geometry, start, count ) {
+
+			var types = {
+
+				1: 'SCALAR',
+				2: 'VEC2',
+				3: 'VEC3',
+				4: 'VEC4',
+				16: 'MAT4'
+
+			};
+
+			var componentType;
+
+			// Detect the component type of the attribute array (float, uint or ushort)
+			if ( attribute.array.constructor === Float32Array ) {
+
+				componentType = WEBGL_CONSTANTS.FLOAT;
+
+			} else if ( attribute.array.constructor === Uint32Array ) {
+
+				componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
+
+			} else if ( attribute.array.constructor === Uint16Array ) {
+
+				componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
+
+			} else if ( attribute.array.constructor === Uint8Array ) {
+
+				componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
+
+			} else {
+
+				throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' );
+
+			}
+
+			if ( start === undefined ) start = 0;
+			if ( count === undefined ) count = attribute.count;
+
+			// @TODO Indexed buffer geometry with drawRange not supported yet
+			if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) {
+
+				var end = start + count;
+				var end2 = geometry.drawRange.count === Infinity
+					? attribute.count
+					: geometry.drawRange.start + geometry.drawRange.count;
+
+				start = Math.max( start, geometry.drawRange.start );
+				count = Math.min( end, end2 ) - start;
+
+				if ( count < 0 ) count = 0;
+
+			}
+
+			// Skip creating an accessor if the attribute doesn't have data to export
+			if ( count === 0 ) {
+
+				return null;
+
+			}
+
+			var minMax = getMinMax( attribute, start, count );
+
+			var bufferViewTarget;
+
+			// If geometry isn't provided, don't infer the target usage of the bufferView. For
+			// animation samplers, target must not be set.
+			if ( geometry !== undefined ) {
+
+				bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
+
+			}
+
+			var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget );
+
+			var gltfAccessor = {
+
+				bufferView: bufferView.id,
+				byteOffset: bufferView.byteOffset,
+				componentType: componentType,
+				count: count,
+				max: minMax.max,
+				min: minMax.min,
+				type: types[ attribute.itemSize ]
+
+			};
+
+			if ( ! outputJSON.accessors ) {
+
+				outputJSON.accessors = [];
+
+			}
+
+			outputJSON.accessors.push( gltfAccessor );
+
+			return outputJSON.accessors.length - 1;
+
+		}
+
+		/**
+		 * Process image
+		 * @param  {Image} image to process
+		 * @param  {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc)
+		 * @param  {Boolean} flipY before writing out the image
+		 * @return {Integer}     Index of the processed texture in the "images" array
+		 */
+		function processImage( image, format, flipY ) {
+
+			if ( ! cachedData.images.has( image ) ) {
+
+				cachedData.images.set( image, {} );
+
+			}
+
+			var cachedImages = cachedData.images.get( image );
+			var mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg';
+			var key = mimeType + ":flipY/" + flipY.toString();
+
+			if ( cachedImages[ key ] !== undefined ) {
+
+				return cachedImages[ key ];
+
+			}
+
+			if ( ! outputJSON.images ) {
+
+				outputJSON.images = [];
+
+			}
+
+			var gltfImage = { mimeType: mimeType };
+
+			if ( options.embedImages ) {
+
+				var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' );
+
+				canvas.width = image.width;
+				canvas.height = image.height;
+
+				if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( image ) ) {
+
+					console.warn( 'GLTFExporter: Resized non-power-of-two image.', image );
+
+					canvas.width = Math.floorPowerOfTwo( canvas.width );
+					canvas.height = Math.floorPowerOfTwo( canvas.height );
+
+				}
+
+				var ctx = canvas.getContext( '2d' );
+
+				if ( flipY === true ) {
+
+					ctx.translate( 0, canvas.height );
+					ctx.scale( 1, - 1 );
+
+				}
+
+				ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
+
+				if ( options.binary === true ) {
+
+					pending.push( new Promise( function ( resolve ) {
+
+						canvas.toBlob( function ( blob ) {
+
+							processBufferViewImage( blob ).then( function ( bufferViewIndex ) {
+
+								gltfImage.bufferView = bufferViewIndex;
+
+								resolve();
+
+							} );
+
+						}, mimeType );
+
+					} ) );
+
+				} else {
+
+					gltfImage.uri = canvas.toDataURL( mimeType );
+
+				}
+
+			} else {
+
+				gltfImage.uri = image.src;
+
+			}
+
+			outputJSON.images.push( gltfImage );
+
+			var index = outputJSON.images.length - 1;
+			cachedImages[ key ] = index;
+
+			return index;
+
+		}
+
+		/**
+		 * Process sampler
+		 * @param  {Texture} map Texture to process
+		 * @return {Integer}     Index of the processed texture in the "samplers" array
+		 */
+		function processSampler( map ) {
+
+			if ( ! outputJSON.samplers ) {
+
+				outputJSON.samplers = [];
+
+			}
+
+			var gltfSampler = {
+
+				magFilter: THREE_TO_WEBGL[ map.magFilter ],
+				minFilter: THREE_TO_WEBGL[ map.minFilter ],
+				wrapS: THREE_TO_WEBGL[ map.wrapS ],
+				wrapT: THREE_TO_WEBGL[ map.wrapT ]
+
+			};
+
+			outputJSON.samplers.push( gltfSampler );
+
+			return outputJSON.samplers.length - 1;
+
+		}
+
+		/**
+		 * Process texture
+		 * @param  {Texture} map Map to process
+		 * @return {Integer}     Index of the processed texture in the "textures" array
+		 */
+		function processTexture( map ) {
+
+			if ( cachedData.textures.has( map ) ) {
+
+				return cachedData.textures.get( map );
+
+			}
+
+			if ( ! outputJSON.textures ) {
+
+				outputJSON.textures = [];
+
+			}
+
+			var gltfTexture = {
+
+				sampler: processSampler( map ),
+				source: processImage( map.image, map.format, map.flipY )
+
+			};
+
+			outputJSON.textures.push( gltfTexture );
+
+			var index = outputJSON.textures.length - 1;
+			cachedData.textures.set( map, index );
+
+			return index;
+
+		}
+
+		/**
+		 * Process material
+		 * @param  {Material} material Material to process
+		 * @return {Integer}      Index of the processed material in the "materials" array
+		 */
+		function processMaterial( material ) {
+
+			if ( cachedData.materials.has( material ) ) {
+
+				return cachedData.materials.get( material );
+
+			}
+
+			if ( ! outputJSON.materials ) {
+
+				outputJSON.materials = [];
+
+			}
+
+			if ( material.isShaderMaterial ) {
+
+				console.warn( 'GLTFExporter: ShaderMaterial not supported.' );
+				return null;
+
+			}
+
+			// @QUESTION Should we avoid including any attribute that has the default value?
+			var gltfMaterial = {
+
+				pbrMetallicRoughness: {}
+
+			};
+
+			if ( material.isMeshBasicMaterial ) {
+
+				gltfMaterial.extensions = { KHR_materials_unlit: {} };
+
+				extensionsUsed[ 'KHR_materials_unlit' ] = true;
+
+			} else if ( ! material.isMeshStandardMaterial ) {
+
+				console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' );
+
+			}
+
+			// pbrMetallicRoughness.baseColorFactor
+			var color = material.color.toArray().concat( [ material.opacity ] );
+
+			if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) {
+
+				gltfMaterial.pbrMetallicRoughness.baseColorFactor = color;
+
+			}
+
+			if ( material.isMeshStandardMaterial ) {
+
+				gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness;
+				gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness;
+
+			} else if ( material.isMeshBasicMaterial ) {
+
+				gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.0;
+				gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.9;
+
+			} else {
+
+				gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5;
+				gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5;
+
+			}
+
+			// pbrMetallicRoughness.metallicRoughnessTexture
+			if ( material.metalnessMap || material.roughnessMap ) {
+
+				if ( material.metalnessMap === material.roughnessMap ) {
+
+					var metalRoughMapDef = { index: processTexture( material.metalnessMap ) };
+					applyTextureTransform( metalRoughMapDef, material.metalnessMap );
+					gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
+
+				} else {
+
+					console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' );
+
+				}
+
+			}
+
+			// pbrMetallicRoughness.baseColorTexture
+			if ( material.map ) {
+
+				var baseColorMapDef = { index: processTexture( material.map ) };
+				applyTextureTransform( baseColorMapDef, material.map );
+				gltfMaterial.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
+
+			}
+
+			if ( material.isMeshBasicMaterial ||
+				material.isLineBasicMaterial ||
+				material.isPointsMaterial ) {
+
+			} else {
+
+				// emissiveFactor
+				var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray();
+
+				if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) {
+
+					gltfMaterial.emissiveFactor = emissive;
+
+				}
+
+				// emissiveTexture
+				if ( material.emissiveMap ) {
+
+					var emissiveMapDef = { index: processTexture( material.emissiveMap ) };
+					applyTextureTransform( emissiveMapDef, material.emissiveMap );
+					gltfMaterial.emissiveTexture = emissiveMapDef;
+
+				}
+
+			}
+
+			// normalTexture
+			if ( material.normalMap ) {
+
+				var normalMapDef = { index: processTexture( material.normalMap ) };
+
+				if ( material.normalScale.x !== - 1 ) {
+
+					if ( material.normalScale.x !== material.normalScale.y ) {
+
+						console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' );
+
+					}
+
+					normalMapDef.scale = material.normalScale.x;
+
+				}
+
+				applyTextureTransform( normalMapDef, material.normalMap );
+
+				gltfMaterial.normalTexture = normalMapDef;
+
+			}
+
+			// occlusionTexture
+			if ( material.aoMap ) {
+
+				var occlusionMapDef = {
+					index: processTexture( material.aoMap ),
+					texCoord: 1
+				};
+
+				if ( material.aoMapIntensity !== 1.0 ) {
+
+					occlusionMapDef.strength = material.aoMapIntensity;
+
+				}
+
+				applyTextureTransform( occlusionMapDef, material.aoMap );
+
+				gltfMaterial.occlusionTexture = occlusionMapDef;
+
+			}
+
+			// alphaMode
+			if ( material.transparent || material.alphaTest > 0.0 ) {
+
+				gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK';
+
+				// Write alphaCutoff if it's non-zero and different from the default (0.5).
+				if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) {
+
+					gltfMaterial.alphaCutoff = material.alphaTest;
+
+				}
+
+			}
+
+			// doubleSided
+			if ( material.side === DoubleSide ) {
+
+				gltfMaterial.doubleSided = true;
+
+			}
+
+			if ( material.name !== '' ) {
+
+				gltfMaterial.name = material.name;
+
+			}
+
+			serializeUserData( material, gltfMaterial );
+
+			outputJSON.materials.push( gltfMaterial );
+
+			var index = outputJSON.materials.length - 1;
+			cachedData.materials.set( material, index );
+
+			return index;
+
+		}
+
+		/**
+		 * Process mesh
+		 * @param  {Mesh} mesh Mesh to process
+		 * @return {Integer}      Index of the processed mesh in the "meshes" array
+		 */
+		function processMesh( mesh ) {
+
+			var cacheKey = mesh.geometry.uuid + ':' + mesh.material.uuid;
+			if ( cachedData.meshes.has( cacheKey ) ) {
+
+				return cachedData.meshes.get( cacheKey );
+
+			}
+
+			var geometry = mesh.geometry;
+
+			var mode;
+
+			// Use the correct mode
+			if ( mesh.isLineSegments ) {
+
+				mode = WEBGL_CONSTANTS.LINES;
+
+			} else if ( mesh.isLineLoop ) {
+
+				mode = WEBGL_CONSTANTS.LINE_LOOP;
+
+			} else if ( mesh.isLine ) {
+
+				mode = WEBGL_CONSTANTS.LINE_STRIP;
+
+			} else if ( mesh.isPoints ) {
+
+				mode = WEBGL_CONSTANTS.POINTS;
+
+			} else {
+
+				if ( ! geometry.isBufferGeometry ) {
+
+					console.warn( 'GLTFExporter: Exporting Geometry will increase file size. Use BufferGeometry instead.' );
+
+					var geometryTemp = new BufferGeometry();
+					geometryTemp.fromGeometry( geometry );
+					geometry = geometryTemp;
+
+				}
+
+				if ( mesh.drawMode === TriangleFanDrawMode ) {
+
+					console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' );
+					mode = WEBGL_CONSTANTS.TRIANGLE_FAN;
+
+				} else if ( mesh.drawMode === TriangleStripDrawMode ) {
+
+					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINE_STRIP : WEBGL_CONSTANTS.TRIANGLE_STRIP;
+
+				} else {
+
+					mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
+
+				}
+
+			}
+
+			var gltfMesh = {};
+
+			var attributes = {};
+			var primitives = [];
+			var targets = [];
+
+			// Conversion between attributes names in threejs and gltf spec
+			var nameConversion = {
+
+				uv: 'TEXCOORD_0',
+				uv2: 'TEXCOORD_1',
+				color: 'COLOR_0',
+				skinWeight: 'WEIGHTS_0',
+				skinIndex: 'JOINTS_0'
+
+			};
+
+			var originalNormal = geometry.getAttribute( 'normal' );
+
+			if ( originalNormal !== undefined && ! isNormalizedNormalAttribute( originalNormal ) ) {
+
+				console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' );
+
+				geometry.addAttribute( 'normal', createNormalizedNormalAttribute( originalNormal ) );
+
+			}
+
+			// @QUESTION Detect if .vertexColors = VertexColors?
+			// For every attribute create an accessor
+			var modifiedAttribute = null;
+			for ( var attributeName in geometry.attributes ) {
+
+				var attribute = geometry.attributes[ attributeName ];
+				attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
+
+				if ( cachedData.attributes.has( attribute ) ) {
+
+					attributes[ attributeName ] = cachedData.attributes.get( attribute );
+					continue;
+
+				}
+
+				// JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
+				modifiedAttribute = null;
+				var array = attribute.array;
+				if ( attributeName === 'JOINTS_0' &&
+					! ( array instanceof Uint16Array ) &&
+					! ( array instanceof Uint8Array ) ) {
+
+					console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
+					modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
+
+				}
+
+				if ( attributeName.substr( 0, 5 ) !== 'MORPH' ) {
+
+					var accessor = processAccessor( modifiedAttribute || attribute, geometry );
+					if ( accessor !== null ) {
+
+						attributes[ attributeName ] = accessor;
+						cachedData.attributes.set( attribute, accessor );
+
+					}
+
+				}
+
+			}
+
+			if ( originalNormal !== undefined ) geometry.addAttribute( 'normal', originalNormal );
+
+			// Skip if no exportable attributes found
+			if ( Object.keys( attributes ).length === 0 ) {
+
+				return null;
+
+			}
+
+			// Morph targets
+			if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {
+
+				var weights = [];
+				var targetNames = [];
+				var reverseDictionary = {};
+
+				if ( mesh.morphTargetDictionary !== undefined ) {
+
+					for ( var key in mesh.morphTargetDictionary ) {
+
+						reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key;
+
+					}
+
+				}
+
+				for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {
+
+					var target = {};
+
+					var warned = false;
+
+					for ( var attributeName in geometry.morphAttributes ) {
+
+						// glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
+						// Three.js doesn't support TANGENT yet.
+
+						if ( attributeName !== 'position' && attributeName !== 'normal' ) {
+
+							if ( ! warned ) {
+
+								console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' );
+								warned = true;
+
+							}
+
+							continue;
+
+						}
+
+						var attribute = geometry.morphAttributes[ attributeName ][ i ];
+						var gltfAttributeName = attributeName.toUpperCase();
+
+						// Three.js morph attribute has absolute values while the one of glTF has relative values.
+						//
+						// glTF 2.0 Specification:
+						// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets
+
+						var baseAttribute = geometry.attributes[ attributeName ];
+
+						if ( cachedData.attributes.has( attribute ) ) {
+
+							target[ gltfAttributeName ] = cachedData.attributes.get( attribute );
+							continue;
+
+						}
+
+						// Clones attribute not to override
+						var relativeAttribute = attribute.clone();
+
+						for ( var j = 0, jl = attribute.count; j < jl; j ++ ) {
+
+							relativeAttribute.setXYZ(
+								j,
+								attribute.getX( j ) - baseAttribute.getX( j ),
+								attribute.getY( j ) - baseAttribute.getY( j ),
+								attribute.getZ( j ) - baseAttribute.getZ( j )
+							);
+
+						}
+
+						target[ gltfAttributeName ] = processAccessor( relativeAttribute, geometry );
+						cachedData.attributes.set( baseAttribute, target[ gltfAttributeName ] );
+
+					}
+
+					targets.push( target );
+
+					weights.push( mesh.morphTargetInfluences[ i ] );
+					if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
+
+				}
+
+				gltfMesh.weights = weights;
+
+				if ( targetNames.length > 0 ) {
+
+					gltfMesh.extras = {};
+					gltfMesh.extras.targetNames = targetNames;
+
+				}
+
+			}
+
+			var forceIndices = options.forceIndices;
+			var isMultiMaterial = Array.isArray( mesh.material );
+
+			if ( isMultiMaterial && geometry.groups.length === 0 ) return null;
+
+			if ( ! forceIndices && geometry.index === null && isMultiMaterial ) {
+
+				// temporal workaround.
+				console.warn( 'THREE.GLTFExporter: Creating index for non-indexed multi-material mesh.' );
+				forceIndices = true;
+
+			}
+
+			var didForceIndices = false;
+
+			if ( geometry.index === null && forceIndices ) {
+
+				var indices = [];
+
+				for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) {
+
+					indices[ i ] = i;
+
+				}
+
+				geometry.setIndex( indices );
+
+				didForceIndices = true;
+
+			}
+
+			var materials = isMultiMaterial ? mesh.material : [ mesh.material ];
+			var groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];
+
+			for ( var i = 0, il = groups.length; i < il; i ++ ) {
+
+				var primitive = {
+					mode: mode,
+					attributes: attributes,
+				};
+
+				serializeUserData( geometry, primitive );
+
+				if ( targets.length > 0 ) primitive.targets = targets;
+
+				if ( geometry.index !== null ) {
+
+					if ( cachedData.attributes.has( geometry.index ) ) {
+
+						primitive.indices = cachedData.attributes.get( geometry.index );
+
+					} else {
+
+						primitive.indices = processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );
+						cachedData.attributes.set( geometry.index, primitive.indices );
+
+					}
+
+				}
+
+				var material = processMaterial( materials[ groups[ i ].materialIndex ] );
+
+				if ( material !== null ) {
+
+					primitive.material = material;
+
+				}
+
+				primitives.push( primitive );
+
+			}
+
+			if ( didForceIndices ) {
+
+				geometry.setIndex( null );
+
+			}
+
+			gltfMesh.primitives = primitives;
+
+			if ( ! outputJSON.meshes ) {
+
+				outputJSON.meshes = [];
+
+			}
+
+			outputJSON.meshes.push( gltfMesh );
+
+			var index = outputJSON.meshes.length - 1;
+			cachedData.meshes.set( cacheKey, index );
+
+			return index;
+
+		}
+
+		/**
+		 * Process camera
+		 * @param  {Camera} camera Camera to process
+		 * @return {Integer}      Index of the processed mesh in the "camera" array
+		 */
+		function processCamera( camera ) {
+
+			if ( ! outputJSON.cameras ) {
+
+				outputJSON.cameras = [];
+
+			}
+
+			var isOrtho = camera.isOrthographicCamera;
+
+			var gltfCamera = {
+
+				type: isOrtho ? 'orthographic' : 'perspective'
+
+			};
+
+			if ( isOrtho ) {
+
+				gltfCamera.orthographic = {
+
+					xmag: camera.right * 2,
+					ymag: camera.top * 2,
+					zfar: camera.far <= 0 ? 0.001 : camera.far,
+					znear: camera.near < 0 ? 0 : camera.near
+
+				};
+
+			} else {
+
+				gltfCamera.perspective = {
+
+					aspectRatio: camera.aspect,
+					yfov: Math.degToRad( camera.fov ),
+					zfar: camera.far <= 0 ? 0.001 : camera.far,
+					znear: camera.near < 0 ? 0 : camera.near
+
+				};
+
+			}
+
+			if ( camera.name !== '' ) {
+
+				gltfCamera.name = camera.type;
+
+			}
+
+			outputJSON.cameras.push( gltfCamera );
+
+			return outputJSON.cameras.length - 1;
+
+		}
+
+		/**
+		 * Creates glTF animation entry from AnimationClip object.
+		 *
+		 * Status:
+		 * - Only properties listed in PATH_PROPERTIES may be animated.
+		 *
+		 * @param {AnimationClip} clip
+		 * @param {Object3D} root
+		 * @return {number}
+		 */
+		function processAnimation( clip, root ) {
+
+			if ( ! outputJSON.animations ) {
+
+				outputJSON.animations = [];
+
+			}
+
+			clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root );
+
+			var tracks = clip.tracks;
+			var channels = [];
+			var samplers = [];
+
+			for ( var i = 0; i < tracks.length; ++ i ) {
+
+				var track = tracks[ i ];
+				var trackBinding = PropertyBinding.parseTrackName( track.name );
+				var trackNode = PropertyBinding.findNode( root, trackBinding.nodeName );
+				var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];
+
+				if ( trackBinding.objectName === 'bones' ) {
+
+					if ( trackNode.isSkinnedMesh === true ) {
+
+						trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex );
+
+					} else {
+
+						trackNode = undefined;
+
+					}
+
+				}
+
+				if ( ! trackNode || ! trackProperty ) {
+
+					console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
+					return null;
+
+				}
+
+				var inputItemSize = 1;
+				var outputItemSize = track.values.length / track.times.length;
+
+				if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {
+
+					outputItemSize /= trackNode.morphTargetInfluences.length;
+
+				}
+
+				var interpolation;
+
+				// @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE
+
+				// Detecting glTF cubic spline interpolant by checking factory method's special property
+				// GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
+				// valid value from .getInterpolation().
+				if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
+
+					interpolation = 'CUBICSPLINE';
+
+					// itemSize of CUBICSPLINE keyframe is 9
+					// (VEC3 * 3: inTangent, splineVertex, and outTangent)
+					// but needs to be stored as VEC3 so dividing by 3 here.
+					outputItemSize /= 3;
+
+				} else if ( track.getInterpolation() === InterpolateDiscrete ) {
+
+					interpolation = 'STEP';
+
+				} else {
+
+					interpolation = 'LINEAR';
+
+				}
+
+				samplers.push( {
+
+					input: processAccessor( new BufferAttribute( track.times, inputItemSize ) ),
+					output: processAccessor( new BufferAttribute( track.values, outputItemSize ) ),
+					interpolation: interpolation
+
+				} );
+
+				channels.push( {
+
+					sampler: samplers.length - 1,
+					target: {
+						node: nodeMap.get( trackNode ),
+						path: trackProperty
+					}
+
+				} );
+
+			}
+
+			outputJSON.animations.push( {
+
+				name: clip.name || 'clip_' + outputJSON.animations.length,
+				samplers: samplers,
+				channels: channels
+
+			} );
+
+			return outputJSON.animations.length - 1;
+
+		}
+
+		function processSkin( object ) {
+
+			var node = outputJSON.nodes[ nodeMap.get( object ) ];
+
+			var skeleton = object.skeleton;
+			var rootJoint = object.skeleton.bones[ 0 ];
+
+			if ( rootJoint === undefined ) return null;
+
+			var joints = [];
+			var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 );
+
+			for ( var i = 0; i < skeleton.bones.length; ++ i ) {
+
+				joints.push( nodeMap.get( skeleton.bones[ i ] ) );
+
+				skeleton.boneInverses[ i ].toArray( inverseBindMatrices, i * 16 );
+
+			}
+
+			if ( outputJSON.skins === undefined ) {
+
+				outputJSON.skins = [];
+
+			}
+
+			outputJSON.skins.push( {
+
+				inverseBindMatrices: processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ),
+				joints: joints,
+				skeleton: nodeMap.get( rootJoint )
+
+			} );
+
+			var skinIndex = node.skin = outputJSON.skins.length - 1;
+
+			return skinIndex;
+
+		}
+
+		function processLight( light ) {
+
+			var lightDef = {};
+
+			if ( light.name ) lightDef.name = light.name;
+
+			lightDef.color = light.color.toArray();
+
+			lightDef.intensity = light.intensity;
+
+			if ( light.isDirectionalLight ) {
+
+				lightDef.type = 'directional';
+
+			} else if ( light.isPointLight ) {
+
+				lightDef.type = 'point';
+				if ( light.distance > 0 ) lightDef.range = light.distance;
+
+			} else if ( light.isSpotLight ) {
+
+				lightDef.type = 'spot';
+				if ( light.distance > 0 ) lightDef.range = light.distance;
+				lightDef.spot = {};
+				lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0;
+				lightDef.spot.outerConeAngle = light.angle;
+
+			}
+
+			if ( light.decay !== undefined && light.decay !== 2 ) {
+
+				console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, '
+					+ 'and expects light.decay=2.' );
+
+			}
+
+			if ( light.target
+					&& ( light.target.parent !== light
+					 || light.target.position.x !== 0
+					 || light.target.position.y !== 0
+					 || light.target.position.z !== - 1 ) ) {
+
+				console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, '
+					+ 'make light.target a child of the light with position 0,0,-1.' );
+
+			}
+
+			var lights = outputJSON.extensions[ 'KHR_lights_punctual' ].lights;
+			lights.push( lightDef );
+			return lights.length - 1;
+
+		}
+
+		/**
+		 * Process Object3D node
+		 * @param  {Object3D} node Object3D to processNode
+		 * @return {Integer}      Index of the node in the nodes list
+		 */
+		function processNode( object ) {
+
+			if ( ! outputJSON.nodes ) {
+
+				outputJSON.nodes = [];
+
+			}
+
+			var gltfNode = {};
+
+			if ( options.trs ) {
+
+				var rotation = object.quaternion.toArray();
+				var position = object.position.toArray();
+				var scale = object.scale.toArray();
+
+				if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) {
+
+					gltfNode.rotation = rotation;
+
+				}
+
+				if ( ! equalArray( position, [ 0, 0, 0 ] ) ) {
+
+					gltfNode.translation = position;
+
+				}
+
+				if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) {
+
+					gltfNode.scale = scale;
+
+				}
+
+			} else {
+
+				object.updateMatrix();
+				if ( ! equalArray( object.matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ) ) {
+
+					gltfNode.matrix = object.matrix.elements;
+
+				}
+
+			}
+
+			// We don't export empty strings name because it represents no-name in Three.js.
+			if ( object.name !== '' ) {
+
+				gltfNode.name = String( object.name );
+
+			}
+
+			serializeUserData( object, gltfNode );
+
+			if ( object.isMesh || object.isLine || object.isPoints ) {
+
+				var mesh = processMesh( object );
+
+				if ( mesh !== null ) {
+
+					gltfNode.mesh = mesh;
+
+				}
+
+			} else if ( object.isCamera ) {
+
+				gltfNode.camera = processCamera( object );
+
+			} else if ( object.isDirectionalLight || object.isPointLight || object.isSpotLight ) {
+
+				if ( ! extensionsUsed[ 'KHR_lights_punctual' ] ) {
+
+					outputJSON.extensions = outputJSON.extensions || {};
+					outputJSON.extensions[ 'KHR_lights_punctual' ] = { lights: [] };
+					extensionsUsed[ 'KHR_lights_punctual' ] = true;
+
+				}
+
+				gltfNode.extensions = gltfNode.extensions || {};
+				gltfNode.extensions[ 'KHR_lights_punctual' ] = { light: processLight( object ) };
+
+			} else if ( object.isLight ) {
+
+				console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.' );
+				return null;
+
+			}
+
+			if ( object.isSkinnedMesh ) {
+
+				skins.push( object );
+
+			}
+
+			if ( object.children.length > 0 ) {
+
+				var children = [];
+
+				for ( var i = 0, l = object.children.length; i < l; i ++ ) {
+
+					var child = object.children[ i ];
+
+					if ( child.visible || options.onlyVisible === false ) {
+
+						var node = processNode( child );
+
+						if ( node !== null ) {
+
+							children.push( node );
+
+						}
+
+					}
+
+				}
+
+				if ( children.length > 0 ) {
+
+					gltfNode.children = children;
+
+				}
+
+
+			}
+
+			outputJSON.nodes.push( gltfNode );
+
+			var nodeIndex = outputJSON.nodes.length - 1;
+			nodeMap.set( object, nodeIndex );
+
+			return nodeIndex;
+
+		}
+
+		/**
+		 * Process Scene
+		 * @param  {Scene} node Scene to process
+		 */
+		function processScene( scene ) {
+
+			if ( ! outputJSON.scenes ) {
+
+				outputJSON.scenes = [];
+				outputJSON.scene = 0;
+
+			}
+
+			var gltfScene = {
+
+				nodes: []
+
+			};
+
+			if ( scene.name !== '' ) {
+
+				gltfScene.name = scene.name;
+
+			}
+
+			if ( scene.userData && Object.keys( scene.userData ).length > 0 ) {
+
+				gltfScene.extras = serializeUserData( scene );
+
+			}
+
+			outputJSON.scenes.push( gltfScene );
+
+			var nodes = [];
+
+			for ( var i = 0, l = scene.children.length; i < l; i ++ ) {
+
+				var child = scene.children[ i ];
+
+				if ( child.visible || options.onlyVisible === false ) {
+
+					var node = processNode( child );
+
+					if ( node !== null ) {
+
+						nodes.push( node );
+
+					}
+
+				}
+
+			}
+
+			if ( nodes.length > 0 ) {
+
+				gltfScene.nodes = nodes;
+
+			}
+
+			serializeUserData( scene, gltfScene );
+
+		}
+
+		/**
+		 * Creates a Scene to hold a list of objects and parse it
+		 * @param  {Array} objects List of objects to process
+		 */
+		function processObjects( objects ) {
+
+			var scene = new Scene();
+			scene.name = 'AuxScene';
+
+			for ( var i = 0; i < objects.length; i ++ ) {
+
+				// We push directly to children instead of calling `add` to prevent
+				// modify the .parent and break its original scene and hierarchy
+				scene.children.push( objects[ i ] );
+
+			}
+
+			processScene( scene );
+
+		}
+
+		function processInput( input ) {
+
+			input = input instanceof Array ? input : [ input ];
+
+			var objectsWithoutScene = [];
+
+			for ( var i = 0; i < input.length; i ++ ) {
+
+				if ( input[ i ] instanceof Scene ) {
+
+					processScene( input[ i ] );
+
+				} else {
+
+					objectsWithoutScene.push( input[ i ] );
+
+				}
+
+			}
+
+			if ( objectsWithoutScene.length > 0 ) {
+
+				processObjects( objectsWithoutScene );
+
+			}
+
+			for ( var i = 0; i < skins.length; ++ i ) {
+
+				processSkin( skins[ i ] );
+
+			}
+
+			for ( var i = 0; i < options.animations.length; ++ i ) {
+
+				processAnimation( options.animations[ i ], input[ 0 ] );
+
+			}
+
+		}
+
+		processInput( input );
+
+		Promise.all( pending ).then( function () {
+
+			// Merge buffers.
+			var blob = new Blob( buffers, { type: 'application/octet-stream' } );
+
+			// Declare extensions.
+			var extensionsUsedList = Object.keys( extensionsUsed );
+			if ( extensionsUsedList.length > 0 ) outputJSON.extensionsUsed = extensionsUsedList;
+
+			if ( outputJSON.buffers && outputJSON.buffers.length > 0 ) {
+
+				// Update bytelength of the single buffer.
+				outputJSON.buffers[ 0 ].byteLength = blob.size;
+
+				var reader = new window.FileReader();
+
+				if ( options.binary === true ) {
+
+					// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
+
+					var GLB_HEADER_BYTES = 12;
+					var GLB_HEADER_MAGIC = 0x46546C67;
+					var GLB_VERSION = 2;
+
+					var GLB_CHUNK_PREFIX_BYTES = 8;
+					var GLB_CHUNK_TYPE_JSON = 0x4E4F534A;
+					var GLB_CHUNK_TYPE_BIN = 0x004E4942;
+
+					reader.readAsArrayBuffer( blob );
+					reader.onloadend = function () {
+
+						// Binary chunk.
+						var binaryChunk = getPaddedArrayBuffer( reader.result );
+						var binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
+						binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true );
+						binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true );
+
+						// JSON chunk.
+						var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( outputJSON ) ), 0x20 );
+						var jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
+						jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true );
+						jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true );
+
+						// GLB header.
+						var header = new ArrayBuffer( GLB_HEADER_BYTES );
+						var headerView = new DataView( header );
+						headerView.setUint32( 0, GLB_HEADER_MAGIC, true );
+						headerView.setUint32( 4, GLB_VERSION, true );
+						var totalByteLength = GLB_HEADER_BYTES
+							+ jsonChunkPrefix.byteLength + jsonChunk.byteLength
+							+ binaryChunkPrefix.byteLength + binaryChunk.byteLength;
+						headerView.setUint32( 8, totalByteLength, true );
+
+						var glbBlob = new Blob( [
+							header,
+							jsonChunkPrefix,
+							jsonChunk,
+							binaryChunkPrefix,
+							binaryChunk
+						], { type: 'application/octet-stream' } );
+
+						var glbReader = new window.FileReader();
+						glbReader.readAsArrayBuffer( glbBlob );
+						glbReader.onloadend = function () {
+
+							onDone( glbReader.result );
+
+						};
+
+					};
+
+				} else {
+
+					reader.readAsDataURL( blob );
+					reader.onloadend = function () {
+
+						var base64data = reader.result;
+						outputJSON.buffers[ 0 ].uri = base64data;
+						onDone( outputJSON );
+
+					};
+
+				}
+
+			} else {
+
+				onDone( outputJSON );
+
+			}
+
+		} );
+
+	}
+
+};
+
+GLTFExporter.Utils = {
+
+	insertKeyframe: function ( track, time ) {
+
+		var tolerance = 0.001; // 1ms
+		var valueSize = track.getValueSize();
+
+		var times = new track.TimeBufferType( track.times.length + 1 );
+		var values = new track.ValueBufferType( track.values.length + valueSize );
+		var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );
+
+		var index;
+
+		if ( track.times.length === 0 ) {
+
+			times[ 0 ] = time;
+
+			for ( var i = 0; i < valueSize; i ++ ) {
+
+				values[ i ] = 0;
+
+			}
+
+			index = 0;
+
+		} else if ( time < track.times[ 0 ] ) {
+
+			if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;
+
+			times[ 0 ] = time;
+			times.set( track.times, 1 );
+
+			values.set( interpolant.evaluate( time ), 0 );
+			values.set( track.values, valueSize );
+
+			index = 0;
+
+		} else if ( time > track.times[ track.times.length - 1 ] ) {
+
+			if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {
+
+				return track.times.length - 1;
+
+			}
+
+			times[ times.length - 1 ] = time;
+			times.set( track.times, 0 );
+
+			values.set( track.values, 0 );
+			values.set( interpolant.evaluate( time ), track.values.length );
+
+			index = times.length - 1;
+
+		} else {
+
+			for ( var i = 0; i < track.times.length; i ++ ) {
+
+				if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;
+
+				if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {
+
+					times.set( track.times.slice( 0, i + 1 ), 0 );
+					times[ i + 1 ] = time;
+					times.set( track.times.slice( i + 1 ), i + 2 );
+
+					values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
+					values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
+					values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );
+
+					index = i + 1;
+
+					break;
+
+				}
+
+			}
+
+		}
+
+		track.times = times;
+		track.values = values;
+
+		return index;
+
+	},
+
+	mergeMorphTargetTracks: function ( clip, root ) {
+
+		var tracks = [];
+		var mergedTracks = {};
+		var sourceTracks = clip.tracks;
+
+		for ( var i = 0; i < sourceTracks.length; ++ i ) {
+
+			var sourceTrack = sourceTracks[ i ];
+			var sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name );
+			var sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName );
+
+			if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) {
+
+				// Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is.
+				tracks.push( sourceTrack );
+				continue;
+
+			}
+
+			if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete
+				&& sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {
+
+				if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+
+					// This should never happen, because glTF morph target animations
+					// affect all targets already.
+					throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );
+
+				}
+
+				console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' );
+
+				sourceTrack = sourceTrack.clone();
+				sourceTrack.setInterpolation( InterpolateLinear );
+
+			}
+
+			var targetCount = sourceTrackNode.morphTargetInfluences.length;
+			var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];
+
+			if ( targetIndex === undefined ) {
+
+				throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex );
+
+			}
+
+			var mergedTrack;
+
+			// If this is the first time we've seen this object, create a new
+			// track to store merged keyframe data for each morph target.
+			if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {
+
+				mergedTrack = sourceTrack.clone();
+
+				var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );
+
+				for ( var j = 0; j < mergedTrack.times.length; j ++ ) {
+
+					values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];
+
+				}
+
+				mergedTrack.name = '.morphTargetInfluences';
+				mergedTrack.values = values;
+
+				mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
+				tracks.push( mergedTrack );
+
+				continue;
+
+			}
+
+			var mergedKeyframeIndex = 0;
+			var sourceKeyframeIndex = 0;
+			var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );
+
+			mergedTrack = mergedTracks[ sourceTrackNode.uuid ];
+
+			// For every existing keyframe of the merged track, write a (possibly
+			// interpolated) value from the source track.
+			for ( var j = 0; j < mergedTrack.times.length; j ++ ) {
+
+				mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );
+
+			}
+
+			// For every existing keyframe of the source track, write a (possibly
+			// new) keyframe to the merged track. Values from the previous loop may
+			// be written again, but keyframes are de-duplicated.
+			for ( var j = 0; j < sourceTrack.times.length; j ++ ) {
+
+				var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
+				mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];
+
+			}
+
+		}
+
+		clip.tracks = tracks;
+
+		return clip;
+
+	}
+
+};
+
+export { GLTFExporter };

+ 27 - 0
examples/jsm/loaders/GLTFLoader.d.ts

@@ -0,0 +1,27 @@
+import {
+  AnimationClip,
+  Camera,
+  LoadingManager,
+  Scene
+} from '../../../src/Three';
+
+export interface GLTF {
+  animations: AnimationClip[];
+  scene: Scene;
+  scenes: Scene[];
+  cameras: Camera[];
+  asset: object;
+}
+
+export class GLTFLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  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;
+  parse(data: ArrayBuffer, path: string, onLoad: (gltf: GLTF) => void, onError?: (event: ErrorEvent) => void) : void;
+}

+ 222 - 444
examples/jsm/loaders/GLTFLoader.js

@@ -10,7 +10,6 @@ import {
 	AddEquation,
 	AlwaysDepth,
 	AnimationClip,
-	AnimationUtils,
 	BackSide,
 	Bone,
 	BufferAttribute,
@@ -33,7 +32,6 @@ import {
 	Interpolant,
 	InterpolateDiscrete,
 	InterpolateLinear,
-	InterpolateSmooth,
 	LessDepth,
 	LessEqualDepth,
 	Line,
@@ -283,7 +281,7 @@ var GLTFLoader = ( function () {
 							break;
 
 						case EXTENSIONS.MSFT_TEXTURE_DDS:
-							extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension( json );
+							extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension();
 							break;
 
 						case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
@@ -312,23 +310,7 @@ var GLTFLoader = ( function () {
 
 			} );
 
-			parser.parse( function ( scene, scenes, cameras, animations, json ) {
-
-				var glTF = {
-					scene: scene,
-					scenes: scenes,
-					cameras: cameras,
-					animations: animations,
-					asset: json.asset,
-					parser: parser,
-					userData: {}
-				};
-
-				addUnknownExtensionsToUserData( extensions, glTF, json );
-
-				onLoad( glTF );
-
-			}, onError );
+			parser.parse( onLoad, onError );
 
 		}
 
@@ -459,6 +441,10 @@ var GLTFLoader = ( function () {
 
 		}
 
+		// Some lights (e.g. spot) default to a position other than the origin. Reset the position
+		// here, because node-level parsing will only override position if explicitly specified.
+		lightNode.position.set( 0, 0, 0 );
+
 		lightNode.decay = 2;
 
 		if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
@@ -602,7 +588,6 @@ var GLTFLoader = ( function () {
 		this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
 		this.json = json;
 		this.dracoLoader = dracoLoader;
-		THREE.DRACOLoader.getDecoderModule();
 
 	}
 
@@ -618,21 +603,23 @@ var GLTFLoader = ( function () {
 
 		for ( var attributeName in gltfAttributeMap ) {
 
-			if ( ! ( attributeName in ATTRIBUTES ) ) continue;
+			var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
 
-			threeAttributeMap[ ATTRIBUTES[ attributeName ] ] = gltfAttributeMap[ attributeName ];
+			threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
 
 		}
 
 		for ( attributeName in primitive.attributes ) {
 
-			if ( ATTRIBUTES[ attributeName ] !== undefined && gltfAttributeMap[ attributeName ] !== undefined ) {
+			var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
+
+			if ( gltfAttributeMap[ attributeName ] !== undefined ) {
 
 				var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
 				var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
 
-				attributeTypeMap[ ATTRIBUTES[ attributeName ] ] = componentType;
-				attributeNormalizedMap[ ATTRIBUTES[ attributeName ] ] = accessorDef.normalized === true;
+				attributeTypeMap[ threeAttributeName ] = componentType;
+				attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
 
 			}
 
@@ -1292,6 +1279,7 @@ var GLTFLoader = ( function () {
 	var ATTRIBUTES = {
 		POSITION: 'position',
 		NORMAL: 'normal',
+		TANGENT: 'tangent',
 		TEXCOORD_0: 'uv',
 		TEXCOORD_1: 'uv2',
 		COLOR_0: 'color',
@@ -1307,10 +1295,8 @@ var GLTFLoader = ( function () {
 	};
 
 	var INTERPOLATION = {
-		CUBICSPLINE: InterpolateSmooth, // We use custom interpolation GLTFCubicSplineInterpolation for CUBICSPLINE.
-		                                      // KeyframeTrack.optimize() can't handle glTF Cubic Spline output values layout,
-		                                      // using InterpolateSmooth for KeyframeTrack instantiation to prevent optimization.
-		                                      // See KeyframeTrack.optimize() for the detail.
+		CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
+		                        // keyframe track will be initialized with a default interpolation type, then modified.
 		LINEAR: InterpolateLinear,
 		STEP: InterpolateDiscrete
 	};
@@ -1356,12 +1342,14 @@ var GLTFLoader = ( function () {
 
 	}
 
+	var defaultMaterial;
+
 	/**
 	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
 	 */
 	function createDefaultMaterial() {
 
-		return new MeshStandardMaterial( {
+		defaultMaterial = defaultMaterial || new MeshStandardMaterial( {
 			color: 0xFFFFFF,
 			emissive: 0x000000,
 			metalness: 1,
@@ -1371,6 +1359,8 @@ var GLTFLoader = ( function () {
 			side: FrontSide
 		} );
 
+		return defaultMaterial;
+
 	}
 
 	function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
@@ -1447,34 +1437,21 @@ var GLTFLoader = ( function () {
 
 			if ( hasMorphPosition ) {
 
-				// TODO: Error-prone use of a callback inside a loop.
-				var accessor = target.POSITION !== undefined
+				var pendingAccessor = target.POSITION !== undefined
 					? parser.getDependency( 'accessor', target.POSITION )
-						.then( function ( accessor ) {
-
-							// Cloning not to pollute original accessor below
-							return cloneBufferAttribute( accessor );
-
-						} )
 					: geometry.attributes.position;
 
-				pendingPositionAccessors.push( accessor );
+				pendingPositionAccessors.push( pendingAccessor );
 
 			}
 
 			if ( hasMorphNormal ) {
 
-				// TODO: Error-prone use of a callback inside a loop.
-				var accessor = target.NORMAL !== undefined
+				var pendingAccessor = target.NORMAL !== undefined
 					? parser.getDependency( 'accessor', target.NORMAL )
-						.then( function ( accessor ) {
-
-							return cloneBufferAttribute( accessor );
-
-						} )
 					: geometry.attributes.normal;
 
-				pendingNormalAccessors.push( accessor );
+				pendingNormalAccessors.push( pendingAccessor );
 
 			}
 
@@ -1488,6 +1465,24 @@ var GLTFLoader = ( function () {
 			var morphPositions = accessors[ 0 ];
 			var morphNormals = accessors[ 1 ];
 
+			// Clone morph target accessors before modifying them.
+
+			for ( var i = 0, il = morphPositions.length; i < il; i ++ ) {
+
+				if ( geometry.attributes.position === morphPositions[ i ] ) continue;
+
+				morphPositions[ i ] = cloneBufferAttribute( morphPositions[ i ] );
+
+			}
+
+			for ( var i = 0, il = morphNormals.length; i < il; i ++ ) {
+
+				if ( geometry.attributes.normal === morphNormals[ i ] ) continue;
+
+				morphNormals[ i ] = cloneBufferAttribute( morphNormals[ i ] );
+
+			}
+
 			for ( var i = 0, il = targets.length; i < il; i ++ ) {
 
 				var target = targets[ i ];
@@ -1608,30 +1603,6 @@ var GLTFLoader = ( function () {
 		}
 
 	}
-
-	function isPrimitiveEqual( a, b ) {
-
-		var dracoExtA = a.extensions ? a.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] : undefined;
-		var dracoExtB = b.extensions ? b.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] : undefined;
-
-		if ( dracoExtA && dracoExtB ) {
-
-			if ( dracoExtA.bufferView !== dracoExtB.bufferView ) return false;
-
-			return isObjectEqual( dracoExtA.attributes, dracoExtB.attributes );
-
-		}
-
-		if ( a.indices !== b.indices ) {
-
-			return false;
-
-		}
-
-		return isObjectEqual( a.attributes, b.attributes );
-
-	}
-
 	function isObjectEqual( a, b ) {
 
 		if ( Object.keys( a ).length !== Object.keys( b ).length ) return false;
@@ -1646,59 +1617,40 @@ var GLTFLoader = ( function () {
 
 	}
 
-	function isArrayEqual( a, b ) {
+	function createPrimitiveKey( primitiveDef ) {
 
-		if ( a.length !== b.length ) return false;
+		var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
+		var geometryKey;
 
-		for ( var i = 0, il = a.length; i < il; i ++ ) {
+		if ( dracoExtension ) {
 
-			if ( a[ i ] !== b[ i ] ) return false;
-
-		}
-
-		return true;
-
-	}
+			geometryKey = 'draco:' + dracoExtension.bufferView
+				+ ':' + dracoExtension.indices
+				+ ':' + createAttributesKey( dracoExtension.attributes );
 
-	function getCachedGeometry( cache, newPrimitive ) {
-
-		for ( var i = 0, il = cache.length; i < il; i ++ ) {
-
-			var cached = cache[ i ];
+		} else {
 
-			if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) return cached.promise;
+			geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
 
 		}
 
-		return null;
+		return geometryKey;
 
 	}
 
-	function getCachedCombinedGeometry( cache, geometries ) {
-
-		for ( var i = 0, il = cache.length; i < il; i ++ ) {
-
-			var cached = cache[ i ];
-
-			if ( isArrayEqual( geometries, cached.baseGeometries ) ) return cached.geometry;
-
-		}
-
-		return null;
-
-	}
+	function createAttributesKey( attributes ) {
 
-	function getCachedMultiPassGeometry( cache, geometry, primitives ) {
+		var attributesKey = '';
 
-		for ( var i = 0, il = cache.length; i < il; i ++ ) {
+		var keys = Object.keys( attributes ).sort();
 
-			var cached = cache[ i ];
+		for ( var i = 0, il = keys.length; i < il; i ++ ) {
 
-			if ( geometry === cached.baseGeometry && isArrayEqual( primitives, cached.primitives ) ) return cached.geometry;
+			attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
 
 		}
 
-		return null;
+		return attributesKey;
 
 	}
 
@@ -1727,48 +1679,6 @@ var GLTFLoader = ( function () {
 
 	}
 
-	/**
-	 * Checks if we can build a single Mesh with MultiMaterial from multiple primitives.
-	 * Returns true if all primitives use the same attributes/morphAttributes/mode
-	 * and also have index. Otherwise returns false.
-	 *
-	 * @param {Array<GLTF.Primitive>} primitives
-	 * @return {Boolean}
-	 */
-	function isMultiPassGeometry( primitives ) {
-
-		if ( primitives.length < 2 ) return false;
-
-		var primitive0 = primitives[ 0 ];
-		var targets0 = primitive0.targets || [];
-
-		if ( primitive0.indices === undefined ) return false;
-
-		for ( var i = 1, il = primitives.length; i < il; i ++ ) {
-
-			var primitive = primitives[ i ];
-
-			if ( primitive0.mode !== primitive.mode ) return false;
-			if ( primitive.indices === undefined ) return false;
-			if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) return false;
-			if ( ! isObjectEqual( primitive0.attributes, primitive.attributes ) ) return false;
-
-			var targets = primitive.targets || [];
-
-			if ( targets0.length !== targets.length ) return false;
-
-			for ( var j = 0, jl = targets0.length; j < jl; j ++ ) {
-
-				if ( ! isObjectEqual( targets0[ j ], targets[ j ] ) ) return false;
-
-			}
-
-		}
-
-		return true;
-
-	}
-
 	/* GLTF PARSER */
 
 	function GLTFParser( json, extensions, options ) {
@@ -1781,9 +1691,7 @@ var GLTFLoader = ( function () {
 		this.cache = new GLTFRegistry();
 
 		// BufferGeometry caching
-		this.primitiveCache = [];
-		this.multiplePrimitivesCache = [];
-		this.multiPassGeometryCache = [];
+		this.primitiveCache = {};
 
 		this.textureLoader = new TextureLoader( this.options.manager );
 		this.textureLoader.setCrossOrigin( this.options.crossOrigin );
@@ -1795,7 +1703,9 @@ var GLTFLoader = ( function () {
 
 	GLTFParser.prototype.parse = function ( onLoad, onError ) {
 
+		var parser = this;
 		var json = this.json;
+		var extensions = this.extensions;
 
 		// Clear the loader cache
 		this.cache.removeAll();
@@ -1803,21 +1713,27 @@ var GLTFLoader = ( function () {
 		// Mark the special nodes/meshes in json for efficient parse
 		this.markDefs();
 
-		// Fire the callback on complete
-		this.getMultiDependencies( [
+		Promise.all( [
 
-			'scene',
-			'animation',
-			'camera'
+			this.getDependencies( 'scene' ),
+			this.getDependencies( 'animation' ),
+			this.getDependencies( 'camera' ),
 
 		] ).then( function ( dependencies ) {
 
-			var scenes = dependencies.scenes || [];
-			var scene = scenes[ json.scene || 0 ];
-			var animations = dependencies.animations || [];
-			var cameras = dependencies.cameras || [];
+			var result = {
+				scene: dependencies[ 0 ][ json.scene || 0 ],
+				scenes: dependencies[ 0 ],
+				animations: dependencies[ 1 ],
+				cameras: dependencies[ 2 ],
+				asset: json.asset,
+				parser: parser,
+				userData: {}
+			};
+
+			addUnknownExtensionsToUserData( extensions, result, json );
 
-			onLoad( scene, scenes, cameras, animations, json );
+			onLoad( result );
 
 		} ).catch( onError );
 
@@ -1990,40 +1906,6 @@ var GLTFLoader = ( function () {
 
 	};
 
-	/**
-	 * Requests all multiple dependencies of the specified types asynchronously, with caching.
-	 * @param {Array<string>} types
-	 * @return {Promise<Object<Array<Object>>>}
-	 */
-	GLTFParser.prototype.getMultiDependencies = function ( types ) {
-
-		var results = {};
-		var pending = [];
-
-		for ( var i = 0, il = types.length; i < il; i ++ ) {
-
-			var type = types[ i ];
-			var value = this.getDependencies( type );
-
-			// TODO: Error-prone use of a callback inside a loop.
-			value = value.then( function ( key, value ) {
-
-				results[ key ] = value;
-
-			}.bind( this, type + ( type === 'mesh' ? 'es' : 's' ) ) );
-
-			pending.push( value );
-
-		}
-
-		return Promise.all( pending ).then( function () {
-
-			return results;
-
-		} );
-
-	};
-
 	/**
 	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
 	 * @param {number} bufferIndex
@@ -2326,6 +2208,18 @@ var GLTFLoader = ( function () {
 
 		return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
 
+			switch ( mapName ) {
+
+				case 'aoMap':
+				case 'emissiveMap':
+				case 'metalnessMap':
+				case 'normalMap':
+				case 'roughnessMap':
+					texture.format = RGBFormat;
+					break;
+
+			}
+
 			if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
 
 				var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
@@ -2344,6 +2238,124 @@ var GLTFLoader = ( function () {
 
 	};
 
+	/**
+	 * Assigns final material to a Mesh, Line, or Points instance. The instance
+	 * already has a material (generated from the glTF material options alone)
+	 * but reuse of the same glTF material may require multiple threejs materials
+	 * to accomodate different primitive types, defines, etc. New materials will
+	 * be created if necessary, and reused from a cache.
+	 * @param  {Object3D} mesh Mesh, Line, or Points instance.
+	 */
+	GLTFParser.prototype.assignFinalMaterial = function ( mesh ) {
+
+		var geometry = mesh.geometry;
+		var material = mesh.material;
+		var extensions = this.extensions;
+
+		var useVertexTangents = geometry.attributes.tangent !== undefined;
+		var useVertexColors = geometry.attributes.color !== undefined;
+		var useFlatShading = geometry.attributes.normal === undefined;
+		var useSkinning = mesh.isSkinnedMesh === true;
+		var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
+		var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
+
+		if ( mesh.isPoints ) {
+
+			var cacheKey = 'PointsMaterial:' + material.uuid;
+
+			var pointsMaterial = this.cache.get( cacheKey );
+
+			if ( ! pointsMaterial ) {
+
+				pointsMaterial = new PointsMaterial();
+				Material.prototype.copy.call( pointsMaterial, material );
+				pointsMaterial.color.copy( material.color );
+				pointsMaterial.map = material.map;
+				pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
+
+				this.cache.add( cacheKey, pointsMaterial );
+
+			}
+
+			material = pointsMaterial;
+
+		} else if ( mesh.isLine ) {
+
+			var cacheKey = 'LineBasicMaterial:' + material.uuid;
+
+			var lineMaterial = this.cache.get( cacheKey );
+
+			if ( ! lineMaterial ) {
+
+				lineMaterial = new LineBasicMaterial();
+				Material.prototype.copy.call( lineMaterial, material );
+				lineMaterial.color.copy( material.color );
+				lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet
+
+				this.cache.add( cacheKey, lineMaterial );
+
+			}
+
+			material = lineMaterial;
+
+		}
+
+		// Clone the material if it will be modified
+		if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
+
+			var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
+
+			if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
+			if ( useSkinning ) cacheKey += 'skinning:';
+			if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
+			if ( useVertexColors ) cacheKey += 'vertex-colors:';
+			if ( useFlatShading ) cacheKey += 'flat-shading:';
+			if ( useMorphTargets ) cacheKey += 'morph-targets:';
+			if ( useMorphNormals ) cacheKey += 'morph-normals:';
+
+			var cachedMaterial = this.cache.get( cacheKey );
+
+			if ( ! cachedMaterial ) {
+
+				cachedMaterial = material.isGLTFSpecularGlossinessMaterial
+					? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material )
+					: material.clone();
+
+				if ( useSkinning ) cachedMaterial.skinning = true;
+				if ( useVertexTangents ) cachedMaterial.vertexTangents = true;
+				if ( useVertexColors ) cachedMaterial.vertexColors = VertexColors;
+				if ( useFlatShading ) cachedMaterial.flatShading = true;
+				if ( useMorphTargets ) cachedMaterial.morphTargets = true;
+				if ( useMorphNormals ) cachedMaterial.morphNormals = true;
+
+				this.cache.add( cacheKey, cachedMaterial );
+
+			}
+
+			material = cachedMaterial;
+
+		}
+
+		// workarounds for mesh and geometry
+
+		if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
+
+			console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' );
+			geometry.addAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) );
+
+		}
+
+		if ( material.isGLTFSpecularGlossinessMaterial ) {
+
+			// for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update
+			mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms;
+
+		}
+
+		mesh.material = material;
+
+	};
+
 	/**
 	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
 	 * @param {number} materialIndex
@@ -2491,14 +2503,6 @@ var GLTFLoader = ( function () {
 
 			if ( materialDef.name !== undefined ) material.name = materialDef.name;
 
-			// Normal map textures use OpenGL conventions:
-			// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materialnormaltexture
-			if ( material.normalScale ) {
-
-				material.normalScale.y = - material.normalScale.y;
-
-			}
-
 			// baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
 			if ( material.map ) material.map.encoding = sRGBEncoding;
 			if ( material.emissiveMap ) material.emissiveMap.encoding = sRGBEncoding;
@@ -2539,9 +2543,7 @@ var GLTFLoader = ( function () {
 
 		for ( var gltfAttributeName in attributes ) {
 
-			var threeAttributeName = ATTRIBUTES[ gltfAttributeName ];
-
-			if ( ! threeAttributeName ) continue;
+			var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
 
 			// Skip attributes already provided by e.g. Draco extension.
 			if ( threeAttributeName in geometry.attributes ) continue;
@@ -2578,8 +2580,6 @@ var GLTFLoader = ( function () {
 	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
 	 *
 	 * Creates BufferGeometries from primitives.
-	 * If we can build a single BufferGeometry with .groups from multiple primitives, returns one BufferGeometry.
-	 * Otherwise, returns BufferGeometries without .groups as many as primitives.
 	 *
 	 * @param {Array<GLTF.Primitive>} primitives
 	 * @return {Promise<Array<BufferGeometry>>}
@@ -2590,22 +2590,6 @@ var GLTFLoader = ( function () {
 		var extensions = this.extensions;
 		var cache = this.primitiveCache;
 
-		var isMultiPass = isMultiPassGeometry( primitives );
-		var originalPrimitives;
-
-		if ( isMultiPass ) {
-
-			originalPrimitives = primitives; // save original primitives and use later
-
-			// We build a single BufferGeometry with .groups from multiple primitives
-			// because all primitives share the same attributes/morph/mode and have indices.
-
-			primitives = [ primitives[ 0 ] ];
-
-			// Sets .groups and combined indices to a geometry later in this method.
-
-		}
-
 		function createDracoPrimitive( primitive ) {
 
 			return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
@@ -2623,14 +2607,15 @@ var GLTFLoader = ( function () {
 		for ( var i = 0, il = primitives.length; i < il; i ++ ) {
 
 			var primitive = primitives[ i ];
+			var cacheKey = createPrimitiveKey( primitive );
 
 			// See if we've already created this geometry
-			var cached = getCachedGeometry( cache, primitive );
+			var cached = cache[ cacheKey ];
 
 			if ( cached ) {
 
 				// Use the cached geometry if it exists
-				pending.push( cached );
+				pending.push( cached.promise );
 
 			} else {
 
@@ -2649,7 +2634,7 @@ var GLTFLoader = ( function () {
 				}
 
 				// Cache this geometry
-				cache.push( { primitive: primitive, promise: geometryPromise } );
+				cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };
 
 				pending.push( geometryPromise );
 
@@ -2657,95 +2642,7 @@ var GLTFLoader = ( function () {
 
 		}
 
-		return Promise.all( pending ).then( function ( geometries ) {
-
-			if ( isMultiPass ) {
-
-				var baseGeometry = geometries[ 0 ];
-
-				// See if we've already created this combined geometry
-				var cache = parser.multiPassGeometryCache;
-				var cached = getCachedMultiPassGeometry( cache, baseGeometry, originalPrimitives );
-
-				if ( cached !== null ) return [ cached.geometry ];
-
-				// Cloning geometry because of index override.
-				// Attributes can be reused so cloning by myself here.
-				var geometry = new BufferGeometry();
-
-				geometry.name = baseGeometry.name;
-				geometry.userData = baseGeometry.userData;
-
-				for ( var key in baseGeometry.attributes ) geometry.addAttribute( key, baseGeometry.attributes[ key ] );
-				for ( var key in baseGeometry.morphAttributes ) geometry.morphAttributes[ key ] = baseGeometry.morphAttributes[ key ];
-
-				var pendingIndices = [];
-
-				for ( var i = 0, il = originalPrimitives.length; i < il; i ++ ) {
-
-					pendingIndices.push( parser.getDependency( 'accessor', originalPrimitives[ i ].indices ) );
-
-				}
-
-				return Promise.all( pendingIndices ).then( function ( accessors ) {
-
-					var indices = [];
-					var offset = 0;
-
-					for ( var i = 0, il = originalPrimitives.length; i < il; i ++ ) {
-
-						var accessor = accessors[ i ];
-
-						for ( var j = 0, jl = accessor.count; j < jl; j ++ ) indices.push( accessor.array[ j ] );
-
-						geometry.addGroup( offset, accessor.count, i );
-
-						offset += accessor.count;
-
-					}
-
-					geometry.setIndex( indices );
-
-					cache.push( { geometry: geometry, baseGeometry: baseGeometry, primitives: originalPrimitives } );
-
-					return [ geometry ];
-
-				} );
-
-			} else if ( geometries.length > 1 && THREE.BufferGeometryUtils !== undefined ) {
-
-				// Tries to merge geometries with BufferGeometryUtils if possible
-
-				for ( var i = 1, il = primitives.length; i < il; i ++ ) {
-
-					// can't merge if draw mode is different
-					if ( primitives[ 0 ].mode !== primitives[ i ].mode ) return geometries;
-
-				}
-
-				// See if we've already created this combined geometry
-				var cache = parser.multiplePrimitivesCache;
-				var cached = getCachedCombinedGeometry( cache, geometries );
-
-				if ( cached ) {
-
-					if ( cached.geometry !== null ) return [ cached.geometry ];
-
-				} else {
-
-					var geometry = THREE.BufferGeometryUtils.mergeBufferGeometries( geometries, true );
-
-					cache.push( { geometry: geometry, baseGeometries: geometries } );
-
-					if ( geometry !== null ) return [ geometry ];
-
-				}
-
-			}
-
-			return geometries;
-
-		} );
+		return Promise.all( pending );
 
 	};
 
@@ -2779,8 +2676,6 @@ var GLTFLoader = ( function () {
 
 			return parser.loadGeometries( primitives ).then( function ( geometries ) {
 
-				var isMultiMaterial = geometries.length === 1 && geometries[ 0 ].groups.length > 0;
-
 				var meshes = [];
 
 				for ( var i = 0, il = geometries.length; i < il; i ++ ) {
@@ -2792,7 +2687,7 @@ var GLTFLoader = ( function () {
 
 					var mesh;
 
-					var material = isMultiMaterial ? originalMaterials : originalMaterials[ i ];
+					var material = originalMaterials[ i ];
 
 					if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
 						primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
@@ -2850,118 +2745,9 @@ var GLTFLoader = ( function () {
 
 					assignExtrasToUserData( mesh, meshDef );
 
-					meshes.push( mesh );
-
-					// 2. update Material depending on Mesh and BufferGeometry
-
-					var materials = isMultiMaterial ? mesh.material : [ mesh.material ];
-
-					var useVertexColors = geometry.attributes.color !== undefined;
-					var useFlatShading = geometry.attributes.normal === undefined;
-					var useSkinning = mesh.isSkinnedMesh === true;
-					var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
-					var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
-
-					for ( var j = 0, jl = materials.length; j < jl; j ++ ) {
-
-						var material = materials[ j ];
-
-						if ( mesh.isPoints ) {
-
-							var cacheKey = 'PointsMaterial:' + material.uuid;
-
-							var pointsMaterial = parser.cache.get( cacheKey );
-
-							if ( ! pointsMaterial ) {
-
-								pointsMaterial = new PointsMaterial();
-								Material.prototype.copy.call( pointsMaterial, material );
-								pointsMaterial.color.copy( material.color );
-								pointsMaterial.map = material.map;
-								pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
-
-								parser.cache.add( cacheKey, pointsMaterial );
-
-							}
-
-							material = pointsMaterial;
-
-						} else if ( mesh.isLine ) {
-
-							var cacheKey = 'LineBasicMaterial:' + material.uuid;
-
-							var lineMaterial = parser.cache.get( cacheKey );
-
-							if ( ! lineMaterial ) {
+					parser.assignFinalMaterial( mesh );
 
-								lineMaterial = new LineBasicMaterial();
-								Material.prototype.copy.call( lineMaterial, material );
-								lineMaterial.color.copy( material.color );
-								lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet
-
-								parser.cache.add( cacheKey, lineMaterial );
-
-							}
-
-							material = lineMaterial;
-
-						}
-
-						// Clone the material if it will be modified
-						if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
-
-							var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
-
-							if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
-							if ( useSkinning ) cacheKey += 'skinning:';
-							if ( useVertexColors ) cacheKey += 'vertex-colors:';
-							if ( useFlatShading ) cacheKey += 'flat-shading:';
-							if ( useMorphTargets ) cacheKey += 'morph-targets:';
-							if ( useMorphNormals ) cacheKey += 'morph-normals:';
-
-							var cachedMaterial = parser.cache.get( cacheKey );
-
-							if ( ! cachedMaterial ) {
-
-								cachedMaterial = material.isGLTFSpecularGlossinessMaterial
-									? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material )
-									: material.clone();
-
-								if ( useSkinning ) cachedMaterial.skinning = true;
-								if ( useVertexColors ) cachedMaterial.vertexColors = VertexColors;
-								if ( useFlatShading ) cachedMaterial.flatShading = true;
-								if ( useMorphTargets ) cachedMaterial.morphTargets = true;
-								if ( useMorphNormals ) cachedMaterial.morphNormals = true;
-
-								parser.cache.add( cacheKey, cachedMaterial );
-
-							}
-
-							material = cachedMaterial;
-
-						}
-
-						materials[ j ] = material;
-
-						// workarounds for mesh and geometry
-
-						if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
-
-							console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' );
-							geometry.addAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) );
-
-						}
-
-						if ( material.isGLTFSpecularGlossinessMaterial ) {
-
-							// for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update
-							mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms;
-
-						}
-
-					}
-
-					mesh.material = isMultiMaterial ? materials : materials[ 0 ];
+					meshes.push( mesh );
 
 				}
 
@@ -3146,10 +2932,7 @@ var GLTFLoader = ( function () {
 
 				if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
 
-					// node can be Group here but
-					// PATH_PROPERTIES.weights(morphTargetInfluences) should be
-					// the property of a mesh object under group.
-
+					// Node may be a Group (glTF mesh with several primitives) or a Mesh.
 					node.traverse( function ( object ) {
 
 						if ( object.isMesh === true && object.morphTargetInfluences ) {
@@ -3166,20 +2949,16 @@ var GLTFLoader = ( function () {
 
 				}
 
-				// KeyframeTrack.optimize() will modify given 'times' and 'values'
-				// buffers before creating a truncated copy to keep. Because buffers may
-				// be reused by other tracks, make copies here.
 				for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) {
 
 					var track = new TypedKeyframeTrack(
 						targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ],
-						AnimationUtils.arraySlice( inputAccessor.array, 0 ),
-						AnimationUtils.arraySlice( outputAccessor.array, 0 ),
+						inputAccessor.array,
+						outputAccessor.array,
 						interpolation
 					);
 
-					// Here is the trick to enable custom interpolation.
-					// Overrides .createInterpolant in a factory method which creates custom interpolation.
+					// Override interpolation with custom factory method.
 					if ( sampler.interpolation === 'CUBICSPLINE' ) {
 
 						track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {
@@ -3192,8 +2971,7 @@ var GLTFLoader = ( function () {
 
 						};
 
-						// Workaround, provide an alternate way to know if the interpolant type is cubis spline to track.
-						// track.getInterpolation() doesn't return valid value for custom interpolant.
+						// Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
 						track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
 
 					}

+ 104 - 0
examples/jsm/loaders/MTLLoader.d.ts

@@ -0,0 +1,104 @@
+import {
+  Material,
+  LoadingManager,
+  Mapping,
+  EventDispatcher,
+  BufferGeometry,
+  Side,
+  Texture,
+  Vector2,
+  Wrapping
+} from '../../../src/Three';
+
+export interface MaterialCreatorOptions {
+  /**
+   * side: Which side to apply the material
+   * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
+   */
+  side?: Side;
+  /*
+   * wrap: What type of wrapping to apply for textures
+   * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
+   */
+  wrap?: Wrapping;
+  /*
+   * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+   * Default: false, assumed to be already normalized
+   */
+  normalizeRGB?: boolean;
+  /*
+   * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+   * Default: false
+   */
+  ignoreZeroRGBs?: boolean;
+  /*
+   * invertTrProperty: Use values 1 of Tr field for fully opaque. This option is useful for obj
+   * exported from 3ds MAX, vcglib or meshlab.
+   * Default: false
+   */
+  invertTrProperty?: boolean;
+}
+
+export class MTLLoader extends EventDispatcher {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  materialOptions: MaterialCreatorOptions;
+  path: string;
+  texturePath: string;
+  crossOrigin: boolean;
+
+  load(url: string, onLoad: (materialCreator: MaterialCreator) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  parse(text: string) : MaterialCreator;
+  setPath(path: string) : void;
+  setTexturePath(path: string) : void;
+  setBaseUrl(path: string) : void;
+  setCrossOrigin(value: boolean) : void;
+  setMaterialOptions(value: MaterialCreatorOptions) : void;
+}
+
+export interface MaterialInfo {
+  ks?: number[];
+  kd?: number[];
+  ke?: number[];
+  map_kd?: string;
+  map_ks?: string;
+  map_ke?: string;
+  norm?: string;
+  map_bump?: string;
+  bump?: string;
+  map_d?: string;
+  ns?: number;
+  d?: number;
+  tr?: number;
+}
+
+export interface TexParams {
+  scale: Vector2;
+  offset: Vector2;
+  url: string;
+}
+
+export class MaterialCreator {
+  constructor(baseUrl?: string, options?: MaterialCreatorOptions);
+
+  baseUrl : string;
+  options : MaterialCreatorOptions;
+  materialsInfo : {[key: string]: MaterialInfo};
+  materials : {[key: string]: Material};
+  private materialsArray : Material[];
+  nameLookup : {[key: string]: number};
+  side : Side;
+  wrap : Wrapping;
+
+  setCrossOrigin( value: boolean ) : void;
+  setManager( value: LoadingManager ) : void;
+  setMaterials( materialsInfo: {[key: string]: MaterialInfo} ) : void;
+  convert( materialsInfo: {[key: string]: MaterialInfo} ) : {[key: string]: MaterialInfo};
+  preload() : void;
+  getIndex( materialName: string ) : Material;
+  getAsArray() : Material[];
+  create( materialName: string ) : Material;
+  createMaterial_( materialName: string ) : Material;
+  getTextureParams( value: string, matParams: any ) : TexParams;
+  loadTexture(url: string, mapping?: Mapping, onLoad?: (bufferGeometry: BufferGeometry) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): Texture;
+}

+ 602 - 0
examples/jsm/loaders/MTLLoader.js

@@ -0,0 +1,602 @@
+/**
+ * Loads a Wavefront .mtl file specifying materials
+ *
+ * @author angelxuanchang
+ */
+
+import {
+	BackSide,
+	ClampToEdgeWrapping,
+	Color,
+	DefaultLoadingManager,
+	DoubleSide,
+	FileLoader,
+	FrontSide,
+	Loader,
+	LoaderUtils,
+	MeshPhongMaterial,
+	MirroredRepeatWrapping,
+	RepeatWrapping,
+	TextureLoader,
+	Vector2
+} from "../../../build/three.module.js";
+
+var MTLLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+MTLLoader.prototype = {
+
+	constructor: MTLLoader,
+
+	/**
+	 * Loads and parses a MTL asset from a URL.
+	 *
+	 * @param {String} url - URL to the MTL file.
+	 * @param {Function} [onLoad] - Callback invoked with the loaded object.
+	 * @param {Function} [onProgress] - Callback for download progress.
+	 * @param {Function} [onError] - Callback for download errors.
+	 *
+	 * @see setPath setResourcePath
+	 *
+	 * @note In order for relative texture references to resolve correctly
+	 * you must call setResourcePath() explicitly prior to load.
+	 */
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var path = ( this.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : this.path;
+
+		var loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text, path ) );
+
+		}, onProgress, onError );
+
+	},
+
+	/**
+	 * Set base path for resolving references.
+	 * If set this path will be prepended to each loaded and found reference.
+	 *
+	 * @see setResourcePath
+	 * @param {String} path
+	 * @return {MTLLoader}
+	 *
+	 * @example
+	 *     mtlLoader.setPath( 'assets/obj/' );
+	 *     mtlLoader.load( 'my.mtl', ... );
+	 */
+	setPath: function ( path ) {
+
+		this.path = path;
+		return this;
+
+	},
+
+	/**
+	 * Set base path for additional resources like textures.
+	 *
+	 * @see setPath
+	 * @param {String} path
+	 * @return {MTLLoader}
+	 *
+	 * @example
+	 *     mtlLoader.setPath( 'assets/obj/' );
+	 *     mtlLoader.setResourcePath( 'assets/textures/' );
+	 *     mtlLoader.load( 'my.mtl', ... );
+	 */
+	setResourcePath: function ( path ) {
+
+		this.resourcePath = path;
+		return this;
+
+	},
+
+	setTexturePath: function ( path ) {
+
+		console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' );
+		return this.setResourcePath( path );
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	setMaterialOptions: function ( value ) {
+
+		this.materialOptions = value;
+		return this;
+
+	},
+
+	/**
+	 * Parses a MTL file.
+	 *
+	 * @param {String} text - Content of MTL file
+	 * @return {MTLLoader.MaterialCreator}
+	 *
+	 * @see setPath setResourcePath
+	 *
+	 * @note In order for relative texture references to resolve correctly
+	 * you must call setResourcePath() explicitly prior to parse.
+	 */
+	parse: function ( text, path ) {
+
+		var lines = text.split( '\n' );
+		var info = {};
+		var delimiter_pattern = /\s+/;
+		var materialsInfo = {};
+
+		for ( var i = 0; i < lines.length; i ++ ) {
+
+			var line = lines[ i ];
+			line = line.trim();
+
+			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+				// Blank line or comment ignore
+				continue;
+
+			}
+
+			var pos = line.indexOf( ' ' );
+
+			var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
+			key = key.toLowerCase();
+
+			var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
+			value = value.trim();
+
+			if ( key === 'newmtl' ) {
+
+				// New material
+
+				info = { name: value };
+				materialsInfo[ value ] = info;
+
+			} else {
+
+				if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) {
+
+					var ss = value.split( delimiter_pattern, 3 );
+					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+
+				} else {
+
+					info[ key ] = value;
+
+				}
+
+			}
+
+		}
+
+		var materialCreator = new MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
+		materialCreator.setCrossOrigin( this.crossOrigin );
+		materialCreator.setManager( this.manager );
+		materialCreator.setMaterials( materialsInfo );
+		return materialCreator;
+
+	}
+
+};
+
+/**
+ * Create a new THREE-MTLLoader.MaterialCreator
+ * @param baseUrl - Url relative to which textures are loaded
+ * @param options - Set of options on how to construct the materials
+ *                  side: Which side to apply the material
+ *                        FrontSide (default), BackSide, DoubleSide
+ *                  wrap: What type of wrapping to apply for textures
+ *                        RepeatWrapping (default), ClampToEdgeWrapping, MirroredRepeatWrapping
+ *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+ *                                Default: false, assumed to be already normalized
+ *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+ *                                  Default: false
+ * @constructor
+ */
+
+MTLLoader.MaterialCreator = function ( baseUrl, options ) {
+
+	this.baseUrl = baseUrl || '';
+	this.options = options;
+	this.materialsInfo = {};
+	this.materials = {};
+	this.materialsArray = [];
+	this.nameLookup = {};
+
+	this.side = ( this.options && this.options.side ) ? this.options.side : FrontSide;
+	this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : RepeatWrapping;
+
+};
+
+MTLLoader.MaterialCreator.prototype = {
+
+	constructor: MTLLoader.MaterialCreator,
+
+	crossOrigin: 'anonymous',
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	setManager: function ( value ) {
+
+		this.manager = value;
+
+	},
+
+	setMaterials: function ( materialsInfo ) {
+
+		this.materialsInfo = this.convert( materialsInfo );
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+	},
+
+	convert: function ( materialsInfo ) {
+
+		if ( ! this.options ) return materialsInfo;
+
+		var converted = {};
+
+		for ( var mn in materialsInfo ) {
+
+			// Convert materials info into normalized form based on options
+
+			var mat = materialsInfo[ mn ];
+
+			var covmat = {};
+
+			converted[ mn ] = covmat;
+
+			for ( var prop in mat ) {
+
+				var save = true;
+				var value = mat[ prop ];
+				var lprop = prop.toLowerCase();
+
+				switch ( lprop ) {
+
+					case 'kd':
+					case 'ka':
+					case 'ks':
+
+						// Diffuse color (color under white light) using RGB values
+
+						if ( this.options && this.options.normalizeRGB ) {
+
+							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+
+						}
+
+						if ( this.options && this.options.ignoreZeroRGBs ) {
+
+							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+
+								// ignore
+
+								save = false;
+
+							}
+
+						}
+
+						break;
+
+					default:
+
+						break;
+
+				}
+
+				if ( save ) {
+
+					covmat[ lprop ] = value;
+
+				}
+
+			}
+
+		}
+
+		return converted;
+
+	},
+
+	preload: function () {
+
+		for ( var mn in this.materialsInfo ) {
+
+			this.create( mn );
+
+		}
+
+	},
+
+	getIndex: function ( materialName ) {
+
+		return this.nameLookup[ materialName ];
+
+	},
+
+	getAsArray: function () {
+
+		var index = 0;
+
+		for ( var mn in this.materialsInfo ) {
+
+			this.materialsArray[ index ] = this.create( mn );
+			this.nameLookup[ mn ] = index;
+			index ++;
+
+		}
+
+		return this.materialsArray;
+
+	},
+
+	create: function ( materialName ) {
+
+		if ( this.materials[ materialName ] === undefined ) {
+
+			this.createMaterial_( materialName );
+
+		}
+
+		return this.materials[ materialName ];
+
+	},
+
+	createMaterial_: function ( materialName ) {
+
+		// Create material
+
+		var scope = this;
+		var mat = this.materialsInfo[ materialName ];
+		var params = {
+
+			name: materialName,
+			side: this.side
+
+		};
+
+		function resolveURL( baseUrl, url ) {
+
+			if ( typeof url !== 'string' || url === '' )
+				return '';
+
+			// Absolute URL
+			if ( /^https?:\/\//i.test( url ) ) return url;
+
+			return baseUrl + url;
+
+		}
+
+		function setMapForType( mapType, value ) {
+
+			if ( params[ mapType ] ) return; // Keep the first encountered texture
+
+			var texParams = scope.getTextureParams( value, params );
+			var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+
+			map.repeat.copy( texParams.scale );
+			map.offset.copy( texParams.offset );
+
+			map.wrapS = scope.wrap;
+			map.wrapT = scope.wrap;
+
+			params[ mapType ] = map;
+
+		}
+
+		for ( var prop in mat ) {
+
+			var value = mat[ prop ];
+			var n;
+
+			if ( value === '' ) continue;
+
+			switch ( prop.toLowerCase() ) {
+
+				// Ns is material specular exponent
+
+				case 'kd':
+
+					// Diffuse color (color under white light) using RGB values
+
+					params.color = new Color().fromArray( value );
+
+					break;
+
+				case 'ks':
+
+					// Specular color (color when light is reflected from shiny surface) using RGB values
+					params.specular = new Color().fromArray( value );
+
+					break;
+
+				case 'ke':
+
+					// Emissive using RGB values
+					params.emissive = new Color().fromArray( value );
+
+					break;
+
+				case 'map_kd':
+
+					// Diffuse texture map
+
+					setMapForType( "map", value );
+
+					break;
+
+				case 'map_ks':
+
+					// Specular map
+
+					setMapForType( "specularMap", value );
+
+					break;
+
+				case 'map_ke':
+
+					// Emissive map
+
+					setMapForType( "emissiveMap", value );
+
+					break;
+
+				case 'norm':
+
+					setMapForType( "normalMap", value );
+
+					break;
+
+				case 'map_bump':
+				case 'bump':
+
+					// Bump texture map
+
+					setMapForType( "bumpMap", value );
+
+					break;
+
+				case 'map_d':
+
+					// Alpha map
+
+					setMapForType( "alphaMap", value );
+					params.transparent = true;
+
+					break;
+
+				case 'ns':
+
+					// The specular exponent (defines the focus of the specular highlight)
+					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+
+					params.shininess = parseFloat( value );
+
+					break;
+
+				case 'd':
+					n = parseFloat( value );
+
+					if ( n < 1 ) {
+
+						params.opacity = n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				case 'tr':
+					n = parseFloat( value );
+
+					if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+
+					if ( n > 0 ) {
+
+						params.opacity = 1 - n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				default:
+					break;
+
+			}
+
+		}
+
+		this.materials[ materialName ] = new MeshPhongMaterial( params );
+		return this.materials[ materialName ];
+
+	},
+
+	getTextureParams: function ( value, matParams ) {
+
+		var texParams = {
+
+			scale: new Vector2( 1, 1 ),
+			offset: new Vector2( 0, 0 )
+
+		 };
+
+		var items = value.split( /\s+/ );
+		var pos;
+
+		pos = items.indexOf( '-bm' );
+
+		if ( pos >= 0 ) {
+
+			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+			items.splice( pos, 2 );
+
+		}
+
+		pos = items.indexOf( '-s' );
+
+		if ( pos >= 0 ) {
+
+			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		pos = items.indexOf( '-o' );
+
+		if ( pos >= 0 ) {
+
+			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		texParams.url = items.join( ' ' ).trim();
+		return texParams;
+
+	},
+
+	loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
+
+		var texture;
+		var loader = Loader.Handlers.get( url );
+		var manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager;
+
+		if ( loader === null ) {
+
+			loader = new TextureLoader( manager );
+
+		}
+
+		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+		texture = loader.load( url, onLoad, onProgress, onError );
+
+		if ( mapping !== undefined ) texture.mapping = mapping;
+
+		return texture;
+
+	}
+
+};
+
+export { MTLLoader };

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

@@ -0,0 +1,19 @@
+import {
+  Material,
+  LoadingManager,
+  Group
+} from '../../../src/Three';
+
+export class OBJLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  regexp: any;
+  materials: Material[];
+  path: string;
+
+  load(url: string, onLoad: (group: Group) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  parse(data: string) : Group;
+  setPath(value: string) : void;
+  setMaterials(materials: Material[]) : void;
+  _createParserState() : any;
+}

+ 0 - 2
examples/misc_lookat.html

@@ -79,8 +79,6 @@
 
 				}
 
-				scene.matrixAutoUpdate = false;
-
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );

+ 212 - 0
examples/webgl2_materials_texture2darray.html

@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - 2D texture array</title>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background:#000;
+				padding:0;
+				margin:0;
+				overflow:hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px;
+				width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+			}
+
+			#info a {
+				color: #ffffff;
+			}
+		</style>
+	</head>
+	<script id="vs" type="x-shader/x-vertex">
+	#version 300 es
+
+	uniform vec2 size;
+	out vec2 vUv;
+
+	void main() {
+
+		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+		// Convert position.xy to 1.0-0.0
+
+		vUv.xy = position.xy / size + 0.5;
+		vUv.y = 1.0 - vUv.y; // original data is upside down
+
+	}
+	</script>
+
+	<script id="fs" type="x-shader/x-fragment">
+	#version 300 es
+
+	precision highp float;
+	precision highp int;
+	precision highp sampler2DArray;
+
+	uniform sampler2DArray diffuse;
+	in vec2 vUv;
+	uniform int depth;
+	out vec4 out_FragColor;
+
+	void main() {
+
+		vec4 color = texture( diffuse, vec3( vUv, depth ) );
+
+		// lighten a bit
+		out_FragColor = vec4( color.rrr * 1.5, 1.0 );
+
+	}
+	</script>
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - 2D Texture array<br />
+			Scanned head data by
+			<a href="https://www.codeproject.com/Articles/352270/Getting-started-with-Volume-Rendering" target="_blank" rel="noopener">Divine Augustine</a><br />
+			licensed under
+			<a href="https://www.codeproject.com/info/cpol10.aspx" target="_blank" rel="noopener">CPOL</a>
+		</div>
+
+		<script src="../build/three.js"></script>
+		<!--<script src="js/controls/OrbitControls.js"></script>-->
+
+		<script src="js/libs/jszip.min.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src="js/WebGL.js"></script>
+
+		<script>
+
+			if ( WEBGL.isWebGL2Available() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
+
+			}
+
+			var camera, scene, mesh, renderer, stats;
+
+			var planeWidth = 50;
+			var planeHeight = 50;
+
+			var depthStep = 0.4;
+
+			init();
+			animate();
+
+			function init() {
+
+				var container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
+				camera.position.z = 70;
+
+				scene = new THREE.Scene();
+
+				// width 256, height 256, depth 109, 8-bit, zip archived raw data
+
+				new THREE.FileLoader()
+					.setResponseType( 'arraybuffer' )
+					.load( 'textures/3d/head256x256x109.zip', function ( data ) {
+
+						var zip = new JSZip( data );
+						var array = zip.files[ 'head256x256x109' ].asUint8Array();
+
+						var texture = new THREE.DataTexture2DArray( array, 256, 256, 109 );
+
+						texture.format = THREE.RedFormat;
+						texture.type = THREE.UnsignedByteType;
+
+						texture.needsUpdate = true;
+
+						var material = new THREE.ShaderMaterial( {
+							uniforms: {
+								diffuse: { value: texture },
+								depth: { value: 0 },
+								size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
+							},
+							vertexShader: document.getElementById( 'vs' ).textContent.trim(),
+							fragmentShader: document.getElementById( 'fs' ).textContent.trim()
+						} );
+
+						var geometry = new THREE.PlaneBufferGeometry( planeWidth, planeHeight );
+
+						mesh = new THREE.Mesh( geometry, material );
+
+						scene.add( mesh );
+
+					} );
+
+				// var controls = new THREE.OrbitControls( camera );
+
+				// 2D Texture array is available on WebGL 2.0
+
+				var canvas = document.createElement( 'canvas' );
+				var context = canvas.getContext( 'webgl2' );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas, context: context } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				if ( mesh ) {
+
+					var value = mesh.material.uniforms[ "depth" ].value;
+
+					value += depthStep;
+
+					if ( value > 109.0 || value < 0.0 ) {
+
+						if ( value > 1.0 ) value = 109.0 * 2.0 - value;
+						if ( value < 0.0 ) value = - value;
+
+						depthStep = - depthStep;
+
+					}
+
+					mesh.material.uniforms[ "depth" ].value = value;
+
+				}
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 188 - 190
examples/webgl2_materials_texture3d.html

@@ -1,214 +1,212 @@
 <!DOCTYPE html>
 <html lang="en">
-	<head>
-		<title>three.js webgl - 3D texture</title>
-		<meta charset="UTF-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<style>
-			body {
-				background:#000;
-				padding:0;
-				margin:0;
-				overflow:hidden;
-			}
-
-			#info {
-				position: absolute;
-				top: 0px;
-				width: 100%;
-				color: #ffffff;
-				padding: 5px;
-				font-family:Monospace;
-				font-size:13px;
-				text-align:center;
-			}
-
-			#info a {
-				color: #ffffff;
-			}
-		</style>
-	</head>
-	<script id="vs" type="x-shader/x-vertex">
-	#version 300 es
-
-	uniform float depth;
-	uniform vec2 size;
-	out vec3 vUv;
-
-	void main() {
-
-		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-
-		// Convert position.xy to 1.0-0.0
-
-		vUv.xy = position.xy / size + 0.5;
-		vUv.y = 1.0 - vUv.y; // original data is upside down
-
-		vUv.z = depth;
-
-	}
-	</script>
-
-	<script id="fs" type="x-shader/x-fragment">
-	#version 300 es
-
-	precision highp float;
-	precision highp int;
-	precision highp sampler3D;
-
-	uniform sampler3D diffuse;
-	in vec3 vUv;
-	out vec4 out_FragColor;
-
-	void main() {
-
-		vec4 color = texture( diffuse, vUv );
-
-		// lighten a bit
-		out_FragColor = vec4( color.rrr * 1.5, 1.0 );
-
-	}
-	</script>
-	<body>
-		<div id="info">
-			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - 3D Texture<br />
-			Scanned head data by
-			<a href="https://www.codeproject.com/Articles/352270/Getting-started-with-Volume-Rendering" target="_blank" rel="noopener">Divine Augustine</a><br />
-			licensed under
-			<a href="https://www.codeproject.com/info/cpol10.aspx" target="_blank" rel="noopener">CPOL</a>
-		</div>
-
-		<script src="../build/three.js"></script>
-		<!--<script src="js/controls/OrbitControls.js"></script>-->
-
-		<script src="js/libs/jszip.min.js"></script>
-		<script src="js/libs/stats.min.js"></script>
-		<script src="js/WebGL.js"></script>
-
-		<script>
-
-			if ( WEBGL.isWebGL2Available() === false ) {
-
-				document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
-
-			}
-
-			var camera, scene, mesh, renderer, stats;
-
-			var planeWidth = 50;
-			var planeHeight = 50;
-
-			var depthStep = 1 / ( 109 * 3 );
-
-			init();
-			animate();
-
-			function init() {
-
-				var container = document.createElement( 'div' );
-				document.body.appendChild( container );
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
-				camera.position.z = 70;
+<head>
+	<title>three.js webgl - volume rendering example</title>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+	<style>
+		body {
+			font-family: Monospace;
+			background-color: #000;
+			color: #fff;
+			margin: 0px;
+			overflow: hidden;
+		}
+		#info {
+			color: #fff;
+			position: absolute;
+			top: 10px;
+			width: 100%;
+			text-align: center;
+			z-index: 5;
+			display:block;
+		}
+		.dg {
+			z-index: 10 !important;
+		}
+		#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
+		#inset  {
+			width: 150px;
+			height: 150px;
+			background-color: transparent; /* or transparent; will show through only if renderer alpha: true */
+			border: none; /* or none; */
+			margin: 0;
+			padding: 0px;
+			position: absolute;
+			left: 20px;
+			bottom: 20px;
+			z-index: 100;
+		}
+	</style>
+</head>
+
+<body>
+	<div id="info">
+		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Float volume render test (mip / isosurface)
+	</div>
+	<div id="inset"></div>
+
+	<script src="../build/three.js"></script>
+
+	<script src="js/controls/OrbitControls.js"></script>
+
+	<script src="js/Volume.js"></script>
+	<script src="js/loaders/NRRDLoader.js"></script>
+	<script src="js/shaders/VolumeShader.js"></script>
+
+	<script src="js/WebGL.js"></script>
+	<script src="js/libs/gunzip.min.js"></script>
+	<script src="js/libs/dat.gui.min.js"></script>
+
+	<script>
+
+		if ( WEBGL.isWebGL2Available() === false ) {
+
+			document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
+
+		}
+
+		var container,
+			renderer,
+			scene,
+			camera,
+			controls,
+			material,
+			volconfig,
+			cmtextures;
+
+		init();
+
+		function init() {
+
+			scene = new THREE.Scene();
+
+			// Create renderer
+			var canvas = document.createElement( 'canvas' );
+			var context = canvas.getContext( 'webgl2' );
+			renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			document.body.appendChild( renderer.domElement );
+
+			// Create camera (The volume renderer does not work very well with perspective yet)
+			var h = 512; // frustum height
+			var aspect = window.innerWidth / window.innerHeight;
+			camera = new THREE.OrthographicCamera( - h * aspect / 2, h * aspect / 2, h / 2, - h / 2, 1, 1000 );
+			camera.position.set( 0, 0, 128 );
+			camera.up.set( 0, 0, 1 ); // In our data, z is up
+
+			// Create controls
+			controls = new THREE.OrbitControls( camera, renderer.domElement );
+			controls.addEventListener( 'change', render );
+			controls.target.set( 64, 64, 128 );
+			controls.minZoom = 0.5;
+			controls.maxZoom = 4;
+			controls.update();
+
+			// scene.add( new THREE.AxesHelper( 128 ) );
+
+			// Lighting is baked into the shader a.t.m.
+			// var dirLight = new THREE.DirectionalLight( 0xffffff );
+
+			// The gui for interaction
+			volconfig = { clim1: 0, clim2: 1, renderstyle: 'iso', isothreshold: 0.15, colormap: 'viridis' };
+			var gui = new dat.GUI();
+			gui.add( volconfig, 'clim1', 0, 1, 0.01 ).onChange( updateUniforms );
+			gui.add( volconfig, 'clim2', 0, 1, 0.01 ).onChange( updateUniforms );
+			gui.add( volconfig, 'colormap', { gray: 'gray', viridis: 'viridis' } ).onChange( updateUniforms );
+			gui.add( volconfig, 'renderstyle', { mip: 'mip', iso: 'iso' } ).onChange( updateUniforms );
+			gui.add( volconfig, 'isothreshold', 0, 1, 0.01 ).onChange( updateUniforms );
+
+			// Load the data ...
+			new THREE.NRRDLoader().load( "models/nrrd/stent.nrrd", function ( volume ) {
+
+				// Texture to hold the volume. We have scalars, so we put our data in the red channel.
+				// THREEJS will select R32F (33326) based on the RedFormat and FloatType.
+				// Also see https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
+				// TODO: look the dtype up in the volume metadata
+				var texture = new THREE.DataTexture3D( volume.data, volume.xLength, volume.yLength, volume.zLength );
+				texture.format = THREE.RedFormat;
+				texture.type = THREE.FloatType;
+				texture.minFilter = texture.magFilter = THREE.LinearFilter;
+				texture.unpackAlignment = 1;
+				texture.needsUpdate = true;
+
+				// Colormap textures
+				cmtextures = {
+					viridis: new THREE.TextureLoader().load( 'textures/cm_viridis.png', render ),
+					gray: new THREE.TextureLoader().load( 'textures/cm_gray.png', render )
+				};
+
+				// Material
+				var shader = THREE.VolumeRenderShader1;
+
+				var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+				uniforms[ "u_data" ].value = texture;
+				uniforms[ "u_size" ].value.set( volume.xLength, volume.yLength, volume.zLength );
+				uniforms[ "u_clim" ].value.set( volconfig.clim1, volconfig.clim2 );
+				uniforms[ "u_renderstyle" ].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
+				uniforms[ "u_renderthreshold" ].value = volconfig.isothreshold; // For ISO renderstyle
+				uniforms[ "u_cmdata" ].value = cmtextures[ volconfig.colormap ];
+
+				material = new THREE.ShaderMaterial( {
+					uniforms: uniforms,
+					vertexShader: shader.vertexShader,
+					fragmentShader: shader.fragmentShader,
+					side: THREE.BackSide // The volume shader uses the backface as its "reference point"
+				} );
+
+				// Mesh
+				var geometry = new THREE.BoxBufferGeometry( volume.xLength, volume.yLength, volume.zLength );
+				geometry.translate( volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5 );
+
+				var mesh = new THREE.Mesh( geometry, material );
+				scene.add( mesh );
 
-				scene = new THREE.Scene();
-
-				// width 256, height 256, depth 109, 8-bit, zip archived raw data
-
-				new THREE.FileLoader()
-					.setResponseType( 'arraybuffer' )
-					.load( 'textures/3d/head256x256x109.zip', function ( data ) {
-
-						var zip = new JSZip( data );
-						var array = zip.files[ 'head256x256x109' ].asUint8Array();
-
-						var texture = new THREE.DataTexture3D( array, 256, 256, 109 );
-
-						texture.format = THREE.RedFormat;
-						texture.type = THREE.UnsignedByteType;
-
-						texture.needsUpdate = true;
-
-						var material = new THREE.ShaderMaterial( {
-							uniforms: {
-								diffuse: { value: texture },
-								depth: { value: 0 },
-								size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
-							},
-							vertexShader: document.getElementById( 'vs' ).textContent.trim(),
-							fragmentShader: document.getElementById( 'fs' ).textContent.trim()
-						} );
-
-						var geometry = new THREE.PlaneBufferGeometry( planeWidth, planeHeight );
-
-						mesh = new THREE.Mesh( geometry, material );
-
-						scene.add( mesh );
-
-					} );
-
-				// var controls = new THREE.OrbitControls( camera );
-
-				// 3D Texture is available on WebGL 2.0
-
-				var canvas = document.createElement( 'canvas' );
-				var context = canvas.getContext( 'webgl2' );
-
-				renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas, context: context } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				container.appendChild( renderer.domElement );
-
-				stats = new Stats();
-				container.appendChild( stats.dom );
-
-				window.addEventListener( 'resize', onWindowResize, false );
-
-			}
+				render();
 
-			function onWindowResize() {
+			} );
 
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
+			window.addEventListener( 'resize', onWindowResize, false );
 
-				renderer.setSize( window.innerWidth, window.innerHeight );
+		}
 
-			}
+		function updateUniforms() {
 
-			function animate() {
+			material.uniforms[ "u_clim" ].value.set( volconfig.clim1, volconfig.clim2 );
+			material.uniforms[ "u_renderstyle" ].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
+			material.uniforms[ "u_renderthreshold" ].value = volconfig.isothreshold; // For ISO renderstyle
+			material.uniforms[ "u_cmdata" ].value = cmtextures[ volconfig.colormap ];
 
-				requestAnimationFrame( animate );
+			render();
 
-				if ( mesh ) {
+		}
 
-					var value = mesh.material.uniforms[ "depth" ].value;
+		function onWindowResize() {
 
-					value += depthStep;
+			renderer.setSize( window.innerWidth, window.innerHeight );
 
-					if ( value > 1.0 || value < 0.0 ) {
+			var aspect = window.innerWidth / window.innerHeight;
 
-						if ( value > 1.0 ) value = 2.0 - value;
-						if ( value < 0.0 ) value = - value;
+			var frustumHeight = camera.top - camera.bottom;
 
-						depthStep = - depthStep;
+			camera.left = - frustumHeight * aspect / 2;
+			camera.right = frustumHeight * aspect / 2;
 
-					}
+			camera.updateProjectionMatrix();
 
-					mesh.material.uniforms[ "depth" ].value = value;
+			render();
 
-				}
+		}
 
-				render();
-				stats.update();
+		function render() {
 
-			}
+			renderer.render( scene, camera );
 
-			function render() {
+		}
 
-				renderer.render( scene, camera );
-
-			}
+	</script>
 
-		</script>
-	</body>
+</body>
 </html>

+ 0 - 212
examples/webgl2_materials_texture3d_volume.html

@@ -1,212 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-	<title>three.js webgl - volume rendering example</title>
-	<meta charset="utf-8">
-	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-	<style>
-		body {
-			font-family: Monospace;
-			background-color: #000;
-			color: #fff;
-			margin: 0px;
-			overflow: hidden;
-		}
-		#info {
-			color: #fff;
-			position: absolute;
-			top: 10px;
-			width: 100%;
-			text-align: center;
-			z-index: 5;
-			display:block;
-		}
-		.dg {
-			z-index: 10 !important;
-		}
-		#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
-		#inset  {
-			width: 150px;
-			height: 150px;
-			background-color: transparent; /* or transparent; will show through only if renderer alpha: true */
-			border: none; /* or none; */
-			margin: 0;
-			padding: 0px;
-			position: absolute;
-			left: 20px;
-			bottom: 20px;
-			z-index: 100;
-		}
-	</style>
-</head>
-
-<body>
-	<div id="info">
-		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Float volume render test (mip / isosurface)
-	</div>
-	<div id="inset"></div>
-
-	<script src="../build/three.js"></script>
-
-	<script src="js/controls/OrbitControls.js"></script>
-
-	<script src="js/Volume.js"></script>
-	<script src="js/loaders/NRRDLoader.js"></script>
-	<script src="js/shaders/VolumeShader.js"></script>
-
-	<script src="js/WebGL.js"></script>
-	<script src="js/libs/gunzip.min.js"></script>
-	<script src="js/libs/dat.gui.min.js"></script>
-
-	<script>
-
-		if ( WEBGL.isWebGL2Available() === false ) {
-
-			document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
-
-		}
-
-		var container,
-			renderer,
-			scene,
-			camera,
-			controls,
-			material,
-			volconfig,
-			cmtextures;
-
-		init();
-
-		function init() {
-
-			scene = new THREE.Scene();
-
-			// Create renderer
-			var canvas = document.createElement( 'canvas' );
-			var context = canvas.getContext( 'webgl2' );
-			renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
-			renderer.setPixelRatio( window.devicePixelRatio );
-			renderer.setSize( window.innerWidth, window.innerHeight );
-			document.body.appendChild( renderer.domElement );
-
-			// Create camera (The volume renderer does not work very well with perspective yet)
-			var h = 512; // frustum height
-			var aspect = window.innerWidth / window.innerHeight;
-			camera = new THREE.OrthographicCamera( - h * aspect / 2, h * aspect / 2, h / 2, - h / 2, 1, 1000 );
-			camera.position.set( 0, 0, 128 );
-			camera.up.set( 0, 0, 1 ); // In our data, z is up
-
-			// Create controls
-			controls = new THREE.OrbitControls( camera, renderer.domElement );
-			controls.addEventListener( 'change', render );
-			controls.target.set( 64, 64, 128 );
-			controls.minZoom = 0.5;
-			controls.maxZoom = 4;
-			controls.update();
-
-			// scene.add( new THREE.AxesHelper( 128 ) );
-
-			// Lighting is baked into the shader a.t.m.
-			// var dirLight = new THREE.DirectionalLight( 0xffffff );
-
-			// The gui for interaction
-			volconfig = { clim1: 0, clim2: 1, renderstyle: 'iso', isothreshold: 0.15, colormap: 'viridis' };
-			var gui = new dat.GUI();
-			gui.add( volconfig, 'clim1', 0, 1, 0.01 ).onChange( updateUniforms );
-			gui.add( volconfig, 'clim2', 0, 1, 0.01 ).onChange( updateUniforms );
-			gui.add( volconfig, 'colormap', { gray: 'gray', viridis: 'viridis' } ).onChange( updateUniforms );
-			gui.add( volconfig, 'renderstyle', { mip: 'mip', iso: 'iso' } ).onChange( updateUniforms );
-			gui.add( volconfig, 'isothreshold', 0, 1, 0.01 ).onChange( updateUniforms );
-
-			// Load the data ...
-			new THREE.NRRDLoader().load( "models/nrrd/stent.nrrd", function ( volume ) {
-
-				// Texture to hold the volume. We have scalars, so we put our data in the red channel.
-				// THREEJS will select R32F (33326) based on the RedFormat and FloatType.
-				// Also see https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
-				// TODO: look the dtype up in the volume metadata
-				var texture = new THREE.DataTexture3D( volume.data, volume.xLength, volume.yLength, volume.zLength );
-				texture.format = THREE.RedFormat;
-				texture.type = THREE.FloatType;
-				texture.minFilter = texture.magFilter = THREE.LinearFilter;
-				texture.unpackAlignment = 1;
-				texture.needsUpdate = true;
-
-				// Colormap textures
-				cmtextures = {
-					viridis: new THREE.TextureLoader().load( 'textures/cm_viridis.png', render ),
-					gray: new THREE.TextureLoader().load( 'textures/cm_gray.png', render )
-				};
-
-				// Material
-				var shader = THREE.VolumeRenderShader1;
-
-				var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
-
-				uniforms[ "u_data" ].value = texture;
-				uniforms[ "u_size" ].value.set( volume.xLength, volume.yLength, volume.zLength );
-				uniforms[ "u_clim" ].value.set( volconfig.clim1, volconfig.clim2 );
-				uniforms[ "u_renderstyle" ].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
-				uniforms[ "u_renderthreshold" ].value = volconfig.isothreshold; // For ISO renderstyle
-				uniforms[ "u_cmdata" ].value = cmtextures[ volconfig.colormap ];
-
-				material = new THREE.ShaderMaterial( {
-					uniforms: uniforms,
-					vertexShader: shader.vertexShader,
-					fragmentShader: shader.fragmentShader,
-					side: THREE.BackSide // The volume shader uses the backface as its "reference point"
-				} );
-
-				// Mesh
-				var geometry = new THREE.BoxBufferGeometry( volume.xLength, volume.yLength, volume.zLength );
-				geometry.translate( volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5 );
-
-				var mesh = new THREE.Mesh( geometry, material );
-				scene.add( mesh );
-
-				render();
-
-			} );
-
-			window.addEventListener( 'resize', onWindowResize, false );
-
-		}
-
-		function updateUniforms() {
-
-			material.uniforms[ "u_clim" ].value.set( volconfig.clim1, volconfig.clim2 );
-			material.uniforms[ "u_renderstyle" ].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
-			material.uniforms[ "u_renderthreshold" ].value = volconfig.isothreshold; // For ISO renderstyle
-			material.uniforms[ "u_cmdata" ].value = cmtextures[ volconfig.colormap ];
-
-			render();
-
-		}
-
-		function onWindowResize() {
-
-			renderer.setSize( window.innerWidth, window.innerHeight );
-
-			var aspect = window.innerWidth / window.innerHeight;
-
-			var frustumHeight = camera.top - camera.bottom;
-
-			camera.left = - frustumHeight * aspect / 2;
-			camera.right = frustumHeight * aspect / 2;
-
-			camera.updateProjectionMatrix();
-
-			render();
-
-		}
-
-		function render() {
-
-			renderer.render( scene, camera );
-
-		}
-
-	</script>
-
-</body>
-</html>

+ 24 - 46
examples/webgl2_sandbox.html

@@ -6,11 +6,14 @@
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<style>
 			body {
-				background:#000;
-				padding:0;
-				margin:0;
+				background: #000;
+				padding: 0;
+				margin: 0;
 				font-weight: bold;
-				overflow:hidden;
+			}
+
+			canvas {
+				display: block;
 			}
 
 			#info {
@@ -18,10 +21,9 @@
 				top: 0px; width: 100%;
 				color: #ffffff;
 				padding: 5px;
-				font-family:Monospace;
-				font-size:13px;
-				text-align:center;
-				z-index:1000;
+				font-family: Monospace;
+				font-size: 13px;
+				text-align: center;
 			}
 
 			a {
@@ -46,40 +48,38 @@
 			import { Scene } from '../src/scenes/Scene.js';
 			import { WebGLRenderer } from '../src/renderers/WebGLRenderer.js';
 
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
 			//
 
 			var camera, scene, renderer;
-
-			var mouseX = 0, mouseY = 0;
-
-			var windowHalfX = window.innerWidth / 2;
-			var windowHalfY = window.innerHeight / 2;
+			var controls;
 
 			init();
-			animate();
+			render();
 
 			function init() {
 
-				camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 20000 );
-				camera.position.z = 3200;
+				camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.z = 3;
 
 				scene = new Scene();
 				scene.background = new Color( 0, 0, 0.5 );
-				scene.fog = new Fog( 0x000000, 1, 20000 );
+				scene.fog = new Fog( 0x000000, 0.1, 3 );
 
 				var light = new PointLight( 0xffffff );
 				scene.add( light );
 
-				var geometry = new SphereBufferGeometry( 50, 32, 16 );
+				var geometry = new SphereBufferGeometry( 0.05, 32, 16 );
 				var material = new MeshNormalMaterial();
 
 				for ( var i = 0; i < 5000; i ++ ) {
 
 					var mesh = new Mesh( geometry, material );
 
-					mesh.position.x = Math.random() * 10000 - 5000;
-					mesh.position.y = Math.random() * 10000 - 5000;
-					mesh.position.z = Math.random() * 10000 - 5000;
+					mesh.position.x = Math.random() * 10 - 5;
+					mesh.position.y = Math.random() * 10 - 5;
+					mesh.position.z = Math.random() * 10 - 5;
 
 					mesh.rotation.y = Math.random() * 2 * Math.PI;
 
@@ -97,19 +97,17 @@
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				document.body.appendChild( renderer.domElement );
 
-				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+				window.addEventListener( 'resize', onWindowResize, false );
 
 				//
 
-				window.addEventListener( 'resize', onWindowResize, false );
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render );
 
 			}
 
 			function onWindowResize() {
 
-				windowHalfX = window.innerWidth / 2;
-				windowHalfY = window.innerHeight / 2;
-
 				camera.aspect = window.innerWidth / window.innerHeight;
 				camera.updateProjectionMatrix();
 
@@ -117,30 +115,10 @@
 
 			}
 
-			function onDocumentMouseMove( event ) {
-
-				mouseX = ( event.clientX - windowHalfX ) * 10;
-				mouseY = ( event.clientY - windowHalfY ) * 10;
-
-			}
-
 			//
 
-			function animate() {
-
-				requestAnimationFrame( animate );
-
-				render();
-
-			}
-
 			function render() {
 
-				camera.position.x += ( mouseX - camera.position.x ) * .05;
-				camera.position.y += ( - mouseY - camera.position.y ) * .05;
-
-				camera.lookAt( scene.position );
-
 				renderer.render( scene, camera );
 
 			}

+ 6 - 6
examples/webgl_gpgpu_birds.html

@@ -365,9 +365,9 @@
 
 						vertices.array[ v ++ ] = arguments[ i ];
 
-		}
+					}
 
-	}
+				}
 
 				var wingsSpan = 20;
 
@@ -442,8 +442,7 @@
 				location.reload();
 				return false;
 
-}
-
+			}
 
 			var options = '';
 			for ( i = 1; i < 7; i ++ ) {
@@ -451,7 +450,8 @@
 				var j = Math.pow( 2, i );
 				options += '<a href="#" onclick="return change(' + j + ')">' + ( j * j ) + '</a> ';
 
-}
+			}
+
 			document.getElementById( 'options' ).innerHTML = options;
 
 			var last = performance.now();
@@ -569,7 +569,7 @@
 
 				    console.error( error );
 
-	}
+				}
 
 			}
 

+ 2 - 4
examples/webgl_lights_rectarealight.html

@@ -77,19 +77,17 @@
 				renderer.gammaOutput = true;
 				document.body.appendChild( renderer.domElement );
 
-				var gl = renderer.context;
-
 				// Check for float-RT support
 				// TODO (abelnation): figure out fall-back for float textures
 
-				if ( ! gl.getExtension( 'OES_texture_float' ) ) {
+				if ( ! renderer.extensions.get( 'OES_texture_float' ) ) {
 
 					alert( 'OES_texture_float not supported' );
 					throw 'missing webgl extension';
 
 				}
 
-				if ( ! gl.getExtension( 'OES_texture_float_linear' ) ) {
+				if ( ! renderer.extensions.get( 'OES_texture_float_linear' ) ) {
 
 					alert( 'OES_texture_float_linear not supported' );
 					throw 'missing webgl extension';

+ 3 - 7
examples/webgl_loader_svg.html

@@ -81,7 +81,7 @@
 
 				window.addEventListener( 'resize', onWindowResize, false );
 
-				document.body.addEventListener( 'dblclick', function ( event ) {
+				document.body.addEventListener( 'dblclick', function () {
 
 					var group = scene.children[ 1 ];
 					group.traverse( function ( child ) {
@@ -102,14 +102,10 @@
 
 			function createGUI() {
 
-				if ( gui ) {
-					gui.destroy();
-				}
-
-				guiData = {
+				if ( gui ) gui.destroy();
 
+				var guiData = {
 					currentURL: currentURL
-
 				};
 
 				gui = new dat.GUI( { width: 350 } );

+ 492 - 0
examples/webgl_materials_envmaps_parallax.html

@@ -0,0 +1,492 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - box projected cubemap environment mapping</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #888888;
+				font-family:Monospace;
+				font-size:13px;
+
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px;
+				width: 500px;
+				left: calc(50% - 250px);
+				text-align: center;
+			}
+
+			a {
+				color: #00f;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - box projected cubemap environment mapping. <br> created by <a href="https://codercat.tk" target="_blank" rel="noopener">codercat</a>
+		</div>
+		<script src="../build/three.js"></script>
+		<script src="js/objects/Reflector.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+		<script src="js/lights/RectAreaLightUniformsLib.js"></script>
+
+		<script>
+
+			// shader injection for box projected cube environment mapping
+			var worldposReplace = `
+			#define BOX_PROJECTED_ENV_MAP
+
+			#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )
+
+				vec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );
+
+				#ifdef BOX_PROJECTED_ENV_MAP
+
+					vWorldPosition = worldPosition.xyz;
+
+				#endif
+
+			#endif
+			`;
+
+			var envmapParsReplace = `
+			#define BOX_PROJECTED_ENV_MAP
+
+			#if defined( USE_ENVMAP ) || defined( PHYSICAL )
+
+				uniform float reflectivity;
+				uniform float envMapIntensity;
+
+			#endif
+
+			#ifdef USE_ENVMAP
+
+				#ifdef BOX_PROJECTED_ENV_MAP
+
+					uniform vec3 cubeMapSize;
+					uniform vec3 cubeMapPos;
+					varying vec3 vWorldPosition;
+
+					vec3 parallaxCorrectNormal( vec3 v, vec3 cubeSize, vec3 cubePos ) {
+
+						vec3 nDir = normalize( v );
+						vec3 rbmax = (   .5 * ( cubeSize - cubePos ) - vWorldPosition ) / nDir;
+						vec3 rbmin = ( - .5 * ( cubeSize - cubePos ) - vWorldPosition ) / nDir;
+
+						vec3 rbminmax;
+						rbminmax.x = ( nDir.x > 0. ) ? rbmax.x : rbmin.x;
+						rbminmax.y = ( nDir.y > 0. ) ? rbmax.y : rbmin.y;
+						rbminmax.z = ( nDir.z > 0. ) ? rbmax.z : rbmin.z;
+
+						float correction = min( min( rbminmax.x, rbminmax.y ), rbminmax.z );
+						vec3 boxIntersection = vWorldPosition + nDir * correction;
+
+						return boxIntersection - cubePos;
+					}
+
+				#endif
+
+				#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )
+
+					varying vec3 vWorldPosition;
+
+				#endif
+
+				#ifdef ENVMAP_TYPE_CUBE
+
+					uniform samplerCube envMap;
+
+				#else
+
+					uniform sampler2D envMap;
+
+				#endif
+
+				uniform float flipEnvMap;
+				uniform int maxMipLevel;
+
+				#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )
+
+					uniform float refractionRatio;
+
+				#else
+
+					varying vec3 vReflect;
+
+				#endif
+
+			#endif
+			`;
+
+			var envmapPhysicalParsReplace = `
+			#if defined( USE_ENVMAP ) && defined( PHYSICAL )
+
+				vec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {
+					vec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );
+					#ifdef ENVMAP_TYPE_CUBE
+
+						vec3 worldNormalFinal = worldNormal;
+
+					#ifdef BOX_PROJECTED_ENV_MAP
+
+						worldNormalFinal = parallaxCorrectNormal( worldNormal, cubeMapSize, cubeMapPos );
+
+					#endif
+
+					vec3 queryVec = vec3( flipEnvMap * worldNormalFinal.x, worldNormalFinal.yz );
+
+					#ifdef TEXTURE_LOD_EXT
+
+						vec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );
+
+					#else
+
+						vec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );
+
+					#endif
+
+						envMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;
+
+					#elif defined( ENVMAP_TYPE_CUBE_UV )
+
+						vec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );
+						vec4 envMapColor = textureCubeUV( envMap, queryVec, 1.0 );
+
+					#else
+
+						vec4 envMapColor = vec4( 0.0 );
+
+					#endif
+
+					return PI * envMapColor.rgb * envMapIntensity;
+				}
+
+				float getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {
+					float maxMIPLevelScalar = float( maxMIPLevel );
+					float desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );
+					return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
+				}
+
+				vec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {
+
+					#ifdef ENVMAP_MODE_REFLECTION
+
+						vec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );
+
+					#else
+
+						vec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );
+
+					#endif
+
+					reflectVec = inverseTransformDirection( reflectVec, viewMatrix );
+					float specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );
+
+					#ifdef ENVMAP_TYPE_CUBE
+
+						vec3 reflectVecFinal = reflectVec;
+
+					#ifdef BOX_PROJECTED_ENV_MAP
+
+					 reflectVecFinal = parallaxCorrectNormal( reflectVec, cubeMapSize, cubeMapPos );
+
+					#endif
+
+					vec3 queryReflectVec = vec3( flipEnvMap * reflectVecFinal.x, reflectVecFinal.yz );
+
+					#ifdef TEXTURE_LOD_EXT
+
+						vec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );
+
+					#else
+
+						vec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );
+
+					#endif
+
+					envMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;
+
+					#elif defined( ENVMAP_TYPE_CUBE_UV )
+
+						vec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );
+						vec4 envMapColor = textureCubeUV( envMap, queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent ));
+
+					#elif defined( ENVMAP_TYPE_EQUIREC )
+
+						vec2 sampleUV;
+						sampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;
+						sampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;
+
+						#ifdef TEXTURE_LOD_EXT
+
+							vec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );
+
+						#else
+
+							vec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );
+
+						#endif
+
+						envMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;
+					#elif defined( ENVMAP_TYPE_SPHERE )
+
+						vec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );
+
+						#ifdef TEXTURE_LOD_EXT
+
+							vec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );
+
+						#else
+
+							vec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );
+
+						#endif
+
+						envMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;
+
+					#endif
+
+					return envMapColor.rgb * envMapIntensity;
+				}
+			#endif
+			`;
+
+			// scene size
+			var WIDTH = window.innerWidth;
+			var HEIGHT = window.innerHeight;
+
+			// camera
+			var VIEW_ANGLE = 45;
+			var ASPECT = WIDTH / HEIGHT;
+			var NEAR = 1;
+			var FAR = 800;
+
+			var camera, cubeCamera, scene, renderer;
+
+			var cameraControls;
+
+			var groundPlane, wallMat;
+
+			init();
+
+			function init() {
+
+				var container = document.getElementById( 'container' );
+
+				// renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( WIDTH, HEIGHT );
+				container.appendChild( renderer.domElement );
+
+				// gui controls
+				var gui = new dat.GUI();
+				var params = {
+					'box projected': true
+				};
+				var bpcemGui = gui.add( params, 'box projected' );
+
+				bpcemGui.onChange( function ( value ) {
+
+					if ( value ) {
+
+						groundPlane.material = boxProjectedMat;
+
+					} else {
+
+						groundPlane.material = defaultMat;
+
+					}
+
+					render();
+
+				} );
+
+				// scene
+				scene = new THREE.Scene();
+
+				// camera
+				camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR );
+				camera.position.set( 280, 106, - 5 );
+
+				cameraControls = new THREE.OrbitControls( camera, renderer.domElement );
+				cameraControls.target.set( 0, - 10, 0 );
+				cameraControls.maxDistance = 400;
+				cameraControls.minDistance = 10;
+				cameraControls.addEventListener( 'change', render );
+				cameraControls.update();
+
+				// cube camera for environment map
+				cubeCamera = new THREE.CubeCamera( 1, 1000, 512 );
+				cubeCamera.renderTarget.texture.generateMipmaps = true;
+				cubeCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter;
+				cubeCamera.renderTarget.texture.mapping = THREE.CubeReflectionMapping;
+
+				cubeCamera.position.set( 0, - 100, 0 );
+				scene.add( cubeCamera );
+
+				// ground floor ( with box projected environment mapping )
+				var loader = new THREE.TextureLoader();
+				var rMap = loader.load( 'textures/lava/lavatile.jpg' );
+				rMap.wrapS = THREE.RepeatWrapping;
+				rMap.wrapT = THREE.RepeatWrapping;
+				rMap.repeat.set( 2, 1 );
+
+				var defaultMat = new THREE.MeshPhysicalMaterial( {
+					roughness: 1,
+					envMap: cubeCamera.renderTarget.texture,
+					roughnessMap: rMap
+				} );
+
+				var boxProjectedMat = new THREE.MeshPhysicalMaterial( {
+					color: new THREE.Color( '#ffffff' ),
+					roughness: 1,
+					envMap: cubeCamera.renderTarget.texture,
+					roughnessMap: rMap
+				} );
+
+				boxProjectedMat.onBeforeCompile = function ( shader ) {
+
+					shader.uniforms.cubeMapSize = { value: new THREE.Vector3( 200, 100, 100 ) };
+					shader.uniforms.cubeMapPos = { value: new THREE.Vector3( 0, - 50, 0 ) };
+					shader.uniforms.flipEnvMap.value = true;
+
+					//replace shader chunks with box projection chunks
+					shader.vertexShader = 'varying vec3 vWorldPosition;\n' + shader.vertexShader;
+
+					shader.vertexShader = shader.vertexShader.replace(
+						'#include <worldpos_vertex>',
+						worldposReplace
+					);
+
+					shader.fragmentShader = shader.fragmentShader.replace(
+						'#include <envmap_pars_fragment>',
+						envmapParsReplace
+					);
+
+					shader.fragmentShader = shader.fragmentShader.replace(
+						'#include <envmap_physical_pars_fragment>',
+						envmapPhysicalParsReplace
+					);
+
+				};
+
+				groundPlane = new THREE.Mesh( new THREE.PlaneBufferGeometry( 200, 100, 100 ), boxProjectedMat );
+				groundPlane.rotateX( - Math.PI / 2 );
+				groundPlane.position.set( 0, - 49, 0 );
+				scene.add( groundPlane );
+
+				// walls
+				var diffuseTex = loader.load( 'textures/brick_diffuse.jpg', function () {
+
+					updateCubeMap();
+
+				} );
+				var bumpTex = loader.load( 'textures/brick_bump.jpg', function () {
+
+					updateCubeMap();
+
+				} );
+
+				wallMat = new THREE.MeshPhysicalMaterial( {
+					map: diffuseTex,
+					bumpMap: bumpTex,
+					bumpScale: 0.3,
+				} );
+
+				var planeGeo = new THREE.PlaneBufferGeometry( 100, 100 );
+
+				var planeBack1 = new THREE.Mesh( planeGeo, wallMat );
+				planeBack1.position.z = - 50;
+				planeBack1.position.x = - 50;
+				scene.add( planeBack1 );
+
+				var planeBack2 = new THREE.Mesh( planeGeo, wallMat );
+				planeBack2.position.z = - 50;
+				planeBack2.position.x = 50;
+				scene.add( planeBack2 );
+
+				var planeFront1 = new THREE.Mesh( planeGeo, wallMat );
+				planeFront1.position.z = 50;
+				planeFront1.position.x = - 50;
+				planeFront1.rotateY( Math.PI );
+				scene.add( planeFront1 );
+
+				var planeFront2 = new THREE.Mesh( planeGeo, wallMat );
+				planeFront2.position.z = 50;
+				planeFront2.position.x = 50;
+				planeFront2.rotateY( Math.PI );
+				scene.add( planeFront2 );
+
+				var planeRight = new THREE.Mesh( planeGeo, wallMat );
+				planeRight.position.x = 100;
+				planeRight.rotateY( - Math.PI / 2 );
+				scene.add( planeRight );
+
+				var planeLeft = new THREE.Mesh( planeGeo, wallMat );
+				planeLeft.position.x = - 100;
+				planeLeft.rotateY( Math.PI / 2 );
+				scene.add( planeLeft );
+
+				//lights
+				var width = 50;
+				var height = 50;
+				var intensity = 10;
+
+				var blueRectLight = new THREE.RectAreaLight( 0xf3aaaa, intensity, width, height );
+				blueRectLight.position.set( 99, 5, 0 );
+				blueRectLight.lookAt( 0, 5, 0 );
+				scene.add( blueRectLight );
+
+				var blueRectLightHelper = new THREE.RectAreaLightHelper( blueRectLight, 0xffffff );
+				blueRectLight.add( blueRectLightHelper );
+
+				var redRectLight = new THREE.RectAreaLight( 0x9aaeff, intensity, width, height );
+				redRectLight.position.set( - 99, 5, 0 );
+				redRectLight.lookAt( 0, 5, 0 );
+				scene.add( redRectLight );
+
+				var redRectLightHelper = new THREE.RectAreaLightHelper( redRectLight, 0xffffff );
+				redRectLight.add( redRectLightHelper );
+
+				render();
+
+			}
+
+			function updateCubeMap() {
+
+				//disable specular highlights on walls in the environment map
+				wallMat.roughness = 1;
+
+				groundPlane.visible = false;
+
+				cubeCamera.position.copy( groundPlane.position );
+
+				cubeCamera.update( renderer, scene );
+
+				wallMat.roughness = 0.6;
+
+				groundPlane.visible = true;
+
+				render();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 0 - 2
examples/webgl_materials_shaders_fresnel.html

@@ -119,8 +119,6 @@
 
 				}
 
-				scene.matrixAutoUpdate = false;
-
 				//
 
 				renderer = new THREE.WebGLRenderer();

+ 0 - 2
examples/webgl_postprocessing_dof.html

@@ -157,8 +157,6 @@
 
 						}
 
-				scene.matrixAutoUpdate = false;
-
 				initPostprocessing();
 
 				renderer.autoClear = false;

+ 13 - 13
examples/webgl_shaders_ocean.html

@@ -110,16 +110,14 @@
 				// Skybox
 
 				var sky = new THREE.Sky();
-				sky.scale.setScalar( 10000 );
-				scene.add( sky );
 
 				var uniforms = sky.material.uniforms;
 
-				uniforms[ "turbidity" ].value = 10;
-				uniforms[ "rayleigh" ].value = 2;
-				uniforms[ "luminance" ].value = 1;
-				uniforms[ "mieCoefficient" ].value = 0.005;
-				uniforms[ "mieDirectionalG" ].value = 0.8;
+				uniforms[ 'turbidity' ].value = 10;
+				uniforms[ 'rayleigh' ].value = 2;
+				uniforms[ 'luminance' ].value = 1;
+				uniforms[ 'mieCoefficient' ].value = 0.005;
+				uniforms[ 'mieDirectionalG' ].value = 0.8;
 
 				var parameters = {
 					distance: 400,
@@ -127,10 +125,12 @@
 					azimuth: 0.205
 				};
 
-				var cubeCamera = new THREE.CubeCamera( 1, 20000, 256 );
+				var cubeCamera = new THREE.CubeCamera( 0.1, 1, 512 );
 				cubeCamera.renderTarget.texture.generateMipmaps = true;
 				cubeCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter;
 
+				scene.background = cubeCamera.renderTarget;
+
 				function updateSun() {
 
 					var theta = Math.PI * ( parameters.inclination - 0.5 );
@@ -140,10 +140,10 @@
 					light.position.y = parameters.distance * Math.sin( phi ) * Math.sin( theta );
 					light.position.z = parameters.distance * Math.sin( phi ) * Math.cos( theta );
 
-					sky.material.uniforms[ "sunPosition" ].value = light.position.copy( light.position );
-					water.material.uniforms[ "sunDirection" ].value.copy( light.position ).normalize();
+					sky.material.uniforms[ 'sunPosition' ].value = light.position.copy( light.position );
+					water.material.uniforms[ 'sunDirection' ].value.copy( light.position ).normalize();
 
-					cubeCamera.update( renderer, scene );
+					cubeCamera.update( renderer, sky );
 
 				}
 
@@ -187,7 +187,7 @@
 				controls.target.set( 0, 10, 0 );
 				controls.minDistance = 40.0;
 				controls.maxDistance = 200.0;
-				camera.lookAt( controls.target );
+				controls.update();
 
 				//
 
@@ -242,7 +242,7 @@
 				sphere.rotation.x = time * 0.5;
 				sphere.rotation.z = time * 0.51;
 
-				water.material.uniforms[ "time" ].value += 1.0 / 60.0;
+				water.material.uniforms[ 'time' ].value += 1.0 / 60.0;
 
 				renderer.render( scene, camera );
 

File diff suppressed because it is too large
+ 4 - 694
package-lock.json


+ 1 - 3
package.json

@@ -40,8 +40,7 @@
     "start": "npm run dev",
     "lint": "eslint src",
     "test": "npm run build-test && qunit test/unit/three.source.unit.js",
-    "travis": "npm run lint && npm test",
-    "editor": "electron ./editor/main.js"
+    "travis": "npm run lint && npm test"
   },
   "keywords": [
     "three",
@@ -57,7 +56,6 @@
   "homepage": "https://threejs.org/",
   "devDependencies": {
     "concurrently": "^4.1.0",
-    "electron": "^4.0.6",
     "eslint": "^5.15.0",
     "eslint-config-mdcs": "^4.2.3",
     "eslint-plugin-html": "^5.0.3",

+ 1 - 0
rollup.config.js

@@ -117,6 +117,7 @@ function glconstants() {
 		ACTIVE_ATTRIBUTES: 35721,
 		IMPLEMENTATION_COLOR_READ_TYPE: 35738,
 		IMPLEMENTATION_COLOR_READ_FORMAT: 35739,
+		TEXTURE_2D_ARRAY: 35866,
 		DEPTH_COMPONENT32F: 36012,
 		COLOR_ATTACHMENT0: 36064,
 		FRAMEBUFFER_COMPLETE: 36053,

+ 2 - 0
src/Three.d.ts

@@ -172,3 +172,5 @@ export * from './renderers/webgl/WebGLUniforms';
 export * from './renderers/webvr/WebVRManager';
 export * from './constants';
 export * from './Three.Legacy';
+
+export as namespace THREE;

+ 1 - 0
src/Three.js

@@ -24,6 +24,7 @@ export { Points } from './objects/Points.js';
 export { Group } from './objects/Group.js';
 export { VideoTexture } from './textures/VideoTexture.js';
 export { DataTexture } from './textures/DataTexture.js';
+export { DataTexture2DArray } from './textures/DataTexture2DArray.js';
 export { DataTexture3D } from './textures/DataTexture3D.js';
 export { CompressedTexture } from './textures/CompressedTexture.js';
 export { CubeTexture } from './textures/CubeTexture.js';

+ 3 - 0
src/audio/Audio.d.ts

@@ -12,6 +12,7 @@ export class Audio extends Object3D {
   gain: GainNode;
   autoplay: boolean;
   buffer: null | Audio;
+  detune: number;
   loop: boolean;
   startTime: number;
   offset: number;
@@ -32,6 +33,8 @@ export class Audio extends Object3D {
   stop(): this;
   connect(): this;
   disconnect(): this;
+  setDetune(value: number): this;
+  getDetune(): number;
   getFilters(): any[];
   setFilter(value: any[]): this;
   getFilter(): any;

+ 6 - 5
src/audio/PositionalAudio.d.ts

@@ -17,12 +17,13 @@ export class PositionalAudio extends Audio {
 
   panner: PannerNode;
 
-  setRefDistance(value: number): void;
+  setRefDistance(value: number): this;
   getRefDistance(): number;
-  setRolloffFactor(value: number): void;
+  setRolloffFactor(value: number): this;
   getRolloffFactor(): number;
-  setDistanceModel(value: number): void;
-  getDistanceModel(): number;
-  setMaxDistance(value: number): void;
+  setDistanceModel(value: string): this;
+  getDistanceModel(): string;
+  setMaxDistance(value: number): this;
   getMaxDistance(): number;
+  setDirectionalCone(coneInnerAngle: number, coneOuterAngle: number, coneOuterGain: number): this;
 }

+ 80 - 17
src/core/BufferGeometry.js

@@ -601,35 +601,59 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
 	computeBoundingBox: function () {
 
-		if ( this.boundingBox === null ) {
+		var box = new Box3();
 
-			this.boundingBox = new Box3();
+		return function computeBoundingBox() {
 
-		}
+			if ( this.boundingBox === null ) {
 
-		var position = this.attributes.position;
+				this.boundingBox = new Box3();
 
-		if ( position !== undefined ) {
+			}
+
+			var position = this.attributes.position;
+			var morphAttributesPosition = this.morphAttributes.position;
 
-			this.boundingBox.setFromBufferAttribute( position );
+			if ( position !== undefined ) {
 
-		} else {
+				this.boundingBox.setFromBufferAttribute( position );
 
-			this.boundingBox.makeEmpty();
+				// process morph attributes if present
 
-		}
+				if ( morphAttributesPosition ) {
 
-		if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
+					for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
 
-			console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
+						var morphAttribute = morphAttributesPosition[ i ];
+						box.setFromBufferAttribute( morphAttribute );
 
-		}
+						this.boundingBox.expandByPoint( box.min );
+						this.boundingBox.expandByPoint( box.max );
 
-	},
+					}
+
+				}
+
+			} else {
+
+				this.boundingBox.makeEmpty();
+
+			}
+
+			if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
+
+				console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
+
+			}
+
+		};
+
+	}(),
 
 	computeBoundingSphere: function () {
 
 		var box = new Box3();
+		var boxMorphTargets = new Box3();
 		var vector = new Vector3();
 
 		return function computeBoundingSphere() {
@@ -641,28 +665,67 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 			}
 
 			var position = this.attributes.position;
+			var morphAttributesPosition = this.morphAttributes.position;
 
 			if ( position ) {
 
+				// first, find the center of the bounding sphere
+
 				var center = this.boundingSphere.center;
 
 				box.setFromBufferAttribute( position );
+
+				// process morph attributes if present
+
+				if ( morphAttributesPosition ) {
+
+					for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+						var morphAttribute = morphAttributesPosition[ i ];
+						boxMorphTargets.setFromBufferAttribute( morphAttribute );
+
+						box.expandByPoint( boxMorphTargets.min );
+						box.expandByPoint( boxMorphTargets.max );
+
+					}
+
+				}
+
 				box.getCenter( center );
 
-				// hoping to find a boundingSphere with a radius smaller than the
+				// second, try to find a boundingSphere with a radius smaller than the
 				// boundingSphere of the boundingBox: sqrt(3) smaller in the best case
 
 				var maxRadiusSq = 0;
 
 				for ( var i = 0, il = position.count; i < il; i ++ ) {
 
-					vector.x = position.getX( i );
-					vector.y = position.getY( i );
-					vector.z = position.getZ( i );
+					vector.fromBufferAttribute( position, i );
+
 					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
 
 				}
 
+				// process morph attributes if present
+
+				if ( morphAttributesPosition ) {
+
+					for ( var i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+						var morphAttribute = morphAttributesPosition[ i ];
+
+						for ( var j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
+
+							vector.fromBufferAttribute( morphAttribute, i );
+
+							maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+
+						}
+
+					}
+
+				}
+
 				this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
 
 				if ( isNaN( this.boundingSphere.radius ) ) {

+ 2 - 1
src/core/InstancedBufferAttribute.d.ts

@@ -37,8 +37,9 @@ export namespace GeometryUtils {
  */
 export class InstancedBufferAttribute extends BufferAttribute {
   constructor(
-    data: ArrayLike<number>,
+    array: ArrayLike<number>,
     itemSize: number,
+    normalized?: boolean,
     meshPerAttribute?: number
   );
 

+ 2 - 1
src/loaders/AnimationLoader.d.ts

@@ -1,4 +1,5 @@
 import { LoadingManager } from './LoadingManager';
+import { AnimationClip } from './../animation/AnimationClip';
 
 export class AnimationLoader {
   constructor(manager?: LoadingManager);
@@ -11,6 +12,6 @@ export class AnimationLoader {
     onProgress?: (request: ProgressEvent) => void,
     onError?: (event: ErrorEvent) => void
   ): any;
-  parse(json: any, onLoad: (response: string | ArrayBuffer) => void): void;
+  parse(json: any): AnimationClip[];
   setPath(path: string): AnimationLoader;
 }

+ 2 - 2
src/loaders/AnimationLoader.js

@@ -28,7 +28,7 @@ Object.assign( AnimationLoader.prototype, {
 
 	},
 
-	parse: function ( json, onLoad ) {
+	parse: function ( json ) {
 
 		var animations = [];
 
@@ -40,7 +40,7 @@ Object.assign( AnimationLoader.prototype, {
 
 		}
 
-		onLoad( animations );
+		return animations;
 
 	},
 

+ 10 - 1
src/loaders/ImageBitmapLoader.js

@@ -71,7 +71,16 @@ ImageBitmapLoader.prototype = {
 
 		} ).then( function ( blob ) {
 
-			return createImageBitmap( blob, scope.options );
+			if ( scope.options === undefined ) {
+
+				// Workaround for FireFox. It causes an error if you pass options.
+				return createImageBitmap( blob );
+
+			} else {
+
+				return createImageBitmap( blob, scope.options );
+
+			}
 
 		} ).then( function ( imageBitmap ) {
 

+ 9 - 0
src/materials/Material.d.ts

@@ -1,5 +1,7 @@
 import { Plane } from './../math/Plane';
+import { Shader } from './../renderers/shaders/ShaderLib'
 import { EventDispatcher } from './../core/EventDispatcher';
+import { WebGLRenderer } from './../renderers/WebGLRenderer';
 import {
   BlendingDstFactor,
   BlendingEquation,
@@ -266,6 +268,13 @@ export class Material extends EventDispatcher {
    */
   dispose(): void;
 
+  /**
+   * An optional callback that is executed immediately before the shader program is compiled. This function is called with the shader source code as a parameter. Useful for the modification of built-in materials. 
+   * @param shader Source code of the shader
+   * @param renderer WebGLRenderer Context that is initializing the material
+   */
+  onBeforeCompile ( shader : Shader, renderer : WebGLRenderer ) : void;
+
   /**
    * Sets the properties based on the values.
    * @param values A container with parameters.

+ 4 - 4
src/math/Matrix3.d.ts

@@ -7,9 +7,9 @@ import { Vector3 } from './Vector3';
  */
 export interface Matrix {
   /**
-   * Float32Array with matrix values.
+   * Array with matrix values.
    */
-  elements: Float32Array;
+  elements: number[];
 
   /**
    * identity():T;
@@ -54,9 +54,9 @@ export class Matrix3 implements Matrix {
   constructor();
 
   /**
-   * Float32Array with matrix values.
+   * Array with matrix values.
    */
-  elements: Float32Array;
+  elements: number[];
 
   set(
     n11: number,

+ 2 - 2
src/math/Matrix4.d.ts

@@ -25,9 +25,9 @@ export class Matrix4 implements Matrix {
   constructor();
 
   /**
-   * Float32Array with matrix values.
+   * Array with matrix values.
    */
-  elements: Float32Array;
+  elements: number[];
 
   /**
    * Sets all fields of this matrix.

+ 3 - 0
src/math/Quaternion.d.ts

@@ -57,6 +57,9 @@ export class Quaternion {
    */
   setFromRotationMatrix(m: Matrix4): Quaternion;
   setFromUnitVectors(vFrom: Vector3, vTo: Vector3): Quaternion;
+  angleTo(q: Quaternion): number;
+  rotateTowards(q: Quaternion, step: number): Quaternion;
+
   /**
    * Inverts this quaternion.
    */

+ 1 - 12
src/objects/Line.js

@@ -244,20 +244,9 @@ Line.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
 	}() ),
 
-	copy: function ( source ) {
-
-		Object3D.prototype.copy.call( this, source );
-
-		this.geometry.copy( source.geometry );
-		this.material.copy( source.material );
-
-		return this;
-
-	},
-
 	clone: function () {
 
-		return new this.constructor().copy( this );
+		return new this.constructor( this.geometry, this.material ).copy( this );
 
 	}
 

+ 12 - 2
src/renderers/WebGLRenderer.js

@@ -1276,7 +1276,11 @@ function WebGLRenderer( parameters ) {
 					var geometry = objects.update( object );
 					var material = object.material;
 
-					currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+					if ( material.visible ) {
+
+						currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+					}
 
 				}
 
@@ -2462,6 +2466,12 @@ function WebGLRenderer( parameters ) {
 
 	}() );
 
+	this.setTexture2DArray = function ( texture, slot ) {
+
+		textures.setTexture2DArray( texture, slot );
+
+	};
+
 	this.setTexture3D = function ( texture, slot ) {
 
 		textures.setTexture3D( texture, slot );
@@ -2601,7 +2611,7 @@ function WebGLRenderer( parameters ) {
 		if ( isCube ) {
 
 			var textureProperties = properties.get( renderTarget.texture );
-			_gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace || 0, textureProperties.__webglTexture, activeMipMapLevel || 0 );
+			_gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + ( activeCubeFace || 0 ), textureProperties.__webglTexture, activeMipMapLevel || 0 );
 
 		}
 

+ 11 - 2
src/renderers/webgl/WebGLBackground.js

@@ -27,8 +27,17 @@ function WebGLBackground( renderer, state, objects, premultipliedAlpha ) {
 
 		var background = scene.background;
 
-		var session = renderer.vr.getSession();
-		if ( session && session.environmentBlendMode === 'additive' ) background = null;
+		// Ignore background in AR
+		// TODO: Reconsider this.
+
+		var vr = renderer.vr;
+		var session = vr.getSession && vr.getSession();
+
+		if ( session && session.environmentBlendMode === 'additive' ) {
+
+			background = null;
+
+		}
 
 		if ( background === null ) {
 

+ 29 - 4
src/renderers/webgl/WebGLTextures.js

@@ -65,7 +65,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 				console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
 
-				return useOffscreenCanvas ? canvas.transferToImageBitmap() : canvas;
+				return canvas;
 
 			} else {
 
@@ -299,6 +299,22 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 	}
 
+	function setTexture2DArray( texture, slot ) {
+
+		var textureProperties = properties.get( texture );
+
+		if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+			uploadTexture( textureProperties, texture, slot );
+			return;
+
+		}
+
+		state.activeTexture( _gl.TEXTURE0 + slot );
+		state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture );
+
+	}
+
 	function setTexture3D( texture, slot ) {
 
 		var textureProperties = properties.get( texture );
@@ -451,7 +467,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, utils.convert( texture.wrapS ) );
 			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, utils.convert( texture.wrapT ) );
 
-			if ( textureType === _gl.TEXTURE_3D ) {
+			if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) {
 
 				_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, utils.convert( texture.wrapR ) );
 
@@ -465,7 +481,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
 			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
 
-			if ( textureType === _gl.TEXTURE_3D ) {
+			if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) {
 
 				_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE );
 
@@ -524,7 +540,10 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 	function uploadTexture( textureProperties, texture, slot ) {
 
-		var textureType = ( texture.isDataTexture3D ) ? _gl.TEXTURE_3D : _gl.TEXTURE_2D;
+		var textureType = _gl.TEXTURE_2D;
+
+		if ( texture.isDataTexture2DArray ) textureType = _gl.TEXTURE_2D_ARRAY;
+		if ( texture.isDataTexture3D ) textureType = _gl.TEXTURE_3D;
 
 		initTexture( textureProperties, texture );
 
@@ -656,6 +675,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			textureProperties.__maxMipLevel = mipmaps.length - 1;
 
+		} else if ( texture.isDataTexture2DArray ) {
+
+			state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+			textureProperties.__maxMipLevel = 0;
+
 		} else if ( texture.isDataTexture3D ) {
 
 			state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
@@ -1054,6 +1078,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 	}
 
 	this.setTexture2D = setTexture2D;
+	this.setTexture2DArray = setTexture2DArray;
 	this.setTexture3D = setTexture3D;
 	this.setTextureCube = setTextureCube;
 	this.setTextureCubeDynamic = setTextureCubeDynamic;

+ 19 - 0
src/renderers/webgl/WebGLUniforms.js

@@ -51,9 +51,11 @@
 
 import { CubeTexture } from '../../textures/CubeTexture.js';
 import { Texture } from '../../textures/Texture.js';
+import { DataTexture2DArray } from '../../textures/DataTexture2DArray.js';
 import { DataTexture3D } from '../../textures/DataTexture3D.js';
 
 var emptyTexture = new Texture();
+var emptyTexture2dArray = new DataTexture2DArray();
 var emptyTexture3d = new DataTexture3D();
 var emptyCubeTexture = new CubeTexture();
 
@@ -390,6 +392,22 @@ function setValueT1( gl, v, renderer ) {
 
 }
 
+function setValueT2DArray1( gl, v, renderer ) {
+
+	var cache = this.cache;
+	var unit = renderer.allocTextureUnit();
+
+	if ( cache[ 0 ] !== unit ) {
+
+		gl.uniform1i( this.addr, unit );
+		cache[ 0 ] = unit;
+
+	}
+
+	renderer.setTexture2DArray( v || emptyTexture2dArray, unit );
+
+}
+
 function setValueT3D1( gl, v, renderer ) {
 
 	var cache = this.cache;
@@ -478,6 +496,7 @@ function getSingularSetter( type ) {
 		case 0x8b5e: case 0x8d66: return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES
 		case 0x8b5f: return setValueT3D1; // SAMPLER_3D
 		case 0x8b60: return setValueT6; // SAMPLER_CUBE
+		case 0x8DC1: return setValueT2DArray1; // SAMPLER_2D_ARRAY
 
 		case 0x1404: case 0x8b56: return setValue1i; // INT, BOOL
 		case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2

+ 1 - 1
src/renderers/webvr/WebXRManager.js

@@ -3,12 +3,12 @@
  */
 
 import { Group } from '../../objects/Group.js';
+import { Matrix4 } from '../../math/Matrix4.js';
 import { Vector4 } from '../../math/Vector4.js';
 import { ArrayCamera } from '../../cameras/ArrayCamera.js';
 import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
 import { WebGLAnimation } from '../webgl/WebGLAnimation.js';
 import { setProjectionFromUnion } from './WebVRUtils.js';
-import { Matrix4 } from '../../math/Matrix4';
 
 function WebXRManager( renderer ) {
 

+ 12 - 0
src/textures/DataTexture2DArray.d.ts

@@ -0,0 +1,12 @@
+import { Texture } from './Texture';
+import { NearestFilter } from '../constants';
+import { TypedArray } from '../polyfills';
+
+export class DataTexture2DArray extends Texture {
+  constructor(
+    data: ArrayBuffer | TypedArray,
+    width: number,
+    height: number,
+    depth: number
+  );
+}

+ 28 - 0
src/textures/DataTexture2DArray.js

@@ -0,0 +1,28 @@
+/**
+ * @author Takahiro https://github.com/takahirox
+ */
+
+import { Texture } from './Texture.js';
+import { ClampToEdgeWrapping, NearestFilter } from '../constants.js';
+
+function DataTexture2DArray( data, width, height, depth ) {
+
+	Texture.call( this, null );
+
+	this.image = { data: data, width: width, height: height, depth: depth };
+
+	this.magFilter = NearestFilter;
+	this.minFilter = NearestFilter;
+
+	this.wrapR = ClampToEdgeWrapping;
+
+	this.generateMipmaps = false;
+	this.flipY = false;
+
+}
+
+DataTexture2DArray.prototype = Object.create( Texture.prototype );
+DataTexture2DArray.prototype.constructor = DataTexture2DArray;
+DataTexture2DArray.prototype.isDataTexture2DArray = true;
+
+export { DataTexture2DArray };

+ 9 - 6
utils/modularize.js

@@ -11,9 +11,11 @@ var files = [
 	{ path: 'controls/OrbitControls.js', ignoreList: [] },
 	{ path: 'controls/MapControls.js', ignoreList: [] },
 	{ path: 'controls/TrackballControls.js', ignoreList: [] },
-	// { file: 'controls/TransformControls.js', ignoreList: [] },
-	{ path: 'loaders/GLTFLoader.js', ignoreList: [ 'NoSide', 'Matrix2', 'DDSLoader', 'DRACOLoader', 'BufferGeometryUtils' ] },
-	{ path: 'loaders/OBJLoader.js', ignoreList: [] }
+	// { path: 'controls/TransformControls.js', ignoreList: [] },
+	{ path: 'exporters/GLTFExporter.js', ignoreList: [] },
+	{ path: 'loaders/GLTFLoader.js', ignoreList: [ 'NoSide', 'Matrix2', 'DDSLoader' ] },
+	{ path: 'loaders/OBJLoader.js', ignoreList: [] },
+	{ path: 'loaders/MTLLoader.js', ignoreList: [] }
 ];
 
 for ( var i = 0; i < files.length; i ++ ) {
@@ -44,9 +46,10 @@ function convert( path, ignoreList ) {
 
 	} );
 
-	contents = contents.replace( /THREE\.([a-zA-Z0-9]+)\./g, function ( match, p1 ) {
+	contents = contents.replace( /(\'?)THREE\.([a-zA-Z0-9]+)(\.{0,1})/g, function ( match, p1, p2, p3 ) {
 
-		if ( p1 === className ) return `${p1}.`;
+		if ( p1 === '\'' ) return match; // Inside a string
+		if ( p2 === className ) return `${p2}${p3}`;
 
 		return match;
 
@@ -83,7 +86,7 @@ function convert( path, ignoreList ) {
 
 	var keys = Object.keys( dependencies ).sort().map( value => '\n\t' + value ).toString();
 	var imports = `import {${keys}\n} from "../../../build/three.module.js";`;
-	var exports = `export { ${className} }`;
+	var exports = `export { ${className} };\n`;
 
 	var output = contents.replace( '_IMPORTS_', imports ) + '\n' + exports;
 

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