super-three 0.181.0 → 0.185.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 (693) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -4
  3. package/build/three.cjs +21332 -19201
  4. package/build/three.core.js +20908 -19853
  5. package/build/three.core.min.js +2 -2
  6. package/build/three.module.js +1947 -862
  7. package/build/three.module.min.js +2 -2
  8. package/build/three.tsl.js +37 -16
  9. package/build/three.tsl.min.js +2 -2
  10. package/build/three.webgpu.js +33628 -25197
  11. package/build/three.webgpu.min.js +2 -2
  12. package/build/three.webgpu.nodes.js +33394 -25000
  13. package/build/three.webgpu.nodes.min.js +2 -2
  14. package/examples/jsm/Addons.js +14 -3
  15. package/examples/jsm/animation/CCDIKSolver.js +7 -3
  16. package/examples/jsm/controls/ArcballControls.js +13 -6
  17. package/examples/jsm/controls/DragControls.js +2 -2
  18. package/examples/jsm/controls/FirstPersonControls.js +127 -86
  19. package/examples/jsm/controls/FlyControls.js +4 -0
  20. package/examples/jsm/controls/MapControls.js +55 -1
  21. package/examples/jsm/controls/OrbitControls.js +111 -8
  22. package/examples/jsm/controls/TrackballControls.js +8 -8
  23. package/examples/jsm/controls/TransformControls.js +102 -17
  24. package/examples/jsm/csm/CSM.js +2 -1
  25. package/examples/jsm/csm/CSMFrustum.js +31 -6
  26. package/examples/jsm/csm/CSMShadowNode.js +10 -3
  27. package/examples/jsm/effects/AnaglyphEffect.js +102 -7
  28. package/examples/jsm/effects/AsciiEffect.js +14 -1
  29. package/examples/jsm/environments/ColorEnvironment.js +59 -0
  30. package/examples/jsm/environments/RoomEnvironment.js +3 -0
  31. package/examples/jsm/exporters/DRACOExporter.js +27 -6
  32. package/examples/jsm/exporters/EXRExporter.js +1 -1
  33. package/examples/jsm/exporters/GLTFExporter.js +266 -21
  34. package/examples/jsm/exporters/PLYExporter.js +286 -73
  35. package/examples/jsm/exporters/USDZExporter.js +353 -99
  36. package/examples/jsm/generators/CityGenerator.js +346 -0
  37. package/examples/jsm/generators/ForestGenerator.js +347 -0
  38. package/examples/jsm/generators/TerrainGenerator.js +504 -0
  39. package/examples/jsm/generators/TreeGenerator.js +377 -0
  40. package/examples/jsm/generators/city/SidewalkGenerator.js +253 -0
  41. package/examples/jsm/generators/city/SkyscraperGenerator.js +1357 -0
  42. package/examples/jsm/geometries/DecalGeometry.js +1 -1
  43. package/examples/jsm/geometries/LoftGeometry.js +355 -0
  44. package/examples/jsm/geometries/TextGeometry.js +18 -0
  45. package/examples/jsm/helpers/AnimationPathHelper.js +302 -0
  46. package/examples/jsm/helpers/LightProbeGridHelper.js +221 -0
  47. package/examples/jsm/helpers/LightProbeHelper.js +5 -4
  48. package/examples/jsm/helpers/LightProbeHelperGPU.js +1 -1
  49. package/examples/jsm/helpers/TextureHelperGPU.js +1 -1
  50. package/examples/jsm/helpers/ViewHelper.js +83 -14
  51. package/examples/jsm/inspector/Extension.js +13 -0
  52. package/examples/jsm/inspector/Inspector.js +244 -91
  53. package/examples/jsm/inspector/RendererInspector.js +165 -39
  54. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +916 -0
  55. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +281 -0
  56. package/examples/jsm/inspector/tabs/Console.js +241 -25
  57. package/examples/jsm/inspector/tabs/Memory.js +136 -0
  58. package/examples/jsm/inspector/tabs/Parameters.js +115 -7
  59. package/examples/jsm/inspector/tabs/Performance.js +24 -9
  60. package/examples/jsm/inspector/tabs/Settings.js +306 -0
  61. package/examples/jsm/inspector/tabs/Timeline.js +1696 -0
  62. package/examples/jsm/inspector/tabs/Viewer.js +723 -9
  63. package/examples/jsm/inspector/ui/Graph.js +162 -25
  64. package/examples/jsm/inspector/ui/Item.js +92 -13
  65. package/examples/jsm/inspector/ui/List.js +2 -2
  66. package/examples/jsm/inspector/ui/Profiler.js +1991 -44
  67. package/examples/jsm/inspector/ui/Style.js +1943 -543
  68. package/examples/jsm/inspector/ui/Tab.js +226 -5
  69. package/examples/jsm/inspector/ui/Values.js +126 -8
  70. package/examples/jsm/inspector/ui/utils.js +143 -10
  71. package/examples/jsm/interaction/InteractionManager.js +226 -0
  72. package/examples/jsm/libs/basis/README.md +0 -1
  73. package/examples/jsm/libs/demuxer_mp4.js +3 -3
  74. package/examples/jsm/libs/draco/README.md +0 -1
  75. package/examples/jsm/libs/meshopt_clusterizer.module.js +421 -0
  76. package/examples/jsm/libs/meshopt_decoder.module.js +9 -8
  77. package/examples/jsm/libs/meshopt_simplifier.module.js +627 -0
  78. package/examples/jsm/libs/mikktspace.module.js +34 -9
  79. package/examples/jsm/lighting/ClusteredLighting.js +55 -0
  80. package/examples/jsm/lighting/DynamicLighting.js +82 -0
  81. package/examples/jsm/lighting/LightProbeGrid.js +681 -0
  82. package/examples/jsm/lines/LineMaterial.js +45 -16
  83. package/examples/jsm/lines/LineSegments2.js +8 -0
  84. package/examples/jsm/lines/webgpu/LineSegments2.js +8 -0
  85. package/examples/jsm/loaders/3DMLoader.js +6 -5
  86. package/examples/jsm/loaders/3MFLoader.js +2 -2
  87. package/examples/jsm/loaders/AMFLoader.js +2 -2
  88. package/examples/jsm/loaders/ColladaLoader.js +24 -4026
  89. package/examples/jsm/loaders/DRACOLoader.js +51 -18
  90. package/examples/jsm/loaders/EXRLoader.js +701 -90
  91. package/examples/jsm/loaders/FBXLoader.js +235 -37
  92. package/examples/jsm/loaders/GCodeLoader.js +34 -8
  93. package/examples/jsm/loaders/GLTFLoader.js +146 -178
  94. package/examples/jsm/loaders/HDRLoader.js +7 -30
  95. package/examples/jsm/loaders/KMZLoader.js +5 -5
  96. package/examples/jsm/loaders/KTX2Loader.js +58 -17
  97. package/examples/jsm/loaders/LDrawLoader.js +49 -58
  98. package/examples/jsm/loaders/LUT3dlLoader.js +2 -2
  99. package/examples/jsm/loaders/LUTCubeLoader.js +2 -2
  100. package/examples/jsm/loaders/LWOLoader.js +11 -39
  101. package/examples/jsm/loaders/LottieLoader.js +1 -1
  102. package/examples/jsm/loaders/MaterialXLoader.js +31 -9
  103. package/examples/jsm/loaders/NRRDLoader.js +5 -5
  104. package/examples/jsm/loaders/PCDLoader.js +11 -9
  105. package/examples/jsm/loaders/PLYLoader.js +218 -55
  106. package/examples/jsm/loaders/SVGLoader.js +549 -497
  107. package/examples/jsm/loaders/TDSLoader.js +0 -2
  108. package/examples/jsm/loaders/TGALoader.js +0 -2
  109. package/examples/jsm/loaders/TTFLoader.js +1 -1
  110. package/examples/jsm/loaders/USDLoader.js +127 -43
  111. package/examples/jsm/loaders/UltraHDRLoader.js +285 -160
  112. package/examples/jsm/loaders/VOXLoader.js +660 -117
  113. package/examples/jsm/loaders/VRMLLoader.js +80 -3
  114. package/examples/jsm/loaders/VTKLoader.js +42 -25
  115. package/examples/jsm/loaders/collada/ColladaComposer.js +3044 -0
  116. package/examples/jsm/loaders/collada/ColladaParser.js +1977 -0
  117. package/examples/jsm/loaders/usd/USDAParser.js +504 -344
  118. package/examples/jsm/loaders/usd/USDCParser.js +1867 -6
  119. package/examples/jsm/loaders/usd/USDComposer.js +4627 -0
  120. package/examples/jsm/materials/LDrawConditionalLineNodeMaterial.js +2 -2
  121. package/examples/jsm/materials/WoodNodeMaterial.js +11 -11
  122. package/examples/jsm/math/Octree.js +131 -1
  123. package/examples/jsm/misc/GPUComputationRenderer.js +2 -0
  124. package/examples/jsm/misc/RollerCoaster.js +42 -4
  125. package/examples/jsm/misc/TileCreasedNormalsPlugin.js +270 -0
  126. package/examples/jsm/misc/Volume.js +2 -3
  127. package/examples/jsm/misc/VolumeSlice.js +0 -1
  128. package/examples/jsm/modifiers/TessellateModifier.js +1 -1
  129. package/examples/jsm/objects/GroundedSkybox.js +1 -1
  130. package/examples/jsm/objects/LensflareMesh.js +1 -1
  131. package/examples/jsm/objects/Reflector.js +72 -25
  132. package/examples/jsm/objects/Sky.js +90 -6
  133. package/examples/jsm/objects/SkyMesh.js +150 -16
  134. package/examples/jsm/objects/Water.js +4 -3
  135. package/examples/jsm/objects/Water2.js +5 -3
  136. package/examples/jsm/objects/Water2Mesh.js +1 -1
  137. package/examples/jsm/objects/WaterMesh.js +5 -7
  138. package/examples/jsm/physics/AmmoPhysics.js +27 -15
  139. package/examples/jsm/physics/JoltPhysics.js +10 -6
  140. package/examples/jsm/physics/RapierPhysics.js +36 -7
  141. package/examples/jsm/postprocessing/EffectComposer.js +7 -5
  142. package/examples/jsm/postprocessing/OutputPass.js +9 -0
  143. package/examples/jsm/postprocessing/RenderPass.js +10 -0
  144. package/examples/jsm/postprocessing/RenderTransitionPass.js +1 -1
  145. package/examples/jsm/postprocessing/UnrealBloomPass.js +48 -18
  146. package/examples/jsm/renderers/CSS3DRenderer.js +1 -1
  147. package/examples/jsm/renderers/Projector.js +246 -28
  148. package/examples/jsm/renderers/SVGRenderer.js +174 -60
  149. package/examples/jsm/shaders/GTAOShader.js +19 -6
  150. package/examples/jsm/shaders/HalftoneShader.js +12 -1
  151. package/examples/jsm/shaders/PoissonDenoiseShader.js +6 -2
  152. package/examples/jsm/shaders/SAOShader.js +17 -4
  153. package/examples/jsm/shaders/SSAOShader.js +11 -1
  154. package/examples/jsm/shaders/SSRShader.js +6 -5
  155. package/examples/jsm/shaders/UnpackDepthRGBAShader.js +2 -4
  156. package/examples/jsm/shaders/VignetteShader.js +1 -1
  157. package/examples/jsm/shaders/VolumeShader.js +31 -44
  158. package/examples/jsm/transpiler/AST.js +44 -0
  159. package/examples/jsm/transpiler/GLSLDecoder.js +65 -8
  160. package/examples/jsm/transpiler/Linker.js +1 -1
  161. package/examples/jsm/transpiler/ShaderToyDecoder.js +2 -0
  162. package/examples/jsm/transpiler/TSLEncoder.js +46 -3
  163. package/examples/jsm/transpiler/TranspilerUtils.js +3 -3
  164. package/examples/jsm/transpiler/WGSLEncoder.js +27 -0
  165. package/examples/jsm/tsl/WebGLNodesHandler.js +605 -0
  166. package/examples/jsm/tsl/display/AfterImageNode.js +11 -1
  167. package/examples/jsm/tsl/display/AnaglyphPassNode.js +458 -16
  168. package/examples/jsm/tsl/display/BilateralBlurNode.js +374 -0
  169. package/examples/jsm/tsl/display/BloomNode.js +75 -25
  170. package/examples/jsm/tsl/display/CRT.js +150 -0
  171. package/examples/jsm/tsl/display/ChromaticAberrationNode.js +3 -36
  172. package/examples/jsm/tsl/display/DenoiseNode.js +1 -1
  173. package/examples/jsm/tsl/display/DepthOfFieldNode.js +3 -3
  174. package/examples/jsm/tsl/display/DotScreenNode.js +1 -1
  175. package/examples/jsm/tsl/display/FSR1Node.js +477 -0
  176. package/examples/jsm/tsl/display/FXAANode.js +13 -14
  177. package/examples/jsm/tsl/display/GTAONode.js +40 -17
  178. package/examples/jsm/tsl/display/GaussianBlurNode.js +21 -2
  179. package/examples/jsm/tsl/display/GodraysNode.js +615 -0
  180. package/examples/jsm/tsl/display/ImportanceSampledEnvironment.js +560 -0
  181. package/examples/jsm/tsl/display/LensflareNode.js +1 -1
  182. package/examples/jsm/tsl/display/Lut3DNode.js +1 -1
  183. package/examples/jsm/tsl/display/OutlineNode.js +69 -19
  184. package/examples/jsm/tsl/display/ParallaxBarrierPassNode.js +2 -2
  185. package/examples/jsm/tsl/display/PixelationPassNode.js +9 -8
  186. package/examples/jsm/tsl/display/RGBShiftNode.js +2 -2
  187. package/examples/jsm/tsl/display/RecurrentDenoiseNode.js +912 -0
  188. package/examples/jsm/tsl/display/RetroPassNode.js +263 -0
  189. package/examples/jsm/tsl/display/SMAANode.js +2 -2
  190. package/examples/jsm/tsl/display/SSAAPassNode.js +15 -27
  191. package/examples/jsm/tsl/display/SSGINode.js +96 -62
  192. package/examples/jsm/tsl/display/SSRNode.js +831 -134
  193. package/examples/jsm/tsl/display/SSSNode.js +8 -6
  194. package/examples/jsm/tsl/display/Shape.js +29 -0
  195. package/examples/jsm/tsl/display/SharpenNode.js +283 -0
  196. package/examples/jsm/tsl/display/SobelOperatorNode.js +2 -2
  197. package/examples/jsm/tsl/display/StereoCompositePassNode.js +8 -1
  198. package/examples/jsm/tsl/display/StereoPassNode.js +1 -2
  199. package/examples/jsm/tsl/display/TAAUNode.js +835 -0
  200. package/examples/jsm/tsl/display/TRAANode.js +315 -126
  201. package/examples/jsm/tsl/display/TemporalReprojectNode.js +1023 -0
  202. package/examples/jsm/tsl/display/TransitionNode.js +1 -1
  203. package/examples/jsm/tsl/display/depthAwareBlend.js +80 -0
  204. package/examples/jsm/tsl/display/radialBlur.js +68 -0
  205. package/examples/jsm/tsl/lighting/ClusteredLightsNode.js +622 -0
  206. package/examples/jsm/tsl/lighting/DynamicLightsNode.js +300 -0
  207. package/examples/jsm/tsl/lighting/data/AmbientLightDataNode.js +61 -0
  208. package/examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js +111 -0
  209. package/examples/jsm/tsl/lighting/data/HemisphereLightDataNode.js +99 -0
  210. package/examples/jsm/tsl/lighting/data/PointLightDataNode.js +134 -0
  211. package/examples/jsm/tsl/lighting/data/SpotLightDataNode.js +161 -0
  212. package/examples/jsm/tsl/math/Bayer.js +53 -3
  213. package/examples/jsm/tsl/math/curlNoise.js +107 -0
  214. package/examples/jsm/tsl/shadows/TileShadowNode.js +1 -1
  215. package/examples/jsm/tsl/shadows/TileShadowNodeHelper.js +3 -3
  216. package/examples/jsm/tsl/utils/GroundedSkybox.js +62 -0
  217. package/examples/jsm/tsl/utils/RNoise.js +51 -0
  218. package/examples/jsm/tsl/utils/SpecularHelpers.js +325 -0
  219. package/examples/jsm/utils/BufferGeometryUtils.js +128 -62
  220. package/examples/jsm/utils/ColorUtils.js +76 -0
  221. package/examples/jsm/utils/GeometryCompressionUtils.js +1 -1
  222. package/examples/jsm/utils/LDrawUtils.js +1 -1
  223. package/examples/jsm/utils/SceneOptimizer.js +1 -1
  224. package/examples/jsm/utils/ShadowMapViewer.js +24 -10
  225. package/examples/jsm/utils/ShadowMapViewerGPU.js +1 -1
  226. package/examples/jsm/utils/SkeletonUtils.js +14 -8
  227. package/examples/jsm/utils/WebGPUTextureUtils.js +1 -1
  228. package/examples/jsm/webxr/ARButton.js +1 -1
  229. package/examples/jsm/webxr/WebGLXRFallback.js +89 -0
  230. package/examples/jsm/webxr/XRControllerModelFactory.js +2 -2
  231. package/examples/jsm/webxr/XRHandMeshModel.js +36 -10
  232. package/examples/jsm/webxr/XRHandModelFactory.js +2 -1
  233. package/package.json +28 -34
  234. package/src/Three.Core.js +3 -1
  235. package/src/Three.TSL.js +35 -14
  236. package/src/Three.WebGPU.Nodes.js +5 -0
  237. package/src/Three.WebGPU.js +13 -0
  238. package/src/Three.js +1 -0
  239. package/src/animation/AnimationAction.js +18 -4
  240. package/src/animation/AnimationClip.js +0 -139
  241. package/src/animation/AnimationMixer.js +6 -0
  242. package/src/animation/AnimationUtils.js +1 -12
  243. package/src/animation/KeyframeTrack.js +47 -8
  244. package/src/animation/PropertyBinding.js +2 -2
  245. package/src/animation/PropertyMixer.js +4 -4
  246. package/src/animation/tracks/BooleanKeyframeTrack.js +1 -1
  247. package/src/animation/tracks/ColorKeyframeTrack.js +1 -1
  248. package/src/animation/tracks/NumberKeyframeTrack.js +1 -1
  249. package/src/animation/tracks/QuaternionKeyframeTrack.js +1 -1
  250. package/src/animation/tracks/StringKeyframeTrack.js +1 -1
  251. package/src/animation/tracks/VectorKeyframeTrack.js +1 -1
  252. package/src/audio/Audio.js +1 -1
  253. package/src/audio/AudioContext.js +2 -2
  254. package/src/audio/AudioListener.js +5 -3
  255. package/src/cameras/Camera.js +34 -4
  256. package/src/cameras/CubeCamera.js +20 -0
  257. package/src/cameras/StereoCamera.js +5 -2
  258. package/src/constants.js +90 -5
  259. package/src/core/BufferAttribute.js +13 -1
  260. package/src/core/BufferGeometry.js +43 -9
  261. package/src/core/Clock.js +7 -0
  262. package/src/core/Object3D.js +69 -11
  263. package/src/core/Raycaster.js +3 -3
  264. package/src/core/RenderTarget.js +19 -6
  265. package/src/extras/PMREMGenerator.js +13 -22
  266. package/src/extras/TextureUtils.js +6 -2
  267. package/src/extras/core/ShapePath.js +149 -160
  268. package/src/extras/curves/CatmullRomCurve3.js +3 -2
  269. package/src/geometries/ExtrudeGeometry.js +2 -2
  270. package/src/geometries/PolyhedronGeometry.js +1 -1
  271. package/src/geometries/SphereGeometry.js +8 -3
  272. package/src/geometries/TorusGeometry.js +8 -3
  273. package/src/helpers/CameraHelper.js +3 -0
  274. package/src/helpers/DirectionalLightHelper.js +6 -1
  275. package/src/helpers/HemisphereLightHelper.js +5 -0
  276. package/src/helpers/PointLightHelper.js +3 -25
  277. package/src/helpers/SpotLightHelper.js +4 -1
  278. package/src/lights/DirectionalLight.js +13 -0
  279. package/src/lights/HemisphereLight.js +10 -0
  280. package/src/lights/Light.js +1 -11
  281. package/src/lights/LightProbe.js +0 -15
  282. package/src/lights/LightShadow.js +15 -6
  283. package/src/lights/PointLight.js +15 -0
  284. package/src/lights/PointLightShadow.js +0 -86
  285. package/src/lights/SpotLight.js +22 -1
  286. package/src/lights/webgpu/IESSpotLight.js +2 -1
  287. package/src/loaders/AudioLoader.js +11 -1
  288. package/src/loaders/Cache.js +28 -0
  289. package/src/loaders/DataTextureLoader.js +75 -42
  290. package/src/loaders/FileLoader.js +2 -3
  291. package/src/loaders/ImageBitmapLoader.js +12 -9
  292. package/src/loaders/Loader.js +6 -0
  293. package/src/loaders/LoadingManager.js +5 -0
  294. package/src/loaders/MaterialLoader.js +37 -266
  295. package/src/loaders/ObjectLoader.js +48 -6
  296. package/src/loaders/nodes/NodeLoader.js +2 -2
  297. package/src/loaders/nodes/NodeObjectLoader.js +18 -0
  298. package/src/materials/LineBasicMaterial.js +4 -0
  299. package/src/materials/Material.js +198 -1
  300. package/src/materials/MeshBasicMaterial.js +24 -0
  301. package/src/materials/MeshDepthMaterial.js +10 -0
  302. package/src/materials/MeshDistanceMaterial.js +10 -0
  303. package/src/materials/MeshLambertMaterial.js +49 -1
  304. package/src/materials/MeshMatcapMaterial.js +25 -1
  305. package/src/materials/MeshNormalMaterial.js +9 -1
  306. package/src/materials/MeshPhongMaterial.js +49 -1
  307. package/src/materials/MeshPhysicalMaterial.js +38 -0
  308. package/src/materials/MeshStandardMaterial.js +42 -1
  309. package/src/materials/MeshToonMaterial.js +35 -2
  310. package/src/materials/PointsMaterial.js +7 -0
  311. package/src/materials/ShaderMaterial.js +106 -1
  312. package/src/materials/SpriteMaterial.js +7 -0
  313. package/src/materials/nodes/Line2NodeMaterial.js +380 -297
  314. package/src/materials/nodes/MeshBasicNodeMaterial.js +2 -2
  315. package/src/materials/nodes/MeshNormalNodeMaterial.js +2 -2
  316. package/src/materials/nodes/MeshPhongNodeMaterial.js +0 -9
  317. package/src/materials/nodes/MeshPhysicalNodeMaterial.js +3 -30
  318. package/src/materials/nodes/MeshSSSNodeMaterial.js +0 -13
  319. package/src/materials/nodes/MeshStandardNodeMaterial.js +5 -15
  320. package/src/materials/nodes/NodeMaterial.js +153 -89
  321. package/src/materials/nodes/SpriteNodeMaterial.js +0 -10
  322. package/src/materials/nodes/manager/NodeMaterialObserver.js +234 -100
  323. package/src/math/Box3.js +5 -0
  324. package/src/math/FrustumArray.js +103 -133
  325. package/src/math/Interpolant.js +1 -1
  326. package/src/math/Line3.js +6 -5
  327. package/src/math/MathUtils.js +12 -12
  328. package/src/math/Matrix2.js +13 -9
  329. package/src/math/Matrix3.js +24 -9
  330. package/src/math/Matrix4.js +112 -74
  331. package/src/math/Plane.js +4 -3
  332. package/src/math/Quaternion.js +3 -29
  333. package/src/math/Sphere.js +1 -1
  334. package/src/math/Triangle.js +1 -1
  335. package/src/math/Vector2.js +13 -9
  336. package/src/math/Vector3.js +17 -15
  337. package/src/math/Vector4.js +15 -11
  338. package/src/math/interpolants/BezierInterpolant.js +106 -0
  339. package/src/nodes/Nodes.js +86 -71
  340. package/src/nodes/TSL.js +15 -13
  341. package/src/nodes/accessors/Arrays.js +5 -5
  342. package/src/nodes/accessors/Batch.js +108 -0
  343. package/src/nodes/accessors/Bitangent.js +7 -7
  344. package/src/nodes/accessors/BufferAttributeNode.js +120 -17
  345. package/src/nodes/accessors/BufferNode.js +29 -2
  346. package/src/nodes/accessors/Camera.js +149 -28
  347. package/src/nodes/accessors/ClippingNode.js +5 -5
  348. package/src/nodes/accessors/CubeTextureNode.js +27 -2
  349. package/src/nodes/accessors/Instance.js +264 -0
  350. package/src/nodes/accessors/MaterialNode.js +27 -3
  351. package/src/nodes/accessors/MaterialProperties.js +6 -8
  352. package/src/nodes/accessors/MaterialReferenceNode.js +1 -2
  353. package/src/nodes/accessors/ModelNode.js +1 -1
  354. package/src/nodes/accessors/{MorphNode.js → Morph.js} +99 -112
  355. package/src/nodes/accessors/Normal.js +24 -24
  356. package/src/nodes/accessors/Object3DNode.js +1 -1
  357. package/src/nodes/accessors/Position.js +39 -3
  358. package/src/nodes/accessors/ReferenceBaseNode.js +6 -6
  359. package/src/nodes/accessors/ReferenceNode.js +9 -8
  360. package/src/nodes/accessors/ReflectVector.js +3 -3
  361. package/src/nodes/accessors/RendererReferenceNode.js +1 -2
  362. package/src/nodes/accessors/SceneProperties.js +47 -0
  363. package/src/nodes/accessors/Skinning.js +263 -0
  364. package/src/nodes/accessors/StorageBufferNode.js +16 -27
  365. package/src/nodes/accessors/StorageTexture3DNode.js +100 -0
  366. package/src/nodes/accessors/StorageTextureNode.js +74 -11
  367. package/src/nodes/accessors/Tangent.js +8 -18
  368. package/src/nodes/accessors/Texture3DNode.js +32 -35
  369. package/src/nodes/accessors/TextureNode.js +124 -25
  370. package/src/nodes/accessors/UniformArrayNode.js +6 -4
  371. package/src/nodes/accessors/UserDataNode.js +1 -2
  372. package/src/nodes/accessors/VertexColorNode.js +1 -2
  373. package/src/nodes/code/FunctionCallNode.js +1 -1
  374. package/src/nodes/code/FunctionNode.js +4 -19
  375. package/src/nodes/core/ArrayNode.js +21 -2
  376. package/src/nodes/core/AssignNode.js +3 -3
  377. package/src/nodes/core/AttributeNode.js +3 -3
  378. package/src/nodes/core/BypassNode.js +1 -1
  379. package/src/nodes/core/ContextNode.js +104 -5
  380. package/src/nodes/core/IndexNode.js +2 -1
  381. package/src/nodes/core/InputNode.js +1 -1
  382. package/src/nodes/core/InspectorNode.js +1 -1
  383. package/src/nodes/core/IsolateNode.js +1 -1
  384. package/src/nodes/core/MRTNode.js +55 -4
  385. package/src/nodes/core/Node.js +151 -17
  386. package/src/nodes/core/NodeBuilder.js +479 -101
  387. package/src/nodes/core/NodeError.js +28 -0
  388. package/src/nodes/core/NodeFrame.js +12 -4
  389. package/src/nodes/core/NodeUtils.js +20 -18
  390. package/src/nodes/core/OutputStructNode.js +13 -11
  391. package/src/nodes/core/OverrideContextNode.js +153 -0
  392. package/src/nodes/core/ParameterNode.js +4 -4
  393. package/src/nodes/core/PropertyNode.js +66 -4
  394. package/src/nodes/core/StackNode.js +91 -20
  395. package/src/nodes/core/StackTrace.js +139 -0
  396. package/src/nodes/core/StructNode.js +20 -8
  397. package/src/nodes/core/StructTypeNode.js +17 -23
  398. package/src/nodes/core/SubBuildNode.js +2 -2
  399. package/src/nodes/core/UniformGroupNode.js +36 -6
  400. package/src/nodes/core/UniformNode.js +21 -5
  401. package/src/nodes/core/VarNode.js +56 -23
  402. package/src/nodes/core/VaryingNode.js +14 -21
  403. package/src/nodes/display/BlendModes.js +1 -105
  404. package/src/nodes/display/BumpMapNode.js +6 -1
  405. package/src/nodes/display/ColorAdjustment.js +27 -9
  406. package/src/nodes/display/ColorSpaceNode.js +3 -3
  407. package/src/nodes/display/FrontFacingNode.js +38 -9
  408. package/src/nodes/display/NormalMapNode.js +41 -6
  409. package/src/nodes/display/PassNode.js +157 -56
  410. package/src/nodes/display/PremultiplyAlphaFunctions.js +39 -0
  411. package/src/nodes/display/RenderOutputNode.js +19 -10
  412. package/src/nodes/display/ScreenNode.js +4 -2
  413. package/src/nodes/display/ToneMappingNode.js +1 -1
  414. package/src/nodes/display/ToonOutlinePassNode.js +2 -2
  415. package/src/nodes/display/ViewportDepthNode.js +52 -4
  416. package/src/nodes/display/ViewportDepthTextureNode.js +11 -15
  417. package/src/nodes/display/ViewportTextureNode.js +39 -11
  418. package/src/nodes/fog/Fog.js +18 -35
  419. package/src/nodes/functions/BSDF/BRDF_GGX_Multiscatter.js +3 -3
  420. package/src/nodes/functions/BSDF/DFGLUT.js +56 -0
  421. package/src/nodes/functions/BSDF/EnvironmentBRDF.js +2 -2
  422. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +2 -2
  423. package/src/nodes/functions/PhysicalLightingModel.js +126 -45
  424. package/src/nodes/functions/VolumetricLightingModel.js +40 -7
  425. package/src/nodes/geometry/RangeNode.js +5 -3
  426. package/src/nodes/gpgpu/AtomicFunctionNode.js +1 -1
  427. package/src/nodes/gpgpu/BarrierNode.js +9 -0
  428. package/src/nodes/gpgpu/ComputeBuiltinNode.js +2 -3
  429. package/src/nodes/gpgpu/ComputeNode.js +74 -48
  430. package/src/nodes/gpgpu/SubgroupFunctionNode.js +2 -2
  431. package/src/nodes/gpgpu/WorkgroupInfoNode.js +5 -5
  432. package/src/nodes/lighting/AnalyticLightNode.js +53 -0
  433. package/src/nodes/lighting/EnvironmentNode.js +30 -5
  434. package/src/nodes/lighting/IESSpotLightNode.js +40 -2
  435. package/src/nodes/lighting/LightingContextNode.js +10 -2
  436. package/src/nodes/lighting/LightsNode.js +97 -65
  437. package/src/nodes/lighting/PointShadowNode.js +166 -148
  438. package/src/nodes/lighting/ShadowFilterNode.js +74 -117
  439. package/src/nodes/lighting/ShadowNode.js +149 -51
  440. package/src/nodes/materialx/lib/mx_noise.js +2 -2
  441. package/src/nodes/math/BitcastNode.js +1 -1
  442. package/src/nodes/math/BitcountNode.js +433 -0
  443. package/src/nodes/math/ConditionalNode.js +6 -6
  444. package/src/nodes/math/MathNode.js +115 -52
  445. package/src/nodes/math/OperatorNode.js +24 -23
  446. package/src/nodes/math/PackFloatNode.js +98 -0
  447. package/src/nodes/math/UnpackFloatNode.js +96 -0
  448. package/src/nodes/parsers/GLSLNodeFunction.js +1 -1
  449. package/src/nodes/pmrem/PMREMNode.js +34 -3
  450. package/src/nodes/pmrem/PMREMUtils.js +9 -15
  451. package/src/nodes/tsl/TSLBase.js +1 -1
  452. package/src/nodes/tsl/TSLCore.js +99 -33
  453. package/src/nodes/utils/ArrayElementNode.js +14 -1
  454. package/src/nodes/utils/ConvertNode.js +1 -1
  455. package/src/nodes/utils/DebugNode.js +12 -12
  456. package/src/nodes/utils/EquirectUV.js +30 -5
  457. package/src/nodes/utils/EventNode.js +31 -2
  458. package/src/nodes/utils/FlipNode.js +1 -1
  459. package/src/nodes/utils/FunctionOverloadingNode.js +2 -2
  460. package/src/nodes/utils/JoinNode.js +3 -3
  461. package/src/nodes/utils/LoopNode.js +1 -1
  462. package/src/nodes/utils/MemberNode.js +2 -2
  463. package/src/nodes/utils/Packing.js +48 -5
  464. package/src/nodes/utils/PostProcessingUtils.js +33 -1
  465. package/src/nodes/utils/RTTNode.js +41 -25
  466. package/src/nodes/utils/ReflectorNode.js +4 -4
  467. package/src/nodes/utils/Remap.js +48 -0
  468. package/src/nodes/utils/RotateNode.js +1 -1
  469. package/src/nodes/utils/SampleNode.js +1 -1
  470. package/src/nodes/utils/SetNode.js +1 -1
  471. package/src/nodes/utils/SplitNode.js +1 -1
  472. package/src/nodes/utils/SpriteSheetUV.js +35 -0
  473. package/src/nodes/utils/StorageArrayElementNode.js +1 -1
  474. package/src/nodes/utils/UVUtils.js +28 -0
  475. package/src/objects/BatchedMesh.js +66 -30
  476. package/src/objects/InstancedMesh.js +19 -3
  477. package/src/objects/Line.js +1 -1
  478. package/src/objects/Mesh.js +1 -1
  479. package/src/objects/Points.js +1 -1
  480. package/src/objects/SkinnedMesh.js +26 -9
  481. package/src/renderers/WebGLRenderer.js +344 -156
  482. package/src/renderers/common/Animation.js +3 -3
  483. package/src/renderers/common/Attributes.js +15 -1
  484. package/src/renderers/common/Backend.js +78 -13
  485. package/src/renderers/common/Background.js +26 -13
  486. package/src/renderers/common/BindGroup.js +1 -16
  487. package/src/renderers/common/Binding.js +11 -0
  488. package/src/renderers/common/Bindings.js +141 -57
  489. package/src/renderers/common/BlendMode.js +143 -0
  490. package/src/renderers/common/Buffer.js +49 -0
  491. package/src/renderers/common/BundleGroup.js +1 -1
  492. package/src/renderers/common/ChainMap.js +30 -6
  493. package/src/renderers/common/ClippingContext.js +10 -2
  494. package/src/renderers/common/ComputePipeline.js +1 -1
  495. package/src/renderers/common/CubeRenderTarget.js +51 -7
  496. package/src/renderers/common/Geometries.js +29 -3
  497. package/src/renderers/common/Info.js +374 -4
  498. package/src/renderers/common/InspectorBase.js +6 -1
  499. package/src/renderers/common/Lighting.js +51 -19
  500. package/src/renderers/common/Pipelines.js +40 -7
  501. package/src/renderers/common/PostProcessing.js +8 -206
  502. package/src/renderers/common/ReadbackBuffer.js +78 -0
  503. package/src/renderers/common/RenderBundle.js +3 -1
  504. package/src/renderers/common/RenderBundles.js +7 -3
  505. package/src/renderers/common/RenderContext.js +16 -0
  506. package/src/renderers/common/RenderContexts.js +33 -49
  507. package/src/renderers/common/RenderList.js +41 -4
  508. package/src/renderers/common/RenderLists.js +2 -1
  509. package/src/renderers/common/RenderObject.js +88 -16
  510. package/src/renderers/common/RenderObjectPipeline.js +40 -0
  511. package/src/renderers/common/RenderObjects.js +21 -5
  512. package/src/renderers/common/RenderPipeline.js +232 -17
  513. package/src/renderers/common/Renderer.js +700 -163
  514. package/src/renderers/common/Sampler.js +16 -48
  515. package/src/renderers/common/StorageBuffer.js +13 -1
  516. package/src/renderers/common/Textures.js +121 -9
  517. package/src/renderers/common/TimestampQueryPool.js +6 -4
  518. package/src/renderers/common/Uniform.js +8 -0
  519. package/src/renderers/common/UniformsGroup.js +84 -1
  520. package/src/renderers/common/XRManager.js +377 -51
  521. package/src/renderers/common/extras/PMREMGenerator.js +40 -61
  522. package/src/renderers/common/nodes/NodeBuilderState.js +10 -2
  523. package/src/renderers/common/nodes/NodeLibrary.js +4 -4
  524. package/src/renderers/common/nodes/{Nodes.js → NodeManager.js} +298 -106
  525. package/src/renderers/common/nodes/NodeStorageBuffer.js +13 -2
  526. package/src/renderers/common/nodes/NodeUniformBuffer.js +65 -0
  527. package/src/renderers/shaders/DFGLUTData.js +19 -34
  528. package/src/renderers/shaders/ShaderChunk/batching_pars_vertex.glsl.js +2 -2
  529. package/src/renderers/shaders/ShaderChunk/color_fragment.glsl.js +1 -5
  530. package/src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl.js +1 -5
  531. package/src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl.js +1 -5
  532. package/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js +8 -10
  533. package/src/renderers/shaders/ShaderChunk/common.glsl.js +13 -4
  534. package/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl.js +0 -6
  535. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +0 -1
  536. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +9 -13
  537. package/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +2 -2
  538. package/src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js +1 -1
  539. package/src/renderers/shaders/ShaderChunk/lightprobes_pars_fragment.glsl.js +80 -0
  540. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +13 -2
  541. package/src/renderers/shaders/ShaderChunk/lights_fragment_end.glsl.js +6 -0
  542. package/src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js +6 -2
  543. package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +3 -1
  544. package/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +8 -4
  545. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +112 -53
  546. package/src/renderers/shaders/ShaderChunk/normal_fragment_begin.glsl.js +2 -2
  547. package/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js +7 -0
  548. package/src/renderers/shaders/ShaderChunk/normal_vertex.glsl.js +6 -0
  549. package/src/renderers/shaders/ShaderChunk/packing.glsl.js +20 -4
  550. package/src/renderers/shaders/ShaderChunk/premultiplied_alpha_fragment.glsl.js +0 -1
  551. package/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +225 -186
  552. package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +12 -2
  553. package/src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl.js +1 -1
  554. package/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js +2 -2
  555. package/src/renderers/shaders/ShaderChunk.js +5 -3
  556. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +1 -2
  557. package/src/renderers/shaders/ShaderLib/depth.glsl.js +3 -0
  558. package/src/renderers/shaders/ShaderLib/{distanceRGBA.glsl.js → distance.glsl.js} +2 -3
  559. package/src/renderers/shaders/ShaderLib/meshlambert.glsl.js +2 -1
  560. package/src/renderers/shaders/ShaderLib/meshnormal.glsl.js +1 -2
  561. package/src/renderers/shaders/ShaderLib/meshphong.glsl.js +2 -1
  562. package/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +4 -9
  563. package/src/renderers/shaders/ShaderLib/meshtoon.glsl.js +0 -1
  564. package/src/renderers/shaders/ShaderLib/shadow.glsl.js +1 -1
  565. package/src/renderers/shaders/ShaderLib/vsm.glsl.js +4 -6
  566. package/src/renderers/shaders/ShaderLib.js +7 -6
  567. package/src/renderers/shaders/UniformsLib.js +7 -5
  568. package/src/renderers/shaders/UniformsUtils.js +27 -5
  569. package/src/renderers/webgl/WebGLAnimation.js +2 -1
  570. package/src/renderers/webgl/WebGLBackground.js +15 -15
  571. package/src/renderers/webgl/WebGLBindingStates.js +99 -27
  572. package/src/renderers/webgl/WebGLBufferRenderer.js +0 -32
  573. package/src/renderers/webgl/WebGLCapabilities.js +9 -4
  574. package/src/renderers/webgl/WebGLEnvironments.js +228 -0
  575. package/src/renderers/webgl/WebGLGeometries.js +10 -7
  576. package/src/renderers/webgl/WebGLIndexedBufferRenderer.js +0 -32
  577. package/src/renderers/webgl/WebGLLights.js +18 -1
  578. package/src/renderers/webgl/WebGLMaterials.js +24 -13
  579. package/src/renderers/webgl/WebGLObjects.js +3 -1
  580. package/src/renderers/webgl/WebGLOutput.js +271 -0
  581. package/src/renderers/webgl/WebGLProgram.js +54 -113
  582. package/src/renderers/webgl/WebGLPrograms.js +74 -55
  583. package/src/renderers/webgl/WebGLRenderLists.js +24 -1
  584. package/src/renderers/webgl/WebGLRenderStates.js +13 -2
  585. package/src/renderers/webgl/WebGLShaderCache.js +5 -11
  586. package/src/renderers/webgl/WebGLShadowMap.js +188 -24
  587. package/src/renderers/webgl/WebGLState.js +75 -37
  588. package/src/renderers/webgl/WebGLTextures.js +230 -56
  589. package/src/renderers/webgl/WebGLUniforms.js +40 -3
  590. package/src/renderers/webgl/WebGLUniformsGroups.js +82 -35
  591. package/src/renderers/webgl/WebGLUtils.js +6 -2
  592. package/src/renderers/webgl-fallback/WebGLBackend.js +322 -92
  593. package/src/renderers/webgl-fallback/WebGLBufferRenderer.js +0 -41
  594. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +295 -66
  595. package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +53 -19
  596. package/src/renderers/webgl-fallback/utils/WebGLCapabilities.js +25 -0
  597. package/src/renderers/webgl-fallback/utils/WebGLState.js +223 -6
  598. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +118 -73
  599. package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +10 -10
  600. package/src/renderers/webgl-fallback/utils/WebGLUtils.js +9 -3
  601. package/src/renderers/webgpu/WebGPUBackend.js +733 -302
  602. package/src/renderers/webgpu/WebGPURenderer.js +2 -1
  603. package/src/renderers/webgpu/descriptors/GPUBindGroupDescriptor.js +48 -0
  604. package/src/renderers/webgpu/descriptors/GPUBufferDescriptor.js +57 -0
  605. package/src/renderers/webgpu/descriptors/GPUCommandEncoderDescriptor.js +30 -0
  606. package/src/renderers/webgpu/descriptors/GPUComputePassDescriptor.js +38 -0
  607. package/src/renderers/webgpu/descriptors/GPUComputePipelineDescriptor.js +48 -0
  608. package/src/renderers/webgpu/descriptors/GPUCopyExternalImageDestInfo.js +47 -0
  609. package/src/renderers/webgpu/descriptors/GPUCopyExternalImageSourceInfo.js +50 -0
  610. package/src/renderers/webgpu/descriptors/GPUExtent3D.js +51 -0
  611. package/src/renderers/webgpu/descriptors/GPUPipelineLayoutDescriptor.js +39 -0
  612. package/src/renderers/webgpu/descriptors/GPUQuerySetDescriptor.js +47 -0
  613. package/src/renderers/webgpu/descriptors/GPURenderBundleEncoderDescriptor.js +74 -0
  614. package/src/renderers/webgpu/descriptors/GPURenderPassColorAttachment.js +72 -0
  615. package/src/renderers/webgpu/descriptors/GPURenderPassDepthStencilAttachment.js +99 -0
  616. package/src/renderers/webgpu/descriptors/GPURenderPassDescriptor.js +72 -0
  617. package/src/renderers/webgpu/descriptors/GPURenderPassTimestampWrites.js +49 -0
  618. package/src/renderers/webgpu/descriptors/GPURenderPipelineDescriptor.js +130 -0
  619. package/src/renderers/webgpu/descriptors/GPUSamplerDescriptor.js +119 -0
  620. package/src/renderers/webgpu/descriptors/GPUShaderModuleDescriptor.js +46 -0
  621. package/src/renderers/webgpu/descriptors/GPUTexelCopyBufferInfo.js +57 -0
  622. package/src/renderers/webgpu/descriptors/GPUTexelCopyBufferLayout.js +48 -0
  623. package/src/renderers/webgpu/descriptors/GPUTexelCopyTextureInfo.js +61 -0
  624. package/src/renderers/webgpu/descriptors/GPUTextureDescriptor.js +99 -0
  625. package/src/renderers/webgpu/descriptors/GPUTextureViewDescriptor.js +108 -0
  626. package/src/renderers/webgpu/nodes/StandardNodeLibrary.js +0 -1
  627. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +526 -93
  628. package/src/renderers/webgpu/nodes/WGSLNodeFunction.js +1 -1
  629. package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +194 -61
  630. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +395 -234
  631. package/src/renderers/webgpu/utils/WebGPUCapabilities.js +48 -0
  632. package/src/renderers/webgpu/utils/WebGPUConstants.js +10 -0
  633. package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +224 -91
  634. package/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js +228 -213
  635. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +428 -175
  636. package/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +40 -25
  637. package/src/renderers/webgpu/utils/WebGPUUtils.js +70 -13
  638. package/src/renderers/webxr/WebXRController.js +12 -0
  639. package/src/renderers/webxr/WebXRManager.js +4 -2
  640. package/src/textures/CubeDepthTexture.js +76 -0
  641. package/src/textures/DepthTexture.js +1 -1
  642. package/src/textures/ExternalTexture.js +0 -3
  643. package/src/textures/HTMLTexture.js +74 -0
  644. package/src/textures/Source.js +2 -2
  645. package/src/textures/Texture.js +16 -5
  646. package/src/utils.js +280 -3
  647. package/examples/fonts/LICENSE +0 -13
  648. package/examples/fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json.zip +0 -0
  649. package/examples/fonts/MPLUSRounded1c/OFL.txt +0 -91
  650. package/examples/fonts/README.md +0 -11
  651. package/examples/fonts/droid/NOTICE +0 -190
  652. package/examples/fonts/droid/README.txt +0 -18
  653. package/examples/fonts/droid/droid_sans_bold.typeface.json +0 -1
  654. package/examples/fonts/droid/droid_sans_mono_regular.typeface.json +0 -1
  655. package/examples/fonts/droid/droid_sans_regular.typeface.json +0 -1
  656. package/examples/fonts/droid/droid_serif_bold.typeface.json +0 -1
  657. package/examples/fonts/droid/droid_serif_regular.typeface.json +0 -1
  658. package/examples/fonts/gentilis_bold.typeface.json +0 -1
  659. package/examples/fonts/gentilis_regular.typeface.json +0 -1
  660. package/examples/fonts/helvetiker_bold.typeface.json +0 -1
  661. package/examples/fonts/helvetiker_regular.typeface.json +0 -1
  662. package/examples/fonts/optimer_bold.typeface.json +0 -1
  663. package/examples/fonts/optimer_regular.typeface.json +0 -1
  664. package/examples/fonts/ttf/README.md +0 -9
  665. package/examples/fonts/ttf/kenpixel.ttf +0 -0
  666. package/examples/jsm/libs/ammo.wasm.js +0 -822
  667. package/examples/jsm/libs/ammo.wasm.wasm +0 -0
  668. package/examples/jsm/libs/draco/draco_encoder.js +0 -33
  669. package/examples/jsm/libs/draco/gltf/draco_encoder.js +0 -33
  670. package/examples/jsm/libs/lottie_canvas.module.js +0 -14849
  671. package/examples/jsm/libs/opentype.module.js +0 -14506
  672. package/examples/jsm/libs/rhino3dm/rhino3dm.js +0 -21
  673. package/examples/jsm/libs/rhino3dm/rhino3dm.module.js +0 -16
  674. package/examples/jsm/libs/rhino3dm/rhino3dm.wasm +0 -0
  675. package/examples/jsm/lighting/TiledLighting.js +0 -42
  676. package/examples/jsm/materials/MeshGouraudMaterial.js +0 -434
  677. package/examples/jsm/materials/MeshPostProcessingMaterial.js +0 -167
  678. package/examples/jsm/shaders/GodRaysShader.js +0 -333
  679. package/examples/jsm/tsl/display/AnamorphicNode.js +0 -282
  680. package/examples/jsm/tsl/lighting/TiledLightsNode.js +0 -442
  681. package/src/nodes/accessors/BatchNode.js +0 -163
  682. package/src/nodes/accessors/InstanceNode.js +0 -244
  683. package/src/nodes/accessors/InstancedMeshNode.js +0 -50
  684. package/src/nodes/accessors/SceneNode.js +0 -145
  685. package/src/nodes/accessors/SkinningNode.js +0 -327
  686. package/src/nodes/code/ScriptableNode.js +0 -726
  687. package/src/nodes/code/ScriptableValueNode.js +0 -253
  688. package/src/nodes/display/PosterizeNode.js +0 -65
  689. package/src/nodes/functions/BSDF/DFGApprox.js +0 -71
  690. package/src/nodes/utils/RemapNode.js +0 -125
  691. package/src/nodes/utils/SpriteSheetUVNode.js +0 -90
  692. package/src/renderers/webgl/WebGLCubeMaps.js +0 -99
  693. package/src/renderers/webgl/WebGLCubeUVMaps.js +0 -134
@@ -0,0 +1,4627 @@
1
+ import {
2
+ AnimationClip,
3
+ BoxGeometry,
4
+ BufferAttribute,
5
+ BufferGeometry,
6
+ CapsuleGeometry,
7
+ ClampToEdgeWrapping,
8
+ Color,
9
+ ConeGeometry,
10
+ CylinderGeometry,
11
+ DirectionalLight,
12
+ Euler,
13
+ Group,
14
+ LoaderUtils,
15
+ Matrix4,
16
+ Mesh,
17
+ MeshPhysicalMaterial,
18
+ MirroredRepeatWrapping,
19
+ NoColorSpace,
20
+ Object3D,
21
+ OrthographicCamera,
22
+ PerspectiveCamera,
23
+ PointLight,
24
+ Quaternion,
25
+ QuaternionKeyframeTrack,
26
+ RectAreaLight,
27
+ RepeatWrapping,
28
+ ShapeUtils,
29
+ SkinnedMesh,
30
+ Skeleton,
31
+ Bone,
32
+ SphereGeometry,
33
+ SpotLight,
34
+ SRGBColorSpace,
35
+ Texture,
36
+ Vector2,
37
+ Vector3,
38
+ VectorKeyframeTrack
39
+ } from 'three';
40
+
41
+ // Pre-compiled regex patterns for performance
42
+ const VARIANT_PATH_REGEX = /^(.+?)\/\{(\w+)=(\w+)\}\/(.+)$/;
43
+
44
+ // Spec types (must match USDCParser)
45
+ const SpecType = {
46
+ Unknown: 0,
47
+ Attribute: 1,
48
+ Connection: 2,
49
+ Expression: 3,
50
+ Mapper: 4,
51
+ MapperArg: 5,
52
+ Prim: 6,
53
+ PseudoRoot: 7,
54
+ Relationship: 8,
55
+ RelationshipTarget: 9,
56
+ Variant: 10,
57
+ VariantSet: 11
58
+ };
59
+
60
+ // UsdGeomCamera fallback values (OpenUSD schema)
61
+ const USD_CAMERA_DEFAULTS = {
62
+ projection: 'perspective',
63
+ clippingRange: [ 1, 1000000 ],
64
+ horizontalAperture: 20.955,
65
+ verticalAperture: 15.2908,
66
+ horizontalApertureOffset: 0,
67
+ verticalApertureOffset: 0,
68
+ focalLength: 50,
69
+ focusDistance: 0,
70
+ fStop: 0
71
+ };
72
+
73
+ /**
74
+ * USDComposer handles scene composition from parsed USD data.
75
+ * This includes reference resolution, variant selection, transform handling,
76
+ * and building the Three.js scene graph.
77
+ *
78
+ * Works with specsByPath format from USDCParser.
79
+ */
80
+ class USDComposer {
81
+
82
+ constructor( manager = null ) {
83
+
84
+ this.textureCache = {};
85
+ this.skinnedMeshes = [];
86
+ this.manager = manager;
87
+ this.texturePromises = [];
88
+
89
+ }
90
+
91
+ /**
92
+ * Compose a Three.js scene from parsed USD data.
93
+ * @param {Object} parsedData - Data from USDCParser or USDAParser
94
+ * @param {Object} assets - Dictionary of referenced assets (specsByPath or blob URLs)
95
+ * @param {Object} variantSelections - External variant selections
96
+ * @param {string} basePath - Base path for resolving relative references
97
+ * @returns {Group} Three.js scene graph
98
+ */
99
+ compose( parsedData, assets = {}, variantSelections = {}, basePath = '' ) {
100
+
101
+ this.specsByPath = parsedData.specsByPath;
102
+ this.assets = assets;
103
+ this.externalVariantSelections = variantSelections;
104
+ this.basePath = basePath;
105
+ this.skinnedMeshes = [];
106
+ this.skeletons = {};
107
+ this.texturePromises = [];
108
+
109
+ // Build indexes for O(1) lookups
110
+ this._buildIndexes();
111
+
112
+ // Get FPS from root spec
113
+ const rootSpec = this.specsByPath[ '/' ];
114
+ const rootFields = rootSpec ? rootSpec.fields : {};
115
+ this.fps = rootFields.timeCodesPerSecond || rootFields.framesPerSecond || 24;
116
+
117
+ const group = new Group();
118
+ this._buildHierarchy( group, '/' );
119
+
120
+ // Bind skeletons to skinned meshes
121
+ this._bindSkeletons();
122
+
123
+ // Expose skeleton on the root group so that AnimationMixer's
124
+ // PropertyBinding.findNode resolves bone names before scene objects.
125
+ // Without this, Xform prims that share a name with a skeleton joint
126
+ // would be animated instead of the bone.
127
+ const skeletonPaths = Object.keys( this.skeletons );
128
+ if ( skeletonPaths.length === 1 ) {
129
+
130
+ group.skeleton = this.skeletons[ skeletonPaths[ 0 ] ].skeleton;
131
+
132
+ }
133
+
134
+ // Build animations
135
+ group.animations = this._buildAnimations();
136
+
137
+ // Handle metersPerUnit scaling
138
+ const metersPerUnit = rootFields.metersPerUnit;
139
+
140
+ if ( metersPerUnit !== undefined && metersPerUnit !== 1 ) {
141
+
142
+ group.scale.setScalar( metersPerUnit );
143
+
144
+ }
145
+
146
+ // Handle Z-up to Y-up conversion
147
+ if ( rootSpec && rootSpec.fields && rootSpec.fields.upAxis === 'Z' ) {
148
+
149
+ group.rotation.x = - Math.PI / 2;
150
+
151
+ }
152
+
153
+ return group;
154
+
155
+ }
156
+
157
+ /**
158
+ * Apply USD transforms to a Three.js object.
159
+ * Handles xformOpOrder with proper matrix composition.
160
+ * USD uses row-vector convention, Three.js uses column-vector.
161
+ */
162
+ applyTransform( obj, fields, attrs = {} ) {
163
+
164
+ const data = { ...fields, ...attrs };
165
+ const xformOpOrder = data[ 'xformOpOrder' ];
166
+
167
+ // If we have xformOpOrder, apply transforms using matrices
168
+ if ( xformOpOrder && xformOpOrder.length > 0 ) {
169
+
170
+ const matrix = new Matrix4();
171
+ const tempMatrix = new Matrix4();
172
+
173
+ // Track scale for handling negative scale with rotation
174
+ let scaleValues = null;
175
+
176
+ // Iterate FORWARD for Three.js column-vector convention
177
+ for ( let i = 0; i < xformOpOrder.length; i ++ ) {
178
+
179
+ const op = xformOpOrder[ i ];
180
+ const isInverse = op.startsWith( '!invert!' );
181
+ const opName = isInverse ? op.slice( 8 ) : op;
182
+
183
+ if ( opName === 'xformOp:transform' ) {
184
+
185
+ const m = data[ 'xformOp:transform' ];
186
+ if ( m && m.length === 16 ) {
187
+
188
+ tempMatrix.set(
189
+ m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
190
+ m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ],
191
+ m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ],
192
+ m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
193
+ );
194
+ if ( isInverse ) tempMatrix.invert();
195
+ matrix.multiply( tempMatrix );
196
+
197
+ }
198
+
199
+ } else if ( opName === 'xformOp:translate' ) {
200
+
201
+ const t = data[ 'xformOp:translate' ];
202
+ if ( t ) {
203
+
204
+ tempMatrix.makeTranslation( t[ 0 ], t[ 1 ], t[ 2 ] );
205
+ if ( isInverse ) tempMatrix.invert();
206
+ matrix.multiply( tempMatrix );
207
+
208
+ }
209
+
210
+ } else if ( opName === 'xformOp:translate:pivot' ) {
211
+
212
+ const t = data[ 'xformOp:translate:pivot' ];
213
+ if ( t ) {
214
+
215
+ tempMatrix.makeTranslation( t[ 0 ], t[ 1 ], t[ 2 ] );
216
+ if ( isInverse ) tempMatrix.invert();
217
+ matrix.multiply( tempMatrix );
218
+
219
+ }
220
+
221
+ } else if ( opName === 'xformOp:scale' ) {
222
+
223
+ const s = data[ 'xformOp:scale' ];
224
+ if ( s ) {
225
+
226
+ if ( Array.isArray( s ) ) {
227
+
228
+ tempMatrix.makeScale( s[ 0 ], s[ 1 ], s[ 2 ] );
229
+ scaleValues = [ s[ 0 ], s[ 1 ], s[ 2 ] ];
230
+
231
+ } else {
232
+
233
+ tempMatrix.makeScale( s, s, s );
234
+ scaleValues = [ s, s, s ];
235
+
236
+ }
237
+
238
+ if ( isInverse ) tempMatrix.invert();
239
+ matrix.multiply( tempMatrix );
240
+
241
+ }
242
+
243
+ } else if ( opName === 'xformOp:rotateXYZ' ) {
244
+
245
+ const r = data[ 'xformOp:rotateXYZ' ];
246
+ if ( r ) {
247
+
248
+ // USD rotateXYZ: matrix = Rx * Ry * Rz
249
+ // Three.js Euler 'ZYX' order produces same result
250
+ const euler = new Euler(
251
+ r[ 0 ] * Math.PI / 180,
252
+ r[ 1 ] * Math.PI / 180,
253
+ r[ 2 ] * Math.PI / 180,
254
+ 'ZYX'
255
+ );
256
+ tempMatrix.makeRotationFromEuler( euler );
257
+ if ( isInverse ) tempMatrix.invert();
258
+ matrix.multiply( tempMatrix );
259
+
260
+ }
261
+
262
+ } else if ( opName === 'xformOp:rotateX' ) {
263
+
264
+ const r = data[ 'xformOp:rotateX' ];
265
+ if ( r !== undefined ) {
266
+
267
+ tempMatrix.makeRotationX( r * Math.PI / 180 );
268
+ if ( isInverse ) tempMatrix.invert();
269
+ matrix.multiply( tempMatrix );
270
+
271
+ }
272
+
273
+ } else if ( opName === 'xformOp:rotateY' ) {
274
+
275
+ const r = data[ 'xformOp:rotateY' ];
276
+ if ( r !== undefined ) {
277
+
278
+ tempMatrix.makeRotationY( r * Math.PI / 180 );
279
+ if ( isInverse ) tempMatrix.invert();
280
+ matrix.multiply( tempMatrix );
281
+
282
+ }
283
+
284
+ } else if ( opName === 'xformOp:rotateZ' ) {
285
+
286
+ const r = data[ 'xformOp:rotateZ' ];
287
+ if ( r !== undefined ) {
288
+
289
+ tempMatrix.makeRotationZ( r * Math.PI / 180 );
290
+ if ( isInverse ) tempMatrix.invert();
291
+ matrix.multiply( tempMatrix );
292
+
293
+ }
294
+
295
+ } else if ( opName === 'xformOp:orient' ) {
296
+
297
+ const q = data[ 'xformOp:orient' ];
298
+ if ( q && q.length === 4 ) {
299
+
300
+ const quat = new Quaternion( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
301
+ tempMatrix.makeRotationFromQuaternion( quat );
302
+ if ( isInverse ) tempMatrix.invert();
303
+ matrix.multiply( tempMatrix );
304
+
305
+ }
306
+
307
+ }
308
+
309
+ }
310
+
311
+ obj.matrix.copy( matrix );
312
+ obj.matrix.decompose( obj.position, obj.quaternion, obj.scale );
313
+
314
+ // Fix for negative scale: decompose() may absorb negative scale into quaternion
315
+ // Restore original scale signs to keep animation consistent
316
+ if ( scaleValues ) {
317
+
318
+ const negX = scaleValues[ 0 ] < 0;
319
+ const negY = scaleValues[ 1 ] < 0;
320
+ const negZ = scaleValues[ 2 ] < 0;
321
+ const negCount = ( negX ? 1 : 0 ) + ( negY ? 1 : 0 ) + ( negZ ? 1 : 0 );
322
+
323
+ // decompose() absorbs pairs of negative scales into rotation
324
+ // For [-1,-1,-1] → [-1,1,1], Y and Z were absorbed, flip quat.y and quat.w
325
+ if ( negCount === 3 ) {
326
+
327
+ obj.scale.set( scaleValues[ 0 ], scaleValues[ 1 ], scaleValues[ 2 ] );
328
+ obj.quaternion.set(
329
+ obj.quaternion.x,
330
+ - obj.quaternion.y,
331
+ obj.quaternion.z,
332
+ - obj.quaternion.w
333
+ );
334
+
335
+ }
336
+
337
+ }
338
+
339
+ return;
340
+
341
+ }
342
+
343
+ // Fallback: handle individual transform ops without order
344
+ if ( data[ 'xformOp:translate' ] ) {
345
+
346
+ const t = data[ 'xformOp:translate' ];
347
+ obj.position.set( t[ 0 ], t[ 1 ], t[ 2 ] );
348
+
349
+ }
350
+
351
+ if ( data[ 'xformOp:translate:pivot' ] ) {
352
+
353
+ const p = data[ 'xformOp:translate:pivot' ];
354
+ obj.pivot = new Vector3( p[ 0 ], p[ 1 ], p[ 2 ] );
355
+
356
+ }
357
+
358
+ if ( data[ 'xformOp:scale' ] ) {
359
+
360
+ const s = data[ 'xformOp:scale' ];
361
+
362
+ if ( Array.isArray( s ) ) {
363
+
364
+ obj.scale.set( s[ 0 ], s[ 1 ], s[ 2 ] );
365
+
366
+ } else {
367
+
368
+ obj.scale.set( s, s, s );
369
+
370
+ }
371
+
372
+ }
373
+
374
+ if ( data[ 'xformOp:rotateXYZ' ] ) {
375
+
376
+ const r = data[ 'xformOp:rotateXYZ' ];
377
+ obj.rotation.set(
378
+ r[ 0 ] * Math.PI / 180,
379
+ r[ 1 ] * Math.PI / 180,
380
+ r[ 2 ] * Math.PI / 180
381
+ );
382
+
383
+ }
384
+
385
+ if ( data[ 'xformOp:orient' ] ) {
386
+
387
+ const q = data[ 'xformOp:orient' ];
388
+ if ( q.length === 4 ) {
389
+
390
+ obj.quaternion.set( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
391
+
392
+ }
393
+
394
+ }
395
+
396
+ }
397
+
398
+ /**
399
+ * Build indexes for efficient lookups.
400
+ * Called once during compose() to avoid O(n) scans per lookup.
401
+ */
402
+ _buildIndexes() {
403
+
404
+ // childrenByPath: parentPath -> [childName1, childName2, ...]
405
+ this.childrenByPath = new Map();
406
+
407
+ // attributesByPrimPath: primPath -> Map(attrName -> attrSpec)
408
+ this.attributesByPrimPath = new Map();
409
+
410
+ // materialsByRoot: rootPath -> [materialPath1, materialPath2, ...]
411
+ this.materialsByRoot = new Map();
412
+
413
+ // shadersByMaterialPath: materialPath -> [shaderPath1, shaderPath2, ...]
414
+ this.shadersByMaterialPath = new Map();
415
+
416
+ // geomSubsetsByMeshPath: meshPath -> [subsetPath1, subsetPath2, ...]
417
+ this.geomSubsetsByMeshPath = new Map();
418
+
419
+ for ( const path in this.specsByPath ) {
420
+
421
+ const spec = this.specsByPath[ path ];
422
+
423
+ if ( spec.specType === SpecType.Prim ) {
424
+
425
+ // Build parent-child index
426
+ const lastSlash = path.lastIndexOf( '/' );
427
+
428
+ if ( lastSlash > 0 ) {
429
+
430
+ const parentPath = path.slice( 0, lastSlash );
431
+ const childName = path.slice( lastSlash + 1 );
432
+
433
+ if ( ! this.childrenByPath.has( parentPath ) ) {
434
+
435
+ this.childrenByPath.set( parentPath, [] );
436
+
437
+ }
438
+
439
+ this.childrenByPath.get( parentPath ).push( { name: childName, path: path } );
440
+
441
+ } else if ( lastSlash === 0 && path.length > 1 ) {
442
+
443
+ // Direct child of root
444
+ const childName = path.slice( 1 );
445
+
446
+ if ( ! this.childrenByPath.has( '/' ) ) {
447
+
448
+ this.childrenByPath.set( '/', [] );
449
+
450
+ }
451
+
452
+ this.childrenByPath.get( '/' ).push( { name: childName, path: path } );
453
+
454
+ }
455
+
456
+ const typeName = spec.fields.typeName;
457
+
458
+ // Build material index
459
+ if ( typeName === 'Material' ) {
460
+
461
+ const parts = path.split( '/' );
462
+ const rootPath = parts.length > 1 ? '/' + parts[ 1 ] : '/';
463
+
464
+ if ( ! this.materialsByRoot.has( rootPath ) ) {
465
+
466
+ this.materialsByRoot.set( rootPath, [] );
467
+
468
+ }
469
+
470
+ this.materialsByRoot.get( rootPath ).push( path );
471
+
472
+ }
473
+
474
+ // Build shader index (shaders are children or descendants of materials)
475
+ if ( typeName === 'Shader' && lastSlash > 0 ) {
476
+
477
+ // Walk up ancestors to find the nearest Material prim.
478
+ // Shaders may be direct children of a Material, or nested
479
+ // inside a NodeGraph (common with MaterialX materials).
480
+
481
+ let ancestorPath = path.slice( 0, lastSlash );
482
+
483
+ while ( ancestorPath.length > 0 ) {
484
+
485
+ const ancestorSpec = this.specsByPath[ ancestorPath ];
486
+
487
+ if ( ancestorSpec && ancestorSpec.specType === SpecType.Prim && ancestorSpec.fields.typeName === 'Material' ) {
488
+
489
+ if ( ! this.shadersByMaterialPath.has( ancestorPath ) ) {
490
+
491
+ this.shadersByMaterialPath.set( ancestorPath, [] );
492
+
493
+ }
494
+
495
+ this.shadersByMaterialPath.get( ancestorPath ).push( path );
496
+ break;
497
+
498
+ }
499
+
500
+ const slash = ancestorPath.lastIndexOf( '/' );
501
+ if ( slash <= 0 ) break;
502
+ ancestorPath = ancestorPath.slice( 0, slash );
503
+
504
+ }
505
+
506
+ }
507
+
508
+ // Build GeomSubset index (subsets are children of meshes)
509
+ if ( typeName === 'GeomSubset' && lastSlash > 0 ) {
510
+
511
+ const meshPath = path.slice( 0, lastSlash );
512
+
513
+ if ( ! this.geomSubsetsByMeshPath.has( meshPath ) ) {
514
+
515
+ this.geomSubsetsByMeshPath.set( meshPath, [] );
516
+
517
+ }
518
+
519
+ this.geomSubsetsByMeshPath.get( meshPath ).push( path );
520
+
521
+ }
522
+
523
+ } else if ( spec.specType === SpecType.Attribute || spec.specType === SpecType.Relationship ) {
524
+
525
+ // Build attribute index
526
+ const dotIndex = path.lastIndexOf( '.' );
527
+
528
+ if ( dotIndex > 0 ) {
529
+
530
+ const primPath = path.slice( 0, dotIndex );
531
+ const attrName = path.slice( dotIndex + 1 );
532
+
533
+ if ( ! this.attributesByPrimPath.has( primPath ) ) {
534
+
535
+ this.attributesByPrimPath.set( primPath, new Map() );
536
+
537
+ }
538
+
539
+ this.attributesByPrimPath.get( primPath ).set( attrName, spec );
540
+
541
+ }
542
+
543
+ }
544
+
545
+ }
546
+
547
+ }
548
+
549
+ /**
550
+ * Check if a path is a direct child of parentPath.
551
+ */
552
+ _isDirectChild( parentPath, path, prefix ) {
553
+
554
+ if ( ! path.startsWith( prefix ) ) return false;
555
+
556
+ const remainder = path.slice( prefix.length );
557
+ if ( remainder.length === 0 ) return false;
558
+
559
+ // Check for variant paths or simple names
560
+ if ( remainder.startsWith( '{' ) ) {
561
+
562
+ return false; // Variant paths are not direct children
563
+
564
+ }
565
+
566
+ return ! remainder.includes( '/' );
567
+
568
+ }
569
+
570
+ /**
571
+ * Build the scene hierarchy recursively.
572
+ * Uses childrenByPath index for O(1) child lookup instead of O(n) iteration.
573
+ */
574
+ _buildHierarchy( parent, parentPath ) {
575
+
576
+ // Collect children from parentPath and any active variant paths
577
+ const childEntries = [];
578
+ const seenPaths = new Set();
579
+
580
+ // Get direct children using the index
581
+ const directChildren = this.childrenByPath.get( parentPath );
582
+
583
+ if ( directChildren ) {
584
+
585
+ for ( const child of directChildren ) {
586
+
587
+ if ( ! seenPaths.has( child.path ) ) {
588
+
589
+ seenPaths.add( child.path );
590
+ childEntries.push( child );
591
+
592
+ }
593
+
594
+ }
595
+
596
+ }
597
+
598
+ // Also get children from active variant paths
599
+ const variantPaths = this._getVariantPaths( parentPath );
600
+
601
+ for ( const vp of variantPaths ) {
602
+
603
+ const variantChildren = this.childrenByPath.get( vp );
604
+
605
+ if ( variantChildren ) {
606
+
607
+ for ( const child of variantChildren ) {
608
+
609
+ if ( ! seenPaths.has( child.path ) ) {
610
+
611
+ seenPaths.add( child.path );
612
+ childEntries.push( child );
613
+
614
+ }
615
+
616
+ }
617
+
618
+ }
619
+
620
+ }
621
+
622
+ // Process each child
623
+ for ( const { name, path } of childEntries ) {
624
+
625
+ const spec = this.specsByPath[ path ];
626
+ if ( ! spec || spec.specType !== SpecType.Prim ) continue;
627
+
628
+ const typeName = spec.fields.typeName;
629
+
630
+ // Check for references/payloads
631
+ const refValues = this._getReferences( spec );
632
+ if ( refValues.length > 0 ) {
633
+
634
+ // Get local variant selections from this prim
635
+ const localVariants = this._getLocalVariantSelections( spec.fields );
636
+
637
+ // Resolve all references
638
+ const resolvedGroups = [];
639
+ for ( const refValue of refValues ) {
640
+
641
+ const referencedGroup = this._resolveReference( refValue, localVariants );
642
+ if ( referencedGroup ) resolvedGroups.push( referencedGroup );
643
+
644
+ }
645
+
646
+ if ( resolvedGroups.length > 0 ) {
647
+
648
+ const attrs = this._getAttributes( path );
649
+
650
+ // Single reference with single mesh: use optimized path
651
+ // This handles the USDZExporter pattern: Xform references geometry file
652
+ if ( resolvedGroups.length === 1 ) {
653
+
654
+ const singleMesh = this._findSingleMesh( resolvedGroups[ 0 ] );
655
+
656
+ if ( singleMesh && ( typeName === 'Xform' || ! typeName ) ) {
657
+
658
+ // Merge the mesh into this prim
659
+ singleMesh.name = name;
660
+ this.applyTransform( singleMesh, spec.fields, attrs );
661
+
662
+ // Apply material binding from the referencing prim if present
663
+ this._applyMaterialBinding( singleMesh, path );
664
+
665
+ parent.add( singleMesh );
666
+
667
+ // Still build local children (overrides)
668
+ this._buildHierarchy( singleMesh, path );
669
+
670
+ continue;
671
+
672
+ }
673
+
674
+ }
675
+
676
+ // Create a container for the referenced content
677
+ const obj = new Object3D();
678
+ obj.name = name;
679
+ this.applyTransform( obj, spec.fields, attrs );
680
+
681
+ // Add all children from all resolved references
682
+ for ( const referencedGroup of resolvedGroups ) {
683
+
684
+ while ( referencedGroup.children.length > 0 ) {
685
+
686
+ obj.add( referencedGroup.children[ 0 ] );
687
+
688
+ }
689
+
690
+ }
691
+
692
+ parent.add( obj );
693
+
694
+ // Still build local children (overrides)
695
+ this._buildHierarchy( obj, path );
696
+
697
+ continue;
698
+
699
+ }
700
+
701
+ }
702
+
703
+ // Build appropriate object based on type
704
+ if ( typeName === 'SkelRoot' ) {
705
+
706
+ // Skeletal root - treat as transform but track for skeleton binding
707
+ const obj = new Object3D();
708
+ obj.name = name;
709
+ obj.userData.isSkelRoot = true;
710
+ const attrs = this._getAttributes( path );
711
+ this.applyTransform( obj, spec.fields, attrs );
712
+ parent.add( obj );
713
+ this._buildHierarchy( obj, path );
714
+
715
+ } else if ( typeName === 'Skeleton' ) {
716
+
717
+ // Build skeleton and store it
718
+ const skeleton = this._buildSkeleton( path );
719
+ if ( skeleton ) {
720
+
721
+ this.skeletons[ path ] = skeleton;
722
+
723
+ }
724
+
725
+ // Recursively build children (may contain SkelAnimation)
726
+ this._buildHierarchy( parent, path );
727
+
728
+ } else if ( typeName === 'SkelAnimation' ) {
729
+
730
+ // Skip - animations are processed separately in _buildAnimations
731
+
732
+ } else if ( typeName === 'Mesh' ) {
733
+
734
+ const obj = this._buildMesh( path, spec );
735
+ if ( obj ) {
736
+
737
+ parent.add( obj );
738
+ this._buildHierarchy( obj, path );
739
+
740
+ }
741
+
742
+ } else if ( typeName === 'Camera' ) {
743
+
744
+ const obj = this._buildCamera( path );
745
+ obj.name = name;
746
+ const attrs = this._getAttributes( path );
747
+ this.applyTransform( obj, spec.fields, attrs );
748
+ parent.add( obj );
749
+ this._buildHierarchy( obj, path );
750
+
751
+ } else if ( typeName === 'DistantLight' || typeName === 'SphereLight' || typeName === 'RectLight' || typeName === 'DiskLight' ) {
752
+
753
+ const obj = this._buildLight( path, typeName );
754
+ obj.name = name;
755
+ const attrs = this._getAttributes( path );
756
+ this.applyTransform( obj, spec.fields, attrs );
757
+ parent.add( obj );
758
+ this._buildHierarchy( obj, path );
759
+
760
+ } else if ( typeName === 'Cube' || typeName === 'Sphere' || typeName === 'Cylinder' || typeName === 'Cone' || typeName === 'Capsule' ) {
761
+
762
+ const obj = this._buildGeomPrimitive( path, spec, typeName );
763
+ if ( obj ) {
764
+
765
+ parent.add( obj );
766
+ this._buildHierarchy( obj, path );
767
+
768
+ }
769
+
770
+ } else if ( typeName === 'Material' || typeName === 'Shader' || typeName === 'GeomSubset' ) {
771
+
772
+ // Skip materials/shaders/subsets, they're referenced by meshes
773
+
774
+ } else {
775
+
776
+ // Transform node, group, or unknown type
777
+ const obj = new Object3D();
778
+ obj.name = name;
779
+ const attrs = this._getAttributes( path );
780
+ this.applyTransform( obj, spec.fields, attrs );
781
+ parent.add( obj );
782
+ this._buildHierarchy( obj, path );
783
+
784
+ }
785
+
786
+ }
787
+
788
+ }
789
+
790
+ /**
791
+ * Get variant paths for a parent path based on variant selections.
792
+ */
793
+ _getVariantPaths( parentPath ) {
794
+
795
+ const parentSpec = this.specsByPath[ parentPath ];
796
+ const variantSetChildren = parentSpec?.fields?.variantSetChildren;
797
+ const variantPaths = [];
798
+
799
+ if ( ! variantSetChildren || variantSetChildren.length === 0 ) {
800
+
801
+ return variantPaths;
802
+
803
+ }
804
+
805
+ for ( const variantSetName of variantSetChildren ) {
806
+
807
+ // External selections take priority
808
+ let selectedVariant = this.externalVariantSelections[ variantSetName ] || null;
809
+
810
+ // Fall back to file's internal selection
811
+ if ( ! selectedVariant ) {
812
+
813
+ const variantSelection = parentSpec.fields.variantSelection;
814
+ selectedVariant = variantSelection ? variantSelection[ variantSetName ] : null;
815
+
816
+ }
817
+
818
+ // Fall back to first variant child
819
+ if ( ! selectedVariant ) {
820
+
821
+ const variantSetPath = parentPath + '/{' + variantSetName + '=}';
822
+ const variantSetSpec = this.specsByPath[ variantSetPath ];
823
+ if ( variantSetSpec?.fields?.variantChildren ) {
824
+
825
+ selectedVariant = variantSetSpec.fields.variantChildren[ 0 ];
826
+
827
+ }
828
+
829
+ }
830
+
831
+ if ( selectedVariant ) {
832
+
833
+ const variantPath = parentPath + '/{' + variantSetName + '=' + selectedVariant + '}';
834
+ variantPaths.push( variantPath );
835
+
836
+ }
837
+
838
+ }
839
+
840
+ return variantPaths;
841
+
842
+ }
843
+
844
+ /**
845
+ * Resolve a file path relative to basePath.
846
+ */
847
+ _resolveFilePath( refPath ) {
848
+
849
+ let cleanPath = refPath;
850
+
851
+ // Remove ./ prefix
852
+ if ( cleanPath.startsWith( './' ) ) {
853
+
854
+ cleanPath = cleanPath.slice( 2 );
855
+
856
+ }
857
+
858
+ if ( ! this.basePath ) return cleanPath;
859
+
860
+ // LoaderUtils.resolveURL expects basePath to end with a separator;
861
+ // the USDZ flow passes the zip-internal directory name without one.
862
+ const base = this.basePath.endsWith( '/' ) ? this.basePath : this.basePath + '/';
863
+
864
+ return LoaderUtils.resolveURL( cleanPath, base );
865
+
866
+ }
867
+
868
+ /**
869
+ * Resolve a USD reference and return the composed content.
870
+ * @param {string} refValue - Reference value like "@./path/to/file.usdc@"
871
+ * @param {Object} localVariants - Variant selections to apply
872
+ * @returns {Group|null} Composed content or null
873
+ */
874
+ _resolveReference( refValue, localVariants = {} ) {
875
+
876
+ if ( ! refValue ) return null;
877
+
878
+ const match = refValue.match( /@([^@]+)@(?:<([^>]+)>)?/ );
879
+ if ( ! match ) return null;
880
+
881
+ const filePath = match[ 1 ];
882
+ const primPath = match[ 2 ]; // e.g., "/Geometry"
883
+
884
+ const resolvedPath = this._resolveFilePath( filePath );
885
+
886
+ // Merge variant selections - external takes priority, then local
887
+ const mergedVariants = { ...localVariants, ...this.externalVariantSelections };
888
+
889
+ // Look up pre-parsed data in assets
890
+ const referencedData = this.assets[ resolvedPath ];
891
+ if ( ! referencedData ) return null;
892
+
893
+ // If it's specsByPath data, compose it
894
+ if ( referencedData.specsByPath ) {
895
+
896
+ const composer = new USDComposer( this.manager );
897
+ const newBasePath = this._getBasePath( resolvedPath );
898
+ const composedGroup = composer.compose( referencedData, this.assets, mergedVariants, newBasePath );
899
+
900
+ // If a primPath is specified, find and return just that subtree
901
+ if ( primPath ) {
902
+
903
+ const primName = primPath.split( '/' ).pop();
904
+
905
+ // Find the direct child with this name (not a deep search)
906
+ // This is important because there may be multiple objects with the same name
907
+ let targetObject = null;
908
+ for ( const child of composedGroup.children ) {
909
+
910
+ if ( child.name === primName ) {
911
+
912
+ targetObject = child;
913
+ break;
914
+
915
+ }
916
+
917
+ }
918
+
919
+ if ( targetObject ) {
920
+
921
+ // Detach from parent for re-parenting
922
+ composedGroup.remove( targetObject );
923
+
924
+ // Wrap in a group to maintain consistent return type
925
+ const wrapper = new Group();
926
+ wrapper.add( targetObject );
927
+ return wrapper;
928
+
929
+ }
930
+
931
+ }
932
+
933
+ return composedGroup;
934
+
935
+ }
936
+
937
+ // If it's already a Three.js Group (legacy support), clone it
938
+ if ( referencedData.isGroup || referencedData.isObject3D ) {
939
+
940
+ return referencedData.clone();
941
+
942
+ }
943
+
944
+ return null;
945
+
946
+ }
947
+
948
+ /**
949
+ * Find a single mesh in the group's shallow hierarchy.
950
+ * Only returns a mesh if it's at depth 0 or 1, not deeply nested.
951
+ * This preserves transforms in complex hierarchies like Kitchen Set
952
+ * while supporting USDZExporter round-trip (Xform > Xform > Mesh pattern).
953
+ */
954
+ _findSingleMesh( group ) {
955
+
956
+ // Check direct children first
957
+ for ( const child of group.children ) {
958
+
959
+ if ( child.isMesh ) {
960
+
961
+ group.remove( child );
962
+ return child;
963
+
964
+ }
965
+
966
+ }
967
+
968
+ // Check grandchildren (USDZExporter pattern: Xform > Geometry > Mesh)
969
+ // Only if there's exactly one child with exactly one grandchild
970
+ if ( group.children.length === 1 ) {
971
+
972
+ const child = group.children[ 0 ];
973
+
974
+ if ( child.children && child.children.length === 1 ) {
975
+
976
+ const grandchild = child.children[ 0 ];
977
+
978
+ if ( grandchild.isMesh && ! this._hasNonIdentityTransform( child ) ) {
979
+
980
+ // Safe to merge - intermediate has identity transform
981
+ child.remove( grandchild );
982
+ return grandchild;
983
+
984
+ }
985
+
986
+ }
987
+
988
+ }
989
+
990
+ return null;
991
+
992
+ }
993
+
994
+ /**
995
+ * Check if an object has a non-identity local transform.
996
+ */
997
+ _hasNonIdentityTransform( obj ) {
998
+
999
+ const pos = obj.position;
1000
+ const rot = obj.rotation;
1001
+ const scale = obj.scale;
1002
+
1003
+ const hasPosition = pos.x !== 0 || pos.y !== 0 || pos.z !== 0;
1004
+ const hasRotation = rot.x !== 0 || rot.y !== 0 || rot.z !== 0;
1005
+ const hasScale = scale.x !== 1 || scale.y !== 1 || scale.z !== 1;
1006
+
1007
+ return hasPosition || hasRotation || hasScale;
1008
+
1009
+ }
1010
+
1011
+ /**
1012
+ * Get the base path (directory) from a file path.
1013
+ */
1014
+ _getBasePath( filePath ) {
1015
+
1016
+ const lastSlash = filePath.lastIndexOf( '/' );
1017
+ return lastSlash >= 0 ? filePath.slice( 0, lastSlash ) : '';
1018
+
1019
+ }
1020
+
1021
+ /**
1022
+ * Extract variant selections from a spec's fields.
1023
+ */
1024
+ _getLocalVariantSelections( fields ) {
1025
+
1026
+ const variants = {};
1027
+
1028
+ if ( fields.variantSelection ) {
1029
+
1030
+ for ( const key in fields.variantSelection ) {
1031
+
1032
+ variants[ key ] = fields.variantSelection[ key ];
1033
+
1034
+ }
1035
+
1036
+ }
1037
+
1038
+ return variants;
1039
+
1040
+ }
1041
+
1042
+ /**
1043
+ * Get all reference values from a prim spec.
1044
+ * @returns {string[]} Array of reference strings like "@path@" or "@path@<prim>"
1045
+ */
1046
+ _getReferences( spec ) {
1047
+
1048
+ const results = [];
1049
+
1050
+ if ( spec.fields.references && spec.fields.references.length > 0 ) {
1051
+
1052
+ const ref = spec.fields.references[ 0 ];
1053
+
1054
+ if ( typeof ref === 'string' ) {
1055
+
1056
+ // Extract all @...@ references (handles both single and array values)
1057
+ const matches = ref.matchAll( /@([^@]+)@(?:<([^>]+)>)?/g );
1058
+ for ( const match of matches ) {
1059
+
1060
+ results.push( match[ 0 ] );
1061
+
1062
+ }
1063
+
1064
+ } else if ( ref.assetPath ) {
1065
+
1066
+ results.push( '@' + ref.assetPath + '@' );
1067
+
1068
+ }
1069
+
1070
+ }
1071
+
1072
+ if ( results.length === 0 && spec.fields.payload ) {
1073
+
1074
+ const payload = spec.fields.payload;
1075
+ if ( typeof payload === 'string' ) results.push( payload );
1076
+ else if ( payload.assetPath ) results.push( '@' + payload.assetPath + '@' );
1077
+
1078
+ }
1079
+
1080
+ return results;
1081
+
1082
+ }
1083
+
1084
+ /**
1085
+ * Get attributes for a path from attribute specs.
1086
+ */
1087
+ _getAttributes( path ) {
1088
+
1089
+ const attrs = {};
1090
+
1091
+ this._collectAttributesFromPath( path, attrs );
1092
+
1093
+ // Collect overrides from sibling variants (when path is inside a variant)
1094
+ const variantMatch = path.match( VARIANT_PATH_REGEX );
1095
+ if ( variantMatch ) {
1096
+
1097
+ const basePath = variantMatch[ 1 ];
1098
+ const relativePath = variantMatch[ 4 ];
1099
+ const variantPaths = this._getVariantPaths( basePath );
1100
+
1101
+ for ( const vp of variantPaths ) {
1102
+
1103
+ if ( path.startsWith( vp ) ) continue;
1104
+
1105
+ const overridePath = vp + '/' + relativePath;
1106
+ this._collectAttributesFromPath( overridePath, attrs );
1107
+
1108
+ }
1109
+
1110
+ } else {
1111
+
1112
+ // Check for variant overrides at ancestor levels
1113
+ const parts = path.split( '/' );
1114
+ for ( let i = 1; i < parts.length - 1; i ++ ) {
1115
+
1116
+ const ancestorPath = parts.slice( 0, i + 1 ).join( '/' );
1117
+ const relativePath = parts.slice( i + 1 ).join( '/' );
1118
+ const variantPaths = this._getVariantPaths( ancestorPath );
1119
+
1120
+ for ( const vp of variantPaths ) {
1121
+
1122
+ const overridePath = vp + '/' + relativePath;
1123
+ this._collectAttributesFromPath( overridePath, attrs );
1124
+
1125
+ }
1126
+
1127
+ }
1128
+
1129
+ }
1130
+
1131
+ return attrs;
1132
+
1133
+ }
1134
+
1135
+ _collectAttributesFromPath( path, attrs ) {
1136
+
1137
+ // Use the attribute index for O(1) lookup instead of O(n) iteration
1138
+ const attrMap = this.attributesByPrimPath.get( path );
1139
+
1140
+ if ( ! attrMap ) return;
1141
+
1142
+ for ( const [ attrName, attrSpec ] of attrMap ) {
1143
+
1144
+ if ( attrSpec.fields?.default !== undefined ) {
1145
+
1146
+ attrs[ attrName ] = attrSpec.fields.default;
1147
+
1148
+ } else if ( attrSpec.fields?.timeSamples ) {
1149
+
1150
+ // For animated attributes without default, use the first time sample (rest pose)
1151
+ const { times, values } = attrSpec.fields.timeSamples;
1152
+ if ( times && values && times.length > 0 ) {
1153
+
1154
+ // Find time 0, or use the first available time
1155
+ const idx = times.indexOf( 0 );
1156
+ attrs[ attrName ] = idx >= 0 ? values[ idx ] : values[ 0 ];
1157
+
1158
+ }
1159
+
1160
+ }
1161
+
1162
+ if ( attrSpec.fields?.elementSize !== undefined ) {
1163
+
1164
+ attrs[ attrName + ':elementSize' ] = attrSpec.fields.elementSize;
1165
+
1166
+ }
1167
+
1168
+ if ( attrName.startsWith( 'primvars:' ) && attrSpec.fields?.typeName !== undefined ) {
1169
+
1170
+ attrs[ attrName + ':typeName' ] = attrSpec.fields.typeName;
1171
+
1172
+ }
1173
+
1174
+ }
1175
+
1176
+ }
1177
+
1178
+ /**
1179
+ * Build a mesh from a USD geometric primitive (Cube, Sphere, Cylinder, Cone, Capsule).
1180
+ */
1181
+ _buildGeomPrimitive( path, spec, typeName ) {
1182
+
1183
+ const attrs = this._getAttributes( path );
1184
+ const name = path.split( '/' ).pop();
1185
+
1186
+ let geometry;
1187
+
1188
+ switch ( typeName ) {
1189
+
1190
+ case 'Cube': {
1191
+
1192
+ const size = attrs[ 'size' ] || 2;
1193
+ geometry = new BoxGeometry( size, size, size );
1194
+ break;
1195
+
1196
+ }
1197
+
1198
+ case 'Sphere': {
1199
+
1200
+ const radius = attrs[ 'radius' ] || 1;
1201
+ geometry = new SphereGeometry( radius, 32, 16 );
1202
+ break;
1203
+
1204
+ }
1205
+
1206
+ case 'Cylinder': {
1207
+
1208
+ const height = attrs[ 'height' ] || 2;
1209
+ const radius = attrs[ 'radius' ] || 1;
1210
+ geometry = new CylinderGeometry( radius, radius, height, 32 );
1211
+ break;
1212
+
1213
+ }
1214
+
1215
+ case 'Cone': {
1216
+
1217
+ const height = attrs[ 'height' ] || 2;
1218
+ const radius = attrs[ 'radius' ] || 1;
1219
+ geometry = new ConeGeometry( radius, height, 32 );
1220
+ break;
1221
+
1222
+ }
1223
+
1224
+ case 'Capsule': {
1225
+
1226
+ const height = attrs[ 'height' ] || 1;
1227
+ const radius = attrs[ 'radius' ] || 0.5;
1228
+ geometry = new CapsuleGeometry( radius, height, 16, 32 );
1229
+ break;
1230
+
1231
+ }
1232
+
1233
+ }
1234
+
1235
+ // USD defaults axis to "Z", Three.js uses Y
1236
+ const axis = attrs[ 'axis' ] || 'Z';
1237
+
1238
+ if ( axis === 'X' ) {
1239
+
1240
+ geometry.rotateZ( - Math.PI / 2 );
1241
+
1242
+ } else if ( axis === 'Z' ) {
1243
+
1244
+ geometry.rotateX( Math.PI / 2 );
1245
+
1246
+ }
1247
+
1248
+ const material = this._buildMaterial( path, spec.fields );
1249
+ const mesh = new Mesh( geometry, material );
1250
+ mesh.name = name;
1251
+
1252
+ this.applyTransform( mesh, spec.fields, attrs );
1253
+
1254
+ return mesh;
1255
+
1256
+ }
1257
+
1258
+ /**
1259
+ * Build a mesh from a Mesh spec.
1260
+ */
1261
+ _buildMesh( path, spec ) {
1262
+
1263
+ const attrs = this._getAttributes( path );
1264
+
1265
+ // Check for skinning data
1266
+ const jointIndices = attrs[ 'primvars:skel:jointIndices' ];
1267
+ const jointWeights = attrs[ 'primvars:skel:jointWeights' ];
1268
+ const hasSkinning = jointIndices && jointWeights &&
1269
+ jointIndices.length > 0 && jointWeights.length > 0;
1270
+
1271
+ // Collect GeomSubsets for multi-material support
1272
+ const geomSubsets = this._getGeomSubsets( path );
1273
+
1274
+ let geometry, material;
1275
+
1276
+ if ( geomSubsets.length > 0 ) {
1277
+
1278
+ geometry = this._buildGeometryWithSubsets( attrs, geomSubsets, hasSkinning );
1279
+
1280
+ const meshMaterialPath = this._getMaterialPath( path, spec.fields );
1281
+
1282
+ material = geomSubsets.map( subset => {
1283
+
1284
+ const matPath = subset.materialPath || meshMaterialPath;
1285
+ return this._buildMaterialForPath( matPath );
1286
+
1287
+ } );
1288
+
1289
+ } else {
1290
+
1291
+ geometry = this._buildGeometry( path, attrs, hasSkinning );
1292
+ material = this._buildMaterial( path, spec.fields );
1293
+
1294
+ }
1295
+
1296
+ const displayColor = attrs[ 'primvars:displayColor' ];
1297
+ if ( displayColor && displayColor.length >= 3 ) {
1298
+
1299
+ const applyDisplayColor = ( mat ) => {
1300
+
1301
+ if ( mat.color && mat.color.r === 1 && mat.color.g === 1 && mat.color.b === 1 && ! mat.map ) {
1302
+
1303
+ mat.color.setRGB( displayColor[ 0 ], displayColor[ 1 ], displayColor[ 2 ], SRGBColorSpace );
1304
+
1305
+ }
1306
+
1307
+ };
1308
+
1309
+ if ( Array.isArray( material ) ) {
1310
+
1311
+ material.forEach( applyDisplayColor );
1312
+
1313
+ } else {
1314
+
1315
+ applyDisplayColor( material );
1316
+
1317
+ }
1318
+
1319
+ }
1320
+
1321
+ const displayOpacity = attrs[ 'primvars:displayOpacity' ];
1322
+ if ( displayOpacity && displayOpacity.length === 1 && geomSubsets.length === 0 ) {
1323
+
1324
+ const opacity = displayOpacity[ 0 ];
1325
+
1326
+ const applyDisplayOpacity = ( mat ) => {
1327
+
1328
+ if ( opacity < 1 && mat.opacity === 1 && mat.transparent === false ) {
1329
+
1330
+ mat.opacity = opacity;
1331
+ mat.transparent = true;
1332
+
1333
+ }
1334
+
1335
+ };
1336
+
1337
+ if ( Array.isArray( material ) ) {
1338
+
1339
+ material.forEach( applyDisplayOpacity );
1340
+
1341
+ } else {
1342
+
1343
+ applyDisplayOpacity( material );
1344
+
1345
+ }
1346
+
1347
+ }
1348
+
1349
+ let mesh;
1350
+
1351
+ if ( hasSkinning ) {
1352
+
1353
+ mesh = new SkinnedMesh( geometry, material );
1354
+
1355
+ // Find skeleton path from skel:skeleton relationship
1356
+ let skelBindingSpec = this.specsByPath[ path + '.skel:skeleton' ];
1357
+ if ( ! skelBindingSpec ) {
1358
+
1359
+ skelBindingSpec = this.specsByPath[ path + '.rel skel:skeleton' ];
1360
+
1361
+ }
1362
+
1363
+ let skeletonPath = null;
1364
+
1365
+ if ( skelBindingSpec ) {
1366
+
1367
+ if ( skelBindingSpec.fields.targetPaths && skelBindingSpec.fields.targetPaths.length > 0 ) {
1368
+
1369
+ skeletonPath = skelBindingSpec.fields.targetPaths[ 0 ];
1370
+
1371
+ } else if ( skelBindingSpec.fields.default ) {
1372
+
1373
+ skeletonPath = skelBindingSpec.fields.default.replace( /<|>/g, '' );
1374
+
1375
+ }
1376
+
1377
+ }
1378
+
1379
+ // Get per-mesh joint mapping
1380
+ const localJoints = attrs[ 'skel:joints' ];
1381
+
1382
+ // Get geomBindTransform if present
1383
+ const geomBindTransform = attrs[ 'primvars:skel:geomBindTransform' ];
1384
+
1385
+ this.skinnedMeshes.push( { mesh, skeletonPath, path, localJoints, geomBindTransform } );
1386
+
1387
+ } else {
1388
+
1389
+ mesh = new Mesh( geometry, material );
1390
+
1391
+ }
1392
+
1393
+ mesh.name = path.split( '/' ).pop();
1394
+ this.applyTransform( mesh, spec.fields, attrs );
1395
+
1396
+ return mesh;
1397
+
1398
+ }
1399
+
1400
+ /**
1401
+ * Build a camera from a Camera spec.
1402
+ */
1403
+ _buildCamera( path ) {
1404
+
1405
+ const attrs = this._getAttributes( path );
1406
+ const projectionToken = attrs[ 'projection' ];
1407
+ const projection = typeof projectionToken === 'string'
1408
+ ? projectionToken.toLowerCase()
1409
+ : USD_CAMERA_DEFAULTS.projection;
1410
+ const clippingRange = attrs[ 'clippingRange' ] || USD_CAMERA_DEFAULTS.clippingRange;
1411
+ const near = Math.max(
1412
+ Number.EPSILON,
1413
+ this._parseNumber( clippingRange[ 0 ], USD_CAMERA_DEFAULTS.clippingRange[ 0 ] )
1414
+ );
1415
+ const far = Math.max(
1416
+ near + Number.EPSILON,
1417
+ this._parseNumber( clippingRange[ 1 ], USD_CAMERA_DEFAULTS.clippingRange[ 1 ] )
1418
+ );
1419
+ const horizontalAperture = this._parseNumber(
1420
+ attrs[ 'horizontalAperture' ],
1421
+ USD_CAMERA_DEFAULTS.horizontalAperture
1422
+ );
1423
+ const verticalAperture = this._parseNumber(
1424
+ attrs[ 'verticalAperture' ],
1425
+ USD_CAMERA_DEFAULTS.verticalAperture
1426
+ );
1427
+ const horizontalApertureOffset = this._parseNumber(
1428
+ attrs[ 'horizontalApertureOffset' ],
1429
+ USD_CAMERA_DEFAULTS.horizontalApertureOffset
1430
+ );
1431
+ const verticalApertureOffset = this._parseNumber(
1432
+ attrs[ 'verticalApertureOffset' ],
1433
+ USD_CAMERA_DEFAULTS.verticalApertureOffset
1434
+ );
1435
+ const focalLength = this._parseNumber( attrs[ 'focalLength' ], USD_CAMERA_DEFAULTS.focalLength );
1436
+ const focusDistance = this._parseNumber( attrs[ 'focusDistance' ], USD_CAMERA_DEFAULTS.focusDistance );
1437
+ const fStop = this._parseNumber( attrs[ 'fStop' ], USD_CAMERA_DEFAULTS.fStop );
1438
+
1439
+ let camera;
1440
+
1441
+ if ( projection === 'orthographic' ) {
1442
+
1443
+ // USD orthographic apertures are in tenths of a world unit.
1444
+ const width = horizontalAperture / 10;
1445
+ const height = verticalAperture / 10;
1446
+ const offsetX = horizontalApertureOffset / 10;
1447
+ const offsetY = verticalApertureOffset / 10;
1448
+
1449
+ camera = new OrthographicCamera(
1450
+ offsetX - width * 0.5,
1451
+ offsetX + width * 0.5,
1452
+ offsetY + height * 0.5,
1453
+ offsetY - height * 0.5,
1454
+ near,
1455
+ far
1456
+ );
1457
+
1458
+ } else {
1459
+
1460
+ const safeVerticalAperture = Math.max( Number.EPSILON, verticalAperture );
1461
+ const safeFocalLength = Math.max( Number.EPSILON, focalLength );
1462
+ const aspect = horizontalAperture / safeVerticalAperture;
1463
+ const fov = 2 * Math.atan( safeVerticalAperture / ( 2 * safeFocalLength ) ) * 180 / Math.PI;
1464
+
1465
+ camera = new PerspectiveCamera( fov, aspect, near, far );
1466
+ camera.filmGauge = Math.max( horizontalAperture, verticalAperture );
1467
+ camera.filmOffset = horizontalApertureOffset;
1468
+ camera.focus = focusDistance;
1469
+ camera.setFocalLength( safeFocalLength );
1470
+
1471
+ if ( verticalApertureOffset !== 0 ) {
1472
+
1473
+ // Three.js supports only horizontal film offset directly.
1474
+ camera.userData.verticalApertureOffset = verticalApertureOffset;
1475
+
1476
+ }
1477
+
1478
+ }
1479
+
1480
+ camera.userData.fStop = fStop;
1481
+ camera.userData.usdProjection = projection;
1482
+ return camera;
1483
+
1484
+ }
1485
+
1486
+ /**
1487
+ * Build a light from a UsdLux light spec.
1488
+ */
1489
+ _buildLight( path, typeName ) {
1490
+
1491
+ const attrs = this._getAttributes( path );
1492
+
1493
+ const intensity = this._parseNumber( attrs[ 'inputs:intensity' ], 1 );
1494
+ const baseColor = attrs[ 'inputs:color' ] || [ 1, 1, 1 ];
1495
+ const enableColorTemperature = attrs[ 'inputs:enableColorTemperature' ] === true;
1496
+ const colorTemperature = this._parseNumber( attrs[ 'inputs:colorTemperature' ], 6500 );
1497
+
1498
+ const color = new Color( baseColor[ 0 ], baseColor[ 1 ], baseColor[ 2 ] );
1499
+
1500
+ if ( enableColorTemperature ) {
1501
+
1502
+ const temp = this._colorTemperature( colorTemperature );
1503
+ color.multiply( temp );
1504
+
1505
+ }
1506
+
1507
+ let light;
1508
+
1509
+ switch ( typeName ) {
1510
+
1511
+ case 'DistantLight':
1512
+ light = new DirectionalLight( color, intensity );
1513
+ break;
1514
+
1515
+ case 'SphereLight': {
1516
+
1517
+ const coneAngle = this._parseNumber( attrs[ 'shaping:cone:angle' ], 0 );
1518
+
1519
+ if ( coneAngle > 0 ) {
1520
+
1521
+ const angle = coneAngle * Math.PI / 180;
1522
+ const softness = this._parseNumber( attrs[ 'shaping:cone:softness' ], 0 );
1523
+ light = new SpotLight( color, intensity, 0, angle, softness );
1524
+
1525
+ } else {
1526
+
1527
+ light = new PointLight( color, intensity );
1528
+
1529
+ }
1530
+
1531
+ break;
1532
+
1533
+ }
1534
+
1535
+ case 'RectLight': {
1536
+
1537
+ const width = this._parseNumber( attrs[ 'inputs:width' ], 1 );
1538
+ const height = this._parseNumber( attrs[ 'inputs:height' ], 1 );
1539
+ light = new RectAreaLight( color, intensity, width, height );
1540
+ break;
1541
+
1542
+ }
1543
+
1544
+ case 'DiskLight': {
1545
+
1546
+ const radius = this._parseNumber( attrs[ 'inputs:radius' ], 0.5 );
1547
+ const side = radius * 2;
1548
+ light = new RectAreaLight( color, intensity, side, side );
1549
+ break;
1550
+
1551
+ }
1552
+
1553
+ }
1554
+
1555
+ return light;
1556
+
1557
+ }
1558
+
1559
+ /**
1560
+ * Convert a color temperature in Kelvin to an RGB Color.
1561
+ * Based on Tanner Helland's algorithm.
1562
+ */
1563
+ _colorTemperature( kelvin ) {
1564
+
1565
+ const temp = kelvin / 100;
1566
+ let r, g, b;
1567
+
1568
+ if ( temp <= 66 ) {
1569
+
1570
+ r = 1;
1571
+ g = 0.39008157876901960784 * Math.log( temp ) - 0.63184144378862745098;
1572
+
1573
+ } else {
1574
+
1575
+ r = 1.29293618606274509804 * Math.pow( temp - 60, - 0.1332047592 );
1576
+ g = 1.12989086089529411765 * Math.pow( temp - 60, - 0.0755148492 );
1577
+
1578
+ }
1579
+
1580
+ if ( temp >= 66 ) {
1581
+
1582
+ b = 1;
1583
+
1584
+ } else if ( temp <= 19 ) {
1585
+
1586
+ b = 0;
1587
+
1588
+ } else {
1589
+
1590
+ b = 0.54320678911019607843 * Math.log( temp - 10 ) - 1.19625408914;
1591
+
1592
+ }
1593
+
1594
+ return new Color(
1595
+ Math.min( Math.max( r, 0 ), 1 ),
1596
+ Math.min( Math.max( g, 0 ), 1 ),
1597
+ Math.min( Math.max( b, 0 ), 1 )
1598
+ );
1599
+
1600
+ }
1601
+
1602
+ _parseNumber( value, fallback ) {
1603
+
1604
+ const n = Number( value );
1605
+ return Number.isFinite( n ) ? n : fallback;
1606
+
1607
+ }
1608
+
1609
+ _getGeomSubsets( meshPath ) {
1610
+
1611
+ const subsets = [];
1612
+ const subsetPaths = this.geomSubsetsByMeshPath.get( meshPath );
1613
+ if ( ! subsetPaths ) return subsets;
1614
+
1615
+ for ( const p of subsetPaths ) {
1616
+
1617
+ const attrs = this._getAttributes( p );
1618
+ const indices = attrs[ 'indices' ];
1619
+ if ( ! indices || indices.length === 0 ) continue;
1620
+
1621
+ // Get material binding - check direct path and variant paths
1622
+ const materialPath = this._getMaterialBindingTarget( p );
1623
+
1624
+ subsets.push( {
1625
+ name: p.split( '/' ).pop(),
1626
+ indices: indices,
1627
+ materialPath: materialPath
1628
+ } );
1629
+
1630
+ }
1631
+
1632
+ return subsets;
1633
+
1634
+ }
1635
+
1636
+ /**
1637
+ * Get material binding target path, checking variant paths if needed.
1638
+ */
1639
+ _getMaterialBindingTarget( primPath ) {
1640
+
1641
+ const attrName = 'material:binding';
1642
+
1643
+ // First check direct path
1644
+ const directPath = primPath + '.' + attrName;
1645
+ const directSpec = this.specsByPath[ directPath ];
1646
+ if ( directSpec?.fields?.targetPaths?.length > 0 ) {
1647
+
1648
+ return directSpec.fields.targetPaths[ 0 ];
1649
+
1650
+ }
1651
+
1652
+ // Check variant paths at ancestor levels
1653
+ const parts = primPath.split( '/' );
1654
+ for ( let i = 1; i < parts.length; i ++ ) {
1655
+
1656
+ const ancestorPath = parts.slice( 0, i + 1 ).join( '/' );
1657
+ const relativePath = parts.slice( i + 1 ).join( '/' );
1658
+ const variantPaths = this._getVariantPaths( ancestorPath );
1659
+
1660
+ for ( const vp of variantPaths ) {
1661
+
1662
+ const overridePath = relativePath ? vp + '/' + relativePath + '.' + attrName : vp + '.' + attrName;
1663
+ const overrideSpec = this.specsByPath[ overridePath ];
1664
+
1665
+ if ( overrideSpec?.fields?.targetPaths?.length > 0 ) {
1666
+
1667
+ return overrideSpec.fields.targetPaths[ 0 ];
1668
+
1669
+ }
1670
+
1671
+ }
1672
+
1673
+ }
1674
+
1675
+ return null;
1676
+
1677
+ }
1678
+
1679
+ _buildGeometry( path, fields, hasSkinning = false ) {
1680
+
1681
+ const geometry = new BufferGeometry();
1682
+
1683
+ const points = fields[ 'points' ];
1684
+ if ( ! points || points.length === 0 ) return geometry;
1685
+
1686
+ const faceVertexIndices = fields[ 'faceVertexIndices' ];
1687
+ const faceVertexCounts = fields[ 'faceVertexCounts' ];
1688
+
1689
+ // Parse polygon holes (Arnold format: [holeFaceIdx, parentFaceIdx, ...])
1690
+ const polygonHoles = fields[ 'primvars:arnold:polygon_holes' ];
1691
+ const holeMap = this._buildHoleMap( polygonHoles );
1692
+
1693
+ // Compute triangulation pattern once using actual vertex positions
1694
+ // This pattern will be reused for normals, UVs, etc.
1695
+ let indices = faceVertexIndices;
1696
+ let triPattern = null;
1697
+
1698
+ if ( faceVertexCounts && faceVertexCounts.length > 0 ) {
1699
+
1700
+ const result = this._triangulateIndicesWithPattern( faceVertexIndices, faceVertexCounts, points, holeMap );
1701
+ indices = result.indices;
1702
+ triPattern = result.pattern;
1703
+
1704
+ }
1705
+
1706
+ let positions = points;
1707
+ if ( indices && indices.length > 0 ) {
1708
+
1709
+ positions = this._expandAttribute( points, indices, 3 );
1710
+
1711
+ }
1712
+
1713
+ geometry.setAttribute( 'position', new BufferAttribute( new Float32Array( positions ), 3 ) );
1714
+
1715
+ const normals = fields[ 'normals' ] || fields[ 'primvars:normals' ];
1716
+ const normalIndicesRaw = fields[ 'normals:indices' ] || fields[ 'primvars:normals:indices' ];
1717
+
1718
+ if ( normals && normals.length > 0 ) {
1719
+
1720
+ let normalData = normals;
1721
+
1722
+ if ( normalIndicesRaw && normalIndicesRaw.length > 0 && triPattern ) {
1723
+
1724
+ // Indexed normals - apply triangulation pattern to indices
1725
+ const triangulatedNormalIndices = this._applyTriangulationPattern( normalIndicesRaw, triPattern );
1726
+ normalData = this._expandAttribute( normals, triangulatedNormalIndices, 3 );
1727
+
1728
+ } else if ( normals.length === points.length ) {
1729
+
1730
+ // Per-vertex normals
1731
+ if ( indices && indices.length > 0 ) {
1732
+
1733
+ normalData = this._expandAttribute( normals, indices, 3 );
1734
+
1735
+ }
1736
+
1737
+ } else if ( triPattern ) {
1738
+
1739
+ // Per-face-vertex normals (no separate indices) - use same triangulation pattern
1740
+ const normalIndices = this._applyTriangulationPattern(
1741
+ Array.from( { length: normals.length / 3 }, ( _, i ) => i ),
1742
+ triPattern
1743
+ );
1744
+ normalData = this._expandAttribute( normals, normalIndices, 3 );
1745
+
1746
+ }
1747
+
1748
+ geometry.setAttribute( 'normal', new BufferAttribute( new Float32Array( normalData ), 3 ) );
1749
+
1750
+ } else {
1751
+
1752
+ // Compute vertex normals from the original indexed topology where
1753
+ // vertices are shared, then expand them like positions.
1754
+ const vertexNormals = this._computeVertexNormals( points, indices );
1755
+ geometry.setAttribute( 'normal', new BufferAttribute( new Float32Array(
1756
+ this._expandAttribute( vertexNormals, indices, 3 )
1757
+ ), 3 ) );
1758
+
1759
+ }
1760
+
1761
+ const { uvs, uvIndices } = this._findUVPrimvar( fields );
1762
+ const numFaceVertices = faceVertexIndices ? faceVertexIndices.length : 0;
1763
+
1764
+ if ( uvs && uvs.length > 0 ) {
1765
+
1766
+ let uvData = uvs;
1767
+
1768
+ if ( uvIndices && uvIndices.length > 0 && triPattern ) {
1769
+
1770
+ const triangulatedUvIndices = this._applyTriangulationPattern( uvIndices, triPattern );
1771
+ uvData = this._expandAttribute( uvs, triangulatedUvIndices, 2 );
1772
+
1773
+ } else if ( indices && uvs.length / 2 === points.length / 3 ) {
1774
+
1775
+ uvData = this._expandAttribute( uvs, indices, 2 );
1776
+
1777
+ } else if ( triPattern && uvs.length / 2 === numFaceVertices ) {
1778
+
1779
+ // Per-face-vertex UVs (faceVarying, no separate indices)
1780
+ const uvIndicesFromPattern = this._applyTriangulationPattern(
1781
+ Array.from( { length: numFaceVertices }, ( _, i ) => i ),
1782
+ triPattern
1783
+ );
1784
+ uvData = this._expandAttribute( uvs, uvIndicesFromPattern, 2 );
1785
+
1786
+ }
1787
+
1788
+ geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( uvData ), 2 ) );
1789
+
1790
+ }
1791
+
1792
+ // Second UV set (st1) for lightmaps/AO
1793
+ const { uvs2, uv2Indices } = this._findUV2Primvar( fields );
1794
+
1795
+ if ( uvs2 && uvs2.length > 0 ) {
1796
+
1797
+ let uv2Data = uvs2;
1798
+
1799
+ if ( uv2Indices && uv2Indices.length > 0 && triPattern ) {
1800
+
1801
+ const triangulatedUv2Indices = this._applyTriangulationPattern( uv2Indices, triPattern );
1802
+ uv2Data = this._expandAttribute( uvs2, triangulatedUv2Indices, 2 );
1803
+
1804
+ } else if ( indices && uvs2.length / 2 === points.length / 3 ) {
1805
+
1806
+ uv2Data = this._expandAttribute( uvs2, indices, 2 );
1807
+
1808
+ } else if ( triPattern && uvs2.length / 2 === numFaceVertices ) {
1809
+
1810
+ // Per-face-vertex UV2 (faceVarying, no separate indices)
1811
+ const uv2IndicesFromPattern = this._applyTriangulationPattern(
1812
+ Array.from( { length: numFaceVertices }, ( _, i ) => i ),
1813
+ triPattern
1814
+ );
1815
+ uv2Data = this._expandAttribute( uvs2, uv2IndicesFromPattern, 2 );
1816
+
1817
+ }
1818
+
1819
+ geometry.setAttribute( 'uv1', new BufferAttribute( new Float32Array( uv2Data ), 2 ) );
1820
+
1821
+ }
1822
+
1823
+ // Add skinning attributes
1824
+ if ( hasSkinning ) {
1825
+
1826
+ const jointIndices = fields[ 'primvars:skel:jointIndices' ];
1827
+ const jointWeights = fields[ 'primvars:skel:jointWeights' ];
1828
+ const elementSize = fields[ 'primvars:skel:jointIndices:elementSize' ] || 4;
1829
+
1830
+ if ( jointIndices && jointWeights ) {
1831
+
1832
+ const numVertices = positions.length / 3;
1833
+
1834
+ let skinIndexData, skinWeightData;
1835
+
1836
+ if ( indices && indices.length > 0 ) {
1837
+
1838
+ skinIndexData = this._expandAttribute( jointIndices, indices, elementSize );
1839
+ skinWeightData = this._expandAttribute( jointWeights, indices, elementSize );
1840
+
1841
+ } else {
1842
+
1843
+ skinIndexData = jointIndices;
1844
+ skinWeightData = jointWeights;
1845
+
1846
+ }
1847
+
1848
+ const skinIndices = new Uint16Array( numVertices * 4 );
1849
+ const skinWeights = new Float32Array( numVertices * 4 );
1850
+
1851
+ this._selectTopWeights( skinIndexData, skinWeightData, elementSize, numVertices, skinIndices, skinWeights );
1852
+
1853
+ geometry.setAttribute( 'skinIndex', new BufferAttribute( skinIndices, 4 ) );
1854
+ geometry.setAttribute( 'skinWeight', new BufferAttribute( skinWeights, 4 ) );
1855
+
1856
+ }
1857
+
1858
+ }
1859
+
1860
+ return geometry;
1861
+
1862
+ }
1863
+
1864
+ _buildGeometryWithSubsets( fields, geomSubsets, hasSkinning = false ) {
1865
+
1866
+ const geometry = new BufferGeometry();
1867
+
1868
+ const points = fields[ 'points' ];
1869
+ if ( ! points || points.length === 0 ) return geometry;
1870
+
1871
+ const faceVertexIndices = fields[ 'faceVertexIndices' ];
1872
+ const faceVertexCounts = fields[ 'faceVertexCounts' ];
1873
+
1874
+ if ( ! faceVertexCounts || faceVertexCounts.length === 0 ) return geometry;
1875
+
1876
+ const polygonHoles = fields[ 'primvars:arnold:polygon_holes' ];
1877
+ const holeMap = this._buildHoleMap( polygonHoles );
1878
+ const holeFaces = holeMap.holeFaces;
1879
+ const parentToHoles = holeMap.parentToHoles;
1880
+
1881
+ const { uvs, uvIndices } = this._findUVPrimvar( fields );
1882
+ const { uvs2, uv2Indices } = this._findUV2Primvar( fields );
1883
+ const normals = fields[ 'normals' ] || fields[ 'primvars:normals' ];
1884
+ const normalIndicesRaw = fields[ 'normals:indices' ] || fields[ 'primvars:normals:indices' ];
1885
+
1886
+ const jointIndices = hasSkinning ? fields[ 'primvars:skel:jointIndices' ] : null;
1887
+ const jointWeights = hasSkinning ? fields[ 'primvars:skel:jointWeights' ] : null;
1888
+ const elementSize = fields[ 'primvars:skel:jointIndices:elementSize' ] || 4;
1889
+
1890
+ // Build face-to-triangle mapping (accounting for holes)
1891
+ const faceTriangleOffset = [];
1892
+ let triangleCount = 0;
1893
+
1894
+ for ( let i = 0; i < faceVertexCounts.length; i ++ ) {
1895
+
1896
+ faceTriangleOffset.push( triangleCount );
1897
+
1898
+ // Skip hole faces - they're triangulated with their parent
1899
+ if ( holeFaces.has( i ) ) continue;
1900
+
1901
+ const count = faceVertexCounts[ i ];
1902
+ const holes = parentToHoles.get( i );
1903
+
1904
+ if ( holes && holes.length > 0 ) {
1905
+
1906
+ // For faces with holes, count triangles based on total vertices
1907
+ // Earcut produces (total_vertices - 2) triangles for any polygon including holes
1908
+ let totalVerts = count;
1909
+ for ( const holeIdx of holes ) {
1910
+
1911
+ totalVerts += faceVertexCounts[ holeIdx ];
1912
+
1913
+ }
1914
+
1915
+ triangleCount += totalVerts - 2;
1916
+
1917
+ } else if ( count >= 3 ) {
1918
+
1919
+ triangleCount += count - 2;
1920
+
1921
+ }
1922
+
1923
+ }
1924
+
1925
+ const triangleToSubset = new Int32Array( triangleCount ).fill( - 1 );
1926
+
1927
+ for ( let si = 0; si < geomSubsets.length; si ++ ) {
1928
+
1929
+ const subset = geomSubsets[ si ];
1930
+
1931
+ for ( let i = 0; i < subset.indices.length; i ++ ) {
1932
+
1933
+ const faceIdx = subset.indices[ i ];
1934
+ if ( faceIdx >= faceVertexCounts.length ) continue;
1935
+
1936
+ const triStart = faceTriangleOffset[ faceIdx ];
1937
+ const triCount = faceVertexCounts[ faceIdx ] - 2;
1938
+
1939
+ for ( let t = 0; t < triCount; t ++ ) {
1940
+
1941
+ triangleToSubset[ triStart + t ] = si;
1942
+
1943
+ }
1944
+
1945
+ }
1946
+
1947
+ }
1948
+
1949
+ // Sort triangles by subset
1950
+ const sortedTriangles = [];
1951
+
1952
+ for ( let tri = 0; tri < triangleCount; tri ++ ) {
1953
+
1954
+ sortedTriangles.push( { original: tri, subset: triangleToSubset[ tri ] } );
1955
+
1956
+ }
1957
+
1958
+ sortedTriangles.sort( ( a, b ) => a.subset - b.subset );
1959
+
1960
+ const groups = [];
1961
+ let currentSubset = sortedTriangles.length > 0 ? sortedTriangles[ 0 ].subset : - 1;
1962
+ let groupStart = 0;
1963
+
1964
+ for ( let i = 0; i < sortedTriangles.length; i ++ ) {
1965
+
1966
+ if ( sortedTriangles[ i ].subset !== currentSubset ) {
1967
+
1968
+ if ( currentSubset >= 0 ) {
1969
+
1970
+ groups.push( {
1971
+ start: groupStart * 3,
1972
+ count: ( i - groupStart ) * 3,
1973
+ materialIndex: currentSubset
1974
+ } );
1975
+
1976
+ }
1977
+
1978
+ currentSubset = sortedTriangles[ i ].subset;
1979
+ groupStart = i;
1980
+
1981
+ }
1982
+
1983
+ }
1984
+
1985
+ if ( currentSubset >= 0 && sortedTriangles.length > groupStart ) {
1986
+
1987
+ groups.push( {
1988
+ start: groupStart * 3,
1989
+ count: ( sortedTriangles.length - groupStart ) * 3,
1990
+ materialIndex: currentSubset
1991
+ } );
1992
+
1993
+ }
1994
+
1995
+ for ( const group of groups ) {
1996
+
1997
+ geometry.addGroup( group.start, group.count, group.materialIndex );
1998
+
1999
+ }
2000
+
2001
+ // Triangulate original data using consistent pattern
2002
+ const { indices: origIndices, pattern: triPattern } = this._triangulateIndicesWithPattern( faceVertexIndices, faceVertexCounts, points, holeMap );
2003
+ const numFaceVertices = faceVertexCounts.reduce( ( a, b ) => a + b, 0 );
2004
+ const faceVaryingIdentity = ( uvs && ! uvIndices && uvs.length / 2 === numFaceVertices ) ||
2005
+ ( uvs2 && ! uv2Indices && uvs2.length / 2 === numFaceVertices )
2006
+ ? this._applyTriangulationPattern( Array.from( { length: numFaceVertices }, ( _, i ) => i ), triPattern )
2007
+ : null;
2008
+ const origUvIndices = uvIndices
2009
+ ? this._applyTriangulationPattern( uvIndices, triPattern )
2010
+ : ( uvs && uvs.length / 2 === numFaceVertices ? faceVaryingIdentity : null );
2011
+ const origUv2Indices = uv2Indices
2012
+ ? this._applyTriangulationPattern( uv2Indices, triPattern )
2013
+ : ( uvs2 && uvs2.length / 2 === numFaceVertices ? faceVaryingIdentity : null );
2014
+ const hasIndexedNormals = normals && normalIndicesRaw && normalIndicesRaw.length > 0;
2015
+ const hasFaceVaryingNormals = normals && normals.length / 3 === numFaceVertices;
2016
+ const origNormalIndices = hasIndexedNormals
2017
+ ? this._applyTriangulationPattern( normalIndicesRaw, triPattern )
2018
+ : ( hasFaceVaryingNormals
2019
+ ? this._applyTriangulationPattern( Array.from( { length: numFaceVertices }, ( _, i ) => i ), triPattern )
2020
+ : null );
2021
+
2022
+ // When no normals are provided, compute vertex normals from
2023
+ // the indexed topology so that shared vertices produce averaged normals.
2024
+ const vertexNormals = ( ! normals && origIndices.length > 0 )
2025
+ ? this._computeVertexNormals( points, origIndices )
2026
+ : null;
2027
+
2028
+ // Build reordered vertex data
2029
+ const vertexCount = triangleCount * 3;
2030
+ const positions = new Float32Array( vertexCount * 3 );
2031
+ const uvData = uvs ? new Float32Array( vertexCount * 2 ) : null;
2032
+ const uv1Data = uvs2 ? new Float32Array( vertexCount * 2 ) : null;
2033
+ const normalData = ( normals || vertexNormals ) ? new Float32Array( vertexCount * 3 ) : null;
2034
+ const skinSrcIndices = jointIndices ? new Uint16Array( vertexCount * elementSize ) : null;
2035
+ const skinSrcWeights = jointWeights ? new Float32Array( vertexCount * elementSize ) : null;
2036
+
2037
+ for ( let i = 0; i < sortedTriangles.length; i ++ ) {
2038
+
2039
+ const origTri = sortedTriangles[ i ].original;
2040
+
2041
+ for ( let v = 0; v < 3; v ++ ) {
2042
+
2043
+ const origIdx = origTri * 3 + v;
2044
+ const newIdx = i * 3 + v;
2045
+
2046
+ const pointIdx = origIndices[ origIdx ];
2047
+ positions[ newIdx * 3 ] = points[ pointIdx * 3 ];
2048
+ positions[ newIdx * 3 + 1 ] = points[ pointIdx * 3 + 1 ];
2049
+ positions[ newIdx * 3 + 2 ] = points[ pointIdx * 3 + 2 ];
2050
+
2051
+ if ( uvData && uvs ) {
2052
+
2053
+ if ( origUvIndices ) {
2054
+
2055
+ const uvIdx = origUvIndices[ origIdx ];
2056
+ uvData[ newIdx * 2 ] = uvs[ uvIdx * 2 ];
2057
+ uvData[ newIdx * 2 + 1 ] = uvs[ uvIdx * 2 + 1 ];
2058
+
2059
+ } else if ( uvs.length / 2 === points.length / 3 ) {
2060
+
2061
+ uvData[ newIdx * 2 ] = uvs[ pointIdx * 2 ];
2062
+ uvData[ newIdx * 2 + 1 ] = uvs[ pointIdx * 2 + 1 ];
2063
+
2064
+ }
2065
+
2066
+ }
2067
+
2068
+ if ( uv1Data && uvs2 ) {
2069
+
2070
+ if ( origUv2Indices ) {
2071
+
2072
+ const uv2Idx = origUv2Indices[ origIdx ];
2073
+ uv1Data[ newIdx * 2 ] = uvs2[ uv2Idx * 2 ];
2074
+ uv1Data[ newIdx * 2 + 1 ] = uvs2[ uv2Idx * 2 + 1 ];
2075
+
2076
+ } else if ( uvs2.length / 2 === points.length / 3 ) {
2077
+
2078
+ uv1Data[ newIdx * 2 ] = uvs2[ pointIdx * 2 ];
2079
+ uv1Data[ newIdx * 2 + 1 ] = uvs2[ pointIdx * 2 + 1 ];
2080
+
2081
+ }
2082
+
2083
+ }
2084
+
2085
+ if ( normalData ) {
2086
+
2087
+ if ( normals && origNormalIndices ) {
2088
+
2089
+ const normalIdx = origNormalIndices[ origIdx ];
2090
+ normalData[ newIdx * 3 ] = normals[ normalIdx * 3 ];
2091
+ normalData[ newIdx * 3 + 1 ] = normals[ normalIdx * 3 + 1 ];
2092
+ normalData[ newIdx * 3 + 2 ] = normals[ normalIdx * 3 + 2 ];
2093
+
2094
+ } else if ( normals && normals.length === points.length ) {
2095
+
2096
+ normalData[ newIdx * 3 ] = normals[ pointIdx * 3 ];
2097
+ normalData[ newIdx * 3 + 1 ] = normals[ pointIdx * 3 + 1 ];
2098
+ normalData[ newIdx * 3 + 2 ] = normals[ pointIdx * 3 + 2 ];
2099
+
2100
+ } else if ( vertexNormals ) {
2101
+
2102
+ normalData[ newIdx * 3 ] = vertexNormals[ pointIdx * 3 ];
2103
+ normalData[ newIdx * 3 + 1 ] = vertexNormals[ pointIdx * 3 + 1 ];
2104
+ normalData[ newIdx * 3 + 2 ] = vertexNormals[ pointIdx * 3 + 2 ];
2105
+
2106
+ }
2107
+
2108
+ }
2109
+
2110
+ if ( skinSrcIndices && skinSrcWeights && jointIndices && jointWeights ) {
2111
+
2112
+ for ( let j = 0; j < elementSize; j ++ ) {
2113
+
2114
+ skinSrcIndices[ newIdx * elementSize + j ] = jointIndices[ pointIdx * elementSize + j ] || 0;
2115
+ skinSrcWeights[ newIdx * elementSize + j ] = jointWeights[ pointIdx * elementSize + j ] || 0;
2116
+
2117
+ }
2118
+
2119
+ }
2120
+
2121
+ }
2122
+
2123
+ }
2124
+
2125
+ geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
2126
+
2127
+ if ( uvData ) {
2128
+
2129
+ geometry.setAttribute( 'uv', new BufferAttribute( uvData, 2 ) );
2130
+
2131
+ }
2132
+
2133
+ if ( uv1Data ) {
2134
+
2135
+ geometry.setAttribute( 'uv1', new BufferAttribute( uv1Data, 2 ) );
2136
+
2137
+ }
2138
+
2139
+ geometry.setAttribute( 'normal', new BufferAttribute( normalData, 3 ) );
2140
+
2141
+ if ( skinSrcIndices && skinSrcWeights ) {
2142
+
2143
+ const skinIndexData = new Uint16Array( vertexCount * 4 );
2144
+ const skinWeightData = new Float32Array( vertexCount * 4 );
2145
+
2146
+ this._selectTopWeights( skinSrcIndices, skinSrcWeights, elementSize, vertexCount, skinIndexData, skinWeightData );
2147
+
2148
+ geometry.setAttribute( 'skinIndex', new BufferAttribute( skinIndexData, 4 ) );
2149
+ geometry.setAttribute( 'skinWeight', new BufferAttribute( skinWeightData, 4 ) );
2150
+
2151
+ }
2152
+
2153
+ return geometry;
2154
+
2155
+ }
2156
+
2157
+ _selectTopWeights( srcIndices, srcWeights, elementSize, numVertices, dstIndices, dstWeights ) {
2158
+
2159
+ if ( elementSize <= 4 ) {
2160
+
2161
+ for ( let i = 0; i < numVertices; i ++ ) {
2162
+
2163
+ for ( let j = 0; j < 4; j ++ ) {
2164
+
2165
+ if ( j < elementSize ) {
2166
+
2167
+ dstIndices[ i * 4 + j ] = srcIndices[ i * elementSize + j ] || 0;
2168
+ dstWeights[ i * 4 + j ] = srcWeights[ i * elementSize + j ] || 0;
2169
+
2170
+ } else {
2171
+
2172
+ dstIndices[ i * 4 + j ] = 0;
2173
+ dstWeights[ i * 4 + j ] = 0;
2174
+
2175
+ }
2176
+
2177
+ }
2178
+
2179
+ }
2180
+
2181
+ return;
2182
+
2183
+ }
2184
+
2185
+ // When elementSize > 4, find the 4 largest weights per vertex
2186
+ // using a partial selection sort (4 iterations of O(elementSize)).
2187
+ const order = new Uint32Array( elementSize );
2188
+
2189
+ for ( let i = 0; i < numVertices; i ++ ) {
2190
+
2191
+ const base = i * elementSize;
2192
+
2193
+ for ( let j = 0; j < elementSize; j ++ ) order[ j ] = j;
2194
+
2195
+ for ( let k = 0; k < 4; k ++ ) {
2196
+
2197
+ let maxIdx = k;
2198
+ let maxW = srcWeights[ base + order[ k ] ] || 0;
2199
+
2200
+ for ( let j = k + 1; j < elementSize; j ++ ) {
2201
+
2202
+ const w = srcWeights[ base + order[ j ] ] || 0;
2203
+
2204
+ if ( w > maxW ) {
2205
+
2206
+ maxW = w;
2207
+ maxIdx = j;
2208
+
2209
+ }
2210
+
2211
+ }
2212
+
2213
+ if ( maxIdx !== k ) {
2214
+
2215
+ const tmp = order[ k ];
2216
+ order[ k ] = order[ maxIdx ];
2217
+ order[ maxIdx ] = tmp;
2218
+
2219
+ }
2220
+
2221
+ }
2222
+
2223
+ let total = 0;
2224
+
2225
+ for ( let j = 0; j < 4; j ++ ) {
2226
+
2227
+ total += srcWeights[ base + order[ j ] ] || 0;
2228
+
2229
+ }
2230
+
2231
+ for ( let j = 0; j < 4; j ++ ) {
2232
+
2233
+ const s = order[ j ];
2234
+
2235
+ if ( total > 0 ) {
2236
+
2237
+ dstIndices[ i * 4 + j ] = srcIndices[ base + s ] || 0;
2238
+ dstWeights[ i * 4 + j ] = ( srcWeights[ base + s ] || 0 ) / total;
2239
+
2240
+ } else {
2241
+
2242
+ dstIndices[ i * 4 + j ] = 0;
2243
+ dstWeights[ i * 4 + j ] = 0;
2244
+
2245
+ }
2246
+
2247
+ }
2248
+
2249
+ }
2250
+
2251
+ }
2252
+
2253
+ _findUVPrimvar( fields ) {
2254
+
2255
+ for ( const key in fields ) {
2256
+
2257
+ if ( ! key.startsWith( 'primvars:' ) ) continue;
2258
+ if ( key.endsWith( ':typeName' ) || key.endsWith( ':elementSize' ) || key.endsWith( ':indices' ) ) continue;
2259
+ if ( key.includes( 'skel:' ) ) continue;
2260
+
2261
+ const typeName = fields[ key + ':typeName' ];
2262
+ if ( typeName && typeName.includes( 'texCoord' ) ) {
2263
+
2264
+ return {
2265
+ uvs: fields[ key ],
2266
+ uvIndices: fields[ key + ':indices' ]
2267
+ };
2268
+
2269
+ }
2270
+
2271
+ }
2272
+
2273
+ const uvs = fields[ 'primvars:st' ] || fields[ 'primvars:UVMap' ];
2274
+ const uvIndices = fields[ 'primvars:st:indices' ];
2275
+ return { uvs, uvIndices };
2276
+
2277
+ }
2278
+
2279
+ _findUV2Primvar( fields ) {
2280
+
2281
+ const uvs2 = fields[ 'primvars:st1' ];
2282
+ const uv2Indices = fields[ 'primvars:st1:indices' ];
2283
+ return { uvs2, uv2Indices };
2284
+
2285
+ }
2286
+
2287
+ _buildHoleMap( polygonHoles ) {
2288
+
2289
+ // polygonHoles is in Arnold format: [holeFaceIdx, parentFaceIdx, holeFaceIdx, parentFaceIdx, ...]
2290
+ // Returns a map: parentFaceIdx -> [holeFaceIdx1, holeFaceIdx2, ...]
2291
+ // Also returns a set of hole face indices to skip during triangulation
2292
+ if ( ! polygonHoles || polygonHoles.length === 0 ) {
2293
+
2294
+ return { parentToHoles: new Map(), holeFaces: new Set() };
2295
+
2296
+ }
2297
+
2298
+ const parentToHoles = new Map();
2299
+ const holeFaces = new Set();
2300
+
2301
+ for ( let i = 0; i < polygonHoles.length; i += 2 ) {
2302
+
2303
+ const holeFaceIdx = polygonHoles[ i ];
2304
+ const parentFaceIdx = polygonHoles[ i + 1 ];
2305
+
2306
+ holeFaces.add( holeFaceIdx );
2307
+
2308
+ if ( ! parentToHoles.has( parentFaceIdx ) ) {
2309
+
2310
+ parentToHoles.set( parentFaceIdx, [] );
2311
+
2312
+ }
2313
+
2314
+ parentToHoles.get( parentFaceIdx ).push( holeFaceIdx );
2315
+
2316
+ }
2317
+
2318
+ return { parentToHoles, holeFaces };
2319
+
2320
+ }
2321
+
2322
+ _triangulateIndicesWithPattern( indices, counts, points = null, holeMap = null ) {
2323
+
2324
+ const triangulated = [];
2325
+ const pattern = []; // Stores face-local indices for each triangle vertex
2326
+
2327
+ // Build face offset lookup for accessing hole face data
2328
+ const faceOffsets = [];
2329
+ let offsetAccum = 0;
2330
+ for ( let i = 0; i < counts.length; i ++ ) {
2331
+
2332
+ faceOffsets.push( offsetAccum );
2333
+ offsetAccum += counts[ i ];
2334
+
2335
+ }
2336
+
2337
+ const parentToHoles = holeMap?.parentToHoles || new Map();
2338
+ const holeFaces = holeMap?.holeFaces || new Set();
2339
+
2340
+ let offset = 0;
2341
+
2342
+ for ( let i = 0; i < counts.length; i ++ ) {
2343
+
2344
+ const count = counts[ i ];
2345
+
2346
+ // Skip faces that are holes - they will be triangulated with their parent
2347
+ if ( holeFaces.has( i ) ) {
2348
+
2349
+ offset += count;
2350
+ continue;
2351
+
2352
+ }
2353
+
2354
+ // Check if this face has holes
2355
+ const holes = parentToHoles.get( i );
2356
+
2357
+ if ( holes && holes.length > 0 && points && points.length > 0 ) {
2358
+
2359
+ // Triangulate face with holes using vertex -> face-vertex mapping
2360
+ const vertexToFaceVertex = new Map();
2361
+
2362
+ const faceIndices = [];
2363
+ for ( let j = 0; j < count; j ++ ) {
2364
+
2365
+ const vertIdx = indices[ offset + j ];
2366
+ faceIndices.push( vertIdx );
2367
+ vertexToFaceVertex.set( vertIdx, offset + j );
2368
+
2369
+ }
2370
+
2371
+ const holeContours = [];
2372
+ for ( const holeFaceIdx of holes ) {
2373
+
2374
+ const holeOffset = faceOffsets[ holeFaceIdx ];
2375
+ const holeCount = counts[ holeFaceIdx ];
2376
+ const holeIndices = [];
2377
+ for ( let j = 0; j < holeCount; j ++ ) {
2378
+
2379
+ const vertIdx = indices[ holeOffset + j ];
2380
+ holeIndices.push( vertIdx );
2381
+ vertexToFaceVertex.set( vertIdx, holeOffset + j );
2382
+
2383
+ }
2384
+
2385
+ holeContours.push( holeIndices );
2386
+
2387
+ }
2388
+
2389
+ const triangles = this._triangulateNGonWithHoles( faceIndices, holeContours, points );
2390
+
2391
+ for ( const tri of triangles ) {
2392
+
2393
+ triangulated.push( tri[ 0 ], tri[ 1 ], tri[ 2 ] );
2394
+ pattern.push(
2395
+ vertexToFaceVertex.get( tri[ 0 ] ),
2396
+ vertexToFaceVertex.get( tri[ 1 ] ),
2397
+ vertexToFaceVertex.get( tri[ 2 ] )
2398
+ );
2399
+
2400
+ }
2401
+
2402
+ } else if ( count === 3 ) {
2403
+
2404
+ triangulated.push(
2405
+ indices[ offset ],
2406
+ indices[ offset + 1 ],
2407
+ indices[ offset + 2 ]
2408
+ );
2409
+ pattern.push( offset, offset + 1, offset + 2 );
2410
+
2411
+ } else if ( count === 4 ) {
2412
+
2413
+ triangulated.push(
2414
+ indices[ offset ],
2415
+ indices[ offset + 1 ],
2416
+ indices[ offset + 2 ],
2417
+ indices[ offset ],
2418
+ indices[ offset + 2 ],
2419
+ indices[ offset + 3 ]
2420
+ );
2421
+ pattern.push(
2422
+ offset, offset + 1, offset + 2,
2423
+ offset, offset + 2, offset + 3
2424
+ );
2425
+
2426
+ } else if ( count > 4 ) {
2427
+
2428
+ // Use ear-clipping for complex n-gons if we have vertex positions
2429
+ if ( points && points.length > 0 ) {
2430
+
2431
+ const faceIndices = [];
2432
+ for ( let j = 0; j < count; j ++ ) {
2433
+
2434
+ faceIndices.push( indices[ offset + j ] );
2435
+
2436
+ }
2437
+
2438
+ const triangles = this._triangulateNGon( faceIndices, points );
2439
+
2440
+ for ( const tri of triangles ) {
2441
+
2442
+ triangulated.push( tri[ 0 ], tri[ 1 ], tri[ 2 ] );
2443
+ // Find local indices within the face
2444
+ pattern.push(
2445
+ offset + faceIndices.indexOf( tri[ 0 ] ),
2446
+ offset + faceIndices.indexOf( tri[ 1 ] ),
2447
+ offset + faceIndices.indexOf( tri[ 2 ] )
2448
+ );
2449
+
2450
+ }
2451
+
2452
+ } else {
2453
+
2454
+ // Fallback to fan triangulation
2455
+ for ( let j = 1; j < count - 1; j ++ ) {
2456
+
2457
+ triangulated.push(
2458
+ indices[ offset ],
2459
+ indices[ offset + j ],
2460
+ indices[ offset + j + 1 ]
2461
+ );
2462
+ pattern.push( offset, offset + j, offset + j + 1 );
2463
+
2464
+ }
2465
+
2466
+ }
2467
+
2468
+ }
2469
+
2470
+ offset += count;
2471
+
2472
+ }
2473
+
2474
+ return { indices: triangulated, pattern };
2475
+
2476
+ }
2477
+
2478
+ _applyTriangulationPattern( indices, pattern ) {
2479
+
2480
+ const result = [];
2481
+ for ( let i = 0; i < pattern.length; i ++ ) {
2482
+
2483
+ result.push( indices[ pattern[ i ] ] );
2484
+
2485
+ }
2486
+
2487
+ return result;
2488
+
2489
+ }
2490
+
2491
+ _triangulateNGon( faceIndices, points ) {
2492
+
2493
+ // Project 3D polygon to 2D for triangulation using Newell's method for normal
2494
+ const contour2D = [];
2495
+ const contour3D = [];
2496
+
2497
+ for ( const idx of faceIndices ) {
2498
+
2499
+ contour3D.push( new Vector3(
2500
+ points[ idx * 3 ],
2501
+ points[ idx * 3 + 1 ],
2502
+ points[ idx * 3 + 2 ]
2503
+ ) );
2504
+
2505
+ }
2506
+
2507
+ // Calculate polygon normal using Newell's method
2508
+ const normal = new Vector3();
2509
+ for ( let i = 0; i < contour3D.length; i ++ ) {
2510
+
2511
+ const curr = contour3D[ i ];
2512
+ const next = contour3D[ ( i + 1 ) % contour3D.length ];
2513
+ normal.x += ( curr.y - next.y ) * ( curr.z + next.z );
2514
+ normal.y += ( curr.z - next.z ) * ( curr.x + next.x );
2515
+ normal.z += ( curr.x - next.x ) * ( curr.y + next.y );
2516
+
2517
+ }
2518
+
2519
+ normal.normalize();
2520
+
2521
+ // Create tangent basis for projection
2522
+ const tangent = new Vector3();
2523
+ const bitangent = new Vector3();
2524
+
2525
+ if ( Math.abs( normal.y ) > 0.9 ) {
2526
+
2527
+ tangent.set( 1, 0, 0 );
2528
+
2529
+ } else {
2530
+
2531
+ tangent.set( 0, 1, 0 );
2532
+
2533
+ }
2534
+
2535
+ bitangent.crossVectors( normal, tangent ).normalize();
2536
+ tangent.crossVectors( bitangent, normal ).normalize();
2537
+
2538
+ // Project to 2D
2539
+ for ( const p of contour3D ) {
2540
+
2541
+ contour2D.push( new Vector2( p.dot( tangent ), p.dot( bitangent ) ) );
2542
+
2543
+ }
2544
+
2545
+ // Triangulate using ShapeUtils
2546
+ const triangles = ShapeUtils.triangulateShape( contour2D, [] );
2547
+
2548
+ // Map back to original indices
2549
+ const result = [];
2550
+ for ( const tri of triangles ) {
2551
+
2552
+ result.push( [
2553
+ faceIndices[ tri[ 0 ] ],
2554
+ faceIndices[ tri[ 1 ] ],
2555
+ faceIndices[ tri[ 2 ] ]
2556
+ ] );
2557
+
2558
+ }
2559
+
2560
+ return result;
2561
+
2562
+ }
2563
+
2564
+ _triangulateNGonWithHoles( outerIndices, holeContours, points ) {
2565
+
2566
+ // Project 3D polygon with holes to 2D for triangulation
2567
+ const outer3D = [];
2568
+
2569
+ for ( const idx of outerIndices ) {
2570
+
2571
+ outer3D.push( new Vector3(
2572
+ points[ idx * 3 ],
2573
+ points[ idx * 3 + 1 ],
2574
+ points[ idx * 3 + 2 ]
2575
+ ) );
2576
+
2577
+ }
2578
+
2579
+ // Calculate polygon normal using Newell's method
2580
+ const normal = new Vector3();
2581
+ for ( let i = 0; i < outer3D.length; i ++ ) {
2582
+
2583
+ const curr = outer3D[ i ];
2584
+ const next = outer3D[ ( i + 1 ) % outer3D.length ];
2585
+ normal.x += ( curr.y - next.y ) * ( curr.z + next.z );
2586
+ normal.y += ( curr.z - next.z ) * ( curr.x + next.x );
2587
+ normal.z += ( curr.x - next.x ) * ( curr.y + next.y );
2588
+
2589
+ }
2590
+
2591
+ normal.normalize();
2592
+
2593
+ // Create tangent basis for projection
2594
+ const tangent = new Vector3();
2595
+ const bitangent = new Vector3();
2596
+
2597
+ if ( Math.abs( normal.y ) > 0.9 ) {
2598
+
2599
+ tangent.set( 1, 0, 0 );
2600
+
2601
+ } else {
2602
+
2603
+ tangent.set( 0, 1, 0 );
2604
+
2605
+ }
2606
+
2607
+ bitangent.crossVectors( normal, tangent ).normalize();
2608
+ tangent.crossVectors( bitangent, normal ).normalize();
2609
+
2610
+ // Project outer contour to 2D
2611
+ const outer2D = [];
2612
+ for ( const p of outer3D ) {
2613
+
2614
+ outer2D.push( new Vector2( p.dot( tangent ), p.dot( bitangent ) ) );
2615
+
2616
+ }
2617
+
2618
+ // Project hole contours to 2D
2619
+ const holes2D = [];
2620
+
2621
+ for ( const holeIndices of holeContours ) {
2622
+
2623
+ const hole2D = [];
2624
+
2625
+ for ( const idx of holeIndices ) {
2626
+
2627
+ const p = new Vector3(
2628
+ points[ idx * 3 ],
2629
+ points[ idx * 3 + 1 ],
2630
+ points[ idx * 3 + 2 ]
2631
+ );
2632
+ hole2D.push( new Vector2( p.dot( tangent ), p.dot( bitangent ) ) );
2633
+
2634
+ }
2635
+
2636
+ holes2D.push( hole2D );
2637
+
2638
+ }
2639
+
2640
+ // Build combined index array: outer contour followed by all holes
2641
+ const allIndices = [ ...outerIndices ];
2642
+ for ( const holeIndices of holeContours ) {
2643
+
2644
+ allIndices.push( ...holeIndices );
2645
+
2646
+ }
2647
+
2648
+ // Triangulate using ShapeUtils with holes
2649
+ const triangles = ShapeUtils.triangulateShape( outer2D, holes2D );
2650
+
2651
+ // Map back to original vertex indices
2652
+ const result = [];
2653
+ for ( const tri of triangles ) {
2654
+
2655
+ result.push( [
2656
+ allIndices[ tri[ 0 ] ],
2657
+ allIndices[ tri[ 1 ] ],
2658
+ allIndices[ tri[ 2 ] ]
2659
+ ] );
2660
+
2661
+ }
2662
+
2663
+ return result;
2664
+
2665
+ }
2666
+
2667
+ _triangulateIndices( indices, counts ) {
2668
+
2669
+ const triangulated = [];
2670
+ let offset = 0;
2671
+
2672
+ for ( let i = 0; i < counts.length; i ++ ) {
2673
+
2674
+ const count = counts[ i ];
2675
+
2676
+ if ( count === 3 ) {
2677
+
2678
+ triangulated.push(
2679
+ indices[ offset ],
2680
+ indices[ offset + 1 ],
2681
+ indices[ offset + 2 ]
2682
+ );
2683
+
2684
+ } else if ( count === 4 ) {
2685
+
2686
+ triangulated.push(
2687
+ indices[ offset ],
2688
+ indices[ offset + 1 ],
2689
+ indices[ offset + 2 ],
2690
+ indices[ offset ],
2691
+ indices[ offset + 2 ],
2692
+ indices[ offset + 3 ]
2693
+ );
2694
+
2695
+ } else if ( count > 4 ) {
2696
+
2697
+ // Fan triangulation for n-gons
2698
+ for ( let j = 1; j < count - 1; j ++ ) {
2699
+
2700
+ triangulated.push(
2701
+ indices[ offset ],
2702
+ indices[ offset + j ],
2703
+ indices[ offset + j + 1 ]
2704
+ );
2705
+
2706
+ }
2707
+
2708
+ }
2709
+
2710
+ offset += count;
2711
+
2712
+ }
2713
+
2714
+ return triangulated;
2715
+
2716
+ }
2717
+
2718
+ _expandAttribute( data, indices, itemSize ) {
2719
+
2720
+ const expanded = new Array( indices.length * itemSize );
2721
+
2722
+ for ( let i = 0; i < indices.length; i ++ ) {
2723
+
2724
+ const srcIdx = indices[ i ];
2725
+
2726
+ for ( let j = 0; j < itemSize; j ++ ) {
2727
+
2728
+ expanded[ i * itemSize + j ] = data[ srcIdx * itemSize + j ];
2729
+
2730
+ }
2731
+
2732
+ }
2733
+
2734
+ return expanded;
2735
+
2736
+ }
2737
+
2738
+ /**
2739
+ * Compute per-vertex normals from indexed triangle data.
2740
+ * Accumulates area-weighted face normals at each shared vertex and normalizes.
2741
+ */
2742
+ _computeVertexNormals( points, indices ) {
2743
+
2744
+ const numVertices = points.length / 3;
2745
+ const normals = new Float32Array( numVertices * 3 );
2746
+
2747
+ for ( let i = 0; i < indices.length; i += 3 ) {
2748
+
2749
+ const a = indices[ i ];
2750
+ const b = indices[ i + 1 ];
2751
+ const c = indices[ i + 2 ];
2752
+
2753
+ const ax = points[ a * 3 ], ay = points[ a * 3 + 1 ], az = points[ a * 3 + 2 ];
2754
+ const bx = points[ b * 3 ], by = points[ b * 3 + 1 ], bz = points[ b * 3 + 2 ];
2755
+ const cx = points[ c * 3 ], cy = points[ c * 3 + 1 ], cz = points[ c * 3 + 2 ];
2756
+
2757
+ const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
2758
+ const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
2759
+
2760
+ const nx = e1y * e2z - e1z * e2y;
2761
+ const ny = e1z * e2x - e1x * e2z;
2762
+ const nz = e1x * e2y - e1y * e2x;
2763
+
2764
+ normals[ a * 3 ] += nx; normals[ a * 3 + 1 ] += ny; normals[ a * 3 + 2 ] += nz;
2765
+ normals[ b * 3 ] += nx; normals[ b * 3 + 1 ] += ny; normals[ b * 3 + 2 ] += nz;
2766
+ normals[ c * 3 ] += nx; normals[ c * 3 + 1 ] += ny; normals[ c * 3 + 2 ] += nz;
2767
+
2768
+ }
2769
+
2770
+ for ( let i = 0; i < numVertices; i ++ ) {
2771
+
2772
+ const x = normals[ i * 3 ], y = normals[ i * 3 + 1 ], z = normals[ i * 3 + 2 ];
2773
+ const len = Math.sqrt( x * x + y * y + z * z );
2774
+
2775
+ if ( len > 0 ) {
2776
+
2777
+ normals[ i * 3 ] /= len;
2778
+ normals[ i * 3 + 1 ] /= len;
2779
+ normals[ i * 3 + 2 ] /= len;
2780
+
2781
+ }
2782
+
2783
+ }
2784
+
2785
+ return normals;
2786
+
2787
+ }
2788
+
2789
+ /**
2790
+ * Get the material path for a mesh, checking various binding sources.
2791
+ */
2792
+ _getMaterialPath( meshPath, fields ) {
2793
+
2794
+ let materialPath = null;
2795
+ const materialBinding = fields[ 'material:binding' ];
2796
+
2797
+ if ( materialBinding ) {
2798
+
2799
+ materialPath = Array.isArray( materialBinding ) ? materialBinding[ 0 ] : materialBinding;
2800
+
2801
+ }
2802
+
2803
+ // Use variant-aware lookup if no direct binding in fields
2804
+ if ( ! materialPath ) {
2805
+
2806
+ materialPath = this._getMaterialBindingTarget( meshPath );
2807
+
2808
+ }
2809
+
2810
+ return materialPath;
2811
+
2812
+ }
2813
+
2814
+ _buildMaterial( meshPath, fields ) {
2815
+
2816
+ const material = new MeshPhysicalMaterial();
2817
+
2818
+ let materialPath = null;
2819
+ const materialBinding = fields[ 'material:binding' ];
2820
+
2821
+ if ( materialBinding ) {
2822
+
2823
+ materialPath = Array.isArray( materialBinding ) ? materialBinding[ 0 ] : materialBinding;
2824
+
2825
+ }
2826
+
2827
+ // Use variant-aware lookup if no direct binding in fields
2828
+ if ( ! materialPath ) {
2829
+
2830
+ materialPath = this._getMaterialBindingTarget( meshPath );
2831
+
2832
+ }
2833
+
2834
+ if ( ! materialPath ) {
2835
+
2836
+ const materialPaths = [];
2837
+ const prefix = meshPath + '/';
2838
+
2839
+ for ( const path in this.specsByPath ) {
2840
+
2841
+ if ( ! path.startsWith( prefix ) ) continue;
2842
+ if ( ! path.endsWith( '.material:binding' ) ) continue;
2843
+
2844
+ const bindingSpec = this.specsByPath[ path ];
2845
+ if ( ! bindingSpec ) continue;
2846
+
2847
+ const targetPaths = bindingSpec.fields.targetPaths;
2848
+ if ( targetPaths && targetPaths.length > 0 ) {
2849
+
2850
+ materialPaths.push( targetPaths[ 0 ] );
2851
+
2852
+ }
2853
+
2854
+ }
2855
+
2856
+ if ( materialPaths.length > 0 ) {
2857
+
2858
+ materialPath = this._pickBestMaterial( materialPaths );
2859
+
2860
+ }
2861
+
2862
+ }
2863
+
2864
+ if ( ! materialPath ) {
2865
+
2866
+ // Use material index for O(1) lookup instead of O(n) iteration
2867
+ const meshParts = meshPath.split( '/' );
2868
+ const rootPath = '/' + meshParts[ 1 ];
2869
+
2870
+ const materialsInRoot = this.materialsByRoot.get( rootPath );
2871
+
2872
+ if ( materialsInRoot ) {
2873
+
2874
+ for ( const path of materialsInRoot ) {
2875
+
2876
+ if ( path.startsWith( rootPath + '/Looks/' ) ||
2877
+ path.startsWith( rootPath + '/Materials/' ) ) {
2878
+
2879
+ materialPath = path;
2880
+ break;
2881
+
2882
+ }
2883
+
2884
+ }
2885
+
2886
+ }
2887
+
2888
+ }
2889
+
2890
+ if ( materialPath ) {
2891
+
2892
+ this._applyMaterial( material, materialPath );
2893
+
2894
+ }
2895
+
2896
+ return material;
2897
+
2898
+ }
2899
+
2900
+ _buildMaterialForPath( materialPath ) {
2901
+
2902
+ const material = new MeshPhysicalMaterial();
2903
+
2904
+ if ( materialPath ) {
2905
+
2906
+ this._applyMaterial( material, materialPath );
2907
+
2908
+ }
2909
+
2910
+ return material;
2911
+
2912
+ }
2913
+
2914
+ /**
2915
+ * Apply material binding from a prim path to a mesh.
2916
+ * Used when merging referenced geometry into a prim that has material binding.
2917
+ */
2918
+ _applyMaterialBinding( mesh, primPath ) {
2919
+
2920
+ // Look for material:binding on this prim
2921
+ const bindingPath = primPath + '.material:binding';
2922
+ const bindingSpec = this.specsByPath[ bindingPath ];
2923
+
2924
+ if ( ! bindingSpec ) return;
2925
+
2926
+ let materialPath = null;
2927
+ const targetPaths = bindingSpec.fields?.targetPaths || bindingSpec.fields?.default;
2928
+
2929
+ if ( targetPaths ) {
2930
+
2931
+ materialPath = Array.isArray( targetPaths ) ? targetPaths[ 0 ] : targetPaths;
2932
+
2933
+ }
2934
+
2935
+ if ( ! materialPath ) return;
2936
+
2937
+ // Clean the material path
2938
+ materialPath = String( materialPath ).replace( /^<|>$/g, '' );
2939
+
2940
+ // Build and apply the material
2941
+ const material = new MeshPhysicalMaterial();
2942
+ this._applyMaterial( material, materialPath );
2943
+ mesh.material = material;
2944
+
2945
+ }
2946
+
2947
+ _pickBestMaterial( materialPaths ) {
2948
+
2949
+ for ( const materialPath of materialPaths ) {
2950
+
2951
+ const shaderPaths = this.shadersByMaterialPath.get( materialPath );
2952
+ if ( ! shaderPaths ) continue;
2953
+
2954
+ for ( const path of shaderPaths ) {
2955
+
2956
+ const attrs = this._getAttributes( path );
2957
+ if ( attrs[ 'info:id' ] === 'UsdUVTexture' && attrs[ 'inputs:file' ] ) {
2958
+
2959
+ return materialPath;
2960
+
2961
+ }
2962
+
2963
+ }
2964
+
2965
+ }
2966
+
2967
+ return materialPaths[ 0 ];
2968
+
2969
+ }
2970
+
2971
+ _applyMaterial( material, materialPath ) {
2972
+
2973
+ const materialSpec = this.specsByPath[ materialPath ];
2974
+ if ( ! materialSpec ) return;
2975
+
2976
+ const shaderPaths = this.shadersByMaterialPath.get( materialPath );
2977
+ if ( ! shaderPaths ) return;
2978
+
2979
+ for ( const path of shaderPaths ) {
2980
+
2981
+ const spec = this.specsByPath[ path ];
2982
+ if ( ! spec ) continue;
2983
+
2984
+ const shaderAttrs = this._getAttributes( path );
2985
+ const infoId = shaderAttrs[ 'info:id' ] || spec.fields[ 'info:id' ];
2986
+
2987
+ if ( infoId === 'UsdPreviewSurface' || infoId === 'ND_UsdPreviewSurface_surfaceshader' ) {
2988
+
2989
+ this._applyPreviewSurface( material, path );
2990
+
2991
+ } else if ( infoId === 'arnold:openpbr_surface' ) {
2992
+
2993
+ this._applyOpenPBRSurface( material, path );
2994
+
2995
+ }
2996
+
2997
+ }
2998
+
2999
+ }
3000
+
3001
+ /**
3002
+ * Shared helper for applying texture or value from shader attribute.
3003
+ * Reduces duplication between _applyPreviewSurface and _applyOpenPBRSurface.
3004
+ */
3005
+ _applyTextureOrValue( material, shaderPath, fields, attrName, textureProperty, colorSpace, valueCallback, textureGetter ) {
3006
+
3007
+ const attrPath = shaderPath + '.' + attrName;
3008
+ const spec = this.specsByPath[ attrPath ];
3009
+
3010
+ if ( spec && spec.fields.connectionPaths && spec.fields.connectionPaths.length > 0 ) {
3011
+
3012
+ // For OpenPBR, try all connection paths; for PreviewSurface, just the first
3013
+ const paths = textureGetter === this._getTextureFromOpenPBRConnection
3014
+ ? spec.fields.connectionPaths
3015
+ : [ spec.fields.connectionPaths[ 0 ] ];
3016
+
3017
+ for ( const connPath of paths ) {
3018
+
3019
+ const texture = textureGetter.call( this, connPath );
3020
+
3021
+ if ( texture ) {
3022
+
3023
+ texture.colorSpace = colorSpace;
3024
+ material[ textureProperty ] = texture;
3025
+ return true;
3026
+
3027
+ }
3028
+
3029
+ }
3030
+
3031
+ }
3032
+
3033
+ if ( fields[ attrName ] !== undefined && valueCallback ) {
3034
+
3035
+ valueCallback( fields[ attrName ] );
3036
+
3037
+ }
3038
+
3039
+ return false;
3040
+
3041
+ }
3042
+
3043
+ _applyPreviewSurface( material, shaderPath ) {
3044
+
3045
+ const fields = this._getAttributes( shaderPath );
3046
+
3047
+ const applyTexture = ( attrName, textureProperty, colorSpace, valueCallback ) => {
3048
+
3049
+ return this._applyTextureOrValue(
3050
+ material, shaderPath, fields, attrName, textureProperty, colorSpace, valueCallback,
3051
+ this._getTextureFromConnection
3052
+ );
3053
+
3054
+ };
3055
+
3056
+ const getAttrSpec = ( attrName ) => {
3057
+
3058
+ const attrPath = shaderPath + '.' + attrName;
3059
+ return this.specsByPath[ attrPath ];
3060
+
3061
+ };
3062
+
3063
+ // Diffuse color / base color map
3064
+ applyTexture(
3065
+ 'inputs:diffuseColor',
3066
+ 'map',
3067
+ SRGBColorSpace,
3068
+ ( color ) => {
3069
+
3070
+ if ( Array.isArray( color ) && color.length >= 3 ) {
3071
+
3072
+ material.color.setRGB( color[ 0 ], color[ 1 ], color[ 2 ], SRGBColorSpace );
3073
+
3074
+ }
3075
+
3076
+ }
3077
+ );
3078
+
3079
+ // Apply UsdUVTexture scale to diffuse color (output = texture * scale + bias)
3080
+ if ( material.map && material.map.userData.scale ) {
3081
+
3082
+ const scale = material.map.userData.scale;
3083
+ if ( Array.isArray( scale ) && scale.length >= 3 ) {
3084
+
3085
+ material.color.setRGB( scale[ 0 ], scale[ 1 ], scale[ 2 ], SRGBColorSpace );
3086
+
3087
+ }
3088
+
3089
+ }
3090
+
3091
+ // Emissive
3092
+ applyTexture(
3093
+ 'inputs:emissiveColor',
3094
+ 'emissiveMap',
3095
+ SRGBColorSpace,
3096
+ ( color ) => {
3097
+
3098
+ if ( Array.isArray( color ) && color.length >= 3 ) {
3099
+
3100
+ material.emissive.setRGB( color[ 0 ], color[ 1 ], color[ 2 ], SRGBColorSpace );
3101
+
3102
+ }
3103
+
3104
+ }
3105
+ );
3106
+
3107
+ if ( material.emissiveMap ) {
3108
+
3109
+ if ( material.emissiveMap.userData.scale ) {
3110
+
3111
+ const scale = material.emissiveMap.userData.scale;
3112
+ if ( Array.isArray( scale ) && scale.length >= 3 ) {
3113
+
3114
+ material.emissive.setRGB( scale[ 0 ], scale[ 1 ], scale[ 2 ], SRGBColorSpace );
3115
+
3116
+ }
3117
+
3118
+ } else {
3119
+
3120
+ material.emissive.set( 0xffffff );
3121
+
3122
+ }
3123
+
3124
+ }
3125
+
3126
+ // Normal map
3127
+ applyTexture( 'inputs:normal', 'normalMap', NoColorSpace, null );
3128
+
3129
+ // Apply normal map scale from UsdUVTexture scale input
3130
+ if ( material.normalMap && material.normalMap.userData.scale ) {
3131
+
3132
+ const scale = material.normalMap.userData.scale;
3133
+ // UsdUVTexture scale is float4 (r,g,b,a), use first two components for normalScale
3134
+ material.normalScale = new Vector2( scale[ 0 ], scale[ 1 ] );
3135
+
3136
+ }
3137
+
3138
+ // Roughness
3139
+ const hasRoughnessMap = applyTexture(
3140
+ 'inputs:roughness',
3141
+ 'roughnessMap',
3142
+ NoColorSpace,
3143
+ ( value ) => {
3144
+
3145
+ material.roughness = value;
3146
+
3147
+ }
3148
+ );
3149
+
3150
+ if ( hasRoughnessMap ) {
3151
+
3152
+ material.roughness = 1.0;
3153
+
3154
+ }
3155
+
3156
+ // Metallic
3157
+ const hasMetalnessMap = applyTexture(
3158
+ 'inputs:metallic',
3159
+ 'metalnessMap',
3160
+ NoColorSpace,
3161
+ ( value ) => {
3162
+
3163
+ material.metalness = value;
3164
+
3165
+ }
3166
+ );
3167
+
3168
+ if ( hasMetalnessMap ) {
3169
+
3170
+ material.metalness = 1.0;
3171
+
3172
+ }
3173
+
3174
+ // Occlusion
3175
+ applyTexture( 'inputs:occlusion', 'aoMap', NoColorSpace, null );
3176
+
3177
+ // IOR
3178
+ if ( fields[ 'inputs:ior' ] !== undefined ) {
3179
+
3180
+ material.ior = fields[ 'inputs:ior' ];
3181
+
3182
+ }
3183
+
3184
+ // Specular color
3185
+ applyTexture(
3186
+ 'inputs:specularColor',
3187
+ 'specularColorMap',
3188
+ SRGBColorSpace,
3189
+ ( color ) => {
3190
+
3191
+ if ( Array.isArray( color ) && color.length >= 3 ) {
3192
+
3193
+ material.specularColor.setRGB( color[ 0 ], color[ 1 ], color[ 2 ], SRGBColorSpace );
3194
+
3195
+ }
3196
+
3197
+ }
3198
+ );
3199
+
3200
+ // Apply UsdUVTexture scale to specular color
3201
+ if ( material.specularColorMap && material.specularColorMap.userData.scale ) {
3202
+
3203
+ const scale = material.specularColorMap.userData.scale;
3204
+ if ( Array.isArray( scale ) && scale.length >= 3 ) {
3205
+
3206
+ material.specularColor.setRGB( scale[ 0 ], scale[ 1 ], scale[ 2 ], SRGBColorSpace );
3207
+
3208
+ }
3209
+
3210
+ }
3211
+
3212
+ // Clearcoat
3213
+ if ( fields[ 'inputs:clearcoat' ] !== undefined ) {
3214
+
3215
+ material.clearcoat = fields[ 'inputs:clearcoat' ];
3216
+
3217
+ }
3218
+
3219
+ // Clearcoat roughness
3220
+ if ( fields[ 'inputs:clearcoatRoughness' ] !== undefined ) {
3221
+
3222
+ material.clearcoatRoughness = fields[ 'inputs:clearcoatRoughness' ];
3223
+
3224
+ }
3225
+
3226
+ // Opacity and opacity modes
3227
+ const opacityThreshold = fields[ 'inputs:opacityThreshold' ] !== undefined ? fields[ 'inputs:opacityThreshold' ] : 0.0;
3228
+
3229
+ // Check if opacity is connected to a texture (e.g., diffuse texture's alpha)
3230
+ const opacitySpec = getAttrSpec( 'inputs:opacity' );
3231
+ const hasOpacityConnection = opacitySpec?.fields?.connectionPaths?.length > 0;
3232
+
3233
+ if ( hasOpacityConnection ) {
3234
+
3235
+ // Opacity from texture alpha - use the diffuse map's alpha channel
3236
+ if ( opacityThreshold > 0 ) {
3237
+
3238
+ // Alpha cutoff mode
3239
+ material.alphaTest = opacityThreshold;
3240
+ material.transparent = false;
3241
+
3242
+ } else {
3243
+
3244
+ // Alpha blend mode
3245
+ material.transparent = true;
3246
+
3247
+ }
3248
+
3249
+ } else {
3250
+
3251
+ // Direct opacity value
3252
+ const opacity = fields[ 'inputs:opacity' ] !== undefined ? fields[ 'inputs:opacity' ] : 1.0;
3253
+
3254
+ if ( opacity < 1.0 ) {
3255
+
3256
+ material.transparent = true;
3257
+ material.opacity = opacity;
3258
+
3259
+ }
3260
+
3261
+ }
3262
+
3263
+ }
3264
+
3265
+ _applyOpenPBRSurface( material, shaderPath ) {
3266
+
3267
+ const fields = this._getAttributes( shaderPath );
3268
+
3269
+ const applyTexture = ( attrName, textureProperty, colorSpace, valueCallback ) => {
3270
+
3271
+ return this._applyTextureOrValue(
3272
+ material, shaderPath, fields, attrName, textureProperty, colorSpace, valueCallback,
3273
+ this._getTextureFromOpenPBRConnection
3274
+ );
3275
+
3276
+ };
3277
+
3278
+ // Base color (diffuse)
3279
+ applyTexture(
3280
+ 'inputs:base_color',
3281
+ 'map',
3282
+ SRGBColorSpace,
3283
+ ( color ) => {
3284
+
3285
+ if ( Array.isArray( color ) && color.length >= 3 ) {
3286
+
3287
+ material.color.setRGB( color[ 0 ], color[ 1 ], color[ 2 ], SRGBColorSpace );
3288
+
3289
+ }
3290
+
3291
+ }
3292
+ );
3293
+
3294
+ // Apply UsdUVTexture scale to base color
3295
+ if ( material.map && material.map.userData.scale ) {
3296
+
3297
+ const scale = material.map.userData.scale;
3298
+ if ( Array.isArray( scale ) && scale.length >= 3 ) {
3299
+
3300
+ material.color.setRGB( scale[ 0 ], scale[ 1 ], scale[ 2 ], SRGBColorSpace );
3301
+
3302
+ }
3303
+
3304
+ }
3305
+
3306
+ // Base metalness
3307
+ applyTexture(
3308
+ 'inputs:base_metalness',
3309
+ 'metalnessMap',
3310
+ NoColorSpace,
3311
+ ( value ) => {
3312
+
3313
+ if ( typeof value === 'number' ) {
3314
+
3315
+ material.metalness = value;
3316
+
3317
+ }
3318
+
3319
+ }
3320
+ );
3321
+
3322
+ // Specular roughness
3323
+ applyTexture(
3324
+ 'inputs:specular_roughness',
3325
+ 'roughnessMap',
3326
+ NoColorSpace,
3327
+ ( value ) => {
3328
+
3329
+ if ( typeof value === 'number' ) {
3330
+
3331
+ material.roughness = value;
3332
+
3333
+ }
3334
+
3335
+ }
3336
+ );
3337
+
3338
+ // Emission color
3339
+ const hasEmissionMap = applyTexture(
3340
+ 'inputs:emission_color',
3341
+ 'emissiveMap',
3342
+ SRGBColorSpace,
3343
+ ( color ) => {
3344
+
3345
+ if ( Array.isArray( color ) && color.length >= 3 ) {
3346
+
3347
+ material.emissive.setRGB( color[ 0 ], color[ 1 ], color[ 2 ], SRGBColorSpace );
3348
+
3349
+ }
3350
+
3351
+ }
3352
+ );
3353
+
3354
+ // Emission luminance/weight - multiply emissive by this factor
3355
+ const emissionLuminance = fields[ 'inputs:emission_luminance' ];
3356
+
3357
+ if ( emissionLuminance !== undefined && emissionLuminance > 0 ) {
3358
+
3359
+ if ( hasEmissionMap ) {
3360
+
3361
+ material.emissiveIntensity = emissionLuminance;
3362
+
3363
+ } else {
3364
+
3365
+ // Scale the emissive color by luminance
3366
+ material.emissive.multiplyScalar( emissionLuminance );
3367
+
3368
+ }
3369
+
3370
+ }
3371
+
3372
+ // Transmission (transparency)
3373
+ const transmissionWeight = fields[ 'inputs:transmission_weight' ];
3374
+
3375
+ if ( transmissionWeight !== undefined && transmissionWeight > 0 ) {
3376
+
3377
+ material.transmission = transmissionWeight;
3378
+
3379
+ const transmissionDepth = fields[ 'inputs:transmission_depth' ];
3380
+
3381
+ if ( transmissionDepth !== undefined ) {
3382
+
3383
+ material.thickness = transmissionDepth;
3384
+
3385
+ }
3386
+
3387
+ const transmissionColor = fields[ 'inputs:transmission_color' ];
3388
+
3389
+ if ( transmissionColor !== undefined && Array.isArray( transmissionColor ) ) {
3390
+
3391
+ material.attenuationColor.setRGB( transmissionColor[ 0 ], transmissionColor[ 1 ], transmissionColor[ 2 ] );
3392
+ material.attenuationDistance = transmissionDepth || 1.0;
3393
+
3394
+ }
3395
+
3396
+ }
3397
+
3398
+ // Geometry opacity (overall surface opacity)
3399
+ const geometryOpacity = fields[ 'inputs:geometry_opacity' ];
3400
+
3401
+ if ( geometryOpacity !== undefined && geometryOpacity < 1.0 ) {
3402
+
3403
+ material.opacity = geometryOpacity;
3404
+ material.transparent = true;
3405
+
3406
+ }
3407
+
3408
+ // Specular IOR
3409
+ const specularIOR = fields[ 'inputs:specular_ior' ];
3410
+
3411
+ if ( specularIOR !== undefined ) {
3412
+
3413
+ material.ior = specularIOR;
3414
+
3415
+ }
3416
+
3417
+ // Coat (clearcoat)
3418
+ const coatWeight = fields[ 'inputs:coat_weight' ];
3419
+
3420
+ if ( coatWeight !== undefined && coatWeight > 0 ) {
3421
+
3422
+ material.clearcoat = coatWeight;
3423
+
3424
+ const coatRoughness = fields[ 'inputs:coat_roughness' ];
3425
+
3426
+ if ( coatRoughness !== undefined ) {
3427
+
3428
+ material.clearcoatRoughness = coatRoughness;
3429
+
3430
+ }
3431
+
3432
+ }
3433
+
3434
+ // Thin film (iridescence)
3435
+ const thinFilmWeight = fields[ 'inputs:thin_film_weight' ];
3436
+
3437
+ if ( thinFilmWeight !== undefined && thinFilmWeight > 0 ) {
3438
+
3439
+ material.iridescence = thinFilmWeight;
3440
+
3441
+ const thinFilmIOR = fields[ 'inputs:thin_film_ior' ];
3442
+
3443
+ if ( thinFilmIOR !== undefined ) {
3444
+
3445
+ material.iridescenceIOR = thinFilmIOR;
3446
+
3447
+ }
3448
+
3449
+ const thinFilmThickness = fields[ 'inputs:thin_film_thickness' ];
3450
+
3451
+ if ( thinFilmThickness !== undefined ) {
3452
+
3453
+ // OpenPBR uses micrometers, Three.js uses nanometers
3454
+ const thicknessNm = thinFilmThickness * 1000;
3455
+ material.iridescenceThicknessRange = [ thicknessNm, thicknessNm ];
3456
+
3457
+ }
3458
+
3459
+ }
3460
+
3461
+ // Specular
3462
+ const specularWeight = fields[ 'inputs:specular_weight' ];
3463
+
3464
+ if ( specularWeight !== undefined ) {
3465
+
3466
+ material.specularIntensity = specularWeight;
3467
+
3468
+ }
3469
+
3470
+ const specularColor = fields[ 'inputs:specular_color' ];
3471
+
3472
+ if ( specularColor !== undefined && Array.isArray( specularColor ) ) {
3473
+
3474
+ material.specularColor.setRGB( specularColor[ 0 ], specularColor[ 1 ], specularColor[ 2 ] );
3475
+
3476
+ }
3477
+
3478
+ // Anisotropy
3479
+ const anisotropy = fields[ 'inputs:specular_roughness_anisotropy' ];
3480
+
3481
+ if ( anisotropy !== undefined && anisotropy > 0 ) {
3482
+
3483
+ material.anisotropy = anisotropy;
3484
+
3485
+ }
3486
+
3487
+ // Geometry normal (normal map)
3488
+ applyTexture(
3489
+ 'inputs:geometry_normal',
3490
+ 'normalMap',
3491
+ NoColorSpace,
3492
+ null
3493
+ );
3494
+
3495
+ }
3496
+
3497
+ _getTextureFromOpenPBRConnection( connPath ) {
3498
+
3499
+ // connPath is like /Material/NodeGraph.outputs:baseColor or /Material/Shader.outputs:out
3500
+ const cleanPath = connPath.replace( /<|>/g, '' );
3501
+ const shaderPath = cleanPath.split( '.' )[ 0 ];
3502
+ const shaderSpec = this.specsByPath[ shaderPath ];
3503
+
3504
+ if ( ! shaderSpec ) return null;
3505
+
3506
+ const attrs = this._getAttributes( shaderPath );
3507
+ const infoId = attrs[ 'info:id' ] || shaderSpec.fields[ 'info:id' ];
3508
+ const typeName = shaderSpec.fields.typeName;
3509
+
3510
+ // Handle NodeGraph - follow output connection to internal shader
3511
+ if ( typeName === 'NodeGraph' ) {
3512
+
3513
+ // Get the output attribute that's connected
3514
+ const outputName = cleanPath.split( '.' )[ 1 ]; // e.g., "outputs:baseColor"
3515
+ const outputAttrPath = shaderPath + '.' + outputName;
3516
+ const outputSpec = this.specsByPath[ outputAttrPath ];
3517
+
3518
+ if ( outputSpec?.fields?.connectionPaths?.length > 0 ) {
3519
+
3520
+ // Follow the internal connection
3521
+ return this._getTextureFromOpenPBRConnection( outputSpec.fields.connectionPaths[ 0 ] );
3522
+
3523
+ }
3524
+
3525
+ return null;
3526
+
3527
+ }
3528
+
3529
+ // Handle arnold:image - Arnold's texture node
3530
+ if ( infoId === 'arnold:image' ) {
3531
+
3532
+ const filePath = attrs[ 'inputs:filename' ];
3533
+ if ( ! filePath ) return null;
3534
+
3535
+ return this._loadTextureFromPath( filePath );
3536
+
3537
+ }
3538
+
3539
+ // Handle MaterialX image nodes (ND_image_color4, ND_image_color3, etc.)
3540
+ if ( infoId && infoId.startsWith( 'ND_image_' ) ) {
3541
+
3542
+ const filePath = attrs[ 'inputs:file' ];
3543
+ if ( ! filePath ) return null;
3544
+
3545
+ return this._loadTextureFromPath( filePath );
3546
+
3547
+ }
3548
+
3549
+ // Handle Maya file texture - follow the inColor connection to the actual image
3550
+ if ( infoId === 'MayaND_fileTexture_color4' ) {
3551
+
3552
+ const inColorPath = shaderPath + '.inputs:inColor';
3553
+ const inColorSpec = this.specsByPath[ inColorPath ];
3554
+
3555
+ if ( inColorSpec?.fields?.connectionPaths?.length > 0 ) {
3556
+
3557
+ return this._getTextureFromOpenPBRConnection( inColorSpec.fields.connectionPaths[ 0 ] );
3558
+
3559
+ }
3560
+
3561
+ return null;
3562
+
3563
+ }
3564
+
3565
+ // Handle color conversion nodes - follow the input connection
3566
+ if ( infoId && infoId.startsWith( 'ND_convert_' ) ) {
3567
+
3568
+ const inPath = shaderPath + '.inputs:in';
3569
+ const inSpec = this.specsByPath[ inPath ];
3570
+
3571
+ if ( inSpec?.fields?.connectionPaths?.length > 0 ) {
3572
+
3573
+ return this._getTextureFromOpenPBRConnection( inSpec.fields.connectionPaths[ 0 ] );
3574
+
3575
+ }
3576
+
3577
+ return null;
3578
+
3579
+ }
3580
+
3581
+ // Handle Arnold bump2d - follow the bump_map input
3582
+ if ( infoId === 'arnold:bump2d' ) {
3583
+
3584
+ const bumpMapPath = shaderPath + '.inputs:bump_map';
3585
+ const bumpMapSpec = this.specsByPath[ bumpMapPath ];
3586
+
3587
+ if ( bumpMapSpec?.fields?.connectionPaths?.length > 0 ) {
3588
+
3589
+ return this._getTextureFromOpenPBRConnection( bumpMapSpec.fields.connectionPaths[ 0 ] );
3590
+
3591
+ }
3592
+
3593
+ return null;
3594
+
3595
+ }
3596
+
3597
+ // Handle Arnold color_correct - follow the input connection
3598
+ if ( infoId === 'arnold:color_correct' ) {
3599
+
3600
+ const inputPath = shaderPath + '.inputs:input';
3601
+ const inputSpec = this.specsByPath[ inputPath ];
3602
+
3603
+ if ( inputSpec?.fields?.connectionPaths?.length > 0 ) {
3604
+
3605
+ return this._getTextureFromOpenPBRConnection( inputSpec.fields.connectionPaths[ 0 ] );
3606
+
3607
+ }
3608
+
3609
+ return null;
3610
+
3611
+ }
3612
+
3613
+ // Handle nested shader paths (e.g., /Material/file2/cc.outputs:a)
3614
+ // Check if parent path is an image node
3615
+ const parentPath = shaderPath.substring( 0, shaderPath.lastIndexOf( '/' ) );
3616
+
3617
+ if ( parentPath ) {
3618
+
3619
+ const parentSpec = this.specsByPath[ parentPath ];
3620
+
3621
+ if ( parentSpec ) {
3622
+
3623
+ const parentAttrs = this._getAttributes( parentPath );
3624
+ const parentInfoId = parentAttrs[ 'info:id' ] || parentSpec.fields[ 'info:id' ];
3625
+
3626
+ if ( parentInfoId === 'arnold:image' ) {
3627
+
3628
+ const filePath = parentAttrs[ 'inputs:filename' ];
3629
+ if ( filePath ) return this._loadTextureFromPath( filePath );
3630
+
3631
+ }
3632
+
3633
+ }
3634
+
3635
+ }
3636
+
3637
+ return null;
3638
+
3639
+ }
3640
+
3641
+ _loadTextureFromPath( filePath ) {
3642
+
3643
+ if ( ! filePath ) return null;
3644
+
3645
+ // Check cache first
3646
+ if ( this.textureCache[ filePath ] ) {
3647
+
3648
+ return this.textureCache[ filePath ];
3649
+
3650
+ }
3651
+
3652
+ const texture = this._loadTexture( filePath, null, null );
3653
+
3654
+ if ( texture ) {
3655
+
3656
+ this.textureCache[ filePath ] = texture;
3657
+
3658
+ }
3659
+
3660
+ return texture;
3661
+
3662
+ }
3663
+
3664
+ _getTextureFromConnection( connPath ) {
3665
+
3666
+ // connPath is like /Material/Shader.outputs:rgb
3667
+ const shaderPath = connPath.split( '.' )[ 0 ];
3668
+ const shaderSpec = this.specsByPath[ shaderPath ];
3669
+
3670
+ if ( ! shaderSpec ) return null;
3671
+
3672
+ const attrs = this._getAttributes( shaderPath );
3673
+ const infoId = attrs[ 'info:id' ] || shaderSpec.fields[ 'info:id' ];
3674
+
3675
+ if ( infoId !== 'UsdUVTexture' ) return null;
3676
+
3677
+ const filePath = attrs[ 'inputs:file' ];
3678
+ if ( ! filePath ) return null;
3679
+
3680
+ // Check for UsdTransform2d connection via inputs:st and trace to PrimvarReader
3681
+ let transformAttrs = null;
3682
+ let uvChannel = 0; // Default to first UV set
3683
+ const stAttrPath = shaderPath + '.inputs:st';
3684
+ const stAttrSpec = this.specsByPath[ stAttrPath ];
3685
+
3686
+ if ( stAttrSpec?.fields?.connectionPaths?.length > 0 ) {
3687
+
3688
+ const stConnPath = stAttrSpec.fields.connectionPaths[ 0 ];
3689
+ const stPath = stConnPath.replace( /<|>/g, '' ).split( '.' )[ 0 ];
3690
+ const stSpec = this.specsByPath[ stPath ];
3691
+
3692
+ if ( stSpec ) {
3693
+
3694
+ const stAttrs = this._getAttributes( stPath );
3695
+ const stInfoId = stAttrs[ 'info:id' ] || stSpec.fields[ 'info:id' ];
3696
+
3697
+ if ( stInfoId === 'UsdTransform2d' ) {
3698
+
3699
+ transformAttrs = stAttrs;
3700
+
3701
+ // Trace to PrimvarReader to find UV set
3702
+ const inAttrPath = stPath + '.inputs:in';
3703
+ const inAttrSpec = this.specsByPath[ inAttrPath ];
3704
+
3705
+ if ( inAttrSpec?.fields?.connectionPaths?.length > 0 ) {
3706
+
3707
+ const inConnPath = inAttrSpec.fields.connectionPaths[ 0 ];
3708
+ const primvarPath = inConnPath.replace( /<|>/g, '' ).split( '.' )[ 0 ];
3709
+ const primvarAttrs = this._getAttributes( primvarPath );
3710
+
3711
+ // Check varname to determine UV channel
3712
+ const varname = primvarAttrs[ 'inputs:varname' ];
3713
+ if ( varname === 'st1' ) uvChannel = 1;
3714
+ else if ( varname === 'st2' ) uvChannel = 2;
3715
+
3716
+ }
3717
+
3718
+ } else if ( stInfoId === 'UsdPrimvarReader_float2' ) {
3719
+
3720
+ // Direct connection to PrimvarReader
3721
+ const varname = stAttrs[ 'inputs:varname' ];
3722
+ if ( varname === 'st1' ) uvChannel = 1;
3723
+ else if ( varname === 'st2' ) uvChannel = 2;
3724
+
3725
+ }
3726
+
3727
+ }
3728
+
3729
+ }
3730
+
3731
+ // Extract scale and bias for texture value modification
3732
+ const scale = attrs[ 'inputs:scale' ];
3733
+ const bias = attrs[ 'inputs:bias' ];
3734
+
3735
+ // Create cache key that includes scale/bias if present
3736
+ let cacheKey = filePath;
3737
+ if ( scale ) cacheKey += ':s' + scale.join( ',' );
3738
+ if ( bias ) cacheKey += ':b' + bias.join( ',' );
3739
+
3740
+ if ( this.textureCache[ cacheKey ] ) {
3741
+
3742
+ return this.textureCache[ cacheKey ];
3743
+
3744
+ }
3745
+
3746
+ const texture = this._loadTexture( filePath, attrs, transformAttrs );
3747
+
3748
+ if ( texture ) {
3749
+
3750
+ // Store scale/bias and UV channel in userData
3751
+ if ( scale ) texture.userData.scale = scale;
3752
+ if ( bias ) texture.userData.bias = bias;
3753
+ if ( uvChannel !== 0 ) texture.channel = uvChannel;
3754
+
3755
+ this.textureCache[ cacheKey ] = texture;
3756
+
3757
+ }
3758
+
3759
+ return texture;
3760
+
3761
+ }
3762
+
3763
+ _applyTextureTransforms( texture, attrs ) {
3764
+
3765
+ if ( ! attrs ) return;
3766
+
3767
+ const scale = attrs[ 'inputs:scale' ];
3768
+ if ( scale && Array.isArray( scale ) && scale.length >= 2 ) {
3769
+
3770
+ texture.repeat.set( scale[ 0 ], scale[ 1 ] );
3771
+
3772
+ }
3773
+
3774
+ const translation = attrs[ 'inputs:translation' ];
3775
+ if ( translation && Array.isArray( translation ) && translation.length >= 2 ) {
3776
+
3777
+ texture.offset.set( translation[ 0 ], translation[ 1 ] );
3778
+
3779
+ }
3780
+
3781
+ const rotation = attrs[ 'inputs:rotation' ];
3782
+ if ( typeof rotation === 'number' ) {
3783
+
3784
+ texture.rotation = rotation * Math.PI / 180;
3785
+
3786
+ }
3787
+
3788
+ }
3789
+
3790
+ _loadTexture( filePath, textureAttrs, transformAttrs ) {
3791
+
3792
+ let cleanPath = filePath;
3793
+ if ( cleanPath.startsWith( '@' ) ) cleanPath = cleanPath.slice( 1 );
3794
+ if ( cleanPath.endsWith( '@' ) ) cleanPath = cleanPath.slice( 0, - 1 );
3795
+
3796
+ // Resolve relative to basePath first
3797
+ const resolvedPath = this._resolveFilePath( cleanPath );
3798
+ let assetData = this.assets[ resolvedPath ];
3799
+
3800
+ // Fallback to unresolved path
3801
+ if ( ! assetData ) {
3802
+
3803
+ assetData = this.assets[ cleanPath ];
3804
+
3805
+ }
3806
+
3807
+ // Last resort: search by basename
3808
+ if ( ! assetData ) {
3809
+
3810
+ const baseName = cleanPath.split( '/' ).pop();
3811
+
3812
+ for ( const key in this.assets ) {
3813
+
3814
+ if ( key.endsWith( baseName ) || key.endsWith( '/' + baseName ) ) {
3815
+
3816
+ return this._createTextureFromData( this.assets[ key ], textureAttrs, transformAttrs );
3817
+
3818
+ }
3819
+
3820
+ }
3821
+
3822
+ // Standalone .usd/.usda/.usdc files don't pre-load assets; treat the
3823
+ // resolved path as a URL relative to basePath so the browser fetches
3824
+ // the texture from disk next to the layer.
3825
+
3826
+ if ( this.basePath ) {
3827
+
3828
+ return this._createTextureFromData( resolvedPath, textureAttrs, transformAttrs );
3829
+
3830
+ }
3831
+
3832
+ // Try loading via LoadingManager if available
3833
+ if ( this.manager ) {
3834
+
3835
+ const url = this.manager.resolveURL( baseName );
3836
+ if ( url !== baseName ) {
3837
+
3838
+ // URL modifier found a match - load it
3839
+ return this._createTextureFromData( url, textureAttrs, transformAttrs );
3840
+
3841
+ }
3842
+
3843
+ }
3844
+
3845
+ console.warn( 'USDLoader: Texture not found:', cleanPath );
3846
+ return null;
3847
+
3848
+ }
3849
+
3850
+ return this._createTextureFromData( assetData, textureAttrs, transformAttrs );
3851
+
3852
+ }
3853
+
3854
+ _createTextureFromData( data, textureAttrs, transformAttrs ) {
3855
+
3856
+ if ( ! data ) return null;
3857
+
3858
+ const scope = this;
3859
+ const texture = new Texture();
3860
+
3861
+ let url;
3862
+
3863
+ if ( typeof data === 'string' ) {
3864
+
3865
+ url = data;
3866
+
3867
+ } else if ( data instanceof Uint8Array || data instanceof ArrayBuffer ) {
3868
+
3869
+ const blob = new Blob( [ data ] );
3870
+ url = URL.createObjectURL( blob );
3871
+
3872
+ } else {
3873
+
3874
+ return null;
3875
+
3876
+ }
3877
+
3878
+ const image = new Image();
3879
+
3880
+ this.texturePromises.push( new Promise( ( resolve ) => {
3881
+
3882
+ image.onload = function () {
3883
+
3884
+ texture.image = image;
3885
+
3886
+ if ( textureAttrs ) {
3887
+
3888
+ texture.wrapS = scope._getWrapMode( textureAttrs[ 'inputs:wrapS' ] );
3889
+ texture.wrapT = scope._getWrapMode( textureAttrs[ 'inputs:wrapT' ] );
3890
+
3891
+ }
3892
+
3893
+ scope._applyTextureTransforms( texture, transformAttrs );
3894
+ texture.needsUpdate = true;
3895
+
3896
+ if ( typeof data !== 'string' ) {
3897
+
3898
+ URL.revokeObjectURL( url );
3899
+
3900
+ }
3901
+
3902
+ resolve();
3903
+
3904
+ };
3905
+
3906
+ image.onerror = function () {
3907
+
3908
+ console.warn( 'USDLoader: Failed to load texture:', url );
3909
+
3910
+ if ( typeof data !== 'string' ) {
3911
+
3912
+ URL.revokeObjectURL( url );
3913
+
3914
+ }
3915
+
3916
+ resolve();
3917
+
3918
+ };
3919
+
3920
+ } ) );
3921
+
3922
+ image.src = url;
3923
+
3924
+ return texture;
3925
+
3926
+ }
3927
+
3928
+ _getWrapMode( wrapValue ) {
3929
+
3930
+ if ( wrapValue === 'repeat' ) return RepeatWrapping;
3931
+ if ( wrapValue === 'mirror' ) return MirroredRepeatWrapping;
3932
+ if ( wrapValue === 'clamp' ) return ClampToEdgeWrapping;
3933
+ return RepeatWrapping;
3934
+
3935
+ }
3936
+
3937
+ // ========================================================================
3938
+ // Skeletal Animation
3939
+ // ========================================================================
3940
+
3941
+ _buildSkeleton( path ) {
3942
+
3943
+ const attrs = this._getAttributes( path );
3944
+
3945
+ // Get joint names (paths like "root", "root/body_joint", etc.)
3946
+ const joints = attrs[ 'joints' ];
3947
+ if ( ! joints || joints.length === 0 ) return null;
3948
+
3949
+ // Get bind transforms (world-space bind pose matrices)
3950
+ // These can be nested arrays (USDA) or flat arrays (USDC)
3951
+ const rawBindTransforms = attrs[ 'bindTransforms' ];
3952
+ const rawRestTransforms = attrs[ 'restTransforms' ];
3953
+
3954
+ const bindTransforms = this._flattenMatrixArray( rawBindTransforms, joints.length );
3955
+ const restTransforms = this._flattenMatrixArray( rawRestTransforms, joints.length );
3956
+
3957
+ // Build bones
3958
+ const bones = [];
3959
+ const bonesByPath = {};
3960
+ const boneInverses = [];
3961
+
3962
+ for ( let i = 0; i < joints.length; i ++ ) {
3963
+
3964
+ const jointPath = joints[ i ];
3965
+ const jointName = jointPath.split( '/' ).pop();
3966
+
3967
+ const bone = new Bone();
3968
+ bone.name = jointName;
3969
+ bones.push( bone );
3970
+ bonesByPath[ jointPath ] = { bone, index: i };
3971
+
3972
+ // Compute inverse bind matrix
3973
+ if ( bindTransforms && bindTransforms.length >= ( i + 1 ) * 16 ) {
3974
+
3975
+ const bindMatrix = new Matrix4();
3976
+ // USD matrices are row-major, Three.js is column-major - need to transpose
3977
+ const m = bindTransforms.slice( i * 16, ( i + 1 ) * 16 );
3978
+ bindMatrix.set(
3979
+ m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
3980
+ m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ],
3981
+ m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ],
3982
+ m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
3983
+ );
3984
+ const inverseBindMatrix = bindMatrix.clone().invert();
3985
+ boneInverses.push( inverseBindMatrix );
3986
+
3987
+ } else {
3988
+
3989
+ boneInverses.push( new Matrix4() );
3990
+
3991
+ }
3992
+
3993
+ }
3994
+
3995
+ // Build parent-child relationships based on joint paths
3996
+ for ( let i = 0; i < joints.length; i ++ ) {
3997
+
3998
+ const jointPath = joints[ i ];
3999
+ const parts = jointPath.split( '/' );
4000
+
4001
+ if ( parts.length > 1 ) {
4002
+
4003
+ const parentPath = parts.slice( 0, - 1 ).join( '/' );
4004
+ const parentData = bonesByPath[ parentPath ];
4005
+
4006
+ if ( parentData ) {
4007
+
4008
+ parentData.bone.add( bones[ i ] );
4009
+
4010
+ }
4011
+
4012
+ }
4013
+
4014
+ }
4015
+
4016
+ // Apply rest transforms as bone local transforms.
4017
+ // Rest transforms are the skeleton's default local-space pose and match
4018
+ // the reference frame used by SkelAnimation data. Bind transforms are
4019
+ // world-space matrices used only for computing inverse bind matrices.
4020
+ if ( restTransforms && restTransforms.length >= joints.length * 16 ) {
4021
+
4022
+ for ( let i = 0; i < joints.length; i ++ ) {
4023
+
4024
+ const matrix = new Matrix4();
4025
+ const m = restTransforms.slice( i * 16, ( i + 1 ) * 16 );
4026
+ matrix.set(
4027
+ m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
4028
+ m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ],
4029
+ m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ],
4030
+ m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
4031
+ );
4032
+ matrix.decompose( bones[ i ].position, bones[ i ].quaternion, bones[ i ].scale );
4033
+
4034
+ }
4035
+
4036
+ }
4037
+
4038
+ // Find root bone(s) - bones without a parent bone
4039
+ const rootBones = bones.filter( bone => ! bone.parent || ! bone.parent.isBone );
4040
+
4041
+ // Get animation source path
4042
+ const animSourceSpec = this.specsByPath[ path + '.skel:animationSource' ];
4043
+ let animationPath = null;
4044
+ if ( animSourceSpec && animSourceSpec.fields.targetPaths && animSourceSpec.fields.targetPaths.length > 0 ) {
4045
+
4046
+ animationPath = animSourceSpec.fields.targetPaths[ 0 ];
4047
+
4048
+ }
4049
+
4050
+ return {
4051
+ skeleton: new Skeleton( bones, boneInverses ),
4052
+ joints: joints,
4053
+ rootBones: rootBones,
4054
+ animationPath: animationPath,
4055
+ path: path
4056
+ };
4057
+
4058
+ }
4059
+
4060
+ _bindSkeletons() {
4061
+
4062
+ for ( const meshData of this.skinnedMeshes ) {
4063
+
4064
+ const { mesh, skeletonPath, localJoints, geomBindTransform } = meshData;
4065
+
4066
+ let skeletonData = null;
4067
+
4068
+ // Try exact match first
4069
+ if ( skeletonPath && this.skeletons[ skeletonPath ] ) {
4070
+
4071
+ skeletonData = this.skeletons[ skeletonPath ];
4072
+
4073
+ }
4074
+
4075
+ // Try includes match as fallback
4076
+ if ( ! skeletonData ) {
4077
+
4078
+ for ( const skelPath in this.skeletons ) {
4079
+
4080
+ if ( skeletonPath && ( skeletonPath.includes( skelPath ) || skelPath.includes( skeletonPath ) ) ) {
4081
+
4082
+ skeletonData = this.skeletons[ skelPath ];
4083
+ break;
4084
+
4085
+ }
4086
+
4087
+ }
4088
+
4089
+ }
4090
+
4091
+ // Fallback to first skeleton for single-skeleton files
4092
+ if ( ! skeletonData ) {
4093
+
4094
+ const skeletonPaths = Object.keys( this.skeletons );
4095
+ if ( skeletonPaths.length > 0 ) {
4096
+
4097
+ skeletonData = this.skeletons[ skeletonPaths[ 0 ] ];
4098
+
4099
+ }
4100
+
4101
+ }
4102
+
4103
+ if ( ! skeletonData ) {
4104
+
4105
+ console.warn( 'USDComposer: No skeleton found for skinned mesh', mesh.name );
4106
+ continue;
4107
+
4108
+ }
4109
+
4110
+ const { skeleton, rootBones, joints } = skeletonData;
4111
+
4112
+ if ( localJoints && localJoints.length > 0 ) {
4113
+
4114
+ const skinIndex = mesh.geometry.attributes.skinIndex;
4115
+ if ( skinIndex ) {
4116
+
4117
+ const localToGlobal = [];
4118
+ for ( let i = 0; i < localJoints.length; i ++ ) {
4119
+
4120
+ const jointName = localJoints[ i ];
4121
+ const globalIdx = joints.indexOf( jointName );
4122
+ localToGlobal[ i ] = globalIdx >= 0 ? globalIdx : 0;
4123
+
4124
+ }
4125
+
4126
+ const arr = skinIndex.array;
4127
+ for ( let i = 0; i < arr.length; i ++ ) {
4128
+
4129
+ const localIdx = arr[ i ];
4130
+ if ( localIdx < localToGlobal.length ) {
4131
+
4132
+ arr[ i ] = localToGlobal[ localIdx ];
4133
+
4134
+ }
4135
+
4136
+ }
4137
+
4138
+ }
4139
+
4140
+ }
4141
+
4142
+ for ( const rootBone of rootBones ) {
4143
+
4144
+ mesh.add( rootBone );
4145
+
4146
+ }
4147
+
4148
+ // Use geomBindTransform if available, otherwise fall back to identity.
4149
+ // Estimating bind transforms from vertex/joint samples is not robust and can
4150
+ // produce severe skinning distortion for valid assets.
4151
+ const bindMatrix = new Matrix4();
4152
+
4153
+ if ( geomBindTransform && geomBindTransform.length === 16 ) {
4154
+
4155
+ // USD matrices are row-major, Three.js is column-major - need to transpose
4156
+ const m = geomBindTransform;
4157
+ bindMatrix.set(
4158
+ m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
4159
+ m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ],
4160
+ m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ],
4161
+ m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
4162
+ );
4163
+
4164
+ }
4165
+
4166
+ mesh.bind( skeleton, bindMatrix );
4167
+
4168
+ }
4169
+
4170
+ }
4171
+
4172
+ _buildAnimations() {
4173
+
4174
+ const animations = [];
4175
+
4176
+ // Find all SkelAnimation prims
4177
+ for ( const path in this.specsByPath ) {
4178
+
4179
+ const spec = this.specsByPath[ path ];
4180
+ if ( spec.specType !== SpecType.Prim ) continue;
4181
+ if ( spec.fields.typeName !== 'SkelAnimation' ) continue;
4182
+
4183
+ const clip = this._buildAnimationClip( path );
4184
+ if ( clip ) {
4185
+
4186
+ animations.push( clip );
4187
+
4188
+ }
4189
+
4190
+ }
4191
+
4192
+ // Build transform animations from time-sampled xformOps
4193
+ const transformTracks = this._buildTransformAnimations();
4194
+ if ( transformTracks.length > 0 ) {
4195
+
4196
+ animations.push( new AnimationClip( 'TransformAnimation', - 1, transformTracks ) );
4197
+
4198
+ }
4199
+
4200
+ return animations;
4201
+
4202
+ }
4203
+
4204
+ _buildTransformAnimations() {
4205
+
4206
+ const tracks = [];
4207
+
4208
+ for ( const path in this.specsByPath ) {
4209
+
4210
+ const spec = this.specsByPath[ path ];
4211
+ if ( spec.specType !== SpecType.Prim ) continue;
4212
+
4213
+ const typeName = spec.fields?.typeName;
4214
+ if ( typeName !== 'Xform' && typeName !== 'Scope' && typeName !== 'Mesh' ) continue;
4215
+
4216
+ const objectName = path.split( '/' ).pop();
4217
+
4218
+ // Check for animated xformOp:orient
4219
+ const orientPath = path + '.xformOp:orient';
4220
+ const orientSpec = this.specsByPath[ orientPath ];
4221
+ if ( orientSpec?.fields?.timeSamples ) {
4222
+
4223
+ const { times, values } = orientSpec.fields.timeSamples;
4224
+ const keyframeTimes = [];
4225
+ const keyframeValues = [];
4226
+
4227
+ for ( let i = 0; i < times.length; i ++ ) {
4228
+
4229
+ keyframeTimes.push( times[ i ] / this.fps );
4230
+
4231
+ const q = values[ i ];
4232
+ keyframeValues.push( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] );
4233
+
4234
+ }
4235
+
4236
+ if ( keyframeTimes.length > 0 ) {
4237
+
4238
+ tracks.push( new QuaternionKeyframeTrack(
4239
+ objectName + '.quaternion',
4240
+ new Float32Array( keyframeTimes ),
4241
+ new Float32Array( keyframeValues )
4242
+ ) );
4243
+
4244
+ }
4245
+
4246
+ }
4247
+
4248
+ // Check for animated xformOp:rotateXYZ
4249
+ const rotateXYZPath = path + '.xformOp:rotateXYZ';
4250
+ const rotateXYZSpec = this.specsByPath[ rotateXYZPath ];
4251
+ if ( rotateXYZSpec?.fields?.timeSamples ) {
4252
+
4253
+ const { times, values } = rotateXYZSpec.fields.timeSamples;
4254
+ const keyframeTimes = [];
4255
+ const keyframeValues = [];
4256
+ const tempEuler = new Euler();
4257
+ const tempQuat = new Quaternion();
4258
+
4259
+ for ( let i = 0; i < times.length; i ++ ) {
4260
+
4261
+ keyframeTimes.push( times[ i ] / this.fps );
4262
+
4263
+ const r = values[ i ];
4264
+ // USD rotateXYZ: matrix = Rx * Ry * Rz, use 'ZYX' order in Three.js
4265
+ tempEuler.set(
4266
+ r[ 0 ] * Math.PI / 180,
4267
+ r[ 1 ] * Math.PI / 180,
4268
+ r[ 2 ] * Math.PI / 180,
4269
+ 'ZYX'
4270
+ );
4271
+ tempQuat.setFromEuler( tempEuler );
4272
+ keyframeValues.push( tempQuat.x, tempQuat.y, tempQuat.z, tempQuat.w );
4273
+
4274
+ }
4275
+
4276
+ if ( keyframeTimes.length > 0 ) {
4277
+
4278
+ tracks.push( new QuaternionKeyframeTrack(
4279
+ objectName + '.quaternion',
4280
+ new Float32Array( keyframeTimes ),
4281
+ new Float32Array( keyframeValues )
4282
+ ) );
4283
+
4284
+ }
4285
+
4286
+ }
4287
+
4288
+ // Check for animated xformOp:translate
4289
+ const translatePath = path + '.xformOp:translate';
4290
+ const translateSpec = this.specsByPath[ translatePath ];
4291
+ if ( translateSpec?.fields?.timeSamples ) {
4292
+
4293
+ const { times, values } = translateSpec.fields.timeSamples;
4294
+ const keyframeTimes = [];
4295
+ const keyframeValues = [];
4296
+
4297
+ for ( let i = 0; i < times.length; i ++ ) {
4298
+
4299
+ keyframeTimes.push( times[ i ] / this.fps );
4300
+
4301
+ const t = values[ i ];
4302
+ keyframeValues.push( t[ 0 ], t[ 1 ], t[ 2 ] );
4303
+
4304
+ }
4305
+
4306
+ if ( keyframeTimes.length > 0 ) {
4307
+
4308
+ tracks.push( new VectorKeyframeTrack(
4309
+ objectName + '.position',
4310
+ new Float32Array( keyframeTimes ),
4311
+ new Float32Array( keyframeValues )
4312
+ ) );
4313
+
4314
+ }
4315
+
4316
+ }
4317
+
4318
+ // Check for animated xformOp:scale
4319
+ const scalePath = path + '.xformOp:scale';
4320
+ const scaleSpec = this.specsByPath[ scalePath ];
4321
+ if ( scaleSpec?.fields?.timeSamples ) {
4322
+
4323
+ const { times, values } = scaleSpec.fields.timeSamples;
4324
+ const keyframeTimes = [];
4325
+ const keyframeValues = [];
4326
+
4327
+ for ( let i = 0; i < times.length; i ++ ) {
4328
+
4329
+ keyframeTimes.push( times[ i ] / this.fps );
4330
+
4331
+ const s = values[ i ];
4332
+ keyframeValues.push( s[ 0 ], s[ 1 ], s[ 2 ] );
4333
+
4334
+ }
4335
+
4336
+ if ( keyframeTimes.length > 0 ) {
4337
+
4338
+ tracks.push( new VectorKeyframeTrack(
4339
+ objectName + '.scale',
4340
+ new Float32Array( keyframeTimes ),
4341
+ new Float32Array( keyframeValues )
4342
+ ) );
4343
+
4344
+ }
4345
+
4346
+ }
4347
+
4348
+ // Check for animated xformOp:transform (matrix animations)
4349
+ // These can have suffixes like xformOp:transform:transform
4350
+ const properties = spec.fields?.properties || [];
4351
+ for ( const prop of properties ) {
4352
+
4353
+ if ( ! prop.startsWith( 'xformOp:transform' ) ) continue;
4354
+
4355
+ const transformPath = path + '.' + prop;
4356
+ const transformSpec = this.specsByPath[ transformPath ];
4357
+
4358
+ if ( ! transformSpec?.fields?.timeSamples ) continue;
4359
+
4360
+ const { times, values } = transformSpec.fields.timeSamples;
4361
+ const positionTimes = [];
4362
+ const positionValues = [];
4363
+ const quaternionTimes = [];
4364
+ const quaternionValues = [];
4365
+ const scaleTimes = [];
4366
+ const scaleValues = [];
4367
+
4368
+ const matrix = new Matrix4();
4369
+ const position = new Vector3();
4370
+ const quaternion = new Quaternion();
4371
+ const scale = new Vector3();
4372
+
4373
+ for ( let i = 0; i < times.length; i ++ ) {
4374
+
4375
+ const m = values[ i ];
4376
+ if ( ! m || m.length < 16 ) continue;
4377
+
4378
+ const t = times[ i ] / this.fps;
4379
+
4380
+ // USD matrices are row-major, Three.js is column-major
4381
+ matrix.set(
4382
+ m[ 0 ], m[ 4 ], m[ 8 ], m[ 12 ],
4383
+ m[ 1 ], m[ 5 ], m[ 9 ], m[ 13 ],
4384
+ m[ 2 ], m[ 6 ], m[ 10 ], m[ 14 ],
4385
+ m[ 3 ], m[ 7 ], m[ 11 ], m[ 15 ]
4386
+ );
4387
+
4388
+ matrix.decompose( position, quaternion, scale );
4389
+
4390
+ positionTimes.push( t );
4391
+ positionValues.push( position.x, position.y, position.z );
4392
+
4393
+ quaternionTimes.push( t );
4394
+ quaternionValues.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
4395
+
4396
+ scaleTimes.push( t );
4397
+ scaleValues.push( scale.x, scale.y, scale.z );
4398
+
4399
+ }
4400
+
4401
+ if ( positionTimes.length > 0 ) {
4402
+
4403
+ tracks.push( new VectorKeyframeTrack(
4404
+ objectName + '.position',
4405
+ new Float32Array( positionTimes ),
4406
+ new Float32Array( positionValues )
4407
+ ) );
4408
+
4409
+ tracks.push( new QuaternionKeyframeTrack(
4410
+ objectName + '.quaternion',
4411
+ new Float32Array( quaternionTimes ),
4412
+ new Float32Array( quaternionValues )
4413
+ ) );
4414
+
4415
+ tracks.push( new VectorKeyframeTrack(
4416
+ objectName + '.scale',
4417
+ new Float32Array( scaleTimes ),
4418
+ new Float32Array( scaleValues )
4419
+ ) );
4420
+
4421
+ }
4422
+
4423
+ break; // Only process first transform op
4424
+
4425
+ }
4426
+
4427
+ }
4428
+
4429
+ return tracks;
4430
+
4431
+ }
4432
+
4433
+ _buildAnimationClip( path ) {
4434
+
4435
+ const attrs = this._getAttributes( path );
4436
+ const joints = attrs[ 'joints' ];
4437
+
4438
+ if ( ! joints || joints.length === 0 ) return null;
4439
+
4440
+ const tracks = [];
4441
+
4442
+ // Get rotation time samples
4443
+ const rotationsAttr = this._getTimeSampledAttribute( path, 'rotations' );
4444
+ if ( rotationsAttr && rotationsAttr.times && rotationsAttr.values ) {
4445
+
4446
+ const { times, values } = rotationsAttr;
4447
+
4448
+ for ( let jointIdx = 0; jointIdx < joints.length; jointIdx ++ ) {
4449
+
4450
+ const jointName = joints[ jointIdx ].split( '/' ).pop();
4451
+ const keyframeTimes = [];
4452
+ const keyframeValues = [];
4453
+
4454
+ for ( let t = 0; t < times.length; t ++ ) {
4455
+
4456
+ const quatData = values[ t ];
4457
+ if ( ! quatData || quatData.length < ( jointIdx + 1 ) * 4 ) continue;
4458
+
4459
+ keyframeTimes.push( times[ t ] / this.fps );
4460
+
4461
+ // USD GfQuatf stores imaginary (x,y,z) first, then real (w)
4462
+ // This matches Three.js quaternion order (x,y,z,w)
4463
+ const x = quatData[ jointIdx * 4 + 0 ];
4464
+ const y = quatData[ jointIdx * 4 + 1 ];
4465
+ const z = quatData[ jointIdx * 4 + 2 ];
4466
+ const w = quatData[ jointIdx * 4 + 3 ];
4467
+ keyframeValues.push( x, y, z, w );
4468
+
4469
+ }
4470
+
4471
+ if ( keyframeTimes.length > 0 ) {
4472
+
4473
+ tracks.push( new QuaternionKeyframeTrack(
4474
+ jointName + '.quaternion',
4475
+ new Float32Array( keyframeTimes ),
4476
+ new Float32Array( keyframeValues )
4477
+ ) );
4478
+
4479
+ }
4480
+
4481
+ }
4482
+
4483
+ }
4484
+
4485
+ // Get translation time samples
4486
+ const translationsAttr = this._getTimeSampledAttribute( path, 'translations' );
4487
+ if ( translationsAttr && translationsAttr.times && translationsAttr.values ) {
4488
+
4489
+ const { times, values } = translationsAttr;
4490
+
4491
+ for ( let jointIdx = 0; jointIdx < joints.length; jointIdx ++ ) {
4492
+
4493
+ const jointName = joints[ jointIdx ].split( '/' ).pop();
4494
+ const keyframeTimes = [];
4495
+ const keyframeValues = [];
4496
+
4497
+ for ( let t = 0; t < times.length; t ++ ) {
4498
+
4499
+ const transData = values[ t ];
4500
+ if ( ! transData || transData.length < ( jointIdx + 1 ) * 3 ) continue;
4501
+
4502
+ keyframeTimes.push( times[ t ] / this.fps );
4503
+ keyframeValues.push(
4504
+ transData[ jointIdx * 3 + 0 ],
4505
+ transData[ jointIdx * 3 + 1 ],
4506
+ transData[ jointIdx * 3 + 2 ]
4507
+ );
4508
+
4509
+ }
4510
+
4511
+ if ( keyframeTimes.length > 0 ) {
4512
+
4513
+ tracks.push( new VectorKeyframeTrack(
4514
+ jointName + '.position',
4515
+ new Float32Array( keyframeTimes ),
4516
+ new Float32Array( keyframeValues )
4517
+ ) );
4518
+
4519
+ }
4520
+
4521
+ }
4522
+
4523
+ }
4524
+
4525
+ // Get scale time samples
4526
+ const scalesAttr = this._getTimeSampledAttribute( path, 'scales' );
4527
+ if ( scalesAttr && scalesAttr.times && scalesAttr.values ) {
4528
+
4529
+ const { times, values } = scalesAttr;
4530
+
4531
+ for ( let jointIdx = 0; jointIdx < joints.length; jointIdx ++ ) {
4532
+
4533
+ const jointName = joints[ jointIdx ].split( '/' ).pop();
4534
+ const keyframeTimes = [];
4535
+ const keyframeValues = [];
4536
+
4537
+ for ( let t = 0; t < times.length; t ++ ) {
4538
+
4539
+ const scaleData = values[ t ];
4540
+ if ( ! scaleData || scaleData.length < ( jointIdx + 1 ) * 3 ) continue;
4541
+
4542
+ keyframeTimes.push( times[ t ] / this.fps );
4543
+ keyframeValues.push(
4544
+ scaleData[ jointIdx * 3 + 0 ],
4545
+ scaleData[ jointIdx * 3 + 1 ],
4546
+ scaleData[ jointIdx * 3 + 2 ]
4547
+ );
4548
+
4549
+ }
4550
+
4551
+ if ( keyframeTimes.length > 0 ) {
4552
+
4553
+ tracks.push( new VectorKeyframeTrack(
4554
+ jointName + '.scale',
4555
+ new Float32Array( keyframeTimes ),
4556
+ new Float32Array( keyframeValues )
4557
+ ) );
4558
+
4559
+ }
4560
+
4561
+ }
4562
+
4563
+ }
4564
+
4565
+ if ( tracks.length === 0 ) return null;
4566
+
4567
+ const clipName = path.split( '/' ).pop();
4568
+ return new AnimationClip( clipName, - 1, tracks );
4569
+
4570
+ }
4571
+
4572
+ _getTimeSampledAttribute( primPath, attrName ) {
4573
+
4574
+ // Look for the attribute spec with time samples
4575
+ const attrPath = primPath + '.' + attrName;
4576
+ const attrSpec = this.specsByPath[ attrPath ];
4577
+
4578
+ if ( attrSpec && attrSpec.fields.timeSamples ) {
4579
+
4580
+ const timeSamples = attrSpec.fields.timeSamples;
4581
+ if ( timeSamples.times && timeSamples.values ) {
4582
+
4583
+ return timeSamples;
4584
+
4585
+ }
4586
+
4587
+ }
4588
+
4589
+ return null;
4590
+
4591
+ }
4592
+
4593
+ _flattenMatrixArray( matrices, numMatrices ) {
4594
+
4595
+ if ( ! matrices || matrices.length === 0 ) return null;
4596
+
4597
+ if ( typeof matrices[ 0 ] === 'number' ) return matrices;
4598
+
4599
+ const flatArray = [];
4600
+
4601
+ for ( let m = 0; m < numMatrices; m ++ ) {
4602
+
4603
+ for ( let row = 0; row < 4; row ++ ) {
4604
+
4605
+ const rowData = matrices[ m * 4 + row ];
4606
+
4607
+ if ( rowData && rowData.length === 4 ) {
4608
+
4609
+ flatArray.push( rowData[ 0 ], rowData[ 1 ], rowData[ 2 ], rowData[ 3 ] );
4610
+
4611
+ } else {
4612
+
4613
+ flatArray.push( row === 0 ? 1 : 0, row === 1 ? 1 : 0, row === 2 ? 1 : 0, row === 3 ? 1 : 0 );
4614
+
4615
+ }
4616
+
4617
+ }
4618
+
4619
+ }
4620
+
4621
+ return flatArray;
4622
+
4623
+ }
4624
+
4625
+ }
4626
+
4627
+ export { USDComposer, SpecType };