|
|
@@ -25,6 +25,9 @@ import {
|
|
|
VectorKeyframeTrack
|
|
|
} from 'three';
|
|
|
|
|
|
+// Pre-compiled regex patterns for performance
|
|
|
+const VARIANT_PATH_REGEX = /^(.+?)\/\{(\w+)=(\w+)\}\/(.+)$/;
|
|
|
+
|
|
|
// Spec types (must match USDCParser)
|
|
|
const SpecType = {
|
|
|
Unknown: 0,
|
|
|
@@ -50,10 +53,11 @@ const SpecType = {
|
|
|
*/
|
|
|
class USDComposer {
|
|
|
|
|
|
- constructor() {
|
|
|
+ constructor( manager = null ) {
|
|
|
|
|
|
this.textureCache = {};
|
|
|
this.skinnedMeshes = [];
|
|
|
+ this.manager = manager;
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -74,6 +78,9 @@ class USDComposer {
|
|
|
this.skinnedMeshes = [];
|
|
|
this.skeletons = {};
|
|
|
|
|
|
+ // Build indexes for O(1) lookups
|
|
|
+ this._buildIndexes();
|
|
|
+
|
|
|
// Get FPS from root spec
|
|
|
const rootSpec = this.specsByPath[ '/' ];
|
|
|
const rootFields = rootSpec ? rootSpec.fields : {};
|
|
|
@@ -304,6 +311,138 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Build indexes for efficient lookups.
|
|
|
+ * Called once during compose() to avoid O(n) scans per lookup.
|
|
|
+ */
|
|
|
+ _buildIndexes() {
|
|
|
+
|
|
|
+ // childrenByPath: parentPath -> [childName1, childName2, ...]
|
|
|
+ this.childrenByPath = new Map();
|
|
|
+
|
|
|
+ // attributesByPrimPath: primPath -> Map(attrName -> attrSpec)
|
|
|
+ this.attributesByPrimPath = new Map();
|
|
|
+
|
|
|
+ // materialsByRoot: rootPath -> [materialPath1, materialPath2, ...]
|
|
|
+ this.materialsByRoot = new Map();
|
|
|
+
|
|
|
+ // shadersByMaterialPath: materialPath -> [shaderPath1, shaderPath2, ...]
|
|
|
+ this.shadersByMaterialPath = new Map();
|
|
|
+
|
|
|
+ // geomSubsetsByMeshPath: meshPath -> [subsetPath1, subsetPath2, ...]
|
|
|
+ this.geomSubsetsByMeshPath = new Map();
|
|
|
+
|
|
|
+ for ( const path in this.specsByPath ) {
|
|
|
+
|
|
|
+ const spec = this.specsByPath[ path ];
|
|
|
+
|
|
|
+ if ( spec.specType === SpecType.Prim ) {
|
|
|
+
|
|
|
+ // Build parent-child index
|
|
|
+ const lastSlash = path.lastIndexOf( '/' );
|
|
|
+
|
|
|
+ if ( lastSlash > 0 ) {
|
|
|
+
|
|
|
+ const parentPath = path.slice( 0, lastSlash );
|
|
|
+ const childName = path.slice( lastSlash + 1 );
|
|
|
+
|
|
|
+ if ( ! this.childrenByPath.has( parentPath ) ) {
|
|
|
+
|
|
|
+ this.childrenByPath.set( parentPath, [] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.childrenByPath.get( parentPath ).push( { name: childName, path: path } );
|
|
|
+
|
|
|
+ } else if ( lastSlash === 0 && path.length > 1 ) {
|
|
|
+
|
|
|
+ // Direct child of root
|
|
|
+ const childName = path.slice( 1 );
|
|
|
+
|
|
|
+ if ( ! this.childrenByPath.has( '/' ) ) {
|
|
|
+
|
|
|
+ this.childrenByPath.set( '/', [] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.childrenByPath.get( '/' ).push( { name: childName, path: path } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ const typeName = spec.fields.typeName;
|
|
|
+
|
|
|
+ // Build material index
|
|
|
+ if ( typeName === 'Material' ) {
|
|
|
+
|
|
|
+ const parts = path.split( '/' );
|
|
|
+ const rootPath = parts.length > 1 ? '/' + parts[ 1 ] : '/';
|
|
|
+
|
|
|
+ if ( ! this.materialsByRoot.has( rootPath ) ) {
|
|
|
+
|
|
|
+ this.materialsByRoot.set( rootPath, [] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.materialsByRoot.get( rootPath ).push( path );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build shader index (shaders are children of materials)
|
|
|
+ if ( typeName === 'Shader' && lastSlash > 0 ) {
|
|
|
+
|
|
|
+ const materialPath = path.slice( 0, lastSlash );
|
|
|
+
|
|
|
+ if ( ! this.shadersByMaterialPath.has( materialPath ) ) {
|
|
|
+
|
|
|
+ this.shadersByMaterialPath.set( materialPath, [] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.shadersByMaterialPath.get( materialPath ).push( path );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build GeomSubset index (subsets are children of meshes)
|
|
|
+ if ( typeName === 'GeomSubset' && lastSlash > 0 ) {
|
|
|
+
|
|
|
+ const meshPath = path.slice( 0, lastSlash );
|
|
|
+
|
|
|
+ if ( ! this.geomSubsetsByMeshPath.has( meshPath ) ) {
|
|
|
+
|
|
|
+ this.geomSubsetsByMeshPath.set( meshPath, [] );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.geomSubsetsByMeshPath.get( meshPath ).push( path );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if ( spec.specType === SpecType.Attribute || spec.specType === SpecType.Relationship ) {
|
|
|
+
|
|
|
+ // Build attribute index
|
|
|
+ const dotIndex = path.lastIndexOf( '.' );
|
|
|
+
|
|
|
+ if ( dotIndex > 0 ) {
|
|
|
+
|
|
|
+ const primPath = path.slice( 0, dotIndex );
|
|
|
+ const attrName = path.slice( dotIndex + 1 );
|
|
|
+
|
|
|
+ if ( ! this.attributesByPrimPath.has( primPath ) ) {
|
|
|
+
|
|
|
+ this.attributesByPrimPath.set( primPath, new Map() );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.attributesByPrimPath.get( primPath ).set( attrName, spec );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Check if a path is a direct child of parentPath.
|
|
|
*/
|
|
|
@@ -327,30 +466,47 @@ class USDComposer {
|
|
|
|
|
|
/**
|
|
|
* Build the scene hierarchy recursively.
|
|
|
+ * Uses childrenByPath index for O(1) child lookup instead of O(n) iteration.
|
|
|
*/
|
|
|
_buildHierarchy( parent, parentPath ) {
|
|
|
|
|
|
- const prefix = parentPath === '/' ? '/' : parentPath + '/';
|
|
|
+ // Collect children from parentPath and any active variant paths
|
|
|
+ const childEntries = [];
|
|
|
+ const seenPaths = new Set();
|
|
|
|
|
|
- // Get variant paths to search
|
|
|
- const variantPaths = this._getVariantPaths( parentPath );
|
|
|
+ // Get direct children using the index
|
|
|
+ const directChildren = this.childrenByPath.get( parentPath );
|
|
|
|
|
|
- for ( const path in this.specsByPath ) {
|
|
|
+ if ( directChildren ) {
|
|
|
|
|
|
- const spec = this.specsByPath[ path ];
|
|
|
+ for ( const child of directChildren ) {
|
|
|
|
|
|
- // Check if direct child of parent or variant paths
|
|
|
- let isChild = this._isDirectChild( parentPath, path, prefix );
|
|
|
+ if ( ! seenPaths.has( child.path ) ) {
|
|
|
|
|
|
- if ( ! isChild ) {
|
|
|
+ seenPaths.add( child.path );
|
|
|
+ childEntries.push( child );
|
|
|
|
|
|
- for ( const vp of variantPaths ) {
|
|
|
+ }
|
|
|
|
|
|
- const vpPrefix = vp + '/';
|
|
|
- if ( this._isDirectChild( vp, path, vpPrefix ) ) {
|
|
|
+ }
|
|
|
|
|
|
- isChild = true;
|
|
|
- break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Also get children from active variant paths
|
|
|
+ const variantPaths = this._getVariantPaths( parentPath );
|
|
|
+
|
|
|
+ for ( const vp of variantPaths ) {
|
|
|
+
|
|
|
+ const variantChildren = this.childrenByPath.get( vp );
|
|
|
+
|
|
|
+ if ( variantChildren ) {
|
|
|
+
|
|
|
+ for ( const child of variantChildren ) {
|
|
|
+
|
|
|
+ if ( ! seenPaths.has( child.path ) ) {
|
|
|
+
|
|
|
+ seenPaths.add( child.path );
|
|
|
+ childEntries.push( child );
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -358,10 +514,14 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
- if ( ! isChild ) continue;
|
|
|
- if ( spec.specType !== SpecType.Prim ) continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Process each child
|
|
|
+ for ( const { name, path } of childEntries ) {
|
|
|
+
|
|
|
+ const spec = this.specsByPath[ path ];
|
|
|
+ if ( ! spec || spec.specType !== SpecType.Prim ) continue;
|
|
|
|
|
|
- const name = path.split( '/' ).pop();
|
|
|
const typeName = spec.fields.typeName;
|
|
|
|
|
|
// Check for references/payloads
|
|
|
@@ -587,7 +747,7 @@ class USDComposer {
|
|
|
// If it's specsByPath data, compose it
|
|
|
if ( referencedData.specsByPath ) {
|
|
|
|
|
|
- const composer = new USDComposer();
|
|
|
+ const composer = new USDComposer( this.manager );
|
|
|
const newBasePath = this._getBasePath( resolvedPath );
|
|
|
const composedGroup = composer.compose( referencedData, this.assets, mergedVariants, newBasePath );
|
|
|
|
|
|
@@ -768,7 +928,7 @@ class USDComposer {
|
|
|
this._collectAttributesFromPath( path, attrs );
|
|
|
|
|
|
// Collect overrides from sibling variants (when path is inside a variant)
|
|
|
- const variantMatch = path.match( /^(.+?)\/\{(\w+)=(\w+)\}\/(.+)$/ );
|
|
|
+ const variantMatch = path.match( VARIANT_PATH_REGEX );
|
|
|
if ( variantMatch ) {
|
|
|
|
|
|
const basePath = variantMatch[ 1 ];
|
|
|
@@ -811,14 +971,12 @@ class USDComposer {
|
|
|
|
|
|
_collectAttributesFromPath( path, attrs ) {
|
|
|
|
|
|
- const prefix = path + '.';
|
|
|
+ // Use the attribute index for O(1) lookup instead of O(n) iteration
|
|
|
+ const attrMap = this.attributesByPrimPath.get( path );
|
|
|
|
|
|
- for ( const attrPath in this.specsByPath ) {
|
|
|
+ if ( ! attrMap ) return;
|
|
|
|
|
|
- if ( ! attrPath.startsWith( prefix ) ) continue;
|
|
|
-
|
|
|
- const attrSpec = this.specsByPath[ attrPath ];
|
|
|
- const attrName = attrPath.slice( prefix.length );
|
|
|
+ for ( const [ attrName, attrSpec ] of attrMap ) {
|
|
|
|
|
|
if ( attrSpec.fields?.default !== undefined ) {
|
|
|
|
|
|
@@ -863,7 +1021,15 @@ class USDComposer {
|
|
|
if ( geomSubsets.length > 0 ) {
|
|
|
|
|
|
geometry = this._buildGeometryWithSubsets( attrs, geomSubsets, hasSkinning );
|
|
|
- material = geomSubsets.map( subset => this._buildMaterialForPath( subset.materialPath ) );
|
|
|
+
|
|
|
+ const meshMaterialPath = this._getMaterialPath( path, spec.fields );
|
|
|
+
|
|
|
+ material = geomSubsets.map( subset => {
|
|
|
+
|
|
|
+ const matPath = subset.materialPath || meshMaterialPath;
|
|
|
+ return this._buildMaterialForPath( matPath );
|
|
|
+
|
|
|
+ } );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
@@ -976,14 +1142,10 @@ class USDComposer {
|
|
|
_getGeomSubsets( meshPath ) {
|
|
|
|
|
|
const subsets = [];
|
|
|
- const prefix = meshPath + '/';
|
|
|
+ const subsetPaths = this.geomSubsetsByMeshPath.get( meshPath );
|
|
|
+ if ( ! subsetPaths ) return subsets;
|
|
|
|
|
|
- for ( const p in this.specsByPath ) {
|
|
|
-
|
|
|
- if ( ! p.startsWith( prefix ) ) continue;
|
|
|
-
|
|
|
- const spec = this.specsByPath[ p ];
|
|
|
- if ( spec.fields.typeName !== 'GeomSubset' ) continue;
|
|
|
+ for ( const p of subsetPaths ) {
|
|
|
|
|
|
const attrs = this._getAttributes( p );
|
|
|
const indices = attrs[ 'indices' ];
|
|
|
@@ -1985,6 +2147,36 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Get the material path for a mesh, checking various binding sources.
|
|
|
+ */
|
|
|
+ _getMaterialPath( meshPath, fields ) {
|
|
|
+
|
|
|
+ let materialPath = null;
|
|
|
+ let materialBinding = fields[ 'material:binding' ];
|
|
|
+
|
|
|
+ if ( ! materialBinding ) {
|
|
|
+
|
|
|
+ const bindingPath = meshPath + '.material:binding';
|
|
|
+ const bindingSpec = this.specsByPath[ bindingPath ];
|
|
|
+ if ( bindingSpec && bindingSpec.specType === SpecType.Relationship ) {
|
|
|
+
|
|
|
+ materialBinding = bindingSpec.fields.targetPaths || bindingSpec.fields.default;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( materialBinding ) {
|
|
|
+
|
|
|
+ materialPath = Array.isArray( materialBinding ) ? materialBinding[ 0 ] : materialBinding;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return materialPath;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
_buildMaterial( meshPath, fields ) {
|
|
|
|
|
|
const material = new MeshPhysicalMaterial();
|
|
|
@@ -2042,20 +2234,23 @@ class USDComposer {
|
|
|
|
|
|
if ( ! materialPath ) {
|
|
|
|
|
|
+ // Use material index for O(1) lookup instead of O(n) iteration
|
|
|
const meshParts = meshPath.split( '/' );
|
|
|
const rootPath = '/' + meshParts[ 1 ];
|
|
|
|
|
|
- for ( const path in this.specsByPath ) {
|
|
|
+ const materialsInRoot = this.materialsByRoot.get( rootPath );
|
|
|
|
|
|
- const spec = this.specsByPath[ path ];
|
|
|
- if ( spec.specType !== SpecType.Prim ) continue;
|
|
|
- if ( spec.fields.typeName !== 'Material' ) continue;
|
|
|
+ if ( materialsInRoot ) {
|
|
|
|
|
|
- if ( path.startsWith( rootPath + '/Looks/' ) ||
|
|
|
- path.startsWith( rootPath + '/Materials/' ) ) {
|
|
|
+ for ( const path of materialsInRoot ) {
|
|
|
|
|
|
- materialPath = path;
|
|
|
- break;
|
|
|
+ if ( path.startsWith( rootPath + '/Looks/' ) ||
|
|
|
+ path.startsWith( rootPath + '/Materials/' ) ) {
|
|
|
+
|
|
|
+ materialPath = path;
|
|
|
+ break;
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -2124,14 +2319,10 @@ class USDComposer {
|
|
|
|
|
|
for ( const materialPath of materialPaths ) {
|
|
|
|
|
|
- const prefix = materialPath + '/';
|
|
|
-
|
|
|
- for ( const path in this.specsByPath ) {
|
|
|
-
|
|
|
- if ( ! path.startsWith( prefix ) ) continue;
|
|
|
+ const shaderPaths = this.shadersByMaterialPath.get( materialPath );
|
|
|
+ if ( ! shaderPaths ) continue;
|
|
|
|
|
|
- const spec = this.specsByPath[ path ];
|
|
|
- if ( spec.fields.typeName !== 'Shader' ) continue;
|
|
|
+ for ( const path of shaderPaths ) {
|
|
|
|
|
|
const attrs = this._getAttributes( path );
|
|
|
if ( attrs[ 'info:id' ] === 'UsdUVTexture' && attrs[ 'inputs:file' ] ) {
|
|
|
@@ -2153,16 +2344,13 @@ class USDComposer {
|
|
|
const materialSpec = this.specsByPath[ materialPath ];
|
|
|
if ( ! materialSpec ) return;
|
|
|
|
|
|
- const prefix = materialPath + '/';
|
|
|
-
|
|
|
- for ( const path in this.specsByPath ) {
|
|
|
+ const shaderPaths = this.shadersByMaterialPath.get( materialPath );
|
|
|
+ if ( ! shaderPaths ) return;
|
|
|
|
|
|
- if ( ! path.startsWith( prefix ) ) continue;
|
|
|
+ for ( const path of shaderPaths ) {
|
|
|
|
|
|
const spec = this.specsByPath[ path ];
|
|
|
- const typeName = spec.fields.typeName;
|
|
|
-
|
|
|
- if ( typeName !== 'Shader' ) continue;
|
|
|
+ if ( ! spec ) continue;
|
|
|
|
|
|
const shaderAttrs = this._getAttributes( path );
|
|
|
const infoId = shaderAttrs[ 'info:id' ] || spec.fields[ 'info:id' ];
|
|
|
@@ -2181,25 +2369,25 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
- _applyPreviewSurface( material, shaderPath ) {
|
|
|
-
|
|
|
- const fields = this._getAttributes( shaderPath );
|
|
|
-
|
|
|
- const getAttrSpec = ( attrName ) => {
|
|
|
-
|
|
|
- const attrPath = shaderPath + '.' + attrName;
|
|
|
- return this.specsByPath[ attrPath ];
|
|
|
+ /**
|
|
|
+ * Shared helper for applying texture or value from shader attribute.
|
|
|
+ * Reduces duplication between _applyPreviewSurface and _applyOpenPBRSurface.
|
|
|
+ */
|
|
|
+ _applyTextureOrValue( material, shaderPath, fields, attrName, textureProperty, colorSpace, valueCallback, textureGetter ) {
|
|
|
|
|
|
- };
|
|
|
+ const attrPath = shaderPath + '.' + attrName;
|
|
|
+ const spec = this.specsByPath[ attrPath ];
|
|
|
|
|
|
- const applyTextureFromConnection = ( attrName, textureProperty, colorSpace, valueCallback ) => {
|
|
|
+ if ( spec && spec.fields.connectionPaths && spec.fields.connectionPaths.length > 0 ) {
|
|
|
|
|
|
- const spec = getAttrSpec( attrName );
|
|
|
+ // For OpenPBR, try all connection paths; for PreviewSurface, just the first
|
|
|
+ const paths = textureGetter === this._getTextureFromOpenPBRConnection
|
|
|
+ ? spec.fields.connectionPaths
|
|
|
+ : [ spec.fields.connectionPaths[ 0 ] ];
|
|
|
|
|
|
- if ( spec && spec.fields.connectionPaths && spec.fields.connectionPaths.length > 0 ) {
|
|
|
+ for ( const connPath of paths ) {
|
|
|
|
|
|
- const connPath = spec.fields.connectionPaths[ 0 ];
|
|
|
- const texture = this._getTextureFromConnection( connPath );
|
|
|
+ const texture = textureGetter.call( this, connPath );
|
|
|
|
|
|
if ( texture ) {
|
|
|
|
|
|
@@ -2211,18 +2399,40 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
- if ( fields[ attrName ] !== undefined && valueCallback ) {
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( fields[ attrName ] !== undefined && valueCallback ) {
|
|
|
|
|
|
- valueCallback( fields[ attrName ] );
|
|
|
+ valueCallback( fields[ attrName ] );
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ _applyPreviewSurface( material, shaderPath ) {
|
|
|
+
|
|
|
+ const fields = this._getAttributes( shaderPath );
|
|
|
+
|
|
|
+ const applyTexture = ( attrName, textureProperty, colorSpace, valueCallback ) => {
|
|
|
|
|
|
- return false;
|
|
|
+ return this._applyTextureOrValue(
|
|
|
+ material, shaderPath, fields, attrName, textureProperty, colorSpace, valueCallback,
|
|
|
+ this._getTextureFromConnection
|
|
|
+ );
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const getAttrSpec = ( attrName ) => {
|
|
|
+
|
|
|
+ const attrPath = shaderPath + '.' + attrName;
|
|
|
+ return this.specsByPath[ attrPath ];
|
|
|
|
|
|
};
|
|
|
|
|
|
// Diffuse color / base color map
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:diffuseColor',
|
|
|
'map',
|
|
|
SRGBColorSpace,
|
|
|
@@ -2238,7 +2448,7 @@ class USDComposer {
|
|
|
);
|
|
|
|
|
|
// Emissive
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:emissiveColor',
|
|
|
'emissiveMap',
|
|
|
SRGBColorSpace,
|
|
|
@@ -2260,7 +2470,7 @@ class USDComposer {
|
|
|
}
|
|
|
|
|
|
// Normal map
|
|
|
- applyTextureFromConnection( 'inputs:normal', 'normalMap', NoColorSpace, null );
|
|
|
+ applyTexture( 'inputs:normal', 'normalMap', NoColorSpace, null );
|
|
|
|
|
|
// Apply normal map scale from UsdUVTexture scale input
|
|
|
if ( material.normalMap && material.normalMap.userData.scale ) {
|
|
|
@@ -2272,7 +2482,7 @@ class USDComposer {
|
|
|
}
|
|
|
|
|
|
// Roughness
|
|
|
- const hasRoughnessMap = applyTextureFromConnection(
|
|
|
+ const hasRoughnessMap = applyTexture(
|
|
|
'inputs:roughness',
|
|
|
'roughnessMap',
|
|
|
NoColorSpace,
|
|
|
@@ -2290,7 +2500,7 @@ class USDComposer {
|
|
|
}
|
|
|
|
|
|
// Metallic
|
|
|
- const hasMetalnessMap = applyTextureFromConnection(
|
|
|
+ const hasMetalnessMap = applyTexture(
|
|
|
'inputs:metallic',
|
|
|
'metalnessMap',
|
|
|
NoColorSpace,
|
|
|
@@ -2308,7 +2518,7 @@ class USDComposer {
|
|
|
}
|
|
|
|
|
|
// Occlusion
|
|
|
- applyTextureFromConnection( 'inputs:occlusion', 'aoMap', NoColorSpace, null );
|
|
|
+ applyTexture( 'inputs:occlusion', 'aoMap', NoColorSpace, null );
|
|
|
|
|
|
// IOR
|
|
|
if ( fields[ 'inputs:ior' ] !== undefined ) {
|
|
|
@@ -2318,7 +2528,7 @@ class USDComposer {
|
|
|
}
|
|
|
|
|
|
// Specular color
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:specularColor',
|
|
|
'specularColorMap',
|
|
|
SRGBColorSpace,
|
|
|
@@ -2390,48 +2600,17 @@ class USDComposer {
|
|
|
|
|
|
const fields = this._getAttributes( shaderPath );
|
|
|
|
|
|
- const getAttrSpec = ( attrName ) => {
|
|
|
-
|
|
|
- const attrPath = shaderPath + '.' + attrName;
|
|
|
- return this.specsByPath[ attrPath ];
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- const applyTextureFromConnection = ( attrName, textureProperty, colorSpace, valueCallback ) => {
|
|
|
-
|
|
|
- const spec = getAttrSpec( attrName );
|
|
|
-
|
|
|
- if ( spec && spec.fields.connectionPaths && spec.fields.connectionPaths.length > 0 ) {
|
|
|
-
|
|
|
- // Try each connection path until one resolves to a texture
|
|
|
- for ( const connPath of spec.fields.connectionPaths ) {
|
|
|
-
|
|
|
- const texture = this._getTextureFromOpenPBRConnection( connPath );
|
|
|
-
|
|
|
- if ( texture ) {
|
|
|
-
|
|
|
- texture.colorSpace = colorSpace;
|
|
|
- material[ textureProperty ] = texture;
|
|
|
- return true;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( fields[ attrName ] !== undefined && valueCallback ) {
|
|
|
+ const applyTexture = ( attrName, textureProperty, colorSpace, valueCallback ) => {
|
|
|
|
|
|
- valueCallback( fields[ attrName ] );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
+ return this._applyTextureOrValue(
|
|
|
+ material, shaderPath, fields, attrName, textureProperty, colorSpace, valueCallback,
|
|
|
+ this._getTextureFromOpenPBRConnection
|
|
|
+ );
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base color (diffuse)
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:base_color',
|
|
|
'map',
|
|
|
SRGBColorSpace,
|
|
|
@@ -2447,7 +2626,7 @@ class USDComposer {
|
|
|
);
|
|
|
|
|
|
// Base metalness
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:base_metalness',
|
|
|
'metalnessMap',
|
|
|
NoColorSpace,
|
|
|
@@ -2463,7 +2642,7 @@ class USDComposer {
|
|
|
);
|
|
|
|
|
|
// Specular roughness
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:specular_roughness',
|
|
|
'roughnessMap',
|
|
|
NoColorSpace,
|
|
|
@@ -2479,7 +2658,7 @@ class USDComposer {
|
|
|
);
|
|
|
|
|
|
// Emission color
|
|
|
- const hasEmissionMap = applyTextureFromConnection(
|
|
|
+ const hasEmissionMap = applyTexture(
|
|
|
'inputs:emission_color',
|
|
|
'emissiveMap',
|
|
|
SRGBColorSpace,
|
|
|
@@ -2628,7 +2807,7 @@ class USDComposer {
|
|
|
}
|
|
|
|
|
|
// Geometry normal (normal map)
|
|
|
- applyTextureFromConnection(
|
|
|
+ applyTexture(
|
|
|
'inputs:geometry_normal',
|
|
|
'normalMap',
|
|
|
NoColorSpace,
|
|
|
@@ -2962,6 +3141,19 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // Try loading via LoadingManager if available
|
|
|
+ if ( this.manager ) {
|
|
|
+
|
|
|
+ const url = this.manager.resolveURL( baseName );
|
|
|
+ if ( url !== baseName ) {
|
|
|
+
|
|
|
+ // URL modifier found a match - load it
|
|
|
+ return this._createTextureFromData( url, textureAttrs, transformAttrs );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
console.warn( 'USDLoader: Texture not found:', cleanPath );
|
|
|
return null;
|
|
|
|