super-three 0.136.0 → 0.141.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 (768) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +24 -26
  3. package/build/three.cjs +35889 -0
  4. package/build/three.js +4837 -6036
  5. package/build/three.min.js +2 -2
  6. package/build/three.module.js +16548 -17930
  7. package/examples/js/animation/CCDIKSolver.js +3 -2
  8. package/examples/js/animation/MMDAnimationHelper.js +3 -1
  9. package/examples/js/animation/MMDPhysics.js +3 -3
  10. package/examples/js/cameras/CinematicCamera.js +2 -7
  11. package/examples/js/controls/ArcballControls.js +3 -1
  12. package/examples/js/controls/PointerLockControls.js +3 -2
  13. package/examples/js/controls/TrackballControls.js +1 -4
  14. package/examples/js/controls/TransformControls.js +5 -6
  15. package/examples/js/controls/experimental/CameraControls.js +8 -4
  16. package/examples/js/csm/CSMFrustum.js +1 -1
  17. package/examples/js/csm/CSMShader.js +51 -35
  18. package/examples/js/curves/CurveExtras.js +14 -18
  19. package/examples/js/effects/AnaglyphEffect.js +7 -4
  20. package/examples/js/effects/AsciiEffect.js +10 -29
  21. package/examples/js/exporters/ColladaExporter.js +29 -7
  22. package/examples/js/exporters/EXRExporter.js +458 -0
  23. package/examples/js/exporters/GLTFExporter.js +233 -105
  24. package/examples/js/exporters/MMDExporter.js +1 -1
  25. package/examples/js/exporters/OBJExporter.js +12 -18
  26. package/examples/js/exporters/PLYExporter.js +15 -20
  27. package/examples/js/exporters/USDZExporter.js +13 -0
  28. package/examples/js/geometries/ConvexGeometry.js +1 -1
  29. package/examples/js/geometries/DecalGeometry.js +0 -7
  30. package/examples/js/geometries/LightningStrike.js +2 -2
  31. package/examples/js/geometries/ParametricGeometries.js +3 -3
  32. package/examples/js/geometries/TextGeometry.js +12 -10
  33. package/examples/js/helpers/OctreeHelper.js +74 -0
  34. package/examples/js/helpers/VertexNormalsHelper.js +4 -23
  35. package/examples/js/helpers/VertexTangentsHelper.js +3 -13
  36. package/examples/js/helpers/ViewHelper.js +266 -0
  37. package/examples/js/interactive/HTMLMesh.js +249 -42
  38. package/examples/js/interactive/SelectionHelper.js +1 -1
  39. package/examples/js/libs/opentype.min.js +1 -118
  40. package/examples/js/lines/Line2.js +1 -2
  41. package/examples/js/lines/LineGeometry.js +10 -21
  42. package/examples/js/lines/LineMaterial.js +2 -3
  43. package/examples/js/lines/LineSegments2.js +214 -140
  44. package/examples/js/lines/LineSegmentsGeometry.js +3 -14
  45. package/examples/js/lines/Wireframe.js +2 -3
  46. package/examples/js/lines/WireframeGeometry2.js +1 -2
  47. package/examples/js/loaders/3MFLoader.js +21 -17
  48. package/examples/js/loaders/AMFLoader.js +1 -1
  49. package/examples/js/loaders/BasisTextureLoader.js +1 -0
  50. package/examples/js/loaders/ColladaLoader.js +33 -12
  51. package/examples/js/loaders/DDSLoader.js +23 -30
  52. package/examples/js/loaders/EXRLoader.js +314 -294
  53. package/examples/js/loaders/FBXLoader.js +11 -0
  54. package/examples/js/loaders/FontLoader.js +1 -2
  55. package/examples/js/loaders/GCodeLoader.js +0 -1
  56. package/examples/js/loaders/GLTFLoader.js +250 -61
  57. package/examples/js/loaders/HDRCubeTextureLoader.js +0 -2
  58. package/examples/js/loaders/KMZLoader.js +1 -1
  59. package/examples/js/loaders/KTX2Loader.js +1 -9
  60. package/examples/js/loaders/LDrawLoader.js +1342 -962
  61. package/examples/js/loaders/LUT3dlLoader.js +18 -11
  62. package/examples/js/loaders/LUTCubeLoader.js +4 -5
  63. package/examples/js/loaders/LWOLoader.js +4 -2
  64. package/examples/js/loaders/LogLuvLoader.js +1 -2
  65. package/examples/js/loaders/MD2Loader.js +5 -5
  66. package/examples/js/loaders/MMDLoader.js +18 -4
  67. package/examples/js/loaders/MTLLoader.js +10 -3
  68. package/examples/js/loaders/NRRDLoader.js +1 -1
  69. package/examples/js/loaders/OBJLoader.js +9 -5
  70. package/examples/js/loaders/PCDLoader.js +24 -15
  71. package/examples/js/loaders/PDBLoader.js +11 -11
  72. package/examples/js/loaders/PLYLoader.js +5 -1
  73. package/examples/js/loaders/RGBELoader.js +9 -17
  74. package/examples/js/loaders/RGBMLoader.js +13 -16
  75. package/examples/js/loaders/STLLoader.js +1 -1
  76. package/examples/js/loaders/SVGLoader.js +21 -11
  77. package/examples/js/loaders/VOXLoader.js +3 -4
  78. package/examples/js/loaders/VRMLLoader.js +9 -21
  79. package/examples/js/loaders/VTKLoader.js +260 -295
  80. package/examples/js/loaders/lwo/LWO2Parser.js +4 -4
  81. package/examples/js/loaders/lwo/LWO3Parser.js +4 -4
  82. package/examples/js/math/ConvexHull.js +13 -28
  83. package/examples/js/math/Lut.js +27 -26
  84. package/examples/js/misc/MorphAnimMesh.js +2 -2
  85. package/examples/js/misc/ProgressiveLightMap.js +1 -1
  86. package/examples/js/misc/Volume.js +39 -48
  87. package/examples/js/misc/VolumeSlice.js +21 -21
  88. package/examples/js/modifiers/CurveModifier.js +8 -7
  89. package/examples/js/modifiers/EdgeSplitModifier.js +0 -13
  90. package/examples/js/modifiers/SimplifyModifier.js +1 -18
  91. package/examples/js/modifiers/TessellateModifier.js +0 -7
  92. package/examples/js/objects/Lensflare.js +4 -4
  93. package/examples/js/objects/LightningStorm.js +4 -5
  94. package/examples/js/objects/MarchingCubes.js +6 -11
  95. package/examples/js/objects/Reflector.js +10 -16
  96. package/examples/js/objects/ReflectorForSSRPass.js +1 -11
  97. package/examples/js/objects/Refractor.js +10 -16
  98. package/examples/js/objects/ShadowMesh.js +1 -2
  99. package/examples/js/objects/Sky.js +1 -1
  100. package/examples/js/objects/Water.js +2 -15
  101. package/examples/js/objects/Water2.js +1 -1
  102. package/examples/js/postprocessing/AdaptiveToneMappingPass.js +6 -10
  103. package/examples/js/postprocessing/AfterimagePass.js +2 -6
  104. package/examples/js/postprocessing/BloomPass.js +50 -19
  105. package/examples/js/postprocessing/DotScreenPass.js +1 -1
  106. package/examples/js/postprocessing/EffectComposer.js +1 -6
  107. package/examples/js/postprocessing/GlitchPass.js +4 -5
  108. package/examples/js/postprocessing/LUTPass.js +2 -2
  109. package/examples/js/postprocessing/OutlinePass.js +8 -17
  110. package/examples/js/postprocessing/SAOPass.js +2 -7
  111. package/examples/js/postprocessing/SMAAPass.js +2 -9
  112. package/examples/js/postprocessing/SSAARenderPass.js +1 -6
  113. package/examples/js/postprocessing/SSAOPass.js +9 -21
  114. package/examples/js/postprocessing/SSRPass.js +3 -8
  115. package/examples/js/postprocessing/SavePass.js +1 -5
  116. package/examples/js/postprocessing/TAARenderPass.js +4 -4
  117. package/examples/js/postprocessing/UnrealBloomPass.js +3 -12
  118. package/examples/js/renderers/CSS2DRenderer.js +20 -19
  119. package/examples/js/renderers/CSS3DRenderer.js +40 -36
  120. package/examples/js/renderers/Projector.js +97 -124
  121. package/examples/js/renderers/SVGRenderer.js +3 -4
  122. package/examples/js/shaders/BlendShader.js +2 -1
  123. package/examples/js/shaders/CopyShader.js +4 -3
  124. package/examples/js/shaders/DigitalGlitch.js +3 -3
  125. package/examples/js/shaders/FXAAShader.js +239 -1069
  126. package/examples/js/shaders/FreiChenShader.js +1 -1
  127. package/examples/js/shaders/HorizontalBlurShader.js +1 -1
  128. package/examples/js/shaders/HorizontalTiltShiftShader.js +1 -1
  129. package/examples/js/shaders/MMDToonShader.js +0 -1
  130. package/examples/js/shaders/SSAOShader.js +1 -1
  131. package/examples/js/shaders/SSRShader.js +3 -3
  132. package/examples/js/shaders/ToneMapShader.js +1 -1
  133. package/examples/js/utils/BufferGeometryUtils.js +282 -22
  134. package/examples/js/utils/GPUStatsPanel.js +1 -1
  135. package/examples/js/utils/LDrawUtils.js +182 -0
  136. package/examples/js/utils/SceneUtils.js +59 -0
  137. package/examples/js/utils/SkeletonUtils.js +1 -1
  138. package/examples/js/utils/UVsDebug.js +23 -33
  139. package/examples/jsm/animation/CCDIKSolver.js +3 -9
  140. package/examples/jsm/animation/MMDPhysics.js +2 -2
  141. package/examples/jsm/cameras/CinematicCamera.js +2 -5
  142. package/examples/jsm/{WebGL.js → capabilities/WebGL.js} +2 -2
  143. package/examples/jsm/{renderers/webgpu → capabilities}/WebGPU.js +7 -1
  144. package/examples/jsm/controls/ArcballControls.js +3 -1
  145. package/examples/jsm/controls/PointerLockControls.js +4 -2
  146. package/examples/jsm/controls/TrackballControls.js +1 -4
  147. package/examples/jsm/controls/TransformControls.js +6 -6
  148. package/examples/jsm/controls/experimental/CameraControls.js +2 -2
  149. package/examples/jsm/csm/CSMFrustum.js +1 -1
  150. package/examples/jsm/csm/CSMShader.js +51 -35
  151. package/examples/jsm/curves/CurveExtras.js +15 -17
  152. package/examples/jsm/effects/AnaglyphEffect.js +4 -4
  153. package/examples/jsm/effects/AsciiEffect.js +9 -22
  154. package/examples/jsm/exporters/ColladaExporter.js +31 -6
  155. package/examples/jsm/exporters/EXRExporter.js +507 -0
  156. package/examples/jsm/exporters/GLTFExporter.js +261 -103
  157. package/examples/jsm/exporters/KTX2Exporter.js +281 -0
  158. package/examples/jsm/exporters/MMDExporter.js +1 -1
  159. package/examples/jsm/exporters/OBJExporter.js +11 -18
  160. package/examples/jsm/exporters/PLYExporter.js +23 -21
  161. package/examples/jsm/exporters/USDZExporter.js +12 -0
  162. package/examples/jsm/geometries/ConvexGeometry.js +1 -1
  163. package/examples/jsm/geometries/DecalGeometry.js +0 -7
  164. package/examples/jsm/geometries/LightningStrike.js +2 -2
  165. package/examples/jsm/geometries/ParametricGeometries.js +3 -3
  166. package/examples/jsm/geometries/TextGeometry.js +13 -13
  167. package/examples/jsm/helpers/OctreeHelper.js +58 -0
  168. package/examples/jsm/helpers/VertexNormalsHelper.js +4 -25
  169. package/examples/jsm/helpers/VertexTangentsHelper.js +3 -15
  170. package/examples/jsm/helpers/ViewHelper.js +295 -0
  171. package/examples/jsm/interactive/HTMLMesh.js +268 -36
  172. package/examples/jsm/interactive/SelectionHelper.js +1 -1
  173. package/examples/jsm/libs/flow.module.js +947 -183
  174. package/examples/jsm/libs/ktx-parse.module.js +1 -1
  175. package/examples/jsm/libs/lil-gui.module.min.js +2 -2
  176. package/examples/jsm/libs/mikktspace.module.js +128 -0
  177. package/examples/jsm/libs/opentype.module.js +14568 -0
  178. package/examples/jsm/lines/Line2.js +2 -2
  179. package/examples/jsm/lines/LineGeometry.js +11 -19
  180. package/examples/jsm/lines/LineMaterial.js +3 -3
  181. package/examples/jsm/lines/LineSegments2.js +210 -145
  182. package/examples/jsm/lines/LineSegmentsGeometry.js +3 -12
  183. package/examples/jsm/lines/Wireframe.js +3 -3
  184. package/examples/jsm/lines/WireframeGeometry2.js +2 -2
  185. package/examples/jsm/loaders/3DMLoader.js +3 -2
  186. package/examples/jsm/loaders/3MFLoader.js +21 -17
  187. package/examples/jsm/loaders/AMFLoader.js +1 -1
  188. package/examples/jsm/loaders/BasisTextureLoader.js +7 -0
  189. package/examples/jsm/loaders/ColladaLoader.js +40 -12
  190. package/examples/jsm/loaders/DDSLoader.js +21 -28
  191. package/examples/jsm/loaders/EXRLoader.js +309 -293
  192. package/examples/jsm/loaders/FBXLoader.js +11 -0
  193. package/examples/jsm/loaders/FontLoader.js +2 -2
  194. package/examples/jsm/loaders/GCodeLoader.js +0 -1
  195. package/examples/jsm/loaders/GLTFLoader.js +263 -68
  196. package/examples/jsm/loaders/HDRCubeTextureLoader.js +1 -4
  197. package/examples/jsm/loaders/KMZLoader.js +1 -1
  198. package/examples/jsm/loaders/KTX2Loader.js +1 -10
  199. package/examples/jsm/loaders/LDrawLoader.js +1335 -988
  200. package/examples/jsm/loaders/LUT3dlLoader.js +19 -14
  201. package/examples/jsm/loaders/LUTCubeLoader.js +5 -7
  202. package/examples/jsm/loaders/LWOLoader.js +5 -3
  203. package/examples/jsm/loaders/LogLuvLoader.js +2 -2
  204. package/examples/jsm/loaders/MD2Loader.js +5 -5
  205. package/examples/jsm/loaders/MMDLoader.js +21 -3
  206. package/examples/jsm/loaders/MTLLoader.js +11 -4
  207. package/examples/jsm/loaders/NRRDLoader.js +1 -2
  208. package/examples/jsm/loaders/OBJLoader.js +11 -7
  209. package/examples/jsm/loaders/PCDLoader.js +25 -17
  210. package/examples/jsm/loaders/PDBLoader.js +11 -11
  211. package/examples/jsm/loaders/PLYLoader.js +10 -2
  212. package/examples/jsm/loaders/RGBELoader.js +11 -20
  213. package/examples/jsm/loaders/RGBMLoader.js +15 -12
  214. package/examples/jsm/loaders/STLLoader.js +1 -1
  215. package/examples/jsm/loaders/SVGLoader.js +26 -11
  216. package/examples/jsm/loaders/TTFLoader.js +1 -1
  217. package/examples/jsm/loaders/VOXLoader.js +5 -5
  218. package/examples/jsm/loaders/VRMLLoader.js +9 -23
  219. package/examples/jsm/loaders/VTKLoader.js +256 -284
  220. package/examples/jsm/loaders/ifc/web-ifc-api.js +9 -8
  221. package/examples/jsm/loaders/lwo/LWO2Parser.js +4 -4
  222. package/examples/jsm/loaders/lwo/LWO3Parser.js +4 -4
  223. package/examples/jsm/math/ConvexHull.js +13 -28
  224. package/examples/jsm/math/Lut.js +35 -25
  225. package/examples/jsm/misc/MorphAnimMesh.js +2 -2
  226. package/examples/jsm/misc/ProgressiveLightMap.js +1 -1
  227. package/examples/jsm/misc/Volume.js +39 -46
  228. package/examples/jsm/misc/VolumeSlice.js +21 -21
  229. package/examples/jsm/modifiers/CurveModifier.js +9 -8
  230. package/examples/jsm/modifiers/EdgeSplitModifier.js +0 -13
  231. package/examples/jsm/modifiers/SimplifyModifier.js +1 -18
  232. package/examples/jsm/modifiers/TessellateModifier.js +0 -7
  233. package/examples/jsm/node-editor/NodeEditor.js +485 -163
  234. package/examples/jsm/node-editor/accessors/MatcapUVEditor.js +14 -0
  235. package/examples/jsm/node-editor/accessors/NormalEditor.js +9 -9
  236. package/examples/jsm/node-editor/accessors/PositionEditor.js +9 -9
  237. package/examples/jsm/node-editor/accessors/UVEditor.js +7 -8
  238. package/examples/jsm/node-editor/core/BaseNode.js +89 -0
  239. package/examples/jsm/node-editor/core/DataFile.js +59 -0
  240. package/examples/jsm/node-editor/core/FileEditor.js +20 -0
  241. package/examples/jsm/node-editor/core/FileURLEditor.js +29 -0
  242. package/examples/jsm/node-editor/display/BlendEditor.js +10 -9
  243. package/examples/jsm/node-editor/display/NormalMapEditor.js +49 -0
  244. package/examples/jsm/node-editor/examples/animate-uv.json +1 -1
  245. package/examples/jsm/node-editor/examples/fake-top-light.json +1 -1
  246. package/examples/jsm/node-editor/examples/matcap.json +1 -0
  247. package/examples/jsm/node-editor/examples/oscillator-color.json +1 -1
  248. package/examples/jsm/node-editor/examples/rim.json +1 -1
  249. package/examples/jsm/node-editor/inputs/ColorEditor.js +18 -13
  250. package/examples/jsm/node-editor/inputs/FloatEditor.js +8 -9
  251. package/examples/jsm/node-editor/inputs/SliderEditor.js +6 -7
  252. package/examples/jsm/node-editor/inputs/TextureEditor.js +155 -0
  253. package/examples/jsm/node-editor/inputs/Vector2Editor.js +9 -9
  254. package/examples/jsm/node-editor/inputs/Vector3Editor.js +11 -11
  255. package/examples/jsm/node-editor/inputs/Vector4Editor.js +12 -12
  256. package/examples/jsm/node-editor/materials/BasicMaterialEditor.js +87 -0
  257. package/examples/jsm/node-editor/materials/PointsMaterialEditor.js +102 -0
  258. package/examples/jsm/node-editor/materials/StandardMaterialEditor.js +31 -18
  259. package/examples/jsm/node-editor/math/AngleEditor.js +40 -0
  260. package/examples/jsm/node-editor/math/DotEditor.js +8 -9
  261. package/examples/jsm/node-editor/math/InvertEditor.js +8 -7
  262. package/examples/jsm/node-editor/math/LimiterEditor.js +28 -14
  263. package/examples/jsm/node-editor/math/NormalizeEditor.js +9 -7
  264. package/examples/jsm/node-editor/math/OperatorEditor.js +29 -16
  265. package/examples/jsm/node-editor/math/PowerEditor.js +20 -10
  266. package/examples/jsm/node-editor/math/TrigonometryEditor.js +15 -9
  267. package/examples/jsm/node-editor/procedural/CheckerEditor.js +7 -6
  268. package/examples/jsm/node-editor/scene/MeshEditor.js +99 -0
  269. package/examples/jsm/node-editor/scene/Object3DEditor.js +160 -0
  270. package/examples/jsm/node-editor/scene/PointsEditor.js +99 -0
  271. package/examples/jsm/node-editor/utils/JoinEditor.js +58 -0
  272. package/examples/jsm/node-editor/utils/OscillatorEditor.js +9 -8
  273. package/examples/jsm/node-editor/utils/PreviewEditor.js +166 -0
  274. package/examples/jsm/node-editor/utils/SplitEditor.js +39 -0
  275. package/examples/jsm/node-editor/utils/TimerEditor.js +7 -6
  276. package/examples/jsm/nodes/Nodes.js +283 -94
  277. package/examples/jsm/nodes/accessors/BufferNode.js +24 -0
  278. package/examples/jsm/nodes/accessors/CameraNode.js +27 -196
  279. package/examples/jsm/nodes/accessors/CubeTextureNode.js +103 -0
  280. package/examples/jsm/nodes/accessors/InstanceNode.js +58 -0
  281. package/examples/jsm/{renderers/nodes → nodes}/accessors/MaterialNode.js +49 -11
  282. package/examples/jsm/{renderers/nodes → nodes}/accessors/MaterialReferenceNode.js +0 -0
  283. package/examples/jsm/{renderers/nodes → nodes}/accessors/ModelNode.js +8 -0
  284. package/examples/jsm/{renderers/nodes → nodes}/accessors/ModelViewProjectionNode.js +0 -0
  285. package/examples/jsm/nodes/accessors/NormalNode.js +39 -99
  286. package/examples/jsm/{renderers/nodes → nodes}/accessors/Object3DNode.js +34 -17
  287. package/examples/jsm/{renderers/nodes → nodes}/accessors/PointUVNode.js +2 -2
  288. package/examples/jsm/nodes/accessors/PositionNode.js +41 -110
  289. package/examples/jsm/nodes/accessors/ReferenceNode.js +65 -0
  290. package/examples/jsm/nodes/accessors/ReflectNode.js +34 -121
  291. package/examples/jsm/nodes/accessors/SkinningNode.js +102 -0
  292. package/examples/jsm/nodes/accessors/StorageBufferNode.js +21 -0
  293. package/examples/jsm/nodes/accessors/TextureNode.js +100 -0
  294. package/examples/jsm/nodes/accessors/UVNode.js +17 -43
  295. package/examples/jsm/nodes/accessors/UserDataNode.js +23 -0
  296. package/examples/jsm/nodes/core/ArrayUniformNode.js +23 -0
  297. package/examples/jsm/nodes/core/AttributeNode.js +33 -30
  298. package/examples/jsm/{renderers/nodes → nodes}/core/BypassNode.js +2 -2
  299. package/examples/jsm/{renderers/nodes → nodes}/core/CodeNode.js +2 -30
  300. package/examples/jsm/nodes/core/ConstNode.js +10 -106
  301. package/examples/jsm/{renderers/nodes → nodes}/core/ContextNode.js +17 -3
  302. package/examples/jsm/nodes/core/ExpressionNode.js +24 -7
  303. package/examples/jsm/nodes/core/FunctionCallNode.js +26 -67
  304. package/examples/jsm/nodes/core/FunctionNode.js +43 -216
  305. package/examples/jsm/nodes/core/InputNode.js +28 -60
  306. package/examples/jsm/nodes/core/InstanceIndexNode.js +21 -0
  307. package/examples/jsm/nodes/core/Node.js +228 -94
  308. package/examples/jsm/{renderers/nodes → nodes}/core/NodeAttribute.js +2 -2
  309. package/examples/jsm/nodes/core/NodeBuilder.js +388 -560
  310. package/examples/jsm/{renderers/nodes → nodes}/core/NodeCode.js +0 -0
  311. package/examples/jsm/nodes/core/NodeFrame.js +30 -23
  312. package/examples/jsm/{renderers/nodes → nodes}/core/NodeFunction.js +0 -0
  313. package/examples/jsm/{renderers/nodes → nodes}/core/NodeFunctionInput.js +0 -0
  314. package/examples/jsm/{renderers/nodes → nodes}/core/NodeKeywords.js +0 -0
  315. package/examples/jsm/{renderers/nodes → nodes}/core/NodeParser.js +0 -0
  316. package/examples/jsm/nodes/core/NodeUniform.js +8 -6
  317. package/examples/jsm/nodes/core/NodeUtils.js +69 -37
  318. package/examples/jsm/{renderers/nodes → nodes}/core/NodeVar.js +2 -2
  319. package/examples/jsm/{renderers/nodes → nodes}/core/NodeVary.js +2 -2
  320. package/examples/jsm/{renderers/nodes → nodes}/core/PropertyNode.js +0 -0
  321. package/examples/jsm/nodes/core/TempNode.js +20 -112
  322. package/examples/jsm/nodes/core/UniformNode.js +46 -0
  323. package/examples/jsm/nodes/core/VarNode.js +58 -27
  324. package/examples/jsm/{renderers/nodes → nodes}/core/VaryNode.js +0 -0
  325. package/examples/jsm/{renderers/nodes → nodes}/core/constants.js +0 -0
  326. package/examples/jsm/nodes/display/ColorSpaceNode.js +95 -0
  327. package/examples/jsm/nodes/display/FrontFacingNode.js +21 -0
  328. package/examples/jsm/nodes/display/NormalMapNode.js +86 -0
  329. package/examples/jsm/nodes/display/ToneMappingNode.js +51 -0
  330. package/examples/jsm/nodes/fog/FogNode.js +31 -0
  331. package/examples/jsm/nodes/fog/FogRangeNode.js +27 -0
  332. package/examples/jsm/nodes/functions/BSDF/BRDF_GGX.js +33 -0
  333. package/examples/jsm/nodes/functions/BSDF/BRDF_Lambert.js +9 -0
  334. package/examples/jsm/nodes/functions/BSDF/DFGApprox.js +27 -0
  335. package/examples/jsm/nodes/functions/BSDF/D_GGX.js +18 -0
  336. package/examples/jsm/nodes/functions/BSDF/F_Schlick.js +18 -0
  337. package/examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated.js +18 -0
  338. package/examples/jsm/nodes/functions/PhysicalLightingModel.js +94 -0
  339. package/examples/jsm/nodes/functions/light/getDistanceAttenuation.js +22 -0
  340. package/examples/jsm/nodes/functions/material/getGeometryRoughness.js +12 -0
  341. package/examples/jsm/nodes/functions/material/getRoughness.js +18 -0
  342. package/examples/jsm/nodes/gpgpu/ComputeNode.js +63 -0
  343. package/examples/jsm/nodes/lighting/AONode.js +25 -0
  344. package/examples/jsm/nodes/lighting/AnalyticLightNode.js +37 -0
  345. package/examples/jsm/nodes/lighting/EnvironmentNode.js +65 -0
  346. package/examples/jsm/nodes/lighting/HemisphereLightNode.js +50 -0
  347. package/examples/jsm/nodes/lighting/LightingContextNode.js +75 -0
  348. package/examples/jsm/nodes/lighting/LightingNode.js +19 -0
  349. package/examples/jsm/nodes/lighting/LightsNode.js +120 -0
  350. package/examples/jsm/nodes/lighting/PunctualLightNode.js +68 -0
  351. package/examples/jsm/nodes/loaders/NodeLoader.js +107 -0
  352. package/examples/jsm/nodes/loaders/NodeMaterialLoader.js +42 -0
  353. package/examples/jsm/nodes/loaders/NodeObjectLoader.js +70 -0
  354. package/examples/jsm/{renderers/nodes → nodes}/materials/LineBasicNodeMaterial.js +11 -4
  355. package/examples/jsm/nodes/materials/Materials.js +65 -0
  356. package/examples/jsm/{renderers/nodes → nodes}/materials/MeshBasicNodeMaterial.js +13 -4
  357. package/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js +162 -22
  358. package/examples/jsm/nodes/materials/NodeMaterial.js +128 -113
  359. package/examples/jsm/{renderers/nodes → nodes}/materials/PointsNodeMaterial.js +13 -4
  360. package/examples/jsm/nodes/materials/SpriteNodeMaterial.js +84 -16
  361. package/examples/jsm/nodes/math/CondNode.js +26 -95
  362. package/examples/jsm/nodes/math/MathNode.js +164 -187
  363. package/examples/jsm/nodes/math/OperatorNode.js +161 -41
  364. package/examples/jsm/{renderers/nodes → nodes}/parsers/GLSLNodeFunction.js +2 -2
  365. package/examples/jsm/{renderers/nodes → nodes}/parsers/GLSLNodeParser.js +0 -0
  366. package/examples/jsm/{renderers/nodes → nodes}/parsers/WGSLNodeFunction.js +20 -5
  367. package/examples/jsm/{renderers/nodes → nodes}/parsers/WGSLNodeParser.js +0 -0
  368. package/examples/jsm/nodes/procedural/CheckerNode.js +16 -57
  369. package/examples/jsm/nodes/shadernode/ShaderNode.js +262 -0
  370. package/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +274 -0
  371. package/examples/jsm/nodes/shadernode/ShaderNodeElements.js +106 -0
  372. package/examples/jsm/{renderers/nodes → nodes}/utils/ArrayElementNode.js +3 -3
  373. package/examples/jsm/{renderers/nodes → nodes}/utils/ConvertNode.js +11 -3
  374. package/examples/jsm/nodes/utils/JoinNode.js +17 -89
  375. package/examples/jsm/nodes/utils/MatcapUVNode.js +25 -0
  376. package/examples/jsm/nodes/utils/MaxMipLevelNode.js +32 -0
  377. package/examples/jsm/{renderers/nodes → nodes}/utils/OscNode.js +18 -2
  378. package/examples/jsm/{renderers/nodes → nodes}/utils/SplitNode.js +35 -4
  379. package/examples/jsm/{renderers/nodes → nodes}/utils/SpriteSheetUVNode.js +3 -3
  380. package/examples/jsm/nodes/utils/TimerNode.js +30 -65
  381. package/examples/jsm/objects/Lensflare.js +6 -6
  382. package/examples/jsm/objects/LightningStorm.js +4 -4
  383. package/examples/jsm/objects/MarchingCubes.js +5 -9
  384. package/examples/jsm/objects/Reflector.js +8 -19
  385. package/examples/jsm/objects/ReflectorForSSRPass.js +2 -14
  386. package/examples/jsm/objects/Refractor.js +8 -19
  387. package/examples/jsm/objects/ShadowMesh.js +2 -2
  388. package/examples/jsm/objects/Sky.js +2 -2
  389. package/examples/jsm/objects/Water.js +3 -18
  390. package/examples/jsm/objects/Water2.js +2 -2
  391. package/examples/jsm/offscreen/jank.js +5 -5
  392. package/examples/jsm/offscreen/offscreen.js +1 -1
  393. package/examples/jsm/offscreen/scene.js +11 -11
  394. package/examples/jsm/postprocessing/AdaptiveToneMappingPass.js +5 -7
  395. package/examples/jsm/postprocessing/AfterimagePass.js +0 -10
  396. package/examples/jsm/postprocessing/BloomPass.js +48 -20
  397. package/examples/jsm/postprocessing/DotScreenPass.js +1 -1
  398. package/examples/jsm/postprocessing/EffectComposer.js +1 -9
  399. package/examples/jsm/postprocessing/GlitchPass.js +7 -6
  400. package/examples/jsm/postprocessing/LUTPass.js +2 -2
  401. package/examples/jsm/postprocessing/OutlinePass.js +8 -15
  402. package/examples/jsm/postprocessing/SAOPass.js +2 -9
  403. package/examples/jsm/postprocessing/SMAAPass.js +2 -11
  404. package/examples/jsm/postprocessing/SSAARenderPass.js +1 -4
  405. package/examples/jsm/postprocessing/SSAOPass.js +13 -25
  406. package/examples/jsm/postprocessing/SSRPass.js +3 -9
  407. package/examples/jsm/postprocessing/SavePass.js +1 -3
  408. package/examples/jsm/postprocessing/TAARenderPass.js +4 -4
  409. package/examples/jsm/postprocessing/UnrealBloomPass.js +3 -8
  410. package/examples/jsm/renderers/CSS2DRenderer.js +21 -19
  411. package/examples/jsm/renderers/CSS3DRenderer.js +39 -34
  412. package/examples/jsm/renderers/Projector.js +101 -128
  413. package/examples/jsm/renderers/SVGRenderer.js +4 -4
  414. package/examples/jsm/renderers/webgl/nodes/SlotNode.js +1 -1
  415. package/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +127 -23
  416. package/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +14 -3
  417. package/examples/jsm/renderers/webgl/nodes/WebGLPhysicalContextNode.js +5 -5
  418. package/examples/jsm/renderers/webgpu/WebGPUAttributes.js +4 -4
  419. package/examples/jsm/renderers/webgpu/WebGPUBackground.js +16 -10
  420. package/examples/jsm/renderers/webgpu/WebGPUBindings.js +8 -7
  421. package/examples/jsm/renderers/webgpu/WebGPUBuffer.js +43 -0
  422. package/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js +14 -7
  423. package/examples/jsm/renderers/webgpu/WebGPUInfo.js +1 -1
  424. package/examples/jsm/renderers/webgpu/WebGPUObjects.js +0 -6
  425. package/examples/jsm/renderers/webgpu/WebGPURenderPipeline.js +20 -20
  426. package/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js +2 -1
  427. package/examples/jsm/renderers/webgpu/WebGPURenderStates.js +66 -0
  428. package/examples/jsm/renderers/webgpu/WebGPURenderer.js +49 -28
  429. package/examples/jsm/renderers/webgpu/WebGPUSampledTexture.js +14 -14
  430. package/examples/jsm/renderers/webgpu/WebGPUSampler.js +2 -2
  431. package/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js +5 -8
  432. package/examples/jsm/renderers/webgpu/WebGPUTextureUtils.js +23 -20
  433. package/examples/jsm/renderers/webgpu/WebGPUTextures.js +38 -20
  434. package/examples/jsm/renderers/webgpu/WebGPUUniform.js +14 -14
  435. package/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js +5 -32
  436. package/examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js +2 -2
  437. package/examples/jsm/renderers/webgpu/constants.js +3 -2
  438. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +276 -278
  439. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js +20 -2
  440. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js +24 -14
  441. package/examples/jsm/shaders/BlendShader.js +2 -1
  442. package/examples/jsm/shaders/CopyShader.js +4 -3
  443. package/examples/jsm/shaders/DigitalGlitch.js +3 -3
  444. package/examples/jsm/shaders/FXAAShader.js +219 -1051
  445. package/examples/jsm/shaders/FreiChenShader.js +1 -1
  446. package/examples/jsm/shaders/HorizontalBlurShader.js +1 -1
  447. package/examples/jsm/shaders/HorizontalTiltShiftShader.js +1 -1
  448. package/examples/jsm/shaders/MMDToonShader.js +0 -1
  449. package/examples/jsm/shaders/SSAOShader.js +1 -1
  450. package/examples/jsm/shaders/SSRShader.js +3 -3
  451. package/examples/jsm/shaders/ToneMapShader.js +1 -1
  452. package/examples/jsm/utils/BufferGeometryUtils.js +292 -22
  453. package/examples/jsm/utils/GPUStatsPanel.js +1 -1
  454. package/examples/jsm/utils/LDrawUtils.js +202 -0
  455. package/examples/jsm/utils/SceneUtils.js +76 -2
  456. package/examples/jsm/utils/SkeletonUtils.js +1 -1
  457. package/examples/jsm/utils/UVsDebug.js +23 -32
  458. package/examples/jsm/webxr/ARButton.js +14 -4
  459. package/examples/jsm/webxr/OculusHandPointerModel.js +3 -3
  460. package/examples/jsm/webxr/Text2D.js +1 -1
  461. package/examples/jsm/webxr/VRButton.js +39 -1
  462. package/examples/jsm/webxr/XREstimatedLight.js +2 -0
  463. package/examples/jsm/webxr/XRHandMeshModel.js +3 -7
  464. package/examples/jsm/webxr/XRHandModelFactory.js +3 -0
  465. package/package.json +53 -25
  466. package/src/Three.Legacy.js +41 -1912
  467. package/src/Three.js +6 -3
  468. package/src/animation/AnimationClip.js +1 -1
  469. package/src/animation/AnimationMixer.js +6 -3
  470. package/src/animation/AnimationObjectGroup.js +2 -2
  471. package/src/animation/PropertyBinding.js +8 -18
  472. package/src/audio/PositionalAudio.js +8 -0
  473. package/src/cameras/ArrayCamera.js +2 -3
  474. package/src/cameras/Camera.js +2 -2
  475. package/src/cameras/CubeCamera.js +8 -1
  476. package/src/cameras/OrthographicCamera.js +2 -2
  477. package/src/cameras/PerspectiveCamera.js +2 -2
  478. package/src/constants.js +8 -18
  479. package/src/core/BufferAttribute.js +7 -12
  480. package/src/core/BufferGeometry.js +6 -6
  481. package/src/core/GLBufferAttribute.js +2 -2
  482. package/src/core/InstancedBufferAttribute.js +2 -2
  483. package/src/core/InstancedBufferGeometry.js +2 -2
  484. package/src/core/InstancedInterleavedBuffer.js +2 -2
  485. package/src/core/InterleavedBuffer.js +2 -2
  486. package/src/core/InterleavedBufferAttribute.js +5 -11
  487. package/src/core/Object3D.js +6 -3
  488. package/src/core/Raycaster.js +2 -2
  489. package/src/extras/DataUtils.js +127 -40
  490. package/src/extras/Earcut.js +2 -2
  491. package/src/extras/ImageUtils.js +63 -0
  492. package/src/extras/PMREMGenerator.js +152 -174
  493. package/src/extras/core/CurvePath.js +3 -3
  494. package/src/extras/core/ShapePath.js +5 -6
  495. package/src/extras/curves/ArcCurve.js +2 -2
  496. package/src/extras/curves/CatmullRomCurve3.js +2 -2
  497. package/src/extras/curves/CubicBezierCurve.js +2 -2
  498. package/src/extras/curves/CubicBezierCurve3.js +2 -2
  499. package/src/extras/curves/EllipseCurve.js +2 -2
  500. package/src/extras/curves/LineCurve.js +2 -2
  501. package/src/extras/curves/LineCurve3.js +2 -1
  502. package/src/extras/curves/QuadraticBezierCurve.js +2 -2
  503. package/src/extras/curves/QuadraticBezierCurve3.js +2 -2
  504. package/src/extras/curves/SplineCurve.js +2 -2
  505. package/src/geometries/CapsuleGeometry.js +33 -0
  506. package/src/geometries/ExtrudeGeometry.js +2 -0
  507. package/src/geometries/Geometries.js +1 -0
  508. package/src/geometries/LatheGeometry.js +1 -1
  509. package/src/geometries/TorusKnotGeometry.js +2 -2
  510. package/src/geometries/WireframeGeometry.js +3 -2
  511. package/src/helpers/BoxHelper.js +2 -2
  512. package/src/helpers/PointLightHelper.js +1 -1
  513. package/src/helpers/SkeletonHelper.js +3 -2
  514. package/src/lights/AmbientLight.js +2 -2
  515. package/src/lights/AmbientLightProbe.js +2 -2
  516. package/src/lights/DirectionalLight.js +2 -2
  517. package/src/lights/DirectionalLightShadow.js +2 -2
  518. package/src/lights/HemisphereLight.js +4 -4
  519. package/src/lights/HemisphereLightProbe.js +2 -2
  520. package/src/lights/Light.js +4 -4
  521. package/src/lights/LightProbe.js +2 -2
  522. package/src/lights/PointLight.js +4 -4
  523. package/src/lights/PointLightShadow.js +2 -2
  524. package/src/lights/RectAreaLight.js +2 -2
  525. package/src/lights/SpotLight.js +4 -4
  526. package/src/lights/SpotLightShadow.js +2 -2
  527. package/src/loaders/FileLoader.js +23 -4
  528. package/src/loaders/ImageBitmapLoader.js +2 -2
  529. package/src/loaders/ImageLoader.js +1 -1
  530. package/src/loaders/LoaderUtils.js +1 -1
  531. package/src/loaders/MaterialLoader.js +8 -3
  532. package/src/loaders/ObjectLoader.js +23 -25
  533. package/src/materials/LineBasicMaterial.js +6 -13
  534. package/src/materials/LineDashedMaterial.js +2 -15
  535. package/src/materials/Material.js +49 -15
  536. package/src/materials/Materials.js +66 -18
  537. package/src/materials/MeshBasicMaterial.js +6 -31
  538. package/src/materials/MeshDepthMaterial.js +2 -22
  539. package/src/materials/MeshDistanceMaterial.js +2 -22
  540. package/src/materials/MeshLambertMaterial.js +6 -34
  541. package/src/materials/MeshMatcapMaterial.js +6 -28
  542. package/src/materials/MeshNormalMaterial.js +2 -26
  543. package/src/materials/MeshPhongMaterial.js +6 -48
  544. package/src/materials/MeshPhysicalMaterial.js +32 -35
  545. package/src/materials/MeshStandardMaterial.js +6 -54
  546. package/src/materials/MeshToonMaterial.js +6 -38
  547. package/src/materials/PointsMaterial.js +6 -15
  548. package/src/materials/RawShaderMaterial.js +2 -2
  549. package/src/materials/ShaderMaterial.js +3 -17
  550. package/src/materials/ShadowMaterial.js +6 -8
  551. package/src/materials/SpriteMaterial.js +6 -12
  552. package/src/math/Box2.js +2 -2
  553. package/src/math/Box3.js +29 -15
  554. package/src/math/Color.js +85 -36
  555. package/src/math/ColorManagement.js +74 -0
  556. package/src/math/Euler.js +16 -14
  557. package/src/math/Interpolant.js +4 -9
  558. package/src/math/MathUtils.js +76 -7
  559. package/src/math/Matrix3.js +2 -2
  560. package/src/math/Matrix4.js +2 -3
  561. package/src/math/Plane.js +2 -2
  562. package/src/math/Quaternion.js +12 -3
  563. package/src/math/Spherical.js +1 -1
  564. package/src/math/SphericalHarmonics3.js +2 -2
  565. package/src/math/Vector2.js +2 -2
  566. package/src/math/Vector3.js +12 -3
  567. package/src/math/Vector4.js +2 -2
  568. package/src/math/interpolants/DiscreteInterpolant.js +1 -1
  569. package/src/objects/Bone.js +2 -2
  570. package/src/objects/Group.js +2 -2
  571. package/src/objects/InstancedMesh.js +4 -4
  572. package/src/objects/Line.js +76 -107
  573. package/src/objects/LineLoop.js +2 -2
  574. package/src/objects/LineSegments.js +15 -23
  575. package/src/objects/Mesh.js +82 -104
  576. package/src/objects/Points.js +32 -54
  577. package/src/objects/SkinnedMesh.js +5 -8
  578. package/src/objects/Sprite.js +4 -4
  579. package/src/renderers/WebGL3DRenderTarget.js +22 -0
  580. package/src/renderers/WebGLArrayRenderTarget.js +22 -0
  581. package/src/renderers/WebGLCubeRenderTarget.js +7 -16
  582. package/src/renderers/WebGLMultipleRenderTargets.js +8 -5
  583. package/src/renderers/WebGLRenderTarget.js +19 -18
  584. package/src/renderers/WebGLRenderer.js +112 -133
  585. package/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js +46 -0
  586. package/src/renderers/shaders/ShaderChunk/common.glsl.js +1 -0
  587. package/src/renderers/shaders/ShaderChunk/cube_uv_reflection_fragment.glsl.js +12 -13
  588. package/src/renderers/shaders/ShaderChunk/dithering_pars_fragment.glsl.js +1 -1
  589. package/src/renderers/shaders/ShaderChunk/emissivemap_fragment.glsl.js +0 -2
  590. package/src/renderers/shaders/ShaderChunk/encodings_pars_fragment.glsl.js +0 -4
  591. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +0 -2
  592. package/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +3 -19
  593. package/src/renderers/shaders/ShaderChunk/iridescence_fragment.glsl.js +107 -0
  594. package/src/renderers/shaders/ShaderChunk/iridescence_pars_fragment.glsl.js +14 -0
  595. package/src/renderers/shaders/ShaderChunk/lightmap_fragment.glsl.js +1 -7
  596. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +25 -0
  597. package/src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js +1 -7
  598. package/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +25 -2
  599. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +43 -5
  600. package/src/renderers/shaders/ShaderChunk/map_fragment.glsl.js +10 -3
  601. package/src/renderers/shaders/ShaderChunk/map_particle_fragment.glsl.js +1 -2
  602. package/src/renderers/shaders/ShaderChunk/morphcolor_vertex.glsl.js +24 -0
  603. package/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js +1 -1
  604. package/src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js +7 -7
  605. package/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js +1 -9
  606. package/src/renderers/shaders/ShaderChunk/skinning_pars_vertex.glsl.js +16 -31
  607. package/src/renderers/shaders/ShaderChunk/transmission_pars_fragment.glsl.js +9 -8
  608. package/src/renderers/shaders/ShaderChunk.js +6 -0
  609. package/src/renderers/shaders/ShaderLib/background.glsl.js +8 -2
  610. package/src/renderers/shaders/ShaderLib/equirect.glsl.js +1 -3
  611. package/src/renderers/shaders/ShaderLib/linedashed.glsl.js +1 -0
  612. package/src/renderers/shaders/ShaderLib/meshbasic.glsl.js +3 -2
  613. package/src/renderers/shaders/ShaderLib/meshlambert.glsl.js +1 -0
  614. package/src/renderers/shaders/ShaderLib/meshmatcap.glsl.js +2 -2
  615. package/src/renderers/shaders/ShaderLib/meshnormal.glsl.js +6 -0
  616. package/src/renderers/shaders/ShaderLib/meshphong.glsl.js +1 -0
  617. package/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +10 -0
  618. package/src/renderers/shaders/ShaderLib/meshtoon.glsl.js +1 -0
  619. package/src/renderers/shaders/ShaderLib/points.glsl.js +1 -0
  620. package/src/renderers/shaders/ShaderLib.js +8 -2
  621. package/src/renderers/shaders/UniformsLib.js +2 -2
  622. package/src/renderers/webgl/WebGLAttributes.js +6 -6
  623. package/src/renderers/webgl/WebGLBackground.js +5 -2
  624. package/src/renderers/webgl/WebGLBindingStates.js +57 -29
  625. package/src/renderers/webgl/WebGLCubeMaps.js +0 -4
  626. package/src/renderers/webgl/WebGLCubeUVMaps.js +29 -16
  627. package/src/renderers/webgl/WebGLGeometries.js +2 -2
  628. package/src/renderers/webgl/WebGLLights.js +0 -1
  629. package/src/renderers/webgl/WebGLMaterials.js +72 -187
  630. package/src/renderers/webgl/WebGLMorphtargets.js +59 -23
  631. package/src/renderers/webgl/WebGLProgram.js +56 -30
  632. package/src/renderers/webgl/WebGLPrograms.js +80 -130
  633. package/src/renderers/webgl/WebGLShaderCache.js +4 -3
  634. package/src/renderers/webgl/WebGLShadowMap.js +6 -8
  635. package/src/renderers/webgl/WebGLState.js +83 -2
  636. package/src/renderers/webgl/WebGLTextures.js +618 -375
  637. package/src/renderers/webgl/WebGLUniforms.js +79 -80
  638. package/src/renderers/webgl/WebGLUtils.js +97 -30
  639. package/src/renderers/webxr/WebXRManager.js +64 -47
  640. package/src/scenes/Fog.js +2 -2
  641. package/src/scenes/FogExp2.js +2 -2
  642. package/src/scenes/Scene.js +2 -2
  643. package/src/textures/CanvasTexture.js +2 -2
  644. package/src/textures/CompressedTexture.js +2 -2
  645. package/src/textures/CubeTexture.js +2 -2
  646. package/src/textures/{DataTexture3D.js → Data3DTexture.js} +4 -4
  647. package/src/textures/{DataTexture2DArray.js → DataArrayTexture.js} +4 -4
  648. package/src/textures/DataTexture.js +2 -5
  649. package/src/textures/DepthTexture.js +5 -5
  650. package/src/textures/FramebufferTexture.js +2 -2
  651. package/src/textures/Source.js +123 -0
  652. package/src/textures/Texture.js +29 -98
  653. package/src/textures/VideoTexture.js +2 -4
  654. package/src/utils.js +15 -1
  655. package/examples/js/WebGL.js +0 -90
  656. package/examples/js/csm/Frustum.js +0 -133
  657. package/examples/js/deprecated/Geometry.js +0 -1656
  658. package/examples/js/loaders/VRMLoader.js +0 -72
  659. package/examples/js/objects/ReflectorRTT.js +0 -16
  660. package/examples/js/postprocessing/SSRrPass.js +0 -494
  661. package/examples/js/shaders/SSRrShader.js +0 -336
  662. package/examples/js/utils/RoughnessMipmapper.js +0 -268
  663. package/examples/jsm/deprecated/Geometry.js +0 -1869
  664. package/examples/jsm/libs/opentype.module.min.js +0 -119
  665. package/examples/jsm/loaders/NodeMaterialLoader.js +0 -263
  666. package/examples/jsm/loaders/VRMLoader.js +0 -78
  667. package/examples/jsm/nodes/accessors/ColorsNode.js +0 -56
  668. package/examples/jsm/nodes/accessors/LightNode.js +0 -60
  669. package/examples/jsm/nodes/accessors/ResolutionNode.js +0 -64
  670. package/examples/jsm/nodes/accessors/ScreenUVNode.js +0 -64
  671. package/examples/jsm/nodes/core/NodeLib.js +0 -64
  672. package/examples/jsm/nodes/core/StructNode.js +0 -106
  673. package/examples/jsm/nodes/effects/BlurNode.js +0 -171
  674. package/examples/jsm/nodes/effects/ColorAdjustmentNode.js +0 -140
  675. package/examples/jsm/nodes/effects/LuminanceNode.js +0 -75
  676. package/examples/jsm/nodes/inputs/BoolNode.js +0 -51
  677. package/examples/jsm/nodes/inputs/ColorNode.js +0 -58
  678. package/examples/jsm/nodes/inputs/CubeTextureNode.js +0 -108
  679. package/examples/jsm/nodes/inputs/FloatNode.js +0 -51
  680. package/examples/jsm/nodes/inputs/IntNode.js +0 -51
  681. package/examples/jsm/nodes/inputs/Matrix3Node.js +0 -63
  682. package/examples/jsm/nodes/inputs/Matrix4Node.js +0 -63
  683. package/examples/jsm/nodes/inputs/PropertyNode.js +0 -47
  684. package/examples/jsm/nodes/inputs/RTTNode.js +0 -157
  685. package/examples/jsm/nodes/inputs/ReflectorNode.js +0 -90
  686. package/examples/jsm/nodes/inputs/ScreenNode.js +0 -28
  687. package/examples/jsm/nodes/inputs/TextureNode.js +0 -116
  688. package/examples/jsm/nodes/inputs/Vector2Node.js +0 -57
  689. package/examples/jsm/nodes/inputs/Vector3Node.js +0 -58
  690. package/examples/jsm/nodes/inputs/Vector4Node.js +0 -59
  691. package/examples/jsm/nodes/materials/BasicNodeMaterial.js +0 -26
  692. package/examples/jsm/nodes/materials/PhongNodeMaterial.js +0 -36
  693. package/examples/jsm/nodes/materials/StandardNodeMaterial.js +0 -40
  694. package/examples/jsm/nodes/materials/nodes/BasicNode.js +0 -154
  695. package/examples/jsm/nodes/materials/nodes/MeshStandardNode.js +0 -116
  696. package/examples/jsm/nodes/materials/nodes/PhongNode.js +0 -409
  697. package/examples/jsm/nodes/materials/nodes/RawNode.js +0 -62
  698. package/examples/jsm/nodes/materials/nodes/SpriteNode.js +0 -236
  699. package/examples/jsm/nodes/materials/nodes/StandardNode.js +0 -609
  700. package/examples/jsm/nodes/misc/BumpMapNode.js +0 -163
  701. package/examples/jsm/nodes/misc/NormalMapNode.js +0 -138
  702. package/examples/jsm/nodes/misc/TextureCubeNode.js +0 -86
  703. package/examples/jsm/nodes/misc/TextureCubeUVNode.js +0 -280
  704. package/examples/jsm/nodes/postprocessing/NodePass.js +0 -93
  705. package/examples/jsm/nodes/postprocessing/NodePostProcessing.js +0 -148
  706. package/examples/jsm/nodes/procedural/Fractal3DNode.js +0 -104
  707. package/examples/jsm/nodes/procedural/Noise2DNode.js +0 -149
  708. package/examples/jsm/nodes/procedural/Noise3DNode.js +0 -141
  709. package/examples/jsm/nodes/utils/BypassNode.js +0 -85
  710. package/examples/jsm/nodes/utils/ColorSpaceNode.js +0 -154
  711. package/examples/jsm/nodes/utils/MaxMIPLevelNode.js +0 -56
  712. package/examples/jsm/nodes/utils/RemapNode.js +0 -155
  713. package/examples/jsm/nodes/utils/SpecularMIPLevelNode.js +0 -101
  714. package/examples/jsm/nodes/utils/SubSlotNode.js +0 -77
  715. package/examples/jsm/nodes/utils/SwitchNode.js +0 -103
  716. package/examples/jsm/nodes/utils/UVTransformNode.js +0 -66
  717. package/examples/jsm/nodes/utils/VelocityNode.js +0 -170
  718. package/examples/jsm/objects/ReflectorRTT.js +0 -15
  719. package/examples/jsm/postprocessing/SSRrPass.js +0 -579
  720. package/examples/jsm/renderers/nodes/Nodes.js +0 -164
  721. package/examples/jsm/renderers/nodes/ShaderNode.js +0 -346
  722. package/examples/jsm/renderers/nodes/accessors/CameraNode.js +0 -68
  723. package/examples/jsm/renderers/nodes/accessors/NormalNode.js +0 -63
  724. package/examples/jsm/renderers/nodes/accessors/PositionNode.js +0 -67
  725. package/examples/jsm/renderers/nodes/accessors/ReferenceNode.js +0 -91
  726. package/examples/jsm/renderers/nodes/accessors/SkinningNode.js +0 -107
  727. package/examples/jsm/renderers/nodes/accessors/UVNode.js +0 -25
  728. package/examples/jsm/renderers/nodes/core/ArrayInputNode.js +0 -23
  729. package/examples/jsm/renderers/nodes/core/AttributeNode.js +0 -54
  730. package/examples/jsm/renderers/nodes/core/ExpressionNode.js +0 -32
  731. package/examples/jsm/renderers/nodes/core/FunctionCallNode.js +0 -67
  732. package/examples/jsm/renderers/nodes/core/FunctionNode.js +0 -87
  733. package/examples/jsm/renderers/nodes/core/InputNode.js +0 -66
  734. package/examples/jsm/renderers/nodes/core/Node.js +0 -104
  735. package/examples/jsm/renderers/nodes/core/NodeBuilder.js +0 -633
  736. package/examples/jsm/renderers/nodes/core/NodeFrame.js +0 -59
  737. package/examples/jsm/renderers/nodes/core/NodeUniform.js +0 -28
  738. package/examples/jsm/renderers/nodes/core/TempNode.js +0 -43
  739. package/examples/jsm/renderers/nodes/core/VarNode.js +0 -51
  740. package/examples/jsm/renderers/nodes/display/ColorSpaceNode.js +0 -133
  741. package/examples/jsm/renderers/nodes/display/NormalMapNode.js +0 -87
  742. package/examples/jsm/renderers/nodes/functions/BSDFs.js +0 -122
  743. package/examples/jsm/renderers/nodes/functions/PhysicalMaterialFunctions.js +0 -27
  744. package/examples/jsm/renderers/nodes/inputs/BufferNode.js +0 -25
  745. package/examples/jsm/renderers/nodes/inputs/ColorNode.js +0 -18
  746. package/examples/jsm/renderers/nodes/inputs/FloatNode.js +0 -17
  747. package/examples/jsm/renderers/nodes/inputs/IntNode.js +0 -17
  748. package/examples/jsm/renderers/nodes/inputs/Matrix3Node.js +0 -18
  749. package/examples/jsm/renderers/nodes/inputs/Matrix4Node.js +0 -18
  750. package/examples/jsm/renderers/nodes/inputs/TextureNode.js +0 -79
  751. package/examples/jsm/renderers/nodes/inputs/Vector2Node.js +0 -18
  752. package/examples/jsm/renderers/nodes/inputs/Vector3Node.js +0 -18
  753. package/examples/jsm/renderers/nodes/inputs/Vector4Node.js +0 -18
  754. package/examples/jsm/renderers/nodes/lights/LightContextNode.js +0 -59
  755. package/examples/jsm/renderers/nodes/lights/LightNode.js +0 -79
  756. package/examples/jsm/renderers/nodes/lights/LightsNode.js +0 -44
  757. package/examples/jsm/renderers/nodes/materials/Materials.js +0 -11
  758. package/examples/jsm/renderers/nodes/materials/MeshStandardNodeMaterial.js +0 -63
  759. package/examples/jsm/renderers/nodes/math/CondNode.js +0 -60
  760. package/examples/jsm/renderers/nodes/math/MathNode.js +0 -241
  761. package/examples/jsm/renderers/nodes/math/OperatorNode.js +0 -174
  762. package/examples/jsm/renderers/nodes/procedural/CheckerNode.js +0 -36
  763. package/examples/jsm/renderers/nodes/utils/JoinNode.js +0 -42
  764. package/examples/jsm/renderers/nodes/utils/TimerNode.js +0 -47
  765. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js +0 -20
  766. package/examples/jsm/shaders/SSRrShader.js +0 -307
  767. package/examples/jsm/utils/RoughnessMipmapper.js +0 -288
  768. package/src/renderers/WebGLMultisampleRenderTarget.js +0 -33
@@ -13,7 +13,8 @@ import {
13
13
  ShaderMaterial,
14
14
  UniformsLib,
15
15
  UniformsUtils,
16
- Vector3
16
+ Vector3,
17
+ Ray
17
18
  } from 'three';
18
19
 
19
20
  // Special surface finish tag types.
@@ -35,6 +36,9 @@ const FILE_LOCATION_TRY_RELATIVE = 4;
35
36
  const FILE_LOCATION_TRY_ABSOLUTE = 5;
36
37
  const FILE_LOCATION_NOT_FOUND = 6;
37
38
 
39
+ const MAIN_COLOUR_CODE = '16';
40
+ const MAIN_EDGE_COLOUR_CODE = '24';
41
+
38
42
  const _tempVec0 = new Vector3();
39
43
  const _tempVec1 = new Vector3();
40
44
 
@@ -167,16 +171,56 @@ class LDrawConditionalLineMaterial extends ShaderMaterial {
167
171
 
168
172
  }
169
173
 
170
- function smoothNormals( faces, lineSegments ) {
174
+ class ConditionalLineSegments extends LineSegments {
175
+
176
+ constructor( geometry, material ) {
177
+
178
+ super( geometry, material );
179
+ this.isConditionalLine = true;
180
+
181
+ }
182
+
183
+ }
184
+
185
+ function generateFaceNormals( faces ) {
186
+
187
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
188
+
189
+ const face = faces[ i ];
190
+ const vertices = face.vertices;
191
+ const v0 = vertices[ 0 ];
192
+ const v1 = vertices[ 1 ];
193
+ const v2 = vertices[ 2 ];
194
+
195
+ _tempVec0.subVectors( v1, v0 );
196
+ _tempVec1.subVectors( v2, v1 );
197
+ face.faceNormal = new Vector3()
198
+ .crossVectors( _tempVec0, _tempVec1 )
199
+ .normalize();
200
+
201
+ }
202
+
203
+ }
204
+
205
+ const _ray = new Ray();
206
+ function smoothNormals( faces, lineSegments, checkSubSegments = false ) {
207
+
208
+ // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because
209
+ // it allows edges to be smoothed as expected (see minifig arms).
210
+ // --
211
+ // And the vector values are initialize multiplied by 1 + 1e-10 to account for floating
212
+ // point errors on vertices along quantization boundaries. Ie after matrix multiplication
213
+ // vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't
214
+ // get merged. This added epsilon attempts to push these error values to the same quantized
215
+ // value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169.
171
216
 
217
+ const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
172
218
  function hashVertex( v ) {
173
219
 
174
- // NOTE: 1e2 is pretty coarse but was chosen because it allows edges
175
- // to be smoothed as expected (see minifig arms). The errors between edges
176
- // could be due to matrix multiplication.
177
- const x = ~ ~ ( v.x * 1e2 );
178
- const y = ~ ~ ( v.y * 1e2 );
179
- const z = ~ ~ ( v.z * 1e2 );
220
+ const x = ~ ~ ( v.x * hashMultiplier );
221
+ const y = ~ ~ ( v.y * hashMultiplier );
222
+ const z = ~ ~ ( v.z * hashMultiplier );
223
+
180
224
  return `${ x },${ y },${ z }`;
181
225
 
182
226
  }
@@ -187,7 +231,27 @@ function smoothNormals( faces, lineSegments ) {
187
231
 
188
232
  }
189
233
 
234
+ // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected
235
+ // onto the original line.
236
+ function toNormalizedRay( v0, v1, targetRay ) {
237
+
238
+ targetRay.direction.subVectors( v1, v0 ).normalize();
239
+
240
+ const scalar = v0.dot( targetRay.direction );
241
+ targetRay.origin.copy( v0 ).addScaledVector( targetRay.direction, - scalar );
242
+
243
+ return targetRay;
244
+
245
+ }
246
+
247
+ function hashRay( ray ) {
248
+
249
+ return hashEdge( ray.origin, ray.direction );
250
+
251
+ }
252
+
190
253
  const hardEdges = new Set();
254
+ const hardEdgeRays = new Map();
191
255
  const halfEdgeList = {};
192
256
  const normals = [];
193
257
 
@@ -201,6 +265,43 @@ function smoothNormals( faces, lineSegments ) {
201
265
  hardEdges.add( hashEdge( v0, v1 ) );
202
266
  hardEdges.add( hashEdge( v1, v0 ) );
203
267
 
268
+ // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check
269
+ // and requires more memory.
270
+ if ( checkSubSegments ) {
271
+
272
+ // add both ray directions to the map
273
+ const ray = toNormalizedRay( v0, v1, new Ray() );
274
+ const rh1 = hashRay( ray );
275
+ if ( ! hardEdgeRays.has( rh1 ) ) {
276
+
277
+ toNormalizedRay( v1, v0, ray );
278
+ const rh2 = hashRay( ray );
279
+
280
+ const info = {
281
+ ray,
282
+ distances: [],
283
+ };
284
+
285
+ hardEdgeRays.set( rh1, info );
286
+ hardEdgeRays.set( rh2, info );
287
+
288
+ }
289
+
290
+ // store both segments ends in min, max order in the distances array to check if a face edge is a
291
+ // subsegment later.
292
+ const info = hardEdgeRays.get( rh1 );
293
+ let d0 = info.ray.direction.dot( v0 );
294
+ let d1 = info.ray.direction.dot( v1 );
295
+ if ( d0 > d1 ) {
296
+
297
+ [ d0, d1 ] = [ d1, d0 ];
298
+
299
+ }
300
+
301
+ info.distances.push( d0, d1 );
302
+
303
+ }
304
+
204
305
  }
205
306
 
206
307
  // track the half edges associated with each triangle
@@ -218,7 +319,53 @@ function smoothNormals( faces, lineSegments ) {
218
319
  const hash = hashEdge( v0, v1 );
219
320
 
220
321
  // don't add the triangle if the edge is supposed to be hard
221
- if ( hardEdges.has( hash ) ) continue;
322
+ if ( hardEdges.has( hash ) ) {
323
+
324
+ continue;
325
+
326
+ }
327
+
328
+ // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds
329
+ if ( checkSubSegments ) {
330
+
331
+ toNormalizedRay( v0, v1, _ray );
332
+
333
+ const rayHash = hashRay( _ray );
334
+ if ( hardEdgeRays.has( rayHash ) ) {
335
+
336
+ const info = hardEdgeRays.get( rayHash );
337
+ const { ray, distances } = info;
338
+ let d0 = ray.direction.dot( v0 );
339
+ let d1 = ray.direction.dot( v1 );
340
+
341
+ if ( d0 > d1 ) {
342
+
343
+ [ d0, d1 ] = [ d1, d0 ];
344
+
345
+ }
346
+
347
+ // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals
348
+ let found = false;
349
+ for ( let i = 0, l = distances.length; i < l; i += 2 ) {
350
+
351
+ if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) {
352
+
353
+ found = true;
354
+ break;
355
+
356
+ }
357
+
358
+ }
359
+
360
+ if ( found ) {
361
+
362
+ continue;
363
+
364
+ }
365
+
366
+ }
367
+
368
+ }
222
369
 
223
370
  const info = {
224
371
  index: index,
@@ -389,13 +536,7 @@ function smoothNormals( faces, lineSegments ) {
389
536
 
390
537
  function isPartType( type ) {
391
538
 
392
- return type === 'Part';
393
-
394
- }
395
-
396
- function isModelType( type ) {
397
-
398
- return type === 'Model' || type === 'Unofficial_Model';
539
+ return type === 'Part' || type === 'Unofficial_Part';
399
540
 
400
541
  }
401
542
 
@@ -462,6 +603,12 @@ class LineParser {
462
603
 
463
604
  }
464
605
 
606
+ getVector() {
607
+
608
+ return new Vector3( parseFloat( this.getToken() ), parseFloat( this.getToken() ), parseFloat( this.getToken() ) );
609
+
610
+ }
611
+
465
612
  getRemainingString() {
466
613
 
467
614
  return this.line.substring( this.currentCharIndex, this.lineLength );
@@ -488,1566 +635,1766 @@ class LineParser {
488
635
 
489
636
  }
490
637
 
491
- class LDrawFileCache {
638
+ // Fetches and parses an intermediate representation of LDraw parts files.
639
+ class LDrawParsedCache {
492
640
 
493
641
  constructor( loader ) {
494
642
 
495
- this.cache = {};
496
643
  this.loader = loader;
644
+ this._cache = {};
497
645
 
498
646
  }
499
647
 
500
- setData( key, contents ) {
501
-
502
- this.cache[ key.toLowerCase() ] = contents;
648
+ cloneResult( original ) {
503
649
 
504
- }
650
+ const result = {};
505
651
 
506
- async loadData( fileName ) {
652
+ // vertices are transformed and normals computed before being converted to geometry
653
+ // so these pieces must be cloned.
654
+ result.faces = original.faces.map( face => {
507
655
 
508
- const key = fileName.toLowerCase();
509
- if ( key in this.cache ) {
656
+ return {
657
+ colorCode: face.colorCode,
658
+ material: face.material,
659
+ vertices: face.vertices.map( v => v.clone() ),
660
+ normals: face.normals.map( () => null ),
661
+ faceNormal: null
662
+ };
510
663
 
511
- return this.cache[ key ];
664
+ } );
512
665
 
513
- }
666
+ result.conditionalSegments = original.conditionalSegments.map( face => {
514
667
 
515
- this.cache[ fileName ] = new Promise( async ( resolve, reject ) => {
668
+ return {
669
+ colorCode: face.colorCode,
670
+ material: face.material,
671
+ vertices: face.vertices.map( v => v.clone() ),
672
+ controlPoints: face.controlPoints.map( v => v.clone() )
673
+ };
516
674
 
517
- let triedLowerCase = false;
518
- let locationState = FILE_LOCATION_AS_IS;
519
- while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
675
+ } );
520
676
 
521
- let subobjectURL = fileName;
522
- switch ( locationState ) {
677
+ result.lineSegments = original.lineSegments.map( face => {
523
678
 
524
- case FILE_LOCATION_AS_IS:
525
- locationState = locationState + 1;
526
- break;
679
+ return {
680
+ colorCode: face.colorCode,
681
+ material: face.material,
682
+ vertices: face.vertices.map( v => v.clone() )
683
+ };
527
684
 
528
- case FILE_LOCATION_TRY_PARTS:
529
- subobjectURL = 'parts/' + subobjectURL;
530
- locationState = locationState + 1;
531
- break;
685
+ } );
532
686
 
533
- case FILE_LOCATION_TRY_P:
534
- subobjectURL = 'p/' + subobjectURL;
535
- locationState = locationState + 1;
536
- break;
687
+ // none if this is subsequently modified
688
+ result.type = original.type;
689
+ result.category = original.category;
690
+ result.keywords = original.keywords;
691
+ result.subobjects = original.subobjects;
692
+ result.totalFaces = original.totalFaces;
693
+ result.startingConstructionStep = original.startingConstructionStep;
694
+ result.materials = original.materials;
695
+ result.group = null;
696
+ return result;
537
697
 
538
- case FILE_LOCATION_TRY_MODELS:
539
- subobjectURL = 'models/' + subobjectURL;
540
- locationState = locationState + 1;
541
- break;
698
+ }
542
699
 
543
- case FILE_LOCATION_TRY_RELATIVE:
544
- subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
545
- locationState = locationState + 1;
546
- break;
700
+ async fetchData( fileName ) {
547
701
 
548
- case FILE_LOCATION_TRY_ABSOLUTE:
702
+ let triedLowerCase = false;
703
+ let locationState = FILE_LOCATION_AS_IS;
704
+ while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
549
705
 
550
- if ( triedLowerCase ) {
706
+ let subobjectURL = fileName;
707
+ switch ( locationState ) {
551
708
 
552
- // Try absolute path
553
- locationState = FILE_LOCATION_NOT_FOUND;
709
+ case FILE_LOCATION_AS_IS:
710
+ locationState = locationState + 1;
711
+ break;
554
712
 
555
- } else {
713
+ case FILE_LOCATION_TRY_PARTS:
714
+ subobjectURL = 'parts/' + subobjectURL;
715
+ locationState = locationState + 1;
716
+ break;
556
717
 
557
- // Next attempt is lower case
558
- fileName = fileName.toLowerCase();
559
- subobjectURL = fileName;
560
- triedLowerCase = true;
561
- locationState = FILE_LOCATION_AS_IS;
718
+ case FILE_LOCATION_TRY_P:
719
+ subobjectURL = 'p/' + subobjectURL;
720
+ locationState = locationState + 1;
721
+ break;
562
722
 
563
- }
723
+ case FILE_LOCATION_TRY_MODELS:
724
+ subobjectURL = 'models/' + subobjectURL;
725
+ locationState = locationState + 1;
726
+ break;
564
727
 
565
- break;
728
+ case FILE_LOCATION_TRY_RELATIVE:
729
+ subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
730
+ locationState = locationState + 1;
731
+ break;
566
732
 
567
- }
733
+ case FILE_LOCATION_TRY_ABSOLUTE:
568
734
 
569
- const loader = this.loader;
570
- const fileLoader = new FileLoader( loader.manager );
571
- fileLoader.setPath( loader.partsLibraryPath );
572
- fileLoader.setRequestHeader( loader.requestHeader );
573
- fileLoader.setWithCredentials( loader.withCredentials );
735
+ if ( triedLowerCase ) {
574
736
 
575
- try {
737
+ // Try absolute path
738
+ locationState = FILE_LOCATION_NOT_FOUND;
576
739
 
577
- const text = await fileLoader.loadAsync( subobjectURL );
578
- this.setData( fileName, text );
579
- resolve( text );
580
- return;
740
+ } else {
581
741
 
582
- } catch {
742
+ // Next attempt is lower case
743
+ fileName = fileName.toLowerCase();
744
+ subobjectURL = fileName;
745
+ triedLowerCase = true;
746
+ locationState = FILE_LOCATION_AS_IS;
583
747
 
584
- continue;
748
+ }
585
749
 
586
- }
750
+ break;
587
751
 
588
752
  }
589
753
 
590
- reject();
754
+ const loader = this.loader;
755
+ const fileLoader = new FileLoader( loader.manager );
756
+ fileLoader.setPath( loader.partsLibraryPath );
757
+ fileLoader.setRequestHeader( loader.requestHeader );
758
+ fileLoader.setWithCredentials( loader.withCredentials );
591
759
 
592
- } );
593
-
594
- return this.cache[ fileName ];
595
-
596
- }
760
+ try {
597
761
 
598
- }
599
-
600
- function sortByMaterial( a, b ) {
762
+ const text = await fileLoader.loadAsync( subobjectURL );
763
+ return text;
601
764
 
602
- if ( a.colourCode === b.colourCode ) {
765
+ } catch {
603
766
 
604
- return 0;
767
+ continue;
605
768
 
606
- }
769
+ }
607
770
 
608
- if ( a.colourCode < b.colourCode ) {
771
+ }
609
772
 
610
- return - 1;
773
+ throw new Error( 'LDrawLoader: Subobject "' + fileName + '" could not be loaded.' );
611
774
 
612
775
  }
613
776
 
614
- return 1;
615
-
616
- }
777
+ parse( text, fileName = null ) {
617
778
 
618
- function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) {
779
+ const loader = this.loader;
619
780
 
620
- // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 )
621
- // With per face / segment material, implemented with mesh groups and materials array
781
+ // final results
782
+ const faces = [];
783
+ const lineSegments = [];
784
+ const conditionalSegments = [];
785
+ const subobjects = [];
786
+ const materials = {};
622
787
 
623
- // Sort the faces or line segments by colour code to make later the mesh groups
624
- elements.sort( sortByMaterial );
788
+ const getLocalMaterial = colorCode => {
625
789
 
626
- if ( totalElements === null ) {
790
+ return materials[ colorCode ] || null;
627
791
 
628
- totalElements = elements.length;
792
+ };
629
793
 
630
- }
794
+ let type = 'Model';
795
+ let category = null;
796
+ let keywords = null;
797
+ let totalFaces = 0;
631
798
 
632
- const positions = new Float32Array( elementSize * totalElements * 3 );
633
- const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null;
634
- const materials = [];
799
+ // split into lines
800
+ if ( text.indexOf( '\r\n' ) !== - 1 ) {
635
801
 
636
- const quadArray = new Array( 6 );
637
- const bufferGeometry = new BufferGeometry();
638
- let prevMaterial = null;
639
- let index0 = 0;
640
- let numGroupVerts = 0;
641
- let offset = 0;
802
+ // This is faster than String.split with regex that splits on both
803
+ text = text.replace( /\r\n/g, '\n' );
642
804
 
643
- for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
805
+ }
644
806
 
645
- const elem = elements[ iElem ];
646
- let vertices = elem.vertices;
647
- if ( vertices.length === 4 ) {
807
+ const lines = text.split( '\n' );
808
+ const numLines = lines.length;
648
809
 
649
- quadArray[ 0 ] = vertices[ 0 ];
650
- quadArray[ 1 ] = vertices[ 1 ];
651
- quadArray[ 2 ] = vertices[ 2 ];
652
- quadArray[ 3 ] = vertices[ 0 ];
653
- quadArray[ 4 ] = vertices[ 2 ];
654
- quadArray[ 5 ] = vertices[ 3 ];
655
- vertices = quadArray;
810
+ let parsingEmbeddedFiles = false;
811
+ let currentEmbeddedFileName = null;
812
+ let currentEmbeddedText = null;
656
813
 
657
- }
814
+ let bfcCertified = false;
815
+ let bfcCCW = true;
816
+ let bfcInverted = false;
817
+ let bfcCull = true;
658
818
 
659
- for ( let j = 0, l = vertices.length; j < l; j ++ ) {
819
+ let startingConstructionStep = false;
660
820
 
661
- const v = vertices[ j ];
662
- const index = offset + j * 3;
663
- positions[ index + 0 ] = v.x;
664
- positions[ index + 1 ] = v.y;
665
- positions[ index + 2 ] = v.z;
821
+ // Parse all line commands
822
+ for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
666
823
 
667
- }
824
+ const line = lines[ lineIndex ];
668
825
 
669
- if ( elementSize === 3 ) {
826
+ if ( line.length === 0 ) continue;
670
827
 
671
- let elemNormals = elem.normals;
672
- if ( elemNormals.length === 4 ) {
828
+ if ( parsingEmbeddedFiles ) {
673
829
 
674
- quadArray[ 0 ] = elemNormals[ 0 ];
675
- quadArray[ 1 ] = elemNormals[ 1 ];
676
- quadArray[ 2 ] = elemNormals[ 2 ];
677
- quadArray[ 3 ] = elemNormals[ 0 ];
678
- quadArray[ 4 ] = elemNormals[ 2 ];
679
- quadArray[ 5 ] = elemNormals[ 3 ];
680
- elemNormals = quadArray;
830
+ if ( line.startsWith( '0 FILE ' ) ) {
681
831
 
682
- }
832
+ // Save previous embedded file in the cache
833
+ this.setData( currentEmbeddedFileName, currentEmbeddedText );
683
834
 
684
- for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
835
+ // New embedded text file
836
+ currentEmbeddedFileName = line.substring( 7 );
837
+ currentEmbeddedText = '';
685
838
 
686
- let n = elem.faceNormal;
687
- if ( elemNormals[ j ] ) {
839
+ } else {
688
840
 
689
- n = elemNormals[ j ].norm;
841
+ currentEmbeddedText += line + '\n';
690
842
 
691
843
  }
692
844
 
693
- const index = offset + j * 3;
694
- normals[ index + 0 ] = n.x;
695
- normals[ index + 1 ] = n.y;
696
- normals[ index + 2 ] = n.z;
845
+ continue;
697
846
 
698
847
  }
699
848
 
700
- }
701
-
702
- if ( prevMaterial !== elem.material ) {
849
+ const lp = new LineParser( line, lineIndex + 1 );
850
+ lp.seekNonSpace();
703
851
 
704
- if ( prevMaterial !== null ) {
852
+ if ( lp.isAtTheEnd() ) {
705
853
 
706
- bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
854
+ // Empty line
855
+ continue;
707
856
 
708
857
  }
709
858
 
710
- materials.push( elem.material );
859
+ // Parse the line type
860
+ const lineType = lp.getToken();
711
861
 
712
- prevMaterial = elem.material;
713
- index0 = offset / 3;
714
- numGroupVerts = vertices.length;
862
+ let material;
863
+ let colorCode;
864
+ let segment;
865
+ let ccw;
866
+ let doubleSided;
867
+ let v0, v1, v2, v3, c0, c1;
715
868
 
716
- } else {
869
+ switch ( lineType ) {
717
870
 
718
- numGroupVerts += vertices.length;
871
+ // Line type 0: Comment or META
872
+ case '0':
719
873
 
720
- }
874
+ // Parse meta directive
875
+ const meta = lp.getToken();
721
876
 
722
- offset += 3 * vertices.length;
877
+ if ( meta ) {
723
878
 
724
- }
879
+ switch ( meta ) {
725
880
 
726
- if ( numGroupVerts > 0 ) {
881
+ case '!LDRAW_ORG':
727
882
 
728
- bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
883
+ type = lp.getToken();
884
+ break;
729
885
 
730
- }
886
+ case '!COLOUR':
731
887
 
732
- bufferGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
888
+ material = loader.parseColorMetaDirective( lp );
889
+ if ( material ) {
733
890
 
734
- if ( normals !== null ) {
891
+ materials[ material.userData.code ] = material;
735
892
 
736
- bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
893
+ } else {
737
894
 
738
- }
895
+ console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
739
896
 
740
- let object3d = null;
897
+ }
741
898
 
742
- if ( elementSize === 2 ) {
899
+ break;
743
900
 
744
- object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
901
+ case '!CATEGORY':
745
902
 
746
- } else if ( elementSize === 3 ) {
903
+ category = lp.getToken();
904
+ break;
747
905
 
748
- object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
906
+ case '!KEYWORDS':
749
907
 
750
- }
908
+ const newKeywords = lp.getRemainingString().split( ',' );
909
+ if ( newKeywords.length > 0 ) {
751
910
 
752
- if ( isConditionalSegments ) {
911
+ if ( ! keywords ) {
753
912
 
754
- object3d.isConditionalLine = true;
913
+ keywords = [];
755
914
 
756
- const controlArray0 = new Float32Array( elements.length * 3 * 2 );
757
- const controlArray1 = new Float32Array( elements.length * 3 * 2 );
758
- const directionArray = new Float32Array( elements.length * 3 * 2 );
759
- for ( let i = 0, l = elements.length; i < l; i ++ ) {
915
+ }
760
916
 
761
- const os = elements[ i ];
762
- const vertices = os.vertices;
763
- const controlPoints = os.controlPoints;
764
- const c0 = controlPoints[ 0 ];
765
- const c1 = controlPoints[ 1 ];
766
- const v0 = vertices[ 0 ];
767
- const v1 = vertices[ 1 ];
768
- const index = i * 3 * 2;
769
- controlArray0[ index + 0 ] = c0.x;
770
- controlArray0[ index + 1 ] = c0.y;
771
- controlArray0[ index + 2 ] = c0.z;
772
- controlArray0[ index + 3 ] = c0.x;
773
- controlArray0[ index + 4 ] = c0.y;
774
- controlArray0[ index + 5 ] = c0.z;
917
+ newKeywords.forEach( function ( keyword ) {
775
918
 
776
- controlArray1[ index + 0 ] = c1.x;
777
- controlArray1[ index + 1 ] = c1.y;
778
- controlArray1[ index + 2 ] = c1.z;
779
- controlArray1[ index + 3 ] = c1.x;
780
- controlArray1[ index + 4 ] = c1.y;
781
- controlArray1[ index + 5 ] = c1.z;
919
+ keywords.push( keyword.trim() );
782
920
 
783
- directionArray[ index + 0 ] = v1.x - v0.x;
784
- directionArray[ index + 1 ] = v1.y - v0.y;
785
- directionArray[ index + 2 ] = v1.z - v0.z;
786
- directionArray[ index + 3 ] = v1.x - v0.x;
787
- directionArray[ index + 4 ] = v1.y - v0.y;
788
- directionArray[ index + 5 ] = v1.z - v0.z;
921
+ } );
789
922
 
790
- }
923
+ }
791
924
 
792
- bufferGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) );
793
- bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) );
794
- bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) );
925
+ break;
795
926
 
796
- }
927
+ case 'FILE':
797
928
 
798
- return object3d;
929
+ if ( lineIndex > 0 ) {
799
930
 
800
- }
931
+ // Start embedded text files parsing
932
+ parsingEmbeddedFiles = true;
933
+ currentEmbeddedFileName = lp.getRemainingString();
934
+ currentEmbeddedText = '';
801
935
 
802
- //
936
+ bfcCertified = false;
937
+ bfcCCW = true;
803
938
 
804
- class LDrawLoader extends Loader {
939
+ }
805
940
 
806
- constructor( manager ) {
941
+ break;
807
942
 
808
- super( manager );
943
+ case 'BFC':
809
944
 
810
- // Array of THREE.Material
811
- this.materials = [];
945
+ // Changes to the backface culling state
946
+ while ( ! lp.isAtTheEnd() ) {
812
947
 
813
- // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
814
- // This also allows to handle the embedded text files ("0 FILE" lines)
815
- this.cache = new LDrawFileCache( this );
948
+ const token = lp.getToken();
816
949
 
817
- // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
818
- this.fileMap = null;
950
+ switch ( token ) {
819
951
 
820
- this.rootParseScope = this.newParseScopeLevel();
952
+ case 'CERTIFY':
953
+ case 'NOCERTIFY':
821
954
 
822
- // Add default main triangle and line edge materials (used in pieces that can be coloured with a main color)
823
- this.setMaterials( [
824
- this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ),
825
- this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) )
826
- ] );
955
+ bfcCertified = token === 'CERTIFY';
956
+ bfcCCW = true;
827
957
 
828
- // If this flag is set to true, each subobject will be a Object.
829
- // If not (the default), only one object which contains all the merged primitives will be created.
830
- this.separateObjects = false;
958
+ break;
831
959
 
832
- // If this flag is set to true the vertex normals will be smoothed.
833
- this.smoothNormals = true;
960
+ case 'CW':
961
+ case 'CCW':
834
962
 
835
- // The path to load parts from the LDraw parts library from.
836
- this.partsLibraryPath = '';
963
+ bfcCCW = token === 'CCW';
837
964
 
838
- }
965
+ break;
839
966
 
840
- setPartsLibraryPath( path ) {
967
+ case 'INVERTNEXT':
841
968
 
842
- this.partsLibraryPath = path;
843
- return this;
969
+ bfcInverted = true;
844
970
 
845
- }
971
+ break;
846
972
 
847
- async preloadMaterials( url ) {
973
+ case 'CLIP':
974
+ case 'NOCLIP':
848
975
 
849
- const fileLoader = new FileLoader( this.manager );
850
- fileLoader.setPath( this.path );
851
- fileLoader.setRequestHeader( this.requestHeader );
852
- fileLoader.setWithCredentials( this.withCredentials );
976
+ bfcCull = token === 'CLIP';
853
977
 
854
- const text = await fileLoader.loadAsync( url );
855
- const colorLineRegex = /^0 !COLOUR/;
856
- const lines = text.split( /[\n\r]/g );
857
- const materials = [];
858
- for ( let i = 0, l = lines.length; i < l; i ++ ) {
978
+ break;
859
979
 
860
- const line = lines[ i ];
861
- if ( colorLineRegex.test( line ) ) {
980
+ default:
862
981
 
863
- const directive = line.replace( colorLineRegex, '' );
864
- const material = this.parseColourMetaDirective( new LineParser( directive ) );
865
- materials.push( material );
982
+ console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
866
983
 
867
- }
984
+ break;
868
985
 
869
- }
986
+ }
870
987
 
871
- this.setMaterials( materials );
988
+ }
872
989
 
873
- }
990
+ break;
874
991
 
875
- load( url, onLoad, onProgress, onError ) {
992
+ case 'STEP':
876
993
 
877
- if ( ! this.fileMap ) {
994
+ startingConstructionStep = true;
878
995
 
879
- this.fileMap = {};
996
+ break;
880
997
 
881
- }
998
+ default:
999
+ // Other meta directives are not implemented
1000
+ break;
882
1001
 
883
- const fileLoader = new FileLoader( this.manager );
884
- fileLoader.setPath( this.path );
885
- fileLoader.setRequestHeader( this.requestHeader );
886
- fileLoader.setWithCredentials( this.withCredentials );
887
- fileLoader.load( url, text => {
1002
+ }
888
1003
 
889
- this.processObject( text, null, url, this.rootParseScope )
890
- .then( function ( result ) {
1004
+ }
891
1005
 
892
- onLoad( result.groupObject );
1006
+ break;
893
1007
 
894
- } );
1008
+ // Line type 1: Sub-object file
1009
+ case '1':
895
1010
 
896
- }, onProgress, onError );
1011
+ colorCode = lp.getToken();
1012
+ material = getLocalMaterial( colorCode );
897
1013
 
898
- }
1014
+ const posX = parseFloat( lp.getToken() );
1015
+ const posY = parseFloat( lp.getToken() );
1016
+ const posZ = parseFloat( lp.getToken() );
1017
+ const m0 = parseFloat( lp.getToken() );
1018
+ const m1 = parseFloat( lp.getToken() );
1019
+ const m2 = parseFloat( lp.getToken() );
1020
+ const m3 = parseFloat( lp.getToken() );
1021
+ const m4 = parseFloat( lp.getToken() );
1022
+ const m5 = parseFloat( lp.getToken() );
1023
+ const m6 = parseFloat( lp.getToken() );
1024
+ const m7 = parseFloat( lp.getToken() );
1025
+ const m8 = parseFloat( lp.getToken() );
899
1026
 
900
- parse( text, path, onLoad ) {
1027
+ const matrix = new Matrix4().set(
1028
+ m0, m1, m2, posX,
1029
+ m3, m4, m5, posY,
1030
+ m6, m7, m8, posZ,
1031
+ 0, 0, 0, 1
1032
+ );
901
1033
 
902
- // Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
903
- this.processObject( text, null, path, this.rootParseScope )
904
- .then( function ( result ) {
1034
+ let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
905
1035
 
906
- onLoad( result.groupObject );
1036
+ if ( loader.fileMap[ fileName ] ) {
907
1037
 
908
- } );
1038
+ // Found the subobject path in the preloaded file path map
1039
+ fileName = loader.fileMap[ fileName ];
909
1040
 
910
- }
1041
+ } else {
911
1042
 
912
- setMaterials( materials ) {
1043
+ // Standardized subfolders
1044
+ if ( fileName.startsWith( 's/' ) ) {
913
1045
 
914
- // Clears parse scopes stack, adds new scope with material library
915
- this.rootParseScope = this.newParseScopeLevel( materials );
916
- this.rootParseScope.isFromParse = false;
1046
+ fileName = 'parts/' + fileName;
917
1047
 
918
- this.materials = materials;
1048
+ } else if ( fileName.startsWith( '48/' ) ) {
919
1049
 
920
- return this;
1050
+ fileName = 'p/' + fileName;
921
1051
 
922
- }
1052
+ }
923
1053
 
924
- setFileMap( fileMap ) {
1054
+ }
925
1055
 
926
- this.fileMap = fileMap;
1056
+ subobjects.push( {
1057
+ material: material,
1058
+ colorCode: colorCode,
1059
+ matrix: matrix,
1060
+ fileName: fileName,
1061
+ inverted: bfcInverted,
1062
+ startingConstructionStep: startingConstructionStep
1063
+ } );
927
1064
 
928
- return this;
1065
+ bfcInverted = false;
929
1066
 
930
- }
1067
+ break;
1068
+
1069
+ // Line type 2: Line segment
1070
+ case '2':
1071
+
1072
+ colorCode = lp.getToken();
1073
+ material = getLocalMaterial( colorCode );
1074
+ v0 = lp.getVector();
1075
+ v1 = lp.getVector();
1076
+
1077
+ segment = {
1078
+ material: material,
1079
+ colorCode: colorCode,
1080
+ vertices: [ v0, v1 ],
1081
+ };
1082
+
1083
+ lineSegments.push( segment );
1084
+
1085
+ break;
1086
+
1087
+ // Line type 5: Conditional Line segment
1088
+ case '5':
1089
+
1090
+ colorCode = lp.getToken();
1091
+ material = getLocalMaterial( colorCode );
1092
+ v0 = lp.getVector();
1093
+ v1 = lp.getVector();
1094
+ c0 = lp.getVector();
1095
+ c1 = lp.getVector();
1096
+
1097
+ segment = {
1098
+ material: material,
1099
+ colorCode: colorCode,
1100
+ vertices: [ v0, v1 ],
1101
+ controlPoints: [ c0, c1 ],
1102
+ };
1103
+
1104
+ conditionalSegments.push( segment );
1105
+
1106
+ break;
1107
+
1108
+ // Line type 3: Triangle
1109
+ case '3':
1110
+
1111
+ colorCode = lp.getToken();
1112
+ material = getLocalMaterial( colorCode );
1113
+ ccw = bfcCCW;
1114
+ doubleSided = ! bfcCertified || ! bfcCull;
1115
+
1116
+ if ( ccw === true ) {
1117
+
1118
+ v0 = lp.getVector();
1119
+ v1 = lp.getVector();
1120
+ v2 = lp.getVector();
1121
+
1122
+ } else {
1123
+
1124
+ v2 = lp.getVector();
1125
+ v1 = lp.getVector();
1126
+ v0 = lp.getVector();
1127
+
1128
+ }
1129
+
1130
+ faces.push( {
1131
+ material: material,
1132
+ colorCode: colorCode,
1133
+ faceNormal: null,
1134
+ vertices: [ v0, v1, v2 ],
1135
+ normals: [ null, null, null ],
1136
+ } );
1137
+ totalFaces ++;
1138
+
1139
+ if ( doubleSided === true ) {
1140
+
1141
+ faces.push( {
1142
+ material: material,
1143
+ colorCode: colorCode,
1144
+ faceNormal: null,
1145
+ vertices: [ v2, v1, v0 ],
1146
+ normals: [ null, null, null ],
1147
+ } );
1148
+ totalFaces ++;
1149
+
1150
+ }
1151
+
1152
+ break;
1153
+
1154
+ // Line type 4: Quadrilateral
1155
+ case '4':
931
1156
 
932
- newParseScopeLevel( materials = null, parentScope = null ) {
1157
+ colorCode = lp.getToken();
1158
+ material = getLocalMaterial( colorCode );
1159
+ ccw = bfcCCW;
1160
+ doubleSided = ! bfcCertified || ! bfcCull;
1161
+
1162
+ if ( ccw === true ) {
1163
+
1164
+ v0 = lp.getVector();
1165
+ v1 = lp.getVector();
1166
+ v2 = lp.getVector();
1167
+ v3 = lp.getVector();
1168
+
1169
+ } else {
933
1170
 
934
- // Adds a new scope level, assign materials to it and returns it
1171
+ v3 = lp.getVector();
1172
+ v2 = lp.getVector();
1173
+ v1 = lp.getVector();
1174
+ v0 = lp.getVector();
935
1175
 
936
- const matLib = {};
1176
+ }
1177
+
1178
+ // specifically place the triangle diagonal in the v0 and v1 slots so we can
1179
+ // account for the doubling of vertices later when smoothing normals.
1180
+ faces.push( {
1181
+ material: material,
1182
+ colorCode: colorCode,
1183
+ faceNormal: null,
1184
+ vertices: [ v0, v1, v2, v3 ],
1185
+ normals: [ null, null, null, null ],
1186
+ } );
1187
+ totalFaces += 2;
1188
+
1189
+ if ( doubleSided === true ) {
1190
+
1191
+ faces.push( {
1192
+ material: material,
1193
+ colorCode: colorCode,
1194
+ faceNormal: null,
1195
+ vertices: [ v3, v2, v1, v0 ],
1196
+ normals: [ null, null, null, null ],
1197
+ } );
1198
+ totalFaces += 2;
937
1199
 
938
- if ( materials ) {
1200
+ }
939
1201
 
940
- for ( let i = 0, n = materials.length; i < n; i ++ ) {
1202
+ break;
941
1203
 
942
- const material = materials[ i ];
943
- matLib[ material.userData.code ] = material;
1204
+ default:
1205
+ throw new Error( 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.' );
944
1206
 
945
1207
  }
946
1208
 
947
1209
  }
948
1210
 
949
- const newParseScope = {
950
-
951
- parentScope: parentScope,
952
- lib: matLib,
953
- url: null,
954
-
955
- // Subobjects
956
- subobjects: null,
957
- numSubobjects: 0,
958
- subobjectIndex: 0,
959
- inverted: false,
960
- category: null,
961
- keywords: null,
962
-
963
- // Current subobject
964
- currentFileName: null,
965
- mainColourCode: parentScope ? parentScope.mainColourCode : '16',
966
- mainEdgeColourCode: parentScope ? parentScope.mainEdgeColourCode : '24',
967
- currentMatrix: new Matrix4(),
968
- matrix: new Matrix4(),
969
- type: 'Model',
970
- groupObject: null,
971
-
972
- // If false, it is a root material scope previous to parse
973
- isFromParse: true,
974
-
975
- faces: [],
976
- lineSegments: [],
977
- conditionalSegments: [],
978
- totalFaces: 0,
979
-
980
- // If true, this object is the start of a construction step
981
- startingConstructionStep: false
1211
+ if ( parsingEmbeddedFiles ) {
1212
+
1213
+ this.setData( currentEmbeddedFileName, currentEmbeddedText );
1214
+
1215
+ }
1216
+
1217
+ return {
1218
+ faces,
1219
+ conditionalSegments,
1220
+ lineSegments,
1221
+ type,
1222
+ category,
1223
+ keywords,
1224
+ subobjects,
1225
+ totalFaces,
1226
+ startingConstructionStep,
1227
+ materials,
1228
+ fileName,
1229
+ group: null
982
1230
  };
983
1231
 
984
- return newParseScope;
1232
+ }
1233
+
1234
+ // returns an (optionally cloned) instance of the data
1235
+ getData( fileName, clone = true ) {
1236
+
1237
+ const key = fileName.toLowerCase();
1238
+ const result = this._cache[ key ];
1239
+ if ( result === null || result instanceof Promise ) {
1240
+
1241
+ return null;
1242
+
1243
+ }
1244
+
1245
+ if ( clone ) {
1246
+
1247
+ return this.cloneResult( result );
1248
+
1249
+ } else {
1250
+
1251
+ return result;
1252
+
1253
+ }
985
1254
 
986
1255
  }
987
1256
 
988
- addMaterial( material, parseScope ) {
1257
+ // kicks off a fetch and parse of the requested data if it hasn't already been loaded. Returns when
1258
+ // the data is ready to use and can be retrieved synchronously with "getData".
1259
+ async ensureDataLoaded( fileName ) {
989
1260
 
990
- // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
1261
+ const key = fileName.toLowerCase();
1262
+ if ( ! ( key in this._cache ) ) {
991
1263
 
992
- const matLib = parseScope.lib;
1264
+ // replace the promise with a copy of the parsed data for immediate processing
1265
+ this._cache[ key ] = this.fetchData( fileName ).then( text => {
993
1266
 
994
- if ( ! matLib[ material.userData.code ] ) {
1267
+ const info = this.parse( text, fileName );
1268
+ this._cache[ key ] = info;
1269
+ return info;
995
1270
 
996
- this.materials.push( material );
1271
+ } );
997
1272
 
998
1273
  }
999
1274
 
1000
- matLib[ material.userData.code ] = material;
1275
+ await this._cache[ key ];
1001
1276
 
1002
- return this;
1277
+ }
1278
+
1279
+ // sets the data in the cache from parsed data
1280
+ setData( fileName, text ) {
1281
+
1282
+ const key = fileName.toLowerCase();
1283
+ this._cache[ key ] = this.parse( text, fileName );
1003
1284
 
1004
1285
  }
1005
1286
 
1006
- getMaterial( colourCode, parseScope = this.rootParseScope ) {
1287
+ }
1007
1288
 
1008
- // Given a colour code search its material in the parse scopes stack
1289
+ // returns the material for an associated color code. If the color code is 16 for a face or 24 for
1290
+ // an edge then the passthroughColorCode is used.
1291
+ function getMaterialFromCode( colorCode, parentColorCode, materialHierarchy, forEdge ) {
1009
1292
 
1010
- if ( colourCode.startsWith( '0x2' ) ) {
1293
+ const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE;
1294
+ if ( isPassthrough ) {
1011
1295
 
1012
- // Special 'direct' material value (RGB colour)
1296
+ colorCode = parentColorCode;
1013
1297
 
1014
- const colour = colourCode.substring( 3 );
1298
+ }
1015
1299
 
1016
- return this.parseColourMetaDirective( new LineParser( 'Direct_Color_' + colour + ' CODE -1 VALUE #' + colour + ' EDGE #' + colour + '' ) );
1300
+ return materialHierarchy[ colorCode ] || null;
1017
1301
 
1018
- }
1302
+ }
1303
+
1304
+ // Class used to parse and build LDraw parts as three.js objects and cache them if they're a "Part" type.
1305
+ class LDrawPartsGeometryCache {
1306
+
1307
+ constructor( loader ) {
1308
+
1309
+ this.loader = loader;
1310
+ this.parseCache = new LDrawParsedCache( loader );
1311
+ this._cache = {};
1312
+
1313
+ }
1314
+
1315
+ // Convert the given file information into a mesh by processing subobjects.
1316
+ async processIntoMesh( info ) {
1317
+
1318
+ const loader = this.loader;
1319
+ const parseCache = this.parseCache;
1320
+ const faceMaterials = new Set();
1321
+
1322
+ // Processes the part subobject information to load child parts and merge geometry onto part
1323
+ // piece object.
1324
+ const processInfoSubobjects = async ( info, subobject = null ) => {
1325
+
1326
+ const subobjects = info.subobjects;
1327
+ const promises = [];
1328
+
1329
+ // Trigger load of all subobjects. If a subobject isn't a primitive then load it as a separate
1330
+ // group which lets instruction steps apply correctly.
1331
+ for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
1332
+
1333
+ const subobject = subobjects[ i ];
1334
+ const promise = parseCache.ensureDataLoaded( subobject.fileName ).then( () => {
1335
+
1336
+ const subobjectInfo = parseCache.getData( subobject.fileName, false );
1337
+ if ( ! isPrimitiveType( subobjectInfo.type ) ) {
1338
+
1339
+ return this.loadModel( subobject.fileName ).catch( error => {
1340
+
1341
+ console.warn( error );
1342
+ return null;
1343
+
1344
+ } );
1345
+
1346
+ }
1347
+
1348
+ return processInfoSubobjects( parseCache.getData( subobject.fileName ), subobject );
1349
+
1350
+ } );
1351
+
1352
+ promises.push( promise );
1019
1353
 
1020
- while ( parseScope ) {
1354
+ }
1021
1355
 
1022
- const material = parseScope.lib[ colourCode ];
1356
+ const group = new Group();
1357
+ group.userData.category = info.category;
1358
+ group.userData.keywords = info.keywords;
1359
+ info.group = group;
1023
1360
 
1024
- if ( material ) {
1361
+ const subobjectInfos = await Promise.all( promises );
1362
+ for ( let i = 0, l = subobjectInfos.length; i < l; i ++ ) {
1025
1363
 
1026
- return material;
1364
+ const subobject = info.subobjects[ i ];
1365
+ const subobjectInfo = subobjectInfos[ i ];
1027
1366
 
1028
- } else {
1367
+ if ( subobjectInfo === null ) {
1029
1368
 
1030
- parseScope = parseScope.parentScope;
1369
+ // the subobject failed to load
1370
+ continue;
1031
1371
 
1032
- }
1372
+ }
1033
1373
 
1034
- }
1374
+ // if the subobject was loaded as a separate group then apply the parent scopes materials
1375
+ if ( subobjectInfo.isGroup ) {
1035
1376
 
1036
- // Material was not found
1037
- return null;
1377
+ const subobjectGroup = subobjectInfo;
1378
+ subobject.matrix.decompose( subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale );
1379
+ subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep;
1380
+ subobjectGroup.name = subobject.fileName;
1038
1381
 
1039
- }
1382
+ loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
1040
1383
 
1041
- parseColourMetaDirective( lineParser ) {
1384
+ group.add( subobjectGroup );
1385
+ continue;
1042
1386
 
1043
- // Parses a colour definition and returns a THREE.Material
1387
+ }
1044
1388
 
1045
- let code = null;
1389
+ // add the subobject group if it has children in case it has both children and primitives
1390
+ if ( subobjectInfo.group.children.length ) {
1046
1391
 
1047
- // Triangle and line colours
1048
- let colour = 0xFF00FF;
1049
- let edgeColour = 0xFF00FF;
1392
+ group.add( subobjectInfo.group );
1050
1393
 
1051
- // Transparency
1052
- let alpha = 1;
1053
- let isTransparent = false;
1054
- // Self-illumination:
1055
- let luminance = 0;
1394
+ }
1056
1395
 
1057
- let finishType = FINISH_TYPE_DEFAULT;
1396
+ // transform the primitives into the local space of the parent piece and append them to
1397
+ // to the parent primitives list.
1398
+ const parentLineSegments = info.lineSegments;
1399
+ const parentConditionalSegments = info.conditionalSegments;
1400
+ const parentFaces = info.faces;
1058
1401
 
1059
- let edgeMaterial = null;
1402
+ const lineSegments = subobjectInfo.lineSegments;
1403
+ const conditionalSegments = subobjectInfo.conditionalSegments;
1060
1404
 
1061
- const name = lineParser.getToken();
1062
- if ( ! name ) {
1405
+ const faces = subobjectInfo.faces;
1406
+ const matrix = subobject.matrix;
1407
+ const inverted = subobject.inverted;
1408
+ const matrixScaleInverted = matrix.determinant() < 0;
1409
+ const colorCode = subobject.colorCode;
1063
1410
 
1064
- throw 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.';
1411
+ const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode;
1412
+ for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
1065
1413
 
1066
- }
1414
+ const ls = lineSegments[ i ];
1415
+ const vertices = ls.vertices;
1416
+ vertices[ 0 ].applyMatrix4( matrix );
1417
+ vertices[ 1 ].applyMatrix4( matrix );
1418
+ ls.colorCode = ls.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : ls.colorCode;
1419
+ ls.material = ls.material || getMaterialFromCode( ls.colorCode, ls.colorCode, info.materials, true );
1067
1420
 
1068
- // Parse tag tokens and their parameters
1069
- let token = null;
1070
- while ( true ) {
1421
+ parentLineSegments.push( ls );
1071
1422
 
1072
- token = lineParser.getToken();
1423
+ }
1073
1424
 
1074
- if ( ! token ) {
1425
+ for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
1075
1426
 
1076
- break;
1427
+ const os = conditionalSegments[ i ];
1428
+ const vertices = os.vertices;
1429
+ const controlPoints = os.controlPoints;
1430
+ vertices[ 0 ].applyMatrix4( matrix );
1431
+ vertices[ 1 ].applyMatrix4( matrix );
1432
+ controlPoints[ 0 ].applyMatrix4( matrix );
1433
+ controlPoints[ 1 ].applyMatrix4( matrix );
1434
+ os.colorCode = os.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : os.colorCode;
1435
+ os.material = os.material || getMaterialFromCode( os.colorCode, os.colorCode, info.materials, true );
1077
1436
 
1078
- }
1437
+ parentConditionalSegments.push( os );
1079
1438
 
1080
- switch ( token.toUpperCase() ) {
1439
+ }
1081
1440
 
1082
- case 'CODE':
1441
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
1083
1442
 
1084
- code = lineParser.getToken();
1085
- break;
1443
+ const tri = faces[ i ];
1444
+ const vertices = tri.vertices;
1445
+ for ( let i = 0, l = vertices.length; i < l; i ++ ) {
1086
1446
 
1087
- case 'VALUE':
1447
+ vertices[ i ].applyMatrix4( matrix );
1088
1448
 
1089
- colour = lineParser.getToken();
1090
- if ( colour.startsWith( '0x' ) ) {
1449
+ }
1091
1450
 
1092
- colour = '#' + colour.substring( 2 );
1451
+ tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode;
1452
+ tri.material = tri.material || getMaterialFromCode( tri.colorCode, colorCode, info.materials, false );
1453
+ faceMaterials.add( tri.colorCode );
1093
1454
 
1094
- } else if ( ! colour.startsWith( '#' ) ) {
1455
+ // If the scale of the object is negated then the triangle winding order
1456
+ // needs to be flipped.
1457
+ if ( matrixScaleInverted !== inverted ) {
1095
1458
 
1096
- throw 'LDrawLoader: Invalid colour while parsing material' + lineParser.getLineNumberString() + '.';
1459
+ vertices.reverse();
1097
1460
 
1098
1461
  }
1099
1462
 
1100
- break;
1101
-
1102
- case 'EDGE':
1463
+ parentFaces.push( tri );
1103
1464
 
1104
- edgeColour = lineParser.getToken();
1105
- if ( edgeColour.startsWith( '0x' ) ) {
1465
+ }
1106
1466
 
1107
- edgeColour = '#' + edgeColour.substring( 2 );
1467
+ info.totalFaces += subobjectInfo.totalFaces;
1108
1468
 
1109
- } else if ( ! edgeColour.startsWith( '#' ) ) {
1469
+ }
1110
1470
 
1111
- // Try to see if edge colour is a colour code
1112
- edgeMaterial = this.getMaterial( edgeColour );
1113
- if ( ! edgeMaterial ) {
1471
+ // Apply the parent subobjects pass through material code to this object. This is done several times due
1472
+ // to material scoping.
1473
+ if ( subobject ) {
1114
1474
 
1115
- throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + '.';
1475
+ loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials );
1116
1476
 
1117
- }
1477
+ }
1118
1478
 
1119
- // Get the edge material for this triangle material
1120
- edgeMaterial = edgeMaterial.userData.edgeMaterial;
1479
+ return info;
1121
1480
 
1122
- }
1481
+ };
1123
1482
 
1124
- break;
1483
+ // Track material use to see if we need to use the normal smooth slow path for hard edges.
1484
+ for ( let i = 0, l = info.faces; i < l; i ++ ) {
1125
1485
 
1126
- case 'ALPHA':
1486
+ faceMaterials.add( info.faces[ i ].colorCode );
1127
1487
 
1128
- alpha = parseInt( lineParser.getToken() );
1488
+ }
1129
1489
 
1130
- if ( isNaN( alpha ) ) {
1490
+ await processInfoSubobjects( info );
1131
1491
 
1132
- throw 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.';
1492
+ if ( loader.smoothNormals ) {
1133
1493
 
1134
- }
1494
+ const checkSubSegments = faceMaterials.size > 1;
1495
+ generateFaceNormals( info.faces );
1496
+ smoothNormals( info.faces, info.lineSegments, checkSubSegments );
1135
1497
 
1136
- alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
1498
+ }
1137
1499
 
1138
- if ( alpha < 1 ) {
1500
+ // Add the primitive objects and metadata.
1501
+ const group = info.group;
1502
+ if ( info.faces.length > 0 ) {
1139
1503
 
1140
- isTransparent = true;
1504
+ group.add( createObject( info.faces, 3, false, info.totalFaces ) );
1141
1505
 
1142
- }
1506
+ }
1143
1507
 
1144
- break;
1508
+ if ( info.lineSegments.length > 0 ) {
1145
1509
 
1146
- case 'LUMINANCE':
1510
+ group.add( createObject( info.lineSegments, 2 ) );
1147
1511
 
1148
- luminance = parseInt( lineParser.getToken() );
1512
+ }
1149
1513
 
1150
- if ( isNaN( luminance ) ) {
1514
+ if ( info.conditionalSegments.length > 0 ) {
1151
1515
 
1152
- throw 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.';
1516
+ group.add( createObject( info.conditionalSegments, 2, true ) );
1153
1517
 
1154
- }
1518
+ }
1155
1519
 
1156
- luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
1520
+ return group;
1157
1521
 
1158
- break;
1522
+ }
1159
1523
 
1160
- case 'CHROME':
1161
- finishType = FINISH_TYPE_CHROME;
1162
- break;
1524
+ hasCachedModel( fileName ) {
1163
1525
 
1164
- case 'PEARLESCENT':
1165
- finishType = FINISH_TYPE_PEARLESCENT;
1166
- break;
1526
+ return fileName !== null && fileName.toLowerCase() in this._cache;
1167
1527
 
1168
- case 'RUBBER':
1169
- finishType = FINISH_TYPE_RUBBER;
1170
- break;
1528
+ }
1171
1529
 
1172
- case 'MATTE_METALLIC':
1173
- finishType = FINISH_TYPE_MATTE_METALLIC;
1174
- break;
1530
+ async getCachedModel( fileName ) {
1175
1531
 
1176
- case 'METAL':
1177
- finishType = FINISH_TYPE_METAL;
1178
- break;
1532
+ if ( fileName !== null && this.hasCachedModel( fileName ) ) {
1179
1533
 
1180
- case 'MATERIAL':
1181
- // Not implemented
1182
- lineParser.setToEnd();
1183
- break;
1534
+ const key = fileName.toLowerCase();
1535
+ const group = await this._cache[ key ];
1536
+ return group.clone();
1184
1537
 
1185
- default:
1186
- throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.';
1187
- break;
1538
+ } else {
1188
1539
 
1189
- }
1540
+ return null;
1190
1541
 
1191
1542
  }
1192
1543
 
1193
- let material = null;
1194
-
1195
- switch ( finishType ) {
1544
+ }
1196
1545
 
1197
- case FINISH_TYPE_DEFAULT:
1546
+ // Loads and parses the model with the given file name. Returns a cached copy if available.
1547
+ async loadModel( fileName ) {
1198
1548
 
1199
- material = new MeshStandardMaterial( { color: colour, roughness: 0.3, metalness: 0 } );
1200
- break;
1549
+ const parseCache = this.parseCache;
1550
+ const key = fileName.toLowerCase();
1551
+ if ( this.hasCachedModel( fileName ) ) {
1201
1552
 
1202
- case FINISH_TYPE_PEARLESCENT:
1553
+ // Return cached model if available.
1554
+ return this.getCachedModel( fileName );
1203
1555
 
1204
- // Try to imitate pearlescency by making the surface glossy
1205
- material = new MeshStandardMaterial( { color: colour, roughness: 0.3, metalness: 0.25 } );
1206
- break;
1556
+ } else {
1207
1557
 
1208
- case FINISH_TYPE_CHROME:
1558
+ // Otherwise parse a new model.
1559
+ // Ensure the file data is loaded and pre parsed.
1560
+ await parseCache.ensureDataLoaded( fileName );
1209
1561
 
1210
- // Mirror finish surface
1211
- material = new MeshStandardMaterial( { color: colour, roughness: 0, metalness: 1 } );
1212
- break;
1562
+ const info = parseCache.getData( fileName );
1563
+ const promise = this.processIntoMesh( info );
1213
1564
 
1214
- case FINISH_TYPE_RUBBER:
1565
+ // Now that the file has loaded it's possible that another part parse has been waiting in parallel
1566
+ // so check the cache again to see if it's been added since the last async operation so we don't
1567
+ // do unnecessary work.
1568
+ if ( this.hasCachedModel( fileName ) ) {
1215
1569
 
1216
- // Rubber finish
1217
- material = new MeshStandardMaterial( { color: colour, roughness: 0.9, metalness: 0 } );
1218
- break;
1570
+ return this.getCachedModel( fileName );
1219
1571
 
1220
- case FINISH_TYPE_MATTE_METALLIC:
1572
+ }
1221
1573
 
1222
- // Brushed metal finish
1223
- material = new MeshStandardMaterial( { color: colour, roughness: 0.8, metalness: 0.4 } );
1224
- break;
1574
+ // Cache object if it's a part so it can be reused later.
1575
+ if ( isPartType( info.type ) ) {
1225
1576
 
1226
- case FINISH_TYPE_METAL:
1577
+ this._cache[ key ] = promise;
1227
1578
 
1228
- // Average metal finish
1229
- material = new MeshStandardMaterial( { color: colour, roughness: 0.2, metalness: 0.85 } );
1230
- break;
1579
+ }
1231
1580
 
1232
- default:
1233
- // Should not happen
1234
- break;
1581
+ // return a copy
1582
+ const group = await promise;
1583
+ return group.clone();
1235
1584
 
1236
1585
  }
1237
1586
 
1238
- material.transparent = isTransparent;
1239
- material.premultipliedAlpha = true;
1240
- material.opacity = alpha;
1241
- material.depthWrite = ! isTransparent;
1587
+ }
1242
1588
 
1243
- material.polygonOffset = true;
1244
- material.polygonOffsetFactor = 1;
1589
+ // parses the given model text into a renderable object. Returns cached copy if available.
1590
+ async parseModel( text ) {
1245
1591
 
1246
- if ( luminance !== 0 ) {
1592
+ const parseCache = this.parseCache;
1593
+ const info = parseCache.parse( text );
1594
+ if ( isPartType( info.type ) && this.hasCachedModel( info.fileName ) ) {
1247
1595
 
1248
- material.emissive.set( material.color ).multiplyScalar( luminance );
1596
+ return this.getCachedModel( info.fileName );
1249
1597
 
1250
1598
  }
1251
1599
 
1252
- if ( ! edgeMaterial ) {
1600
+ return this.processIntoMesh( info );
1253
1601
 
1254
- // This is the material used for edges
1255
- edgeMaterial = new LineBasicMaterial( {
1256
- color: edgeColour,
1257
- transparent: isTransparent,
1258
- opacity: alpha,
1259
- depthWrite: ! isTransparent
1260
- } );
1261
- edgeMaterial.userData.code = code;
1262
- edgeMaterial.name = name + ' - Edge';
1602
+ }
1263
1603
 
1264
- // This is the material used for conditional edges
1265
- edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( {
1604
+ }
1266
1605
 
1267
- fog: true,
1268
- transparent: isTransparent,
1269
- depthWrite: ! isTransparent,
1270
- color: edgeColour,
1271
- opacity: alpha,
1606
+ function sortByMaterial( a, b ) {
1272
1607
 
1273
- } );
1608
+ if ( a.colorCode === b.colorCode ) {
1274
1609
 
1275
- }
1610
+ return 0;
1276
1611
 
1277
- material.userData.code = code;
1278
- material.name = name;
1612
+ }
1279
1613
 
1280
- material.userData.edgeMaterial = edgeMaterial;
1614
+ if ( a.colorCode < b.colorCode ) {
1281
1615
 
1282
- return material;
1616
+ return - 1;
1283
1617
 
1284
1618
  }
1285
1619
 
1286
- //
1620
+ return 1;
1287
1621
 
1288
- objectParse( text, parseScope ) {
1622
+ }
1289
1623
 
1290
- // Retrieve data from the parent parse scope
1291
- const currentParseScope = parseScope;
1292
- const parentParseScope = currentParseScope.parentScope;
1624
+ function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) {
1293
1625
 
1294
- // Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
1295
- const mainColourCode = currentParseScope.mainColourCode;
1296
- const mainEdgeColourCode = currentParseScope.mainEdgeColourCode;
1626
+ // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 )
1627
+ // With per face / segment material, implemented with mesh groups and materials array
1297
1628
 
1629
+ // Sort the faces or line segments by color code to make later the mesh groups
1630
+ elements.sort( sortByMaterial );
1298
1631
 
1299
- // Parse result variables
1300
- let faces;
1301
- let lineSegments;
1302
- let conditionalSegments;
1632
+ if ( totalElements === null ) {
1303
1633
 
1304
- const subobjects = [];
1634
+ totalElements = elements.length;
1305
1635
 
1306
- let category = null;
1307
- let keywords = null;
1636
+ }
1308
1637
 
1309
- if ( text.indexOf( '\r\n' ) !== - 1 ) {
1638
+ const positions = new Float32Array( elementSize * totalElements * 3 );
1639
+ const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null;
1640
+ const materials = [];
1310
1641
 
1311
- // This is faster than String.split with regex that splits on both
1312
- text = text.replace( /\r\n/g, '\n' );
1642
+ const quadArray = new Array( 6 );
1643
+ const bufferGeometry = new BufferGeometry();
1644
+ let prevMaterial = null;
1645
+ let index0 = 0;
1646
+ let numGroupVerts = 0;
1647
+ let offset = 0;
1313
1648
 
1314
- }
1649
+ for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
1315
1650
 
1316
- const lines = text.split( '\n' );
1317
- const numLines = lines.length;
1651
+ const elem = elements[ iElem ];
1652
+ let vertices = elem.vertices;
1653
+ if ( vertices.length === 4 ) {
1318
1654
 
1319
- let parsingEmbeddedFiles = false;
1320
- let currentEmbeddedFileName = null;
1321
- let currentEmbeddedText = null;
1655
+ quadArray[ 0 ] = vertices[ 0 ];
1656
+ quadArray[ 1 ] = vertices[ 1 ];
1657
+ quadArray[ 2 ] = vertices[ 2 ];
1658
+ quadArray[ 3 ] = vertices[ 0 ];
1659
+ quadArray[ 4 ] = vertices[ 2 ];
1660
+ quadArray[ 5 ] = vertices[ 3 ];
1661
+ vertices = quadArray;
1322
1662
 
1323
- let bfcCertified = false;
1324
- let bfcCCW = true;
1325
- let bfcInverted = false;
1326
- let bfcCull = true;
1327
- let type = '';
1663
+ }
1328
1664
 
1329
- let startingConstructionStep = false;
1665
+ for ( let j = 0, l = vertices.length; j < l; j ++ ) {
1330
1666
 
1331
- const scope = this;
1332
- function parseColourCode( lineParser, forEdge ) {
1667
+ const v = vertices[ j ];
1668
+ const index = offset + j * 3;
1669
+ positions[ index + 0 ] = v.x;
1670
+ positions[ index + 1 ] = v.y;
1671
+ positions[ index + 2 ] = v.z;
1333
1672
 
1334
- // Parses next colour code and returns a THREE.Material
1673
+ }
1335
1674
 
1336
- let colourCode = lineParser.getToken();
1675
+ // create the normals array if this is a set of faces
1676
+ if ( elementSize === 3 ) {
1337
1677
 
1338
- if ( ! forEdge && colourCode === '16' ) {
1678
+ if ( ! elem.faceNormal ) {
1339
1679
 
1340
- colourCode = mainColourCode;
1680
+ const v0 = vertices[ 0 ];
1681
+ const v1 = vertices[ 1 ];
1682
+ const v2 = vertices[ 2 ];
1683
+ _tempVec0.subVectors( v1, v0 );
1684
+ _tempVec1.subVectors( v2, v1 );
1685
+ elem.faceNormal = new Vector3()
1686
+ .crossVectors( _tempVec0, _tempVec1 )
1687
+ .normalize();
1341
1688
 
1342
1689
  }
1343
1690
 
1344
- if ( forEdge && colourCode === '24' ) {
1691
+ let elemNormals = elem.normals;
1692
+ if ( elemNormals.length === 4 ) {
1345
1693
 
1346
- colourCode = mainEdgeColourCode;
1694
+ quadArray[ 0 ] = elemNormals[ 0 ];
1695
+ quadArray[ 1 ] = elemNormals[ 1 ];
1696
+ quadArray[ 2 ] = elemNormals[ 2 ];
1697
+ quadArray[ 3 ] = elemNormals[ 0 ];
1698
+ quadArray[ 4 ] = elemNormals[ 2 ];
1699
+ quadArray[ 5 ] = elemNormals[ 3 ];
1700
+ elemNormals = quadArray;
1347
1701
 
1348
1702
  }
1349
1703
 
1350
- const material = scope.getMaterial( colourCode, currentParseScope );
1704
+ for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
1351
1705
 
1352
- if ( ! material ) {
1706
+ // use face normal if a vertex normal is not provided
1707
+ let n = elem.faceNormal;
1708
+ if ( elemNormals[ j ] ) {
1353
1709
 
1354
- throw 'LDrawLoader: Unknown colour code "' + colourCode + '" is used' + lineParser.getLineNumberString() + ' but it was not defined previously.';
1710
+ n = elemNormals[ j ].norm;
1355
1711
 
1356
- }
1712
+ }
1357
1713
 
1358
- return material;
1714
+ const index = offset + j * 3;
1715
+ normals[ index + 0 ] = n.x;
1716
+ normals[ index + 1 ] = n.y;
1717
+ normals[ index + 2 ] = n.z;
1359
1718
 
1360
- }
1719
+ }
1361
1720
 
1362
- function parseVector( lp ) {
1721
+ }
1363
1722
 
1364
- const v = new Vector3( parseFloat( lp.getToken() ), parseFloat( lp.getToken() ), parseFloat( lp.getToken() ) );
1723
+ if ( prevMaterial !== elem.colorCode ) {
1365
1724
 
1366
- if ( ! scope.separateObjects ) {
1725
+ if ( prevMaterial !== null ) {
1367
1726
 
1368
- v.applyMatrix4( currentParseScope.currentMatrix );
1727
+ bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
1369
1728
 
1370
1729
  }
1371
1730
 
1372
- return v;
1373
-
1374
- }
1375
-
1376
- // Parse all line commands
1377
- for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
1731
+ const material = elem.material;
1378
1732
 
1379
- const line = lines[ lineIndex ];
1733
+ if ( material !== null ) {
1380
1734
 
1381
- if ( line.length === 0 ) continue;
1735
+ if ( elementSize === 3 ) {
1382
1736
 
1383
- if ( parsingEmbeddedFiles ) {
1737
+ materials.push( material );
1384
1738
 
1385
- if ( line.startsWith( '0 FILE ' ) ) {
1739
+ } else if ( elementSize === 2 ) {
1386
1740
 
1387
- // Save previous embedded file in the cache
1388
- this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
1741
+ if ( isConditionalSegments ) {
1389
1742
 
1390
- // New embedded text file
1391
- currentEmbeddedFileName = line.substring( 7 );
1392
- currentEmbeddedText = '';
1743
+ materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial );
1393
1744
 
1394
- } else {
1745
+ } else {
1395
1746
 
1396
- currentEmbeddedText += line + '\n';
1747
+ materials.push( material.userData.edgeMaterial );
1748
+
1749
+ }
1397
1750
 
1398
1751
  }
1399
1752
 
1400
- continue;
1753
+ } else {
1754
+
1755
+ // If a material has not been made available yet then keep the color code string in the material array
1756
+ // to save the spot for the material once a parent scopes materials are being applied to the object.
1757
+ materials.push( elem.colorCode );
1401
1758
 
1402
1759
  }
1403
1760
 
1404
- const lp = new LineParser( line, lineIndex + 1 );
1761
+ prevMaterial = elem.colorCode;
1762
+ index0 = offset / 3;
1763
+ numGroupVerts = vertices.length;
1405
1764
 
1406
- lp.seekNonSpace();
1765
+ } else {
1407
1766
 
1408
- if ( lp.isAtTheEnd() ) {
1767
+ numGroupVerts += vertices.length;
1409
1768
 
1410
- // Empty line
1411
- continue;
1769
+ }
1412
1770
 
1413
- }
1771
+ offset += 3 * vertices.length;
1414
1772
 
1415
- // Parse the line type
1416
- const lineType = lp.getToken();
1773
+ }
1417
1774
 
1418
- let material;
1419
- let segment;
1420
- let inverted;
1421
- let ccw;
1422
- let doubleSided;
1423
- let v0, v1, v2, v3, c0, c1, faceNormal;
1775
+ if ( numGroupVerts > 0 ) {
1424
1776
 
1425
- switch ( lineType ) {
1777
+ bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
1426
1778
 
1427
- // Line type 0: Comment or META
1428
- case '0':
1779
+ }
1429
1780
 
1430
- // Parse meta directive
1431
- const meta = lp.getToken();
1781
+ bufferGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
1432
1782
 
1433
- if ( meta ) {
1783
+ if ( normals !== null ) {
1434
1784
 
1435
- switch ( meta ) {
1785
+ bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
1436
1786
 
1437
- case '!LDRAW_ORG':
1787
+ }
1438
1788
 
1439
- type = lp.getToken();
1789
+ let object3d = null;
1440
1790
 
1441
- currentParseScope.type = type;
1791
+ if ( elementSize === 2 ) {
1442
1792
 
1443
- // If the scale of the object is negated then the triangle winding order
1444
- // needs to be flipped.
1445
- if (
1446
- currentParseScope.matrix.determinant() < 0 && (
1447
- scope.separateObjects && isPrimitiveType( type ) ||
1448
- ! scope.separateObjects
1449
- ) ) {
1793
+ if ( isConditionalSegments ) {
1450
1794
 
1451
- currentParseScope.inverted = ! currentParseScope.inverted;
1795
+ object3d = new ConditionalLineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
1452
1796
 
1453
- }
1797
+ } else {
1454
1798
 
1455
- faces = currentParseScope.faces;
1456
- lineSegments = currentParseScope.lineSegments;
1457
- conditionalSegments = currentParseScope.conditionalSegments;
1799
+ object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
1458
1800
 
1459
- break;
1801
+ }
1460
1802
 
1461
- case '!COLOUR':
1803
+ } else if ( elementSize === 3 ) {
1462
1804
 
1463
- material = this.parseColourMetaDirective( lp );
1464
- if ( material ) {
1805
+ object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
1465
1806
 
1466
- this.addMaterial( material, parseScope );
1807
+ }
1467
1808
 
1468
- } else {
1809
+ if ( isConditionalSegments ) {
1469
1810
 
1470
- console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
1811
+ object3d.isConditionalLine = true;
1471
1812
 
1472
- }
1813
+ const controlArray0 = new Float32Array( elements.length * 3 * 2 );
1814
+ const controlArray1 = new Float32Array( elements.length * 3 * 2 );
1815
+ const directionArray = new Float32Array( elements.length * 3 * 2 );
1816
+ for ( let i = 0, l = elements.length; i < l; i ++ ) {
1473
1817
 
1474
- break;
1818
+ const os = elements[ i ];
1819
+ const vertices = os.vertices;
1820
+ const controlPoints = os.controlPoints;
1821
+ const c0 = controlPoints[ 0 ];
1822
+ const c1 = controlPoints[ 1 ];
1823
+ const v0 = vertices[ 0 ];
1824
+ const v1 = vertices[ 1 ];
1825
+ const index = i * 3 * 2;
1826
+ controlArray0[ index + 0 ] = c0.x;
1827
+ controlArray0[ index + 1 ] = c0.y;
1828
+ controlArray0[ index + 2 ] = c0.z;
1829
+ controlArray0[ index + 3 ] = c0.x;
1830
+ controlArray0[ index + 4 ] = c0.y;
1831
+ controlArray0[ index + 5 ] = c0.z;
1475
1832
 
1476
- case '!CATEGORY':
1833
+ controlArray1[ index + 0 ] = c1.x;
1834
+ controlArray1[ index + 1 ] = c1.y;
1835
+ controlArray1[ index + 2 ] = c1.z;
1836
+ controlArray1[ index + 3 ] = c1.x;
1837
+ controlArray1[ index + 4 ] = c1.y;
1838
+ controlArray1[ index + 5 ] = c1.z;
1477
1839
 
1478
- category = lp.getToken();
1479
- break;
1840
+ directionArray[ index + 0 ] = v1.x - v0.x;
1841
+ directionArray[ index + 1 ] = v1.y - v0.y;
1842
+ directionArray[ index + 2 ] = v1.z - v0.z;
1843
+ directionArray[ index + 3 ] = v1.x - v0.x;
1844
+ directionArray[ index + 4 ] = v1.y - v0.y;
1845
+ directionArray[ index + 5 ] = v1.z - v0.z;
1480
1846
 
1481
- case '!KEYWORDS':
1847
+ }
1482
1848
 
1483
- const newKeywords = lp.getRemainingString().split( ',' );
1484
- if ( newKeywords.length > 0 ) {
1849
+ bufferGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) );
1850
+ bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) );
1851
+ bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) );
1485
1852
 
1486
- if ( ! keywords ) {
1853
+ }
1487
1854
 
1488
- keywords = [];
1855
+ return object3d;
1489
1856
 
1490
- }
1857
+ }
1491
1858
 
1492
- newKeywords.forEach( function ( keyword ) {
1859
+ //
1493
1860
 
1494
- keywords.push( keyword.trim() );
1861
+ class LDrawLoader extends Loader {
1495
1862
 
1496
- } );
1863
+ constructor( manager ) {
1497
1864
 
1498
- }
1865
+ super( manager );
1499
1866
 
1500
- break;
1867
+ // Array of THREE.Material
1868
+ this.materials = [];
1869
+ this.materialLibrary = {};
1501
1870
 
1502
- case 'FILE':
1871
+ // This also allows to handle the embedded text files ("0 FILE" lines)
1872
+ this.partsCache = new LDrawPartsGeometryCache( this );
1503
1873
 
1504
- if ( lineIndex > 0 ) {
1874
+ // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
1875
+ this.fileMap = {};
1505
1876
 
1506
- // Start embedded text files parsing
1507
- parsingEmbeddedFiles = true;
1508
- currentEmbeddedFileName = lp.getRemainingString();
1509
- currentEmbeddedText = '';
1877
+ // Initializes the materials library with default materials
1878
+ this.setMaterials( [] );
1510
1879
 
1511
- bfcCertified = false;
1512
- bfcCCW = true;
1880
+ // If this flag is set to true the vertex normals will be smoothed.
1881
+ this.smoothNormals = true;
1513
1882
 
1514
- }
1883
+ // The path to load parts from the LDraw parts library from.
1884
+ this.partsLibraryPath = '';
1515
1885
 
1516
- break;
1886
+ }
1517
1887
 
1518
- case 'BFC':
1888
+ setPartsLibraryPath( path ) {
1519
1889
 
1520
- // Changes to the backface culling state
1521
- while ( ! lp.isAtTheEnd() ) {
1890
+ this.partsLibraryPath = path;
1891
+ return this;
1522
1892
 
1523
- const token = lp.getToken();
1893
+ }
1524
1894
 
1525
- switch ( token ) {
1895
+ async preloadMaterials( url ) {
1526
1896
 
1527
- case 'CERTIFY':
1528
- case 'NOCERTIFY':
1897
+ const fileLoader = new FileLoader( this.manager );
1898
+ fileLoader.setPath( this.path );
1899
+ fileLoader.setRequestHeader( this.requestHeader );
1900
+ fileLoader.setWithCredentials( this.withCredentials );
1529
1901
 
1530
- bfcCertified = token === 'CERTIFY';
1531
- bfcCCW = true;
1902
+ const text = await fileLoader.loadAsync( url );
1903
+ const colorLineRegex = /^0 !COLOUR/;
1904
+ const lines = text.split( /[\n\r]/g );
1905
+ const materials = [];
1906
+ for ( let i = 0, l = lines.length; i < l; i ++ ) {
1532
1907
 
1533
- break;
1908
+ const line = lines[ i ];
1909
+ if ( colorLineRegex.test( line ) ) {
1534
1910
 
1535
- case 'CW':
1536
- case 'CCW':
1911
+ const directive = line.replace( colorLineRegex, '' );
1912
+ const material = this.parseColorMetaDirective( new LineParser( directive ) );
1913
+ materials.push( material );
1537
1914
 
1538
- bfcCCW = token === 'CCW';
1915
+ }
1539
1916
 
1540
- break;
1917
+ }
1541
1918
 
1542
- case 'INVERTNEXT':
1919
+ this.setMaterials( materials );
1543
1920
 
1544
- bfcInverted = true;
1921
+ }
1545
1922
 
1546
- break;
1923
+ load( url, onLoad, onProgress, onError ) {
1547
1924
 
1548
- case 'CLIP':
1549
- case 'NOCLIP':
1925
+ const fileLoader = new FileLoader( this.manager );
1926
+ fileLoader.setPath( this.path );
1927
+ fileLoader.setRequestHeader( this.requestHeader );
1928
+ fileLoader.setWithCredentials( this.withCredentials );
1929
+ fileLoader.load( url, text => {
1550
1930
 
1551
- bfcCull = token === 'CLIP';
1931
+ this.partsCache
1932
+ .parseModel( text, this.materialLibrary )
1933
+ .then( group => {
1552
1934
 
1553
- break;
1935
+ this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
1936
+ this.computeConstructionSteps( group );
1937
+ onLoad( group );
1554
1938
 
1555
- default:
1939
+ } )
1940
+ .catch( onError );
1556
1941
 
1557
- console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
1942
+ }, onProgress, onError );
1558
1943
 
1559
- break;
1944
+ }
1560
1945
 
1561
- }
1946
+ parse( text, onLoad ) {
1562
1947
 
1563
- }
1948
+ this.partsCache
1949
+ .parseModel( text, this.materialLibrary )
1950
+ .then( group => {
1564
1951
 
1565
- break;
1952
+ this.computeConstructionSteps( group );
1953
+ onLoad( group );
1566
1954
 
1567
- case 'STEP':
1955
+ } );
1568
1956
 
1569
- startingConstructionStep = true;
1957
+ }
1570
1958
 
1571
- break;
1959
+ setMaterials( materials ) {
1572
1960
 
1573
- default:
1574
- // Other meta directives are not implemented
1575
- break;
1961
+ this.materialLibrary = {};
1962
+ this.materials = [];
1963
+ for ( let i = 0, l = materials.length; i < l; i ++ ) {
1576
1964
 
1577
- }
1965
+ this.addMaterial( materials[ i ] );
1578
1966
 
1579
- }
1967
+ }
1580
1968
 
1581
- break;
1969
+ // Add default main triangle and line edge materials (used in pieces that can be colored with a main color)
1970
+ this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ) );
1971
+ this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) );
1582
1972
 
1583
- // Line type 1: Sub-object file
1584
- case '1':
1973
+ return this;
1585
1974
 
1586
- material = parseColourCode( lp );
1975
+ }
1587
1976
 
1588
- const posX = parseFloat( lp.getToken() );
1589
- const posY = parseFloat( lp.getToken() );
1590
- const posZ = parseFloat( lp.getToken() );
1591
- const m0 = parseFloat( lp.getToken() );
1592
- const m1 = parseFloat( lp.getToken() );
1593
- const m2 = parseFloat( lp.getToken() );
1594
- const m3 = parseFloat( lp.getToken() );
1595
- const m4 = parseFloat( lp.getToken() );
1596
- const m5 = parseFloat( lp.getToken() );
1597
- const m6 = parseFloat( lp.getToken() );
1598
- const m7 = parseFloat( lp.getToken() );
1599
- const m8 = parseFloat( lp.getToken() );
1977
+ setFileMap( fileMap ) {
1600
1978
 
1601
- const matrix = new Matrix4().set(
1602
- m0, m1, m2, posX,
1603
- m3, m4, m5, posY,
1604
- m6, m7, m8, posZ,
1605
- 0, 0, 0, 1
1606
- );
1979
+ this.fileMap = fileMap;
1607
1980
 
1608
- let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
1981
+ return this;
1609
1982
 
1610
- if ( scope.fileMap[ fileName ] ) {
1983
+ }
1611
1984
 
1612
- // Found the subobject path in the preloaded file path map
1613
- fileName = scope.fileMap[ fileName ];
1985
+ addMaterial( material ) {
1614
1986
 
1615
- } else {
1987
+ // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
1616
1988
 
1617
- // Standardized subfolders
1618
- if ( fileName.startsWith( 's/' ) ) {
1989
+ const matLib = this.materialLibrary;
1990
+ if ( ! matLib[ material.userData.code ] ) {
1619
1991
 
1620
- fileName = 'parts/' + fileName;
1992
+ this.materials.push( material );
1993
+ matLib[ material.userData.code ] = material;
1621
1994
 
1622
- } else if ( fileName.startsWith( '48/' ) ) {
1995
+ }
1623
1996
 
1624
- fileName = 'p/' + fileName;
1997
+ return this;
1625
1998
 
1626
- }
1999
+ }
1627
2000
 
1628
- }
2001
+ getMaterial( colorCode ) {
1629
2002
 
1630
- subobjects.push( {
1631
- material: material,
1632
- matrix: matrix,
1633
- fileName: fileName,
1634
- inverted: bfcInverted !== currentParseScope.inverted,
1635
- startingConstructionStep: startingConstructionStep
1636
- } );
2003
+ if ( colorCode.startsWith( '0x2' ) ) {
1637
2004
 
1638
- bfcInverted = false;
2005
+ // Special 'direct' material value (RGB color)
2006
+ const color = colorCode.substring( 3 );
1639
2007
 
1640
- break;
2008
+ return this.parseColorMetaDirective( new LineParser( 'Direct_Color_' + color + ' CODE -1 VALUE #' + color + ' EDGE #' + color + '' ) );
1641
2009
 
1642
- // Line type 2: Line segment
1643
- case '2':
2010
+ }
1644
2011
 
1645
- material = parseColourCode( lp, true );
1646
- v0 = parseVector( lp );
1647
- v1 = parseVector( lp );
2012
+ return this.materialLibrary[ colorCode ] || null;
1648
2013
 
1649
- segment = {
1650
- material: material.userData.edgeMaterial,
1651
- colourCode: material.userData.code,
1652
- v0: v0,
1653
- v1: v1,
2014
+ }
1654
2015
 
1655
- vertices: [ v0, v1 ],
1656
- };
2016
+ // Applies the appropriate materials to a prebuilt hierarchy of geometry. Assumes that color codes are present
2017
+ // in the material array if they need to be filled in.
2018
+ applyMaterialsToMesh( group, parentColorCode, materialHierarchy, finalMaterialPass = false ) {
1657
2019
 
1658
- lineSegments.push( segment );
2020
+ // find any missing materials as indicated by a color code string and replace it with a material from the current material lib
2021
+ const loader = this;
2022
+ const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE;
2023
+ group.traverse( c => {
1659
2024
 
1660
- break;
2025
+ if ( c.isMesh || c.isLineSegments ) {
1661
2026
 
1662
- // Line type 5: Conditional Line segment
1663
- case '5':
2027
+ if ( Array.isArray( c.material ) ) {
1664
2028
 
1665
- material = parseColourCode( lp, true );
1666
- v0 = parseVector( lp );
1667
- v1 = parseVector( lp );
1668
- c0 = parseVector( lp );
1669
- c1 = parseVector( lp );
2029
+ for ( let i = 0, l = c.material.length; i < l; i ++ ) {
1670
2030
 
1671
- segment = {
1672
- material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
1673
- colourCode: material.userData.code,
1674
- vertices: [ v0, v1 ],
1675
- controlPoints: [ c0, c1 ],
1676
- };
2031
+ if ( ! c.material[ i ].isMaterial ) {
1677
2032
 
1678
- conditionalSegments.push( segment );
2033
+ c.material[ i ] = getMaterial( c, c.material[ i ] );
1679
2034
 
1680
- break;
2035
+ }
1681
2036
 
1682
- // Line type 3: Triangle
1683
- case '3':
2037
+ }
1684
2038
 
1685
- material = parseColourCode( lp );
2039
+ } else if ( ! c.material.isMaterial ) {
1686
2040
 
1687
- inverted = currentParseScope.inverted;
1688
- ccw = bfcCCW !== inverted;
1689
- doubleSided = ! bfcCertified || ! bfcCull;
2041
+ c.material = getMaterial( c, c.material );
1690
2042
 
1691
- if ( ccw === true ) {
2043
+ }
1692
2044
 
1693
- v0 = parseVector( lp );
1694
- v1 = parseVector( lp );
1695
- v2 = parseVector( lp );
2045
+ }
1696
2046
 
1697
- } else {
2047
+ } );
1698
2048
 
1699
- v2 = parseVector( lp );
1700
- v1 = parseVector( lp );
1701
- v0 = parseVector( lp );
1702
2049
 
1703
- }
2050
+ // Returns the appropriate material for the object (line or face) given color code. If the code is "pass through"
2051
+ // (24 for lines, 16 for edges) then the pass through color code is used. If that is also pass through then it's
2052
+ // simply returned for the subsequent material application.
2053
+ function getMaterial( c, colorCode ) {
1704
2054
 
1705
- _tempVec0.subVectors( v1, v0 );
1706
- _tempVec1.subVectors( v2, v1 );
1707
- faceNormal = new Vector3()
1708
- .crossVectors( _tempVec0, _tempVec1 )
1709
- .normalize();
2055
+ // if our parent is a passthrough color code and we don't have the current material color available then
2056
+ // return early.
2057
+ if ( parentIsPassthrough && ! ( colorCode in materialHierarchy ) && ! finalMaterialPass ) {
1710
2058
 
1711
- faces.push( {
1712
- material: material,
1713
- colourCode: material.userData.code,
1714
- faceNormal: faceNormal,
1715
- vertices: [ v0, v1, v2 ],
1716
- normals: [ null, null, null ],
1717
- } );
1718
- currentParseScope.totalFaces ++;
2059
+ return colorCode;
1719
2060
 
1720
- if ( doubleSided === true ) {
2061
+ }
1721
2062
 
1722
- faces.push( {
1723
- material: material,
1724
- colourCode: material.userData.code,
1725
- faceNormal: faceNormal,
1726
- vertices: [ v2, v1, v0 ],
1727
- normals: [ null, null, null ],
1728
- } );
1729
- currentParseScope.totalFaces ++;
2063
+ const forEdge = c.isLineSegments || c.isConditionalLine;
2064
+ const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE;
2065
+ if ( isPassthrough ) {
1730
2066
 
1731
- }
2067
+ colorCode = parentColorCode;
1732
2068
 
1733
- break;
2069
+ }
1734
2070
 
1735
- // Line type 4: Quadrilateral
1736
- case '4':
2071
+ let material = null;
2072
+ if ( colorCode in materialHierarchy ) {
1737
2073
 
1738
- material = parseColourCode( lp );
2074
+ material = materialHierarchy[ colorCode ];
1739
2075
 
1740
- inverted = currentParseScope.inverted;
1741
- ccw = bfcCCW !== inverted;
1742
- doubleSided = ! bfcCertified || ! bfcCull;
2076
+ } else if ( finalMaterialPass ) {
1743
2077
 
1744
- if ( ccw === true ) {
2078
+ // see if we can get the final material from from the "getMaterial" function which will attempt to
2079
+ // parse the "direct" colors
2080
+ material = loader.getMaterial( colorCode );
2081
+ if ( material === null ) {
1745
2082
 
1746
- v0 = parseVector( lp );
1747
- v1 = parseVector( lp );
1748
- v2 = parseVector( lp );
1749
- v3 = parseVector( lp );
2083
+ // otherwise throw an error if this is final opportunity to set the material
2084
+ throw new Error( `LDrawLoader: Material properties for code ${ colorCode } not available.` );
1750
2085
 
1751
- } else {
2086
+ }
1752
2087
 
1753
- v3 = parseVector( lp );
1754
- v2 = parseVector( lp );
1755
- v1 = parseVector( lp );
1756
- v0 = parseVector( lp );
1757
2088
 
1758
- }
2089
+ } else {
1759
2090
 
1760
- _tempVec0.subVectors( v1, v0 );
1761
- _tempVec1.subVectors( v2, v1 );
1762
- faceNormal = new Vector3()
1763
- .crossVectors( _tempVec0, _tempVec1 )
1764
- .normalize();
2091
+ return colorCode;
1765
2092
 
1766
- // specifically place the triangle diagonal in the v0 and v1 slots so we can
1767
- // account for the doubling of vertices later when smoothing normals.
1768
- faces.push( {
1769
- material: material,
1770
- colourCode: material.userData.code,
1771
- faceNormal: faceNormal,
1772
- vertices: [ v0, v1, v2, v3 ],
1773
- normals: [ null, null, null, null ],
1774
- } );
1775
- currentParseScope.totalFaces += 2;
2093
+ }
1776
2094
 
1777
- if ( doubleSided === true ) {
2095
+ if ( c.isLineSegments ) {
1778
2096
 
1779
- faces.push( {
1780
- material: material,
1781
- colourCode: material.userData.code,
1782
- faceNormal: faceNormal,
1783
- vertices: [ v3, v2, v1, v0 ],
1784
- normals: [ null, null, null, null ],
1785
- } );
1786
- currentParseScope.totalFaces += 2;
2097
+ material = material.userData.edgeMaterial;
1787
2098
 
1788
- }
2099
+ if ( c.isConditionalLine ) {
1789
2100
 
1790
- break;
2101
+ material = material.userData.conditionalEdgeMaterial;
1791
2102
 
1792
- default:
1793
- throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
1794
- break;
2103
+ }
1795
2104
 
1796
2105
  }
1797
2106
 
1798
- }
2107
+ return material;
1799
2108
 
1800
- if ( parsingEmbeddedFiles ) {
2109
+ }
1801
2110
 
1802
- this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
2111
+ }
1803
2112
 
1804
- }
2113
+ getMainMaterial() {
1805
2114
 
1806
- currentParseScope.category = category;
1807
- currentParseScope.keywords = keywords;
1808
- currentParseScope.subobjects = subobjects;
1809
- currentParseScope.numSubobjects = subobjects.length;
1810
- currentParseScope.subobjectIndex = 0;
2115
+ return this.getMaterial( MAIN_COLOUR_CODE );
1811
2116
 
1812
- const isRoot = ! parentParseScope.isFromParse;
1813
- if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
2117
+ }
1814
2118
 
1815
- currentParseScope.groupObject = new Group();
1816
- currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
2119
+ getMainEdgeMaterial() {
1817
2120
 
1818
- }
2121
+ const mainMat = this.getMainMaterial();
2122
+ return mainMat && mainMat.userData ? mainMat.userData.edgeMaterial : null;
1819
2123
 
1820
2124
  }
1821
2125
 
1822
- computeConstructionSteps( model ) {
2126
+ parseColorMetaDirective( lineParser ) {
1823
2127
 
1824
- // Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object.
2128
+ // Parses a color definition and returns a THREE.Material
1825
2129
 
1826
- let stepNumber = 0;
2130
+ let code = null;
1827
2131
 
1828
- model.traverse( c => {
2132
+ // Triangle and line colors
2133
+ let color = 0xFF00FF;
2134
+ let edgeColor = 0xFF00FF;
1829
2135
 
1830
- if ( c.isGroup ) {
2136
+ // Transparency
2137
+ let alpha = 1;
2138
+ let isTransparent = false;
2139
+ // Self-illumination:
2140
+ let luminance = 0;
1831
2141
 
1832
- if ( c.userData.startingConstructionStep ) {
2142
+ let finishType = FINISH_TYPE_DEFAULT;
1833
2143
 
1834
- stepNumber ++;
2144
+ let edgeMaterial = null;
1835
2145
 
1836
- }
2146
+ const name = lineParser.getToken();
2147
+ if ( ! name ) {
1837
2148
 
1838
- c.userData.constructionStep = stepNumber;
2149
+ throw new Error( 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.' );
2150
+
2151
+ }
2152
+
2153
+ // Parse tag tokens and their parameters
2154
+ let token = null;
2155
+ while ( true ) {
2156
+
2157
+ token = lineParser.getToken();
2158
+
2159
+ if ( ! token ) {
2160
+
2161
+ break;
1839
2162
 
1840
2163
  }
1841
2164
 
1842
- } );
2165
+ switch ( token.toUpperCase() ) {
1843
2166
 
1844
- model.userData.numConstructionSteps = stepNumber + 1;
2167
+ case 'CODE':
1845
2168
 
1846
- }
2169
+ code = lineParser.getToken();
2170
+ break;
1847
2171
 
1848
- finalizeObject( subobjectParseScope ) {
2172
+ case 'VALUE':
1849
2173
 
1850
- // fail gracefully if an object could not be loaded
1851
- if ( subobjectParseScope === null ) {
2174
+ color = lineParser.getToken();
2175
+ if ( color.startsWith( '0x' ) ) {
1852
2176
 
1853
- return;
2177
+ color = '#' + color.substring( 2 );
1854
2178
 
1855
- }
2179
+ } else if ( ! color.startsWith( '#' ) ) {
1856
2180
 
1857
- const parentParseScope = subobjectParseScope.parentScope;
2181
+ throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
1858
2182
 
1859
- // Smooth the normals if this is a part or if this is a case where the subpart
1860
- // is added directly into the parent model (meaning it will never get smoothed by
1861
- // being added to a part)
1862
- const doSmooth =
1863
- isPartType( subobjectParseScope.type ) ||
1864
- (
1865
- ! isPartType( subobjectParseScope.type ) &&
1866
- ! isModelType( subobjectParseScope.type ) &&
1867
- isModelType( subobjectParseScope.parentScope.type )
1868
- );
2183
+ }
1869
2184
 
1870
- if ( this.smoothNormals && doSmooth ) {
2185
+ break;
1871
2186
 
1872
- smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments );
2187
+ case 'EDGE':
1873
2188
 
1874
- }
2189
+ edgeColor = lineParser.getToken();
2190
+ if ( edgeColor.startsWith( '0x' ) ) {
1875
2191
 
1876
- const isRoot = ! parentParseScope.isFromParse;
1877
- if ( this.separateObjects && ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
2192
+ edgeColor = '#' + edgeColor.substring( 2 );
1878
2193
 
1879
- const objGroup = subobjectParseScope.groupObject;
2194
+ } else if ( ! edgeColor.startsWith( '#' ) ) {
1880
2195
 
1881
- if ( subobjectParseScope.faces.length > 0 ) {
2196
+ // Try to see if edge color is a color code
2197
+ edgeMaterial = this.getMaterial( edgeColor );
2198
+ if ( ! edgeMaterial ) {
1882
2199
 
1883
- objGroup.add( createObject( subobjectParseScope.faces, 3, false, subobjectParseScope.totalFaces ) );
2200
+ throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
1884
2201
 
1885
- }
2202
+ }
1886
2203
 
1887
- if ( subobjectParseScope.lineSegments.length > 0 ) {
2204
+ // Get the edge material for this triangle material
2205
+ edgeMaterial = edgeMaterial.userData.edgeMaterial;
1888
2206
 
1889
- objGroup.add( createObject( subobjectParseScope.lineSegments, 2 ) );
2207
+ }
1890
2208
 
1891
- }
2209
+ break;
1892
2210
 
1893
- if ( subobjectParseScope.conditionalSegments.length > 0 ) {
2211
+ case 'ALPHA':
1894
2212
 
1895
- objGroup.add( createObject( subobjectParseScope.conditionalSegments, 2, true ) );
2213
+ alpha = parseInt( lineParser.getToken() );
1896
2214
 
1897
- }
2215
+ if ( isNaN( alpha ) ) {
1898
2216
 
1899
- if ( parentParseScope.groupObject ) {
2217
+ throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
1900
2218
 
1901
- objGroup.name = subobjectParseScope.fileName;
1902
- objGroup.userData.category = subobjectParseScope.category;
1903
- objGroup.userData.keywords = subobjectParseScope.keywords;
1904
- subobjectParseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
2219
+ }
1905
2220
 
1906
- parentParseScope.groupObject.add( objGroup );
2221
+ alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
1907
2222
 
1908
- }
2223
+ if ( alpha < 1 ) {
1909
2224
 
1910
- } else {
2225
+ isTransparent = true;
1911
2226
 
1912
- const separateObjects = this.separateObjects;
1913
- const parentLineSegments = parentParseScope.lineSegments;
1914
- const parentConditionalSegments = parentParseScope.conditionalSegments;
1915
- const parentFaces = parentParseScope.faces;
2227
+ }
1916
2228
 
1917
- const lineSegments = subobjectParseScope.lineSegments;
1918
- const conditionalSegments = subobjectParseScope.conditionalSegments;
1919
- const faces = subobjectParseScope.faces;
2229
+ break;
1920
2230
 
1921
- for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
2231
+ case 'LUMINANCE':
1922
2232
 
1923
- const ls = lineSegments[ i ];
2233
+ luminance = parseInt( lineParser.getToken() );
1924
2234
 
1925
- if ( separateObjects ) {
2235
+ if ( isNaN( luminance ) ) {
1926
2236
 
1927
- const vertices = ls.vertices;
1928
- vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1929
- vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
2237
+ throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
1930
2238
 
1931
- }
2239
+ }
1932
2240
 
1933
- parentLineSegments.push( ls );
2241
+ luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
1934
2242
 
1935
- }
2243
+ break;
1936
2244
 
1937
- for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
2245
+ case 'CHROME':
2246
+ finishType = FINISH_TYPE_CHROME;
2247
+ break;
1938
2248
 
1939
- const os = conditionalSegments[ i ];
2249
+ case 'PEARLESCENT':
2250
+ finishType = FINISH_TYPE_PEARLESCENT;
2251
+ break;
1940
2252
 
1941
- if ( separateObjects ) {
2253
+ case 'RUBBER':
2254
+ finishType = FINISH_TYPE_RUBBER;
2255
+ break;
1942
2256
 
1943
- const vertices = os.vertices;
1944
- const controlPoints = os.controlPoints;
1945
- vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1946
- vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1947
- controlPoints[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1948
- controlPoints[ 1 ].applyMatrix4( subobjectParseScope.matrix );
2257
+ case 'MATTE_METALLIC':
2258
+ finishType = FINISH_TYPE_MATTE_METALLIC;
2259
+ break;
1949
2260
 
1950
- }
2261
+ case 'METAL':
2262
+ finishType = FINISH_TYPE_METAL;
2263
+ break;
2264
+
2265
+ case 'MATERIAL':
2266
+ // Not implemented
2267
+ lineParser.setToEnd();
2268
+ break;
1951
2269
 
1952
- parentConditionalSegments.push( os );
2270
+ default:
2271
+ throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
1953
2272
 
1954
2273
  }
1955
2274
 
1956
- for ( let i = 0, l = faces.length; i < l; i ++ ) {
2275
+ }
2276
+
2277
+ let material = null;
1957
2278
 
1958
- const tri = faces[ i ];
2279
+ switch ( finishType ) {
1959
2280
 
1960
- if ( separateObjects ) {
2281
+ case FINISH_TYPE_DEFAULT:
1961
2282
 
1962
- const vertices = tri.vertices;
1963
- for ( let i = 0, l = vertices.length; i < l; i ++ ) {
2283
+ material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0 } );
2284
+ break;
1964
2285
 
1965
- vertices[ i ] = vertices[ i ].clone().applyMatrix4( subobjectParseScope.matrix );
2286
+ case FINISH_TYPE_PEARLESCENT:
1966
2287
 
1967
- }
2288
+ // Try to imitate pearlescency by making the surface glossy
2289
+ material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0.25 } );
2290
+ break;
1968
2291
 
1969
- _tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
1970
- _tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
1971
- tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
2292
+ case FINISH_TYPE_CHROME:
1972
2293
 
1973
- }
2294
+ // Mirror finish surface
2295
+ material = new MeshStandardMaterial( { color: color, roughness: 0, metalness: 1 } );
2296
+ break;
1974
2297
 
1975
- parentFaces.push( tri );
2298
+ case FINISH_TYPE_RUBBER:
1976
2299
 
1977
- }
2300
+ // Rubber finish
2301
+ material = new MeshStandardMaterial( { color: color, roughness: 0.9, metalness: 0 } );
2302
+ break;
1978
2303
 
1979
- parentParseScope.totalFaces += subobjectParseScope.totalFaces;
2304
+ case FINISH_TYPE_MATTE_METALLIC:
1980
2305
 
1981
- }
2306
+ // Brushed metal finish
2307
+ material = new MeshStandardMaterial( { color: color, roughness: 0.8, metalness: 0.4 } );
2308
+ break;
1982
2309
 
1983
- }
2310
+ case FINISH_TYPE_METAL:
2311
+
2312
+ // Average metal finish
2313
+ material = new MeshStandardMaterial( { color: color, roughness: 0.2, metalness: 0.85 } );
2314
+ break;
1984
2315
 
1985
- async processObject( text, subobject, url, parentScope ) {
2316
+ default:
2317
+ // Should not happen
2318
+ break;
1986
2319
 
1987
- const scope = this;
2320
+ }
1988
2321
 
1989
- const parseScope = this.newParseScopeLevel( null, parentScope );
1990
- parseScope.url = url;
2322
+ material.transparent = isTransparent;
2323
+ material.premultipliedAlpha = true;
2324
+ material.opacity = alpha;
2325
+ material.depthWrite = ! isTransparent;
2326
+ material.color.convertSRGBToLinear();
1991
2327
 
1992
- const parentParseScope = parseScope.parentScope;
2328
+ material.polygonOffset = true;
2329
+ material.polygonOffsetFactor = 1;
1993
2330
 
1994
- // Set current matrix
1995
- if ( subobject ) {
2331
+ if ( luminance !== 0 ) {
1996
2332
 
1997
- parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
1998
- parseScope.matrix.copy( subobject.matrix );
1999
- parseScope.inverted = subobject.inverted;
2000
- parseScope.startingConstructionStep = subobject.startingConstructionStep;
2001
- parseScope.mainColourCode = subobject.material.userData.code;
2002
- parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
2003
- parseScope.fileName = subobject.fileName;
2333
+ material.emissive.set( material.color ).multiplyScalar( luminance );
2004
2334
 
2005
2335
  }
2006
2336
 
2007
- // Parse the object
2008
- this.objectParse( text, parseScope );
2337
+ if ( ! edgeMaterial ) {
2338
+
2339
+ // This is the material used for edges
2340
+ edgeMaterial = new LineBasicMaterial( {
2341
+ color: edgeColor,
2342
+ transparent: isTransparent,
2343
+ opacity: alpha,
2344
+ depthWrite: ! isTransparent
2345
+ } );
2346
+ edgeMaterial.userData.code = code;
2347
+ edgeMaterial.name = name + ' - Edge';
2348
+ edgeMaterial.color.convertSRGBToLinear();
2349
+
2350
+ // This is the material used for conditional edges
2351
+ edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( {
2009
2352
 
2010
- const subobjects = parseScope.subobjects;
2011
- const promises = [];
2012
- for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
2353
+ fog: true,
2354
+ transparent: isTransparent,
2355
+ depthWrite: ! isTransparent,
2356
+ color: edgeColor,
2357
+ opacity: alpha,
2013
2358
 
2014
- promises.push( loadSubobject( parseScope.subobjects[ i ] ) );
2359
+ } );
2360
+ edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear();
2015
2361
 
2016
2362
  }
2017
2363
 
2018
- // Kick off of the downloads in parallel but process all the subobjects
2019
- // in order so all the assembly instructions are correct
2020
- const subobjectScopes = await Promise.all( promises );
2021
- for ( let i = 0, l = subobjectScopes.length; i < l; i ++ ) {
2364
+ material.userData.code = code;
2365
+ material.name = name;
2022
2366
 
2023
- this.finalizeObject( subobjectScopes[ i ] );
2367
+ material.userData.edgeMaterial = edgeMaterial;
2024
2368
 
2025
- }
2369
+ this.addMaterial( material );
2026
2370
 
2027
- // If it is root object then finalize this object and compute construction steps
2028
- if ( ! parentParseScope.isFromParse ) {
2371
+ return material;
2029
2372
 
2030
- this.finalizeObject( parseScope );
2031
- this.computeConstructionSteps( parseScope.groupObject );
2373
+ }
2032
2374
 
2033
- }
2375
+ computeConstructionSteps( model ) {
2034
2376
 
2035
- return parseScope;
2377
+ // Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object.
2036
2378
 
2037
- function loadSubobject( subobject ) {
2379
+ let stepNumber = 0;
2038
2380
 
2039
- return scope.cache.loadData( subobject.fileName ).then( function ( text ) {
2381
+ model.traverse( c => {
2040
2382
 
2041
- return scope.processObject( text, subobject, url, parseScope );
2383
+ if ( c.isGroup ) {
2042
2384
 
2043
- } ).catch( function () {
2385
+ if ( c.userData.startingConstructionStep ) {
2044
2386
 
2045
- console.warn( 'LDrawLoader: Subobject "' + subobject.fileName + '" could not be found.' );
2046
- return null;
2387
+ stepNumber ++;
2047
2388
 
2048
- } );
2389
+ }
2049
2390
 
2050
- }
2391
+ c.userData.constructionStep = stepNumber;
2392
+
2393
+ }
2394
+
2395
+ } );
2396
+
2397
+ model.userData.numConstructionSteps = stepNumber + 1;
2051
2398
 
2052
2399
  }
2053
2400