@plastic-software/three 0.178.0 → 0.179.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +1 -1
  2. package/build/three.cjs +856 -196
  3. package/build/three.core.js +647 -123
  4. package/build/three.core.min.js +1 -1
  5. package/build/three.module.js +211 -76
  6. package/build/three.module.min.js +1 -1
  7. package/build/three.tsl.js +70 -21
  8. package/build/three.tsl.min.js +1 -1
  9. package/build/three.webgpu.js +1796 -557
  10. package/build/three.webgpu.min.js +1 -1
  11. package/build/three.webgpu.nodes.js +1754 -557
  12. package/build/three.webgpu.nodes.min.js +1 -1
  13. package/examples/jsm/Addons.js +1 -2
  14. package/examples/jsm/capabilities/WebGPU.js +1 -1
  15. package/examples/jsm/csm/CSMShadowNode.js +4 -4
  16. package/examples/jsm/environments/RoomEnvironment.js +8 -3
  17. package/examples/jsm/exporters/USDZExporter.js +676 -299
  18. package/examples/jsm/geometries/RoundedBoxGeometry.js +47 -8
  19. package/examples/jsm/interactive/HTMLMesh.js +5 -3
  20. package/examples/jsm/libs/meshopt_decoder.module.js +75 -58
  21. package/examples/jsm/lights/LightProbeGenerator.js +14 -3
  22. package/examples/jsm/loaders/EXRLoader.js +210 -22
  23. package/examples/jsm/loaders/FBXLoader.js +1 -1
  24. package/examples/jsm/loaders/MaterialXLoader.js +212 -30
  25. package/examples/jsm/loaders/TTFLoader.js +13 -1
  26. package/examples/jsm/loaders/USDLoader.js +219 -0
  27. package/examples/jsm/loaders/USDZLoader.js +4 -892
  28. package/examples/jsm/loaders/usd/USDAParser.js +741 -0
  29. package/examples/jsm/loaders/usd/USDCParser.js +17 -0
  30. package/examples/jsm/objects/LensflareMesh.js +3 -3
  31. package/examples/jsm/objects/SkyMesh.js +2 -2
  32. package/examples/jsm/physics/RapierPhysics.js +14 -5
  33. package/examples/jsm/postprocessing/GTAOPass.js +10 -9
  34. package/examples/jsm/postprocessing/OutlinePass.js +17 -17
  35. package/examples/jsm/postprocessing/SSAOPass.js +10 -9
  36. package/examples/jsm/shaders/UnpackDepthRGBAShader.js +11 -2
  37. package/examples/jsm/transpiler/GLSLDecoder.js +2 -2
  38. package/examples/jsm/tsl/display/BloomNode.js +8 -7
  39. package/examples/jsm/tsl/display/GaussianBlurNode.js +6 -8
  40. package/examples/jsm/tsl/display/{TRAAPassNode.js → TRAANode.js} +181 -172
  41. package/examples/jsm/tsl/lighting/TiledLightsNode.js +1 -1
  42. package/package.json +1 -1
  43. package/src/Three.Core.js +1 -0
  44. package/src/Three.TSL.js +69 -20
  45. package/src/animation/KeyframeTrack.js +1 -1
  46. package/src/animation/tracks/BooleanKeyframeTrack.js +1 -1
  47. package/src/animation/tracks/StringKeyframeTrack.js +1 -1
  48. package/src/cameras/Camera.js +14 -0
  49. package/src/cameras/OrthographicCamera.js +1 -1
  50. package/src/cameras/PerspectiveCamera.js +1 -1
  51. package/src/constants.js +1 -1
  52. package/{examples/jsm/misc → src/core}/Timer.js +4 -42
  53. package/src/extras/PMREMGenerator.js +11 -0
  54. package/src/helpers/CameraHelper.js +41 -11
  55. package/src/helpers/SkeletonHelper.js +35 -6
  56. package/src/lights/LightShadow.js +21 -8
  57. package/src/lights/PointLightShadow.js +1 -1
  58. package/src/loaders/FileLoader.js +25 -2
  59. package/src/loaders/ImageBitmapLoader.js +23 -0
  60. package/src/loaders/Loader.js +14 -0
  61. package/src/loaders/LoadingManager.js +23 -0
  62. package/src/materials/MeshBasicMaterial.js +1 -1
  63. package/src/materials/nodes/Line2NodeMaterial.js +0 -8
  64. package/src/materials/nodes/NodeMaterial.js +1 -1
  65. package/src/materials/nodes/PointsNodeMaterial.js +5 -0
  66. package/src/materials/nodes/manager/NodeMaterialObserver.js +87 -2
  67. package/src/math/Frustum.js +19 -8
  68. package/src/math/FrustumArray.js +10 -5
  69. package/src/math/Line3.js +129 -2
  70. package/src/math/Matrix4.js +48 -27
  71. package/src/math/Spherical.js +2 -2
  72. package/src/nodes/Nodes.js +1 -0
  73. package/src/nodes/TSL.js +1 -0
  74. package/src/nodes/accessors/Camera.js +12 -12
  75. package/src/nodes/accessors/Normal.js +11 -11
  76. package/src/nodes/accessors/ReferenceNode.js +18 -3
  77. package/src/nodes/accessors/SceneNode.js +1 -1
  78. package/src/nodes/accessors/StorageTextureNode.js +1 -1
  79. package/src/nodes/accessors/TextureNode.js +12 -0
  80. package/src/nodes/core/ArrayNode.js +12 -0
  81. package/src/nodes/core/AssignNode.js +3 -0
  82. package/src/nodes/core/ContextNode.js +20 -1
  83. package/src/nodes/core/Node.js +14 -2
  84. package/src/nodes/core/NodeBuilder.js +25 -20
  85. package/src/nodes/core/NodeUtils.js +4 -1
  86. package/src/nodes/core/StackNode.js +42 -0
  87. package/src/nodes/core/UniformNode.js +63 -5
  88. package/src/nodes/core/VarNode.js +91 -2
  89. package/src/nodes/display/PassNode.js +148 -2
  90. package/src/nodes/display/ViewportTextureNode.js +67 -7
  91. package/src/nodes/functions/PhysicalLightingModel.js +2 -2
  92. package/src/nodes/gpgpu/AtomicFunctionNode.js +1 -1
  93. package/src/nodes/gpgpu/ComputeNode.js +67 -23
  94. package/src/nodes/gpgpu/WorkgroupInfoNode.js +28 -3
  95. package/src/nodes/lighting/ProjectorLightNode.js +19 -6
  96. package/src/nodes/lighting/ShadowFilterNode.js +1 -1
  97. package/src/nodes/materialx/MaterialXNodes.js +131 -2
  98. package/src/nodes/materialx/lib/mx_noise.js +165 -1
  99. package/src/nodes/math/ConditionalNode.js +1 -1
  100. package/src/nodes/math/MathNode.js +78 -54
  101. package/src/nodes/math/OperatorNode.js +22 -22
  102. package/src/nodes/tsl/TSLCore.js +64 -9
  103. package/src/nodes/utils/DebugNode.js +1 -1
  104. package/src/nodes/utils/EventNode.js +83 -0
  105. package/src/nodes/utils/RTTNode.js +9 -0
  106. package/src/objects/BatchedMesh.js +4 -2
  107. package/src/renderers/WebGLRenderer.js +21 -22
  108. package/src/renderers/common/Bindings.js +19 -18
  109. package/src/renderers/common/Color4.js +2 -2
  110. package/src/renderers/common/PostProcessing.js +60 -5
  111. package/src/renderers/common/Renderer.js +18 -15
  112. package/src/renderers/common/SampledTexture.js +3 -71
  113. package/src/renderers/common/Sampler.js +79 -0
  114. package/src/renderers/common/Storage3DTexture.js +21 -0
  115. package/src/renderers/common/StorageArrayTexture.js +21 -0
  116. package/src/renderers/common/StorageTexture.js +19 -0
  117. package/src/renderers/common/Textures.js +19 -3
  118. package/src/renderers/common/XRManager.js +26 -8
  119. package/src/renderers/common/nodes/NodeSampledTexture.js +0 -12
  120. package/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +20 -2
  121. package/src/renderers/shaders/ShaderLib/depth.glsl.js +11 -2
  122. package/src/renderers/webgl/WebGLCapabilities.js +2 -2
  123. package/src/renderers/webgl/WebGLMaterials.js +6 -6
  124. package/src/renderers/webgl/WebGLProgram.js +22 -16
  125. package/src/renderers/webgl/WebGLPrograms.js +4 -4
  126. package/src/renderers/webgl/WebGLShadowMap.js +11 -1
  127. package/src/renderers/webgl/WebGLTextures.js +19 -7
  128. package/src/renderers/webgl-fallback/WebGLBackend.js +22 -12
  129. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +2 -2
  130. package/src/renderers/webgpu/WebGPUBackend.js +54 -15
  131. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +53 -73
  132. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +35 -31
  133. package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +1 -1
  134. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +11 -64
  135. package/src/renderers/webgpu/utils/WebGPUUtils.js +2 -17
  136. package/src/renderers/webxr/WebXRDepthSensing.js +6 -10
  137. package/src/renderers/webxr/WebXRManager.js +68 -8
  138. package/src/textures/ExternalTexture.js +45 -0
  139. package/src/textures/FramebufferTexture.js +2 -2
  140. package/src/textures/Source.js +11 -1
  141. package/src/textures/VideoTexture.js +30 -2
@@ -9,6 +9,121 @@ import {
9
9
  zipSync,
10
10
  } from '../libs/fflate.module.js';
11
11
 
12
+ class USDNode {
13
+
14
+ constructor( name, type = '', metadata = [], properties = [] ) {
15
+
16
+ this.name = name;
17
+ this.type = type;
18
+ this.metadata = metadata;
19
+ this.properties = properties;
20
+ this.children = [];
21
+
22
+ }
23
+
24
+ addMetadata( key, value ) {
25
+
26
+ this.metadata.push( { key, value } );
27
+
28
+ }
29
+
30
+ addProperty( property, metadata = [] ) {
31
+
32
+ this.properties.push( { property, metadata } );
33
+
34
+ }
35
+
36
+ addChild( child ) {
37
+
38
+ this.children.push( child );
39
+
40
+ }
41
+
42
+ toString( indent = 0 ) {
43
+
44
+ const pad = '\t'.repeat( indent );
45
+
46
+ const formattedMetadata = this.metadata.map( ( item ) => {
47
+
48
+ const key = item.key;
49
+ const value = item.value;
50
+
51
+ if ( Array.isArray( value ) ) {
52
+
53
+ const lines = [];
54
+ lines.push( `${key} = {` );
55
+ value.forEach( ( line ) => {
56
+
57
+ lines.push( `${pad}\t\t${line}` );
58
+
59
+ } );
60
+ lines.push( `${pad}\t}` );
61
+ return lines.join( '\n' );
62
+
63
+ } else {
64
+
65
+ return `${key} = ${value}`;
66
+
67
+ }
68
+
69
+ } );
70
+
71
+ const meta = formattedMetadata.length
72
+ ? ` (\n${formattedMetadata
73
+ .map( ( l ) => `${pad}\t${l}` )
74
+ .join( '\n' )}\n${pad})`
75
+ : '';
76
+
77
+ const properties = this.properties.map( ( l ) => {
78
+
79
+ const property = l.property;
80
+ const metadata = l.metadata.length
81
+ ? ` (\n${l.metadata.map( ( m ) => `${pad}\t\t${m}` ).join( '\n' )}\n${pad}\t)`
82
+ : '';
83
+ return `${pad}\t${property}${metadata}`;
84
+
85
+ } );
86
+ const children = this.children.map( ( c ) => c.toString( indent + 1 ) );
87
+
88
+ const bodyLines = [];
89
+
90
+ if ( properties.length > 0 ) {
91
+
92
+ bodyLines.push( ...properties );
93
+
94
+ }
95
+
96
+ if ( children.length > 0 ) {
97
+
98
+ if ( properties.length > 0 ) {
99
+
100
+ bodyLines.push( '' );
101
+
102
+ }
103
+
104
+ for ( let i = 0; i < children.length; i ++ ) {
105
+
106
+ bodyLines.push( children[ i ] );
107
+ if ( i < children.length - 1 ) {
108
+
109
+ bodyLines.push( '' );
110
+
111
+ }
112
+
113
+ }
114
+
115
+ }
116
+
117
+ const bodyContent = bodyLines.join( '\n' );
118
+
119
+ const type = this.type ? this.type + ' ' : '';
120
+
121
+ return `${pad}def ${type}"${this.name}"${meta}\n${pad}{\n${bodyContent}\n${pad}}`;
122
+
123
+ }
124
+
125
+ }
126
+
12
127
  /**
13
128
  * An exporter for USDZ.
14
129
  *
@@ -74,15 +189,21 @@ class USDZExporter {
74
189
  */
75
190
  async parseAsync( scene, options = {} ) {
76
191
 
77
- options = Object.assign( {
78
- ar: {
79
- anchoring: { type: 'plane' },
80
- planeAnchoring: { alignment: 'horizontal' }
192
+ options = Object.assign(
193
+ {
194
+ ar: {
195
+ anchoring: { type: 'plane' },
196
+ planeAnchoring: { alignment: 'horizontal' },
197
+ },
198
+ includeAnchoringProperties: true,
199
+ onlyVisible: true,
200
+ quickLookCompatible: false,
201
+ maxTextureSize: 1024,
81
202
  },
82
- includeAnchoringProperties: true,
83
- quickLookCompatible: false,
84
- maxTextureSize: 1024,
85
- }, options );
203
+ options
204
+ );
205
+
206
+ const usedNames = new Set();
86
207
 
87
208
  const files = {};
88
209
  const modelFileName = 'model.usda';
@@ -90,57 +211,50 @@ class USDZExporter {
90
211
  // model file should be first in USDZ archive so we init it here
91
212
  files[ modelFileName ] = null;
92
213
 
93
- let output = buildHeader();
94
-
95
- output += buildSceneStart( options );
96
-
97
- const materials = {};
98
- const textures = {};
99
-
100
- scene.traverseVisible( ( object ) => {
101
-
102
- if ( object.isMesh ) {
103
-
104
- const geometry = object.geometry;
105
- const material = object.material;
106
-
107
- if ( material.isMeshStandardMaterial ) {
108
-
109
- const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usda';
110
-
111
- if ( ! ( geometryFileName in files ) ) {
112
-
113
- const meshObject = buildMeshObject( geometry );
114
- files[ geometryFileName ] = buildUSDFileAsString( meshObject );
214
+ const root = new USDNode( 'Root', 'Xform' );
215
+ const scenesNode = new USDNode( 'Scenes', 'Scope' );
216
+ scenesNode.addMetadata( 'kind', '"sceneLibrary"' );
217
+ root.addChild( scenesNode );
218
+
219
+ const sceneName = 'Scene';
220
+ const sceneNode = new USDNode( sceneName, 'Xform' );
221
+ sceneNode.addMetadata( 'customData', [
222
+ 'bool preliminary_collidesWithEnvironment = 0',
223
+ `string sceneName = "${sceneName}"`,
224
+ ] );
225
+ sceneNode.addMetadata( 'sceneName', `"${sceneName}"` );
226
+ if ( options.includeAnchoringProperties ) {
227
+
228
+ sceneNode.addProperty(
229
+ `token preliminary:anchoring:type = "${options.ar.anchoring.type}"`
230
+ );
231
+ sceneNode.addProperty(
232
+ `token preliminary:planeAnchoring:alignment = "${options.ar.planeAnchoring.alignment}"`
233
+ );
115
234
 
116
- }
117
-
118
- if ( ! ( material.uuid in materials ) ) {
119
-
120
- materials[ material.uuid ] = material;
121
-
122
- }
123
-
124
- output += buildXform( object, geometry, materials[ material.uuid ] );
125
-
126
- } else {
127
-
128
- console.warn( 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', object );
129
-
130
- }
131
-
132
- } else if ( object.isCamera ) {
235
+ }
133
236
 
134
- output += buildCamera( object );
237
+ scenesNode.addChild( sceneNode );
135
238
 
136
- }
239
+ let output;
137
240
 
138
- } );
241
+ const materials = {};
242
+ const textures = {};
139
243
 
244
+ buildHierarchy( scene, sceneNode, materials, usedNames, files, options );
140
245
 
141
- output += buildSceneEnd();
246
+ const materialsNode = buildMaterials(
247
+ materials,
248
+ textures,
249
+ options.quickLookCompatible
250
+ );
142
251
 
143
- output += buildMaterials( materials, textures, options.quickLookCompatible );
252
+ output =
253
+ buildHeader() +
254
+ '\n' +
255
+ root.toString() +
256
+ '\n\n' +
257
+ materialsNode.toString();
144
258
 
145
259
  files[ modelFileName ] = strToU8( output );
146
260
  output = null;
@@ -153,7 +267,9 @@ class USDZExporter {
153
267
 
154
268
  if ( this.textureUtils === null ) {
155
269
 
156
- throw new Error( 'THREE.USDZExporter: setTextureUtils() must be called to process compressed textures.' );
270
+ throw new Error(
271
+ 'THREE.USDZExporter: setTextureUtils() must be called to process compressed textures.'
272
+ );
157
273
 
158
274
  } else {
159
275
 
@@ -163,10 +279,18 @@ class USDZExporter {
163
279
 
164
280
  }
165
281
 
166
- const canvas = imageToCanvas( texture.image, texture.flipY, options.maxTextureSize );
167
- const blob = await new Promise( resolve => canvas.toBlob( resolve, 'image/png', 1 ) );
282
+ const canvas = imageToCanvas(
283
+ texture.image,
284
+ texture.flipY,
285
+ options.maxTextureSize
286
+ );
287
+ const blob = await new Promise( ( resolve ) =>
288
+ canvas.toBlob( resolve, 'image/png', 1 )
289
+ );
168
290
 
169
- files[ `textures/Texture_${ id }.png` ] = new Uint8Array( await blob.arrayBuffer() );
291
+ files[ `textures/Texture_${id}.png` ] = new Uint8Array(
292
+ await blob.arrayBuffer()
293
+ );
170
294
 
171
295
  }
172
296
 
@@ -203,12 +327,53 @@ class USDZExporter {
203
327
 
204
328
  }
205
329
 
330
+ function getName( object, namesSet ) {
331
+
332
+ let name = object.name;
333
+ name = name.replace( /[^A-Za-z0-9_]/g, '' );
334
+ if ( /^[0-9]/.test( name ) ) {
335
+
336
+ name = '_' + name;
337
+
338
+ }
339
+
340
+ if ( name === '' ) {
341
+
342
+ if ( object.isCamera ) {
343
+
344
+ name = 'Camera';
345
+
346
+ } else {
347
+
348
+ name = 'Object';
349
+
350
+ }
351
+
352
+ }
353
+
354
+ if ( namesSet.has( name ) ) {
355
+
356
+ name = name + '_' + object.id;
357
+
358
+ }
359
+
360
+ namesSet.add( name );
361
+
362
+ return name;
363
+
364
+ }
365
+
206
366
  function imageToCanvas( image, flipY, maxTextureSize ) {
207
367
 
208
- if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
209
- ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
210
- ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ||
211
- ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
368
+ if (
369
+ ( typeof HTMLImageElement !== 'undefined' &&
370
+ image instanceof HTMLImageElement ) ||
371
+ ( typeof HTMLCanvasElement !== 'undefined' &&
372
+ image instanceof HTMLCanvasElement ) ||
373
+ ( typeof OffscreenCanvas !== 'undefined' &&
374
+ image instanceof OffscreenCanvas ) ||
375
+ ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap )
376
+ ) {
212
377
 
213
378
  const scale = maxTextureSize / Math.max( image.width, image.height );
214
379
 
@@ -233,7 +398,9 @@ function imageToCanvas( image, flipY, maxTextureSize ) {
233
398
 
234
399
  } else {
235
400
 
236
- throw new Error( 'THREE.USDZExporter: No valid image data found. Unable to process texture.' );
401
+ throw new Error(
402
+ 'THREE.USDZExporter: No valid image data found. Unable to process texture.'
403
+ );
237
404
 
238
405
  }
239
406
 
@@ -254,79 +421,121 @@ function buildHeader() {
254
421
  metersPerUnit = 1
255
422
  upAxis = "Y"
256
423
  )
257
-
258
424
  `;
259
425
 
260
426
  }
261
427
 
262
- function buildSceneStart( options ) {
263
-
264
- const alignment = options.includeAnchoringProperties === true ? `
265
- token preliminary:anchoring:type = "${options.ar.anchoring.type}"
266
- token preliminary:planeAnchoring:alignment = "${options.ar.planeAnchoring.alignment}"
267
- ` : '';
268
- return `def Xform "Root"
269
- {
270
- def Scope "Scenes" (
271
- kind = "sceneLibrary"
272
- )
273
- {
274
- def Xform "Scene" (
275
- customData = {
276
- bool preliminary_collidesWithEnvironment = 0
277
- string sceneName = "Scene"
428
+ // Xform
429
+
430
+ function buildHierarchy( object, parentNode, materials, usedNames, files, options ) {
431
+
432
+ for ( let i = 0, l = object.children.length; i < l; i ++ ) {
433
+
434
+ const child = object.children[ i ];
435
+
436
+ if ( child.visible === false && options.onlyVisible === true ) continue;
437
+
438
+ let childNode;
439
+
440
+ if ( child.isMesh ) {
441
+
442
+ const geometry = child.geometry;
443
+ const material = child.material;
444
+
445
+ if ( material.isMeshStandardMaterial ) {
446
+
447
+ const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usda';
448
+
449
+ if ( ! ( geometryFileName in files ) ) {
450
+
451
+ const meshObject = buildMeshObject( geometry );
452
+ files[ geometryFileName ] = strToU8(
453
+ buildHeader() + '\n' + meshObject.toString()
454
+ );
455
+
456
+ }
457
+
458
+ if ( ! ( material.uuid in materials ) ) {
459
+
460
+ materials[ material.uuid ] = material;
461
+
462
+ }
463
+
464
+ childNode = buildMesh(
465
+ child,
466
+ geometry,
467
+ materials[ material.uuid ],
468
+ usedNames
469
+ );
470
+
471
+ } else {
472
+
473
+ console.warn(
474
+ 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)',
475
+ child
476
+ );
477
+
278
478
  }
279
- sceneName = "Scene"
280
- )
281
- {${alignment}
282
- `;
283
479
 
284
- }
480
+ } else if ( child.isCamera ) {
285
481
 
286
- function buildSceneEnd() {
482
+ childNode = buildCamera( child, usedNames );
483
+
484
+ } else {
485
+
486
+ childNode = buildXform( child, usedNames );
287
487
 
288
- return `
289
488
  }
489
+
490
+ if ( childNode ) {
491
+
492
+ parentNode.addChild( childNode );
493
+ buildHierarchy( child, childNode, materials, usedNames, files, options );
494
+
495
+ }
496
+
290
497
  }
498
+
291
499
  }
292
500
 
293
- `;
501
+ function buildXform( object, usedNames ) {
294
502
 
295
- }
503
+ const name = getName( object, usedNames );
504
+ const transform = buildMatrix( object.matrix );
296
505
 
297
- function buildUSDFileAsString( dataToInsert ) {
506
+ if ( object.matrix.determinant() < 0 ) {
298
507
 
299
- let output = buildHeader();
300
- output += dataToInsert;
301
- return strToU8( output );
508
+ console.warn(
509
+ 'THREE.USDZExporter: USDZ does not support negative scales',
510
+ object
511
+ );
302
512
 
303
- }
513
+ }
304
514
 
305
- // Xform
515
+ const node = new USDNode( name, 'Xform' );
306
516
 
307
- function buildXform( object, geometry, material ) {
517
+ node.addProperty( `matrix4d xformOp:transform = ${transform}` );
518
+ node.addProperty( 'uniform token[] xformOpOrder = ["xformOp:transform"]' );
308
519
 
309
- const name = 'Object_' + object.id;
310
- const transform = buildMatrix( object.matrixWorld );
520
+ return node;
311
521
 
312
- if ( object.matrixWorld.determinant() < 0 ) {
522
+ }
313
523
 
314
- console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', object );
524
+ function buildMesh( object, geometry, material, usedNames ) {
315
525
 
316
- }
526
+ const node = buildXform( object, usedNames );
317
527
 
318
- return ` def Xform "${ name }" (
319
- prepend references = @./geometries/Geometry_${ geometry.id }.usda@</Geometry>
320
- prepend apiSchemas = ["MaterialBindingAPI"]
321
- )
322
- {
323
- matrix4d xformOp:transform = ${ transform }
324
- uniform token[] xformOpOrder = ["xformOp:transform"]
528
+ node.addMetadata(
529
+ 'prepend references',
530
+ `@./geometries/Geometry_${geometry.id}.usda@</Geometry>`
531
+ );
532
+ node.addMetadata( 'prepend apiSchemas', '["MaterialBindingAPI"]' );
325
533
 
326
- rel material:binding = </Materials/Material_${ material.id }>
327
- }
534
+ node.addProperty(
535
+ `rel material:binding = </Materials/Material_${material.id}>`
536
+ );
328
537
 
329
- `;
538
+ return node;
330
539
 
331
540
  }
332
541
 
@@ -334,13 +543,18 @@ function buildMatrix( matrix ) {
334
543
 
335
544
  const array = matrix.elements;
336
545
 
337
- return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`;
546
+ return `( ${buildMatrixRow( array, 0 )}, ${buildMatrixRow(
547
+ array,
548
+ 4
549
+ )}, ${buildMatrixRow( array, 8 )}, ${buildMatrixRow( array, 12 )} )`;
338
550
 
339
551
  }
340
552
 
341
553
  function buildMatrixRow( array, offset ) {
342
554
 
343
- return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`;
555
+ return `(${array[ offset + 0 ]}, ${array[ offset + 1 ]}, ${array[ offset + 2 ]}, ${
556
+ array[ offset + 3 ]
557
+ })`;
344
558
 
345
559
  }
346
560
 
@@ -348,43 +562,81 @@ function buildMatrixRow( array, offset ) {
348
562
 
349
563
  function buildMeshObject( geometry ) {
350
564
 
351
- const mesh = buildMesh( geometry );
352
- return `
353
- def "Geometry"
354
- {
355
- ${mesh}
356
- }
357
- `;
565
+ const node = new USDNode( 'Geometry' );
566
+
567
+ const meshNode = buildMeshNode( geometry );
568
+ node.addChild( meshNode );
569
+
570
+ return node;
358
571
 
359
572
  }
360
573
 
361
- function buildMesh( geometry ) {
574
+ function buildMeshNode( geometry ) {
362
575
 
363
576
  const name = 'Geometry';
364
577
  const attributes = geometry.attributes;
365
578
  const count = attributes.position.count;
366
579
 
367
- return `
368
- def Mesh "${ name }"
369
- {
370
- int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }]
371
- int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }]
372
- normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] (
373
- interpolation = "vertex"
374
- )
375
- point3f[] points = [${ buildVector3Array( attributes.position, count )}]
376
- ${ buildPrimvars( attributes ) }
377
- uniform token subdivisionScheme = "none"
580
+ const node = new USDNode( name, 'Mesh' );
581
+
582
+ node.addProperty(
583
+ `int[] faceVertexCounts = [${buildMeshVertexCount( geometry )}]`
584
+ );
585
+ node.addProperty(
586
+ `int[] faceVertexIndices = [${buildMeshVertexIndices( geometry )}]`
587
+ );
588
+ node.addProperty(
589
+ `normal3f[] normals = [${buildVector3Array( attributes.normal, count )}]`,
590
+ [ 'interpolation = "vertex"' ]
591
+ );
592
+ node.addProperty(
593
+ `point3f[] points = [${buildVector3Array( attributes.position, count )}]`
594
+ );
595
+
596
+ for ( let i = 0; i < 4; i ++ ) {
597
+
598
+ const id = i > 0 ? i : '';
599
+ const attribute = attributes[ 'uv' + id ];
600
+ if ( attribute !== undefined ) {
601
+
602
+ node.addProperty(
603
+ `texCoord2f[] primvars:st${id} = [${buildVector2Array( attribute )}]`,
604
+ [ 'interpolation = "vertex"' ]
605
+ );
606
+
607
+ }
608
+
378
609
  }
379
- `;
610
+
611
+ const colorAttribute = attributes.color;
612
+ if ( colorAttribute !== undefined ) {
613
+
614
+ node.addProperty(
615
+ `color3f[] primvars:displayColor = [${buildVector3Array(
616
+ colorAttribute,
617
+ count
618
+ )}]`,
619
+ [ 'interpolation = "vertex"' ]
620
+ );
621
+
622
+ }
623
+
624
+ node.addProperty( 'uniform token subdivisionScheme = "none"' );
625
+
626
+ return node;
380
627
 
381
628
  }
382
629
 
383
630
  function buildMeshVertexCount( geometry ) {
384
631
 
385
- const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count;
632
+ const count =
633
+ geometry.index !== null
634
+ ? geometry.index.count
635
+ : geometry.attributes.position.count;
386
636
 
387
- return Array( count / 3 ).fill( 3 ).join( ', ' );
637
+ return Array( count / 3 )
638
+ .fill( 3 )
639
+ .join( ', ' );
388
640
 
389
641
  }
390
642
 
@@ -434,7 +686,11 @@ function buildVector3Array( attribute, count ) {
434
686
  const y = attribute.getY( i );
435
687
  const z = attribute.getZ( i );
436
688
 
437
- array.push( `(${ x.toPrecision( PRECISION ) }, ${ y.toPrecision( PRECISION ) }, ${ z.toPrecision( PRECISION ) })` );
689
+ array.push(
690
+ `(${x.toPrecision( PRECISION )}, ${y.toPrecision(
691
+ PRECISION
692
+ )}, ${z.toPrecision( PRECISION )})`
693
+ );
438
694
 
439
695
  }
440
696
 
@@ -451,7 +707,9 @@ function buildVector2Array( attribute ) {
451
707
  const x = attribute.getX( i );
452
708
  const y = attribute.getY( i );
453
709
 
454
- array.push( `(${ x.toPrecision( PRECISION ) }, ${ 1 - y.toPrecision( PRECISION ) })` );
710
+ array.push(
711
+ `(${x.toPrecision( PRECISION )}, ${1 - y.toPrecision( PRECISION )})`
712
+ );
455
713
 
456
714
  }
457
715
 
@@ -459,65 +717,23 @@ function buildVector2Array( attribute ) {
459
717
 
460
718
  }
461
719
 
462
- function buildPrimvars( attributes ) {
463
-
464
- let string = '';
465
-
466
- for ( let i = 0; i < 4; i ++ ) {
467
-
468
- const id = ( i > 0 ? i : '' );
469
- const attribute = attributes[ 'uv' + id ];
470
-
471
- if ( attribute !== undefined ) {
472
-
473
- string += `
474
- texCoord2f[] primvars:st${ id } = [${ buildVector2Array( attribute )}] (
475
- interpolation = "vertex"
476
- )`;
477
-
478
- }
479
-
480
- }
481
-
482
- // vertex colors
483
-
484
- const colorAttribute = attributes.color;
485
-
486
- if ( colorAttribute !== undefined ) {
487
-
488
- const count = colorAttribute.count;
489
-
490
- string += `
491
- color3f[] primvars:displayColor = [${buildVector3Array( colorAttribute, count )}] (
492
- interpolation = "vertex"
493
- )`;
494
-
495
- }
496
-
497
- return string;
498
-
499
- }
500
-
501
720
  // Materials
502
721
 
503
722
  function buildMaterials( materials, textures, quickLookCompatible = false ) {
504
723
 
505
- const array = [];
724
+ const materialsNode = new USDNode( 'Materials' );
506
725
 
507
726
  for ( const uuid in materials ) {
508
727
 
509
728
  const material = materials[ uuid ];
510
729
 
511
- array.push( buildMaterial( material, textures, quickLookCompatible ) );
730
+ materialsNode.addChild(
731
+ buildMaterial( material, textures, quickLookCompatible )
732
+ );
512
733
 
513
734
  }
514
735
 
515
- return `def "Materials"
516
- {
517
- ${ array.join( '' ) }
518
- }
519
-
520
- `;
736
+ return materialsNode;
521
737
 
522
738
  }
523
739
 
@@ -525,11 +741,9 @@ function buildMaterial( material, textures, quickLookCompatible = false ) {
525
741
 
526
742
  // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
527
743
 
528
- const pad = ' ';
529
- const inputs = [];
530
- const samplers = [];
744
+ const materialNode = new USDNode( `Material_${material.id}`, 'Material' );
531
745
 
532
- function buildTexture( texture, mapType, color ) {
746
+ function buildTextureNodes( texture, mapType, color ) {
533
747
 
534
748
  const id = texture.source.id + '_' + texture.flipY;
535
749
 
@@ -540,7 +754,7 @@ function buildMaterial( material, textures, quickLookCompatible = false ) {
540
754
  const WRAPPINGS = {
541
755
  1000: 'repeat', // RepeatWrapping
542
756
  1001: 'clamp', // ClampToEdgeWrapping
543
- 1002: 'mirror' // MirroredRepeatWrapping
757
+ 1002: 'mirror', // MirroredRepeatWrapping
544
758
  };
545
759
 
546
760
  const repeat = texture.repeat.clone();
@@ -575,135 +789,248 @@ function buildMaterial( material, textures, quickLookCompatible = false ) {
575
789
 
576
790
  }
577
791
 
578
- return `
579
- def Shader "PrimvarReader_${ mapType }"
580
- {
581
- uniform token info:id = "UsdPrimvarReader_float2"
582
- float2 inputs:fallback = (0.0, 0.0)
583
- token inputs:varname = "${ uv }"
584
- float2 outputs:result
792
+ const primvarReaderNode = new USDNode( `PrimvarReader_${mapType}`, 'Shader' );
793
+ primvarReaderNode.addProperty(
794
+ 'uniform token info:id = "UsdPrimvarReader_float2"'
795
+ );
796
+ primvarReaderNode.addProperty( 'float2 inputs:fallback = (0.0, 0.0)' );
797
+ primvarReaderNode.addProperty( `token inputs:varname = "${uv}"` );
798
+ primvarReaderNode.addProperty( 'float2 outputs:result' );
799
+
800
+ const transform2dNode = new USDNode( `Transform2d_${mapType}`, 'Shader' );
801
+ transform2dNode.addProperty( 'uniform token info:id = "UsdTransform2d"' );
802
+ transform2dNode.addProperty(
803
+ `token inputs:in.connect = </Materials/Material_${material.id}/PrimvarReader_${mapType}.outputs:result>`
804
+ );
805
+ transform2dNode.addProperty(
806
+ `float inputs:rotation = ${( rotation * ( 180 / Math.PI ) ).toFixed(
807
+ PRECISION
808
+ )}`
809
+ );
810
+ transform2dNode.addProperty(
811
+ `float2 inputs:scale = ${buildVector2( repeat )}`
812
+ );
813
+ transform2dNode.addProperty(
814
+ `float2 inputs:translation = ${buildVector2( offset )}`
815
+ );
816
+ transform2dNode.addProperty( 'float2 outputs:result' );
817
+
818
+ const textureNode = new USDNode(
819
+ `Texture_${texture.id}_${mapType}`,
820
+ 'Shader'
821
+ );
822
+ textureNode.addProperty( 'uniform token info:id = "UsdUVTexture"' );
823
+ textureNode.addProperty( `asset inputs:file = @textures/Texture_${id}.png@` );
824
+ textureNode.addProperty(
825
+ `float2 inputs:st.connect = </Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`
826
+ );
827
+
828
+ if ( color !== undefined ) {
829
+
830
+ textureNode.addProperty( `float4 inputs:scale = ${buildColor4( color )}` );
831
+
585
832
  }
586
833
 
587
- def Shader "Transform2d_${ mapType }"
588
- {
589
- uniform token info:id = "UsdTransform2d"
590
- token inputs:in.connect = </Materials/Material_${ material.id }/PrimvarReader_${ mapType }.outputs:result>
591
- float inputs:rotation = ${ ( rotation * ( 180 / Math.PI ) ).toFixed( PRECISION ) }
592
- float2 inputs:scale = ${ buildVector2( repeat ) }
593
- float2 inputs:translation = ${ buildVector2( offset ) }
594
- float2 outputs:result
834
+ textureNode.addProperty(
835
+ `token inputs:sourceColorSpace = "${
836
+ texture.colorSpace === NoColorSpace ? 'raw' : 'sRGB'
837
+ }"`
838
+ );
839
+ textureNode.addProperty(
840
+ `token inputs:wrapS = "${WRAPPINGS[ texture.wrapS ]}"`
841
+ );
842
+ textureNode.addProperty(
843
+ `token inputs:wrapT = "${WRAPPINGS[ texture.wrapT ]}"`
844
+ );
845
+ textureNode.addProperty( 'float outputs:r' );
846
+ textureNode.addProperty( 'float outputs:g' );
847
+ textureNode.addProperty( 'float outputs:b' );
848
+ textureNode.addProperty( 'float3 outputs:rgb' );
849
+
850
+ if ( material.transparent || material.alphaTest > 0.0 ) {
851
+
852
+ textureNode.addProperty( 'float outputs:a' );
853
+
595
854
  }
596
855
 
597
- def Shader "Texture_${ texture.id }_${ mapType }"
598
- {
599
- uniform token info:id = "UsdUVTexture"
600
- asset inputs:file = @textures/Texture_${ id }.png@
601
- float2 inputs:st.connect = </Materials/Material_${ material.id }/Transform2d_${ mapType }.outputs:result>
602
- ${ color !== undefined ? 'float4 inputs:scale = ' + buildColor4( color ) : '' }
603
- token inputs:sourceColorSpace = "${ texture.colorSpace === NoColorSpace ? 'raw' : 'sRGB' }"
604
- token inputs:wrapS = "${ WRAPPINGS[ texture.wrapS ] }"
605
- token inputs:wrapT = "${ WRAPPINGS[ texture.wrapT ] }"
606
- float outputs:r
607
- float outputs:g
608
- float outputs:b
609
- float3 outputs:rgb
610
- ${ material.transparent || material.alphaTest > 0.0 ? 'float outputs:a' : '' }
611
- }`;
856
+ return [ primvarReaderNode, transform2dNode, textureNode ];
612
857
 
613
858
  }
614
859
 
615
-
616
860
  if ( material.side === DoubleSide ) {
617
861
 
618
- console.warn( 'THREE.USDZExporter: USDZ does not support double sided materials', material );
862
+ console.warn(
863
+ 'THREE.USDZExporter: USDZ does not support double sided materials',
864
+ material
865
+ );
619
866
 
620
867
  }
621
868
 
869
+ const previewSurfaceNode = new USDNode( 'PreviewSurface', 'Shader' );
870
+ previewSurfaceNode.addProperty( 'uniform token info:id = "UsdPreviewSurface"' );
871
+
622
872
  if ( material.map !== null ) {
623
873
 
624
- inputs.push( `${ pad }color3f inputs:diffuseColor.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:rgb>` );
874
+ previewSurfaceNode.addProperty(
875
+ `color3f inputs:diffuseColor.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:rgb>`
876
+ );
625
877
 
626
878
  if ( material.transparent ) {
627
879
 
628
- inputs.push( `${ pad }float inputs:opacity.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:a>` );
880
+ previewSurfaceNode.addProperty(
881
+ `float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:a>`
882
+ );
629
883
 
630
884
  } else if ( material.alphaTest > 0.0 ) {
631
885
 
632
- inputs.push( `${ pad }float inputs:opacity.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:a>` );
633
- inputs.push( `${ pad }float inputs:opacityThreshold = ${material.alphaTest}` );
886
+ previewSurfaceNode.addProperty(
887
+ `float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:a>`
888
+ );
889
+ previewSurfaceNode.addProperty(
890
+ `float inputs:opacityThreshold = ${material.alphaTest}`
891
+ );
634
892
 
635
893
  }
636
894
 
637
- samplers.push( buildTexture( material.map, 'diffuse', material.color ) );
895
+ const textureNodes = buildTextureNodes(
896
+ material.map,
897
+ 'diffuse',
898
+ material.color
899
+ );
900
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
638
901
 
639
902
  } else {
640
903
 
641
- inputs.push( `${ pad }color3f inputs:diffuseColor = ${ buildColor( material.color ) }` );
904
+ previewSurfaceNode.addProperty(
905
+ `color3f inputs:diffuseColor = ${buildColor( material.color )}`
906
+ );
642
907
 
643
908
  }
644
909
 
645
910
  if ( material.emissiveMap !== null ) {
646
911
 
647
- inputs.push( `${ pad }color3f inputs:emissiveColor.connect = </Materials/Material_${ material.id }/Texture_${ material.emissiveMap.id }_emissive.outputs:rgb>` );
648
-
649
- samplers.push( buildTexture( material.emissiveMap, 'emissive', new Color( material.emissive.r * material.emissiveIntensity, material.emissive.g * material.emissiveIntensity, material.emissive.b * material.emissiveIntensity ) ) );
912
+ previewSurfaceNode.addProperty(
913
+ `color3f inputs:emissiveColor.connect = </Materials/Material_${material.id}/Texture_${material.emissiveMap.id}_emissive.outputs:rgb>`
914
+ );
915
+
916
+ const emissiveColor = new Color(
917
+ material.emissive.r * material.emissiveIntensity,
918
+ material.emissive.g * material.emissiveIntensity,
919
+ material.emissive.b * material.emissiveIntensity
920
+ );
921
+ const textureNodes = buildTextureNodes(
922
+ material.emissiveMap,
923
+ 'emissive',
924
+ emissiveColor
925
+ );
926
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
650
927
 
651
928
  } else if ( material.emissive.getHex() > 0 ) {
652
929
 
653
- inputs.push( `${ pad }color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }` );
930
+ previewSurfaceNode.addProperty(
931
+ `color3f inputs:emissiveColor = ${buildColor( material.emissive )}`
932
+ );
654
933
 
655
934
  }
656
935
 
657
936
  if ( material.normalMap !== null ) {
658
937
 
659
- inputs.push( `${ pad }normal3f inputs:normal.connect = </Materials/Material_${ material.id }/Texture_${ material.normalMap.id }_normal.outputs:rgb>` );
938
+ previewSurfaceNode.addProperty(
939
+ `normal3f inputs:normal.connect = </Materials/Material_${material.id}/Texture_${material.normalMap.id}_normal.outputs:rgb>`
940
+ );
660
941
 
661
- samplers.push( buildTexture( material.normalMap, 'normal' ) );
942
+ const textureNodes = buildTextureNodes( material.normalMap, 'normal' );
943
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
662
944
 
663
945
  }
664
946
 
665
947
  if ( material.aoMap !== null ) {
666
948
 
667
- inputs.push( `${ pad }float inputs:occlusion.connect = </Materials/Material_${ material.id }/Texture_${ material.aoMap.id }_occlusion.outputs:r>` );
668
-
669
- samplers.push( buildTexture( material.aoMap, 'occlusion', new Color( material.aoMapIntensity, material.aoMapIntensity, material.aoMapIntensity ) ) );
949
+ previewSurfaceNode.addProperty(
950
+ `float inputs:occlusion.connect = </Materials/Material_${material.id}/Texture_${material.aoMap.id}_occlusion.outputs:r>`
951
+ );
952
+
953
+ const aoColor = new Color(
954
+ material.aoMapIntensity,
955
+ material.aoMapIntensity,
956
+ material.aoMapIntensity
957
+ );
958
+ const textureNodes = buildTextureNodes(
959
+ material.aoMap,
960
+ 'occlusion',
961
+ aoColor
962
+ );
963
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
670
964
 
671
965
  }
672
966
 
673
967
  if ( material.roughnessMap !== null ) {
674
968
 
675
- inputs.push( `${ pad }float inputs:roughness.connect = </Materials/Material_${ material.id }/Texture_${ material.roughnessMap.id }_roughness.outputs:g>` );
676
-
677
- samplers.push( buildTexture( material.roughnessMap, 'roughness', new Color( material.roughness, material.roughness, material.roughness ) ) );
969
+ previewSurfaceNode.addProperty(
970
+ `float inputs:roughness.connect = </Materials/Material_${material.id}/Texture_${material.roughnessMap.id}_roughness.outputs:g>`
971
+ );
972
+
973
+ const roughnessColor = new Color(
974
+ material.roughness,
975
+ material.roughness,
976
+ material.roughness
977
+ );
978
+ const textureNodes = buildTextureNodes(
979
+ material.roughnessMap,
980
+ 'roughness',
981
+ roughnessColor
982
+ );
983
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
678
984
 
679
985
  } else {
680
986
 
681
- inputs.push( `${ pad }float inputs:roughness = ${ material.roughness }` );
987
+ previewSurfaceNode.addProperty(
988
+ `float inputs:roughness = ${material.roughness}`
989
+ );
682
990
 
683
991
  }
684
992
 
685
993
  if ( material.metalnessMap !== null ) {
686
994
 
687
- inputs.push( `${ pad }float inputs:metallic.connect = </Materials/Material_${ material.id }/Texture_${ material.metalnessMap.id }_metallic.outputs:b>` );
688
-
689
- samplers.push( buildTexture( material.metalnessMap, 'metallic', new Color( material.metalness, material.metalness, material.metalness ) ) );
995
+ previewSurfaceNode.addProperty(
996
+ `float inputs:metallic.connect = </Materials/Material_${material.id}/Texture_${material.metalnessMap.id}_metallic.outputs:b>`
997
+ );
998
+
999
+ const metalnessColor = new Color(
1000
+ material.metalness,
1001
+ material.metalness,
1002
+ material.metalness
1003
+ );
1004
+ const textureNodes = buildTextureNodes(
1005
+ material.metalnessMap,
1006
+ 'metallic',
1007
+ metalnessColor
1008
+ );
1009
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
690
1010
 
691
1011
  } else {
692
1012
 
693
- inputs.push( `${ pad }float inputs:metallic = ${ material.metalness }` );
1013
+ previewSurfaceNode.addProperty(
1014
+ `float inputs:metallic = ${material.metalness}`
1015
+ );
694
1016
 
695
1017
  }
696
1018
 
697
1019
  if ( material.alphaMap !== null ) {
698
1020
 
699
- inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>` );
700
- inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` );
1021
+ previewSurfaceNode.addProperty(
1022
+ `float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>`
1023
+ );
1024
+ previewSurfaceNode.addProperty( 'float inputs:opacityThreshold = 0.0001' );
701
1025
 
702
- samplers.push( buildTexture( material.alphaMap, 'opacity' ) );
1026
+ const textureNodes = buildTextureNodes( material.alphaMap, 'opacity' );
1027
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
703
1028
 
704
1029
  } else {
705
1030
 
706
- inputs.push( `${pad}float inputs:opacity = ${material.opacity}` );
1031
+ previewSurfaceNode.addProperty(
1032
+ `float inputs:opacity = ${material.opacity}`
1033
+ );
707
1034
 
708
1035
  }
709
1036
 
@@ -711,115 +1038,164 @@ function buildMaterial( material, textures, quickLookCompatible = false ) {
711
1038
 
712
1039
  if ( material.clearcoatMap !== null ) {
713
1040
 
714
- inputs.push( `${pad}float inputs:clearcoat.connect = </Materials/Material_${material.id}/Texture_${material.clearcoatMap.id}_clearcoat.outputs:r>` );
715
- samplers.push( buildTexture( material.clearcoatMap, 'clearcoat', new Color( material.clearcoat, material.clearcoat, material.clearcoat ) ) );
1041
+ previewSurfaceNode.addProperty(
1042
+ `float inputs:clearcoat.connect = </Materials/Material_${material.id}/Texture_${material.clearcoatMap.id}_clearcoat.outputs:r>`
1043
+ );
1044
+
1045
+ const clearcoatColor = new Color(
1046
+ material.clearcoat,
1047
+ material.clearcoat,
1048
+ material.clearcoat
1049
+ );
1050
+ const textureNodes = buildTextureNodes(
1051
+ material.clearcoatMap,
1052
+ 'clearcoat',
1053
+ clearcoatColor
1054
+ );
1055
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
716
1056
 
717
1057
  } else {
718
1058
 
719
- inputs.push( `${pad}float inputs:clearcoat = ${material.clearcoat}` );
1059
+ previewSurfaceNode.addProperty(
1060
+ `float inputs:clearcoat = ${material.clearcoat}`
1061
+ );
720
1062
 
721
1063
  }
722
1064
 
723
1065
  if ( material.clearcoatRoughnessMap !== null ) {
724
1066
 
725
- inputs.push( `${pad}float inputs:clearcoatRoughness.connect = </Materials/Material_${material.id}/Texture_${material.clearcoatRoughnessMap.id}_clearcoatRoughness.outputs:g>` );
726
- samplers.push( buildTexture( material.clearcoatRoughnessMap, 'clearcoatRoughness', new Color( material.clearcoatRoughness, material.clearcoatRoughness, material.clearcoatRoughness ) ) );
1067
+ previewSurfaceNode.addProperty(
1068
+ `float inputs:clearcoatRoughness.connect = </Materials/Material_${material.id}/Texture_${material.clearcoatRoughnessMap.id}_clearcoatRoughness.outputs:g>`
1069
+ );
1070
+
1071
+ const clearcoatRoughnessColor = new Color(
1072
+ material.clearcoatRoughness,
1073
+ material.clearcoatRoughness,
1074
+ material.clearcoatRoughness
1075
+ );
1076
+ const textureNodes = buildTextureNodes(
1077
+ material.clearcoatRoughnessMap,
1078
+ 'clearcoatRoughness',
1079
+ clearcoatRoughnessColor
1080
+ );
1081
+ textureNodes.forEach( ( node ) => materialNode.addChild( node ) );
727
1082
 
728
1083
  } else {
729
1084
 
730
- inputs.push( `${pad}float inputs:clearcoatRoughness = ${material.clearcoatRoughness}` );
1085
+ previewSurfaceNode.addProperty(
1086
+ `float inputs:clearcoatRoughness = ${material.clearcoatRoughness}`
1087
+ );
731
1088
 
732
1089
  }
733
1090
 
734
- inputs.push( `${ pad }float inputs:ior = ${ material.ior }` );
1091
+ previewSurfaceNode.addProperty( `float inputs:ior = ${material.ior}` );
735
1092
 
736
1093
  }
737
1094
 
738
- return `
739
- def Material "Material_${ material.id }"
740
- {
741
- def Shader "PreviewSurface"
742
- {
743
- uniform token info:id = "UsdPreviewSurface"
744
- ${ inputs.join( '\n' ) }
745
- int inputs:useSpecularWorkflow = 0
746
- token outputs:surface
747
- }
1095
+ previewSurfaceNode.addProperty( 'int inputs:useSpecularWorkflow = 0' );
1096
+ previewSurfaceNode.addProperty( 'token outputs:surface' );
748
1097
 
749
- token outputs:surface.connect = </Materials/Material_${ material.id }/PreviewSurface.outputs:surface>
1098
+ materialNode.addChild( previewSurfaceNode );
750
1099
 
751
- ${ samplers.join( '\n' ) }
1100
+ materialNode.addProperty(
1101
+ `token outputs:surface.connect = </Materials/Material_${material.id}/PreviewSurface.outputs:surface>`
1102
+ );
752
1103
 
753
- }
754
- `;
1104
+ return materialNode;
755
1105
 
756
1106
  }
757
1107
 
758
1108
  function buildColor( color ) {
759
1109
 
760
- return `(${ color.r }, ${ color.g }, ${ color.b })`;
1110
+ return `(${color.r}, ${color.g}, ${color.b})`;
761
1111
 
762
1112
  }
763
1113
 
764
1114
  function buildColor4( color ) {
765
1115
 
766
- return `(${ color.r }, ${ color.g }, ${ color.b }, 1.0)`;
1116
+ return `(${color.r}, ${color.g}, ${color.b}, 1.0)`;
767
1117
 
768
1118
  }
769
1119
 
770
1120
  function buildVector2( vector ) {
771
1121
 
772
- return `(${ vector.x }, ${ vector.y })`;
1122
+ return `(${vector.x}, ${vector.y})`;
773
1123
 
774
1124
  }
775
1125
 
1126
+ function buildCamera( camera, usedNames ) {
776
1127
 
777
- function buildCamera( camera ) {
1128
+ const name = getName( camera, usedNames );
778
1129
 
779
- const name = camera.name ? camera.name : 'Camera_' + camera.id;
1130
+ const transform = buildMatrix( camera.matrix );
780
1131
 
781
- const transform = buildMatrix( camera.matrixWorld );
1132
+ if ( camera.matrix.determinant() < 0 ) {
782
1133
 
783
- if ( camera.matrixWorld.determinant() < 0 ) {
784
-
785
- console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', camera );
1134
+ console.warn(
1135
+ 'THREE.USDZExporter: USDZ does not support negative scales',
1136
+ camera
1137
+ );
786
1138
 
787
1139
  }
788
1140
 
1141
+ const node = new USDNode( name, 'Camera' );
1142
+ node.addProperty( `matrix4d xformOp:transform = ${transform}` );
1143
+ node.addProperty( 'uniform token[] xformOpOrder = ["xformOp:transform"]' );
1144
+
1145
+ const projection = camera.isOrthographicCamera
1146
+ ? 'orthographic'
1147
+ : 'perspective';
1148
+ node.addProperty( `token projection = "${projection}"` );
1149
+
1150
+ const clippingRange = `(${camera.near.toPrecision(
1151
+ PRECISION
1152
+ )}, ${camera.far.toPrecision( PRECISION )})`;
1153
+ node.addProperty( `float2 clippingRange = ${clippingRange}` );
1154
+
1155
+ let horizontalAperture;
789
1156
  if ( camera.isOrthographicCamera ) {
790
1157
 
791
- return `def Camera "${name}"
792
- {
793
- matrix4d xformOp:transform = ${ transform }
794
- uniform token[] xformOpOrder = ["xformOp:transform"]
1158
+ horizontalAperture = (
1159
+ ( Math.abs( camera.left ) + Math.abs( camera.right ) ) *
1160
+ 10
1161
+ ).toPrecision( PRECISION );
795
1162
 
796
- float2 clippingRange = (${ camera.near.toPrecision( PRECISION ) }, ${ camera.far.toPrecision( PRECISION ) })
797
- float horizontalAperture = ${ ( ( Math.abs( camera.left ) + Math.abs( camera.right ) ) * 10 ).toPrecision( PRECISION ) }
798
- float verticalAperture = ${ ( ( Math.abs( camera.top ) + Math.abs( camera.bottom ) ) * 10 ).toPrecision( PRECISION ) }
799
- token projection = "orthographic"
800
- }
1163
+ } else {
1164
+
1165
+ horizontalAperture = camera.getFilmWidth().toPrecision( PRECISION );
1166
+
1167
+ }
1168
+
1169
+ node.addProperty( `float horizontalAperture = ${horizontalAperture}` );
1170
+
1171
+ let verticalAperture;
1172
+ if ( camera.isOrthographicCamera ) {
801
1173
 
802
- `;
1174
+ verticalAperture = (
1175
+ ( Math.abs( camera.top ) + Math.abs( camera.bottom ) ) *
1176
+ 10
1177
+ ).toPrecision( PRECISION );
803
1178
 
804
1179
  } else {
805
1180
 
806
- return `def Camera "${name}"
807
- {
808
- matrix4d xformOp:transform = ${ transform }
809
- uniform token[] xformOpOrder = ["xformOp:transform"]
810
-
811
- float2 clippingRange = (${ camera.near.toPrecision( PRECISION ) }, ${ camera.far.toPrecision( PRECISION ) })
812
- float focalLength = ${ camera.getFocalLength().toPrecision( PRECISION ) }
813
- float focusDistance = ${ camera.focus.toPrecision( PRECISION ) }
814
- float horizontalAperture = ${ camera.getFilmWidth().toPrecision( PRECISION ) }
815
- token projection = "perspective"
816
- float verticalAperture = ${ camera.getFilmHeight().toPrecision( PRECISION ) }
817
- }
1181
+ verticalAperture = camera.getFilmHeight().toPrecision( PRECISION );
1182
+
1183
+ }
1184
+
1185
+ node.addProperty( `float verticalAperture = ${verticalAperture}` );
818
1186
 
819
- `;
1187
+ if ( camera.isPerspectiveCamera ) {
1188
+
1189
+ const focalLength = camera.getFocalLength().toPrecision( PRECISION );
1190
+ node.addProperty( `float focalLength = ${focalLength}` );
1191
+
1192
+ const focusDistance = camera.focus.toPrecision( PRECISION );
1193
+ node.addProperty( `float focusDistance = ${focusDistance}` );
820
1194
 
821
1195
  }
822
1196
 
1197
+ return node;
1198
+
823
1199
  }
824
1200
 
825
1201
  /**
@@ -828,6 +1204,7 @@ function buildCamera( camera ) {
828
1204
  * @typedef {Object} USDZExporter~Options
829
1205
  * @property {number} [maxTextureSize=1024] - The maximum texture size that is going to be exported.
830
1206
  * @property {boolean} [includeAnchoringProperties=true] - Whether to include anchoring properties or not.
1207
+ * @property {boolean} [onlyVisible=true] - Export only visible 3D objects.
831
1208
  * @property {Object} [ar] - If `includeAnchoringProperties` is set to `true`, the anchoring type and alignment
832
1209
  * can be configured via `ar.anchoring.type` and `ar.planeAnchoring.alignment`.
833
1210
  * @property {boolean} [quickLookCompatible=false] - Whether to make the exported USDZ compatible to QuickLook