Просмотр исходного кода

USDLoader: Various bug fixes and improvements (#32766)

mrdoob 1 месяц назад
Родитель
Сommit
c6515c3bf9

+ 3 - 3
examples/jsm/loaders/usd/USDAParser.js

@@ -103,7 +103,7 @@ class USDAParser {
 
 				target = stack[ stack.length - 1 ];
 
-			} else {
+			} else if ( line.trim() ) {
 
 				string = line.trim();
 
@@ -660,12 +660,12 @@ class USDAParser {
 		}
 
 		// Quaternion types (quatf, quatd, quath)
+		// Text format is (w, x, y, z), convert to (x, y, z, w)
 		if ( valueType.startsWith( 'quat' ) ) {
 
-			// Parse (w, x, y, z) format
 			const cleaned = str.replace( /[()]/g, '' );
 			const values = cleaned.split( ',' ).map( s => parseFloat( s.trim() ) );
-			return values;
+			return [ values[ 1 ], values[ 2 ], values[ 3 ], values[ 0 ] ];
 
 		}
 

+ 33 - 0
examples/jsm/loaders/usd/USDCParser.js

@@ -1182,6 +1182,39 @@ class USDCParser {
 
 			}
 
+			case TypeEnum.Matrix2d: {
+
+				// Inlined Matrix2d stores diagonal values as 2 signed int8 values
+				const buf = new ArrayBuffer( 4 );
+				const view = new DataView( buf );
+				view.setUint32( 0, payload, true );
+				const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 );
+				return [ d0, 0, 0, d1 ];
+
+			}
+
+			case TypeEnum.Matrix3d: {
+
+				// Inlined Matrix3d stores diagonal values as 3 signed int8 values
+				const buf = new ArrayBuffer( 4 );
+				const view = new DataView( buf );
+				view.setUint32( 0, payload, true );
+				const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 );
+				return [ d0, 0, 0, 0, d1, 0, 0, 0, d2 ];
+
+			}
+
+			case TypeEnum.Matrix4d: {
+
+				// Inlined Matrix4d stores diagonal values as 4 signed int8 values
+				const buf = new ArrayBuffer( 4 );
+				const view = new DataView( buf );
+				view.setUint32( 0, payload, true );
+				const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 ), d3 = view.getInt8( 3 );
+				return [ d0, 0, 0, 0, 0, d1, 0, 0, 0, 0, d2, 0, 0, 0, 0, d3 ];
+
+			}
+
 			default:
 				return payload;
 

+ 146 - 18
examples/jsm/loaders/usd/USDComposer.js

@@ -232,9 +232,7 @@ class USDComposer {
 					const q = data[ 'xformOp:orient' ];
 					if ( q && q.length === 4 ) {
 
-						// USD quaternion format is (w, x, y, z) - real part first
-						// Three.js Quaternion is (x, y, z, w)
-						const quat = new Quaternion( q[ 1 ], q[ 2 ], q[ 3 ], q[ 0 ] );
+						const quat = new Quaternion( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
 						tempMatrix.makeRotationFromQuaternion( quat );
 						if ( isInverse ) tempMatrix.invert();
 						matrix.multiply( tempMatrix );
@@ -298,7 +296,7 @@ class USDComposer {
 			const q = data[ 'xformOp:orient' ];
 			if ( q.length === 4 ) {
 
-				obj.quaternion.set( q[ 1 ], q[ 2 ], q[ 3 ], q[ 0 ] );
+				obj.quaternion.set( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
 
 			}
 
@@ -1091,6 +1089,7 @@ class USDComposer {
 		}
 
 		const { uvs, uvIndices } = this._findUVPrimvar( fields );
+		const numFaceVertices = faceVertexIndices ? faceVertexIndices.length : 0;
 
 		if ( uvs && uvs.length > 0 ) {
 
@@ -1105,6 +1104,15 @@ class USDComposer {
 
 				uvData = this._expandAttribute( uvs, indices, 2 );
 
+			} else if ( triPattern && uvs.length / 2 === numFaceVertices ) {
+
+				// Per-face-vertex UVs (faceVarying, no separate indices)
+				const uvIndicesFromPattern = this._applyTriangulationPattern(
+					Array.from( { length: numFaceVertices }, ( _, i ) => i ),
+					triPattern
+				);
+				uvData = this._expandAttribute( uvs, uvIndicesFromPattern, 2 );
+
 			}
 
 			geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( uvData ), 2 ) );
@@ -1127,6 +1135,15 @@ class USDComposer {
 
 				uv2Data = this._expandAttribute( uvs2, indices, 2 );
 
+			} else if ( triPattern && uvs2.length / 2 === numFaceVertices ) {
+
+				// Per-face-vertex UV2 (faceVarying, no separate indices)
+				const uv2IndicesFromPattern = this._applyTriangulationPattern(
+					Array.from( { length: numFaceVertices }, ( _, i ) => i ),
+					triPattern
+				);
+				uv2Data = this._expandAttribute( uvs2, uv2IndicesFromPattern, 2 );
+
 			}
 
 			geometry.setAttribute( 'uv1', new BufferAttribute( new Float32Array( uv2Data ), 2 ) );
@@ -2300,6 +2317,22 @@ class USDComposer {
 
 		}
 
+		// Specular color
+		applyTextureFromConnection(
+			'inputs:specularColor',
+			'specularColorMap',
+			SRGBColorSpace,
+			( color ) => {
+
+				if ( Array.isArray( color ) && color.length >= 3 ) {
+
+					material.specularColor.setRGB( color[ 0 ], color[ 1 ], color[ 2 ], SRGBColorSpace );
+
+				}
+
+			}
+		);
+
 		// Clearcoat
 		if ( fields[ 'inputs:clearcoat' ] !== undefined ) {
 
@@ -2315,25 +2348,33 @@ class USDComposer {
 		}
 
 		// Opacity and opacity modes
-		const opacity = fields[ 'inputs:opacity' ] !== undefined ? fields[ 'inputs:opacity' ] : 1.0;
 		const opacityThreshold = fields[ 'inputs:opacityThreshold' ] !== undefined ? fields[ 'inputs:opacityThreshold' ] : 0.0;
-		const opacityMode = fields[ 'inputs:opacityMode' ] || 'transparent';
 
-		if ( opacityMode === 'presence' ) {
+		// Check if opacity is connected to a texture (e.g., diffuse texture's alpha)
+		const opacitySpec = getAttrSpec( 'inputs:opacity' );
+		const hasOpacityConnection = opacitySpec?.fields?.connectionPaths?.length > 0;
 
-			// Presence mode: use alpha testing (cutout transparency)
-			material.alphaTest = opacityThreshold;
-			material.transparent = false;
+		if ( hasOpacityConnection ) {
 
-			if ( opacity < 1.0 ) {
+			// Opacity from texture alpha - use the diffuse map's alpha channel
+			if ( opacityThreshold > 0 ) {
 
-				material.opacity = opacity;
+				// Alpha cutoff mode
+				material.alphaTest = opacityThreshold;
+				material.transparent = false;
+
+			} else {
+
+				// Alpha blend mode
+				material.transparent = true;
 
 			}
 
-		} else if ( opacityMode === 'transparent' ) {
+		} else {
+
+			// Direct opacity value
+			const opacity = fields[ 'inputs:opacity' ] !== undefined ? fields[ 'inputs:opacity' ] : 1.0;
 
-			// Transparent mode: traditional alpha blending
 			if ( opacity < 1.0 ) {
 
 				material.transparent = true;
@@ -2342,7 +2383,6 @@ class USDComposer {
 			}
 
 		}
-		// opacityMode === 'opaque': ignore opacity entirely (default material state)
 
 	}
 
@@ -2896,8 +2936,18 @@ class USDComposer {
 		if ( cleanPath.startsWith( '@' ) ) cleanPath = cleanPath.slice( 1 );
 		if ( cleanPath.endsWith( '@' ) ) cleanPath = cleanPath.slice( 0, - 1 );
 
-		const assetData = this.assets[ cleanPath ];
+		// Resolve relative to basePath first
+		const resolvedPath = this._resolveFilePath( cleanPath );
+		let assetData = this.assets[ resolvedPath ];
+
+		// Fallback to unresolved path
+		if ( ! assetData ) {
+
+			assetData = this.assets[ cleanPath ];
 
+		}
+
+		// Last resort: search by basename
 		if ( ! assetData ) {
 
 			const baseName = cleanPath.split( '/' ).pop();
@@ -3242,9 +3292,8 @@ class USDComposer {
 
 					keyframeTimes.push( times[ i ] / this.fps );
 
-					// Convert USD quaternion (w, x, y, z) to Three.js (x, y, z, w)
 					const q = values[ i ];
-					keyframeValues.push( q[ 1 ], q[ 2 ], q[ 3 ], q[ 0 ] );
+					keyframeValues.push( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
 
 				}
 
@@ -3320,6 +3369,85 @@ class USDComposer {
 
 			}
 
+			// Check for animated xformOp:transform (matrix animations)
+			// These can have suffixes like xformOp:transform:transform
+			const properties = spec.fields?.properties || [];
+			for ( const prop of properties ) {
+
+				if ( ! prop.startsWith( 'xformOp:transform' ) ) continue;
+
+				const transformPath = path + '.' + prop;
+				const transformSpec = this.specsByPath[ transformPath ];
+
+				if ( ! transformSpec?.fields?.timeSamples ) continue;
+
+				const { times, values } = transformSpec.fields.timeSamples;
+				const positionTimes = [];
+				const positionValues = [];
+				const quaternionTimes = [];
+				const quaternionValues = [];
+				const scaleTimes = [];
+				const scaleValues = [];
+
+				const matrix = new Matrix4();
+				const position = new Vector3();
+				const quaternion = new Quaternion();
+				const scale = new Vector3();
+
+				for ( let i = 0; i < times.length; i ++ ) {
+
+					const m = values[ i ];
+					if ( ! m || m.length < 16 ) continue;
+
+					const t = times[ i ] / this.fps;
+
+					// USD matrices are row-major, Three.js is column-major
+					matrix.set(
+						m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
+						m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ],
+						m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ],
+						m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
+					);
+
+					matrix.decompose( position, quaternion, scale );
+
+					positionTimes.push( t );
+					positionValues.push( position.x, position.y, position.z );
+
+					quaternionTimes.push( t );
+					quaternionValues.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
+
+					scaleTimes.push( t );
+					scaleValues.push( scale.x, scale.y, scale.z );
+
+				}
+
+				if ( positionTimes.length > 0 ) {
+
+					tracks.push( new VectorKeyframeTrack(
+						objectName + '.position',
+						new Float32Array( positionTimes ),
+						new Float32Array( positionValues )
+					) );
+
+					tracks.push( new QuaternionKeyframeTrack(
+						objectName + '.quaternion',
+						new Float32Array( quaternionTimes ),
+						new Float32Array( quaternionValues )
+					) );
+
+					tracks.push( new VectorKeyframeTrack(
+						objectName + '.scale',
+						new Float32Array( scaleTimes ),
+						new Float32Array( scaleValues )
+					) );
+
+				}
+
+				break; // Only process first transform op
+
+			}
+
 		}
 
 		return tracks;

粤ICP备19079148号