@plastic-software/three 0.183.4 → 0.184.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 (277) hide show
  1. package/build/three.cjs +773 -286
  2. package/build/three.core.js +372 -110
  3. package/build/three.core.min.js +1 -1
  4. package/build/three.module.js +426 -180
  5. package/build/three.module.min.js +1 -1
  6. package/build/three.tsl.js +7 -1
  7. package/build/three.tsl.min.js +1 -1
  8. package/build/three.webgpu.js +2979 -1281
  9. package/build/three.webgpu.min.js +1 -1
  10. package/build/three.webgpu.nodes.js +2942 -1281
  11. package/build/three.webgpu.nodes.min.js +1 -1
  12. package/examples/jsm/Addons.js +11 -0
  13. package/examples/jsm/animation/CCDIKSolver.js +5 -1
  14. package/examples/jsm/controls/ArcballControls.js +4 -1
  15. package/examples/jsm/controls/DragControls.js +2 -2
  16. package/examples/jsm/controls/FirstPersonControls.js +58 -54
  17. package/examples/jsm/controls/FlyControls.js +4 -0
  18. package/examples/jsm/controls/OrbitControls.js +2 -2
  19. package/examples/jsm/controls/TrackballControls.js +2 -2
  20. package/examples/jsm/controls/TransformControls.js +34 -2
  21. package/examples/jsm/csm/CSMShadowNode.js +6 -2
  22. package/examples/jsm/exporters/GLTFExporter.js +21 -5
  23. package/examples/jsm/geometries/TextGeometry.js +18 -0
  24. package/examples/jsm/helpers/LightProbeGridHelper.js +221 -0
  25. package/examples/jsm/inspector/Extension.js +13 -0
  26. package/examples/jsm/inspector/Inspector.js +169 -114
  27. package/examples/jsm/inspector/RendererInspector.js +2 -2
  28. package/examples/jsm/inspector/extensions/extensions.json +6 -0
  29. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +916 -0
  30. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +281 -0
  31. package/examples/jsm/inspector/tabs/Memory.js +128 -0
  32. package/examples/jsm/inspector/tabs/Parameters.js +34 -2
  33. package/examples/jsm/inspector/tabs/Performance.js +2 -2
  34. package/examples/jsm/inspector/tabs/Settings.js +264 -0
  35. package/examples/jsm/inspector/tabs/Timeline.js +1611 -0
  36. package/examples/jsm/inspector/tabs/Viewer.js +105 -3
  37. package/examples/jsm/inspector/ui/Graph.js +2 -2
  38. package/examples/jsm/inspector/ui/List.js +1 -1
  39. package/examples/jsm/inspector/ui/Profiler.js +273 -176
  40. package/examples/jsm/inspector/ui/Style.js +64 -10
  41. package/examples/jsm/inspector/ui/Tab.js +39 -7
  42. package/examples/jsm/inspector/ui/Values.js +39 -2
  43. package/examples/jsm/inspector/ui/utils.js +13 -0
  44. package/examples/jsm/interaction/InteractionManager.js +226 -0
  45. package/examples/jsm/libs/meshopt_decoder.module.js +8 -8
  46. package/examples/jsm/lighting/DynamicLighting.js +82 -0
  47. package/examples/jsm/lighting/LightProbeGrid.js +651 -0
  48. package/examples/jsm/lines/LineMaterial.js +1 -1
  49. package/examples/jsm/loaders/EXRLoader.js +682 -43
  50. package/examples/jsm/loaders/FBXLoader.js +233 -33
  51. package/examples/jsm/loaders/GLTFLoader.js +24 -7
  52. package/examples/jsm/loaders/HDRLoader.js +1 -1
  53. package/examples/jsm/loaders/KTX2Loader.js +8 -2
  54. package/examples/jsm/loaders/LDrawLoader.js +39 -47
  55. package/examples/jsm/loaders/SVGLoader.js +1 -1
  56. package/examples/jsm/loaders/VTKLoader.js +5 -1
  57. package/examples/jsm/loaders/collada/ColladaComposer.js +101 -7
  58. package/examples/jsm/loaders/collada/ColladaParser.js +19 -4
  59. package/examples/jsm/loaders/usd/USDAParser.js +6 -0
  60. package/examples/jsm/loaders/usd/USDCParser.js +26 -0
  61. package/examples/jsm/loaders/usd/USDComposer.js +656 -103
  62. package/examples/jsm/misc/GPUComputationRenderer.js +2 -0
  63. package/examples/jsm/misc/RollerCoaster.js +42 -4
  64. package/examples/jsm/modifiers/TessellateModifier.js +1 -1
  65. package/examples/jsm/objects/Reflector.js +73 -25
  66. package/examples/jsm/objects/Sky.js +14 -2
  67. package/examples/jsm/objects/SkyMesh.js +23 -6
  68. package/examples/jsm/renderers/Projector.js +18 -38
  69. package/examples/jsm/renderers/SVGRenderer.js +6 -25
  70. package/examples/jsm/transpiler/GLSLDecoder.js +2 -2
  71. package/examples/jsm/tsl/WebGLNodesHandler.js +605 -0
  72. package/examples/jsm/tsl/display/AfterImageNode.js +10 -0
  73. package/examples/jsm/tsl/display/AnamorphicNode.js +11 -0
  74. package/examples/jsm/tsl/display/BilateralBlurNode.js +10 -0
  75. package/examples/jsm/tsl/display/ChromaticAberrationNode.js +3 -36
  76. package/examples/jsm/tsl/display/FSR1Node.js +477 -0
  77. package/examples/jsm/tsl/display/GTAONode.js +2 -1
  78. package/examples/jsm/tsl/display/GaussianBlurNode.js +10 -0
  79. package/examples/jsm/tsl/display/GodraysNode.js +2 -11
  80. package/examples/jsm/tsl/display/OutlineNode.js +66 -16
  81. package/examples/jsm/tsl/display/SSGINode.js +0 -4
  82. package/examples/jsm/tsl/display/SharpenNode.js +283 -0
  83. package/examples/jsm/tsl/display/TAAUNode.js +835 -0
  84. package/examples/jsm/tsl/display/TRAANode.js +48 -7
  85. package/examples/jsm/tsl/lighting/DynamicLightsNode.js +300 -0
  86. package/examples/jsm/tsl/lighting/data/AmbientLightDataNode.js +61 -0
  87. package/examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js +111 -0
  88. package/examples/jsm/tsl/lighting/data/HemisphereLightDataNode.js +99 -0
  89. package/examples/jsm/tsl/lighting/data/PointLightDataNode.js +134 -0
  90. package/examples/jsm/tsl/lighting/data/SpotLightDataNode.js +161 -0
  91. package/examples/jsm/tsl/math/Bayer.js +13 -2
  92. package/examples/jsm/utils/BufferGeometryUtils.js +2 -3
  93. package/examples/jsm/utils/ColorUtils.js +76 -0
  94. package/examples/jsm/utils/SkeletonUtils.js +14 -8
  95. package/examples/jsm/webxr/XRHandMeshModel.js +36 -10
  96. package/examples/jsm/webxr/XRHandModelFactory.js +2 -1
  97. package/package.json +4 -4
  98. package/src/Three.Core.js +1 -0
  99. package/src/Three.TSL.js +6 -0
  100. package/src/Three.WebGPU.Nodes.js +3 -0
  101. package/src/Three.WebGPU.js +6 -0
  102. package/src/animation/AnimationAction.js +11 -1
  103. package/src/audio/AudioContext.js +2 -2
  104. package/src/constants.js +1 -1
  105. package/src/core/BufferAttribute.js +13 -1
  106. package/src/core/Clock.js +1 -1
  107. package/src/core/Object3D.js +1 -5
  108. package/src/core/RenderTarget.js +1 -0
  109. package/src/extras/curves/CatmullRomCurve3.js +3 -2
  110. package/src/loaders/AudioLoader.js +11 -1
  111. package/src/loaders/DataTextureLoader.js +6 -4
  112. package/src/loaders/FileLoader.js +1 -2
  113. package/src/loaders/ImageBitmapLoader.js +4 -6
  114. package/src/loaders/MaterialLoader.js +1 -1
  115. package/src/loaders/ObjectLoader.js +25 -4
  116. package/src/loaders/nodes/NodeObjectLoader.js +18 -0
  117. package/src/materials/MeshToonMaterial.js +1 -1
  118. package/src/materials/nodes/Line2NodeMaterial.js +27 -0
  119. package/src/materials/nodes/NodeMaterial.js +0 -27
  120. package/src/materials/nodes/manager/NodeMaterialObserver.js +188 -89
  121. package/src/math/Line3.js +3 -0
  122. package/src/math/Matrix2.js +13 -9
  123. package/src/math/Matrix3.js +13 -9
  124. package/src/math/Matrix4.js +13 -9
  125. package/src/math/Plane.js +4 -3
  126. package/src/math/Triangle.js +1 -1
  127. package/src/math/Vector2.js +11 -7
  128. package/src/math/Vector3.js +12 -8
  129. package/src/math/Vector4.js +13 -9
  130. package/src/nodes/Nodes.js +0 -1
  131. package/src/nodes/TSL.js +1 -1
  132. package/src/nodes/accessors/BufferAttributeNode.js +9 -3
  133. package/src/nodes/accessors/CubeTextureNode.js +7 -1
  134. package/src/nodes/accessors/MaterialProperties.js +2 -5
  135. package/src/nodes/accessors/Object3DNode.js +1 -1
  136. package/src/nodes/accessors/ReferenceBaseNode.js +2 -2
  137. package/src/nodes/accessors/ReferenceNode.js +4 -4
  138. package/src/nodes/accessors/SceneProperties.js +2 -8
  139. package/src/nodes/accessors/StorageBufferNode.js +10 -4
  140. package/src/nodes/accessors/StorageTextureNode.js +4 -9
  141. package/src/nodes/accessors/TextureNode.js +10 -2
  142. package/src/nodes/accessors/UniformArrayNode.js +2 -2
  143. package/src/nodes/code/FunctionCallNode.js +1 -1
  144. package/src/nodes/code/FunctionNode.js +1 -1
  145. package/src/nodes/core/ArrayNode.js +1 -1
  146. package/src/nodes/core/AssignNode.js +1 -1
  147. package/src/nodes/core/AttributeNode.js +1 -1
  148. package/src/nodes/core/BypassNode.js +1 -1
  149. package/src/nodes/core/ContextNode.js +1 -1
  150. package/src/nodes/core/IndexNode.js +2 -1
  151. package/src/nodes/core/InputNode.js +1 -1
  152. package/src/nodes/core/InspectorNode.js +1 -1
  153. package/src/nodes/core/IsolateNode.js +1 -1
  154. package/src/nodes/core/Node.js +83 -12
  155. package/src/nodes/core/NodeBuilder.js +117 -16
  156. package/src/nodes/core/NodeUtils.js +1 -1
  157. package/src/nodes/core/OutputStructNode.js +1 -1
  158. package/src/nodes/core/ParameterNode.js +1 -1
  159. package/src/nodes/core/StackNode.js +1 -1
  160. package/src/nodes/core/StructNode.js +1 -1
  161. package/src/nodes/core/StructTypeNode.js +1 -1
  162. package/src/nodes/core/SubBuildNode.js +1 -1
  163. package/src/nodes/core/UniformGroupNode.js +36 -6
  164. package/src/nodes/core/VarNode.js +1 -1
  165. package/src/nodes/core/VaryingNode.js +1 -1
  166. package/src/nodes/display/NormalMapNode.js +2 -2
  167. package/src/nodes/display/PassNode.js +27 -7
  168. package/src/nodes/display/RenderOutputNode.js +4 -4
  169. package/src/nodes/display/ScreenNode.js +1 -1
  170. package/src/nodes/display/ViewportDepthTextureNode.js +11 -15
  171. package/src/nodes/display/ViewportTextureNode.js +18 -7
  172. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +2 -2
  173. package/src/nodes/geometry/RangeNode.js +1 -1
  174. package/src/nodes/gpgpu/AtomicFunctionNode.js +1 -1
  175. package/src/nodes/gpgpu/BarrierNode.js +9 -0
  176. package/src/nodes/gpgpu/ComputeBuiltinNode.js +1 -1
  177. package/src/nodes/gpgpu/ComputeNode.js +69 -44
  178. package/src/nodes/gpgpu/SubgroupFunctionNode.js +1 -1
  179. package/src/nodes/lighting/LightsNode.js +6 -27
  180. package/src/nodes/lighting/ShadowNode.js +24 -2
  181. package/src/nodes/math/BitcastNode.js +1 -1
  182. package/src/nodes/math/ConditionalNode.js +1 -1
  183. package/src/nodes/math/MathNode.js +73 -1
  184. package/src/nodes/math/OperatorNode.js +1 -1
  185. package/src/nodes/math/PackFloatNode.js +1 -1
  186. package/src/nodes/math/UnpackFloatNode.js +1 -1
  187. package/src/nodes/tsl/TSLBase.js +1 -1
  188. package/src/nodes/tsl/TSLCore.js +21 -3
  189. package/src/nodes/utils/ArrayElementNode.js +1 -1
  190. package/src/nodes/utils/ConvertNode.js +1 -1
  191. package/src/nodes/utils/DebugNode.js +1 -1
  192. package/src/nodes/utils/EventNode.js +30 -0
  193. package/src/nodes/utils/FlipNode.js +1 -1
  194. package/src/nodes/utils/FunctionOverloadingNode.js +1 -1
  195. package/src/nodes/utils/JoinNode.js +1 -1
  196. package/src/nodes/utils/MemberNode.js +1 -1
  197. package/src/nodes/utils/Remap.js +48 -0
  198. package/src/nodes/utils/RotateNode.js +1 -1
  199. package/src/nodes/utils/SetNode.js +1 -1
  200. package/src/nodes/utils/SplitNode.js +1 -1
  201. package/src/objects/BatchedMesh.js +17 -2
  202. package/src/objects/InstancedMesh.js +19 -3
  203. package/src/objects/SkinnedMesh.js +26 -9
  204. package/src/renderers/WebGLRenderer.js +147 -48
  205. package/src/renderers/common/Animation.js +3 -3
  206. package/src/renderers/common/Attributes.js +15 -1
  207. package/src/renderers/common/Backend.js +0 -8
  208. package/src/renderers/common/Background.js +2 -2
  209. package/src/renderers/common/BindGroup.js +1 -8
  210. package/src/renderers/common/Bindings.js +2 -2
  211. package/src/renderers/common/ComputePipeline.js +1 -1
  212. package/src/renderers/common/CubeRenderTarget.js +1 -1
  213. package/src/renderers/common/Info.js +333 -4
  214. package/src/renderers/common/InspectorBase.js +6 -1
  215. package/src/renderers/common/Pipelines.js +36 -3
  216. package/src/renderers/common/ReadbackBuffer.js +78 -0
  217. package/src/renderers/common/RenderBundle.js +3 -1
  218. package/src/renderers/common/RenderBundles.js +5 -2
  219. package/src/renderers/common/RenderObject.js +2 -2
  220. package/src/renderers/common/RenderObjects.js +3 -3
  221. package/src/renderers/common/RenderPipeline.js +35 -6
  222. package/src/renderers/common/Renderer.js +232 -53
  223. package/src/renderers/common/Textures.js +72 -3
  224. package/src/renderers/common/UniformsGroup.js +1 -1
  225. package/src/renderers/common/XRManager.js +34 -27
  226. package/src/renderers/common/extras/PMREMGenerator.js +23 -15
  227. package/src/renderers/common/nodes/NodeBuilderState.js +1 -1
  228. package/src/renderers/common/nodes/NodeManager.js +230 -99
  229. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +0 -1
  230. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +1 -1
  231. package/src/renderers/shaders/ShaderChunk/lightprobes_pars_fragment.glsl.js +80 -0
  232. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +8 -0
  233. package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +2 -0
  234. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +1 -3
  235. package/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js +7 -0
  236. package/src/renderers/shaders/ShaderChunk/premultiplied_alpha_fragment.glsl.js +0 -1
  237. package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +12 -2
  238. package/src/renderers/shaders/ShaderChunk.js +2 -0
  239. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +1 -2
  240. package/src/renderers/shaders/ShaderLib.js +0 -1
  241. package/src/renderers/shaders/UniformsLib.js +7 -2
  242. package/src/renderers/shaders/UniformsUtils.js +27 -5
  243. package/src/renderers/webgl/WebGLAnimation.js +2 -1
  244. package/src/renderers/webgl/WebGLBackground.js +13 -13
  245. package/src/renderers/webgl/WebGLBufferRenderer.js +0 -32
  246. package/src/renderers/webgl/WebGLCapabilities.js +6 -0
  247. package/src/renderers/webgl/WebGLIndexedBufferRenderer.js +0 -32
  248. package/src/renderers/webgl/WebGLMaterials.js +12 -13
  249. package/src/renderers/webgl/WebGLOutput.js +4 -1
  250. package/src/renderers/webgl/WebGLProgram.js +4 -0
  251. package/src/renderers/webgl/WebGLPrograms.js +19 -3
  252. package/src/renderers/webgl/WebGLRenderStates.js +13 -2
  253. package/src/renderers/webgl/WebGLState.js +43 -0
  254. package/src/renderers/webgl/WebGLTextures.js +129 -26
  255. package/src/renderers/webgl/WebGLUniformsGroups.js +19 -0
  256. package/src/renderers/webgl-fallback/WebGLBackend.js +106 -65
  257. package/src/renderers/webgl-fallback/WebGLBufferRenderer.js +0 -41
  258. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +29 -51
  259. package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +53 -19
  260. package/src/renderers/webgl-fallback/utils/WebGLCapabilities.js +25 -0
  261. package/src/renderers/webgl-fallback/utils/WebGLState.js +42 -1
  262. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +63 -50
  263. package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +1 -1
  264. package/src/renderers/webgpu/WebGPUBackend.js +160 -146
  265. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +55 -33
  266. package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +103 -17
  267. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +1 -1
  268. package/src/renderers/webgpu/utils/WebGPUCapabilities.js +48 -0
  269. package/src/renderers/webgpu/utils/WebGPUConstants.js +8 -0
  270. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +91 -17
  271. package/src/renderers/webgpu/utils/WebGPUUtils.js +18 -2
  272. package/src/renderers/webxr/WebXRController.js +12 -0
  273. package/src/textures/HTMLTexture.js +74 -0
  274. package/src/textures/Source.js +1 -1
  275. package/src/textures/Texture.js +13 -2
  276. package/src/utils.js +23 -1
  277. package/src/nodes/utils/RemapNode.js +0 -125
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  AnimationClip,
3
+ BoxGeometry,
3
4
  BufferAttribute,
4
5
  BufferGeometry,
6
+ CapsuleGeometry,
5
7
  ClampToEdgeWrapping,
8
+ Color,
9
+ ConeGeometry,
10
+ CylinderGeometry,
11
+ DirectionalLight,
6
12
  Euler,
7
13
  Group,
8
14
  Matrix4,
@@ -11,13 +17,19 @@ import {
11
17
  MirroredRepeatWrapping,
12
18
  NoColorSpace,
13
19
  Object3D,
20
+ OrthographicCamera,
21
+ PerspectiveCamera,
22
+ PointLight,
14
23
  Quaternion,
15
24
  QuaternionKeyframeTrack,
25
+ RectAreaLight,
16
26
  RepeatWrapping,
17
27
  ShapeUtils,
18
28
  SkinnedMesh,
19
29
  Skeleton,
20
30
  Bone,
31
+ SphereGeometry,
32
+ SpotLight,
21
33
  SRGBColorSpace,
22
34
  Texture,
23
35
  Vector2,
@@ -44,6 +56,19 @@ const SpecType = {
44
56
  VariantSet: 11
45
57
  };
46
58
 
59
+ // UsdGeomCamera fallback values (OpenUSD schema)
60
+ const USD_CAMERA_DEFAULTS = {
61
+ projection: 'perspective',
62
+ clippingRange: [ 1, 1000000 ],
63
+ horizontalAperture: 20.955,
64
+ verticalAperture: 15.2908,
65
+ horizontalApertureOffset: 0,
66
+ verticalApertureOffset: 0,
67
+ focalLength: 50,
68
+ focusDistance: 0,
69
+ fStop: 0
70
+ };
71
+
47
72
  /**
48
73
  * USDComposer handles scene composition from parsed USD data.
49
74
  * This includes reference resolution, variant selection, transform handling,
@@ -92,9 +117,29 @@ class USDComposer {
92
117
  // Bind skeletons to skinned meshes
93
118
  this._bindSkeletons();
94
119
 
120
+ // Expose skeleton on the root group so that AnimationMixer's
121
+ // PropertyBinding.findNode resolves bone names before scene objects.
122
+ // Without this, Xform prims that share a name with a skeleton joint
123
+ // would be animated instead of the bone.
124
+ const skeletonPaths = Object.keys( this.skeletons );
125
+ if ( skeletonPaths.length === 1 ) {
126
+
127
+ group.skeleton = this.skeletons[ skeletonPaths[ 0 ] ].skeleton;
128
+
129
+ }
130
+
95
131
  // Build animations
96
132
  group.animations = this._buildAnimations();
97
133
 
134
+ // Handle metersPerUnit scaling
135
+ const metersPerUnit = rootFields.metersPerUnit;
136
+
137
+ if ( metersPerUnit !== undefined && metersPerUnit !== 1 ) {
138
+
139
+ group.scale.setScalar( metersPerUnit );
140
+
141
+ }
142
+
98
143
  // Handle Z-up to Y-up conversion
99
144
  if ( rootSpec && rootSpec.fields && rootSpec.fields.upAxis === 'Z' ) {
100
145
 
@@ -423,18 +468,37 @@ class USDComposer {
423
468
 
424
469
  }
425
470
 
426
- // Build shader index (shaders are children of materials)
471
+ // Build shader index (shaders are children or descendants of materials)
427
472
  if ( typeName === 'Shader' && lastSlash > 0 ) {
428
473
 
429
- const materialPath = path.slice( 0, lastSlash );
474
+ // Walk up ancestors to find the nearest Material prim.
475
+ // Shaders may be direct children of a Material, or nested
476
+ // inside a NodeGraph (common with MaterialX materials).
430
477
 
431
- if ( ! this.shadersByMaterialPath.has( materialPath ) ) {
478
+ let ancestorPath = path.slice( 0, lastSlash );
432
479
 
433
- this.shadersByMaterialPath.set( materialPath, [] );
480
+ while ( ancestorPath.length > 0 ) {
434
481
 
435
- }
482
+ const ancestorSpec = this.specsByPath[ ancestorPath ];
483
+
484
+ if ( ancestorSpec && ancestorSpec.specType === SpecType.Prim && ancestorSpec.fields.typeName === 'Material' ) {
485
+
486
+ if ( ! this.shadersByMaterialPath.has( ancestorPath ) ) {
487
+
488
+ this.shadersByMaterialPath.set( ancestorPath, [] );
436
489
 
437
- this.shadersByMaterialPath.get( materialPath ).push( path );
490
+ }
491
+
492
+ this.shadersByMaterialPath.get( ancestorPath ).push( path );
493
+ break;
494
+
495
+ }
496
+
497
+ const slash = ancestorPath.lastIndexOf( '/' );
498
+ if ( slash <= 0 ) break;
499
+ ancestorPath = ancestorPath.slice( 0, slash );
500
+
501
+ }
438
502
 
439
503
  }
440
504
 
@@ -561,56 +625,71 @@ class USDComposer {
561
625
  const typeName = spec.fields.typeName;
562
626
 
563
627
  // Check for references/payloads
564
- const refValue = this._getReference( spec );
565
- if ( refValue ) {
628
+ const refValues = this._getReferences( spec );
629
+ if ( refValues.length > 0 ) {
566
630
 
567
631
  // Get local variant selections from this prim
568
632
  const localVariants = this._getLocalVariantSelections( spec.fields );
569
633
 
570
- // Resolve the reference
571
- const referencedGroup = this._resolveReference( refValue, localVariants );
572
- if ( referencedGroup ) {
634
+ // Resolve all references
635
+ const resolvedGroups = [];
636
+ for ( const refValue of refValues ) {
637
+
638
+ const referencedGroup = this._resolveReference( refValue, localVariants );
639
+ if ( referencedGroup ) resolvedGroups.push( referencedGroup );
640
+
641
+ }
642
+
643
+ if ( resolvedGroups.length > 0 ) {
573
644
 
574
645
  const attrs = this._getAttributes( path );
575
646
 
576
- // Check if the referenced content is a single mesh (or container with single mesh)
647
+ // Single reference with single mesh: use optimized path
577
648
  // This handles the USDZExporter pattern: Xform references geometry file
578
- const singleMesh = this._findSingleMesh( referencedGroup );
649
+ if ( resolvedGroups.length === 1 ) {
579
650
 
580
- if ( singleMesh && ( typeName === 'Xform' || ! typeName ) ) {
651
+ const singleMesh = this._findSingleMesh( resolvedGroups[ 0 ] );
581
652
 
582
- // Merge the mesh into this prim
583
- singleMesh.name = name;
584
- this.applyTransform( singleMesh, spec.fields, attrs );
653
+ if ( singleMesh && ( typeName === 'Xform' || ! typeName ) ) {
585
654
 
586
- // Apply material binding from the referencing prim if present
587
- this._applyMaterialBinding( singleMesh, path );
655
+ // Merge the mesh into this prim
656
+ singleMesh.name = name;
657
+ this.applyTransform( singleMesh, spec.fields, attrs );
588
658
 
589
- parent.add( singleMesh );
659
+ // Apply material binding from the referencing prim if present
660
+ this._applyMaterialBinding( singleMesh, path );
590
661
 
591
- // Still build local children (overrides)
592
- this._buildHierarchy( singleMesh, path );
662
+ parent.add( singleMesh );
593
663
 
594
- } else {
664
+ // Still build local children (overrides)
665
+ this._buildHierarchy( singleMesh, path );
666
+
667
+ continue;
595
668
 
596
- // Create a container for the referenced content
597
- const obj = new Object3D();
598
- obj.name = name;
599
- this.applyTransform( obj, spec.fields, attrs );
669
+ }
670
+
671
+ }
672
+
673
+ // Create a container for the referenced content
674
+ const obj = new Object3D();
675
+ obj.name = name;
676
+ this.applyTransform( obj, spec.fields, attrs );
677
+
678
+ // Add all children from all resolved references
679
+ for ( const referencedGroup of resolvedGroups ) {
600
680
 
601
- // Add all children from the referenced group
602
681
  while ( referencedGroup.children.length > 0 ) {
603
682
 
604
683
  obj.add( referencedGroup.children[ 0 ] );
605
684
 
606
685
  }
607
686
 
608
- parent.add( obj );
687
+ }
609
688
 
610
- // Still build local children (overrides)
611
- this._buildHierarchy( obj, path );
689
+ parent.add( obj );
612
690
 
613
- }
691
+ // Still build local children (overrides)
692
+ this._buildHierarchy( obj, path );
614
693
 
615
694
  continue;
616
695
 
@@ -653,12 +732,41 @@ class USDComposer {
653
732
  if ( obj ) {
654
733
 
655
734
  parent.add( obj );
735
+ this._buildHierarchy( obj, path );
736
+
737
+ }
738
+
739
+ } else if ( typeName === 'Camera' ) {
740
+
741
+ const obj = this._buildCamera( path );
742
+ obj.name = name;
743
+ const attrs = this._getAttributes( path );
744
+ this.applyTransform( obj, spec.fields, attrs );
745
+ parent.add( obj );
746
+ this._buildHierarchy( obj, path );
747
+
748
+ } else if ( typeName === 'DistantLight' || typeName === 'SphereLight' || typeName === 'RectLight' || typeName === 'DiskLight' ) {
749
+
750
+ const obj = this._buildLight( path, typeName );
751
+ obj.name = name;
752
+ const attrs = this._getAttributes( path );
753
+ this.applyTransform( obj, spec.fields, attrs );
754
+ parent.add( obj );
755
+ this._buildHierarchy( obj, path );
756
+
757
+ } else if ( typeName === 'Cube' || typeName === 'Sphere' || typeName === 'Cylinder' || typeName === 'Cone' || typeName === 'Capsule' ) {
758
+
759
+ const obj = this._buildGeomPrimitive( path, spec, typeName );
760
+ if ( obj ) {
761
+
762
+ parent.add( obj );
763
+ this._buildHierarchy( obj, path );
656
764
 
657
765
  }
658
766
 
659
- } else if ( typeName === 'Material' || typeName === 'Shader' ) {
767
+ } else if ( typeName === 'Material' || typeName === 'Shader' || typeName === 'GeomSubset' ) {
660
768
 
661
- // Skip materials/shaders, they're referenced by meshes
769
+ // Skip materials/shaders/subsets, they're referenced by meshes
662
770
 
663
771
  } else {
664
772
 
@@ -930,27 +1038,44 @@ class USDComposer {
930
1038
  }
931
1039
 
932
1040
  /**
933
- * Get reference value from a prim spec.
1041
+ * Get all reference values from a prim spec.
1042
+ * @returns {string[]} Array of reference strings like "@path@" or "@path@<prim>"
934
1043
  */
935
- _getReference( spec ) {
1044
+ _getReferences( spec ) {
1045
+
1046
+ const results = [];
936
1047
 
937
1048
  if ( spec.fields.references && spec.fields.references.length > 0 ) {
938
1049
 
939
1050
  const ref = spec.fields.references[ 0 ];
940
- if ( typeof ref === 'string' ) return ref;
941
- if ( ref.assetPath ) return '@' + ref.assetPath + '@';
1051
+
1052
+ if ( typeof ref === 'string' ) {
1053
+
1054
+ // Extract all @...@ references (handles both single and array values)
1055
+ const matches = ref.matchAll( /@([^@]+)@(?:<([^>]+)>)?/g );
1056
+ for ( const match of matches ) {
1057
+
1058
+ results.push( match[ 0 ] );
1059
+
1060
+ }
1061
+
1062
+ } else if ( ref.assetPath ) {
1063
+
1064
+ results.push( '@' + ref.assetPath + '@' );
1065
+
1066
+ }
942
1067
 
943
1068
  }
944
1069
 
945
- if ( spec.fields.payload ) {
1070
+ if ( results.length === 0 && spec.fields.payload ) {
946
1071
 
947
1072
  const payload = spec.fields.payload;
948
- if ( typeof payload === 'string' ) return payload;
949
- if ( payload.assetPath ) return '@' + payload.assetPath + '@';
1073
+ if ( typeof payload === 'string' ) results.push( payload );
1074
+ else if ( payload.assetPath ) results.push( '@' + payload.assetPath + '@' );
950
1075
 
951
1076
  }
952
1077
 
953
- return null;
1078
+ return results;
954
1079
 
955
1080
  }
956
1081
 
@@ -1048,6 +1173,86 @@ class USDComposer {
1048
1173
 
1049
1174
  }
1050
1175
 
1176
+ /**
1177
+ * Build a mesh from a USD geometric primitive (Cube, Sphere, Cylinder, Cone, Capsule).
1178
+ */
1179
+ _buildGeomPrimitive( path, spec, typeName ) {
1180
+
1181
+ const attrs = this._getAttributes( path );
1182
+ const name = path.split( '/' ).pop();
1183
+
1184
+ let geometry;
1185
+
1186
+ switch ( typeName ) {
1187
+
1188
+ case 'Cube': {
1189
+
1190
+ const size = attrs[ 'size' ] || 2;
1191
+ geometry = new BoxGeometry( size, size, size );
1192
+ break;
1193
+
1194
+ }
1195
+
1196
+ case 'Sphere': {
1197
+
1198
+ const radius = attrs[ 'radius' ] || 1;
1199
+ geometry = new SphereGeometry( radius, 32, 16 );
1200
+ break;
1201
+
1202
+ }
1203
+
1204
+ case 'Cylinder': {
1205
+
1206
+ const height = attrs[ 'height' ] || 2;
1207
+ const radius = attrs[ 'radius' ] || 1;
1208
+ geometry = new CylinderGeometry( radius, radius, height, 32 );
1209
+ break;
1210
+
1211
+ }
1212
+
1213
+ case 'Cone': {
1214
+
1215
+ const height = attrs[ 'height' ] || 2;
1216
+ const radius = attrs[ 'radius' ] || 1;
1217
+ geometry = new ConeGeometry( radius, height, 32 );
1218
+ break;
1219
+
1220
+ }
1221
+
1222
+ case 'Capsule': {
1223
+
1224
+ const height = attrs[ 'height' ] || 1;
1225
+ const radius = attrs[ 'radius' ] || 0.5;
1226
+ geometry = new CapsuleGeometry( radius, height, 16, 32 );
1227
+ break;
1228
+
1229
+ }
1230
+
1231
+ }
1232
+
1233
+ // USD defaults axis to "Z", Three.js uses Y
1234
+ const axis = attrs[ 'axis' ] || 'Z';
1235
+
1236
+ if ( axis === 'X' ) {
1237
+
1238
+ geometry.rotateZ( - Math.PI / 2 );
1239
+
1240
+ } else if ( axis === 'Z' ) {
1241
+
1242
+ geometry.rotateX( Math.PI / 2 );
1243
+
1244
+ }
1245
+
1246
+ const material = this._buildMaterial( path, spec.fields );
1247
+ const mesh = new Mesh( geometry, material );
1248
+ mesh.name = name;
1249
+
1250
+ this.applyTransform( mesh, spec.fields, attrs );
1251
+
1252
+ return mesh;
1253
+
1254
+ }
1255
+
1051
1256
  /**
1052
1257
  * Build a mesh from a Mesh spec.
1053
1258
  */
@@ -1112,13 +1317,13 @@ class USDComposer {
1112
1317
  }
1113
1318
 
1114
1319
  const displayOpacity = attrs[ 'primvars:displayOpacity' ];
1115
- if ( displayOpacity && displayOpacity.length >= 1 ) {
1320
+ if ( displayOpacity && displayOpacity.length === 1 && geomSubsets.length === 0 ) {
1116
1321
 
1117
1322
  const opacity = displayOpacity[ 0 ];
1118
1323
 
1119
1324
  const applyDisplayOpacity = ( mat ) => {
1120
1325
 
1121
- if ( opacity < 1 ) {
1326
+ if ( opacity < 1 && mat.opacity === 1 && mat.transparent === false ) {
1122
1327
 
1123
1328
  mat.opacity = opacity;
1124
1329
  mat.transparent = true;
@@ -1190,6 +1395,215 @@ class USDComposer {
1190
1395
 
1191
1396
  }
1192
1397
 
1398
+ /**
1399
+ * Build a camera from a Camera spec.
1400
+ */
1401
+ _buildCamera( path ) {
1402
+
1403
+ const attrs = this._getAttributes( path );
1404
+ const projectionToken = attrs[ 'projection' ];
1405
+ const projection = typeof projectionToken === 'string'
1406
+ ? projectionToken.toLowerCase()
1407
+ : USD_CAMERA_DEFAULTS.projection;
1408
+ const clippingRange = attrs[ 'clippingRange' ] || USD_CAMERA_DEFAULTS.clippingRange;
1409
+ const near = Math.max(
1410
+ Number.EPSILON,
1411
+ this._parseNumber( clippingRange[ 0 ], USD_CAMERA_DEFAULTS.clippingRange[ 0 ] )
1412
+ );
1413
+ const far = Math.max(
1414
+ near + Number.EPSILON,
1415
+ this._parseNumber( clippingRange[ 1 ], USD_CAMERA_DEFAULTS.clippingRange[ 1 ] )
1416
+ );
1417
+ const horizontalAperture = this._parseNumber(
1418
+ attrs[ 'horizontalAperture' ],
1419
+ USD_CAMERA_DEFAULTS.horizontalAperture
1420
+ );
1421
+ const verticalAperture = this._parseNumber(
1422
+ attrs[ 'verticalAperture' ],
1423
+ USD_CAMERA_DEFAULTS.verticalAperture
1424
+ );
1425
+ const horizontalApertureOffset = this._parseNumber(
1426
+ attrs[ 'horizontalApertureOffset' ],
1427
+ USD_CAMERA_DEFAULTS.horizontalApertureOffset
1428
+ );
1429
+ const verticalApertureOffset = this._parseNumber(
1430
+ attrs[ 'verticalApertureOffset' ],
1431
+ USD_CAMERA_DEFAULTS.verticalApertureOffset
1432
+ );
1433
+ const focalLength = this._parseNumber( attrs[ 'focalLength' ], USD_CAMERA_DEFAULTS.focalLength );
1434
+ const focusDistance = this._parseNumber( attrs[ 'focusDistance' ], USD_CAMERA_DEFAULTS.focusDistance );
1435
+ const fStop = this._parseNumber( attrs[ 'fStop' ], USD_CAMERA_DEFAULTS.fStop );
1436
+
1437
+ let camera;
1438
+
1439
+ if ( projection === 'orthographic' ) {
1440
+
1441
+ // USD orthographic apertures are in tenths of a world unit.
1442
+ const width = horizontalAperture / 10;
1443
+ const height = verticalAperture / 10;
1444
+ const offsetX = horizontalApertureOffset / 10;
1445
+ const offsetY = verticalApertureOffset / 10;
1446
+
1447
+ camera = new OrthographicCamera(
1448
+ offsetX - width * 0.5,
1449
+ offsetX + width * 0.5,
1450
+ offsetY + height * 0.5,
1451
+ offsetY - height * 0.5,
1452
+ near,
1453
+ far
1454
+ );
1455
+
1456
+ } else {
1457
+
1458
+ const safeVerticalAperture = Math.max( Number.EPSILON, verticalAperture );
1459
+ const safeFocalLength = Math.max( Number.EPSILON, focalLength );
1460
+ const aspect = horizontalAperture / safeVerticalAperture;
1461
+ const fov = 2 * Math.atan( safeVerticalAperture / ( 2 * safeFocalLength ) ) * 180 / Math.PI;
1462
+
1463
+ camera = new PerspectiveCamera( fov, aspect, near, far );
1464
+ camera.filmGauge = Math.max( horizontalAperture, verticalAperture );
1465
+ camera.filmOffset = horizontalApertureOffset;
1466
+ camera.focus = focusDistance;
1467
+ camera.setFocalLength( safeFocalLength );
1468
+
1469
+ if ( verticalApertureOffset !== 0 ) {
1470
+
1471
+ // Three.js supports only horizontal film offset directly.
1472
+ camera.userData.verticalApertureOffset = verticalApertureOffset;
1473
+
1474
+ }
1475
+
1476
+ }
1477
+
1478
+ camera.userData.fStop = fStop;
1479
+ camera.userData.usdProjection = projection;
1480
+ return camera;
1481
+
1482
+ }
1483
+
1484
+ /**
1485
+ * Build a light from a UsdLux light spec.
1486
+ */
1487
+ _buildLight( path, typeName ) {
1488
+
1489
+ const attrs = this._getAttributes( path );
1490
+
1491
+ const intensity = this._parseNumber( attrs[ 'inputs:intensity' ], 1 );
1492
+ const baseColor = attrs[ 'inputs:color' ] || [ 1, 1, 1 ];
1493
+ const enableColorTemperature = attrs[ 'inputs:enableColorTemperature' ] === true;
1494
+ const colorTemperature = this._parseNumber( attrs[ 'inputs:colorTemperature' ], 6500 );
1495
+
1496
+ const color = new Color( baseColor[ 0 ], baseColor[ 1 ], baseColor[ 2 ] );
1497
+
1498
+ if ( enableColorTemperature ) {
1499
+
1500
+ const temp = this._colorTemperature( colorTemperature );
1501
+ color.multiply( temp );
1502
+
1503
+ }
1504
+
1505
+ let light;
1506
+
1507
+ switch ( typeName ) {
1508
+
1509
+ case 'DistantLight':
1510
+ light = new DirectionalLight( color, intensity );
1511
+ break;
1512
+
1513
+ case 'SphereLight': {
1514
+
1515
+ const coneAngle = this._parseNumber( attrs[ 'shaping:cone:angle' ], 0 );
1516
+
1517
+ if ( coneAngle > 0 ) {
1518
+
1519
+ const angle = coneAngle * Math.PI / 180;
1520
+ const softness = this._parseNumber( attrs[ 'shaping:cone:softness' ], 0 );
1521
+ light = new SpotLight( color, intensity, 0, angle, softness );
1522
+
1523
+ } else {
1524
+
1525
+ light = new PointLight( color, intensity );
1526
+
1527
+ }
1528
+
1529
+ break;
1530
+
1531
+ }
1532
+
1533
+ case 'RectLight': {
1534
+
1535
+ const width = this._parseNumber( attrs[ 'inputs:width' ], 1 );
1536
+ const height = this._parseNumber( attrs[ 'inputs:height' ], 1 );
1537
+ light = new RectAreaLight( color, intensity, width, height );
1538
+ break;
1539
+
1540
+ }
1541
+
1542
+ case 'DiskLight': {
1543
+
1544
+ const radius = this._parseNumber( attrs[ 'inputs:radius' ], 0.5 );
1545
+ const side = radius * 2;
1546
+ light = new RectAreaLight( color, intensity, side, side );
1547
+ break;
1548
+
1549
+ }
1550
+
1551
+ }
1552
+
1553
+ return light;
1554
+
1555
+ }
1556
+
1557
+ /**
1558
+ * Convert a color temperature in Kelvin to an RGB Color.
1559
+ * Based on Tanner Helland's algorithm.
1560
+ */
1561
+ _colorTemperature( kelvin ) {
1562
+
1563
+ const temp = kelvin / 100;
1564
+ let r, g, b;
1565
+
1566
+ if ( temp <= 66 ) {
1567
+
1568
+ r = 1;
1569
+ g = 0.39008157876901960784 * Math.log( temp ) - 0.63184144378862745098;
1570
+
1571
+ } else {
1572
+
1573
+ r = 1.29293618606274509804 * Math.pow( temp - 60, - 0.1332047592 );
1574
+ g = 1.12989086089529411765 * Math.pow( temp - 60, - 0.0755148492 );
1575
+
1576
+ }
1577
+
1578
+ if ( temp >= 66 ) {
1579
+
1580
+ b = 1;
1581
+
1582
+ } else if ( temp <= 19 ) {
1583
+
1584
+ b = 0;
1585
+
1586
+ } else {
1587
+
1588
+ b = 0.54320678911019607843 * Math.log( temp - 10 ) - 1.19625408914;
1589
+
1590
+ }
1591
+
1592
+ return new Color(
1593
+ Math.min( Math.max( r, 0 ), 1 ),
1594
+ Math.min( Math.max( g, 0 ), 1 ),
1595
+ Math.min( Math.max( b, 0 ), 1 )
1596
+ );
1597
+
1598
+ }
1599
+
1600
+ _parseNumber( value, fallback ) {
1601
+
1602
+ const n = Number( value );
1603
+ return Number.isFinite( n ) ? n : fallback;
1604
+
1605
+ }
1606
+
1193
1607
  _getGeomSubsets( meshPath ) {
1194
1608
 
1195
1609
  const subsets = [];
@@ -1203,7 +1617,7 @@ class USDComposer {
1203
1617
  if ( ! indices || indices.length === 0 ) continue;
1204
1618
 
1205
1619
  // Get material binding - check direct path and variant paths
1206
- let materialPath = this._getMaterialBindingTarget( p );
1620
+ const materialPath = this._getMaterialBindingTarget( p );
1207
1621
 
1208
1622
  subsets.push( {
1209
1623
  name: p.split( '/' ).pop(),
@@ -1333,7 +1747,12 @@ class USDComposer {
1333
1747
 
1334
1748
  } else {
1335
1749
 
1336
- geometry.computeVertexNormals();
1750
+ // Compute vertex normals from the original indexed topology where
1751
+ // vertices are shared, then expand them like positions.
1752
+ const vertexNormals = this._computeVertexNormals( points, indices );
1753
+ geometry.setAttribute( 'normal', new BufferAttribute( new Float32Array(
1754
+ this._expandAttribute( vertexNormals, indices, 3 )
1755
+ ), 3 ) );
1337
1756
 
1338
1757
  }
1339
1758
 
@@ -1427,25 +1846,7 @@ class USDComposer {
1427
1846
  const skinIndices = new Uint16Array( numVertices * 4 );
1428
1847
  const skinWeights = new Float32Array( numVertices * 4 );
1429
1848
 
1430
- for ( let i = 0; i < numVertices; i ++ ) {
1431
-
1432
- for ( let j = 0; j < 4; j ++ ) {
1433
-
1434
- if ( j < elementSize ) {
1435
-
1436
- skinIndices[ i * 4 + j ] = skinIndexData[ i * elementSize + j ] || 0;
1437
- skinWeights[ i * 4 + j ] = skinWeightData[ i * elementSize + j ] || 0;
1438
-
1439
- } else {
1440
-
1441
- skinIndices[ i * 4 + j ] = 0;
1442
- skinWeights[ i * 4 + j ] = 0;
1443
-
1444
- }
1445
-
1446
- }
1447
-
1448
- }
1849
+ this._selectTopWeights( skinIndexData, skinWeightData, elementSize, numVertices, skinIndices, skinWeights );
1449
1850
 
1450
1851
  geometry.setAttribute( 'skinIndex', new BufferAttribute( skinIndices, 4 ) );
1451
1852
  geometry.setAttribute( 'skinWeight', new BufferAttribute( skinWeights, 4 ) );
@@ -1597,10 +1998,17 @@ class USDComposer {
1597
1998
 
1598
1999
  // Triangulate original data using consistent pattern
1599
2000
  const { indices: origIndices, pattern: triPattern } = this._triangulateIndicesWithPattern( faceVertexIndices, faceVertexCounts, points, holeMap );
1600
- const origUvIndices = uvIndices ? this._applyTriangulationPattern( uvIndices, triPattern ) : null;
1601
- const origUv2Indices = uv2Indices ? this._applyTriangulationPattern( uv2Indices, triPattern ) : null;
1602
-
1603
2001
  const numFaceVertices = faceVertexCounts.reduce( ( a, b ) => a + b, 0 );
2002
+ const faceVaryingIdentity = ( uvs && ! uvIndices && uvs.length / 2 === numFaceVertices ) ||
2003
+ ( uvs2 && ! uv2Indices && uvs2.length / 2 === numFaceVertices )
2004
+ ? this._applyTriangulationPattern( Array.from( { length: numFaceVertices }, ( _, i ) => i ), triPattern )
2005
+ : null;
2006
+ const origUvIndices = uvIndices
2007
+ ? this._applyTriangulationPattern( uvIndices, triPattern )
2008
+ : ( uvs && uvs.length / 2 === numFaceVertices ? faceVaryingIdentity : null );
2009
+ const origUv2Indices = uv2Indices
2010
+ ? this._applyTriangulationPattern( uv2Indices, triPattern )
2011
+ : ( uvs2 && uvs2.length / 2 === numFaceVertices ? faceVaryingIdentity : null );
1604
2012
  const hasIndexedNormals = normals && normalIndicesRaw && normalIndicesRaw.length > 0;
1605
2013
  const hasFaceVaryingNormals = normals && normals.length / 3 === numFaceVertices;
1606
2014
  const origNormalIndices = hasIndexedNormals
@@ -1609,14 +2017,20 @@ class USDComposer {
1609
2017
  ? this._applyTriangulationPattern( Array.from( { length: numFaceVertices }, ( _, i ) => i ), triPattern )
1610
2018
  : null );
1611
2019
 
2020
+ // When no normals are provided, compute vertex normals from
2021
+ // the indexed topology so that shared vertices produce averaged normals.
2022
+ const vertexNormals = ( ! normals && origIndices.length > 0 )
2023
+ ? this._computeVertexNormals( points, origIndices )
2024
+ : null;
2025
+
1612
2026
  // Build reordered vertex data
1613
2027
  const vertexCount = triangleCount * 3;
1614
2028
  const positions = new Float32Array( vertexCount * 3 );
1615
2029
  const uvData = uvs ? new Float32Array( vertexCount * 2 ) : null;
1616
2030
  const uv1Data = uvs2 ? new Float32Array( vertexCount * 2 ) : null;
1617
- const normalData = normals ? new Float32Array( vertexCount * 3 ) : null;
1618
- const skinIndexData = jointIndices ? new Uint16Array( vertexCount * 4 ) : null;
1619
- const skinWeightData = jointWeights ? new Float32Array( vertexCount * 4 ) : null;
2031
+ const normalData = ( normals || vertexNormals ) ? new Float32Array( vertexCount * 3 ) : null;
2032
+ const skinSrcIndices = jointIndices ? new Uint16Array( vertexCount * elementSize ) : null;
2033
+ const skinSrcWeights = jointWeights ? new Float32Array( vertexCount * elementSize ) : null;
1620
2034
 
1621
2035
  for ( let i = 0; i < sortedTriangles.length; i ++ ) {
1622
2036
 
@@ -1666,40 +2080,37 @@ class USDComposer {
1666
2080
 
1667
2081
  }
1668
2082
 
1669
- if ( normalData && normals ) {
2083
+ if ( normalData ) {
1670
2084
 
1671
- if ( origNormalIndices ) {
2085
+ if ( normals && origNormalIndices ) {
1672
2086
 
1673
2087
  const normalIdx = origNormalIndices[ origIdx ];
1674
2088
  normalData[ newIdx * 3 ] = normals[ normalIdx * 3 ];
1675
2089
  normalData[ newIdx * 3 + 1 ] = normals[ normalIdx * 3 + 1 ];
1676
2090
  normalData[ newIdx * 3 + 2 ] = normals[ normalIdx * 3 + 2 ];
1677
2091
 
1678
- } else if ( normals.length === points.length ) {
2092
+ } else if ( normals && normals.length === points.length ) {
1679
2093
 
1680
2094
  normalData[ newIdx * 3 ] = normals[ pointIdx * 3 ];
1681
2095
  normalData[ newIdx * 3 + 1 ] = normals[ pointIdx * 3 + 1 ];
1682
2096
  normalData[ newIdx * 3 + 2 ] = normals[ pointIdx * 3 + 2 ];
1683
2097
 
1684
- }
1685
-
1686
- }
1687
-
1688
- if ( skinIndexData && skinWeightData && jointIndices && jointWeights ) {
2098
+ } else if ( vertexNormals ) {
1689
2099
 
1690
- for ( let j = 0; j < 4; j ++ ) {
2100
+ normalData[ newIdx * 3 ] = vertexNormals[ pointIdx * 3 ];
2101
+ normalData[ newIdx * 3 + 1 ] = vertexNormals[ pointIdx * 3 + 1 ];
2102
+ normalData[ newIdx * 3 + 2 ] = vertexNormals[ pointIdx * 3 + 2 ];
1691
2103
 
1692
- if ( j < elementSize ) {
2104
+ }
1693
2105
 
1694
- skinIndexData[ newIdx * 4 + j ] = jointIndices[ pointIdx * elementSize + j ] || 0;
1695
- skinWeightData[ newIdx * 4 + j ] = jointWeights[ pointIdx * elementSize + j ] || 0;
2106
+ }
1696
2107
 
1697
- } else {
2108
+ if ( skinSrcIndices && skinSrcWeights && jointIndices && jointWeights ) {
1698
2109
 
1699
- skinIndexData[ newIdx * 4 + j ] = 0;
1700
- skinWeightData[ newIdx * 4 + j ] = 0;
2110
+ for ( let j = 0; j < elementSize; j ++ ) {
1701
2111
 
1702
- }
2112
+ skinSrcIndices[ newIdx * elementSize + j ] = jointIndices[ pointIdx * elementSize + j ] || 0;
2113
+ skinSrcWeights[ newIdx * elementSize + j ] = jointWeights[ pointIdx * elementSize + j ] || 0;
1703
2114
 
1704
2115
  }
1705
2116
 
@@ -1723,29 +2134,117 @@ class USDComposer {
1723
2134
 
1724
2135
  }
1725
2136
 
1726
- if ( normalData ) {
2137
+ geometry.setAttribute( 'normal', new BufferAttribute( normalData, 3 ) );
1727
2138
 
1728
- geometry.setAttribute( 'normal', new BufferAttribute( normalData, 3 ) );
2139
+ if ( skinSrcIndices && skinSrcWeights ) {
1729
2140
 
1730
- } else {
2141
+ const skinIndexData = new Uint16Array( vertexCount * 4 );
2142
+ const skinWeightData = new Float32Array( vertexCount * 4 );
1731
2143
 
1732
- geometry.computeVertexNormals();
2144
+ this._selectTopWeights( skinSrcIndices, skinSrcWeights, elementSize, vertexCount, skinIndexData, skinWeightData );
2145
+
2146
+ geometry.setAttribute( 'skinIndex', new BufferAttribute( skinIndexData, 4 ) );
2147
+ geometry.setAttribute( 'skinWeight', new BufferAttribute( skinWeightData, 4 ) );
1733
2148
 
1734
2149
  }
1735
2150
 
1736
- if ( skinIndexData ) {
2151
+ return geometry;
1737
2152
 
1738
- geometry.setAttribute( 'skinIndex', new BufferAttribute( skinIndexData, 4 ) );
2153
+ }
1739
2154
 
1740
- }
2155
+ _selectTopWeights( srcIndices, srcWeights, elementSize, numVertices, dstIndices, dstWeights ) {
1741
2156
 
1742
- if ( skinWeightData ) {
2157
+ if ( elementSize <= 4 ) {
1743
2158
 
1744
- geometry.setAttribute( 'skinWeight', new BufferAttribute( skinWeightData, 4 ) );
2159
+ for ( let i = 0; i < numVertices; i ++ ) {
2160
+
2161
+ for ( let j = 0; j < 4; j ++ ) {
2162
+
2163
+ if ( j < elementSize ) {
2164
+
2165
+ dstIndices[ i * 4 + j ] = srcIndices[ i * elementSize + j ] || 0;
2166
+ dstWeights[ i * 4 + j ] = srcWeights[ i * elementSize + j ] || 0;
2167
+
2168
+ } else {
2169
+
2170
+ dstIndices[ i * 4 + j ] = 0;
2171
+ dstWeights[ i * 4 + j ] = 0;
2172
+
2173
+ }
2174
+
2175
+ }
2176
+
2177
+ }
2178
+
2179
+ return;
1745
2180
 
1746
2181
  }
1747
2182
 
1748
- return geometry;
2183
+ // When elementSize > 4, find the 4 largest weights per vertex
2184
+ // using a partial selection sort (4 iterations of O(elementSize)).
2185
+ const order = new Uint32Array( elementSize );
2186
+
2187
+ for ( let i = 0; i < numVertices; i ++ ) {
2188
+
2189
+ const base = i * elementSize;
2190
+
2191
+ for ( let j = 0; j < elementSize; j ++ ) order[ j ] = j;
2192
+
2193
+ for ( let k = 0; k < 4; k ++ ) {
2194
+
2195
+ let maxIdx = k;
2196
+ let maxW = srcWeights[ base + order[ k ] ] || 0;
2197
+
2198
+ for ( let j = k + 1; j < elementSize; j ++ ) {
2199
+
2200
+ const w = srcWeights[ base + order[ j ] ] || 0;
2201
+
2202
+ if ( w > maxW ) {
2203
+
2204
+ maxW = w;
2205
+ maxIdx = j;
2206
+
2207
+ }
2208
+
2209
+ }
2210
+
2211
+ if ( maxIdx !== k ) {
2212
+
2213
+ const tmp = order[ k ];
2214
+ order[ k ] = order[ maxIdx ];
2215
+ order[ maxIdx ] = tmp;
2216
+
2217
+ }
2218
+
2219
+ }
2220
+
2221
+ let total = 0;
2222
+
2223
+ for ( let j = 0; j < 4; j ++ ) {
2224
+
2225
+ total += srcWeights[ base + order[ j ] ] || 0;
2226
+
2227
+ }
2228
+
2229
+ for ( let j = 0; j < 4; j ++ ) {
2230
+
2231
+ const s = order[ j ];
2232
+
2233
+ if ( total > 0 ) {
2234
+
2235
+ dstIndices[ i * 4 + j ] = srcIndices[ base + s ] || 0;
2236
+ dstWeights[ i * 4 + j ] = ( srcWeights[ base + s ] || 0 ) / total;
2237
+
2238
+ } else {
2239
+
2240
+ dstIndices[ i * 4 + j ] = 0;
2241
+ dstWeights[ i * 4 + j ] = 0;
2242
+
2243
+ }
2244
+
2245
+ }
2246
+
2247
+ }
1749
2248
 
1750
2249
  }
1751
2250
 
@@ -2234,13 +2733,64 @@ class USDComposer {
2234
2733
 
2235
2734
  }
2236
2735
 
2736
+ /**
2737
+ * Compute per-vertex normals from indexed triangle data.
2738
+ * Accumulates area-weighted face normals at each shared vertex and normalizes.
2739
+ */
2740
+ _computeVertexNormals( points, indices ) {
2741
+
2742
+ const numVertices = points.length / 3;
2743
+ const normals = new Float32Array( numVertices * 3 );
2744
+
2745
+ for ( let i = 0; i < indices.length; i += 3 ) {
2746
+
2747
+ const a = indices[ i ];
2748
+ const b = indices[ i + 1 ];
2749
+ const c = indices[ i + 2 ];
2750
+
2751
+ const ax = points[ a * 3 ], ay = points[ a * 3 + 1 ], az = points[ a * 3 + 2 ];
2752
+ const bx = points[ b * 3 ], by = points[ b * 3 + 1 ], bz = points[ b * 3 + 2 ];
2753
+ const cx = points[ c * 3 ], cy = points[ c * 3 + 1 ], cz = points[ c * 3 + 2 ];
2754
+
2755
+ const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
2756
+ const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
2757
+
2758
+ const nx = e1y * e2z - e1z * e2y;
2759
+ const ny = e1z * e2x - e1x * e2z;
2760
+ const nz = e1x * e2y - e1y * e2x;
2761
+
2762
+ normals[ a * 3 ] += nx; normals[ a * 3 + 1 ] += ny; normals[ a * 3 + 2 ] += nz;
2763
+ normals[ b * 3 ] += nx; normals[ b * 3 + 1 ] += ny; normals[ b * 3 + 2 ] += nz;
2764
+ normals[ c * 3 ] += nx; normals[ c * 3 + 1 ] += ny; normals[ c * 3 + 2 ] += nz;
2765
+
2766
+ }
2767
+
2768
+ for ( let i = 0; i < numVertices; i ++ ) {
2769
+
2770
+ const x = normals[ i * 3 ], y = normals[ i * 3 + 1 ], z = normals[ i * 3 + 2 ];
2771
+ const len = Math.sqrt( x * x + y * y + z * z );
2772
+
2773
+ if ( len > 0 ) {
2774
+
2775
+ normals[ i * 3 ] /= len;
2776
+ normals[ i * 3 + 1 ] /= len;
2777
+ normals[ i * 3 + 2 ] /= len;
2778
+
2779
+ }
2780
+
2781
+ }
2782
+
2783
+ return normals;
2784
+
2785
+ }
2786
+
2237
2787
  /**
2238
2788
  * Get the material path for a mesh, checking various binding sources.
2239
2789
  */
2240
2790
  _getMaterialPath( meshPath, fields ) {
2241
2791
 
2242
2792
  let materialPath = null;
2243
- let materialBinding = fields[ 'material:binding' ];
2793
+ const materialBinding = fields[ 'material:binding' ];
2244
2794
 
2245
2795
  if ( materialBinding ) {
2246
2796
 
@@ -2264,7 +2814,7 @@ class USDComposer {
2264
2814
  const material = new MeshPhysicalMaterial();
2265
2815
 
2266
2816
  let materialPath = null;
2267
- let materialBinding = fields[ 'material:binding' ];
2817
+ const materialBinding = fields[ 'material:binding' ];
2268
2818
 
2269
2819
  if ( materialBinding ) {
2270
2820
 
@@ -2432,7 +2982,7 @@ class USDComposer {
2432
2982
  const shaderAttrs = this._getAttributes( path );
2433
2983
  const infoId = shaderAttrs[ 'info:id' ] || spec.fields[ 'info:id' ];
2434
2984
 
2435
- if ( infoId === 'UsdPreviewSurface' ) {
2985
+ if ( infoId === 'UsdPreviewSurface' || infoId === 'ND_UsdPreviewSurface_surfaceshader' ) {
2436
2986
 
2437
2987
  this._applyPreviewSurface( material, path );
2438
2988
 
@@ -3335,6 +3885,7 @@ class USDComposer {
3335
3885
  }
3336
3886
 
3337
3887
  };
3888
+
3338
3889
  image.src = url;
3339
3890
 
3340
3891
  return texture;
@@ -3429,13 +3980,15 @@ class USDComposer {
3429
3980
 
3430
3981
  }
3431
3982
 
3432
- // Apply rest transforms to bones (local transforms)
3983
+ // Apply rest transforms as bone local transforms.
3984
+ // Rest transforms are the skeleton's default local-space pose and match
3985
+ // the reference frame used by SkelAnimation data. Bind transforms are
3986
+ // world-space matrices used only for computing inverse bind matrices.
3433
3987
  if ( restTransforms && restTransforms.length >= joints.length * 16 ) {
3434
3988
 
3435
3989
  for ( let i = 0; i < joints.length; i ++ ) {
3436
3990
 
3437
3991
  const matrix = new Matrix4();
3438
- // USD matrices are row-major, Three.js is column-major - need to transpose
3439
3992
  const m = restTransforms.slice( i * 16, ( i + 1 ) * 16 );
3440
3993
  matrix.set(
3441
3994
  m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
@@ -3562,7 +4115,7 @@ class USDComposer {
3562
4115
  // Use geomBindTransform if available, otherwise fall back to identity.
3563
4116
  // Estimating bind transforms from vertex/joint samples is not robust and can
3564
4117
  // produce severe skinning distortion for valid assets.
3565
- let bindMatrix = new Matrix4();
4118
+ const bindMatrix = new Matrix4();
3566
4119
 
3567
4120
  if ( geomBindTransform && geomBindTransform.length === 16 ) {
3568
4121