@plastic-software/three 0.183.4 → 0.184.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/build/three.cjs +773 -286
  2. package/build/three.core.js +372 -110
  3. package/build/three.core.min.js +1 -1
  4. package/build/three.module.js +426 -180
  5. package/build/three.module.min.js +1 -1
  6. package/build/three.tsl.js +7 -1
  7. package/build/three.tsl.min.js +1 -1
  8. package/build/three.webgpu.js +2979 -1281
  9. package/build/three.webgpu.min.js +1 -1
  10. package/build/three.webgpu.nodes.js +2942 -1281
  11. package/build/three.webgpu.nodes.min.js +1 -1
  12. package/examples/jsm/Addons.js +11 -0
  13. package/examples/jsm/animation/CCDIKSolver.js +5 -1
  14. package/examples/jsm/controls/ArcballControls.js +4 -1
  15. package/examples/jsm/controls/DragControls.js +2 -2
  16. package/examples/jsm/controls/FirstPersonControls.js +58 -54
  17. package/examples/jsm/controls/FlyControls.js +4 -0
  18. package/examples/jsm/controls/OrbitControls.js +2 -2
  19. package/examples/jsm/controls/TrackballControls.js +2 -2
  20. package/examples/jsm/controls/TransformControls.js +34 -2
  21. package/examples/jsm/csm/CSMShadowNode.js +6 -2
  22. package/examples/jsm/exporters/GLTFExporter.js +21 -5
  23. package/examples/jsm/geometries/TextGeometry.js +18 -0
  24. package/examples/jsm/helpers/LightProbeGridHelper.js +221 -0
  25. package/examples/jsm/inspector/Extension.js +13 -0
  26. package/examples/jsm/inspector/Inspector.js +169 -114
  27. package/examples/jsm/inspector/RendererInspector.js +2 -2
  28. package/examples/jsm/inspector/extensions/extensions.json +6 -0
  29. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +916 -0
  30. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +281 -0
  31. package/examples/jsm/inspector/tabs/Memory.js +128 -0
  32. package/examples/jsm/inspector/tabs/Parameters.js +34 -2
  33. package/examples/jsm/inspector/tabs/Performance.js +2 -2
  34. package/examples/jsm/inspector/tabs/Settings.js +264 -0
  35. package/examples/jsm/inspector/tabs/Timeline.js +1611 -0
  36. package/examples/jsm/inspector/tabs/Viewer.js +105 -3
  37. package/examples/jsm/inspector/ui/Graph.js +2 -2
  38. package/examples/jsm/inspector/ui/List.js +1 -1
  39. package/examples/jsm/inspector/ui/Profiler.js +273 -176
  40. package/examples/jsm/inspector/ui/Style.js +64 -10
  41. package/examples/jsm/inspector/ui/Tab.js +39 -7
  42. package/examples/jsm/inspector/ui/Values.js +39 -2
  43. package/examples/jsm/inspector/ui/utils.js +13 -0
  44. package/examples/jsm/interaction/InteractionManager.js +226 -0
  45. package/examples/jsm/libs/meshopt_decoder.module.js +8 -8
  46. package/examples/jsm/lighting/DynamicLighting.js +82 -0
  47. package/examples/jsm/lighting/LightProbeGrid.js +651 -0
  48. package/examples/jsm/lines/LineMaterial.js +1 -1
  49. package/examples/jsm/loaders/EXRLoader.js +682 -43
  50. package/examples/jsm/loaders/FBXLoader.js +233 -33
  51. package/examples/jsm/loaders/GLTFLoader.js +24 -7
  52. package/examples/jsm/loaders/HDRLoader.js +1 -1
  53. package/examples/jsm/loaders/KTX2Loader.js +8 -2
  54. package/examples/jsm/loaders/LDrawLoader.js +39 -47
  55. package/examples/jsm/loaders/SVGLoader.js +1 -1
  56. package/examples/jsm/loaders/VTKLoader.js +5 -1
  57. package/examples/jsm/loaders/collada/ColladaComposer.js +101 -7
  58. package/examples/jsm/loaders/collada/ColladaParser.js +19 -4
  59. package/examples/jsm/loaders/usd/USDAParser.js +6 -0
  60. package/examples/jsm/loaders/usd/USDCParser.js +26 -0
  61. package/examples/jsm/loaders/usd/USDComposer.js +656 -103
  62. package/examples/jsm/misc/GPUComputationRenderer.js +2 -0
  63. package/examples/jsm/misc/RollerCoaster.js +42 -4
  64. package/examples/jsm/modifiers/TessellateModifier.js +1 -1
  65. package/examples/jsm/objects/Reflector.js +73 -25
  66. package/examples/jsm/objects/Sky.js +14 -2
  67. package/examples/jsm/objects/SkyMesh.js +23 -6
  68. package/examples/jsm/renderers/Projector.js +18 -38
  69. package/examples/jsm/renderers/SVGRenderer.js +6 -25
  70. package/examples/jsm/transpiler/GLSLDecoder.js +2 -2
  71. package/examples/jsm/tsl/WebGLNodesHandler.js +605 -0
  72. package/examples/jsm/tsl/display/AfterImageNode.js +10 -0
  73. package/examples/jsm/tsl/display/AnamorphicNode.js +11 -0
  74. package/examples/jsm/tsl/display/BilateralBlurNode.js +10 -0
  75. package/examples/jsm/tsl/display/ChromaticAberrationNode.js +3 -36
  76. package/examples/jsm/tsl/display/FSR1Node.js +477 -0
  77. package/examples/jsm/tsl/display/GTAONode.js +2 -1
  78. package/examples/jsm/tsl/display/GaussianBlurNode.js +10 -0
  79. package/examples/jsm/tsl/display/GodraysNode.js +2 -11
  80. package/examples/jsm/tsl/display/OutlineNode.js +66 -16
  81. package/examples/jsm/tsl/display/SSGINode.js +0 -4
  82. package/examples/jsm/tsl/display/SharpenNode.js +283 -0
  83. package/examples/jsm/tsl/display/TAAUNode.js +835 -0
  84. package/examples/jsm/tsl/display/TRAANode.js +48 -7
  85. package/examples/jsm/tsl/lighting/DynamicLightsNode.js +300 -0
  86. package/examples/jsm/tsl/lighting/data/AmbientLightDataNode.js +61 -0
  87. package/examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js +111 -0
  88. package/examples/jsm/tsl/lighting/data/HemisphereLightDataNode.js +99 -0
  89. package/examples/jsm/tsl/lighting/data/PointLightDataNode.js +134 -0
  90. package/examples/jsm/tsl/lighting/data/SpotLightDataNode.js +161 -0
  91. package/examples/jsm/tsl/math/Bayer.js +13 -2
  92. package/examples/jsm/utils/BufferGeometryUtils.js +2 -3
  93. package/examples/jsm/utils/ColorUtils.js +76 -0
  94. package/examples/jsm/utils/SkeletonUtils.js +14 -8
  95. package/examples/jsm/webxr/XRHandMeshModel.js +36 -10
  96. package/examples/jsm/webxr/XRHandModelFactory.js +2 -1
  97. package/package.json +4 -4
  98. package/src/Three.Core.js +1 -0
  99. package/src/Three.TSL.js +6 -0
  100. package/src/Three.WebGPU.Nodes.js +3 -0
  101. package/src/Three.WebGPU.js +6 -0
  102. package/src/animation/AnimationAction.js +11 -1
  103. package/src/audio/AudioContext.js +2 -2
  104. package/src/constants.js +1 -1
  105. package/src/core/BufferAttribute.js +13 -1
  106. package/src/core/Clock.js +1 -1
  107. package/src/core/Object3D.js +1 -5
  108. package/src/core/RenderTarget.js +1 -0
  109. package/src/extras/curves/CatmullRomCurve3.js +3 -2
  110. package/src/loaders/AudioLoader.js +11 -1
  111. package/src/loaders/DataTextureLoader.js +6 -4
  112. package/src/loaders/FileLoader.js +1 -2
  113. package/src/loaders/ImageBitmapLoader.js +4 -6
  114. package/src/loaders/MaterialLoader.js +1 -1
  115. package/src/loaders/ObjectLoader.js +25 -4
  116. package/src/loaders/nodes/NodeObjectLoader.js +18 -0
  117. package/src/materials/MeshToonMaterial.js +1 -1
  118. package/src/materials/nodes/Line2NodeMaterial.js +27 -0
  119. package/src/materials/nodes/NodeMaterial.js +0 -27
  120. package/src/materials/nodes/manager/NodeMaterialObserver.js +188 -89
  121. package/src/math/Line3.js +3 -0
  122. package/src/math/Matrix2.js +13 -9
  123. package/src/math/Matrix3.js +13 -9
  124. package/src/math/Matrix4.js +13 -9
  125. package/src/math/Plane.js +4 -3
  126. package/src/math/Triangle.js +1 -1
  127. package/src/math/Vector2.js +11 -7
  128. package/src/math/Vector3.js +12 -8
  129. package/src/math/Vector4.js +13 -9
  130. package/src/nodes/Nodes.js +0 -1
  131. package/src/nodes/TSL.js +1 -1
  132. package/src/nodes/accessors/BufferAttributeNode.js +9 -3
  133. package/src/nodes/accessors/CubeTextureNode.js +7 -1
  134. package/src/nodes/accessors/MaterialProperties.js +2 -5
  135. package/src/nodes/accessors/Object3DNode.js +1 -1
  136. package/src/nodes/accessors/ReferenceBaseNode.js +2 -2
  137. package/src/nodes/accessors/ReferenceNode.js +4 -4
  138. package/src/nodes/accessors/SceneProperties.js +2 -8
  139. package/src/nodes/accessors/StorageBufferNode.js +10 -4
  140. package/src/nodes/accessors/StorageTextureNode.js +4 -9
  141. package/src/nodes/accessors/TextureNode.js +10 -2
  142. package/src/nodes/accessors/UniformArrayNode.js +2 -2
  143. package/src/nodes/code/FunctionCallNode.js +1 -1
  144. package/src/nodes/code/FunctionNode.js +1 -1
  145. package/src/nodes/core/ArrayNode.js +1 -1
  146. package/src/nodes/core/AssignNode.js +1 -1
  147. package/src/nodes/core/AttributeNode.js +1 -1
  148. package/src/nodes/core/BypassNode.js +1 -1
  149. package/src/nodes/core/ContextNode.js +1 -1
  150. package/src/nodes/core/IndexNode.js +2 -1
  151. package/src/nodes/core/InputNode.js +1 -1
  152. package/src/nodes/core/InspectorNode.js +1 -1
  153. package/src/nodes/core/IsolateNode.js +1 -1
  154. package/src/nodes/core/Node.js +83 -12
  155. package/src/nodes/core/NodeBuilder.js +117 -16
  156. package/src/nodes/core/NodeUtils.js +1 -1
  157. package/src/nodes/core/OutputStructNode.js +1 -1
  158. package/src/nodes/core/ParameterNode.js +1 -1
  159. package/src/nodes/core/StackNode.js +1 -1
  160. package/src/nodes/core/StructNode.js +1 -1
  161. package/src/nodes/core/StructTypeNode.js +1 -1
  162. package/src/nodes/core/SubBuildNode.js +1 -1
  163. package/src/nodes/core/UniformGroupNode.js +36 -6
  164. package/src/nodes/core/VarNode.js +1 -1
  165. package/src/nodes/core/VaryingNode.js +1 -1
  166. package/src/nodes/display/NormalMapNode.js +2 -2
  167. package/src/nodes/display/PassNode.js +27 -7
  168. package/src/nodes/display/RenderOutputNode.js +4 -4
  169. package/src/nodes/display/ScreenNode.js +1 -1
  170. package/src/nodes/display/ViewportDepthTextureNode.js +11 -15
  171. package/src/nodes/display/ViewportTextureNode.js +18 -7
  172. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +2 -2
  173. package/src/nodes/geometry/RangeNode.js +1 -1
  174. package/src/nodes/gpgpu/AtomicFunctionNode.js +1 -1
  175. package/src/nodes/gpgpu/BarrierNode.js +9 -0
  176. package/src/nodes/gpgpu/ComputeBuiltinNode.js +1 -1
  177. package/src/nodes/gpgpu/ComputeNode.js +69 -44
  178. package/src/nodes/gpgpu/SubgroupFunctionNode.js +1 -1
  179. package/src/nodes/lighting/LightsNode.js +6 -27
  180. package/src/nodes/lighting/ShadowNode.js +24 -2
  181. package/src/nodes/math/BitcastNode.js +1 -1
  182. package/src/nodes/math/ConditionalNode.js +1 -1
  183. package/src/nodes/math/MathNode.js +73 -1
  184. package/src/nodes/math/OperatorNode.js +1 -1
  185. package/src/nodes/math/PackFloatNode.js +1 -1
  186. package/src/nodes/math/UnpackFloatNode.js +1 -1
  187. package/src/nodes/tsl/TSLBase.js +1 -1
  188. package/src/nodes/tsl/TSLCore.js +21 -3
  189. package/src/nodes/utils/ArrayElementNode.js +1 -1
  190. package/src/nodes/utils/ConvertNode.js +1 -1
  191. package/src/nodes/utils/DebugNode.js +1 -1
  192. package/src/nodes/utils/EventNode.js +30 -0
  193. package/src/nodes/utils/FlipNode.js +1 -1
  194. package/src/nodes/utils/FunctionOverloadingNode.js +1 -1
  195. package/src/nodes/utils/JoinNode.js +1 -1
  196. package/src/nodes/utils/MemberNode.js +1 -1
  197. package/src/nodes/utils/Remap.js +48 -0
  198. package/src/nodes/utils/RotateNode.js +1 -1
  199. package/src/nodes/utils/SetNode.js +1 -1
  200. package/src/nodes/utils/SplitNode.js +1 -1
  201. package/src/objects/BatchedMesh.js +17 -2
  202. package/src/objects/InstancedMesh.js +19 -3
  203. package/src/objects/SkinnedMesh.js +26 -9
  204. package/src/renderers/WebGLRenderer.js +147 -48
  205. package/src/renderers/common/Animation.js +3 -3
  206. package/src/renderers/common/Attributes.js +15 -1
  207. package/src/renderers/common/Backend.js +0 -8
  208. package/src/renderers/common/Background.js +2 -2
  209. package/src/renderers/common/BindGroup.js +1 -8
  210. package/src/renderers/common/Bindings.js +2 -2
  211. package/src/renderers/common/ComputePipeline.js +1 -1
  212. package/src/renderers/common/CubeRenderTarget.js +1 -1
  213. package/src/renderers/common/Info.js +333 -4
  214. package/src/renderers/common/InspectorBase.js +6 -1
  215. package/src/renderers/common/Pipelines.js +36 -3
  216. package/src/renderers/common/ReadbackBuffer.js +78 -0
  217. package/src/renderers/common/RenderBundle.js +3 -1
  218. package/src/renderers/common/RenderBundles.js +5 -2
  219. package/src/renderers/common/RenderObject.js +2 -2
  220. package/src/renderers/common/RenderObjects.js +3 -3
  221. package/src/renderers/common/RenderPipeline.js +35 -6
  222. package/src/renderers/common/Renderer.js +232 -53
  223. package/src/renderers/common/Textures.js +72 -3
  224. package/src/renderers/common/UniformsGroup.js +1 -1
  225. package/src/renderers/common/XRManager.js +34 -27
  226. package/src/renderers/common/extras/PMREMGenerator.js +23 -15
  227. package/src/renderers/common/nodes/NodeBuilderState.js +1 -1
  228. package/src/renderers/common/nodes/NodeManager.js +230 -99
  229. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +0 -1
  230. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +1 -1
  231. package/src/renderers/shaders/ShaderChunk/lightprobes_pars_fragment.glsl.js +80 -0
  232. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +8 -0
  233. package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +2 -0
  234. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +1 -3
  235. package/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js +7 -0
  236. package/src/renderers/shaders/ShaderChunk/premultiplied_alpha_fragment.glsl.js +0 -1
  237. package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +12 -2
  238. package/src/renderers/shaders/ShaderChunk.js +2 -0
  239. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +1 -2
  240. package/src/renderers/shaders/ShaderLib.js +0 -1
  241. package/src/renderers/shaders/UniformsLib.js +7 -2
  242. package/src/renderers/shaders/UniformsUtils.js +27 -5
  243. package/src/renderers/webgl/WebGLAnimation.js +2 -1
  244. package/src/renderers/webgl/WebGLBackground.js +13 -13
  245. package/src/renderers/webgl/WebGLBufferRenderer.js +0 -32
  246. package/src/renderers/webgl/WebGLCapabilities.js +6 -0
  247. package/src/renderers/webgl/WebGLIndexedBufferRenderer.js +0 -32
  248. package/src/renderers/webgl/WebGLMaterials.js +12 -13
  249. package/src/renderers/webgl/WebGLOutput.js +4 -1
  250. package/src/renderers/webgl/WebGLProgram.js +4 -0
  251. package/src/renderers/webgl/WebGLPrograms.js +19 -3
  252. package/src/renderers/webgl/WebGLRenderStates.js +13 -2
  253. package/src/renderers/webgl/WebGLState.js +43 -0
  254. package/src/renderers/webgl/WebGLTextures.js +129 -26
  255. package/src/renderers/webgl/WebGLUniformsGroups.js +19 -0
  256. package/src/renderers/webgl-fallback/WebGLBackend.js +106 -65
  257. package/src/renderers/webgl-fallback/WebGLBufferRenderer.js +0 -41
  258. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +29 -51
  259. package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +53 -19
  260. package/src/renderers/webgl-fallback/utils/WebGLCapabilities.js +25 -0
  261. package/src/renderers/webgl-fallback/utils/WebGLState.js +42 -1
  262. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +63 -50
  263. package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +1 -1
  264. package/src/renderers/webgpu/WebGPUBackend.js +160 -146
  265. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +55 -33
  266. package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +103 -17
  267. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +1 -1
  268. package/src/renderers/webgpu/utils/WebGPUCapabilities.js +48 -0
  269. package/src/renderers/webgpu/utils/WebGPUConstants.js +8 -0
  270. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +91 -17
  271. package/src/renderers/webgpu/utils/WebGPUUtils.js +18 -2
  272. package/src/renderers/webxr/WebXRController.js +12 -0
  273. package/src/textures/HTMLTexture.js +74 -0
  274. package/src/textures/Source.js +1 -1
  275. package/src/textures/Texture.js +13 -2
  276. package/src/utils.js +23 -1
  277. package/src/nodes/utils/RemapNode.js +0 -125
@@ -601,7 +601,8 @@ class FBXTreeParser {
601
601
 
602
602
  }
603
603
 
604
- // the transparency handling is implemented based on Blender/Unity's approach: https://github.com/sobotka/blender-addons/blob/7d80f2f97161fc8e353a657b179b9aa1f8e5280b/io_scene_fbx/import_fbx.py#L1444-L1459
604
+ // the transparency handling is implemented based on Blender's approach:
605
+ // https://github.com/blender/blender/blob/main/scripts/addons_core/io_scene_fbx/import_fbx.py
605
606
 
606
607
  parameters.opacity = 1 - ( materialNode.TransparencyFactor ? parseFloat( materialNode.TransparencyFactor.value ) : 0 );
607
608
 
@@ -611,7 +612,10 @@ class FBXTreeParser {
611
612
 
612
613
  if ( parameters.opacity === null ) {
613
614
 
614
- parameters.opacity = 1 - ( materialNode.TransparentColor ? parseFloat( materialNode.TransparentColor.value[ 0 ] ) : 0 );
615
+ // Default to opaque. Some exporters (e.g. 3ds Max) define TransparentColor
616
+ // as white (1,1,1) without intending transparency, which makes the Unity-style
617
+ // fallback of `1 - TransparentColor.r` produce incorrect zero opacity.
618
+ parameters.opacity = 1;
615
619
 
616
620
  }
617
621
 
@@ -824,8 +828,6 @@ class FBXTreeParser {
824
828
  indices: [],
825
829
  weights: [],
826
830
  transformLink: new Matrix4().fromArray( boneNode.TransformLink.a ),
827
- // transform: new Matrix4().fromArray( boneNode.Transform.a ),
828
- // linkMode: boneNode.Mode,
829
831
 
830
832
  };
831
833
 
@@ -918,8 +920,6 @@ class FBXTreeParser {
918
920
 
919
921
  } );
920
922
 
921
- this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
922
-
923
923
  this.addGlobalSceneSettings();
924
924
 
925
925
  sceneGraph.traverse( function ( node ) {
@@ -942,6 +942,64 @@ class FBXTreeParser {
942
942
 
943
943
  } );
944
944
 
945
+ // Like Blender's FBX importer, use the BindPose section to set the
946
+ // rest pose for bones that are not part of a skin cluster. The BindPose
947
+ // provides a more authoritative rest pose than the Lcl properties which
948
+ // may represent an animation frame rather than the true rest state.
949
+ // Bones WITH clusters will get their bind pose from TransformLink
950
+ // (set via bindSkeleton below), which takes priority.
951
+ const bindPoseMatrices = this.parsePoseNodes();
952
+ const clusterBoneIDs = new Set();
953
+
954
+ for ( const ID in deformers.skeletons ) {
955
+
956
+ deformers.skeletons[ ID ].rawBones.forEach( function ( _, i ) {
957
+
958
+ const bone = deformers.skeletons[ ID ].bones[ i ];
959
+ if ( bone ) clusterBoneIDs.add( bone.ID );
960
+
961
+ } );
962
+
963
+ }
964
+
965
+ const tempMatrix = new Matrix4();
966
+
967
+ sceneGraph.traverse( function ( node ) {
968
+
969
+ if ( node.isBone && node.ID !== undefined && ! clusterBoneIDs.has( node.ID ) ) {
970
+
971
+ const bindPose = bindPoseMatrices[ node.ID ];
972
+
973
+ if ( bindPose !== undefined ) {
974
+
975
+ if ( node.parent ) {
976
+
977
+ tempMatrix.copy( node.parent.matrixWorld ).invert();
978
+ tempMatrix.multiply( bindPose );
979
+
980
+ } else {
981
+
982
+ tempMatrix.copy( bindPose );
983
+
984
+ }
985
+
986
+ tempMatrix.decompose( node.position, node.quaternion, node.scale );
987
+ node.updateMatrix();
988
+ node.matrixWorld.copy( bindPose );
989
+
990
+ }
991
+
992
+ }
993
+
994
+ } );
995
+
996
+ // Bind skeletons after transforms are applied so that bind matrices
997
+ // are computed from the final scene state. This ensures the rest pose
998
+ // is correct even when the FBX file's Cluster TransformLink matrices
999
+ // differ from the reconstructed bone transforms (common in files
1000
+ // without a BindPose section).
1001
+ this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
1002
+
945
1003
  const animations = new AnimationParser().parse();
946
1004
 
947
1005
  // if all the models where already combined in a single group, just return that
@@ -954,6 +1012,24 @@ class FBXTreeParser {
954
1012
 
955
1013
  sceneGraph.animations = animations;
956
1014
 
1015
+ // Apply coordinate system correction. FBX files can use different
1016
+ // up-axis conventions (Y-up or Z-up). Three.js uses Y-up, so rotate
1017
+ // the scene when the file uses Z-up (UpAxis === 2).
1018
+
1019
+ if ( 'GlobalSettings' in fbxTree && 'UpAxis' in fbxTree.GlobalSettings ) {
1020
+
1021
+ const upAxis = fbxTree.GlobalSettings.UpAxis.value;
1022
+
1023
+ if ( upAxis === 2 ) {
1024
+
1025
+ console.warn( 'THREE.FBXLoader: You are loading an asset with a Z-UP coordinate system. The loader just rotates the asset to transform it into Y-UP. The vertex data are not converted.' );
1026
+
1027
+ sceneGraph.rotation.set( - Math.PI / 2, 0, 0 );
1028
+
1029
+ }
1030
+
1031
+ }
1032
+
957
1033
  }
958
1034
 
959
1035
  // parse nodes in FBXTree.Objects.Model
@@ -1236,21 +1312,24 @@ class FBXTreeParser {
1236
1312
 
1237
1313
  case 2: // Spot
1238
1314
  let angle = Math.PI / 3;
1315
+ let penumbra = 0;
1239
1316
 
1240
- if ( lightAttribute.InnerAngle !== undefined ) {
1317
+ if ( lightAttribute.OuterAngle !== undefined ) {
1241
1318
 
1242
- angle = MathUtils.degToRad( lightAttribute.InnerAngle.value );
1319
+ angle = MathUtils.degToRad( lightAttribute.OuterAngle.value );
1243
1320
 
1244
- }
1321
+ if ( lightAttribute.InnerAngle !== undefined ) {
1245
1322
 
1246
- let penumbra = 0;
1247
- if ( lightAttribute.OuterAngle !== undefined ) {
1323
+ penumbra = 1 - ( lightAttribute.InnerAngle.value / lightAttribute.OuterAngle.value );
1324
+ penumbra = Math.max( 0, penumbra ); // penumbra must be in the range [0,1]
1248
1325
 
1249
- // TODO: this is not correct - FBX calculates outer and inner angle in degrees
1250
- // with OuterAngle > InnerAngle && OuterAngle <= Math.PI
1251
- // while three.js uses a penumbra between (0, 1) to attenuate the inner angle
1252
- penumbra = MathUtils.degToRad( lightAttribute.OuterAngle.value );
1253
- penumbra = Math.max( penumbra, 1 );
1326
+ }
1327
+
1328
+ } else if ( lightAttribute.InnerAngle !== undefined ) {
1329
+
1330
+ // fallback if only InnerAngle is defined
1331
+
1332
+ angle = MathUtils.degToRad( lightAttribute.InnerAngle.value );
1254
1333
 
1255
1334
  }
1256
1335
 
@@ -1460,12 +1539,30 @@ class FBXTreeParser {
1460
1539
 
1461
1540
  bindSkeleton( skeletons, geometryMap, modelMap ) {
1462
1541
 
1463
- const bindMatrices = this.parsePoseNodes();
1464
-
1465
1542
  for ( const ID in skeletons ) {
1466
1543
 
1467
1544
  const skeleton = skeletons[ ID ];
1468
1545
 
1546
+ // Compute bone inverses from TransformLink rather than from the
1547
+ // bones' current matrixWorld. The TransformLink matrices represent
1548
+ // each bone's global transform at the time the skin weights were
1549
+ // painted, which may differ from the scene-reconstructed transforms.
1550
+ const boneInverses = [];
1551
+
1552
+ for ( let i = 0, l = skeleton.bones.length; i < l; i ++ ) {
1553
+
1554
+ const inverse = new Matrix4();
1555
+
1556
+ if ( skeleton.bones[ i ] && skeleton.rawBones[ i ] ) {
1557
+
1558
+ inverse.copy( skeleton.rawBones[ i ].transformLink ).invert();
1559
+
1560
+ }
1561
+
1562
+ boneInverses.push( inverse );
1563
+
1564
+ }
1565
+
1469
1566
  const parents = connections.get( parseInt( skeleton.ID ) ).parents;
1470
1567
 
1471
1568
  parents.forEach( function ( parent ) {
@@ -1481,7 +1578,16 @@ class FBXTreeParser {
1481
1578
 
1482
1579
  const model = modelMap.get( geoConnParent.ID );
1483
1580
 
1484
- model.bind( new Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
1581
+ // Use the mesh's current matrixWorld as bind matrix.
1582
+ // The BindPose section is intentionally not used here
1583
+ // since it may contain scale/rotation from the model
1584
+ // hierarchy that is inconsistent with the TransformLink-
1585
+ // based bone inverses. Always provide a bind matrix to
1586
+ // prevent bind() from calling calculateInverses() which
1587
+ // would overwrite the bone inverses computed above.
1588
+ model.updateMatrixWorld( true );
1589
+
1590
+ model.bind( new Skeleton( skeleton.bones, boneInverses ), model.matrixWorld );
1485
1591
 
1486
1592
  }
1487
1593
 
@@ -1495,6 +1601,7 @@ class FBXTreeParser {
1495
1601
 
1496
1602
  }
1497
1603
 
1604
+ // Parse BindPose nodes and return a map of node ID to bind matrix.
1498
1605
  parsePoseNodes() {
1499
1606
 
1500
1607
  const bindMatrices = {};
@@ -2257,6 +2364,13 @@ class GeometryParser {
2257
2364
  parentGeo.morphAttributes.position = [];
2258
2365
  // parentGeo.morphAttributes.normal = []; // not implemented
2259
2366
 
2367
+ // Morph attribute positions are stored as deltas (morphTargetsRelative = true), so the
2368
+ // translation component of the geometric transform must not be applied to them — only the
2369
+ // rotation/scale part. Otherwise every delta gets the geometric translation added, which
2370
+ // shifts morphed vertices away from their intended position by `weight * translation` as
2371
+ // the influence increases.
2372
+ const morphPreTransform = preTransform.clone().setPosition( 0, 0, 0 );
2373
+
2260
2374
  const scope = this;
2261
2375
  morphTargets.forEach( function ( morphTarget ) {
2262
2376
 
@@ -2266,7 +2380,7 @@ class GeometryParser {
2266
2380
 
2267
2381
  if ( morphGeoNode !== undefined ) {
2268
2382
 
2269
- scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name );
2383
+ scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, morphPreTransform, rawTarget.name );
2270
2384
 
2271
2385
  }
2272
2386
 
@@ -2661,11 +2775,15 @@ class AnimationParser {
2661
2775
 
2662
2776
  if ( layerCurveNodes[ i ] === undefined ) {
2663
2777
 
2664
- const modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
2778
+ const filteredParents = connections.get( child.ID ).parents.filter( function ( parent ) {
2665
2779
 
2666
2780
  return parent.relationship !== undefined;
2667
2781
 
2668
- } )[ 0 ].ID;
2782
+ } );
2783
+
2784
+ if ( filteredParents.length === 0 ) return;
2785
+
2786
+ const modelID = filteredParents[ 0 ].ID;
2669
2787
 
2670
2788
  if ( modelID !== undefined ) {
2671
2789
 
@@ -2694,7 +2812,13 @@ class AnimationParser {
2694
2812
 
2695
2813
  node.transform = child.matrix;
2696
2814
 
2697
- if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
2815
+ if ( child.userData.transformData ) {
2816
+
2817
+ node.eulerOrder = child.userData.transformData.eulerOrder;
2818
+
2819
+ if ( child.userData.transformData.rotation ) node.initialRotation = child.userData.transformData.rotation;
2820
+
2821
+ }
2698
2822
 
2699
2823
  }
2700
2824
 
@@ -2719,11 +2843,15 @@ class AnimationParser {
2719
2843
 
2720
2844
  if ( layerCurveNodes[ i ] === undefined ) {
2721
2845
 
2722
- const deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
2846
+ const filteredParents = connections.get( child.ID ).parents.filter( function ( parent ) {
2723
2847
 
2724
2848
  return parent.relationship !== undefined;
2725
2849
 
2726
- } )[ 0 ].ID;
2850
+ } );
2851
+
2852
+ if ( filteredParents.length === 0 ) return;
2853
+
2854
+ const deformerID = filteredParents[ 0 ].ID;
2727
2855
 
2728
2856
  const morpherID = connections.get( deformerID ).parents[ 0 ].ID;
2729
2857
  const geoID = connections.get( morpherID ).parents[ 0 ].ID;
@@ -2834,7 +2962,7 @@ class AnimationParser {
2834
2962
 
2835
2963
  if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
2836
2964
 
2837
- const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
2965
+ const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder, rawTracks.initialRotation );
2838
2966
  if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
2839
2967
 
2840
2968
  }
@@ -2866,17 +2994,33 @@ class AnimationParser {
2866
2994
 
2867
2995
  }
2868
2996
 
2869
- generateRotationTrack( modelName, curves, preRotation, postRotation, eulerOrder ) {
2997
+ generateRotationTrack( modelName, curves, preRotation, postRotation, eulerOrder, initialRotation ) {
2870
2998
 
2871
2999
  let times;
2872
3000
  let values;
2873
3001
 
2874
- if ( curves.x !== undefined && curves.y !== undefined && curves.z !== undefined ) {
3002
+ if ( curves.x !== undefined || curves.y !== undefined || curves.z !== undefined ) {
3003
+
3004
+ // Get merged, sorted, unique times from all available curves
3005
+ const mergedTimes = this.getTimesForAllAxes( curves );
3006
+
3007
+ if ( mergedTimes.length > 0 ) {
3008
+
3009
+ const initialRot = initialRotation || [ 0, 0, 0 ];
2875
3010
 
2876
- const result = this.interpolateRotations( curves.x, curves.y, curves.z, eulerOrder );
3011
+ // Synchronize all curves to the merged time array.
3012
+ // Missing axes are filled with constant values from the initial rotation (Lcl Rotation).
3013
+ // Existing curves at different times are linearly interpolated.
3014
+ const syncX = this.synchronizeCurve( curves.x, mergedTimes, initialRot[ 0 ] );
3015
+ const syncY = this.synchronizeCurve( curves.y, mergedTimes, initialRot[ 1 ] );
3016
+ const syncZ = this.synchronizeCurve( curves.z, mergedTimes, initialRot[ 2 ] );
2877
3017
 
2878
- times = result[ 0 ];
2879
- values = result[ 1 ];
3018
+ const result = this.interpolateRotations( syncX, syncY, syncZ, eulerOrder );
3019
+
3020
+ times = result[ 0 ];
3021
+ values = result[ 1 ];
3022
+
3023
+ }
2880
3024
 
2881
3025
  }
2882
3026
 
@@ -2908,7 +3052,7 @@ class AnimationParser {
2908
3052
 
2909
3053
  const quaternionValues = [];
2910
3054
 
2911
- if ( ! values || ! times ) return new QuaternionKeyframeTrack( modelName + '.quaternion', [ 0 ], [ 0 ] );
3055
+ if ( ! values || ! times ) return undefined;
2912
3056
 
2913
3057
  for ( let i = 0; i < values.length; i += 3 ) {
2914
3058
 
@@ -3061,6 +3205,62 @@ class AnimationParser {
3061
3205
 
3062
3206
  }
3063
3207
 
3208
+ // Synchronize a curve to a target time array using linear interpolation.
3209
+ // If the curve is undefined (axis not animated), returns constant values from initialValue.
3210
+ synchronizeCurve( curve, targetTimes, initialValue ) {
3211
+
3212
+ if ( curve === undefined ) {
3213
+
3214
+ return { times: targetTimes, values: targetTimes.map( () => initialValue ) };
3215
+
3216
+ }
3217
+
3218
+ // If the curve already has the same number of keyframes as the target, assume times match
3219
+ if ( curve.times.length === targetTimes.length ) return curve;
3220
+
3221
+ // Linearly interpolate curve values at each target time
3222
+ const values = [];
3223
+
3224
+ for ( let i = 0; i < targetTimes.length; i ++ ) {
3225
+
3226
+ values.push( this.sampleCurveValue( curve, targetTimes[ i ], initialValue ) );
3227
+
3228
+ }
3229
+
3230
+ return { times: targetTimes, values: values };
3231
+
3232
+ }
3233
+
3234
+ // Sample a single value from a curve at a given time using linear interpolation
3235
+ sampleCurveValue( curve, time, initialValue ) {
3236
+
3237
+ const times = curve.times;
3238
+ const values = curve.values;
3239
+
3240
+ // Before first keyframe
3241
+ if ( time <= times[ 0 ] ) return values[ 0 ];
3242
+
3243
+ // After last keyframe
3244
+ if ( time >= times[ times.length - 1 ] ) return values[ values.length - 1 ];
3245
+
3246
+ // Find surrounding keyframes and linearly interpolate
3247
+ for ( let i = 0; i < times.length - 1; i ++ ) {
3248
+
3249
+ if ( time >= times[ i ] && time <= times[ i + 1 ] ) {
3250
+
3251
+ if ( times[ i ] === time ) return values[ i ];
3252
+
3253
+ const alpha = ( time - times[ i ] ) / ( times[ i + 1 ] - times[ i ] );
3254
+ return values[ i ] * ( 1 - alpha ) + values[ i + 1 ] * alpha;
3255
+
3256
+ }
3257
+
3258
+ }
3259
+
3260
+ return initialValue;
3261
+
3262
+ }
3263
+
3064
3264
  // Rotations are defined as Euler angles which can have values of any size
3065
3265
  // These will be converted to quaternions which don't support values greater than
3066
3266
  // PI, so we'll interpolate large rotations
@@ -3130,7 +3330,7 @@ class AnimationParser {
3130
3330
  const Q2 = new Quaternion().setFromEuler( E2 );
3131
3331
 
3132
3332
  // Check unroll
3133
- if ( Q1.dot( Q2 ) ) {
3333
+ if ( Q1.dot( Q2 ) < 0 ) {
3134
3334
 
3135
3335
  Q2.set( - Q2.x, - Q2.y, - Q2.z, - Q2.w );
3136
3336
 
@@ -83,8 +83,11 @@ import { clone } from '../utils/SkeletonUtils.js';
83
83
  *
84
84
  * `GLTFLoader` supports the following glTF 2.0 extensions:
85
85
  * - KHR_draco_mesh_compression
86
+ * - KHR_lights_punctual
87
+ * - KHR_materials_anisotropy
86
88
  * - KHR_materials_clearcoat
87
89
  * - KHR_materials_dispersion
90
+ * - KHR_materials_emissive_strength
88
91
  * - KHR_materials_ior
89
92
  * - KHR_materials_specular
90
93
  * - KHR_materials_transmission
@@ -93,12 +96,13 @@ import { clone } from '../utils/SkeletonUtils.js';
93
96
  * - KHR_materials_volume
94
97
  * - KHR_mesh_quantization
95
98
  * - KHR_meshopt_compression
96
- * - KHR_lights_punctual
97
99
  * - KHR_texture_basisu
98
100
  * - KHR_texture_transform
99
- * - EXT_texture_webp
101
+ * - EXT_materials_bump
100
102
  * - EXT_meshopt_compression
101
103
  * - EXT_mesh_gpu_instancing
104
+ * - EXT_texture_avif
105
+ * - EXT_texture_webp
102
106
  *
103
107
  * The following glTF 2.0 extension is supported by an external user plugin:
104
108
  * - [KHR_materials_variants](https://github.com/takahirox/three-gltf-extensions)
@@ -1269,6 +1273,8 @@ class GLTFMaterialsIorExtension {
1269
1273
 
1270
1274
  materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5;
1271
1275
 
1276
+ if ( materialParams.ior === 0 ) materialParams.ior = 1000; // see #26167
1277
+
1272
1278
  return Promise.resolve();
1273
1279
 
1274
1280
  }
@@ -4512,17 +4518,28 @@ class GLTFParser {
4512
4518
  const targetName = node.name ? node.name : node.uuid;
4513
4519
  const targetNames = [];
4514
4520
 
4521
+ function collectMorphTargets( object ) {
4522
+
4523
+ if ( object.morphTargetInfluences ) {
4524
+
4525
+ targetNames.push( object.name ? object.name : object.uuid );
4526
+
4527
+ }
4528
+
4529
+ }
4530
+
4531
+
4515
4532
  if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
4516
4533
 
4517
- node.traverse( function ( object ) {
4534
+ collectMorphTargets( node );
4518
4535
 
4519
- if ( object.morphTargetInfluences ) {
4536
+ // for multi-primitive meshes, the node is a Group containing the sub-meshes
4520
4537
 
4521
- targetNames.push( object.name ? object.name : object.uuid );
4538
+ if ( node.isGroup ) {
4522
4539
 
4523
- }
4540
+ node.children.forEach( collectMorphTargets );
4524
4541
 
4525
- } );
4542
+ }
4526
4543
 
4527
4544
  } else {
4528
4545
 
@@ -100,7 +100,7 @@ class HDRLoader extends DataTextureLoader {
100
100
 
101
101
  s += chunk; len += chunk.length;
102
102
  p += chunkSize;
103
- chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
103
+ chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
104
104
 
105
105
  }
106
106
 
@@ -42,7 +42,8 @@ import {
42
42
  SRGBColorSpace,
43
43
  UnsignedByteType,
44
44
  UnsignedInt5999Type,
45
- UnsignedInt101111Type
45
+ UnsignedInt101111Type,
46
+ UnsignedShortType
46
47
  } from 'three';
47
48
  import { WorkerPool } from '../utils/WorkerPool.js';
48
49
  import {
@@ -83,6 +84,7 @@ import {
83
84
  VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,
84
85
  VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG,
85
86
  VK_FORMAT_R16G16B16A16_SFLOAT,
87
+ VK_FORMAT_R16G16B16A16_UNORM,
86
88
  VK_FORMAT_R16G16_SFLOAT,
87
89
  VK_FORMAT_R16_SFLOAT,
88
90
  VK_FORMAT_R32G32B32A32_SFLOAT,
@@ -964,6 +966,8 @@ const FORMAT_MAP = {
964
966
  [ VK_FORMAT_R16G16_SFLOAT ]: RGFormat,
965
967
  [ VK_FORMAT_R16_SFLOAT ]: RedFormat,
966
968
 
969
+ [ VK_FORMAT_R16G16B16A16_UNORM ]: RGBAFormat,
970
+
967
971
  [ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat,
968
972
  [ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat,
969
973
  [ VK_FORMAT_R8G8_SRGB ]: RGFormat,
@@ -1022,6 +1026,8 @@ const TYPE_MAP = {
1022
1026
  [ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType,
1023
1027
  [ VK_FORMAT_R16_SFLOAT ]: HalfFloatType,
1024
1028
 
1029
+ [ VK_FORMAT_R16G16B16A16_UNORM ]: UnsignedShortType,
1030
+
1025
1031
  [ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType,
1026
1032
  [ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType,
1027
1033
  [ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType,
@@ -1149,7 +1155,7 @@ async function createRawTexture( container ) {
1149
1155
 
1150
1156
  );
1151
1157
 
1152
- } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) {
1158
+ } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType || TYPE_MAP[ vkFormat ] === UnsignedShortType ) {
1153
1159
 
1154
1160
  data = new Uint16Array(
1155
1161
 
@@ -1015,21 +1015,9 @@ class LDrawParsedCache {
1015
1015
  faceNormal: null,
1016
1016
  vertices: [ v0, v1, v2 ],
1017
1017
  normals: [ null, null, null ],
1018
+ doubleSided: doubleSided,
1018
1019
  } );
1019
- totalFaces ++;
1020
-
1021
- if ( doubleSided === true ) {
1022
-
1023
- faces.push( {
1024
- material: material,
1025
- colorCode: colorCode,
1026
- faceNormal: null,
1027
- vertices: [ v2, v1, v0 ],
1028
- normals: [ null, null, null ],
1029
- } );
1030
- totalFaces ++;
1031
-
1032
- }
1020
+ totalFaces += doubleSided ? 2 : 1;
1033
1021
 
1034
1022
  break;
1035
1023
 
@@ -1065,21 +1053,9 @@ class LDrawParsedCache {
1065
1053
  faceNormal: null,
1066
1054
  vertices: [ v0, v1, v2, v3 ],
1067
1055
  normals: [ null, null, null, null ],
1056
+ doubleSided: doubleSided,
1068
1057
  } );
1069
- totalFaces += 2;
1070
-
1071
- if ( doubleSided === true ) {
1072
-
1073
- faces.push( {
1074
- material: material,
1075
- colorCode: colorCode,
1076
- faceNormal: null,
1077
- vertices: [ v3, v2, v1, v0 ],
1078
- normals: [ null, null, null, null ],
1079
- } );
1080
- totalFaces += 2;
1081
-
1082
- }
1058
+ totalFaces += doubleSided ? 4 : 2;
1083
1059
 
1084
1060
  break;
1085
1061
 
@@ -1550,13 +1526,22 @@ function createObject( loader, elements, elementSize, isConditionalSegments = fa
1550
1526
 
1551
1527
  }
1552
1528
 
1553
- for ( let j = 0, l = vertices.length; j < l; j ++ ) {
1529
+ const sideCount = elementSize === 3 && elem.doubleSided ? 2 : 1;
1530
+ const sideVertCount = vertices.length;
1531
+ const totalVertCount = sideVertCount * sideCount;
1554
1532
 
1555
- const v = vertices[ j ];
1556
- const index = offset + j * 3;
1557
- positions[ index + 0 ] = v.x;
1558
- positions[ index + 1 ] = v.y;
1559
- positions[ index + 2 ] = v.z;
1533
+ for ( let s = 0; s < sideCount; s ++ ) {
1534
+
1535
+ for ( let j = 0; j < sideVertCount; j ++ ) {
1536
+
1537
+ // front side: original order; back side: reversed winding
1538
+ const v = vertices[ s === 0 ? j : sideVertCount - 1 - j ];
1539
+ const index = offset + ( s * sideVertCount + j ) * 3;
1540
+ positions[ index + 0 ] = v.x;
1541
+ positions[ index + 1 ] = v.y;
1542
+ positions[ index + 2 ] = v.z;
1543
+
1544
+ }
1560
1545
 
1561
1546
  }
1562
1547
 
@@ -1589,20 +1574,27 @@ function createObject( loader, elements, elementSize, isConditionalSegments = fa
1589
1574
 
1590
1575
  }
1591
1576
 
1592
- for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
1577
+ const normalCount = elemNormals.length;
1578
+ for ( let s = 0; s < sideCount; s ++ ) {
1593
1579
 
1594
- // use face normal if a vertex normal is not provided
1595
- let n = elem.faceNormal;
1596
- if ( elemNormals[ j ] ) {
1580
+ const sign = s === 0 ? 1 : - 1;
1581
+ for ( let j = 0; j < normalCount; j ++ ) {
1597
1582
 
1598
- n = elemNormals[ j ].norm;
1583
+ // back side reuses the front normals in reversed order, with negated direction
1584
+ const idx = s === 0 ? j : normalCount - 1 - j;
1585
+ let n = elem.faceNormal;
1586
+ if ( elemNormals[ idx ] ) {
1599
1587
 
1600
- }
1588
+ n = elemNormals[ idx ].norm;
1601
1589
 
1602
- const index = offset + j * 3;
1603
- normals[ index + 0 ] = n.x;
1604
- normals[ index + 1 ] = n.y;
1605
- normals[ index + 2 ] = n.z;
1590
+ }
1591
+
1592
+ const index = offset + ( s * normalCount + j ) * 3;
1593
+ normals[ index + 0 ] = sign * n.x;
1594
+ normals[ index + 1 ] = sign * n.y;
1595
+ normals[ index + 2 ] = sign * n.z;
1596
+
1597
+ }
1606
1598
 
1607
1599
  }
1608
1600
 
@@ -1650,15 +1642,15 @@ function createObject( loader, elements, elementSize, isConditionalSegments = fa
1650
1642
 
1651
1643
  prevMaterial = elem.colorCode;
1652
1644
  index0 = offset / 3;
1653
- numGroupVerts = vertices.length;
1645
+ numGroupVerts = totalVertCount;
1654
1646
 
1655
1647
  } else {
1656
1648
 
1657
- numGroupVerts += vertices.length;
1649
+ numGroupVerts += totalVertCount;
1658
1650
 
1659
1651
  }
1660
1652
 
1661
- offset += 3 * vertices.length;
1653
+ offset += 3 * totalVertCount;
1662
1654
 
1663
1655
  }
1664
1656
 
@@ -3005,7 +3005,7 @@ class SVGLoader extends Loader {
3005
3005
 
3006
3006
  }
3007
3007
 
3008
- addVertex( tempV2_4, u, v );
3008
+ addVertex( tempV2_3, u, v );
3009
3009
  addVertex( p2, u, v );
3010
3010
  addVertex( center, u, 0.5 );
3011
3011