Browse Source

Merge branch 'dev' into dev_ior

WestLangley 4 years ago
parent
commit
bcfa3339ed
48 changed files with 1117 additions and 1473 deletions
  1. 92 69
      build/three.js
  2. 0 613
      build/three.min.js
  3. 87 68
      build/three.module.js
  4. 0 5
      docs/api/en/lights/AmbientLight.html
  5. 0 6
      docs/api/en/lights/HemisphereLight.html
  6. 3 3
      docs/api/en/materials/Material.html
  7. 2 2
      docs/api/en/renderers/WebGLRenderer.html
  8. 1 1
      editor/js/Sidebar.Object.js
  9. 3 0
      examples/js/controls/OrbitControls.js
  10. 3 0
      examples/js/controls/TrackballControls.js
  11. 1 0
      examples/js/controls/TransformControls.js
  12. 84 36
      examples/js/loaders/GLTFLoader.js
  13. 3 0
      examples/jsm/controls/OrbitControls.js
  14. 3 0
      examples/jsm/controls/TrackballControls.js
  15. 1 0
      examples/jsm/controls/TransformControls.js
  16. 84 36
      examples/jsm/loaders/GLTFLoader.js
  17. 57 66
      examples/jsm/renderers/webgpu/WebGPUBindings.js
  18. 40 23
      examples/jsm/renderers/webgpu/WebGPURenderPipelines.js
  19. 2 2
      examples/jsm/renderers/webgpu/WebGPURenderer.js
  20. 129 0
      examples/jsm/renderers/webgpu/WebGPUUniform.js
  21. 57 57
      examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js
  22. 7 12
      examples/jsm/webxr/ARButton.js
  23. 8 7
      examples/jsm/webxr/VRButton.js
  24. 4 3
      examples/misc_controls_pointerlock.html
  25. 11 11
      examples/webgl_camera_logarithmicdepthbuffer.html
  26. 300 322
      package-lock.json
  27. 1 2
      package.json
  28. 1 1
      src/core/Face3.d.ts
  29. 0 14
      src/core/GLBufferAttribute.js
  30. 0 2
      src/core/Geometry.d.ts
  31. 0 2
      src/core/Object3D.d.ts
  32. 81 46
      src/geometries/EdgesGeometry.js
  33. 0 4
      src/lights/AmbientLight.d.ts
  34. 0 2
      src/lights/AmbientLight.js
  35. 0 5
      src/lights/HemisphereLight.d.ts
  36. 0 2
      src/lights/HemisphereLight.js
  37. 0 4
      src/lights/Light.d.ts
  38. 0 2
      src/lights/Light.js
  39. 4 1
      src/loaders/ImageBitmapLoader.js
  40. 0 3
      src/materials/Material.d.ts
  41. 0 10
      src/materials/ShaderMaterial.d.ts
  42. 0 20
      src/math/Vector2.d.ts
  43. 1 3
      src/objects/Points.d.ts
  44. 1 1
      src/renderers/webgl/WebGLShadowMap.d.ts
  45. 2 1
      src/renderers/webxr/WebXRManager.d.ts
  46. 0 3
      src/textures/Texture.d.ts
  47. 1 1
      test/unit/src/geometries/EdgesGeometry.tests.js
  48. 43 2
      utils/build/rollup.config.js

+ 92 - 69
build/three.js

@@ -1,3 +1,4 @@
+// threejs.org/license
 (function (global, factory) {
 	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
 	typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -32476,7 +32477,12 @@
 
 	}
 
-	function EdgesGeometry( geometry, thresholdAngle ) {
+	var _v0$2 = new Vector3();
+	var _v1$5 = new Vector3();
+	var _normal$1 = new Vector3();
+	var _triangle = new Triangle();
+
+		function EdgesGeometry( geometry, thresholdAngle ) {
 
 			BufferGeometry.call(this);
 
@@ -32488,89 +32494,123 @@
 
 			thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;
 
-			// buffer
+			if ( geometry.isGeometry ) {
 
-			var vertices = [];
+				geometry = new BufferGeometry().fromGeometry( geometry );
 
-			// helper variables
+			}
 
+			var precisionPoints = 4;
+			var precision = Math.pow( 10, precisionPoints );
 			var thresholdDot = Math.cos( MathUtils.DEG2RAD * thresholdAngle );
-			var edge = [ 0, 0 ], edges = {};
-			var edge1, edge2, key;
-			var keys = [ 'a', 'b', 'c' ];
 
-			// prepare source geometry
+			var indexAttr = geometry.getIndex();
+			var positionAttr = geometry.getAttribute( 'position' );
+			var indexCount = indexAttr ? indexAttr.count : positionAttr.count;
 
-			var geometry2;
+			var indexArr = [ 0, 0, 0 ];
+			var vertKeys = [ 'a', 'b', 'c' ];
+			var hashes = new Array( 3 );
 
-			if ( geometry.isBufferGeometry ) {
+			var edgeData = {};
+			var vertices = [];
+			for ( var i = 0; i < indexCount; i += 3 ) {
 
-				geometry2 = new Geometry();
-				geometry2.fromBufferGeometry( geometry );
+				if ( indexAttr ) {
 
-			} else {
+					indexArr[ 0 ] = indexAttr.getX( i );
+					indexArr[ 1 ] = indexAttr.getX( i + 1 );
+					indexArr[ 2 ] = indexAttr.getX( i + 2 );
 
-				geometry2 = geometry.clone();
+				} else {
 
-			}
+					indexArr[ 0 ] = i;
+					indexArr[ 1 ] = i + 1;
+					indexArr[ 2 ] = i + 2;
 
-			geometry2.mergeVertices();
-			geometry2.computeFaceNormals();
+				}
 
-			var sourceVertices = geometry2.vertices;
-			var faces = geometry2.faces;
+				var a = _triangle.a;
+				var b = _triangle.b;
+				var c = _triangle.c;
+				a.fromBufferAttribute( positionAttr, indexArr[ 0 ] );
+				b.fromBufferAttribute( positionAttr, indexArr[ 1 ] );
+				c.fromBufferAttribute( positionAttr, indexArr[ 2 ] );
+				_triangle.getNormal( _normal$1 );
 
-			// now create a data structure where each entry represents an edge with its adjoining faces
+				// create hashes for the edge from the vertices
+				hashes[ 0 ] = (Math.round( a.x * precision )) + "," + (Math.round( a.y * precision )) + "," + (Math.round( a.z * precision ));
+				hashes[ 1 ] = (Math.round( b.x * precision )) + "," + (Math.round( b.y * precision )) + "," + (Math.round( b.z * precision ));
+				hashes[ 2 ] = (Math.round( c.x * precision )) + "," + (Math.round( c.y * precision )) + "," + (Math.round( c.z * precision ));
 
-			for ( var i = 0, l = faces.length; i < l; i ++ ) {
+				// skip degenerate triangles
+				if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) {
+
+					continue;
 
-				var face = faces[ i ];
+				}
 
+				// iterate over every edge
 				for ( var j = 0; j < 3; j ++ ) {
 
-					edge1 = face[ keys[ j ] ];
-					edge2 = face[ keys[ ( j + 1 ) % 3 ] ];
-					edge[ 0 ] = Math.min( edge1, edge2 );
-					edge[ 1 ] = Math.max( edge1, edge2 );
+					// get the first and next vertex making up the edge
+					var jNext = ( j + 1 ) % 3;
+					var vecHash0 = hashes[ j ];
+					var vecHash1 = hashes[ jNext ];
+					var v0 = _triangle[ vertKeys[ j ] ];
+					var v1 = _triangle[ vertKeys[ jNext ] ];
 
-					key = edge[ 0 ] + ',' + edge[ 1 ];
+					var hash = vecHash0 + "_" + vecHash1;
+					var reverseHash = vecHash1 + "_" + vecHash0;
 
-					if ( edges[ key ] === undefined ) {
+					if ( reverseHash in edgeData && edgeData[ reverseHash ] ) {
 
-						edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };
+						// if we found a sibling edge add it into the vertex array if
+						// it meets the angle threshold and delete the edge from the map.
+						if ( _normal$1.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) {
 
-					} else {
+							vertices.push( v0.x, v0.y, v0.z );
+							vertices.push( v1.x, v1.y, v1.z );
 
-						edges[ key ].face2 = i;
+						}
 
-					}
+						edgeData[ reverseHash ] = null;
 
-				}
+					} else if ( ! ( hash in edgeData ) ) {
 
-			}
+						// if we've already got an edge here then skip adding a new one
+						edgeData[ hash ] = {
+
+							index0: indexArr[ j ],
+							index1: indexArr[ jNext ],
+							normal: _normal$1.clone(),
 
-			// generate vertices
+						};
+
+					}
 
-			for ( key in edges ) {
+				}
 
-				var e = edges[ key ];
+			}
 
-				// an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.
+			// iterate over all remaining, unmatched edges and add them to the vertex array
+			for ( var key in edgeData ) {
 
-				if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {
+				if ( edgeData[ key ] ) {
 
-					var vertex = sourceVertices[ e.index1 ];
-					vertices.push( vertex.x, vertex.y, vertex.z );
+					var ref = edgeData[ key ];
+					var index0 = ref.index0;
+					var index1 = ref.index1;
+					_v0$2.fromBufferAttribute( positionAttr, index0 );
+					_v1$5.fromBufferAttribute( positionAttr, index1 );
 
-					vertex = sourceVertices[ e.index2 ];
-					vertices.push( vertex.x, vertex.y, vertex.z );
+					vertices.push( _v0$2.x, _v0$2.y, _v0$2.z );
+					vertices.push( _v1$5.x, _v1$5.y, _v1$5.z );
 
 				}
 
 			}
 
-			// build geometry
-
 			this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
 
 		}
@@ -38977,8 +39017,6 @@
 		this.color = new Color( color );
 		this.intensity = intensity !== undefined ? intensity : 1;
 
-		this.receiveShadow = undefined;
-
 	}
 
 	Light.prototype = Object.assign( Object.create( Object3D.prototype ), {
@@ -39026,8 +39064,6 @@
 
 		this.type = 'HemisphereLight';
 
-		this.castShadow = undefined;
-
 		this.position.copy( Object3D.DefaultUp );
 		this.updateMatrix();
 
@@ -39610,8 +39646,6 @@
 
 		this.type = 'AmbientLight';
 
-		this.castShadow = undefined;
-
 	}
 
 	AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {
@@ -41607,7 +41641,10 @@
 
 			}
 
-			fetch( url ).then( function ( res ) {
+			var fetchOptions = {};
+			fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+
+			fetch( url, fetchOptions ).then( function ( res ) {
 
 				return res.blob();
 
@@ -46002,20 +46039,6 @@
 
 	} );
 
-	/**
-	 * @author raub / https://github.com/raub
-	 */
-
-	/**
-	 * Element size is one of:
-	 * 5126: 4
-	 * 5123: 2
-	 * 5122: 2
-	 * 5125: 4
-	 * 5124: 4
-	 * 5120: 1
-	 * 5121: 1
-	 */
 	function GLBufferAttribute( buffer, type, itemSize, elementSize, count ) {
 
 		this.buffer = buffer;
@@ -47198,7 +47221,7 @@
 		PolarGridHelper.prototype = Object.create( LineSegments.prototype );
 		PolarGridHelper.prototype.constructor = PolarGridHelper;
 
-	var _v1$5 = /*@__PURE__*/ new Vector3();
+	var _v1$6 = /*@__PURE__*/ new Vector3();
 	var _v2$3 = /*@__PURE__*/ new Vector3();
 	var _v3$1 = /*@__PURE__*/ new Vector3();
 
@@ -47253,9 +47276,9 @@
 
 		DirectionalLightHelper.prototype.update = function update () {
 
-			_v1$5.setFromMatrixPosition( this.light.matrixWorld );
+			_v1$6.setFromMatrixPosition( this.light.matrixWorld );
 			_v2$3.setFromMatrixPosition( this.light.target.matrixWorld );
-			_v3$1.subVectors( _v2$3, _v1$5 );
+			_v3$1.subVectors( _v2$3, _v1$6 );
 
 			this.lightPlane.lookAt( _v2$3 );
 

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


+ 87 - 68
build/three.module.js

@@ -1,3 +1,4 @@
+// threejs.org/license
 // Polyfills
 
 if ( Number.EPSILON === undefined ) {
@@ -32573,6 +32574,11 @@ function toJSON$1( shapes, data ) {
 
 }
 
+const _v0$2 = new Vector3();
+const _v1$5 = new Vector3();
+const _normal$1 = new Vector3();
+const _triangle = new Triangle();
+
 class EdgesGeometry extends BufferGeometry {
 
 	constructor( geometry, thresholdAngle ) {
@@ -32587,89 +32593,119 @@ class EdgesGeometry extends BufferGeometry {
 
 		thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;
 
-		// buffer
+		if ( geometry.isGeometry ) {
 
-		const vertices = [];
+			geometry = new BufferGeometry().fromGeometry( geometry );
 
-		// helper variables
+		}
 
+		const precisionPoints = 4;
+		const precision = Math.pow( 10, precisionPoints );
 		const thresholdDot = Math.cos( MathUtils.DEG2RAD * thresholdAngle );
-		const edge = [ 0, 0 ], edges = {};
-		let edge1, edge2, key;
-		const keys = [ 'a', 'b', 'c' ];
 
-		// prepare source geometry
+		const indexAttr = geometry.getIndex();
+		const positionAttr = geometry.getAttribute( 'position' );
+		const indexCount = indexAttr ? indexAttr.count : positionAttr.count;
 
-		let geometry2;
+		const indexArr = [ 0, 0, 0 ];
+		const vertKeys = [ 'a', 'b', 'c' ];
+		const hashes = new Array( 3 );
 
-		if ( geometry.isBufferGeometry ) {
+		const edgeData = {};
+		const vertices = [];
+		for ( let i = 0; i < indexCount; i += 3 ) {
 
-			geometry2 = new Geometry();
-			geometry2.fromBufferGeometry( geometry );
+			if ( indexAttr ) {
 
-		} else {
+				indexArr[ 0 ] = indexAttr.getX( i );
+				indexArr[ 1 ] = indexAttr.getX( i + 1 );
+				indexArr[ 2 ] = indexAttr.getX( i + 2 );
 
-			geometry2 = geometry.clone();
+			} else {
 
-		}
+				indexArr[ 0 ] = i;
+				indexArr[ 1 ] = i + 1;
+				indexArr[ 2 ] = i + 2;
 
-		geometry2.mergeVertices();
-		geometry2.computeFaceNormals();
+			}
 
-		const sourceVertices = geometry2.vertices;
-		const faces = geometry2.faces;
+			const { a, b, c } = _triangle;
+			a.fromBufferAttribute( positionAttr, indexArr[ 0 ] );
+			b.fromBufferAttribute( positionAttr, indexArr[ 1 ] );
+			c.fromBufferAttribute( positionAttr, indexArr[ 2 ] );
+			_triangle.getNormal( _normal$1 );
 
-		// now create a data structure where each entry represents an edge with its adjoining faces
+			// create hashes for the edge from the vertices
+			hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`;
+			hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`;
+			hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`;
 
-		for ( let i = 0, l = faces.length; i < l; i ++ ) {
+			// skip degenerate triangles
+			if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) {
 
-			const face = faces[ i ];
+				continue;
 
+			}
+
+			// iterate over every edge
 			for ( let j = 0; j < 3; j ++ ) {
 
-				edge1 = face[ keys[ j ] ];
-				edge2 = face[ keys[ ( j + 1 ) % 3 ] ];
-				edge[ 0 ] = Math.min( edge1, edge2 );
-				edge[ 1 ] = Math.max( edge1, edge2 );
+				// get the first and next vertex making up the edge
+				const jNext = ( j + 1 ) % 3;
+				const vecHash0 = hashes[ j ];
+				const vecHash1 = hashes[ jNext ];
+				const v0 = _triangle[ vertKeys[ j ] ];
+				const v1 = _triangle[ vertKeys[ jNext ] ];
 
-				key = edge[ 0 ] + ',' + edge[ 1 ];
+				const hash = `${ vecHash0 }_${ vecHash1 }`;
+				const reverseHash = `${ vecHash1 }_${ vecHash0 }`;
 
-				if ( edges[ key ] === undefined ) {
+				if ( reverseHash in edgeData && edgeData[ reverseHash ] ) {
 
-					edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };
+					// if we found a sibling edge add it into the vertex array if
+					// it meets the angle threshold and delete the edge from the map.
+					if ( _normal$1.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) {
 
-				} else {
+						vertices.push( v0.x, v0.y, v0.z );
+						vertices.push( v1.x, v1.y, v1.z );
 
-					edges[ key ].face2 = i;
+					}
 
-				}
+					edgeData[ reverseHash ] = null;
 
-			}
+				} else if ( ! ( hash in edgeData ) ) {
 
-		}
+					// if we've already got an edge here then skip adding a new one
+					edgeData[ hash ] = {
 
-		// generate vertices
+						index0: indexArr[ j ],
+						index1: indexArr[ jNext ],
+						normal: _normal$1.clone(),
 
-		for ( key in edges ) {
+					};
 
-			const e = edges[ key ];
+				}
 
-			// an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.
+			}
 
-			if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {
+		}
 
-				let vertex = sourceVertices[ e.index1 ];
-				vertices.push( vertex.x, vertex.y, vertex.z );
+		// iterate over all remaining, unmatched edges and add them to the vertex array
+		for ( const key in edgeData ) {
 
-				vertex = sourceVertices[ e.index2 ];
-				vertices.push( vertex.x, vertex.y, vertex.z );
+			if ( edgeData[ key ] ) {
+
+				const { index0, index1 } = edgeData[ key ];
+				_v0$2.fromBufferAttribute( positionAttr, index0 );
+				_v1$5.fromBufferAttribute( positionAttr, index1 );
+
+				vertices.push( _v0$2.x, _v0$2.y, _v0$2.z );
+				vertices.push( _v1$5.x, _v1$5.y, _v1$5.z );
 
 			}
 
 		}
 
-		// build geometry
-
 		this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
 
 	}
@@ -39079,8 +39115,6 @@ function Light( color, intensity ) {
 	this.color = new Color( color );
 	this.intensity = intensity !== undefined ? intensity : 1;
 
-	this.receiveShadow = undefined;
-
 }
 
 Light.prototype = Object.assign( Object.create( Object3D.prototype ), {
@@ -39128,8 +39162,6 @@ function HemisphereLight( skyColor, groundColor, intensity ) {
 
 	this.type = 'HemisphereLight';
 
-	this.castShadow = undefined;
-
 	this.position.copy( Object3D.DefaultUp );
 	this.updateMatrix();
 
@@ -39712,8 +39744,6 @@ function AmbientLight( color, intensity ) {
 
 	this.type = 'AmbientLight';
 
-	this.castShadow = undefined;
-
 }
 
 AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {
@@ -41714,7 +41744,10 @@ ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 		}
 
-		fetch( url ).then( function ( res ) {
+		const fetchOptions = {};
+		fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+
+		fetch( url, fetchOptions ).then( function ( res ) {
 
 			return res.blob();
 
@@ -46128,20 +46161,6 @@ InstancedInterleavedBuffer.prototype = Object.assign( Object.create( Interleaved
 
 } );
 
-/**
- * @author raub / https://github.com/raub
- */
-
-/**
- * Element size is one of:
- * 5126: 4
- * 5123: 2
- * 5122: 2
- * 5125: 4
- * 5124: 4
- * 5120: 1
- * 5121: 1
- */
 function GLBufferAttribute( buffer, type, itemSize, elementSize, count ) {
 
 	this.buffer = buffer;
@@ -47342,7 +47361,7 @@ class PolarGridHelper extends LineSegments {
 
 }
 
-const _v1$5 = /*@__PURE__*/ new Vector3();
+const _v1$6 = /*@__PURE__*/ new Vector3();
 const _v2$3 = /*@__PURE__*/ new Vector3();
 const _v3$1 = /*@__PURE__*/ new Vector3();
 
@@ -47396,9 +47415,9 @@ class DirectionalLightHelper extends Object3D {
 
 	update() {
 
-		_v1$5.setFromMatrixPosition( this.light.matrixWorld );
+		_v1$6.setFromMatrixPosition( this.light.matrixWorld );
 		_v2$3.setFromMatrixPosition( this.light.target.matrixWorld );
-		_v3$1.subVectors( _v2$3, _v1$5 );
+		_v3$1.subVectors( _v2$3, _v1$6 );
 
 		this.lightPlane.lookAt( _v2$3 );
 

+ 0 - 5
docs/api/en/lights/AmbientLight.html

@@ -46,11 +46,6 @@
 				See the base [page:Light Light] class for common properties.
 		</p>
 
-		<h3>[property:Boolean castShadow]</h3>
-		<p>
-			This is set to *undefined* in the constructor as ambient lights cannot cast shadows.
-		</p>
-
 
 		<h2>Methods</h2>
 		<p>

+ 0 - 6
docs/api/en/lights/HemisphereLight.html

@@ -52,12 +52,6 @@
 			See the base [page:Light Light] class for common properties.
 		</p>
 
-		<h3>[property:Boolean castShadow]</h3>
-		<p>
-			This is set to *undefined* in the constructor as hemisphere lights cannot cast shadows.
-		</p>
-
-
 		<h3>[property:Float color]</h3>
 		<p>
 			The light's sky color, as passed in the constructor.

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

@@ -45,7 +45,7 @@
 		</p>
 
 		<h3>[property:Integer blendDstAlpha]</h3>
-		<p>The transparency of the [page:.blendDst]. Default is *null*.</p>
+		<p>The transparency of the [page:.blendDst]. Uses [page:.blendDst] value if null. Default is *null*.</p>
 
 		<h3>[property:Integer blendEquation]</h3>
 		<p>
@@ -55,7 +55,7 @@
 		</p>
 
 		<h3>[property:Integer blendEquationAlpha]</h3>
-		<p>The tranparency of the [page:.blendEquation]. Default is *null*.</p>
+		<p>The transparency of the [page:.blendEquation]. Uses [page:.blendEquation] value if null. Default is *null*.</p>
 
 		<h3>[property:Blending blending]</h3>
 		<p>
@@ -72,7 +72,7 @@
 		</p>
 
 		<h3>[property:Integer blendSrcAlpha]</h3>
-		<p>The tranparency of the [page:.blendSrc]. Default is *null*.</p>
+		<p>The transparency of the [page:.blendSrc]. Uses [page:.blendSrc] value if null. Default is *null*.</p>
 
 		<h3>[property:Boolean clipIntersection]</h3>
 		<p>

+ 2 - 2
docs/api/en/renderers/WebGLRenderer.html

@@ -55,10 +55,10 @@
 
 		[page:String powerPreference] - Provides a hint to the user agent indicating what configuration
 		of GPU is suitable for this WebGL context. Can be *"high-performance"*, *"low-power"* or *"default"*. Default is *"default"*.
-		See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 WebGL spec] for details.<br />
+		See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.2 WebGL spec] for details.<br />
 
 		[page:Boolean failIfMajorPerformanceCaveat] - whether the renderer creation will fail upon low perfomance is detected. Default is *false*.
-		See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 WebGL spec] for details.<br />
+		See [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.2 WebGL spec] for details.<br />
 
 		[page:Boolean depth] - whether the drawing buffer has a
 		[link:https://en.wikipedia.org/wiki/Z-buffering depth buffer] of at least 16 bits.

+ 1 - 1
editor/js/Sidebar.Object.js

@@ -539,7 +539,7 @@ function SidebarObject( editor ) {
 
 			}
 
-			if ( object.receiveShadow !== undefined && object.receiveShadow !== objectReceiveShadow.getValue() ) {
+			if ( object.receiveShadow !== objectReceiveShadow.getValue() ) {
 
 				if ( object.material !== undefined ) object.material.needsUpdate = true;
 				editor.execute( new SetValueCommand( editor, object, 'receiveShadow', objectReceiveShadow.getValue() ) );

+ 3 - 0
examples/js/controls/OrbitControls.js

@@ -773,6 +773,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseDown( event );
 				break;
 
@@ -789,6 +790,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseMove( event );
 				break;
 
@@ -805,6 +807,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseUp( event );
 				break;
 

+ 3 - 0
examples/js/controls/TrackballControls.js

@@ -402,6 +402,7 @@ THREE.TrackballControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseDown( event );
 				break;
 
@@ -418,6 +419,7 @@ THREE.TrackballControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseMove( event );
 				break;
 
@@ -434,6 +436,7 @@ THREE.TrackballControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseUp( event );
 				break;
 

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

@@ -616,6 +616,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				scope.pointerHover( getPointer( event ) );
 				break;
 

+ 84 - 36
examples/js/loaders/GLTFLoader.js

@@ -17,6 +17,7 @@ THREE.GLTFLoader = ( function () {
 			return new GLTFMaterialsClearcoatExtension( parser );
 
 		} );
+
 		this.register( function ( parser ) {
 
 			return new GLTFTextureBasisUExtension( parser );
@@ -29,6 +30,12 @@ THREE.GLTFLoader = ( function () {
 
 		} );
 
+		this.register( function ( parser ) {
+
+			return new GLTFLightsExtension( parser );
+
+		} );
+
 	}
 
 	GLTFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
@@ -235,10 +242,6 @@ THREE.GLTFLoader = ( function () {
 
 					switch ( extensionName ) {
 
-						case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
-							extensions[ extensionName ] = new GLTFLightsExtension( json );
-							break;
-
 						case EXTENSIONS.KHR_MATERIALS_UNLIT:
 							extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
 							break;
@@ -363,21 +366,53 @@ THREE.GLTFLoader = ( function () {
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
 	 */
-	function GLTFLightsExtension( json ) {
+	function GLTFLightsExtension( parser ) {
 
+		this.parser = parser;
 		this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
 
-		var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
-		this.lightDefs = extension.lights || [];
+		// Object3D instance caches
+		this.cache = { refs: {}, uses: {} };
 
 	}
 
-	GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
+	GLTFLightsExtension.prototype._markDefs = function () {
+
+		var parser = this.parser;
+		var nodeDefs = this.parser.json.nodes || [];
+
+		for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
+
+			var nodeDef = nodeDefs[ nodeIndex ];
 
-		var lightDef = this.lightDefs[ lightIndex ];
+			if ( nodeDef.extensions
+				&& nodeDef.extensions[ this.name ]
+				&& nodeDef.extensions[ this.name ].light !== undefined ) {
+
+				parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light );
+
+			}
+
+		}
+
+	};
+
+	GLTFLightsExtension.prototype._loadLight = function ( lightIndex ) {
+
+		var parser = this.parser;
+		var cacheKey = 'light:' + lightIndex;
+		var dependency = parser.cache.get( cacheKey );
+
+		if ( dependency ) return dependency;
+
+		var json = parser.json;
+		var extensions = ( json.extensions && json.extensions[ this.name ] ) || {};
+		var lightDefs = extensions.lights || [];
+		var lightDef = lightDefs[ lightIndex ];
 		var lightNode;
 
 		var color = new THREE.Color( 0xffffff );
+
 		if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
 
 		var range = lightDef.range !== undefined ? lightDef.range : 0;
@@ -423,7 +458,30 @@ THREE.GLTFLoader = ( function () {
 
 		lightNode.name = lightDef.name || ( 'light_' + lightIndex );
 
-		return Promise.resolve( lightNode );
+		dependency = Promise.resolve( lightNode );
+
+		parser.cache.add( cacheKey, dependency );
+
+		return dependency;
+
+	};
+
+	GLTFLightsExtension.prototype.createNodeAttachment = function ( nodeIndex ) {
+
+		var self = this;
+		var parser = this.parser;
+		var json = parser.json;
+		var nodeDef = json.nodes[ nodeIndex ];
+		var lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {};
+		var lightIndex = lightDef.light;
+
+		if ( lightIndex === undefined ) return null;
+
+		return this._loadLight( lightIndex ).then( function ( light ) {
+
+			return parser._getNodeRef( self.cache, lightIndex, light );
+
+		} );
 
 	};
 
@@ -1668,7 +1726,11 @@ THREE.GLTFLoader = ( function () {
 		this.cache.removeAll();
 
 		// Mark the special nodes/meshes in json for efficient parse
-		this._markDefs();
+		this._invokeAll( function ( ext ) {
+
+			return ext._markDefs && ext._markDefs();
+
+		} );
 
 		Promise.all( [
 
@@ -1748,14 +1810,6 @@ THREE.GLTFLoader = ( function () {
 
 			}
 
-			if ( nodeDef.extensions
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {
-
-				this._addNodeRef( this.lightCache, nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light );
-
-			}
-
 		}
 
 	};
@@ -1820,11 +1874,13 @@ THREE.GLTFLoader = ( function () {
 
 		for ( var i = 0; i < extensions.length; i ++ ) {
 
-			pending.push( func( extensions[ i ] ) );
+			var result = func( extensions[ i ] );
+
+			if ( result ) pending.push( result );
 
 		}
 
-		return Promise.all( pending );
+		return pending;
 
 	};
 
@@ -1903,10 +1959,6 @@ THREE.GLTFLoader = ( function () {
 					dependency = this.loadCamera( index );
 					break;
 
-				case 'light':
-					dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
-					break;
-
 				default:
 					throw new Error( 'Unknown type: ' + type );
 
@@ -2516,11 +2568,11 @@ THREE.GLTFLoader = ( function () {
 
 			} );
 
-			pending.push( this._invokeAll( function ( ext ) {
+			pending.push( Promise.all( this._invokeAll( function ( ext ) {
 
 				return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams );
 
-			} ) );
+			} ) ) );
 
 		}
 
@@ -3389,19 +3441,15 @@ THREE.GLTFLoader = ( function () {
 
 			}
 
-			if ( nodeDef.extensions
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {
-
-				var lightIndex = nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light;
+			parser._invokeAll( function ( ext ) {
 
-				pending.push( parser.getDependency( 'light', lightIndex ).then( function ( light ) {
+				return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex );
 
-					return parser._getNodeRef( parser.lightCache, lightIndex, light );
+			} ).forEach( function ( promise ) {
 
-				} ) );
+				pending.push( promise );
 
-			}
+			} );
 
 			return Promise.all( pending );
 

+ 3 - 0
examples/jsm/controls/OrbitControls.js

@@ -781,6 +781,7 @@ var OrbitControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseDown( event );
 				break;
 
@@ -797,6 +798,7 @@ var OrbitControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseMove( event );
 				break;
 
@@ -813,6 +815,7 @@ var OrbitControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseUp( event );
 				break;
 

+ 3 - 0
examples/jsm/controls/TrackballControls.js

@@ -408,6 +408,7 @@ var TrackballControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseDown( event );
 				break;
 
@@ -424,6 +425,7 @@ var TrackballControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseMove( event );
 				break;
 
@@ -440,6 +442,7 @@ var TrackballControls = function ( object, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				onMouseUp( event );
 				break;
 

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

@@ -637,6 +637,7 @@ var TransformControls = function ( camera, domElement ) {
 		switch ( event.pointerType ) {
 
 			case 'mouse':
+			case 'pen':
 				scope.pointerHover( getPointer( event ) );
 				break;
 

+ 84 - 36
examples/jsm/loaders/GLTFLoader.js

@@ -80,6 +80,7 @@ var GLTFLoader = ( function () {
 			return new GLTFMaterialsClearcoatExtension( parser );
 
 		} );
+
 		this.register( function ( parser ) {
 
 			return new GLTFTextureBasisUExtension( parser );
@@ -92,6 +93,12 @@ var GLTFLoader = ( function () {
 
 		} );
 
+		this.register( function ( parser ) {
+
+			return new GLTFLightsExtension( parser );
+
+		} );
+
 	}
 
 	GLTFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
@@ -298,10 +305,6 @@ var GLTFLoader = ( function () {
 
 					switch ( extensionName ) {
 
-						case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:
-							extensions[ extensionName ] = new GLTFLightsExtension( json );
-							break;
-
 						case EXTENSIONS.KHR_MATERIALS_UNLIT:
 							extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
 							break;
@@ -426,21 +429,53 @@ var GLTFLoader = ( function () {
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
 	 */
-	function GLTFLightsExtension( json ) {
+	function GLTFLightsExtension( parser ) {
 
+		this.parser = parser;
 		this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
 
-		var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {};
-		this.lightDefs = extension.lights || [];
+		// Object3D instance caches
+		this.cache = { refs: {}, uses: {} };
 
 	}
 
-	GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) {
+	GLTFLightsExtension.prototype._markDefs = function () {
+
+		var parser = this.parser;
+		var nodeDefs = this.parser.json.nodes || [];
+
+		for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
+
+			var nodeDef = nodeDefs[ nodeIndex ];
 
-		var lightDef = this.lightDefs[ lightIndex ];
+			if ( nodeDef.extensions
+				&& nodeDef.extensions[ this.name ]
+				&& nodeDef.extensions[ this.name ].light !== undefined ) {
+
+				parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light );
+
+			}
+
+		}
+
+	};
+
+	GLTFLightsExtension.prototype._loadLight = function ( lightIndex ) {
+
+		var parser = this.parser;
+		var cacheKey = 'light:' + lightIndex;
+		var dependency = parser.cache.get( cacheKey );
+
+		if ( dependency ) return dependency;
+
+		var json = parser.json;
+		var extensions = ( json.extensions && json.extensions[ this.name ] ) || {};
+		var lightDefs = extensions.lights || [];
+		var lightDef = lightDefs[ lightIndex ];
 		var lightNode;
 
 		var color = new Color( 0xffffff );
+
 		if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
 
 		var range = lightDef.range !== undefined ? lightDef.range : 0;
@@ -486,7 +521,30 @@ var GLTFLoader = ( function () {
 
 		lightNode.name = lightDef.name || ( 'light_' + lightIndex );
 
-		return Promise.resolve( lightNode );
+		dependency = Promise.resolve( lightNode );
+
+		parser.cache.add( cacheKey, dependency );
+
+		return dependency;
+
+	};
+
+	GLTFLightsExtension.prototype.createNodeAttachment = function ( nodeIndex ) {
+
+		var self = this;
+		var parser = this.parser;
+		var json = parser.json;
+		var nodeDef = json.nodes[ nodeIndex ];
+		var lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {};
+		var lightIndex = lightDef.light;
+
+		if ( lightIndex === undefined ) return null;
+
+		return this._loadLight( lightIndex ).then( function ( light ) {
+
+			return parser._getNodeRef( self.cache, lightIndex, light );
+
+		} );
 
 	};
 
@@ -1731,7 +1789,11 @@ var GLTFLoader = ( function () {
 		this.cache.removeAll();
 
 		// Mark the special nodes/meshes in json for efficient parse
-		this._markDefs();
+		this._invokeAll( function ( ext ) {
+
+			return ext._markDefs && ext._markDefs();
+
+		} );
 
 		Promise.all( [
 
@@ -1811,14 +1873,6 @@ var GLTFLoader = ( function () {
 
 			}
 
-			if ( nodeDef.extensions
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {
-
-				this._addNodeRef( this.lightCache, nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light );
-
-			}
-
 		}
 
 	};
@@ -1883,11 +1937,13 @@ var GLTFLoader = ( function () {
 
 		for ( var i = 0; i < extensions.length; i ++ ) {
 
-			pending.push( func( extensions[ i ] ) );
+			var result = func( extensions[ i ] );
+
+			if ( result ) pending.push( result );
 
 		}
 
-		return Promise.all( pending );
+		return pending;
 
 	};
 
@@ -1966,10 +2022,6 @@ var GLTFLoader = ( function () {
 					dependency = this.loadCamera( index );
 					break;
 
-				case 'light':
-					dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index );
-					break;
-
 				default:
 					throw new Error( 'Unknown type: ' + type );
 
@@ -2579,11 +2631,11 @@ var GLTFLoader = ( function () {
 
 			} );
 
-			pending.push( this._invokeAll( function ( ext ) {
+			pending.push( Promise.all( this._invokeAll( function ( ext ) {
 
 				return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams );
 
-			} ) );
+			} ) ) );
 
 		}
 
@@ -3452,19 +3504,15 @@ var GLTFLoader = ( function () {
 
 			}
 
-			if ( nodeDef.extensions
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ]
-				&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) {
-
-				var lightIndex = nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light;
+			parser._invokeAll( function ( ext ) {
 
-				pending.push( parser.getDependency( 'light', lightIndex ).then( function ( light ) {
+				return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex );
 
-					return parser._getNodeRef( parser.lightCache, lightIndex, light );
+			} ).forEach( function ( promise ) {
 
-				} ) );
+				pending.push( promise );
 
-			}
+			} );
 
 			return Promise.all( pending );
 

+ 57 - 66
examples/jsm/renderers/webgpu/WebGPUBindings.js

@@ -1,16 +1,17 @@
 import WebGPUUniformsGroup from './WebGPUUniformsGroup.js';
+import { FloatUniform, Matrix4Uniform } from './WebGPUUniform.js';
 import WebGPUSampler from './WebGPUSampler.js';
 import WebGPUSampledTexture from './WebGPUSampledTexture.js';
-import { Matrix4 } from '../../../../build/three.module.js';
 
 class WebGPUBindings {
 
-	constructor( device, info, properties, textures ) {
+	constructor( device, info, properties, textures, pipelines ) {
 
 		this.device = device;
 		this.info = info;
 		this.properties = properties;
 		this.textures = textures;
+		this.pipelines = pipelines;
 
 		this.uniformsData = new WeakMap();
 
@@ -28,6 +29,7 @@ class WebGPUBindings {
 
 		if ( data === undefined ) {
 
+			const pipeline = this.pipelines.get( object );
 			const material = object.material;
 			let bindings;
 
@@ -53,7 +55,7 @@ class WebGPUBindings {
 
 			// setup (static) binding layout and (dynamic) binding group
 
-			const bindLayout = this._createBindLayout( bindings );
+			const bindLayout = pipeline.getBindGroupLayout( 0 );
 			const bindGroup = this._createBindGroup( bindings, bindLayout );
 
 			data = {
@@ -97,7 +99,9 @@ class WebGPUBindings {
 				const array = binding.array;
 				const bufferGPU = binding.bufferGPU;
 
-				const needsBufferWrite = binding.update( object, camera );
+				binding.onBeforeUpdate( object, camera );
+
+				const needsBufferWrite = binding.update();
 
 				if ( needsBufferWrite === true ) {
 
@@ -162,23 +166,6 @@ class WebGPUBindings {
 
 	}
 
-	_createBindLayout( bindings ) {
-
-		let bindingPoint = 0;
-		const entries = [];
-
-		for ( const binding of bindings ) {
-
-			entries.push( { binding: bindingPoint, visibility: binding.visibility, type: binding.type } );
-
-			bindingPoint ++;
-
-		}
-
-		return this.device.createBindGroupLayout( { entries: entries } );
-
-	}
-
 	_createBindGroup( bindings, layout ) {
 
 		let bindingPoint = 0;
@@ -240,49 +227,56 @@ class WebGPUBindings {
 
 		const bindings = [];
 
-		// ubos
+		// UBOs
 
-		const modelGroup = new WebGPUUniformsGroup();
-		modelGroup.setName( 'modelUniforms' );
-		modelGroup.setUniform( 'modelMatrix', new Matrix4() );
-		modelGroup.setUniform( 'modelViewMatrix', new Matrix4() );
-		modelGroup.setUpdateCallback( function ( object/*, camera */ ) {
+		// model
 
-			let updated = false;
+		const modelViewUniform = new Matrix4Uniform( 'modelMatrix' );
+		const modelViewMatrixUniform = new Matrix4Uniform( 'modelViewMatrix' );
 
-			if ( modelGroup.updateMatrix4( object.matrixWorld, 0 ) ) updated = true;
-			if ( modelGroup.updateMatrix4( object.modelViewMatrix, 16 ) ) updated = true;
+		const modelGroup = new WebGPUUniformsGroup();
+		modelGroup.setName( 'modelUniforms' );
+		modelGroup.addUniform( modelViewUniform );
+		modelGroup.addUniform( modelViewMatrixUniform );
+		modelGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
 
-			return updated;
+			modelViewUniform.setValue( object.matrixWorld );
+			modelViewMatrixUniform.setValue( object.modelViewMatrix );
 
 		} );
 
+		// camera
+
 		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
 
+		// material (opacity for testing)
+
+		const opacityUniform = new FloatUniform( 'opacity', 1 );
+
 		const opacityGroup = new WebGPUUniformsGroup();
 		opacityGroup.setName( 'opacityUniforms' );
-		opacityGroup.setUniform( 'opacity', 1.0 );
+		opacityGroup.addUniform( opacityUniform );
 		opacityGroup.visibility = GPUShaderStage.FRAGMENT;
-		opacityGroup.setUpdateCallback( function ( object/*, camera */ ) {
+		opacityGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
 
 			const material = object.material;
 			const opacity = ( material.transparent === true ) ? material.opacity : 1.0;
 
-			return opacityGroup.updateNumber( opacity, 0 );
+			opacityUniform.setValue( opacity );
 
 		} );
 
-		// samplers
+		// sampler
 
 		const diffuseSampler = new WebGPUSampler();
 		diffuseSampler.setName( 'map' );
 
-		// textures
+		// texture
 
 		const diffuseTexture = new WebGPUSampledTexture();
 		diffuseTexture.setName( 'map' );
 
-		//
+		// the order of WebGPUBinding objects must match the binding order in the shader
 
 		bindings.push( modelGroup );
 		bindings.push( cameraGroup );
@@ -298,20 +292,19 @@ class WebGPUBindings {
 
 		const bindings = [];
 
-		// ubos
+		// UBOs
+
+		const modelViewUniform = new Matrix4Uniform( 'modelMatrix' );
+		const modelViewMatrixUniform = new Matrix4Uniform( 'modelViewMatrix' );
 
 		const modelGroup = new WebGPUUniformsGroup();
 		modelGroup.setName( 'modelUniforms' );
-		modelGroup.setUniform( 'modelMatrix', new Matrix4() );
-		modelGroup.setUniform( 'modelViewMatrix', new Matrix4() );
-		modelGroup.setUpdateCallback( function ( object/*, camera */ ) {
+		modelGroup.addUniform( modelViewUniform );
+		modelGroup.addUniform( modelViewMatrixUniform );
+		modelGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
 
-			let updated = false;
-
-			if ( modelGroup.updateMatrix4( object.matrixWorld, 0 ) ) updated = true;
-			if ( modelGroup.updateMatrix4( object.modelViewMatrix, 16 ) ) updated = true;
-
-			return updated;
+			modelViewUniform.setValue( object.matrixWorld );
+			modelViewMatrixUniform.setValue( object.modelViewMatrix );
 
 		} );
 
@@ -330,20 +323,19 @@ class WebGPUBindings {
 
 		const bindings = [];
 
-		// ubos
+		// UBOs
+
+		const modelViewUniform = new Matrix4Uniform( 'modelMatrix' );
+		const modelViewMatrixUniform = new Matrix4Uniform( 'modelViewMatrix' );
 
 		const modelGroup = new WebGPUUniformsGroup();
 		modelGroup.setName( 'modelUniforms' );
-		modelGroup.setUniform( 'modelMatrix', new Matrix4() );
-		modelGroup.setUniform( 'modelViewMatrix', new Matrix4() );
-		modelGroup.setUpdateCallback( function ( object/*, camera */ ) {
-
-			let updated = false;
+		modelGroup.addUniform( modelViewUniform );
+		modelGroup.addUniform( modelViewMatrixUniform );
+		modelGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
 
-			if ( modelGroup.updateMatrix4( object.matrixWorld, 0 ) ) updated = true;
-			if ( modelGroup.updateMatrix4( object.modelViewMatrix, 16 ) ) updated = true;
-
-			return updated;
+			modelViewUniform.setValue( object.matrixWorld );
+			modelViewMatrixUniform.setValue( object.modelViewMatrix );
 
 		} );
 
@@ -360,18 +352,17 @@ class WebGPUBindings {
 
 	_setupSharedUniformsGroups() {
 
+		const projectionMatrixUniform = new Matrix4Uniform( 'projectionMatrix' );
+		const viewMatrixUniform = new Matrix4Uniform( 'viewMatrix' );
+
 		const cameraGroup = new WebGPUUniformsGroup();
 		cameraGroup.setName( 'cameraUniforms' );
-		cameraGroup.setUniform( 'projectionMatrix', new Matrix4() );
-		cameraGroup.setUniform( 'viewMatrix', new Matrix4() );
-		cameraGroup.setUpdateCallback( function ( object, camera ) {
-
-			let updated = false;
-
-			if ( cameraGroup.updateMatrix4( camera.projectionMatrix, 0 ) ) updated = true;
-			if ( cameraGroup.updateMatrix4( camera.matrixWorldInverse, 16 ) ) updated = true;
+		cameraGroup.addUniform( projectionMatrixUniform );
+		cameraGroup.addUniform( viewMatrixUniform );
+		cameraGroup.setOnBeforeUpdate( function ( object, camera ) {
 
-			return updated;
+			projectionMatrixUniform.setValue( camera.projectionMatrix );
+			viewMatrixUniform.setValue( camera.matrixWorldInverse );
 
 		} );
 

+ 40 - 23
examples/jsm/renderers/webgpu/WebGPURenderPipelines.js

@@ -11,11 +11,10 @@ import {
 
 class WebGPURenderPipelines {
 
-	constructor( device, glslang, bindings, sampleCount ) {
+	constructor( device, glslang, sampleCount ) {
 
 		this.device = device;
 		this.glslang = glslang;
-		this.bindings = bindings;
 		this.sampleCount = sampleCount;
 
 		this.pipelines = new WeakMap();
@@ -93,33 +92,19 @@ class WebGPURenderPipelines {
 
 			}
 
-			// layout
+			// determine shader attributes
 
-			const bindLayout = this.bindings.get( object ).layout;
-			const layout = device.createPipelineLayout( { bindGroupLayouts: [ bindLayout ] } );
+			const shaderAttributes = this._parseShaderAttributes( shader.vertexShader );
 
 			// vertex buffers
 
 			const vertexBuffers = [];
-			const shaderAttributes = [];
 
-			// find "layout (location = num) in type name" in vertex shader
-
-			const regex = /^\s*layout\s*\(\s*location\s*=\s*(?<location>[0-9]+)\s*\)\s*in\s+(?<type>\w+)\s+(?<name>\w+)\s*;/gmi;
-
-			let shaderAttribute = null;
-
-			while ( shaderAttribute = regex.exec( shader.vertexShader ) ) {
-
-				const shaderLocation = parseInt( shaderAttribute.groups.location );
-				const arrayStride = this._getArrayStride( shaderAttribute.groups.type );
-				const vertexFormat = this._getVertexFormat( shaderAttribute.groups.type );
-
-				shaderAttributes.push( { name: shaderAttribute.groups.name, slot: shaderLocation } );
+			for ( const attribute of shaderAttributes ) {
 
 				vertexBuffers.push( {
-					arrayStride: arrayStride,
-					attributes: [ { shaderLocation: shaderLocation, offset: 0, format: vertexFormat } ]
+					arrayStride: attribute.arrayStride,
+					attributes: [ { shaderLocation: attribute.slot, offset: 0, format: attribute.format } ]
 				} );
 
 			}
@@ -172,7 +157,6 @@ class WebGPURenderPipelines {
 			const depthCompare = this._getDepthCompare( material );
 
 			pipeline = device.createRenderPipeline( {
-				layout: layout,
 				vertexStage: moduleVertex,
 				fragmentStage: moduleFragment,
 				primitiveTopology: primitiveTopology,
@@ -202,7 +186,6 @@ class WebGPURenderPipelines {
 			this.pipelines.set( object, pipeline );
 			this.shaderAttributes.set( pipeline, shaderAttributes );
 
-
 		}
 
 		return pipeline;
@@ -707,6 +690,40 @@ class WebGPURenderPipelines {
 
 	}
 
+	_parseShaderAttributes( shader ) {
+
+		// find "layout (location = num) in type name" in vertex shader
+
+		const regex = /^\s*layout\s*\(\s*location\s*=\s*(?<location>[0-9]+)\s*\)\s*in\s+(?<type>\w+)\s+(?<name>\w+)\s*;/gmi;
+		let shaderAttribute = null;
+
+		const attributes = [];
+
+		while ( shaderAttribute = regex.exec( shader ) ) {
+
+			const shaderLocation = parseInt( shaderAttribute.groups.location );
+			const arrayStride = this._getArrayStride( shaderAttribute.groups.type );
+			const vertexFormat = this._getVertexFormat( shaderAttribute.groups.type );
+
+			attributes.push( {
+				name: shaderAttribute.groups.name,
+				arrayStride: arrayStride,
+				slot: shaderLocation,
+				format: vertexFormat
+			} );
+
+		}
+
+		// the sort ensures to setup vertex buffers in the correct order
+
+		return attributes.sort( function ( a, b ) {
+
+			return a.slot - b.slot;
+
+		} );
+
+	}
+
 }
 
 const ShaderLib = {

+ 2 - 2
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -172,9 +172,9 @@ class WebGPURenderer {
 		this._attributes = new WebGPUAttributes( device );
 		this._geometries = new WebGPUGeometries( this._attributes, this._info );
 		this._textures = new WebGPUTextures( device, this._properties, this._info, compiler );
-		this._bindings = new WebGPUBindings( device, this._info, this._properties, this._textures );
 		this._objects = new WebGPUObjects( this._geometries, this._info );
-		this._renderPipelines = new WebGPURenderPipelines( device, compiler, this._bindings, parameters.sampleCount );
+		this._renderPipelines = new WebGPURenderPipelines( device, compiler, parameters.sampleCount );
+		this._bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines );
 		this._renderLists = new WebGPURenderLists();
 		this._background = new WebGPUBackground( this );
 

+ 129 - 0
examples/jsm/renderers/webgpu/WebGPUUniform.js

@@ -0,0 +1,129 @@
+import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from '../../../../build/three.module.js';
+
+class WebGPUUniform {
+
+	constructor( name, value ) {
+
+		this.name = name;
+		this.value = value;
+
+		this.byteLength = 0;
+		this.itemSize = 0;
+		this.value = null;
+
+	}
+
+	setValue( value ) {
+
+		this.value = value;
+
+	}
+
+}
+
+class FloatUniform extends WebGPUUniform {
+
+	constructor( name, value = 0 ) {
+
+		super( name, value );
+
+		this.byteLength = 4;
+		this.itemSize = 1;
+
+		Object.defineProperty( this, 'isFloatUniform', { value: true } );
+
+	}
+
+}
+
+class Vector2Uniform extends WebGPUUniform {
+
+	constructor( name, value = new Vector2() ) {
+
+		super( name, value );
+
+		this.byteLength = 8;
+		this.itemSize = 2;
+
+		Object.defineProperty( this, 'isVector2Uniform', { value: true } );
+
+	}
+
+}
+
+class Vector3Uniform extends WebGPUUniform {
+
+	constructor( name, value = new Vector3() ) {
+
+		super( name, value );
+
+		this.byteLength = 12;
+		this.itemSize = 3;
+
+		Object.defineProperty( this, 'isVector3Uniform', { value: true } );
+
+	}
+
+}
+
+class Vector4Uniform extends WebGPUUniform {
+
+	constructor( name, value = new Vector4() ) {
+
+		super( name, value );
+
+		this.byteLength = 16;
+		this.itemSize = 4;
+
+		Object.defineProperty( this, 'isVector4Uniform', { value: true } );
+
+	}
+
+}
+
+class ColorUniform extends WebGPUUniform {
+
+	constructor( name, value = new Color() ) {
+
+		super( name, value );
+
+		this.byteLength = 12;
+		this.itemSize = 3;
+
+		Object.defineProperty( this, 'isColorUniform', { value: true } );
+
+	}
+
+}
+
+class Matrix3Uniform extends WebGPUUniform {
+
+	constructor( name, value = new Matrix3() ) {
+
+		super( name, value );
+
+		this.byteLength = 48; // (3 * 4) * 4 bytes
+		this.itemSize = 12;
+
+		Object.defineProperty( this, 'isMatrix3Uniform', { value: true } );
+
+	}
+
+}
+
+class Matrix4Uniform extends WebGPUUniform {
+
+	constructor( name, value = new Matrix4() ) {
+
+		super( name, value );
+
+		this.byteLength = 64;
+		this.itemSize = 16;
+
+		Object.defineProperty( this, 'isMatrix4Uniform', { value: true } );
+
+	}
+
+}
+
+export { FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform, Matrix3Uniform, Matrix4Uniform };

+ 57 - 57
examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js

@@ -7,9 +7,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		super();
 
 		this.name = '';
-		this.uniforms = new Map();
 
-		this.update = function () {};
+		 // the order of uniforms in this array must match the order of uniforms in the shader
+
+		this.uniforms = [];
+
+		this.onBeforeUpdate = function () {};
 
 		this.type = 'uniform-buffer';
 		this.visibility = GPUShaderStage.VERTEX;
@@ -29,25 +32,31 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	setUniform( name, uniform ) {
+	addUniform( uniform ) {
 
-		this.uniforms.set( name, uniform );
+		this.uniforms.push( uniform );
 
 		return this;
 
 	}
 
-	removeUniform( name ) {
+	removeUniform( uniform ) {
+
+		const index = this.uniforms.indexOf( uniform );
 
-		this.uniforms.delete( name );
+		if ( index !== - 1 ) {
+
+			this.uniforms.splice( index, 1 );
+
+		}
 
 		return this;
 
 	}
 
-	setUpdateCallback( callback ) {
+	setOnBeforeUpdate( callback ) {
 
-		this.update = callback;
+		this.onBeforeUpdate = callback;
 
 		return this;
 
@@ -57,15 +66,8 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 		this.name = source.name;
 
-		const uniformsSource = source.uniforms;
-
-		this.uniforms.clear();
-
-		for ( const entry of uniformsSource ) {
-
-			this.uniforms.set( ...entry );
-
-		}
+		this.uniforms.length = 0;
+		this.uniforms.push( ...source.uniforms );
 
 		return this;
 
@@ -79,65 +81,59 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	getByteLength() {
 
-		let size = 0;
+		let byteLength = 0;
 
-		for ( const uniform of this.uniforms.values() ) {
+		for ( const uniform of this.uniforms ) {
 
-			size += this.getUniformByteLength( uniform );
+			byteLength += uniform.byteLength;
 
 		}
 
-		return size;
+		return byteLength;
 
 	}
 
-	getUniformByteLength( uniform ) {
-
-		let size;
+	update() {
 
-		if ( typeof uniform === 'number' ) {
-
-			size = 4;
-
-		} else if ( uniform.isVector2 ) {
-
-			size = 8;
-
-		} else if ( uniform.isVector3 || uniform.isColor ) {
-
-			size = 12;
-
-		} else if ( uniform.isVector4 ) {
+		let updated = false;
+		let offset = 0;
 
-			size = 16;
+		for ( const uniform of this.uniforms ) {
 
-		} else if ( uniform.isMatrix3 ) {
+			if ( this.updateByType( uniform, offset ) === true ) {
 
-			size = 48; // (3 * 4) * 4 bytes
+				updated = true;
 
-		} else if ( uniform.isMatrix4 ) {
+			}
 
-			size = 64;
+			offset += uniform.itemSize;
 
-		} else if ( uniform.isTexture ) {
+		}
 
-			console.error( 'THREE.UniformsGroup: Texture samplers can not be part of an uniforms group.' );
+		return updated;
 
-		} else {
+	}
 
-			console.error( 'THREE.UniformsGroup: Unsupported uniform value type.', uniform );
+	updateByType( uniform, offset ) {
 
-		}
+		if ( uniform.isFloatUniform ) return this.updateNumber( uniform, offset );
+		if ( uniform.isVector2Uniform ) return this.updateVector2( uniform, offset );
+		if ( uniform.isVector3Uniform ) return this.updateVector3( uniform, offset );
+		if ( uniform.isVector4Uniform ) return this.updateVector4( uniform, offset );
+		if ( uniform.isColorUniform ) return this.updateColor( uniform, offset );
+		if ( uniform.isMatrix3Uniform ) return this.updateMatrix3( uniform, offset );
+		if ( uniform.isMatrix4Uniform ) return this.updateMatrix4( uniform, offset );
 
-		return size;
+		console.error( 'THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform );
 
 	}
 
-	updateNumber( v, offset ) {
+	updateNumber( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
+		const v = uniform.value;
 
 		if ( a[ offset ] !== v ) {
 
@@ -150,11 +146,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	updateVector2( v, offset ) {
+	updateVector2( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
+		const v = uniform.value;
 
 		if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) {
 
@@ -169,11 +166,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	updateVector3( v, offset ) {
+	updateVector3( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
+		const v = uniform.value;
 
 		if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) {
 
@@ -189,11 +187,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	updateVector4( v, offset ) {
+	updateVector4( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
+		const v = uniform.value;
 
 		if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) {
 
@@ -210,11 +209,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	updateColor( c, offset ) {
+	updateColor( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
+		const c = uniform.value;
 
 		if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) {
 
@@ -230,12 +230,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	updateMatrix3( m, offset ) {
+	updateMatrix3( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
-		const e = m.elements;
+		const e = uniform.value.elements;
 
 		if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] ||
 			a[ offset + 4 ] !== e[ 3 ] || a[ offset + 5 ] !== e[ 4 ] || a[ offset + 6 ] !== e[ 5 ] ||
@@ -259,12 +259,12 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 
 	}
 
-	updateMatrix4( m, offset ) {
+	updateMatrix4( uniform, offset ) {
 
 		let updated = false;
 
 		const a = this.array;
-		const e = m.elements;
+		const e = uniform.value.elements;
 
 		if ( arraysEqual( a, e, offset ) === false ) {
 

+ 7 - 12
examples/jsm/webxr/ARButton.js

@@ -1,21 +1,17 @@
-var ARButton = {
+class ARButton {
 
-	createButton: function ( renderer, sessionInit = {} ) {
+	static createButton( renderer, sessionInit = {} ) {
+
+		const button = document.createElement( 'button' );
 
 		function showStartAR( /*device*/ ) {
 
-			var currentSession = null;
+			let currentSession = null;
 
 			function onSessionStarted( session ) {
 
 				session.addEventListener( 'end', onSessionEnded );
 
-				/*
-				session.updateWorldTrackingState( {
-					'planeDetectionState': { 'enabled': true }
-				} );
-				*/
-
 				renderer.xr.setReferenceSpaceType( 'local' );
 				renderer.xr.setSession( session );
 				button.textContent = 'STOP AR';
@@ -114,7 +110,6 @@ var ARButton = {
 
 		if ( 'xr' in navigator ) {
 
-			var button = document.createElement( 'button' );
 			button.id = 'ARButton';
 			button.style.display = 'none';
 
@@ -130,7 +125,7 @@ var ARButton = {
 
 		} else {
 
-			var message = document.createElement( 'a' );
+			const message = document.createElement( 'a' );
 
 			if ( window.isSecureContext === false ) {
 
@@ -156,6 +151,6 @@ var ARButton = {
 
 	}
 
-};
+}
 
 export { ARButton };

+ 8 - 7
examples/jsm/webxr/VRButton.js

@@ -1,6 +1,6 @@
-var VRButton = {
+class VRButton {
 
-	createButton: function ( renderer, options ) {
+	static createButton( renderer, options ) {
 
 		if ( options ) {
 
@@ -8,9 +8,11 @@ var VRButton = {
 
 		}
 
+		const button = document.createElement( 'button' );
+
 		function showEnterVR( /*device*/ ) {
 
-			var currentSession = null;
+			let currentSession = null;
 
 			function onSessionStarted( session ) {
 
@@ -66,7 +68,7 @@ var VRButton = {
 					// ('local' is always available for immersive sessions and doesn't need to
 					// be requested separately.)
 
-					var sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking' ] };
+					const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking' ] };
 					navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
 
 				} else {
@@ -121,7 +123,6 @@ var VRButton = {
 
 		if ( 'xr' in navigator ) {
 
-			var button = document.createElement( 'button' );
 			button.id = 'VRButton';
 			button.style.display = 'none';
 
@@ -137,7 +138,7 @@ var VRButton = {
 
 		} else {
 
-			var message = document.createElement( 'a' );
+			const message = document.createElement( 'a' );
 
 			if ( window.isSecureContext === false ) {
 
@@ -163,6 +164,6 @@ var VRButton = {
 
 	}
 
-};
+}
 
 export { VRButton };

+ 4 - 3
examples/misc_controls_pointerlock.html

@@ -287,6 +287,8 @@
 
 				requestAnimationFrame( animate );
 
+				var time = performance.now();
+
 				if ( controls.isLocked === true ) {
 
 					raycaster.ray.origin.copy( controls.getObject().position );
@@ -296,7 +298,6 @@
 
 					var onObject = intersections.length > 0;
 
-					var time = performance.now();
 					var delta = ( time - prevTime ) / 1000;
 
 					velocity.x -= velocity.x * 10.0 * delta;
@@ -332,10 +333,10 @@
 
 					}
 
-					prevTime = time;
-
 				}
 
+				prevTime = time;
+
 				renderer.render( scene, camera );
 
 			}

+ 11 - 11
examples/webgl_camera_logarithmicdepthbuffer.html

@@ -6,6 +6,9 @@
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<link type="text/css" rel="stylesheet" href="main.css">
 		<style>
+			body{
+				touch-action: none;
+			}
 			.renderer_label {
 				position: absolute;
 				bottom: 1em;
@@ -122,7 +125,7 @@
 
 				// Resize border allows the user to easily compare effects of logarithmic depth buffer over the whole scene
 				border = document.getElementById( 'renderer_border' );
-				border.addEventListener( 'mousedown', onBorderMouseDown );
+				border.addEventListener( 'pointerdown', onBorderPointerDown );
 
 				window.addEventListener( 'mousemove', onMouseMove, false );
 				window.addEventListener( 'resize', onWindowResize, false );
@@ -289,27 +292,24 @@
 
 			}
 
-			function onBorderMouseDown( ev ) {
+			function onBorderPointerDown() {
 
 				// activate draggable window resizing bar
-				window.addEventListener( "mousemove", onBorderMouseMove );
-				window.addEventListener( "mouseup", onBorderMouseUp );
-				ev.stopPropagation();
-				ev.preventDefault();
+				window.addEventListener( "pointermove", onBorderPointerMove );
+				window.addEventListener( "pointerup", onBorderPointerUp );
 
 			}
 
-			function onBorderMouseMove( ev ) {
+			function onBorderPointerMove( ev ) {
 
 				screensplit = Math.max( 0, Math.min( 1, ev.clientX / window.innerWidth ) );
-				ev.stopPropagation();
 
 			}
 
-			function onBorderMouseUp() {
+			function onBorderPointerUp() {
 
-				window.removeEventListener( "mousemove", onBorderMouseMove );
-				window.removeEventListener( "mouseup", onBorderMouseUp );
+				window.removeEventListener( "pointermove", onBorderPointerMove );
+				window.removeEventListener( "pointerup", onBorderPointerUp );
 
 			}
 

File diff suppressed because it is too large
+ 300 - 322
package-lock.json


+ 1 - 2
package.json

@@ -55,7 +55,6 @@
     "start": "npm run dev",
     "test": "npm run test-lint && npm run test-unit",
     "build": "rollup -c utils/build/rollup.config.js",
-    "build-closure": "npm run build && google-closure-compiler --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",
     "dev": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c utils/build/rollup.config.js -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 utils/build/rollup.config.js -w -m inline\" \"npm run dev --prefix test\" \"http-server -p 8080\"",
     "lint-fix": "eslint src --ext js --ext ts --fix && eslint examples/js/ --ext js --ext ts --ignore-pattern libs --fix",
@@ -95,10 +94,10 @@
     "eslint": "^7.6.0",
     "eslint-config-mdcs": "^5.0.0",
     "eslint-plugin-html": "^6.0.3",
-    "google-closure-compiler": "20200719.0.0",
     "http-server": "^0.12.3",
     "rollup": "^2.23.1",
     "rollup-plugin-buble": "^0.19.8",
+    "rollup-plugin-terser": "^7.0.2",
     "typescript": "^4.0.2"
   },
   "jspm": {

+ 1 - 1
src/core/Face3.d.ts

@@ -89,7 +89,7 @@ export class Face3 {
 	vertexColors: Color[];
 
 	/**
-	 * Material index (points to {@link Geometry.materials}).
+	 * Material index (points to {@link Mesh.material}).
 	 * @default 0
 	 */
 	materialIndex: number;

+ 0 - 14
src/core/GLBufferAttribute.js

@@ -1,17 +1,3 @@
-/**
- * @author raub / https://github.com/raub
- */
-
-/**
- * Element size is one of:
- * gl.FLOAT: 4
- * gl.UNSIGNED_SHORT: 2
- * gl.SHORT: 2
- * gl.UNSIGNED_INT: 4
- * gl.INT: 4
- * gl.BYTE: 1
- * gl.UNSIGNED_BYTE: 1
- */
 function GLBufferAttribute( buffer, type, itemSize, elementSize, count ) {
 
 	this.buffer = buffer;

+ 0 - 2
src/core/Geometry.d.ts

@@ -32,8 +32,6 @@ export interface MorphNormals {
 	normals: Vector3[];
 }
 
-export let GeometryIdCount: number;
-
 /**
  * Base class for geometries
  *

+ 0 - 2
src/core/Object3D.d.ts

@@ -15,8 +15,6 @@ import { EventDispatcher } from './EventDispatcher';
 import { BufferGeometry } from './BufferGeometry';
 import { Intersection } from './Raycaster';
 
-export let Object3DIdCount: number;
-
 /**
  * Base class for scene graph objects
  */

+ 81 - 46
src/geometries/EdgesGeometry.js

@@ -1,7 +1,13 @@
 import { BufferGeometry } from '../core/BufferGeometry.js';
 import { Float32BufferAttribute } from '../core/BufferAttribute.js';
-import { Geometry } from '../core/Geometry.js';
 import { MathUtils } from '../math/MathUtils.js';
+import { Triangle } from '../math/Triangle.js';
+import { Vector3 } from '../math/Vector3.js';
+
+const _v0 = new Vector3();
+const _v1 = new Vector3();
+const _normal = new Vector3();
+const _triangle = new Triangle();
 
 class EdgesGeometry extends BufferGeometry {
 
@@ -17,94 +23,123 @@ class EdgesGeometry extends BufferGeometry {
 
 		thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;
 
-		// buffer
+		if ( geometry.isGeometry ) {
 
-		const vertices = [];
+			geometry = new BufferGeometry().fromGeometry( geometry );
 
-		// helper variables
+		}
 
+		const precisionPoints = 4;
+		const precision = Math.pow( 10, precisionPoints );
 		const thresholdDot = Math.cos( MathUtils.DEG2RAD * thresholdAngle );
-		const edge = [ 0, 0 ], edges = {};
-		let edge1, edge2, key;
-		const keys = [ 'a', 'b', 'c' ];
 
-		// prepare source geometry
+		const indexAttr = geometry.getIndex();
+		const positionAttr = geometry.getAttribute( 'position' );
+		const indexCount = indexAttr ? indexAttr.count : positionAttr.count;
 
-		let geometry2;
+		const indexArr = [ 0, 0, 0 ];
+		const vertKeys = [ 'a', 'b', 'c' ];
+		const hashes = new Array( 3 );
 
-		if ( geometry.isBufferGeometry ) {
+		const edgeData = {};
+		const vertices = [];
+		for ( let i = 0; i < indexCount; i += 3 ) {
 
-			geometry2 = new Geometry();
-			geometry2.fromBufferGeometry( geometry );
+			if ( indexAttr ) {
 
-		} else {
+				indexArr[ 0 ] = indexAttr.getX( i );
+				indexArr[ 1 ] = indexAttr.getX( i + 1 );
+				indexArr[ 2 ] = indexAttr.getX( i + 2 );
 
-			geometry2 = geometry.clone();
+			} else {
 
-		}
+				indexArr[ 0 ] = i;
+				indexArr[ 1 ] = i + 1;
+				indexArr[ 2 ] = i + 2;
 
-		geometry2.mergeVertices();
-		geometry2.computeFaceNormals();
+			}
+
+			const { a, b, c } = _triangle;
+			a.fromBufferAttribute( positionAttr, indexArr[ 0 ] );
+			b.fromBufferAttribute( positionAttr, indexArr[ 1 ] );
+			c.fromBufferAttribute( positionAttr, indexArr[ 2 ] );
+			_triangle.getNormal( _normal );
 
-		const sourceVertices = geometry2.vertices;
-		const faces = geometry2.faces;
+			// create hashes for the edge from the vertices
+			hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`;
+			hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`;
+			hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`;
 
-		// now create a data structure where each entry represents an edge with its adjoining faces
+			// skip degenerate triangles
+			if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) {
 
-		for ( let i = 0, l = faces.length; i < l; i ++ ) {
+				continue;
 
-			const face = faces[ i ];
+			}
 
+			// iterate over every edge
 			for ( let j = 0; j < 3; j ++ ) {
 
-				edge1 = face[ keys[ j ] ];
-				edge2 = face[ keys[ ( j + 1 ) % 3 ] ];
-				edge[ 0 ] = Math.min( edge1, edge2 );
-				edge[ 1 ] = Math.max( edge1, edge2 );
+				// get the first and next vertex making up the edge
+				const jNext = ( j + 1 ) % 3;
+				const vecHash0 = hashes[ j ];
+				const vecHash1 = hashes[ jNext ];
+				const v0 = _triangle[ vertKeys[ j ] ];
+				const v1 = _triangle[ vertKeys[ jNext ] ];
 
-				key = edge[ 0 ] + ',' + edge[ 1 ];
+				const hash = `${ vecHash0 }_${ vecHash1 }`;
+				const reverseHash = `${ vecHash1 }_${ vecHash0 }`;
 
-				if ( edges[ key ] === undefined ) {
+				if ( reverseHash in edgeData && edgeData[ reverseHash ] ) {
 
-					edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };
+					// if we found a sibling edge add it into the vertex array if
+					// it meets the angle threshold and delete the edge from the map.
+					if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) {
 
-				} else {
+						vertices.push( v0.x, v0.y, v0.z );
+						vertices.push( v1.x, v1.y, v1.z );
 
-					edges[ key ].face2 = i;
+					}
 
-				}
+					edgeData[ reverseHash ] = null;
 
-			}
+				} else if ( ! ( hash in edgeData ) ) {
 
-		}
+					// if we've already got an edge here then skip adding a new one
+					edgeData[ hash ] = {
 
-		// generate vertices
+						index0: indexArr[ j ],
+						index1: indexArr[ jNext ],
+						normal: _normal.clone(),
 
-		for ( key in edges ) {
+					};
 
-			const e = edges[ key ];
+				}
 
-			// an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.
+			}
 
-			if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {
+		}
 
-				let vertex = sourceVertices[ e.index1 ];
-				vertices.push( vertex.x, vertex.y, vertex.z );
+		// iterate over all remaining, unmatched edges and add them to the vertex array
+		for ( const key in edgeData ) {
 
-				vertex = sourceVertices[ e.index2 ];
-				vertices.push( vertex.x, vertex.y, vertex.z );
+			if ( edgeData[ key ] ) {
+
+				const { index0, index1 } = edgeData[ key ];
+				_v0.fromBufferAttribute( positionAttr, index0 );
+				_v1.fromBufferAttribute( positionAttr, index1 );
+
+				vertices.push( _v0.x, _v0.y, _v0.z );
+				vertices.push( _v1.x, _v1.y, _v1.z );
 
 			}
 
 		}
 
-		// build geometry
-
 		this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
 
 	}
 
 }
 
-
 export { EdgesGeometry };

+ 0 - 4
src/lights/AmbientLight.d.ts

@@ -20,10 +20,6 @@ export class AmbientLight extends Light {
 	 */
 	type: string;
 
-	/**
-	 * @default undefined
-	 */
-	castShadow: boolean;
 	readonly isAmbientLight: true;
 
 }

+ 0 - 2
src/lights/AmbientLight.js

@@ -6,8 +6,6 @@ function AmbientLight( color, intensity ) {
 
 	this.type = 'AmbientLight';
 
-	this.castShadow = undefined;
-
 }
 
 AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {

+ 0 - 5
src/lights/HemisphereLight.d.ts

@@ -25,11 +25,6 @@ export class HemisphereLight extends Light {
 	 */
 	position: Vector3;
 
-	/**
-	 * @default undefined
-	 */
-	castShadow: boolean;
-
 	groundColor: Color;
 
 	readonly isHemisphereLight: true;

+ 0 - 2
src/lights/HemisphereLight.js

@@ -8,8 +8,6 @@ function HemisphereLight( skyColor, groundColor, intensity ) {
 
 	this.type = 'HemisphereLight';
 
-	this.castShadow = undefined;
-
 	this.position.copy( Object3D.DefaultUp );
 	this.updateMatrix();
 

+ 0 - 4
src/lights/Light.d.ts

@@ -24,10 +24,6 @@ export class Light extends Object3D {
 	intensity: number;
 	readonly isLight: true;
 
-	/**
-	 * @default undefined
-	 */
-	receiveShadow: boolean;
 	shadow: LightShadow;
 	/**
 	 * @deprecated Use shadow.camera.fov instead.

+ 0 - 2
src/lights/Light.js

@@ -10,8 +10,6 @@ function Light( color, intensity ) {
 	this.color = new Color( color );
 	this.intensity = intensity !== undefined ? intensity : 1;
 
-	this.receiveShadow = undefined;
-
 }
 
 Light.prototype = Object.assign( Object.create( Object3D.prototype ), {

+ 4 - 1
src/loaders/ImageBitmapLoader.js

@@ -63,7 +63,10 @@ ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 		}
 
-		fetch( url ).then( function ( res ) {
+		const fetchOptions = {};
+		fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+
+		fetch( url, fetchOptions ).then( function ( res ) {
 
 			return res.blob();
 

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

@@ -13,9 +13,6 @@ import {
 	StencilOp
 } from '../constants';
 
-// Materials //////////////////////////////////////////////////////////////////////////////////
-export let MaterialIdCount: number;
-
 export interface MaterialParameters {
 	alphaTest?: number;
 	blendDst?: BlendingDstFactor;

+ 0 - 10
src/materials/ShaderMaterial.d.ts

@@ -2,16 +2,6 @@ import { IUniform } from '../renderers/shaders/UniformsLib';
 import { MaterialParameters, Material } from './Material';
 import { GLSLVersion } from '../constants';
 
-/**
- * @deprecated Use {@link PointsMaterial THREE.PointsMaterial} instead
- */
-/**
- * @deprecated Use {@link PointsMaterial THREE.PointsMaterial} instead
- */
-/**
- * @deprecated Use {@link PointsMaterial THREE.PointsMaterial} instead
- */
-
 export interface ShaderMaterialParameters extends MaterialParameters {
 	uniforms?: { [uniform: string]: IUniform };
 	vertexShader?: string;

+ 0 - 20
src/math/Vector2.d.ts

@@ -450,26 +450,6 @@ export class Vector2 implements Vector {
 	 */
 	rotateAround( center: Vector2, angle: number ): this;
 
-	/**
-	 * Computes the Manhattan length of this vector.
-	 *
-	 * @return {number}
-	 *
-	 * @see {@link http://en.wikipedia.org/wiki/Taxicab_geometry|Wikipedia: Taxicab Geometry}
-	 */
-	manhattanLength(): number;
-
-	/**
-	 * Computes the Manhattan length (distance) from this vector to the given vector v
-	 *
-	 * @param {Vector2} v
-	 *
-	 * @return {number}
-	 *
-	 * @see {@link http://en.wikipedia.org/wiki/Taxicab_geometry|Wikipedia: Taxicab Geometry}
-	 */
-	manhattanDistanceTo( v: Vector2 ): number;
-
 	/**
 	 * Sets this vector's x and y from Math.random
 	 */

+ 1 - 3
src/objects/Points.d.ts

@@ -6,9 +6,7 @@ import { BufferGeometry } from '../core/BufferGeometry';
 import { Intersection } from '../core/Raycaster';
 
 /**
- * A class for displaying particles in the form of variable size points. For example, if using the WebGLRenderer, the particles are displayed using GL_POINTS.
- *
- * @see {@link https://github.com/mrdoob/three.js/blob/master/src/objects/ParticleSystem.js|src/objects/ParticleSystem.js}
+ * A class for displaying points. The points are rendered by the WebGLRenderer using gl.POINTS.
  */
 export class Points <
 	TGeometry extends Geometry | BufferGeometry = Geometry | BufferGeometry,

+ 1 - 1
src/renderers/webgl/WebGLShadowMap.d.ts

@@ -36,7 +36,7 @@ export class WebGLShadowMap {
 	render( shadowsArray: Light[], scene: Scene, camera: Camera ): void;
 
 	/**
-	 * @deprecated Use {@link WebGLShadowMap#renderReverseSided .shadowMap.renderReverseSided} instead.
+	 * @deprecated Use {@link Material#shadowSide} instead.
 	 */
 	cullFace: any;
 

+ 2 - 1
src/renderers/webxr/WebXRManager.d.ts

@@ -1,7 +1,8 @@
 import { Group } from '../../objects/Group';
 import { Camera } from '../../cameras/Camera';
+import { EventDispatcher } from '../../core/EventDispatcher';
 
-export class WebXRManager {
+export class WebXRManager extends EventDispatcher {
 
 	constructor( renderer: any, gl: WebGLRenderingContext );
 

+ 0 - 3
src/textures/Texture.d.ts

@@ -11,9 +11,6 @@ import {
 	TextureEncoding
 } from '../constants';
 
-// Textures /////////////////////////////////////////////////////////////////////
-export let TextureIdCount: number;
-
 export class Texture extends EventDispatcher {
 
 	/**

+ 1 - 1
test/unit/src/geometries/EdgesGeometry.tests.js

@@ -285,7 +285,7 @@ export default QUnit.module( 'Geometries', () => {
 
 		QUnit.test( "three triangles, coplanar first", ( assert ) => {
 
-			testEdges( vertList, [ 0, 1, 2, 0, 2, 3, 0, 4, 2 ], 7, assert );
+			testEdges( vertList, [ 0, 2, 3, 0, 1, 2, 0, 4, 2 ], 7, assert );
 
 		} );
 

+ 43 - 2
utils/build/rollup.config.js

@@ -1,4 +1,5 @@
 import buble from 'rollup-plugin-buble';
+import { terser } from "rollup-plugin-terser";
 
 function glconstants() {
 
@@ -240,6 +241,20 @@ function bubleCleanup() {
 
 }
 
+function header() {
+
+	return {
+
+		renderChunk( code ) {
+
+			return "// threejs.org/license\n" + code;
+
+		}
+
+	};
+
+}
+
 export default [
 	{
 		input: 'src/Three.js',
@@ -252,7 +267,8 @@ export default [
 					classes: true
 				}
 			} ),
-			bubleCleanup()
+			bubleCleanup(),
+			header()
 		],
 		output: [
 			{
@@ -267,7 +283,32 @@ export default [
 		input: 'src/Three.js',
 		plugins: [
 			glconstants(),
-			glsl()
+			glsl(),
+			buble( {
+				transforms: {
+					arrow: false,
+					classes: true
+				}
+			} ),
+			bubleCleanup(),
+			terser(),
+			header()
+		],
+		output: [
+			{
+				format: 'umd',
+				name: 'THREE',
+				file: 'build/three.min.js',
+				indent: '\t'
+			}
+		]
+	},
+	{
+		input: 'src/Three.js',
+		plugins: [
+			glconstants(),
+			glsl(),
+			header()
 		],
 		output: [
 			{

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