Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion editor/js/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ Editor.prototype = {

setViewportCamera: function ( uuid ) {

this.viewportCamera = this.cameras[ uuid ];
this.viewportCamera = this.cameras[ uuid ] || this.camera;
this.signals.viewportCameraChanged.dispatch();

},
Expand Down
112 changes: 62 additions & 50 deletions examples/jsm/loaders/USDLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,46 +90,60 @@ class USDLoader extends Loader {

const usda = new USDAParser();
const usdc = new USDCParser();
const textDecoder = new TextDecoder();

function parseAssets( zip ) {
function toArrayBuffer( data ) {

const data = {};
const loader = new FileLoader();
loader.setResponseType( 'arraybuffer' );
if ( data instanceof ArrayBuffer ) return data;

for ( const filename in zip ) {
if ( data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ) {

if ( filename.endsWith( 'png' ) || filename.endsWith( 'jpg' ) || filename.endsWith( 'jpeg' ) ) {
return data.buffer;

const type = filename.endsWith( 'png' ) ? 'image/png' : 'image/jpeg';
const blob = new Blob( [ zip[ filename ] ], { type } );
data[ filename ] = URL.createObjectURL( blob );
}

}
return data.buffer.slice( data.byteOffset, data.byteOffset + data.byteLength );

}
}

function getLowercaseExtension( filename ) {

const lastDot = filename.lastIndexOf( '.' );
if ( lastDot < 0 ) return '';

const lastSlash = filename.lastIndexOf( '/' );
if ( lastSlash > lastDot ) return '';

return filename.slice( lastDot + 1 ).toLowerCase();

}

function parseAssets( zip ) {

const data = {};

for ( const filename in zip ) {

if ( filename.endsWith( 'usd' ) || filename.endsWith( 'usda' ) || filename.endsWith( 'usdc' ) ) {
const fileBytes = zip[ filename ];
const ext = getLowercaseExtension( filename );

if ( ext === 'png' || ext === 'jpg' || ext === 'jpeg' || ext === 'avif' ) {

// Keep raw image bytes and create object URLs lazily in USDComposer.
data[ filename ] = fileBytes;
continue;

}

if ( isCrateFile( zip[ filename ] ) ) {
if ( ext !== 'usd' && ext !== 'usda' && ext !== 'usdc' ) continue;

// Store parsed data (specsByPath) for on-demand composition
const parsedData = usdc.parseData( zip[ filename ].buffer );
data[ filename ] = parsedData;
// Store raw buffer for re-parsing with variant selections
data[ filename + ':buffer' ] = zip[ filename ].buffer;
if ( isCrateFile( fileBytes ) ) {

} else {
data[ filename ] = usdc.parseData( toArrayBuffer( fileBytes ) );

const text = new TextDecoder().decode( zip[ filename ] );
// Store parsed data (specsByPath) for on-demand composition
data[ filename ] = usda.parseData( text );
// Store raw text for re-parsing with variant selections
data[ filename + ':text' ] = text;
} else {

}
data[ filename ] = usda.parseData( textDecoder.decode( fileBytes ) );

}

Expand All @@ -142,10 +156,9 @@ class USDLoader extends Loader {
function isCrateFile( buffer ) {

const crateHeader = new Uint8Array( [ 0x50, 0x58, 0x52, 0x2D, 0x55, 0x53, 0x44, 0x43 ] ); // PXR-USDC
const view = buffer instanceof Uint8Array ? buffer : new Uint8Array( buffer );

if ( buffer.byteLength < crateHeader.length ) return false;

const view = new Uint8Array( buffer, 0, crateHeader.length );
if ( view.byteLength < crateHeader.length ) return false;

for ( let i = 0; i < crateHeader.length; i ++ ) {

Expand All @@ -159,29 +172,30 @@ class USDLoader extends Loader {

function findUSD( zip ) {

if ( zip.length < 1 ) return { file: undefined, basePath: '' };
const fileNames = Object.keys( zip );
if ( fileNames.length < 1 ) return { file: undefined, filename: '', basePath: '' };

const firstFileName = Object.keys( zip )[ 0 ];
const firstFileName = fileNames[ 0 ];
const ext = getLowercaseExtension( firstFileName );
let isCrate = false;

const lastSlash = firstFileName.lastIndexOf( '/' );
const basePath = lastSlash >= 0 ? firstFileName.slice( 0, lastSlash ) : '';

// As per the USD specification, the first entry in the zip archive is used as the main file ("UsdStage").
// Per AOUSD core spec v1.0.1 section 16.4.1.2, the first ZIP entry is the root layer.
// ASCII files can end in either .usda or .usd.
// See https://openusd.org/release/spec_usdz.html#layout
if ( firstFileName.endsWith( 'usda' ) ) return { file: zip[ firstFileName ], basePath };
if ( ext === 'usda' ) return { file: zip[ firstFileName ], filename: firstFileName, basePath };

if ( firstFileName.endsWith( 'usdc' ) ) {
if ( ext === 'usdc' ) {

isCrate = true;

} else if ( firstFileName.endsWith( 'usd' ) ) {
} else if ( ext === 'usd' ) {

// If this is not a crate file, we assume it is a plain USDA file.
if ( ! isCrateFile( zip[ firstFileName ] ) ) {

return { file: zip[ firstFileName ], basePath };
return { file: zip[ firstFileName ], filename: firstFileName, basePath };

} else {

Expand All @@ -193,11 +207,11 @@ class USDLoader extends Loader {

if ( isCrate ) {

return { file: zip[ firstFileName ], basePath };
return { file: zip[ firstFileName ], filename: firstFileName, basePath };

}

return { file: undefined, basePath: '' };
return { file: undefined, filename: '', basePath: '' };

}

Expand All @@ -218,7 +232,7 @@ class USDLoader extends Loader {
if ( isCrateFile( buffer ) ) {

const composer = new USDComposer( scope.manager );
const data = usdc.parseData( buffer );
const data = usdc.parseData( toArrayBuffer( buffer ) );
return composer.compose( data, {} );

}
Expand All @@ -230,22 +244,20 @@ class USDLoader extends Loader {
if ( bytes[ 0 ] === 0x50 && bytes[ 1 ] === 0x4B ) {

const zip = unzipSync( bytes );

const assets = parseAssets( zip );
const { file, filename, basePath } = findUSD( zip );

const { file, basePath } = findUSD( zip );
if ( ! file ) {

const composer = new USDComposer( scope.manager );
let data;
throw new Error( 'USDLoader: Invalid USDZ package. The first ZIP entry must be a USD layer (.usd/.usda/.usdc).' );

if ( isCrateFile( file ) ) {

data = usdc.parseData( file.buffer );
}

} else {
const composer = new USDComposer( scope.manager );
const data = assets[ filename ];
if ( ! data ) {

const text = new TextDecoder().decode( file );
data = usda.parseData( text );
throw new Error( 'USDLoader: Failed to parse root layer "' + filename + '".' );

}

Expand All @@ -256,7 +268,7 @@ class USDLoader extends Loader {
// USDA (standalone, as ArrayBuffer)

const composer = new USDComposer( scope.manager );
const text = new TextDecoder().decode( bytes );
const text = textDecoder.decode( bytes );
const data = usda.parseData( text );
return composer.compose( data, {} );

Expand Down
76 changes: 3 additions & 73 deletions examples/jsm/loaders/usd/USDComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3559,7 +3559,9 @@ class USDComposer {

}

// Use geomBindTransform if available, otherwise compute from mesh/skeleton alignment
// Use geomBindTransform if available, otherwise fall back to identity.
// Estimating bind transforms from vertex/joint samples is not robust and can
// produce severe skinning distortion for valid assets.
let bindMatrix = new Matrix4();

if ( geomBindTransform && geomBindTransform.length === 16 ) {
Expand All @@ -3573,11 +3575,6 @@ class USDComposer {
m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
);

} else {

// Compute geomBindTransform by comparing mesh vertices with skeleton bind positions
bindMatrix = this._computeGeomBindTransform( mesh, skeleton );

}

mesh.bind( skeleton, bindMatrix );
Expand All @@ -3586,73 +3583,6 @@ class USDComposer {

}

_computeGeomBindTransform( mesh, skeleton ) {

const bindMatrix = new Matrix4();
const geometry = mesh.geometry;
const position = geometry.attributes.position;
const skinIndex = geometry.attributes.skinIndex;

if ( ! position || ! skinIndex || position.count === 0 ) {

return bindMatrix;

}

// Sample vertices and their influencing joints to compute average scale
const boneInverses = skeleton.boneInverses;
const sampleCount = Math.min( 50, position.count );
let sumRatioX = 0, sumRatioY = 0, sumRatioZ = 0;
let validSamples = 0;

for ( let i = 0; i < sampleCount; i ++ ) {

const vi = Math.floor( i * position.count / sampleCount );
const vx = position.getX( vi );
const vy = position.getY( vi );
const vz = position.getZ( vi );

// Get primary joint for this vertex
const jointIdx = skinIndex.getX( vi );
if ( jointIdx >= boneInverses.length ) continue;

// Get joint bind position from inverse bind matrix
const inverseBindMatrix = boneInverses[ jointIdx ];
const bindTransform = inverseBindMatrix.clone().invert();
const jx = bindTransform.elements[ 12 ];
const jy = bindTransform.elements[ 13 ];
const jz = bindTransform.elements[ 14 ];

// Compute ratio if both values are non-zero
if ( Math.abs( vx ) > 0.001 && Math.abs( jx ) > 0.001 ) {

sumRatioX += jx / vx;
sumRatioY += jy / vy;
sumRatioZ += jz / vz;
validSamples ++;

}

}

if ( validSamples > 0 ) {

// Use average scale to create geomBindTransform
const avgScale = ( sumRatioX + sumRatioY + sumRatioZ ) / ( validSamples * 3 );

// Only apply if scale is significantly different from 1
if ( Math.abs( avgScale - 1 ) > 0.1 ) {

bindMatrix.makeScale( avgScale, avgScale, avgScale );

}

}

return bindMatrix;

}

_buildAnimations() {

const animations = [];
Expand Down
22 changes: 22 additions & 0 deletions src/nodes/accessors/StorageTextureNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,28 @@ class StorageTextureNode extends TextureNode {

}

/**
* Generates the snippet for the storage texture.
*
* @param {NodeBuilder} builder - The current node builder.
* @param {string} textureProperty - The texture property.
* @param {string} uvSnippet - The uv snippet.
* @param {?string} levelSnippet - The level snippet.
* @param {?string} biasSnippet - The bias snippet.
* @param {?string} depthSnippet - The depth snippet.
* @param {?string} compareSnippet - The compare snippet.
* @param {?Array<string>} gradSnippet - The grad snippet.
* @param {?string} offsetSnippet - The offset snippet.
* @return {string} The generated code snippet.
*/
generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, offsetSnippet ) {

const texture = this.value;

return builder.generateStorageTextureLoad( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, offsetSnippet );

}

/**
* Convenience method for configuring a read/write node access.
*
Expand Down
8 changes: 4 additions & 4 deletions src/renderers/common/Sampler.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Sampler extends Binding {
this._onTextureDispose = () => {

this.generation = null;
this.version = 0;
this.version = - 1;

};

Expand All @@ -47,7 +47,7 @@ class Sampler extends Binding {
*
* @type {number}
*/
this.version = texture ? texture.version : 0;
this.version = texture ? texture.version : - 1;

/**
* The binding's generation which is an additional version
Expand Down Expand Up @@ -95,7 +95,7 @@ class Sampler extends Binding {
this._texture = value;

this.generation = null;
this.version = 0;
this.version = - 1;

if ( this._texture ) {

Expand Down Expand Up @@ -150,7 +150,7 @@ class Sampler extends Binding {
clonedSampler._onTextureDispose = () => {

clonedSampler.generation = null;
clonedSampler.version = 0;
clonedSampler.version = - 1;

};

Expand Down
Loading