浏览代码

Updated examples builds.

Mr.doob 2 年之前
父节点
当前提交
eea66f09e6

+ 1 - 0
examples/js/interactive/HTMLMesh.js

@@ -29,6 +29,7 @@
 				geometry.dispose();
 				material.dispose();
 				material.map.dispose();
+				canvases.delete( dom );
 				this.removeEventListener( 'mousedown', onEvent );
 				this.removeEventListener( 'mousemove', onEvent );
 				this.removeEventListener( 'mouseup', onEvent );

+ 16 - 3
examples/js/loaders/FBXLoader.js

@@ -1371,7 +1371,13 @@
 
 	class GeometryParser {
 
-		// Parse nodes in FBXTree.Objects.Geometry
+		constructor() {
+
+			this.negativeMaterialIndices = false;
+
+		} // Parse nodes in FBXTree.Objects.Geometry
+
+
 		parse( deformers ) {
 
 			const geometryMap = new Map();
@@ -1388,6 +1394,13 @@
 
 				}
 
+			} // report warnings
+
+
+			if ( this.negativeMaterialIndices === true ) {
+
+				console.warn( 'THREE.FBXLoader: The FBX file contains invalid (negative) material indices. The asset might not render as expected.' );
+
 			}
 
 			return geometryMap;
@@ -1746,8 +1759,8 @@
 
 					if ( materialIndex < 0 ) {
 
-						console.warn( 'THREE.FBXLoader: Invalid material index:', materialIndex );
-						materialIndex = 0;
+						scope.negativeMaterialIndices = true;
+						materialIndex = 0; // fallback
 
 					}
 

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

@@ -69,6 +69,11 @@
 				return new GLTFMeshoptCompression( parser );
 
 			} );
+			this.register( function ( parser ) {
+
+				return new GLTFMeshGpuInstancing( parser );
+
+			} );
 
 		}
 
@@ -373,7 +378,8 @@
 		KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
 		KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength',
 		EXT_TEXTURE_WEBP: 'EXT_texture_webp',
-		EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression'
+		EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression',
+		EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing'
 	};
 	/**
  * Punctual Lights Extension
@@ -1249,6 +1255,146 @@
 
 		}
 
+	}
+	/**
+ * GPU Instancing Extension
+ *
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing
+ *
+ */
+
+
+	class GLTFMeshGpuInstancing {
+
+		constructor( parser ) {
+
+			this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING;
+			this.parser = parser;
+
+		}
+
+		createNodeMesh( nodeIndex ) {
+
+			const json = this.parser.json;
+			const nodeDef = json.nodes[ nodeIndex ];
+
+			if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] || nodeDef.mesh === undefined ) {
+
+				return null;
+
+			}
+
+			const meshDef = json.meshes[ nodeDef.mesh ]; // No THREE.Points or Lines + Instancing support yet
+
+			for ( const primitive of meshDef.primitives ) {
+
+				if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && primitive.mode !== undefined ) {
+
+					return null;
+
+				}
+
+			}
+
+			const extensionDef = nodeDef.extensions[ this.name ];
+			const attributesDef = extensionDef.attributes; // @TODO: Can we support THREE.InstancedMesh + THREE.SkinnedMesh?
+
+			const pending = [];
+			const attributes = {};
+
+			for ( const key in attributesDef ) {
+
+				pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => {
+
+					attributes[ key ] = accessor;
+					return attributes[ key ];
+
+				} ) );
+
+			}
+
+			if ( pending.length < 1 ) {
+
+				return null;
+
+			}
+
+			pending.push( this.parser.createNodeMesh( nodeIndex ) );
+			return Promise.all( pending ).then( results => {
+
+				const nodeObject = results.pop();
+				const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ];
+				const count = results[ 0 ].count; // All attribute counts should be same
+
+				const instancedMeshes = [];
+
+				for ( const mesh of meshes ) {
+
+					// Temporal variables
+					const m = new THREE.Matrix4();
+					const p = new THREE.Vector3();
+					const q = new THREE.Quaternion();
+					const s = new THREE.Vector3( 1, 1, 1 );
+					const instancedMesh = new THREE.InstancedMesh( mesh.geometry, mesh.material, count );
+
+					for ( let i = 0; i < count; i ++ ) {
+
+						if ( attributes.TRANSLATION ) {
+
+							p.fromBufferAttribute( attributes.TRANSLATION, i );
+
+						}
+
+						if ( attributes.ROTATION ) {
+
+							q.fromBufferAttribute( attributes.ROTATION, i );
+
+						}
+
+						if ( attributes.SCALE ) {
+
+							s.fromBufferAttribute( attributes.SCALE, i );
+
+						}
+
+						instancedMesh.setMatrixAt( i, m.compose( p, q, s ) );
+
+					} // Add instance attributes to the geometry, excluding TRS.
+
+
+					for ( const attributeName in attributes ) {
+
+						if ( attributeName !== 'TRANSLATION' && attributeName !== 'ROTATION' && attributeName !== 'SCALE' ) {
+
+							mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] );
+
+						}
+
+					} // Just in case
+
+
+					THREE.Object3D.prototype.copy.call( instancedMesh, mesh ); // https://github.com/mrdoob/three.js/issues/18334
+
+					instancedMesh.frustumCulled = false;
+					this.parser.assignFinalMaterial( instancedMesh );
+					instancedMeshes.push( instancedMesh );
+
+				}
+
+				if ( nodeObject.isGroup ) {
+
+					nodeObject.clear();
+					nodeObject.add( ...instancedMeshes );
+					return nodeObject;
+
+				}
+
+				return instancedMeshes[ 0 ];
+
+			} );
+
+		}
+
 	}
 	/* BINARY EXTENSION */
 

+ 2 - 2
examples/js/loaders/LottieLoader.js

@@ -18,14 +18,14 @@
 			loader.setWithCredentials( this.withCredentials );
 			loader.load( url, function ( text ) {
 
-				const data = JSON.parse( text ); // bodymoving uses container.offetWidth and offsetHeight
+				const data = JSON.parse( text ); // lottie uses container.offetWidth and offsetHeight
 				// to define width/height
 
 				const container = document.createElement( 'div' );
 				container.style.width = data.w + 'px';
 				container.style.height = data.h + 'px';
 				document.body.appendChild( container );
-				const animation = bodymovin.loadAnimation( {
+				const animation = lottie.loadAnimation( {
 					container: container,
 					animType: 'canvas',
 					loop: true,

+ 192 - 12
examples/js/loaders/SVGLoader.js

@@ -1389,7 +1389,89 @@
 
 				}
 
-				const isRotated = isTransformRotated( m );
+				function transfEllipseGeneric( curve ) {
+
+					// For math description see:
+					// https://math.stackexchange.com/questions/4544164
+					const a = curve.xRadius;
+					const b = curve.yRadius;
+					const cosTheta = Math.cos( curve.aRotation );
+					const sinTheta = Math.sin( curve.aRotation );
+					const v1 = new THREE.Vector3( a * cosTheta, a * sinTheta, 0 );
+					const v2 = new THREE.Vector3( - b * sinTheta, b * cosTheta, 0 );
+					const f1 = v1.applyMatrix3( m );
+					const f2 = v2.applyMatrix3( m );
+					const mF = tempTransform0.set( f1.x, f2.x, 0, f1.y, f2.y, 0, 0, 0, 1 );
+					const mFInv = tempTransform1.copy( mF ).invert();
+					const mFInvT = tempTransform2.copy( mFInv ).transpose();
+					const mQ = mFInvT.multiply( mFInv );
+					const mQe = mQ.elements;
+					const ed = eigenDecomposition( mQe[ 0 ], mQe[ 1 ], mQe[ 4 ] );
+					const rt1sqrt = Math.sqrt( ed.rt1 );
+					const rt2sqrt = Math.sqrt( ed.rt2 );
+					curve.xRadius = 1 / rt1sqrt;
+					curve.yRadius = 1 / rt2sqrt;
+					curve.aRotation = Math.atan2( ed.sn, ed.cs );
+					const isFullEllipse = ( curve.aEndAngle - curve.aStartAngle ) % ( 2 * Math.PI ) < Number.EPSILON; // Do not touch angles of a full ellipse because after transformation they
+					// would converge to a sinle value effectively removing the whole curve
+
+					if ( ! isFullEllipse ) {
+
+						const mDsqrt = tempTransform1.set( rt1sqrt, 0, 0, 0, rt2sqrt, 0, 0, 0, 1 );
+						const mRT = tempTransform2.set( ed.cs, ed.sn, 0, - ed.sn, ed.cs, 0, 0, 0, 1 );
+						const mDRF = mDsqrt.multiply( mRT ).multiply( mF );
+
+						const transformAngle = phi => {
+
+							const {
+								x: cosR,
+								y: sinR
+							} = new THREE.Vector3( Math.cos( phi ), Math.sin( phi ), 0 ).applyMatrix3( mDRF );
+							return Math.atan2( sinR, cosR );
+
+						};
+
+						curve.aStartAngle = transformAngle( curve.aStartAngle );
+						curve.aEndAngle = transformAngle( curve.aEndAngle );
+
+						if ( isTransformFlipped( m ) ) {
+
+							curve.aClockwise = ! curve.aClockwise;
+
+						}
+
+					}
+
+				}
+
+				function transfEllipseNoSkew( curve ) {
+
+					// Faster shortcut if no skew is applied
+					// (e.g, a euclidean transform of a group containing the ellipse)
+					const sx = getTransformScaleX( m );
+					const sy = getTransformScaleY( m );
+					curve.xRadius *= sx;
+					curve.yRadius *= sy; // Extract rotation angle from the matrix of form:
+					//
+					//  | cosθ sx   -sinθ sy |
+					//  | sinθ sx    cosθ sy |
+					//
+					// Remembering that tanθ = sinθ / cosθ; and that
+					// `sx`, `sy`, or both might be zero.
+
+					const theta = sx > Number.EPSILON ? Math.atan2( m.elements[ 1 ], m.elements[ 0 ] ) : Math.atan2( - m.elements[ 3 ], m.elements[ 4 ] );
+					curve.aRotation += theta;
+
+					if ( isTransformFlipped( m ) ) {
+
+						curve.aStartAngle *= - 1;
+						curve.aEndAngle *= - 1;
+						curve.aClockwise = ! curve.aClockwise;
+
+					}
+
+				}
+
 				const subPaths = path.subPaths;
 
 				for ( let i = 0, n = subPaths.length; i < n; i ++ ) {
@@ -1421,18 +1503,21 @@
 
 						} else if ( curve.isEllipseCurve ) {
 
-							if ( isRotated ) {
-
-								console.warn( 'SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented.' );
-
-							}
-
+							// Transform ellipse center point
 							tempV2.set( curve.aX, curve.aY );
 							transfVec2( tempV2 );
 							curve.aX = tempV2.x;
-							curve.aY = tempV2.y;
-							curve.xRadius *= getTransformScaleX( m );
-							curve.yRadius *= getTransformScaleY( m );
+							curve.aY = tempV2.y; // Transform ellipse shape parameters
+
+							if ( isTransformSkewed( m ) ) {
+
+								transfEllipseGeneric( curve );
+
+							} else {
+
+								transfEllipseNoSkew( curve );
+
+							}
 
 						}
 
@@ -1442,9 +1527,22 @@
 
 			}
 
-			function isTransformRotated( m ) {
+			function isTransformFlipped( m ) {
 
-				return m.elements[ 1 ] !== 0 || m.elements[ 3 ] !== 0;
+				const te = m.elements;
+				return te[ 0 ] * te[ 4 ] - te[ 1 ] * te[ 3 ] < 0;
+
+			}
+
+			function isTransformSkewed( m ) {
+
+				const te = m.elements;
+				const basisDot = te[ 0 ] * te[ 3 ] + te[ 1 ] * te[ 4 ]; // Shortcut for trivial rotations and transformations
+
+				if ( basisDot === 0 ) return false;
+				const sx = getTransformScaleX( m );
+				const sy = getTransformScaleY( m );
+				return Math.abs( basisDot / ( sx * sy ) ) > Number.EPSILON;
 
 			}
 
@@ -1460,6 +1558,88 @@
 				const te = m.elements;
 				return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] );
 
+			} // Calculates the eigensystem of a real symmetric 2x2 matrix
+			//    [ A  B ]
+			//    [ B  C ]
+			// in the form
+			//    [ A  B ]  =  [ cs  -sn ] [ rt1   0  ] [  cs  sn ]
+			//    [ B  C ]     [ sn   cs ] [  0   rt2 ] [ -sn  cs ]
+			// where rt1 >= rt2.
+			//
+			// Adapted from: https://www.mpi-hd.mpg.de/personalhomes/globes/3x3/index.html
+			// -> Algorithms for real symmetric matrices -> Analytical (2x2 symmetric)
+
+
+			function eigenDecomposition( A, B, C ) {
+
+				let rt1, rt2, cs, sn, t;
+				const sm = A + C;
+				const df = A - C;
+				const rt = Math.sqrt( df * df + 4 * B * B );
+
+				if ( sm > 0 ) {
+
+					rt1 = 0.5 * ( sm + rt );
+					t = 1 / rt1;
+					rt2 = A * t * C - B * t * B;
+
+				} else if ( sm < 0 ) {
+
+					rt2 = 0.5 * ( sm - rt );
+
+				} else {
+
+					// This case needs to be treated separately to avoid div by 0
+					rt1 = 0.5 * rt;
+					rt2 = - 0.5 * rt;
+
+				} // Calculate eigenvectors
+
+
+				if ( df > 0 ) {
+
+					cs = df + rt;
+
+				} else {
+
+					cs = df - rt;
+
+				}
+
+				if ( Math.abs( cs ) > 2 * Math.abs( B ) ) {
+
+					t = - 2 * B / cs;
+					sn = 1 / Math.sqrt( 1 + t * t );
+					cs = t * sn;
+
+				} else if ( Math.abs( B ) === 0 ) {
+
+					cs = 1;
+					sn = 0;
+
+				} else {
+
+					t = - 0.5 * cs / B;
+					cs = 1 / Math.sqrt( 1 + t * t );
+					sn = t * cs;
+
+				}
+
+				if ( df > 0 ) {
+
+					t = cs;
+					cs = - sn;
+					sn = t;
+
+				}
+
+				return {
+					rt1,
+					rt2,
+					cs,
+					sn
+				};
+
 			} //
 
 

+ 12 - 3
examples/js/postprocessing/BloomPass.js

@@ -2,13 +2,15 @@
 
 	class BloomPass extends THREE.Pass {
 
-		constructor( strength = 1, kernelSize = 25, sigma = 4, resolution = 256 ) {
+		constructor( strength = 1, kernelSize = 25, sigma = 4 ) {
 
 			super(); // render targets
 
-			this.renderTargetX = new THREE.WebGLRenderTarget( resolution, resolution );
+			this.renderTargetX = new THREE.WebGLRenderTarget(); // will be resized later
+
 			this.renderTargetX.texture.name = 'BloomPass.x';
-			this.renderTargetY = new THREE.WebGLRenderTarget( resolution, resolution );
+			this.renderTargetY = new THREE.WebGLRenderTarget(); // will be resized later
+
 			this.renderTargetY.texture.name = 'BloomPass.y'; // combine material
 
 			this.combineUniforms = THREE.UniformsUtils.clone( CombineShader.uniforms );
@@ -66,6 +68,13 @@
 
 		}
 
+		setSize( width, height ) {
+
+			this.renderTargetX.setSize( width, height );
+			this.renderTargetY.setSize( width, height );
+
+		}
+
 		dispose() {
 
 			this.renderTargetX.dispose();

+ 8 - 3
examples/js/postprocessing/BokehPass.js

@@ -16,9 +16,8 @@
 			const aperture = params.aperture !== undefined ? params.aperture : 0.025;
 			const maxblur = params.maxblur !== undefined ? params.maxblur : 1.0; // render targets
 
-			const width = params.width || window.innerWidth || 1;
-			const height = params.height || window.innerHeight || 1;
-			this.renderTargetDepth = new THREE.WebGLRenderTarget( width, height, {
+			this.renderTargetDepth = new THREE.WebGLRenderTarget( 1, 1, {
+				// will be resized later
 				minFilter: THREE.NearestFilter,
 				magFilter: THREE.NearestFilter
 			} );
@@ -96,6 +95,12 @@
 
 		}
 
+		setSize( width, height ) {
+
+			this.renderTargetDepth.setSize( width, height );
+
+		}
+
 		dispose() {
 
 			this.renderTargetDepth.dispose();

+ 8 - 1
examples/js/postprocessing/SavePass.js

@@ -18,7 +18,8 @@
 
 			if ( this.renderTarget === undefined ) {
 
-				this.renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight );
+				this.renderTarget = new THREE.WebGLRenderTarget(); // will be resized later
+
 				this.renderTarget.texture.name = 'SavePass.rt';
 
 			}
@@ -44,6 +45,12 @@
 
 		}
 
+		setSize( width, height ) {
+
+			this.renderTarget.setSize( width, height );
+
+		}
+
 		dispose() {
 
 			this.renderTarget.dispose();

+ 1 - 5
examples/js/utils/BufferGeometryUtils.js

@@ -156,11 +156,7 @@
 				if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
 				morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
 
-			} // gather .userData
-
-
-			mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || [];
-			mergedGeometry.userData.mergedUserData.push( geometry.userData );
+			}
 
 			if ( useGroups ) {