Browse Source

Merge branch 'dev' of https://github.com/mrdoob/three.js into dev

linbingquan 6 years ago
parent
commit
248ca17428
58 changed files with 1925 additions and 917 deletions
  1. 245 48
      build/three.js
  2. 160 396
      build/three.min.js
  3. 244 48
      build/three.module.js
  4. 6 6
      docs/api/en/core/Object3D.html
  5. 1 1
      docs/api/en/loaders/CompressedTextureLoader.html
  6. 2 2
      docs/api/en/loaders/CubeTextureLoader.html
  7. 1 1
      docs/api/en/loaders/DataTextureLoader.html
  8. 4 4
      docs/api/en/loaders/FileLoader.html
  9. 1 1
      docs/api/en/loaders/FontLoader.html
  10. 1 1
      docs/api/en/loaders/ImageLoader.html
  11. 1 1
      docs/api/en/loaders/MaterialLoader.html
  12. 49 0
      docs/api/en/renderers/WebGLMultisampleRenderTarget.html
  13. 5 0
      docs/api/en/scenes/Scene.html
  14. 46 0
      docs/api/zh/renderers/WebGLMultisampleRenderTarget.html
  15. 5 0
      docs/api/zh/scenes/Scene.html
  16. 2 0
      docs/list.js
  17. 2 0
      docs/manual/en/introduction/How-to-use-WebGL2.html
  18. 11 9
      docs/manual/en/introduction/WebGL-compatibility-check.html
  19. 2 0
      docs/manual/zh/introduction/How-to-use-WebGL2.html
  20. 7 1
      docs/scenes/js/geometry.js
  21. 1 0
      examples/files.js
  22. 1 1
      examples/js/Ocean.js
  23. 0 4
      examples/js/ShaderSkin.js
  24. 51 49
      examples/js/controls/TransformControls.js
  25. 10 1
      examples/js/exporters/ColladaExporter.js
  26. 2 1
      examples/js/exporters/GLTFExporter.js
  27. 105 100
      examples/js/interactive/SelectionBox.js
  28. 60 50
      examples/js/interactive/SelectionHelper.js
  29. 0 1
      examples/js/objects/Reflector.js
  30. 6 2
      examples/js/offscreen/scene.js
  31. 2 0
      examples/js/renderers/SVGRenderer.js
  32. 5 0
      examples/js/shaders/OceanShaders.js
  33. 259 0
      examples/js/utils/BufferGeometryUtils.js
  34. 27 28
      examples/misc_boxselection.html
  35. 189 0
      examples/webgl2_multisampled_renderbuffers.html
  36. 0 1
      examples/webgl_loader_ctm_materials.html
  37. 8 46
      examples/webgl_materials_bumpmap_skin.html
  38. 1 0
      examples/webgl_panorama_dualfisheye.html
  39. 6 46
      examples/webgl_physics_volume.html
  40. 1 0
      examples/webgl_postprocessing_masking.html
  41. 1 0
      package.json
  42. 85 0
      rollup-examples.config.js
  43. 7 3
      rollup.config.js
  44. 1 0
      src/Three.js
  45. 6 2
      src/cameras/CubeCamera.js
  46. 47 10
      src/core/BufferGeometry.js
  47. 2 2
      src/helpers/ArrowHelper.js
  48. 2 1
      src/helpers/RectAreaLightHelper.js
  49. 4 0
      src/loaders/MaterialLoader.js
  50. 7 0
      src/materials/ShaderMaterial.js
  51. 35 0
      src/renderers/WebGLMultisampleRenderTarget.js
  52. 11 1
      src/renderers/WebGLRenderer.js
  53. 5 1
      src/renderers/webgl/WebGLCapabilities.js
  54. 12 0
      src/renderers/webgl/WebGLRenderLists.js
  55. 12 0
      src/renderers/webgl/WebGLRenderStates.js
  56. 2 4
      src/renderers/webgl/WebGLShadowMap.js
  57. 151 44
      src/renderers/webgl/WebGLTextures.js
  58. 6 0
      src/scenes/Scene.js

+ 245 - 48
build/three.js

@@ -4742,6 +4742,37 @@
 
 	} );
 
+	/**
+	 * @author Mugen87 / https://github.com/Mugen87
+	 * @author Matt DesLauriers / @mattdesl
+	 */
+
+	function WebGLMultisampleRenderTarget( width, height, options ) {
+
+		WebGLRenderTarget.call( this, width, height, options );
+
+		this.samples = 4;
+
+	}
+
+	WebGLMultisampleRenderTarget.prototype = Object.assign( Object.create( WebGLRenderTarget.prototype ), {
+
+		constructor: WebGLMultisampleRenderTarget,
+
+		isWebGLMultisampleRenderTarget: true,
+
+		copy: function ( source ) {
+
+			WebGLRenderTarget.prototype.copy.call( this, source );
+
+			this.samples = source.samples;
+
+			return this;
+
+		}
+
+	} );
+
 	/**
 	 * @author alteredq / http://alteredqualia.com
 	 */
@@ -13120,6 +13151,13 @@
 					value: value.toArray()
 				};
 
+			} else if ( value && value.isMatrix3 ) {
+
+				data.uniforms[ name ] = {
+					type: 'm3',
+					value: value.toArray()
+				};
+
 			} else if ( value && value.isMatrix4 ) {
 
 				data.uniforms[ name ] = {
@@ -14940,6 +14978,8 @@
 		var floatFragmentTextures = isWebGL2 || !! extensions.get( 'OES_texture_float' );
 		var floatVertexTextures = vertexTextures && floatFragmentTextures;
 
+		var maxSamples = isWebGL2 ? gl.getParameter( 36183 ) : 0;
+
 		return {
 
 			isWebGL2: isWebGL2,
@@ -14962,7 +15002,9 @@
 
 			vertexTextures: vertexTextures,
 			floatFragmentTextures: floatFragmentTextures,
-			floatVertexTextures: floatVertexTextures
+			floatVertexTextures: floatVertexTextures,
+
+			maxSamples: maxSamples
 
 		};
 
@@ -17892,6 +17934,16 @@
 
 		var lists = {};
 
+		function onSceneDispose( event ) {
+
+			var scene = event.target;
+
+			scene.removeEventListener( 'dispose', onSceneDispose );
+
+			delete lists[ scene.id ];
+
+		}
+
 		function get( scene, camera ) {
 
 			var cameras = lists[ scene.id ];
@@ -17902,6 +17954,8 @@
 				lists[ scene.id ] = {};
 				lists[ scene.id ][ camera.id ] = list;
 
+				scene.addEventListener( 'dispose', onSceneDispose );
+
 			} else {
 
 				list = cameras[ camera.id ];
@@ -18327,6 +18381,16 @@
 
 		var renderStates = {};
 
+		function onSceneDispose( event ) {
+
+			var scene = event.target;
+
+			scene.removeEventListener( 'dispose', onSceneDispose );
+
+			delete renderStates[ scene.id ];
+
+		}
+
 		function get( scene, camera ) {
 
 			var renderState;
@@ -18337,6 +18401,8 @@
 				renderStates[ scene.id ] = {};
 				renderStates[ scene.id ][ camera.id ] = renderState;
 
+				scene.addEventListener( 'dispose', onSceneDispose );
+
 			} else {
 
 				if ( renderStates[ scene.id ][ camera.id ] === undefined ) {
@@ -19931,60 +19997,55 @@
 
 		//
 
-		function clampToMaxSize( image, maxSize ) {
+		function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
 
-			if ( image.width > maxSize || image.height > maxSize ) {
+			var scale = 1;
 
-				if ( 'data' in image ) {
+			// handle case if texture exceeds max size
 
-					console.warn( 'THREE.WebGLRenderer: image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
-					return;
+			if ( image.width > maxSize || image.height > maxSize ) {
 
-				}
+				scale = maxSize / Math.max( image.width, image.height );
 
-				// Warning: Scaling through the canvas will only work with images that use
-				// premultiplied alpha.
+			}
 
-				var scale = maxSize / Math.max( image.width, image.height );
+			// only perform resize if necessary
 
-				var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
-				canvas.width = Math.floor( image.width * scale );
-				canvas.height = Math.floor( image.height * scale );
+			if ( scale < 1 || needsPowerOfTwo === true ) {
 
-				var context = canvas.getContext( '2d' );
-				context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height );
+				// only perform resize for certain image types
 
-				console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height );
+				if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) {
 
-				return canvas;
+					if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
 
-			}
+					// cube textures can't reuse the same canvas
 
-			return image;
+					var canvas = needsNewCanvas ? document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ) : _canvas;
 
-		}
+					var floor = needsPowerOfTwo ? _Math.floorPowerOfTwo : Math.floor;
 
-		function isPowerOfTwo( image ) {
+					canvas.width = floor( scale * image.width );
+					canvas.height = floor( scale * image.height );
 
-			return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height );
+					var context = canvas.getContext( '2d' );
+					context.drawImage( image, 0, 0, canvas.width, canvas.height );
 
-		}
+					console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + canvas.width + 'x' + canvas.height + ').' );
 
-		function makePowerOfTwo( image ) {
+					return canvas;
 
-			if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) {
+				} else {
 
-				if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+					if ( 'data' in image ) {
 
-				_canvas.width = _Math.floorPowerOfTwo( image.width );
-				_canvas.height = _Math.floorPowerOfTwo( image.height );
+						console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
 
-				var context = _canvas.getContext( '2d' );
-				context.drawImage( image, 0, 0, _canvas.width, _canvas.height );
+					}
 
-				console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + _canvas.width + 'x' + _canvas.height );
+					return image;
 
-				return _canvas;
+				}
 
 			}
 
@@ -19992,6 +20053,12 @@
 
 		}
 
+		function isPowerOfTwo( image ) {
+
+			return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height );
+
+		}
+
 		function textureNeedsPowerOfTwo( texture ) {
 
 			if ( capabilities.isWebGL2 ) return false;
@@ -20262,7 +20329,7 @@
 
 						if ( ! isCompressed && ! isDataTexture ) {
 
-							cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize );
+							cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, capabilities.maxCubemapSize );
 
 						} else {
 
@@ -20454,13 +20521,8 @@
 			_gl.pixelStorei( 37441, texture.premultiplyAlpha );
 			_gl.pixelStorei( 3317, texture.unpackAlignment );
 
-			var image = clampToMaxSize( texture.image, capabilities.maxTextureSize );
-
-			if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) {
-
-				image = makePowerOfTwo( image );
-
-			}
+			var needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( texture.image ) === false;
+			var image = resizeImage( texture.image, needsPowerOfTwo, false, capabilities.maxTextureSize );
 
 			var isPowerOfTwoImage = isPowerOfTwo( image ),
 				glFormat = utils.convert( texture.format ),
@@ -20642,24 +20704,60 @@
 		}
 
 		// Setup storage for internal depth/stencil buffers and bind to correct framebuffer
-		function setupRenderBufferStorage( renderbuffer, renderTarget ) {
+		function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
 
 			_gl.bindRenderbuffer( 36161, renderbuffer );
 
 			if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
 
-				_gl.renderbufferStorage( 36161, 33189, renderTarget.width, renderTarget.height );
+				if ( isMultisample ) {
+
+					var samples = getRenderTargetSamples( renderTarget );
+
+					_gl.renderbufferStorageMultisample( 36161, samples, 33189, renderTarget.width, renderTarget.height );
+
+				} else {
+
+					_gl.renderbufferStorage( 36161, 33189, renderTarget.width, renderTarget.height );
+
+				}
+
 				_gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
 
 			} else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
 
-				_gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
+				if ( isMultisample ) {
+
+					var samples = getRenderTargetSamples( renderTarget );
+
+					_gl.renderbufferStorageMultisample( 36161, samples, 34041, renderTarget.width, renderTarget.height );
+
+				} else {
+
+					_gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
+
+				}
+
+
 				_gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
 
 			} else {
 
-				// FIXME: We don't support !depth !stencil
-				_gl.renderbufferStorage( 36161, 32854, renderTarget.width, renderTarget.height );
+				var glFormat = utils.convert( renderTarget.texture.format );
+				var glType = utils.convert( renderTarget.texture.type );
+				var glInternalFormat = getInternalFormat( glFormat, glType );
+
+				if ( isMultisample ) {
+
+					var samples = getRenderTargetSamples( renderTarget );
+
+					_gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+				} else {
+
+					_gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+
+				}
 
 			}
 
@@ -20766,6 +20864,7 @@
 			info.memory.textures ++;
 
 			var isCube = ( renderTarget.isWebGLRenderTargetCube === true );
+			var isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
 			var isTargetPowerOfTwo = isPowerOfTwo( renderTarget );
 
 			// Setup framebuffer
@@ -20784,6 +20883,42 @@
 
 				renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
 
+				if ( isMultisample ) {
+
+					if ( capabilities.isWebGL2 ) {
+
+						renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+						renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+
+						_gl.bindRenderbuffer( 36161, renderTargetProperties.__webglColorRenderbuffer );
+						var glFormat = utils.convert( renderTarget.texture.format );
+						var glType = utils.convert( renderTarget.texture.type );
+						var glInternalFormat = getInternalFormat( glFormat, glType );
+						var samples = getRenderTargetSamples( renderTarget );
+						_gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+						_gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
+						_gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
+						_gl.bindRenderbuffer( 36161, null );
+
+						if ( renderTarget.depthBuffer ) {
+
+							renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
+							setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
+
+						}
+
+						_gl.bindFramebuffer( 36160, null );
+
+
+					} else {
+
+						console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+					}
+
+				}
+
 			}
 
 			// Setup color buffer
@@ -20851,6 +20986,43 @@
 
 		}
 
+		function updateMultisampleRenderTarget( renderTarget ) {
+
+			if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+				if ( capabilities.isWebGL2 ) {
+
+					var renderTargetProperties = properties.get( renderTarget );
+
+					_gl.bindFramebuffer( 36008, renderTargetProperties.__webglMultisampledFramebuffer );
+					_gl.bindFramebuffer( 36009, renderTargetProperties.__webglFramebuffer );
+
+					var width = renderTarget.width;
+					var height = renderTarget.height;
+					var mask = 16384;
+
+					if ( renderTarget.depthBuffer ) mask |= 256;
+					if ( renderTarget.stencilBuffer ) mask |= 1024;
+
+					_gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
+
+				} else {
+
+					console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+				}
+
+			}
+
+		}
+
+		function getRenderTargetSamples( renderTarget ) {
+
+			return ( capabilities.isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
+				Math.min( capabilities.maxSamples, renderTarget.samples ) : 0;
+
+		}
+
 		function updateVideoTexture( texture ) {
 
 			var id = texture.id;
@@ -20873,6 +21045,7 @@
 		this.setTextureCubeDynamic = setTextureCubeDynamic;
 		this.setupRenderTarget = setupRenderTarget;
 		this.updateRenderTargetMipmap = updateRenderTargetMipmap;
+		this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
 
 	}
 
@@ -23237,12 +23410,18 @@
 
 			}
 
-			// Generate mipmap if we're using any kind of mipmap filtering
+			//
 
 			if ( renderTarget ) {
 
+				// Generate mipmap if we're using any kind of mipmap filtering
+
 				textures.updateRenderTargetMipmap( renderTarget );
 
+				// resolve multisample renderbuffers to a single-sample texture if necessary
+
+				textures.updateMultisampleRenderTarget( renderTarget );
+
 			}
 
 			// Ensure depth buffer writing is enabled so it can be cleared on next render
@@ -24598,6 +24777,10 @@
 					framebuffer = __webglFramebuffer[ renderTarget.activeCubeFace ];
 					isCube = true;
 
+				} else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+					framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+
 				} else {
 
 					framebuffer = __webglFramebuffer;
@@ -24859,6 +25042,12 @@
 
 			return data;
 
+		},
+
+		dispose: function () {
+
+			this.dispatchEvent( { type: 'dispose' } );
+
 		}
 
 	} );
@@ -37349,6 +37538,9 @@
 							material.uniforms[ name ].value = new Vector4().fromArray( uniform.value );
 							break;
 
+						case 'm3':
+							material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value );
+
 						case 'm4':
 							material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value );
 							break;
@@ -39676,6 +39868,8 @@
 
 			if ( this.parent === null ) this.updateMatrixWorld();
 
+			var currentRenderTarget = renderer.getRenderTarget();
+
 			var renderTarget = this.renderTarget;
 			var generateMipmaps = renderTarget.texture.generateMipmaps;
 
@@ -39701,12 +39895,14 @@
 			renderTarget.activeCubeFace = 5;
 			renderer.render( scene, cameraNZ, renderTarget );
 
-			renderer.setRenderTarget( null );
+			renderer.setRenderTarget( currentRenderTarget );
 
 		};
 
 		this.clear = function ( renderer, color, depth, stencil ) {
 
+			var currentRenderTarget = renderer.getRenderTarget();
+
 			var renderTarget = this.renderTarget;
 
 			for ( var i = 0; i < 6; i ++ ) {
@@ -39718,7 +39914,7 @@
 
 			}
 
-			renderer.setRenderTarget( null );
+			renderer.setRenderTarget( currentRenderTarget );
 
 		};
 
@@ -47343,6 +47539,7 @@
 
 	}
 
+	exports.WebGLMultisampleRenderTarget = WebGLMultisampleRenderTarget;
 	exports.WebGLRenderTargetCube = WebGLRenderTargetCube;
 	exports.WebGLRenderTarget = WebGLRenderTarget;
 	exports.WebGLRenderer = WebGLRenderer;

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


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


+ 6 - 6
docs/api/en/core/Object3D.html

@@ -107,14 +107,14 @@
 		On the other hand the translation part of the modelViewMatrix is not relevant for the calculation of normals. Thus a Matrix3 is sufficient.
 		</p>
 
-		<h3>[property:function onAfterRender]</h3>
+		<h3>[property:Function onAfterRender]</h3>
 		<p>
 		An optional callback that is executed immediately after the Object3D is rendered.
 		This function is called with the following parameters: renderer, scene, camera, geometry,
 		material, group.
 		</p>
 
-		<h3>[property:function onBeforeRender]</h3>
+		<h3>[property:Function onBeforeRender]</h3>
 		<p>
 		An optional callback that is executed immediately before the Object3D is rendered.
 		This function is called with the following parameters: renderer, scene, camera, geometry,
@@ -158,7 +158,7 @@
 		Default is [page:Object3D.DefaultUp] - that is, ( 0, 1, 0 ).
 		</p>
 
-		<h3>[property:object userData]</h3>
+		<h3>[property:Object userData]</h3>
 		<p>
 		An object that can be used to store custom data about the Object3D. It should not hold
 		references to functions as these will not be cloned.
@@ -203,7 +203,7 @@
 
 		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
 
-		<h3>[method:null add]( [param:Object3D object], ... )</h3>
+		<h3>[method:this add]( [param:Object3D object], ... )</h3>
 		<p>
 		Adds *object* as child of this object. An arbitrary number of objects may be added. Any current parent on an
 		object passed in here will be removed, since an object can have at most one parent.<br /><br />
@@ -214,7 +214,7 @@
 		<h3>[method:null applyMatrix]( [param:Matrix4 matrix] )</h3>
 		<p>Applies the matrix transform to the object and updates the object's position, rotation and scale.</p>
 
-		<h3>[method:Object3D applyQuaternion]( [param:Quaternion quaternion] )</h3>
+		<h3>[method:this applyQuaternion]( [param:Quaternion quaternion] )</h3>
 		<p>Applies the rotation represented by the quaternion to the object.</p>
 
 		<h3>[method:Object3D clone]( [param:Boolean recursive] )</h3>
@@ -310,7 +310,7 @@
 		to use raycasting.
 		</p>
 
-		<h3>[method:null remove]( [param:Object3D object], ... )</h3>
+		<h3>[method:this remove]( [param:Object3D object], ... )</h3>
 		<p>
 		Removes *object* as child of this object. An arbitrary number of objects may be removed.
 		</p>

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

@@ -48,7 +48,7 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:CompressedTexture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<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 />

+ 2 - 2
docs/api/en/loaders/CubeTextureLoader.html

@@ -70,7 +70,7 @@ scene.background = new THREE.CubeTextureLoader()
 
 		<h2>Methods</h2>
 
-		<h3>[method:null load]( [param:String urls], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:CubeTexture load]( [param:String urls], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<p>
 		[page:String urls] — array of 6 urls to images, one for each side of the CubeTexture.
 		The urls should be specified in the following order: pos-x, neg-x, pos-y, neg-y, pos-z, neg-z.
@@ -83,7 +83,7 @@ scene.background = new THREE.CubeTextureLoader()
 		[page:Function onError] — Will be called when load errors.<br />
 		</p>
 		<p>
-		Begin loading from url and pass the loaded [page:Texture texture] to onLoad.
+		Begin loading from url and pass the loaded [page:CubeTexture texture] to onLoad.
 		</p>
 
 		<h3>[method:null setCrossOrigin]( [param:String value] )</h3>

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

@@ -44,7 +44,7 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:DataTexture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<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 />

+ 4 - 4
docs/api/en/loaders/FileLoader.html

@@ -76,7 +76,7 @@
 		<h3>[property:String path]</h3>
 		<p>The base path from which files will be loaded. See [page:.setPath]. Default is *undefined*.</p>
 
-		<h3>[property:object requestHeader]</h3>
+		<h3>[property:Object requestHeader]</h3>
 		<p>The [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header request header] used in HTTP request. See [page:.setRequestHeader]. Default is *undefined*.</p>
 
 		<h3>[property:String responseType]</h3>
@@ -91,7 +91,7 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:XMLHttpRequest load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<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 />
@@ -115,7 +115,7 @@
 			you are loading many models from the same directory.
 		</p>
 
-		<h3>[method:FileLoader setRequestHeader]( [param:object requestHeader] )</h3>
+		<h3>[method:FileLoader setRequestHeader]( [param:Object requestHeader] )</h3>
 		<p>
 			[page:object requestHeader] - key: The name of the header whose value is to be set. value: The value to set as the body of the header.<br /><br />
 
@@ -125,7 +125,7 @@
 		<h3>[method:FileLoader setResponseType]( [param:String responseType] )</h3>
 		<p>
 			Change the response type. Valid values are:<br />
-			[page:String text] or empty string (default) - returns the data as [page:String string].<br />
+			[page:String text] or empty string (default) - returns the data as [page:String String].<br />
 			[page:String arraybuffer] - loads the data into a [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer ArrayBuffer] and returns that.<br />
 			[page:String blob] - returns the data as a [link:https://developer.mozilla.org/en/docs/Web/API/Blob Blob].<br />
 			[page:String document] - parses the file using the [link:https://developer.mozilla.org/en-US/docs/Web/API/DOMParser DOMParser].<br />

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

@@ -73,7 +73,7 @@
 		<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:Texture texture].<br />
+		[page:Function onLoad] — Will be called when load completes. The argument will be the loaded [page:Font font].<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 when load errors.<br /><br />
 

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

@@ -82,7 +82,7 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
+		<h3>[method:HTMLImageElement load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )</h3>
 		<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 />

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

@@ -74,7 +74,7 @@
 		[page:Function onProgress] — Will be called while load progresses. The argument will be the progress event.<br />
 		[page:Function onError] — Will be called when load errors.<br /><br />
 
-		Begin loading from url and return the [page:Material] object that will contain the data.
+		Begin loading from url.
 		</p>
 
 		<h3>[method:Material parse]( [param:Object json] )</h3>

+ 49 - 0
docs/api/en/renderers/WebGLMultisampleRenderTarget.html

@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<p class="desc">
+			A special render target that can be used to utilize multi-sampled renderbuffers.
+			Heads up: [name] can only be used with a WebGL 2 rendering context.
+		</p>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([param:Number width], [param:Number height], [param:Object options])</h3>
+
+		<p>
+		[page:Float width] - The width of the render target. <br />
+		[page:Float height] - The height of the render target.<br />
+		[page:Object options] - (optional) object that holds texture parameters for an auto-generated target
+		texture and depthBuffer/stencilBuffer booleans.
+		</p>
+
+		<h2>Properties</h2>
+
+		<h3>[property:number samples]</h3>
+		<p>
+		Specifies the number of samples to be used for the renderbuffer storage. However, the maximum supported
+		size for multisampling is platform dependent and defined via *gl.MAX_SAMPLES*.
+		</p>
+
+		<p>[page:WebGLRenderTarget WebGLRenderTarget] properties are available on this class.</p>
+
+		<h2>Methods</h2>
+
+		<p>[page:WebGLRenderTarget WebGLRenderTarget] methods are available on this class.</p>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 5 - 0
docs/api/en/scenes/Scene.html

@@ -51,6 +51,11 @@
 		Return the scene data in JSON format.
 		</p>
 
+		<h3>[method:null dispose]()</h3>
+		<p>
+		Clears scene related data internally cached by [page:WebGLRenderer].
+		</p>
+
 		<h2>Source</h2>
 
 		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 46 - 0
docs/api/zh/renderers/WebGLMultisampleRenderTarget.html

@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<p class="desc">
+			TODO
+		</p>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([param:Number width], [param:Number height], [param:Object options])</h3>
+
+		<p>
+		[page:Float width] - TODO <br />
+		[page:Float height] - TODO<br />
+		[page:Object options] - TODO
+		</p>
+
+		<h2>Properties</h2>
+
+		<h3>[property:Number samples]</h3>
+		<p>
+		TODO
+		</p>
+
+		<p>[page:WebGLRenderTarget WebGLRenderTarget] TODO</p>
+
+		<h2>Methods</h2>
+
+		<p>[page:WebGLRenderTarget WebGLRenderTarget] TODO</p>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 5 - 0
docs/api/zh/scenes/Scene.html

@@ -54,6 +54,11 @@
 			使用JSON格式返回场景数据。
 		</p>
 
+		<h3>[method:null dispose]()</h3>
+		<p>
+			TODO
+		</p>
+
 		<h2>源代码</h2>
 
 		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 2 - 0
docs/list.js

@@ -309,6 +309,7 @@ var list = {
 			},
 
 			"Renderers": {
+				"WebGLMultisampleRenderTarget": "api/en/renderers/WebGLMultisampleRenderTarget",
 				"WebGLRenderer": "api/en/renderers/WebGLRenderer",
 				"WebGLRenderTarget": "api/en/renderers/WebGLRenderTarget",
 				"WebGLRenderTargetCube": "api/en/renderers/WebGLRenderTargetCube"
@@ -733,6 +734,7 @@ var list = {
 			},
 
 			"渲染器": {
+				"WebGLMultisampleRenderTarget": "api/zh/renderers/WebGLMultisampleRenderTarget",
 				"WebGLRenderer": "api/zh/renderers/WebGLRenderer",
 				"WebGLRenderTarget": "api/zh/renderers/WebGLRenderTarget",
 				"WebGLRenderTargetCube": "api/zh/renderers/WebGLRenderTargetCube"

+ 2 - 0
docs/manual/en/introduction/How-to-use-WebGL2.html

@@ -101,6 +101,7 @@ var material = new THREE.ShaderMaterial( {
 
 		[example:webgl2_materials_texture3d WebGL2 / materials / texture3d]<br />
 		[example:webgl2_materials_texture3d_volume WebGL2 / materials / texture3d / volume]<br />
+		[example:webgl2_multisampled_renderbuffers WebGL2 / multisampled renderbuffers]
 	</p>
 
 	<h2>Supported features</h2>
@@ -110,6 +111,7 @@ var material = new THREE.ShaderMaterial( {
 		overview about what's already available in the latest version of three.js.
 		<ul>
 			<li>3D Textures</li>
+			<li>Multisampled Renderbuffers</li>
 		</ul>
 
 	</p>

+ 11 - 9
docs/manual/en/introduction/WebGL-compatibility-check.html

@@ -19,16 +19,18 @@
 			to your javascript and run the following before attempting to render anything.
 		</p>
 
-<code>
-if (WEBGL.isWebGLAvailable()) {
-    // Initiate function or other initializations here
-    animate();
-} else {
-    var warning = WEBGL.getWebGLErrorMessage();
-    document.getElementById('container').appendChild(warning);
-}
-</code>
+		<code>
+		if ( WEBGL.isWebGLAvailable() ) {
 
+			// Initiate function or other initializations here
+			animate();
 
+		} else {
+
+			var warning = WEBGL.getWebGLErrorMessage();
+			document.getElementById( 'container' ).appendChild( warning );
+
+		}
+		</code>
 	</body>
 </html>

+ 2 - 0
docs/manual/zh/introduction/How-to-use-WebGL2.html

@@ -99,6 +99,7 @@ var material = new THREE.ShaderMaterial( {
 
 		[example:webgl2_materials_texture3d WebGL2 / materials / texture3d]<br />
 		[example:webgl2_materials_texture3d_volume WebGL2 / materials / texture3d / volume]<br />
+		[example:webgl2_multisampled_renderbuffers WebGL2 / multisampled renderbuffers]
 	</p>
 
 	<h2>支持的特性</h2>
@@ -108,6 +109,7 @@ var material = new THREE.ShaderMaterial( {
 		下列列表展现了在最新版本three.js中,已可用的特性的概览。
 		<ul>
 			<li>3D Textures</li>
+			<li>Multisampled Renderbuffers</li>
 		</ul>
 
 	</p>

+ 7 - 1
docs/scenes/js/geometry.js

@@ -6,7 +6,13 @@ var twoPi = Math.PI * 2;
 
 function updateGroupGeometry( mesh, geometry ) {
 
-	if ( geometry.isGeometry ) geometry = new THREE.BufferGeometry().fromGeometry( geometry );
+	if ( geometry.isGeometry ) {
+
+		geometry = new THREE.BufferGeometry().fromGeometry( geometry );
+
+		console.warn( 'THREE.GeometryBrowser: Converted Geometry to BufferGeometry.' );
+
+	}
 
 	mesh.children[ 0 ].geometry.dispose();
 	mesh.children[ 1 ].geometry.dispose();

+ 1 - 0
examples/files.js

@@ -316,6 +316,7 @@ var files = {
 	"webgl2": [
 		"webgl2_materials_texture3d",
 		"webgl2_materials_texture3d_volume",
+		"webgl2_multisampled_renderbuffers",
 		"webgl2_sandbox"
 	],
 	"webaudio": [

+ 1 - 1
examples/js/Ocean.js

@@ -120,7 +120,7 @@ THREE.Ocean = function ( renderer, camera, scene, options ) {
 	var initialSpectrumUniforms = THREE.UniformsUtils.clone( initialSpectrumShader.uniforms );
 	this.materialInitialSpectrum = new THREE.ShaderMaterial( {
 		uniforms: initialSpectrumUniforms,
-		vertexShader: fullscreeenVertexShader.vertexShader,
+		vertexShader: initialSpectrumShader.vertexShader,
 		fragmentShader: initialSpectrumShader.fragmentShader
 	} );
 	this.materialInitialSpectrum.uniforms.u_wind = { value: new THREE.Vector2() };

+ 0 - 4
examples/js/ShaderSkin.js

@@ -16,7 +16,6 @@ THREE.ShaderSkin = {
 	//		- specular map
 	//		- point, directional and hemisphere lights (use with "lights: true" material option)
 	//		- fog (use with "fog: true" material option)
-	//		- shadow maps
 	//
 	// ------------------------------------------------------------------------------------------ */
 
@@ -85,7 +84,6 @@ THREE.ShaderSkin = {
 			THREE.ShaderChunk[ "bsdfs" ],
 			THREE.ShaderChunk[ "packing" ],
 			THREE.ShaderChunk[ "lights_pars_begin" ],
-			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 			THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
 
@@ -265,7 +263,6 @@ THREE.ShaderSkin = {
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "lights_pars_begin" ],
-			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
 			THREE.ShaderChunk[ "fog_pars_vertex" ],
 
 			"void main() {",
@@ -281,7 +278,6 @@ THREE.ShaderSkin = {
 
 				"gl_Position = projectionMatrix * mvPosition;",
 
-				THREE.ShaderChunk[ "shadowmap_vertex" ],
 				THREE.ShaderChunk[ "fog_vertex" ],
 
 			"}"

+ 51 - 49
examples/js/controls/TransformControls.js

@@ -58,7 +58,10 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	var pointStart = new THREE.Vector3();
 	var pointEnd = new THREE.Vector3();
+	var offset = new THREE.Vector3();
 	var rotationAxis = new THREE.Vector3();
+	var startNorm = new THREE.Vector3();
+	var endNorm = new THREE.Vector3();
 	var rotationAngle = 0;
 
 	var cameraPosition = new THREE.Vector3();
@@ -67,6 +70,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	var parentPosition = new THREE.Vector3();
 	var parentQuaternion = new THREE.Quaternion();
+	var parentQuaternionInv = new THREE.Quaternion();
 	var parentScale = new THREE.Vector3();
 
 	var worldPositionStart = new THREE.Vector3();
@@ -75,17 +79,17 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	var worldPosition = new THREE.Vector3();
 	var worldQuaternion = new THREE.Quaternion();
+	var worldQuaternionInv = new THREE.Quaternion();
 	var worldScale = new THREE.Vector3();
 
 	var eye = new THREE.Vector3();
 
-	var _positionStart = new THREE.Vector3();
-	var _quaternionStart = new THREE.Quaternion();
-	var _scaleStart = new THREE.Vector3();
+	var positionStart = new THREE.Vector3();
+	var quaternionStart = new THREE.Quaternion();
+	var scaleStart = new THREE.Vector3();
 
 	// TODO: remove properties unused in plane and gizmo
 
-	defineProperty( "parentQuaternion", parentQuaternion );
 	defineProperty( "worldPosition", worldPosition );
 	defineProperty( "worldPositionStart", worldPositionStart );
 	defineProperty( "worldQuaternion", worldQuaternion );
@@ -190,6 +194,9 @@ THREE.TransformControls = function ( camera, domElement ) {
 			this.object.parent.matrixWorld.decompose( parentPosition, parentQuaternion, parentScale );
 			this.object.matrixWorld.decompose( worldPosition, worldQuaternion, worldScale );
 
+			parentQuaternionInv.copy( parentQuaternion ).inverse();
+			worldQuaternionInv.copy( worldQuaternion ).inverse();
+
 		}
 
 		this.camera.updateMatrixWorld();
@@ -266,16 +273,14 @@ THREE.TransformControls = function ( camera, domElement ) {
 				this.object.updateMatrixWorld();
 				this.object.parent.updateMatrixWorld();
 
-				_positionStart.copy( this.object.position );
-				_quaternionStart.copy( this.object.quaternion );
-				_scaleStart.copy( this.object.scale );
+				positionStart.copy( this.object.position );
+				quaternionStart.copy( this.object.quaternion );
+				scaleStart.copy( this.object.scale );
 
 				this.object.matrixWorld.decompose( worldPositionStart, worldQuaternionStart, worldScaleStart );
 
 				pointStart.copy( planeIntersect.point ).sub( worldPositionStart );
 
-				if ( space === 'local' ) pointStart.applyQuaternion( worldQuaternionStart.clone().inverse() );
-
 			}
 
 			this.dragging = true;
@@ -313,29 +318,27 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 		pointEnd.copy( planeIntersect.point ).sub( worldPositionStart );
 
-		if ( space === 'local' ) pointEnd.applyQuaternion( worldQuaternionStart.clone().inverse() );
-
 		if ( mode === 'translate' ) {
 
-			if ( axis.search( 'X' ) === -1 ) {
-				pointEnd.x = pointStart.x;
-			}
-			if ( axis.search( 'Y' ) === -1 ) {
-				pointEnd.y = pointStart.y;
-			}
-			if ( axis.search( 'Z' ) === -1 ) {
-				pointEnd.z = pointStart.z;
+			// Apply translate
+
+			offset.copy( pointEnd ).sub( pointStart );
+
+			if ( space === 'local' && axis !== 'XYZ' ) {
+				offset.applyQuaternion( worldQuaternionInv );
 			}
 
-			// Apply translate
+			if ( axis.indexOf( 'X' ) === -1 ) offset.x = 0;
+			if ( axis.indexOf( 'Y' ) === -1 ) offset.y = 0;
+			if ( axis.indexOf( 'Z' ) === -1 ) offset.z = 0;
 
-			if ( space === 'local' ) {
-				object.position.copy( pointEnd ).sub( pointStart ).applyQuaternion( _quaternionStart );
+			if ( space === 'local' && axis !== 'XYZ') {
+				offset.applyQuaternion( quaternionStart ).divide( parentScale );
 			} else {
-				object.position.copy( pointEnd ).sub( pointStart );
+				offset.applyQuaternion( parentQuaternionInv ).divide( parentScale );
 			}
 
-			object.position.add( _positionStart );
+			object.position.copy( offset ).add( positionStart );
 
 			// Apply translation snap
 
@@ -343,7 +346,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 				if ( space === 'local' ) {
 
-					object.position.applyQuaternion(_tempQuaternion.copy( _quaternionStart ).inverse() );
+					object.position.applyQuaternion(_tempQuaternion.copy( quaternionStart ).inverse() );
 
 					if ( axis.search( 'X' ) !== -1 ) {
 						object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
@@ -357,7 +360,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 						object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
 					}
 
-					object.position.applyQuaternion( _quaternionStart );
+					object.position.applyQuaternion( quaternionStart );
 
 				}
 
@@ -415,41 +418,40 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 			// Apply scale
 
-			object.scale.copy( _scaleStart ).multiply( _tempVector );
+			object.scale.copy( scaleStart ).multiply( _tempVector );
 
 		} else if ( mode === 'rotate' ) {
 
-			var ROTATION_SPEED = 20 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
-
-			var quaternion = this.space === "local" ? worldQuaternion : _identityQuaternion;
+			offset.copy( pointEnd ).sub( pointStart );
 
-			var unit = _unit[ axis ];
+			var ROTATION_SPEED = 20 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
 
 			if ( axis === 'E' ) {
 
-				_tempVector.copy( pointEnd ).cross( pointStart );
 				rotationAxis.copy( eye );
-				rotationAngle = pointEnd.angleTo( pointStart ) * ( _tempVector.dot( eye ) < 0 ? 1 : -1 );
+				rotationAngle = pointEnd.angleTo( pointStart );
+
+				startNorm.copy( pointStart ).normalize();
+				endNorm.copy( pointEnd ).normalize();
+
+				rotationAngle *= ( endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : -1);
 
 			} else if ( axis === 'XYZE' ) {
 
-				_tempVector.copy( pointEnd ).sub( pointStart ).cross( eye ).normalize();
-				rotationAxis.copy( _tempVector );
-				rotationAngle = pointEnd.sub( pointStart ).dot( _tempVector.cross( eye ) ) * ROTATION_SPEED;
+				rotationAxis.copy( offset ).cross( eye ).normalize(  );
+				rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
 
 			} else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
 
-				_alignVector.copy( unit ).applyQuaternion( quaternion );
+				rotationAxis.copy( _unit[ axis ] );
 
-				rotationAxis.copy( unit );
+				_tempVector.copy( _unit[ axis ] );
 
-				_tempVector = unit.clone();
-				_tempVector2 = pointEnd.clone().sub( pointStart );
 				if ( space === 'local' ) {
-					_tempVector.applyQuaternion( quaternion );
-					_tempVector2.applyQuaternion( worldQuaternionStart );
+					_tempVector.applyQuaternion( worldQuaternion );
 				}
-				rotationAngle = _tempVector2.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED;
+
+				rotationAngle = offset.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED;
 
 			}
 
@@ -460,16 +462,16 @@ THREE.TransformControls = function ( camera, domElement ) {
 			this.rotationAngle = rotationAngle;
 
 			// Apply rotate
+			if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
 
-			if ( space === 'local' ) {
-
-				object.quaternion.copy( _quaternionStart );
-				object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) );
+				object.quaternion.copy( quaternionStart );
+				object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) ).normalize();
 
 			} else {
 
+				rotationAxis.applyQuaternion( parentQuaternionInv );
 				object.quaternion.copy( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) );
-				object.quaternion.multiply( _quaternionStart );
+				object.quaternion.multiply( quaternionStart ).normalize();
 
 			}
 
@@ -534,7 +536,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 		if ( !scope.enabled ) return;
 
 		event.preventDefault();
-		
+
 		document.addEventListener( "mousemove", onPointerMove, false );
 
 		scope.pointerHover( getPointer( event ) );

+ 10 - 1
examples/js/exporters/ColladaExporter.js

@@ -359,6 +359,15 @@ THREE.ColladaExporter.prototype = {
 
 					type = 'constant';
 
+					if ( m.map !== null ) {
+
+						// The Collada spec does not support diffuse texture maps with the
+						// constant shader type.
+						// mrdoob/three.js#15469
+						console.warn( 'ColladaExporter: Texture maps not supported with MeshBasicMaterial.' );
+
+					}
+
 				}
 
 				var emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 );
@@ -427,7 +436,7 @@ THREE.ColladaExporter.prototype = {
 								`<float sid="shininess">${ shininess }</float>`
 						) +
 
-						'</shininess>' 
+						'</shininess>'
 						: ''
 					) +
 

+ 2 - 1
examples/js/exporters/GLTFExporter.js

@@ -1128,6 +1128,7 @@ THREE.GLTFExporter.prototype = {
 
 			// @QUESTION Detect if .vertexColors = THREE.VertexColors?
 			// For every attribute create an accessor
+			var modifiedAttribute = null;
 			for ( var attributeName in geometry.attributes ) {
 
 				var attribute = geometry.attributes[ attributeName ];
@@ -1141,7 +1142,7 @@ THREE.GLTFExporter.prototype = {
 				}
 
 				// JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT.
-				var modifiedAttribute;
+				modifiedAttribute = null;
 				var array = attribute.array;
 				if ( attributeName === 'JOINTS_0' &&
 					! ( array instanceof Uint16Array ) &&

+ 105 - 100
examples/js/interactive/SelectionBox.js

@@ -3,114 +3,119 @@
  * This is a class to check whether objects are in a selection area in 3D space
  */
 
-function SelectionBox ( camera, scene, deep ) {
-	
-	this.camera = camera;
-	this.scene = scene;
-	this.startPoint = new THREE.Vector3();
-	this.endPoint = new THREE.Vector3();
-	this.collection = [];
-	this.deep = deep || Number.MAX_VALUE;
-	
-}
-
-SelectionBox.prototype.select = function ( startPoint, endPoint ) {
-	
-	this.startPoint = startPoint || this.startPoint;
-	this.endPoint = endPoint || this.endPoint;
-	this.collection = [];
-    
-	var boxSelectionFrustum = this.createFrustum( this.startPoint, this.endPoint );
-	this.searchChildInFrustum( boxSelectionFrustum, this.scene );
-    
-	return this.collection;
-	
-}
-
-SelectionBox.prototype.createFrustum = function ( startPoint, endPoint ) {
-	
-	startPoint = startPoint || this.startPoint;
-	endPoint = endPoint || this.endPoint
-
-	this.camera.updateProjectionMatrix();
-	this.camera.updateMatrixWorld();
-	this.camera.updateMatrix();
-
-	var tmpPoint = startPoint.clone();
-	tmpPoint.x = Math.min( startPoint.x, endPoint.x );
-	tmpPoint.y = Math.max( startPoint.y, endPoint.y );
-	endPoint.x = Math.max( startPoint.x, endPoint.x );
-	endPoint.y = Math.min( startPoint.y, endPoint.y );
-
-	var vecNear = this.camera.position.clone();
-	var vecTopLeft = tmpPoint.clone();
-	var vecTopRight = new THREE.Vector3( endPoint.x, tmpPoint.y, 0 );
-	var vecDownRight = endPoint.clone();
-	var vecDownLeft = new THREE.Vector3( tmpPoint.x, endPoint.y, 0 );
-	vecTopLeft.unproject( this.camera );
-	vecTopRight.unproject( this.camera );
-	vecDownRight.unproject( this.camera );
-	vecDownLeft.unproject( this.camera );
-
-	var vectemp1 = vecTopLeft.clone().sub( vecNear );
-	var vectemp2 = vecTopRight.clone().sub( vecNear );
-	var vectemp3 = vecDownRight.clone().sub( vecNear );
-	vectemp1.normalize();
-	vectemp2.normalize();
-	vectemp3.normalize();
-
-	vectemp1.multiplyScalar( this.deep );
-	vectemp2.multiplyScalar( this.deep );
-	vectemp3.multiplyScalar( this.deep );
-	vectemp1.add( vecNear );
-	vectemp2.add( vecNear );
-	vectemp3.add( vecNear );
-
-	var planeTop = new THREE.Plane();
-	planeTop.setFromCoplanarPoints( vecNear, vecTopLeft, vecTopRight );
-	var planeRight = new THREE.Plane();
-	planeRight.setFromCoplanarPoints( vecNear, vecTopRight, vecDownRight );
-	var planeDown = new THREE.Plane();
-	planeDown.setFromCoplanarPoints( vecDownRight, vecDownLeft, vecNear );
-	var planeLeft = new THREE.Plane();
-	planeLeft.setFromCoplanarPoints( vecDownLeft, vecTopLeft, vecNear );
-	var planeFront = new THREE.Plane();
-	planeFront.setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
-	var planeBack = new THREE.Plane();
-	planeBack.setFromCoplanarPoints( vectemp3, vectemp2, vectemp1 );
-	planeBack.normal = planeBack.normal.multiplyScalar( -1 );
-
-	return new THREE.Frustum( planeTop, planeRight, planeDown, planeLeft, planeFront, planeBack );
-
-}
-
-SelectionBox.prototype.searchChildInFrustum = function ( frustum, object ) {
-
-	if ( object instanceof THREE.Mesh ) {
-
-		if ( object.material !== undefined ) {
-
-			object.geometry.computeBoundingSphere();
-			var center = object.geometry.boundingSphere.center.clone().applyMatrix4( object.matrixWorld );
-
-			if ( frustum.containsPoint( center ) ) {
-
-				this.collection.push( object );
+THREE.SelectionBox = ( function () {
+
+	var frustum = new THREE.Frustum();
+	var center = new THREE.Vector3();
+
+	function SelectionBox( camera, scene, deep ) {
+
+		this.camera = camera;
+		this.scene = scene;
+		this.startPoint = new THREE.Vector3();
+		this.endPoint = new THREE.Vector3();
+		this.collection = [];
+		this.deep = deep || Number.MAX_VALUE;
+
+	}
+
+	SelectionBox.prototype.select = function ( startPoint, endPoint ) {
+
+		this.startPoint = startPoint || this.startPoint;
+		this.endPoint = endPoint || this.endPoint;
+		this.collection = [];
+
+		this.updateFrustum( this.startPoint, this.endPoint );
+		this.searchChildInFrustum( frustum, this.scene );
+
+		return this.collection;
+
+	};
+
+	SelectionBox.prototype.updateFrustum = function ( startPoint, endPoint ) {
+
+		startPoint = startPoint || this.startPoint;
+		endPoint = endPoint || this.endPoint;
+
+		this.camera.updateProjectionMatrix();
+		this.camera.updateMatrixWorld();
+
+		var tmpPoint = startPoint.clone();
+		tmpPoint.x = Math.min( startPoint.x, endPoint.x );
+		tmpPoint.y = Math.max( startPoint.y, endPoint.y );
+		endPoint.x = Math.max( startPoint.x, endPoint.x );
+		endPoint.y = Math.min( startPoint.y, endPoint.y );
+
+		var vecNear = this.camera.position.clone();
+		var vecTopLeft = tmpPoint.clone();
+		var vecTopRight = new THREE.Vector3( endPoint.x, tmpPoint.y, 0 );
+		var vecDownRight = endPoint.clone();
+		var vecDownLeft = new THREE.Vector3( tmpPoint.x, endPoint.y, 0 );
+		vecTopLeft.unproject( this.camera );
+		vecTopRight.unproject( this.camera );
+		vecDownRight.unproject( this.camera );
+		vecDownLeft.unproject( this.camera );
+
+		var vectemp1 = vecTopLeft.clone().sub( vecNear );
+		var vectemp2 = vecTopRight.clone().sub( vecNear );
+		var vectemp3 = vecDownRight.clone().sub( vecNear );
+		vectemp1.normalize();
+		vectemp2.normalize();
+		vectemp3.normalize();
+
+		vectemp1.multiplyScalar( this.deep );
+		vectemp2.multiplyScalar( this.deep );
+		vectemp3.multiplyScalar( this.deep );
+		vectemp1.add( vecNear );
+		vectemp2.add( vecNear );
+		vectemp3.add( vecNear );
+
+		var planes = frustum.planes;
+
+		planes[ 0 ].setFromCoplanarPoints( vecNear, vecTopLeft, vecTopRight );
+		planes[ 1 ].setFromCoplanarPoints( vecNear, vecTopRight, vecDownRight );
+		planes[ 2 ].setFromCoplanarPoints( vecDownRight, vecDownLeft, vecNear );
+		planes[ 3 ].setFromCoplanarPoints( vecDownLeft, vecTopLeft, vecNear );
+		planes[ 4 ].setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
+		planes[ 5 ].setFromCoplanarPoints( vectemp3, vectemp2, vectemp1 );
+		planes[ 5 ].normal.multiplyScalar( - 1 );
+
+	};
+
+	SelectionBox.prototype.searchChildInFrustum = function ( frustum, object ) {
+
+		if ( object.isMesh ) {
+
+			if ( object.material !== undefined ) {
+
+				object.geometry.computeBoundingSphere();
+
+				center.copy( object.geometry.boundingSphere.center );
+
+				center.applyMatrix4( object.matrixWorld );
+
+				if ( frustum.containsPoint( center ) ) {
+
+					this.collection.push( object );
+
+				}
 
 			}
 
 		}
 
-	}
+		if ( object.children.length > 0 ) {
 
-	if ( object.children.length > 0 ) {
+			for ( var x = 0; x < object.children.length; x ++ ) {
 
-		for ( var x = 0; x < object.children.length; x++ ) {
+				this.searchChildInFrustum( frustum, object.children[ x ] );
 
-			this.searchChildInFrustum( frustum, object.children[x] );
+			}
 
 		}
 
-	}
-    
-}
+	};
+
+	return SelectionBox;
+
+} )();

+ 60 - 50
examples/js/interactive/SelectionHelper.js

@@ -1,73 +1,83 @@
-function SelectionHelper ( selectionBox, renderer, cssClassName ) {
+/**
+ * @author HypnosNova / https://www.threejs.org.cn/gallery
+ */
 
-	this.element = document.createElement( "div" );
-	this.element.classList.add( cssClassName );
-	this.element.style.pointerEvents = "none";
+THREE.SelectionHelper = ( function () {
 
-	this.renderer = renderer;
+	function SelectionHelper( selectionBox, renderer, cssClassName ) {
 
-	this.startPoint = { x: 0, y: 0 };
-	this.pointTopLeft = { x: 0, y: 0 };
-	this.pointBottomRight = { x: 0, y: 0 };
+		this.element = document.createElement( 'div' );
+		this.element.classList.add( cssClassName );
+		this.element.style.pointerEvents = 'none';
 
-	this.isDown = false;
+		this.renderer = renderer;
 
-	this.renderer.domElement.addEventListener( "mousedown", function ( event ) {
-		
-		this.isDown = true;
-		this.onSelectStart( event );
+		this.startPoint = { x: 0, y: 0 };
+		this.pointTopLeft = { x: 0, y: 0 };
+		this.pointBottomRight = { x: 0, y: 0 };
 
-	}.bind( this ), false );
+		this.isDown = false;
 
-	this.renderer.domElement.addEventListener( "mousemove", function ( event ) {
+		this.renderer.domElement.addEventListener( 'mousedown', function ( event ) {
 
-		if ( this.isDown ) {
+			this.isDown = true;
+			this.onSelectStart( event );
 
-			this.onSelectMove( event );
+		}.bind( this ), false );
 
-		}
+		this.renderer.domElement.addEventListener( 'mousemove', function ( event ) {
 
-	}.bind( this ), false );
+			if ( this.isDown ) {
 
-	this.renderer.domElement.addEventListener( "mouseup", function ( event ) {
+				this.onSelectMove( event );
 
-		this.isDown = false;
-		this.onSelectOver( event );
+			}
+
+		}.bind( this ), false );
+
+		this.renderer.domElement.addEventListener( 'mouseup', function ( event ) {
+
+			this.isDown = false;
+			this.onSelectOver( event );
+
+		}.bind( this ), false );
+
+	}
+
+	SelectionHelper.prototype.onSelectStart = function ( event ) {
+
+		this.renderer.domElement.parentElement.appendChild( this.element );
+
+		this.element.style.left = event.clientX + 'px';
+		this.element.style.top = event.clientY + 'px';
+		this.element.style.width = '0px';
+		this.element.style.height = '0px';
+
+		this.startPoint.x = event.clientX;
+		this.startPoint.y = event.clientY;
 
-	}.bind( this ), false );
-	
-}
+	};
 
-SelectionHelper.prototype.onSelectStart = function ( event ) {
-	
-	this.renderer.domElement.parentElement.appendChild( this.element );
+	SelectionHelper.prototype.onSelectMove = function ( event ) {
 
-	this.element.style.left = event.clientX + "px";
-	this.element.style.top = event.clientY + "px";
-	this.element.style.width = "0px";
-	this.element.style.height = "0px";
+		this.pointBottomRight.x = Math.max( this.startPoint.x, event.clientX );
+		this.pointBottomRight.y = Math.max( this.startPoint.y, event.clientY );
+		this.pointTopLeft.x = Math.min( this.startPoint.x, event.clientX );
+		this.pointTopLeft.y = Math.min( this.startPoint.y, event.clientY );
 
-	this.startPoint.x = event.clientX;
-	this.startPoint.y = event.clientY;
+		this.element.style.left = this.pointTopLeft.x + 'px';
+		this.element.style.top = this.pointTopLeft.y + 'px';
+		this.element.style.width = ( this.pointBottomRight.x - this.pointTopLeft.x ) + 'px';
+		this.element.style.height = ( this.pointBottomRight.y - this.pointTopLeft.y ) + 'px';
 
-}
+	};
 
-SelectionHelper.prototype.onSelectMove = function ( event ) {
+	SelectionHelper.prototype.onSelectOver = function () {
 
-	this.pointBottomRight.x = Math.max( this.startPoint.x, event.clientX );
-	this.pointBottomRight.y = Math.max( this.startPoint.y, event.clientY );
-	this.pointTopLeft.x = Math.min( this.startPoint.x, event.clientX );
-	this.pointTopLeft.y = Math.min( this.startPoint.y, event.clientY );
+		this.element.parentElement.removeChild( this.element );
 
-	this.element.style.left = this.pointTopLeft.x + "px";
-	this.element.style.top = this.pointTopLeft.y + "px";
-	this.element.style.width = ( this.pointBottomRight.x - this.pointTopLeft.x ) + "px";
-	this.element.style.height = ( this.pointBottomRight.y - this.pointTopLeft.y ) + "px";
-	
-}
+	};
 
-SelectionHelper.prototype.onSelectOver = function ( event ) {
+	return SelectionHelper;
 
-	this.element.parentElement.removeChild( this.element );
-	
-}
+} )();

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

@@ -63,7 +63,6 @@ THREE.Reflector = function ( geometry, options ) {
 	material.uniforms[ "textureMatrix" ].value = textureMatrix;
 
 	this.material = material;
-	this.renderOrder = - Infinity; // render first
 
 	this.onBeforeRender = function ( renderer, scene, camera ) {
 

+ 6 - 2
examples/js/offscreen/scene.js

@@ -71,7 +71,11 @@ function animate() {
 // PRNG
 
 var seed = 1;
+
 function random() {
-	var x = Math.sin(seed++) * 10000;
-	return x - Math.floor(x);
+
+	var x = Math.sin( seed ++ ) * 10000;
+
+	return x - Math.floor( x );
+
 }

+ 2 - 0
examples/js/renderers/SVGRenderer.js

@@ -252,6 +252,8 @@ THREE.SVGRenderer = function () {
 				_vector3.setFromMatrixPosition( object.matrixWorld );
 				_vector3.applyMatrix4( _viewProjectionMatrix );
 
+				if ( _vector3.z < 0 || _vector3.z > 1 ) return; // #15476
+
 				var x = _vector3.x * _svgWidthHalf;
 				var y = - _vector3.y * _svgHeightHalf;
 

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

@@ -88,6 +88,11 @@ THREE.ShaderLib[ 'ocean_initial_spectrum' ] = {
 		"u_resolution": { value: 512.0 },
 		"u_size": { value: 250.0 }
 	},
+	vertexShader: [
+		'void main (void) {',
+			'gl_Position = vec4(position, 1.0);',
+		'}'
+	].join( '\n' ),
 	fragmentShader: [
 		'precision highp float;',
 		'#include <common>',

+ 259 - 0
examples/js/utils/BufferGeometryUtils.js

@@ -381,6 +381,265 @@ THREE.BufferGeometryUtils = {
 
 		return new THREE.BufferAttribute( array, itemSize, normalized );
 
+	},
+
+	/**
+	 * @param {Array<THREE.BufferAttribute>} attributes
+	 * @return {Array<THREE.InterleavedBufferAttribute>}
+	 */
+	interleaveAttributes: function ( attributes ) {
+
+		// Interleaves the provided attributes into an InterleavedBuffer and returns
+		// a set of InterleavedBufferAttributes for each attribute
+		var TypedArray;
+		var arrayLength = 0;
+		var stride = 0;
+
+		// calculate the the length and type of the interleavedBuffer
+		for ( var i = 0, l = attributes.length; i < l; ++ i ) {
+
+			var attribute = attributes[ i ];
+
+			if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
+			if ( TypedArray !== attribute.array.constructor ) {
+
+				console.warn( 'AttributeBuffers of different types cannot be interleaved' );
+				return null;
+
+			}
+
+			arrayLength += attribute.array.length;
+			stride += attribute.itemSize;
+
+		}
+
+		// Create the set of buffer attributes
+		var interleavedBuffer = new THREE.InterleavedBuffer( new TypedArray( arrayLength ), stride );
+		var offset = 0;
+		var res = [];
+		var getters = [ 'getX', 'getY', 'getZ', 'getW' ];
+		var setters = [ 'setX', 'setY', 'setZ', 'setW' ];
+
+		for ( var j = 0, l = attributes.length; j < l; j ++ ) {
+
+			var attribute = attributes[ j ];
+			var itemSize = attribute.itemSize;
+			var count = attribute.count;
+			var iba = new THREE.InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
+			res.push( iba );
+
+			offset += itemSize;
+
+			// Move the data for each attribute into the new interleavedBuffer
+			// at the appropriate offset
+			for ( var c = 0; c < count; c ++ ) {
+
+				for ( var k = 0; k < itemSize; k ++ ) {
+
+					iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );
+
+				}
+
+			}
+
+		}
+
+		return res;
+
+	},
+
+	/**
+	 * @param {Array<THREE.BufferGeometry>} geometry
+	 * @return {number}
+	 */
+	estimateBytesUsed: function ( geometry ) {
+
+		// Return the estimated memory used by this geometry in bytes
+		// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
+		// for InterleavedBufferAttributes.
+		var mem = 0;
+		for ( var name in geometry.attributes ) {
+
+			var attr = geometry.getAttribute( name );
+			mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;
+
+		}
+
+		var indices = geometry.getIndex();
+		mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
+		return mem;
+
+	},
+
+	/**
+	 * @param {THREE.BufferGeometry} geometry
+	 * @param {number} tolerance
+	 * @return {THREE.BufferGeometry>}
+	 */
+	mergeVertices: function ( geometry, tolerance = 1e-4 ) {
+
+		tolerance = Math.max( tolerance, Number.EPSILON );
+
+		// Generate an index buffer if the geometry doesn't have one, or optimize it
+		// if it's already available.
+		var hashToIndex = {};
+		var indices = geometry.getIndex();
+		var positions = geometry.getAttribute( 'position' );
+		var vertexCount = indices ? indices.count : positions.count;
+
+		// next value for triangle indices
+		var nextIndex = 0;
+
+		// attributes and new attribute arrays
+		var attributeNames = Object.keys( geometry.attributes );
+		var attrArrays = {};
+		var morphAttrsArrays = {};
+		var newIndices = [];
+		var getters = [ 'getX', 'getY', 'getZ', 'getW' ];
+
+		// initialize the arrays
+		for ( var name of attributeNames ) {
+
+			attrArrays[ name ] = [];
+
+			var morphAttr = geometry.morphAttributes[ name ];
+			if ( morphAttr ) {
+
+				morphAttrsArrays[ name ] = new Array( morphAttr.length ).fill().map( () => [] );
+
+			}
+
+		}
+
+		// convert the error tolerance to an amount of decimal places to truncate to
+		var decimalShift = Math.log10( 1 / tolerance );
+		var shiftMultiplier = Math.pow( 10, decimalShift );
+		for ( var i = 0; i < vertexCount; i ++ ) {
+
+			var index = indices ? indices.getX( i ) : i;
+
+			// Generate a hash for the vertex attributes at the current index 'i'
+			var hash = '';
+			for ( var j = 0, l = attributeNames.length; j < l; j ++ ) {
+
+				var name = attributeNames[ j ];
+				var attribute = geometry.getAttribute( name );
+				var itemSize = attribute.itemSize;
+
+				for ( var k = 0; k < itemSize; k ++ ) {
+
+					// double tilde truncates the decimal value
+					hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier ) },`;
+
+				}
+
+			}
+
+			// Add another reference to the vertex if it's already
+			// used by another index
+			if ( hash in hashToIndex ) {
+
+				newIndices.push( hashToIndex[ hash ] );
+
+			} else {
+
+				// copy data to the new index in the attribute arrays
+				for ( var j = 0, l = attributeNames.length; j < l; j ++ ) {
+
+					var name = attributeNames[ j ];
+					var attribute = geometry.getAttribute( name );
+					var morphAttr = geometry.morphAttributes[ name ];
+					var itemSize = attribute.itemSize;
+					var newarray = attrArrays[ name ];
+					var newMorphArrays = morphAttrsArrays[ name ];
+
+					for ( var k = 0; k < itemSize; k ++ ) {
+
+						var getterFunc = getters[ k ];
+						newarray.push( attribute[ getterFunc ]( index ) );
+
+						if ( morphAttr ) {
+
+							for ( var m = 0, ml = morphAttr.length; m < ml; m ++ ) {
+
+								newMorphArrays[ m ].push( morphAttr[ m ][ getterFunc ]( index ) );
+
+							}
+
+						}
+
+					}
+
+				}
+
+				hashToIndex[ hash ] = nextIndex;
+				newIndices.push( nextIndex );
+				nextIndex ++;
+
+			}
+
+		}
+
+		// Generate typed arrays from new attribute arrays and update
+		// the attributeBuffers
+		const result = geometry.clone();
+		for ( var i = 0, l = attributeNames.length; i < l; i ++ ) {
+
+			var name = attributeNames[ i ];
+			var oldAttribute = geometry.getAttribute( name );
+			var attribute;
+
+			var buffer = new oldAttribute.array.constructor( attrArrays[ name ] );
+			if ( oldAttribute.isInterleavedBufferAttribute ) {
+
+				attribute = new THREE.BufferAttribute( buffer, oldAttribute.itemSize, oldAttribute.itemSize );
+
+			} else {
+
+				attribute = geometry.getAttribute( name ).clone();
+				attribute.setArray( buffer );
+
+			}
+
+			result.addAttribute( name, attribute );
+
+			// Update the attribute arrays
+			if ( name in morphAttrsArrays ) {
+
+				for ( var j = 0; j < morphAttrsArrays[ name ].length; j ++ ) {
+
+					var morphAttribute = geometry.morphAttributes[ name ][ j ].clone();
+					morphAttribute.setArray( new morphAttribute.array.constructor( morphAttrsArrays[ name ][ j ] ) );
+					result.morphAttributes[ name ][ j ] = morphAttribute;
+
+				}
+
+			}
+
+		}
+
+		// Generate an index buffer typed array
+		var cons = Uint8Array;
+		if ( newIndices.length >= Math.pow( 2, 8 ) ) cons = Uint16Array;
+		if ( newIndices.length >= Math.pow( 2, 16 ) ) cons = Uint32Array;
+
+		var newIndexBuffer = new cons( newIndices );
+		var newIndices = null;
+		if ( indices === null ) {
+
+			newIndices = new THREE.BufferAttribute( newIndexBuffer, 1 );
+
+		} else {
+
+			newIndices = geometry.getIndex().clone();
+			newIndices.setArray( newIndexBuffer );
+
+		}
+
+		result.setIndex( newIndices );
+
+		return result;
+
 	}
 
 };

+ 27 - 28
examples/misc_boxselection.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js webgl - draggable cubes</title>
+		<title>three.js webgl - box selection</title>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<style>
@@ -11,7 +11,7 @@
 				margin: 0px;
 				overflow: hidden;
 			}
-			
+
 			.selectBox {
 				border: 1px solid #55aaff;
 				background-color: rgba(75, 160, 255, 0.3);
@@ -23,15 +23,14 @@
 
 		<script src="../build/three.js"></script>
 		<script src="js/libs/stats.min.js"></script>
-		
+
 		<script src="js/interactive/SelectionBox.js"></script>
 		<script src="js/interactive/SelectionHelper.js"></script>
 
 		<script>
 
 			var container, stats;
-			var camera, controls, scene, renderer;
-			var objects = [];
+			var camera, scene, renderer;
 
 			init();
 			animate();
@@ -84,8 +83,6 @@
 
 					scene.add( object );
 
-					objects.push( object );
-
 				}
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
@@ -137,66 +134,68 @@
 				renderer.render( scene, camera );
 
 			}
-			
-			var selectionBox = new SelectionBox( camera, scene );
-    		var helper = new SelectionHelper( selectionBox, renderer, "selectBox" );
 
-			document.addEventListener("mousedown", function ( event ) {
+			var selectionBox = new THREE.SelectionBox( camera, scene );
+			var helper = new THREE.SelectionHelper( selectionBox, renderer, 'selectBox' );
+
+			document.addEventListener( 'mousedown', function ( event ) {
 
 				for ( var item of selectionBox.collection ) {
 
 					item.material.emissive = new THREE.Color( 0x000000 );
 
 				}
-			
+
 				selectionBox.startPoint.set(
 					( event.clientX / window.innerWidth ) * 2 - 1,
-					-( event.clientY / window.innerHeight ) * 2 + 1,
-					 0.5 );
-    		} );
+					- ( event.clientY / window.innerHeight ) * 2 + 1,
+					0.5 );
 
-    		document.addEventListener( "mousemove", function ( event ) {
+			} );
+
+			document.addEventListener( 'mousemove', function ( event ) {
 
 				if ( helper.isDown ) {
 
-					for ( var i = 0; i < selectionBox.collection.length; i++ ) {
+					for ( var i = 0; i < selectionBox.collection.length; i ++ ) {
 
-						selectionBox.collection[i].material.emissive = new THREE.Color( 0x000000 );
+						selectionBox.collection[ i ].material.emissive = new THREE.Color( 0x000000 );
 
 					}
 
 					selectionBox.endPoint.set(
 						( event.clientX / window.innerWidth ) * 2 - 1,
-						-( event.clientY / window.innerHeight ) * 2 + 1,
+						- ( event.clientY / window.innerHeight ) * 2 + 1,
 						0.5 );
 
 					var allSelected = selectionBox.select();
 
-					for ( var i = 0; i < allSelected.length; i++ ) {
+					for ( var i = 0; i < allSelected.length; i ++ ) {
 
-						allSelected[i].material.emissive = new THREE.Color( 0x0000ff );
+						allSelected[ i ].material.emissive = new THREE.Color( 0x0000ff );
 
 					}
+
 				}
 
 			} );
 
-			document.addEventListener("mouseup", function ( event ) {
-				
+			document.addEventListener( 'mouseup', function ( event ) {
+
 				selectionBox.endPoint.set(
-					(event.clientX / window.innerWidth) * 2 - 1,
-					-(event.clientY / window.innerHeight) * 2 + 1,
+					( event.clientX / window.innerWidth ) * 2 - 1,
+					- ( event.clientY / window.innerHeight ) * 2 + 1,
 					0.5 );
 
 				var allSelected = selectionBox.select();
 
-				for ( var i = 0; i < allSelected.length; i++ ) {
+				for ( var i = 0; i < allSelected.length; i ++ ) {
 
-					allSelected[i].material.emissive = new THREE.Color(0x0000ff);
+					allSelected[ i ].material.emissive = new THREE.Color( 0x0000ff );
 
 				}
 
-			});
+			} );
 
 		</script>
 

+ 189 - 0
examples/webgl2_multisampled_renderbuffers.html

@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js WebGL 2 - Multisampled Renderbuffers</title>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			html, body {
+				height: 100%;
+			}
+
+			body {
+				background: #ffffff;
+				padding: 0;
+				margin: 0;
+				font-family: Monospace;
+				font-size: 13px;
+				overflow: hidden;
+			}
+
+			#info {
+				top: 0px;
+				width: 100%;
+				color: #000000;
+				margin: 6px 0px;
+				text-align: center;
+			}
+
+			#info a {
+				color: #000000;
+			}
+
+			#container {
+				width: 100%;
+				height: calc(100% - 80px);
+			}
+		</style>
+	</head>
+
+	<body>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Multisampled Renderbuffers<br />
+			Left scene is multi-sampled, right scene is rendered without anti-aliasing.
+		</div>
+		<div id="container">
+		</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/WebGL.js"></script>
+
+		<script src="js/shaders/CopyShader.js"></script>
+
+		<script src="js/postprocessing/EffectComposer.js"></script>
+		<script src="js/postprocessing/RenderPass.js"></script>
+		<script src="js/postprocessing/ShaderPass.js"></script>
+
+		<script>
+
+			if ( WEBGL.isWebGL2Available() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
+
+			}
+
+			var camera, scene, renderer, clock, group;
+
+			var composer1, composer2;
+
+			init();
+			animate();
+
+			function init() {
+
+				var container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 45, ( window.innerWidth * 0.5 ) / window.innerHeight, 1, 2000 );
+				camera.position.z = 500;
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xffffff );
+				scene.fog = new THREE.Fog( 0xcccccc, 100, 1500 );
+
+				clock = new THREE.Clock();
+
+				//
+
+				var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
+				hemiLight.position.set( 0, 1000, 0 );
+				scene.add( hemiLight );
+
+				var dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
+				dirLight.position.set( - 3000, 1000, - 1000 );
+				scene.add( dirLight );
+
+				//
+
+				group = new THREE.Group();
+
+				var geometry = new THREE.IcosahedronBufferGeometry( 10, 2 );
+				var material = new THREE.MeshStandardMaterial( { color: 0xee0808, flatShading: true } );
+
+				for ( var i = 0; i < 100; i ++ ) {
+
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.position.x = Math.random() * 500 - 250;
+					mesh.position.y = Math.random() * 500 - 250;
+					mesh.position.z = Math.random() * 500 - 250;
+					mesh.scale.setScalar( Math.random() * 2 + 1 );
+					group.add( mesh );
+
+				}
+
+				scene.add( group );
+
+				//
+
+				var canvas = document.createElement( 'canvas' );
+				var context = canvas.getContext( 'webgl2', { antialias: false } );
+
+				renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
+				renderer.autoClear = false;
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				//
+
+				var parameters = {
+					format: THREE.RGBFormat,
+					stencilBuffer: false
+				};
+
+				var size = renderer.getDrawingBufferSize();
+				var renderTarget = new THREE.WebGLMultisampleRenderTarget( size.width, size.height, parameters );
+
+				var renderPass = new THREE.RenderPass( scene, camera );
+				var copyPass = new THREE.ShaderPass( THREE.CopyShader );
+				copyPass.renderToScreen = true;
+
+				//
+
+				composer1 = new THREE.EffectComposer( renderer, renderTarget );
+				composer1.addPass( renderPass );
+				composer1.addPass( copyPass );
+
+				//
+
+				composer2 = new THREE.EffectComposer( renderer );
+				composer2.addPass( renderPass );
+				composer2.addPass( copyPass );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = ( window.innerWidth * 0.5 ) / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				composer1.setSize( window.innerWidth, window.innerHeight );
+				composer2.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				var halfWidth = window.innerWidth / 2;
+
+				group.rotation.y += clock.getDelta() * 0.1;
+
+				renderer.setViewport( 0, 0, halfWidth, window.innerHeight );
+
+				composer1.render();
+
+				renderer.setViewport( halfWidth, 0, halfWidth, window.innerHeight );
+
+				composer2.render();
+
+			}
+
+		</script>
+	</body>
+</html>

+ 0 - 1
examples/webgl_loader_ctm_materials.html

@@ -231,7 +231,6 @@
 						mm.specularMap = m.map;
 						mm.shininess = 30;
 						mm.color.setHex( 0x404040 );
-						mm.metal = true;
 
 						materials[ i ] = mm;
 

+ 8 - 46
examples/webgl_materials_bumpmap_skin.html

@@ -64,16 +64,10 @@
 
 			}
 
-			var statsEnabled = true;
-
-			var container, stats, loader;
-
-			var camera, scene, renderer;
+			var camera, scene, renderer, stats;
 
 			var mesh;
 
-			var directionalLight;
-
 			var mouseX = 0;
 			var mouseY = 0;
 
@@ -91,7 +85,7 @@
 
 			function init() {
 
-				container = document.createElement( 'div' );
+				var container = document.createElement( 'div' );
 				document.body.appendChild( container );
 
 				//
@@ -106,29 +100,14 @@
 
 				scene.add( new THREE.AmbientLight( 0x333344 ) );
 
-				directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
+				var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
 				directionalLight.position.set( 500, 0, 500 );
 
-				directionalLight.castShadow = true;
-
-				directionalLight.shadow.mapSize.width = 2048;
-				directionalLight.shadow.mapSize.height = 2048;
-
-				directionalLight.shadow.camera.near = 200;
-				directionalLight.shadow.camera.far = 1500;
-
-				directionalLight.shadow.camera.left = - 500;
-				directionalLight.shadow.camera.right = 500;
-				directionalLight.shadow.camera.top = 500;
-				directionalLight.shadow.camera.bottom = - 500;
-
-				directionalLight.shadow.bias = - 0.005;
-
 				scene.add( directionalLight );
 
 				//
 
-				loader = new THREE.GLTFLoader();
+				var loader = new THREE.GLTFLoader();
 				loader.load( "models/gltf/LeePerrySmith/LeePerrySmith.glb", function ( gltf ) {
 
 					createScene( gltf.scene.children[ 0 ].geometry, 100 );
@@ -141,29 +120,14 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				container.appendChild( renderer.domElement );
-
-				renderer.shadowMap.enabled = true;
-
 				renderer.autoClear = false;
-
-				//
-
-				renderer.gammaInput = true;
 				renderer.gammaOutput = true;
 
 				//
 
-				if ( statsEnabled ) {
-
-					stats = new Stats();
-					container.appendChild( stats.dom );
-
-				}
-
-
-				// COMPOSER
+				stats = new Stats();
+				container.appendChild( stats.dom );
 
-				renderer.autoClear = false;
 
 				// BECKMANN
 
@@ -240,9 +204,6 @@
 				mesh.position.y = - 50;
 				mesh.scale.set( scale, scale, scale );
 
-				mesh.castShadow = true;
-				mesh.receiveShadow = true;
-
 				scene.add( mesh );
 
 			}
@@ -272,7 +233,8 @@
 				requestAnimationFrame( animate );
 
 				render();
-				if ( statsEnabled ) stats.update();
+
+				stats.update();
 
 			}
 

+ 1 - 0
examples/webgl_panorama_dualfisheye.html

@@ -99,6 +99,7 @@
 				//
 
 				var texture = new THREE.TextureLoader().load( 'textures/ricoh_theta_s.jpg' );
+				texture.minFilter = THREE.LinearFilter;
 				texture.format = THREE.RGBFormat;
 
 				var material = new THREE.MeshBasicMaterial( { map: texture } );

+ 6 - 46
examples/webgl_physics_volume.html

@@ -31,6 +31,7 @@
 		<script src="../build/three.js"></script>
 		<script src="js/libs/ammo.js"></script>
 		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/utils/BufferGeometryUtils.js"></script>
 		<script src="js/WebGL.js"></script>
 		<script src="js/libs/stats.min.js"></script>
 
@@ -190,60 +191,19 @@
 
 			function processGeometry( bufGeometry ) {
 
-				// Obtain a Geometry
-				var geometry = new THREE.Geometry().fromBufferGeometry( bufGeometry );
+				// Ony consider the position values when merging the vertices
+				var posOnlyBufGeometry = new THREE.BufferGeometry();
+				posOnlyBufGeometry.addAttribute( 'position', bufGeometry.getAttribute( 'position' ) );
+				posOnlyBufGeometry.setIndex( bufGeometry.getIndex() );
 
 				// Merge the vertices so the triangle soup is converted to indexed triangles
-				geometry.mergeVertices();
-
-				// Convert again to BufferGeometry, indexed
-				var indexedBufferGeom = createIndexedBufferGeometryFromGeometry( geometry );
+				var indexedBufferGeom = THREE.BufferGeometryUtils.mergeVertices( posOnlyBufGeometry );
 
 				// Create index arrays mapping the indexed vertices to bufGeometry vertices
 				mapIndices( bufGeometry, indexedBufferGeom );
 
 			}
 
-			function createIndexedBufferGeometryFromGeometry( geometry ) {
-
-				var numVertices = geometry.vertices.length;
-				var numFaces = geometry.faces.length;
-
-				var bufferGeom = new THREE.BufferGeometry();
-				var vertices = new Float32Array( numVertices * 3 );
-				var indices = new ( numFaces * 3 > 65535 ? Uint32Array : Uint16Array )( numFaces * 3 );
-
-				for ( var i = 0; i < numVertices; i ++ ) {
-
-					var p = geometry.vertices[ i ];
-
-					var i3 = i * 3;
-
-					vertices[ i3 ] = p.x;
-					vertices[ i3 + 1 ] = p.y;
-					vertices[ i3 + 2 ] = p.z;
-
-				}
-
-				for ( var i = 0; i < numFaces; i ++ ) {
-
-					var f = geometry.faces[ i ];
-
-					var i3 = i * 3;
-
-					indices[ i3 ] = f.a;
-					indices[ i3 + 1 ] = f.b;
-					indices[ i3 + 2 ] = f.c;
-
-				}
-
-				bufferGeom.setIndex( new THREE.BufferAttribute( indices, 1 ) );
-				bufferGeom.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
-
-				return bufferGeom;
-
-			}
-
 			function isEqual( x1, y1, z1, x2, y2, z2 ) {
 
 				var delta = 0.000001;

+ 1 - 0
examples/webgl_postprocessing_masking.html

@@ -74,6 +74,7 @@
 				var maskPass2 = new THREE.MaskPass( scene2, camera );
 
 				var texture1 = new THREE.TextureLoader().load( 'textures/758px-Canestra_di_frutta_(Caravaggio).jpg' );
+				texture1.minFilter = THREE.LinearFilter;
 				var texture2 = new THREE.TextureLoader().load( 'textures/2294472375_24a3b8ef46_o.jpg' );
 
 				var texturePass1 = new THREE.TexturePass( texture1 );

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "build": "rollup -c",
     "build-test": "rollup -c test/rollup.unit.config.js",
     "build-closure": "rollup -c && java -jar node_modules/google-closure-compiler-java/compiler.jar --warning_level=VERBOSE --jscomp_off=globalThis --jscomp_off=checkTypes --externs utils/build/externs.js --language_in=ECMASCRIPT5_STRICT --js build/three.js --js_output_file build/three.min.js",
+    "build-examples": "rollup -c rollup-examples.config.js",
     "dev": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c -w -m inline\" \"http-server -c-1 -p 8080\"",
     "dev-test": "concurrently --names \"ROLLUP,ROLLUPTEST,HTTP\" -c \"bgBlue.bold,bgRed.bold,bgGreen.bold\" \"rollup -c -w -m inline\" \"rollup -c test/rollup.unit.config.js -w -m inline\" \"http-server -p 8080\"",
     "start": "npm run dev",

+ 85 - 0
rollup-examples.config.js

@@ -0,0 +1,85 @@
+var path = require( 'path' );
+var fs = require( 'fs' );
+
+// Creates a rollup config object for the given file to
+// be converted to umd
+function createOutput( file ) {
+
+	var inputPath = path.resolve( file );
+	var outputPath = inputPath.replace( /[\\\/]examples[\\\/]jsm[\\\/]/, '/examples/js/' );
+
+	// Every import is marked as external so the output is 1-to-1. We
+	// assume that that global object should be the THREE object so we
+	// replicate the existing behavior.
+	return {
+
+		input: inputPath,
+		treeshake: false,
+		external: p => p !== inputPath,
+
+		plugins: [ {
+
+			generateBundle: function ( options, bundle ) {
+
+				for ( var key in bundle ) {
+
+					bundle[ key ].code = bundle[ key ].code.replace( /three_module_js/g, 'THREE' );
+
+				}
+
+			}
+
+		} ],
+
+		output: {
+
+			format: 'umd',
+			name: 'THREE',
+			file: outputPath,
+
+			globals: () => 'THREE',
+			paths: p => /three\.module\.js$/.test( p ) ? 'three' : p,
+			extend: true,
+
+			banner:
+				'/**\n' +
+				` * Generated from '${ path.relative( '.', inputPath ).replace( /\\/g, '/' ) }'\n` +
+				' */\n',
+			esModule: false
+
+		}
+
+	};
+
+}
+
+// Walk the file structure starting at the given directory and fire
+// the callback for every js file.
+function walk( dir, cb ) {
+
+	var files = fs.readdirSync( dir );
+	files.forEach( f => {
+
+		var p = path.join( dir, f );
+		var stats = fs.statSync( p );
+
+		if ( stats.isDirectory() ) {
+
+			walk( p, cb );
+
+		} else if ( f.endsWith( '.js' ) ) {
+
+			cb( p );
+
+		}
+
+	} );
+
+}
+
+// Gather up all the files
+var files = [];
+walk( 'examples/jsm/', p => files.push( p ) );
+
+// Create a rollup config for each module.js file
+export default files.map( p => createOutput( p ) );

+ 7 - 3
rollup.config.js

@@ -129,7 +129,10 @@ function glconstants() {
 		MAX_VARYING_VECTORS: 36348,
 		MAX_FRAGMENT_UNIFORM_VECTORS: 36349,
 		UNPACK_FLIP_Y_WEBGL: 37440,
-		UNPACK_PREMULTIPLY_ALPHA_WEBGL: 37441
+		UNPACK_PREMULTIPLY_ALPHA_WEBGL: 37441,
+		MAX_SAMPLES: 36183,
+		READ_FRAMEBUFFER: 36008,
+		DRAW_FRAMEBUFFER: 36009
 	};
 
 	return {
@@ -147,10 +150,11 @@ function glconstants() {
 			return {
 				code: code,
 				map: { mappings: '' }
-			}
+			};
+
 		}
 
-	}
+	};
 
 }
 

+ 1 - 0
src/Three.js

@@ -1,5 +1,6 @@
 import './polyfills.js';
 
+export { WebGLMultisampleRenderTarget } from './renderers/WebGLMultisampleRenderTarget.js';
 export { WebGLRenderTargetCube } from './renderers/WebGLRenderTargetCube.js';
 export { WebGLRenderTarget } from './renderers/WebGLRenderTarget.js';
 export { WebGLRenderer } from './renderers/WebGLRenderer.js';

+ 6 - 2
src/cameras/CubeCamera.js

@@ -58,6 +58,8 @@ function CubeCamera( near, far, cubeResolution, options ) {
 
 		if ( this.parent === null ) this.updateMatrixWorld();
 
+		var currentRenderTarget = renderer.getRenderTarget();
+
 		var renderTarget = this.renderTarget;
 		var generateMipmaps = renderTarget.texture.generateMipmaps;
 
@@ -83,12 +85,14 @@ function CubeCamera( near, far, cubeResolution, options ) {
 		renderTarget.activeCubeFace = 5;
 		renderer.render( scene, cameraNZ, renderTarget );
 
-		renderer.setRenderTarget( null );
+		renderer.setRenderTarget( currentRenderTarget );
 
 	};
 
 	this.clear = function ( renderer, color, depth, stencil ) {
 
+		var currentRenderTarget = renderer.getRenderTarget();
+
 		var renderTarget = this.renderTarget;
 
 		for ( var i = 0; i < 6; i ++ ) {
@@ -100,7 +104,7 @@ function CubeCamera( near, far, cubeResolution, options ) {
 
 		}
 
-		renderer.setRenderTarget( null );
+		renderer.setRenderTarget( currentRenderTarget );
 
 	};
 

+ 47 - 10
src/core/BufferGeometry.js

@@ -848,6 +848,33 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
 	toNonIndexed: function () {
 
+		function convertBufferAttribute( attribute, indices ) {
+
+			var array = attribute.array;
+			var itemSize = attribute.itemSize;
+
+			var array2 = new array.constructor( indices.length * itemSize );
+
+			var index = 0, index2 = 0;
+
+			for ( var i = 0, l = indices.length; i < l; i ++ ) {
+
+				index = indices[ i ] * itemSize;
+
+				for ( var j = 0; j < itemSize; j ++ ) {
+
+					array2[ index2 ++ ] = array[ index ++ ];
+
+				}
+
+			}
+
+			return new BufferAttribute( array2, itemSize );
+
+		}
+
+		//
+
 		if ( this.index === null ) {
 
 			console.warn( 'THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.' );
@@ -860,33 +887,43 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 		var indices = this.index.array;
 		var attributes = this.attributes;
 
+		// attributes
+
 		for ( var name in attributes ) {
 
 			var attribute = attributes[ name ];
 
-			var array = attribute.array;
-			var itemSize = attribute.itemSize;
+			var newAttribute = convertBufferAttribute( attribute, indices );
 
-			var array2 = new array.constructor( indices.length * itemSize );
+			geometry2.addAttribute( name, newAttribute );
 
-			var index = 0, index2 = 0;
+		}
 
-			for ( var i = 0, l = indices.length; i < l; i ++ ) {
+		// morph attributes
 
-				index = indices[ i ] * itemSize;
+		var morphAttributes = this.morphAttributes;
 
-				for ( var j = 0; j < itemSize; j ++ ) {
+		for ( name in morphAttributes ) {
 
-					array2[ index2 ++ ] = array[ index ++ ];
+			var morphArray = [];
+			var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
 
-				}
+			for ( var i = 0, il = morphAttribute.length; i < il; i ++ ) {
+
+				var attribute = morphAttribute[ i ];
+
+				var newAttribute = convertBufferAttribute( attribute, indices );
+
+				morphArray.push( newAttribute );
 
 			}
 
-			geometry2.addAttribute( name, new BufferAttribute( array2, itemSize ) );
+			geometry2.morphAttributes[ name ] = morphArray;
 
 		}
 
+		// groups
+
 		var groups = this.groups;
 
 		for ( var i = 0, l = groups.length; i < l; i ++ ) {

+ 2 - 2
src/helpers/ArrowHelper.js

@@ -32,8 +32,8 @@ function ArrowHelper( dir, origin, length, color, headLength, headWidth ) {
 
 	Object3D.call( this );
 
-	if ( dir === undefined ) dir = new THREE.Vector3( 0, 0, 1 );
-	if ( origin === undefined ) origin = new THREE.Vector3( 0, 0, 0 );
+	if ( dir === undefined ) dir = new Vector3( 0, 0, 1 );
+	if ( origin === undefined ) origin = new Vector3( 0, 0, 0 );
 	if ( length === undefined ) length = 1;
 	if ( color === undefined ) color = 0xffff00;
 	if ( headLength === undefined ) headLength = 0.2 * length;

+ 2 - 1
src/helpers/RectAreaLightHelper.js

@@ -12,6 +12,7 @@ import { LineBasicMaterial } from '../materials/LineBasicMaterial.js';
 import { MeshBasicMaterial } from '../materials/MeshBasicMaterial.js';
 import { Float32BufferAttribute } from '../core/BufferAttribute.js';
 import { BufferGeometry } from '../core/BufferGeometry.js';
+import { BackSide } from '../constants.js';
 
 function RectAreaLightHelper( light, color ) {
 
@@ -39,7 +40,7 @@ function RectAreaLightHelper( light, color ) {
 	geometry2.addAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) );
 	geometry2.computeBoundingSphere();
 
-	this.add( new Mesh( geometry2, new MeshBasicMaterial( { side: THREE.BackSide, fog: false } ) ) );
+	this.add( new Mesh( geometry2, new MeshBasicMaterial( { side: BackSide, fog: false } ) ) );
 
 	this.update();
 

+ 4 - 0
src/loaders/MaterialLoader.js

@@ -2,6 +2,7 @@ import { Color } from '../math/Color.js';
 import { Vector2 } from '../math/Vector2.js';
 import { Vector3 } from '../math/Vector3.js';
 import { Vector4 } from '../math/Vector4.js';
+import { Matrix3 } from '../math/Matrix3.js';
 import { Matrix4 } from '../math/Matrix4.js';
 import { FileLoader } from './FileLoader.js';
 import { DefaultLoadingManager } from './LoadingManager.js';
@@ -129,6 +130,9 @@ Object.assign( MaterialLoader.prototype, {
 						material.uniforms[ name ].value = new Vector4().fromArray( uniform.value );
 						break;
 
+					case 'm3':
+						material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value );
+
 					case 'm4':
 						material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value );
 						break;

+ 7 - 0
src/materials/ShaderMaterial.js

@@ -158,6 +158,13 @@ ShaderMaterial.prototype.toJSON = function ( meta ) {
 				value: value.toArray()
 			};
 
+		} else if ( value && value.isMatrix3 ) {
+
+			data.uniforms[ name ] = {
+				type: 'm3',
+				value: value.toArray()
+			};
+
 		} else if ( value && value.isMatrix4 ) {
 
 			data.uniforms[ name ] = {

+ 35 - 0
src/renderers/WebGLMultisampleRenderTarget.js

@@ -0,0 +1,35 @@
+import { WebGLRenderTarget } from './WebGLRenderTarget.js';
+
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ * @author Matt DesLauriers / @mattdesl
+ */
+
+function WebGLMultisampleRenderTarget( width, height, options ) {
+
+	WebGLRenderTarget.call( this, width, height, options );
+
+	this.samples = 4;
+
+}
+
+WebGLMultisampleRenderTarget.prototype = Object.assign( Object.create( WebGLRenderTarget.prototype ), {
+
+	constructor: WebGLMultisampleRenderTarget,
+
+	isWebGLMultisampleRenderTarget: true,
+
+	copy: function ( source ) {
+
+		WebGLRenderTarget.prototype.copy.call( this, source );
+
+		this.samples = source.samples;
+
+		return this;
+
+	}
+
+} );
+
+
+export { WebGLMultisampleRenderTarget };

+ 11 - 1
src/renderers/WebGLRenderer.js

@@ -1133,12 +1133,18 @@ function WebGLRenderer( parameters ) {
 
 		}
 
-		// Generate mipmap if we're using any kind of mipmap filtering
+		//
 
 		if ( renderTarget ) {
 
+			// Generate mipmap if we're using any kind of mipmap filtering
+
 			textures.updateRenderTargetMipmap( renderTarget );
 
+			// resolve multisample renderbuffers to a single-sample texture if necessary
+
+			textures.updateMultisampleRenderTarget( renderTarget );
+
 		}
 
 		// Ensure depth buffer writing is enabled so it can be cleared on next render
@@ -2494,6 +2500,10 @@ function WebGLRenderer( parameters ) {
 				framebuffer = __webglFramebuffer[ renderTarget.activeCubeFace ];
 				isCube = true;
 
+			} else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+				framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+
 			} else {
 
 				framebuffer = __webglFramebuffer;

+ 5 - 1
src/renderers/webgl/WebGLCapabilities.js

@@ -84,6 +84,8 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 	var floatFragmentTextures = isWebGL2 || !! extensions.get( 'OES_texture_float' );
 	var floatVertexTextures = vertexTextures && floatFragmentTextures;
 
+	var maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0;
+
 	return {
 
 		isWebGL2: isWebGL2,
@@ -106,7 +108,9 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 
 		vertexTextures: vertexTextures,
 		floatFragmentTextures: floatFragmentTextures,
-		floatVertexTextures: floatVertexTextures
+		floatVertexTextures: floatVertexTextures,
+
+		maxSamples: maxSamples
 
 	};
 

+ 12 - 0
src/renderers/webgl/WebGLRenderLists.js

@@ -152,6 +152,16 @@ function WebGLRenderLists() {
 
 	var lists = {};
 
+	function onSceneDispose( event ) {
+
+		var scene = event.target;
+
+		scene.removeEventListener( 'dispose', onSceneDispose );
+
+		delete lists[ scene.id ];
+
+	}
+
 	function get( scene, camera ) {
 
 		var cameras = lists[ scene.id ];
@@ -162,6 +172,8 @@ function WebGLRenderLists() {
 			lists[ scene.id ] = {};
 			lists[ scene.id ][ camera.id ] = list;
 
+			scene.addEventListener( 'dispose', onSceneDispose );
+
 		} else {
 
 			list = cameras[ camera.id ];

+ 12 - 0
src/renderers/webgl/WebGLRenderStates.js

@@ -58,6 +58,16 @@ function WebGLRenderStates() {
 
 	var renderStates = {};
 
+	function onSceneDispose( event ) {
+
+		var scene = event.target;
+
+		scene.removeEventListener( 'dispose', onSceneDispose );
+
+		delete renderStates[ scene.id ];
+
+	}
+
 	function get( scene, camera ) {
 
 		var renderState;
@@ -68,6 +78,8 @@ function WebGLRenderStates() {
 			renderStates[ scene.id ] = {};
 			renderStates[ scene.id ][ camera.id ] = renderState;
 
+			scene.addEventListener( 'dispose', onSceneDispose );
+
 		} else {
 
 			if ( renderStates[ scene.id ][ camera.id ] === undefined ) {

+ 2 - 4
src/renderers/webgl/WebGLShadowMap.js

@@ -3,7 +3,7 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
-import { FrontSide, BackSide, DoubleSide, RGBAFormat, NearestFilter, PCFShadowMap, RGBADepthPacking } from '../../constants.js';
+import { FrontSide, BackSide, DoubleSide, RGBAFormat, NearestFilter, PCFShadowMap, RGBADepthPacking, NoBlending } from '../../constants.js';
 import { WebGLRenderTarget } from '../WebGLRenderTarget.js';
 import { MeshDepthMaterial } from '../../materials/MeshDepthMaterial.js';
 import { MeshDistanceMaterial } from '../../materials/MeshDistanceMaterial.js';
@@ -100,12 +100,10 @@ function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
 
 		if ( lights.length === 0 ) return;
 
-		// TODO Clean up (needed in case of contextlost)
-		var _gl = _renderer.context;
 		var _state = _renderer.state;
 
 		// Set GL state for depth map.
-		_state.disable( _gl.BLEND );
+		_state.setBlending( NoBlending );
 		_state.buffers.color.setClear( 1, 1, 1, 1 );
 		_state.buffers.depth.setTest( true );
 		_state.setScissorTest( false );

+ 151 - 44
src/renderers/webgl/WebGLTextures.js

@@ -12,60 +12,55 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 	//
 
-	function clampToMaxSize( image, maxSize ) {
+	function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
 
-		if ( image.width > maxSize || image.height > maxSize ) {
+		var scale = 1;
 
-			if ( 'data' in image ) {
+		// handle case if texture exceeds max size
 
-				console.warn( 'THREE.WebGLRenderer: image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
-				return;
+		if ( image.width > maxSize || image.height > maxSize ) {
 
-			}
+			scale = maxSize / Math.max( image.width, image.height );
 
-			// Warning: Scaling through the canvas will only work with images that use
-			// premultiplied alpha.
+		}
 
-			var scale = maxSize / Math.max( image.width, image.height );
+		// only perform resize if necessary
 
-			var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
-			canvas.width = Math.floor( image.width * scale );
-			canvas.height = Math.floor( image.height * scale );
+		if ( scale < 1 || needsPowerOfTwo === true ) {
 
-			var context = canvas.getContext( '2d' );
-			context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height );
+			// only perform resize for certain image types
 
-			console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height );
+			if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) {
 
-			return canvas;
+				if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
 
-		}
+				// cube textures can't reuse the same canvas
 
-		return image;
+				var canvas = needsNewCanvas ? document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ) : _canvas;
 
-	}
+				var floor = needsPowerOfTwo ? _Math.floorPowerOfTwo : Math.floor;
 
-	function isPowerOfTwo( image ) {
+				canvas.width = floor( scale * image.width );
+				canvas.height = floor( scale * image.height );
 
-		return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height );
+				var context = canvas.getContext( '2d' );
+				context.drawImage( image, 0, 0, canvas.width, canvas.height );
 
-	}
+				console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + canvas.width + 'x' + canvas.height + ').' );
 
-	function makePowerOfTwo( image ) {
+				return canvas;
 
-		if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) {
+			} else {
 
-			if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+				if ( 'data' in image ) {
 
-			_canvas.width = _Math.floorPowerOfTwo( image.width );
-			_canvas.height = _Math.floorPowerOfTwo( image.height );
+					console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
 
-			var context = _canvas.getContext( '2d' );
-			context.drawImage( image, 0, 0, _canvas.width, _canvas.height );
+				}
 
-			console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + _canvas.width + 'x' + _canvas.height );
+				return image;
 
-			return _canvas;
+			}
 
 		}
 
@@ -73,6 +68,12 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 	}
 
+	function isPowerOfTwo( image ) {
+
+		return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height );
+
+	}
+
 	function textureNeedsPowerOfTwo( texture ) {
 
 		if ( capabilities.isWebGL2 ) return false;
@@ -343,7 +344,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 					if ( ! isCompressed && ! isDataTexture ) {
 
-						cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize );
+						cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, capabilities.maxCubemapSize );
 
 					} else {
 
@@ -535,13 +536,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 		_gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
 		_gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
 
-		var image = clampToMaxSize( texture.image, capabilities.maxTextureSize );
-
-		if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) {
-
-			image = makePowerOfTwo( image );
-
-		}
+		var needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( texture.image ) === false;
+		var image = resizeImage( texture.image, needsPowerOfTwo, false, capabilities.maxTextureSize );
 
 		var isPowerOfTwoImage = isPowerOfTwo( image ),
 			glFormat = utils.convert( texture.format ),
@@ -723,24 +719,60 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 	}
 
 	// Setup storage for internal depth/stencil buffers and bind to correct framebuffer
-	function setupRenderBufferStorage( renderbuffer, renderTarget ) {
+	function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
 
 		_gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer );
 
 		if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
 
-			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );
+			if ( isMultisample ) {
+
+				var samples = getRenderTargetSamples( renderTarget );
+
+				_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );
+
+			} else {
+
+				_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );
+
+			}
+
 			_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
 
 		} else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
 
-			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );
+			if ( isMultisample ) {
+
+				var samples = getRenderTargetSamples( renderTarget );
+
+				_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );
+
+			} else {
+
+				_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );
+
+			}
+
+
 			_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
 
 		} else {
 
-			// FIXME: We don't support !depth !stencil
-			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height );
+			var glFormat = utils.convert( renderTarget.texture.format );
+			var glType = utils.convert( renderTarget.texture.type );
+			var glInternalFormat = getInternalFormat( glFormat, glType );
+
+			if ( isMultisample ) {
+
+				var samples = getRenderTargetSamples( renderTarget );
+
+				_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+			} else {
+
+				_gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height );
+
+			}
 
 		}
 
@@ -847,6 +879,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 		info.memory.textures ++;
 
 		var isCube = ( renderTarget.isWebGLRenderTargetCube === true );
+		var isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
 		var isTargetPowerOfTwo = isPowerOfTwo( renderTarget );
 
 		// Setup framebuffer
@@ -865,6 +898,42 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
 
+			if ( isMultisample ) {
+
+				if ( capabilities.isWebGL2 ) {
+
+					renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+					renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+
+					_gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer );
+					var glFormat = utils.convert( renderTarget.texture.format );
+					var glType = utils.convert( renderTarget.texture.type );
+					var glInternalFormat = getInternalFormat( glFormat, glType );
+					var samples = getRenderTargetSamples( renderTarget );
+					_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+					_gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
+					_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer );
+					_gl.bindRenderbuffer( _gl.RENDERBUFFER, null );
+
+					if ( renderTarget.depthBuffer ) {
+
+						renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
+						setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
+
+					}
+
+					_gl.bindFramebuffer( _gl.FRAMEBUFFER, null );
+
+
+				} else {
+
+					console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+				}
+
+			}
+
 		}
 
 		// Setup color buffer
@@ -932,6 +1001,43 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 	}
 
+	function updateMultisampleRenderTarget( renderTarget ) {
+
+		if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+			if ( capabilities.isWebGL2 ) {
+
+				var renderTargetProperties = properties.get( renderTarget );
+
+				_gl.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
+				_gl.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );
+
+				var width = renderTarget.width;
+				var height = renderTarget.height;
+				var mask = _gl.COLOR_BUFFER_BIT;
+
+				if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT;
+				if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT;
+
+				_gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST );
+
+			} else {
+
+				console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+			}
+
+		}
+
+	}
+
+	function getRenderTargetSamples( renderTarget ) {
+
+		return ( capabilities.isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
+			Math.min( capabilities.maxSamples, renderTarget.samples ) : 0;
+
+	}
+
 	function updateVideoTexture( texture ) {
 
 		var id = texture.id;
@@ -954,6 +1060,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 	this.setTextureCubeDynamic = setTextureCubeDynamic;
 	this.setupRenderTarget = setupRenderTarget;
 	this.updateRenderTargetMipmap = updateRenderTargetMipmap;
+	this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
 
 }
 

+ 6 - 0
src/scenes/Scene.js

@@ -46,6 +46,12 @@ Scene.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
 		return data;
 
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
 	}
 
 } );

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