Skip to content

Commit 3b39ab8

Browse files
mrdoobclaude
andauthored
USDLoader: Performance improvements and external texture support. (#32790)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 38493a2 commit 3b39ab8

File tree

5 files changed

+421
-152
lines changed

5 files changed

+421
-152
lines changed

editor/js/Loader.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,8 @@ function Loader( editor ) {
635635

636636
const { USDLoader } = await import( 'three/addons/loaders/USDLoader.js' );
637637

638-
const group = new USDLoader().parse( contents );
638+
const loader = new USDLoader( manager );
639+
const group = loader.parse( contents );
639640
group.name = filename;
640641

641642
editor.execute( new AddObjectCommand( editor, group ) );
@@ -759,6 +760,15 @@ function Loader( editor ) {
759760

760761
}
761762

763+
case 'bmp':
764+
case 'gif':
765+
case 'jpg':
766+
case 'jpeg':
767+
case 'png':
768+
case 'tga':
769+
770+
break; // Image files are handled as textures by other loaders
771+
762772
default:
763773

764774
console.error( 'Unsupported file format (' + extension + ').' );

examples/jsm/loaders/USDLoader.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,13 @@ class USDLoader extends Loader {
201201

202202
}
203203

204+
const scope = this;
205+
204206
// USDA (standalone)
205207

206208
if ( typeof buffer === 'string' ) {
207209

208-
const composer = new USDComposer();
210+
const composer = new USDComposer( scope.manager );
209211
const data = usda.parseData( buffer );
210212
return composer.compose( data, {} );
211213

@@ -215,7 +217,7 @@ class USDLoader extends Loader {
215217

216218
if ( isCrateFile( buffer ) ) {
217219

218-
const composer = new USDComposer();
220+
const composer = new USDComposer( scope.manager );
219221
const data = usdc.parseData( buffer );
220222
return composer.compose( data, {} );
221223

@@ -233,7 +235,7 @@ class USDLoader extends Loader {
233235

234236
const { file, basePath } = findUSD( zip );
235237

236-
const composer = new USDComposer();
238+
const composer = new USDComposer( scope.manager );
237239
let data;
238240

239241
if ( isCrateFile( file ) ) {
@@ -253,7 +255,7 @@ class USDLoader extends Loader {
253255

254256
// USDA (standalone, as ArrayBuffer)
255257

256-
const composer = new USDComposer();
258+
const composer = new USDComposer( scope.manager );
257259
const text = new TextDecoder().decode( bytes );
258260
const data = usda.parseData( text );
259261
return composer.compose( data, {} );

examples/jsm/loaders/usd/USDAParser.js

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Pre-compiled regex patterns for performance
2+
const DEF_MATCH_REGEX = /^def\s+(?:(\w+)\s+)?"?([^"]+)"?$/;
3+
const VARIANT_STRING_REGEX = /^string\s+(\w+)$/;
4+
const ATTR_MATCH_REGEX = /^(?:uniform\s+)?(\w+(?:\[\])?)\s+(.+)$/;
5+
16
class USDAParser {
27

38
parseText( text ) {
@@ -120,6 +125,9 @@ class USDAParser {
120125
// Remove block comments /* ... */
121126
text = this._stripBlockComments( text );
122127

128+
// Collapse triple-quoted strings into single lines
129+
text = this._collapseTripleQuotedStrings( text );
130+
123131
// Remove line comments # ... (but preserve #usda header)
124132
// Only remove # comments that aren't at the start of a line or after whitespace
125133
const lines = text.split( '\n' );
@@ -256,6 +264,64 @@ class USDAParser {
256264

257265
}
258266

267+
_collapseTripleQuotedStrings( text ) {
268+
269+
let result = '';
270+
let i = 0;
271+
272+
while ( i < text.length ) {
273+
274+
if ( i + 2 < text.length ) {
275+
276+
const triple = text.slice( i, i + 3 );
277+
278+
if ( triple === '\'\'\'' || triple === '"""' ) {
279+
280+
const quoteChar = triple;
281+
result += quoteChar;
282+
i += 3;
283+
284+
while ( i < text.length ) {
285+
286+
if ( i + 2 < text.length && text.slice( i, i + 3 ) === quoteChar ) {
287+
288+
result += quoteChar;
289+
i += 3;
290+
break;
291+
292+
} else {
293+
294+
if ( text[ i ] === '\n' ) {
295+
296+
result += '\\n';
297+
298+
} else if ( text[ i ] !== '\r' ) {
299+
300+
result += text[ i ];
301+
302+
}
303+
304+
i ++;
305+
306+
}
307+
308+
}
309+
310+
continue;
311+
312+
}
313+
314+
}
315+
316+
result += text[ i ];
317+
i ++;
318+
319+
}
320+
321+
return result;
322+
323+
}
324+
259325
_stripInlineComment( line ) {
260326

261327
// Don't strip if line starts with #usda
@@ -405,7 +471,7 @@ class USDAParser {
405471

406472
// Check for primitive definitions
407473
// Matches both 'def TypeName "name"' and 'def "name"' (no type)
408-
const defMatch = key.match( /^def\s+(?:(\w+)\s+)?"?([^"]+)"?$/ );
474+
const defMatch = key.match( DEF_MATCH_REGEX );
409475
if ( defMatch ) {
410476

411477
const typeName = defMatch[ 1 ] || '';
@@ -474,7 +540,7 @@ class USDAParser {
474540

475541
for ( const vKey in variants ) {
476542

477-
const match = vKey.match( /^string\s+(\w+)$/ );
543+
const match = vKey.match( VARIANT_STRING_REGEX );
478544
if ( match ) {
479545

480546
const variantSetName = match[ 1 ];
@@ -522,7 +588,7 @@ class USDAParser {
522588

523589
// Handle typed attributes
524590
// Format: [qualifier] type attrName (e.g., "uniform token[] joints", "float3 position")
525-
const attrMatch = key.match( /^(?:uniform\s+)?(\w+(?:\[\])?)\s+(.+)$/ );
591+
const attrMatch = key.match( ATTR_MATCH_REGEX );
526592
if ( attrMatch ) {
527593

528594
const valueType = attrMatch[ 1 ];

examples/jsm/loaders/usd/USDCParser.js

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
const textDecoder = new TextDecoder();
22

3+
// Pre-computed half-float exponent lookup table for fast conversion
4+
// Math.pow(2, exp - 15) for exp = 0..31
5+
const HALF_EXPONENT_TABLE = new Float32Array( 32 );
6+
for ( let i = 0; i < 32; i ++ ) {
7+
8+
HALF_EXPONENT_TABLE[ i ] = Math.pow( 2, i - 15 );
9+
10+
}
11+
12+
// Pre-computed constant for denormalized half-floats: 2^-14
13+
const HALF_DENORM_SCALE = Math.pow( 2, - 14 );
14+
315
// Type enum values from crateDataTypes.h
416
const TypeEnum = {
517
Invalid: 0,
@@ -502,6 +514,9 @@ class USDCParser {
502514
this.reader = new BinaryReader( this.buffer );
503515
this.version = { major: 0, minor: 0, patch: 0 };
504516

517+
this._conversionBuffer = new ArrayBuffer( 4 );
518+
this._conversionView = new DataView( this._conversionBuffer );
519+
505520
this._readBootstrap();
506521
this._readTOC();
507522
this._readTokens();
@@ -1101,6 +1116,7 @@ class USDCParser {
11011116

11021117
const type = valueRep.typeEnum;
11031118
const payload = valueRep.getInlinedValue();
1119+
const view = this._conversionView;
11041120

11051121
switch ( type ) {
11061122

@@ -1113,18 +1129,16 @@ class USDCParser {
11131129
return payload;
11141130
case TypeEnum.Float: {
11151131

1116-
const buf = new ArrayBuffer( 4 );
1117-
new DataView( buf ).setUint32( 0, payload, true );
1118-
return new DataView( buf ).getFloat32( 0, true );
1132+
view.setUint32( 0, payload, true );
1133+
return view.getFloat32( 0, true );
11191134

11201135
}
11211136

11221137
case TypeEnum.Double: {
11231138

11241139
// When a double is inlined, it's stored as float32 bits in the payload
1125-
const buf = new ArrayBuffer( 4 );
1126-
new DataView( buf ).setUint32( 0, payload, true );
1127-
return new DataView( buf ).getFloat32( 0, true );
1140+
view.setUint32( 0, payload, true );
1141+
return view.getFloat32( 0, true );
11281142

11291143
}
11301144

@@ -1143,8 +1157,6 @@ class USDCParser {
11431157
// Vec2h: Two half-floats fit in 4 bytes, stored directly
11441158
case TypeEnum.Vec2h: {
11451159

1146-
const buf = new ArrayBuffer( 4 );
1147-
const view = new DataView( buf );
11481160
view.setUint32( 0, payload, true );
11491161
return [ this._halfToFloat( view.getUint16( 0, true ) ), this._halfToFloat( view.getUint16( 2, true ) ) ];
11501162

@@ -1155,8 +1167,6 @@ class USDCParser {
11551167
case TypeEnum.Vec2f:
11561168
case TypeEnum.Vec2i: {
11571169

1158-
const buf = new ArrayBuffer( 4 );
1159-
const view = new DataView( buf );
11601170
view.setUint32( 0, payload, true );
11611171
return [ view.getInt8( 0 ), view.getInt8( 1 ) ];
11621172

@@ -1165,8 +1175,6 @@ class USDCParser {
11651175
case TypeEnum.Vec3f:
11661176
case TypeEnum.Vec3i: {
11671177

1168-
const buf = new ArrayBuffer( 4 );
1169-
const view = new DataView( buf );
11701178
view.setUint32( 0, payload, true );
11711179
return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ) ];
11721180

@@ -1175,8 +1183,6 @@ class USDCParser {
11751183
case TypeEnum.Vec4f:
11761184
case TypeEnum.Vec4i: {
11771185

1178-
const buf = new ArrayBuffer( 4 );
1179-
const view = new DataView( buf );
11801186
view.setUint32( 0, payload, true );
11811187
return [ view.getInt8( 0 ), view.getInt8( 1 ), view.getInt8( 2 ), view.getInt8( 3 ) ];
11821188

@@ -1185,8 +1191,6 @@ class USDCParser {
11851191
case TypeEnum.Matrix2d: {
11861192

11871193
// Inlined Matrix2d stores diagonal values as 2 signed int8 values
1188-
const buf = new ArrayBuffer( 4 );
1189-
const view = new DataView( buf );
11901194
view.setUint32( 0, payload, true );
11911195
const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 );
11921196
return [ d0, 0, 0, d1 ];
@@ -1196,8 +1200,6 @@ class USDCParser {
11961200
case TypeEnum.Matrix3d: {
11971201

11981202
// Inlined Matrix3d stores diagonal values as 3 signed int8 values
1199-
const buf = new ArrayBuffer( 4 );
1200-
const view = new DataView( buf );
12011203
view.setUint32( 0, payload, true );
12021204
const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 );
12031205
return [ d0, 0, 0, 0, d1, 0, 0, 0, d2 ];
@@ -1207,8 +1209,6 @@ class USDCParser {
12071209
case TypeEnum.Matrix4d: {
12081210

12091211
// Inlined Matrix4d stores diagonal values as 4 signed int8 values
1210-
const buf = new ArrayBuffer( 4 );
1211-
const view = new DataView( buf );
12121212
view.setUint32( 0, payload, true );
12131213
const d0 = view.getInt8( 0 ), d1 = view.getInt8( 1 ), d2 = view.getInt8( 2 ), d3 = view.getInt8( 3 );
12141214
return [ d0, 0, 0, 0, 0, d1, 0, 0, 0, 0, d2, 0, 0, 0, 0, d3 ];
@@ -1786,7 +1786,6 @@ class USDCParser {
17861786

17871787
_halfToFloat( h ) {
17881788

1789-
// Convert half to float (IEEE 754 half-precision)
17901789
const sign = ( h & 0x8000 ) >> 15;
17911790
const exp = ( h & 0x7C00 ) >> 10;
17921791
const frac = h & 0x03FF;
@@ -1801,15 +1800,15 @@ class USDCParser {
18011800
}
18021801

18031802
// Denormalized: value = ±2^-14 × (frac/1024)
1804-
return ( sign ? - 1 : 1 ) * Math.pow( 2, - 14 ) * ( frac / 1024 );
1803+
return ( sign ? - 1 : 1 ) * HALF_DENORM_SCALE * ( frac / 1024 );
18051804

18061805
} else if ( exp === 31 ) {
18071806

18081807
return frac ? NaN : ( sign ? - Infinity : Infinity );
18091808

18101809
}
18111810

1812-
return ( sign ? - 1 : 1 ) * Math.pow( 2, exp - 15 ) * ( 1 + frac / 1024 );
1811+
return ( sign ? - 1 : 1 ) * HALF_EXPONENT_TABLE[ exp ] * ( 1 + frac / 1024 );
18131812

18141813
}
18151814

0 commit comments

Comments
 (0)