super-three 0.133.4 → 0.136.1

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 (409) hide show
  1. package/LICENSE +1 -1
  2. package/build/three.js +2285 -1983
  3. package/build/three.min.js +1 -1
  4. package/build/three.module.js +5983 -5358
  5. package/examples/fonts/open-sans/open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff +0 -0
  6. package/examples/fonts/open-sans/open-sans-v15-cyrillic-ext_greek_greek-ext_cyrillic_latin_latin-ext_vietnamese-regular.woff2 +0 -0
  7. package/examples/fonts/open-sans/open-sans.css +9 -0
  8. package/examples/fonts/tabler-icons/fonts/tabler-icons.eot +0 -0
  9. package/examples/fonts/tabler-icons/fonts/tabler-icons.svg +3966 -0
  10. package/examples/fonts/tabler-icons/fonts/tabler-icons.ttf +0 -0
  11. package/examples/fonts/tabler-icons/fonts/tabler-icons.woff +0 -0
  12. package/examples/fonts/tabler-icons/fonts/tabler-icons.woff2 +0 -0
  13. package/examples/fonts/tabler-icons/tabler-icons.min.css +4 -0
  14. package/examples/js/animation/CCDIKSolver.js +0 -1
  15. package/examples/js/animation/MMDPhysics.js +2 -4
  16. package/examples/js/cameras/CinematicCamera.js +2 -7
  17. package/examples/js/controls/ArcballControls.js +99 -72
  18. package/examples/js/controls/DragControls.js +7 -0
  19. package/examples/js/controls/OrbitControls.js +5 -36
  20. package/examples/js/controls/TrackballControls.js +3 -2
  21. package/examples/js/controls/TransformControls.js +24 -1
  22. package/examples/js/csm/CSM.js +2 -2
  23. package/examples/js/csm/CSMFrustum.js +133 -0
  24. package/examples/js/curves/CurveExtras.js +0 -2
  25. package/examples/js/effects/AsciiEffect.js +2 -9
  26. package/examples/js/exporters/GLTFExporter.js +148 -64
  27. package/examples/js/exporters/MMDExporter.js +1 -1
  28. package/examples/js/exporters/USDZExporter.js +45 -26
  29. package/examples/js/geometries/ConvexGeometry.js +1 -1
  30. package/examples/js/geometries/TeapotGeometry.js +0 -1
  31. package/examples/js/interactive/SelectionBox.js +2 -0
  32. package/examples/js/interactive/SelectionHelper.js +22 -7
  33. package/examples/js/lights/RectAreaLightUniformsLib.js +4 -0
  34. package/examples/js/lines/LineMaterial.js +58 -20
  35. package/examples/js/lines/LineSegments2.js +29 -24
  36. package/examples/js/loaders/3DMLoader.js +24 -11
  37. package/examples/js/loaders/3MFLoader.js +2 -2
  38. package/examples/js/loaders/AMFLoader.js +0 -2
  39. package/examples/js/loaders/BasisTextureLoader.js +1 -0
  40. package/examples/js/loaders/ColladaLoader.js +61 -4
  41. package/examples/js/loaders/EXRLoader.js +201 -272
  42. package/examples/js/loaders/FBXLoader.js +4 -5
  43. package/examples/js/loaders/GLTFLoader.js +98 -35
  44. package/examples/js/loaders/HDRCubeTextureLoader.js +0 -10
  45. package/examples/js/loaders/KTX2Loader.js +23 -2
  46. package/examples/js/loaders/KTXLoader.js +1 -1
  47. package/examples/js/loaders/LDrawLoader.js +1169 -901
  48. package/examples/js/loaders/LUT3dlLoader.js +19 -10
  49. package/examples/js/loaders/LUTCubeLoader.js +5 -4
  50. package/examples/js/loaders/LWOLoader.js +8 -6
  51. package/examples/js/loaders/LogLuvLoader.js +766 -0
  52. package/examples/js/loaders/MMDLoader.js +0 -1
  53. package/examples/js/loaders/NRRDLoader.js +9 -2
  54. package/examples/js/loaders/OBJLoader.js +0 -2
  55. package/examples/js/loaders/PLYLoader.js +33 -8
  56. package/examples/js/loaders/RGBELoader.js +8 -22
  57. package/examples/js/loaders/RGBMLoader.js +56 -6
  58. package/examples/js/loaders/SVGLoader.js +12 -6
  59. package/examples/js/loaders/TDSLoader.js +237 -271
  60. package/examples/js/loaders/VOXLoader.js +1 -0
  61. package/examples/js/loaders/VRMLLoader.js +11 -22
  62. package/examples/js/loaders/lwo/LWO2Parser.js +1 -0
  63. package/examples/js/loaders/lwo/LWO3Parser.js +4 -2
  64. package/examples/js/math/ConvexHull.js +7 -13
  65. package/examples/js/math/ImprovedNoise.js +1 -1
  66. package/examples/js/misc/GPUComputationRenderer.js +3 -1
  67. package/examples/js/misc/Volume.js +2 -2
  68. package/examples/js/modifiers/CurveModifier.js +8 -7
  69. package/examples/js/modifiers/EdgeSplitModifier.js +1 -1
  70. package/examples/js/modifiers/SimplifyModifier.js +1 -1
  71. package/examples/js/objects/Lensflare.js +2 -10
  72. package/examples/js/objects/MarchingCubes.js +42 -128
  73. package/examples/js/objects/Reflector.js +8 -13
  74. package/examples/js/objects/ReflectorForSSRPass.js +0 -10
  75. package/examples/js/objects/Refractor.js +8 -13
  76. package/examples/js/objects/Sky.js +1 -1
  77. package/examples/js/objects/Water.js +3 -15
  78. package/examples/js/objects/Water2.js +4 -7
  79. package/examples/js/postprocessing/GlitchPass.js +6 -5
  80. package/examples/js/postprocessing/SMAAPass.js +2 -9
  81. package/examples/js/postprocessing/SSAOPass.js +10 -21
  82. package/examples/js/postprocessing/SavePass.js +1 -5
  83. package/examples/js/renderers/CSS2DRenderer.js +4 -4
  84. package/examples/js/renderers/CSS3DRenderer.js +4 -4
  85. package/examples/js/shaders/ConvolutionShader.js +0 -1
  86. package/examples/js/shaders/DigitalGlitch.js +3 -3
  87. package/examples/js/shaders/FXAAShader.js +1 -1
  88. package/examples/js/shaders/FilmShader.js +1 -1
  89. package/examples/js/shaders/GammaCorrectionShader.js +1 -1
  90. package/examples/js/shaders/HorizontalBlurShader.js +1 -2
  91. package/examples/js/shaders/MMDToonShader.js +0 -1
  92. package/examples/js/shaders/SSAOShader.js +1 -1
  93. package/examples/js/shaders/SSRShader.js +1 -1
  94. package/examples/js/shaders/SSRrShader.js +1 -1
  95. package/examples/js/shaders/VerticalBlurShader.js +1 -2
  96. package/examples/js/utils/GeometryUtils.js +1 -1
  97. package/examples/jsm/animation/CCDIKSolver.js +0 -1
  98. package/examples/jsm/animation/MMDPhysics.js +1 -3
  99. package/examples/jsm/cameras/CinematicCamera.js +2 -5
  100. package/examples/jsm/{WebGL.js → capabilities/WebGL.js} +2 -2
  101. package/examples/jsm/{renderers/webgpu → capabilities}/WebGPU.js +1 -1
  102. package/examples/jsm/controls/ArcballControls.js +80 -77
  103. package/examples/jsm/controls/DragControls.js +7 -0
  104. package/examples/jsm/controls/OrbitControls.js +9 -51
  105. package/examples/jsm/controls/TrackballControls.js +1 -1
  106. package/examples/jsm/controls/TransformControls.js +24 -1
  107. package/examples/jsm/csm/CSM.js +3 -3
  108. package/examples/jsm/csm/{Frustum.js → CSMFrustum.js} +3 -3
  109. package/examples/jsm/curves/CurveExtras.js +0 -2
  110. package/examples/jsm/effects/AsciiEffect.js +2 -9
  111. package/examples/jsm/exporters/GLTFExporter.js +169 -99
  112. package/examples/jsm/exporters/MMDExporter.js +1 -1
  113. package/examples/jsm/exporters/USDZExporter.js +47 -26
  114. package/examples/jsm/geometries/ConvexGeometry.js +1 -1
  115. package/examples/jsm/geometries/TeapotGeometry.js +0 -1
  116. package/examples/jsm/interactive/SelectionBox.js +1 -0
  117. package/examples/jsm/interactive/SelectionHelper.js +19 -7
  118. package/examples/jsm/libs/flow.module.js +3338 -0
  119. package/examples/jsm/libs/lil-gui.module.min.js +8 -0
  120. package/examples/jsm/libs/rhino3dm/rhino3dm.wasm +0 -0
  121. package/examples/jsm/lights/RectAreaLightUniformsLib.js +7 -0
  122. package/examples/jsm/lines/LineMaterial.js +59 -23
  123. package/examples/jsm/lines/LineSegments2.js +22 -18
  124. package/examples/jsm/loaders/3DMLoader.js +18 -12
  125. package/examples/jsm/loaders/3MFLoader.js +2 -2
  126. package/examples/jsm/loaders/AMFLoader.js +0 -2
  127. package/examples/jsm/loaders/BasisTextureLoader.js +7 -0
  128. package/examples/jsm/loaders/ColladaLoader.js +61 -4
  129. package/examples/jsm/loaders/EXRLoader.js +200 -306
  130. package/examples/jsm/loaders/FBXLoader.js +4 -5
  131. package/examples/jsm/loaders/GLTFLoader.js +102 -40
  132. package/examples/jsm/loaders/HDRCubeTextureLoader.js +1 -17
  133. package/examples/jsm/loaders/KTX2Loader.js +30 -2
  134. package/examples/jsm/loaders/KTXLoader.js +1 -1
  135. package/examples/jsm/loaders/LDrawLoader.js +1159 -898
  136. package/examples/jsm/loaders/LUT3dlLoader.js +19 -12
  137. package/examples/jsm/loaders/LUTCubeLoader.js +5 -5
  138. package/examples/jsm/loaders/LWOLoader.js +8 -6
  139. package/examples/jsm/loaders/LogLuvLoader.js +606 -0
  140. package/examples/jsm/loaders/MMDLoader.js +0 -1
  141. package/examples/jsm/loaders/NRRDLoader.js +10 -2
  142. package/examples/jsm/loaders/OBJLoader.js +0 -2
  143. package/examples/jsm/loaders/PLYLoader.js +34 -8
  144. package/examples/jsm/loaders/RGBELoader.js +7 -28
  145. package/examples/jsm/loaders/RGBMLoader.js +62 -8
  146. package/examples/jsm/loaders/SVGLoader.js +15 -7
  147. package/examples/jsm/loaders/TDSLoader.js +236 -275
  148. package/examples/jsm/loaders/VOXLoader.js +1 -0
  149. package/examples/jsm/loaders/VRMLLoader.js +11 -23
  150. package/examples/jsm/loaders/lwo/LWO2Parser.js +1 -0
  151. package/examples/jsm/loaders/lwo/LWO3Parser.js +2 -2
  152. package/examples/jsm/math/ConvexHull.js +7 -13
  153. package/examples/jsm/math/ImprovedNoise.js +1 -1
  154. package/examples/jsm/misc/GPUComputationRenderer.js +3 -1
  155. package/examples/jsm/misc/Volume.js +2 -2
  156. package/examples/jsm/modifiers/CurveModifier.js +9 -8
  157. package/examples/jsm/modifiers/EdgeSplitModifier.js +1 -1
  158. package/examples/jsm/modifiers/SimplifyModifier.js +1 -1
  159. package/examples/jsm/node-editor/NodeEditor.js +455 -0
  160. package/examples/jsm/node-editor/accessors/NormalEditor.js +30 -0
  161. package/examples/jsm/node-editor/accessors/PositionEditor.js +30 -0
  162. package/examples/jsm/node-editor/accessors/UVEditor.js +26 -0
  163. package/examples/jsm/node-editor/display/BlendEditor.js +43 -0
  164. package/examples/jsm/node-editor/examples/animate-uv.json +1 -0
  165. package/examples/jsm/node-editor/examples/fake-top-light.json +1 -0
  166. package/examples/jsm/node-editor/examples/oscillator-color.json +1 -0
  167. package/examples/jsm/node-editor/examples/rim.json +1 -0
  168. package/examples/jsm/node-editor/inputs/ColorEditor.js +91 -0
  169. package/examples/jsm/node-editor/inputs/FloatEditor.js +24 -0
  170. package/examples/jsm/node-editor/inputs/SliderEditor.js +68 -0
  171. package/examples/jsm/node-editor/inputs/Vector2Editor.js +28 -0
  172. package/examples/jsm/node-editor/inputs/Vector3Editor.js +30 -0
  173. package/examples/jsm/node-editor/inputs/Vector4Editor.js +37 -0
  174. package/examples/jsm/node-editor/materials/StandardMaterialEditor.js +108 -0
  175. package/examples/jsm/node-editor/math/DotEditor.js +36 -0
  176. package/examples/jsm/node-editor/math/InvertEditor.js +38 -0
  177. package/examples/jsm/node-editor/math/LimiterEditor.js +48 -0
  178. package/examples/jsm/node-editor/math/NormalizeEditor.js +26 -0
  179. package/examples/jsm/node-editor/math/OperatorEditor.js +50 -0
  180. package/examples/jsm/node-editor/math/PowerEditor.js +34 -0
  181. package/examples/jsm/node-editor/math/TrigonometryEditor.js +39 -0
  182. package/examples/jsm/node-editor/procedural/CheckerEditor.js +26 -0
  183. package/examples/jsm/node-editor/utils/OscillatorEditor.js +42 -0
  184. package/examples/jsm/node-editor/utils/TimerEditor.js +57 -0
  185. package/examples/jsm/nodes/core/NodeBuilder.js +2 -2
  186. package/examples/jsm/nodes/materials/StandardNodeMaterial.js +1 -1
  187. package/examples/jsm/nodes/materials/nodes/StandardNode.js +7 -6
  188. package/examples/jsm/nodes/math/MathNode.js +12 -0
  189. package/examples/jsm/nodes/misc/BumpMapNode.js +1 -1
  190. package/examples/jsm/nodes/misc/TextureCubeUVNode.js +1 -4
  191. package/examples/jsm/nodes/utils/ColorSpaceNode.js +5 -177
  192. package/examples/jsm/objects/Lensflare.js +5 -16
  193. package/examples/jsm/objects/MarchingCubes.js +46 -152
  194. package/examples/jsm/objects/Reflector.js +8 -16
  195. package/examples/jsm/objects/ReflectorForSSRPass.js +0 -12
  196. package/examples/jsm/objects/Refractor.js +8 -16
  197. package/examples/jsm/objects/Sky.js +1 -1
  198. package/examples/jsm/objects/Water.js +3 -18
  199. package/examples/jsm/objects/Water2.js +4 -8
  200. package/examples/jsm/postprocessing/AdaptiveToneMappingPass.js +1 -1
  201. package/examples/jsm/postprocessing/AfterimagePass.js +1 -1
  202. package/examples/jsm/postprocessing/BloomPass.js +1 -1
  203. package/examples/jsm/postprocessing/BokehPass.js +1 -1
  204. package/examples/jsm/postprocessing/ClearPass.js +1 -1
  205. package/examples/jsm/postprocessing/CubeTexturePass.js +1 -1
  206. package/examples/jsm/postprocessing/DotScreenPass.js +1 -1
  207. package/examples/jsm/postprocessing/EffectComposer.js +3 -3
  208. package/examples/jsm/postprocessing/FilmPass.js +1 -1
  209. package/examples/jsm/postprocessing/GlitchPass.js +10 -7
  210. package/examples/jsm/postprocessing/HalftonePass.js +1 -1
  211. package/examples/jsm/postprocessing/MaskPass.js +1 -1
  212. package/examples/jsm/postprocessing/OutlinePass.js +1 -1
  213. package/examples/jsm/postprocessing/RenderPass.js +1 -1
  214. package/examples/jsm/postprocessing/SAOPass.js +2 -2
  215. package/examples/jsm/postprocessing/SMAAPass.js +3 -12
  216. package/examples/jsm/postprocessing/SSAARenderPass.js +1 -1
  217. package/examples/jsm/postprocessing/SSAOPass.js +15 -26
  218. package/examples/jsm/postprocessing/SSRPass.js +1 -1
  219. package/examples/jsm/postprocessing/SSRrPass.js +1 -1
  220. package/examples/jsm/postprocessing/SavePass.js +2 -4
  221. package/examples/jsm/postprocessing/ShaderPass.js +1 -1
  222. package/examples/jsm/postprocessing/TAARenderPass.js +1 -1
  223. package/examples/jsm/postprocessing/TexturePass.js +1 -1
  224. package/examples/jsm/postprocessing/UnrealBloomPass.js +1 -1
  225. package/examples/jsm/renderers/CSS2DRenderer.js +5 -4
  226. package/examples/jsm/renderers/CSS3DRenderer.js +5 -4
  227. package/examples/jsm/renderers/nodes/Nodes.js +17 -13
  228. package/examples/jsm/renderers/nodes/ShaderNode.js +229 -49
  229. package/examples/jsm/renderers/nodes/accessors/MaterialNode.js +6 -6
  230. package/examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js +5 -5
  231. package/examples/jsm/renderers/nodes/accessors/NormalNode.js +14 -3
  232. package/examples/jsm/renderers/nodes/accessors/PointUVNode.js +3 -3
  233. package/examples/jsm/renderers/nodes/accessors/PositionNode.js +15 -5
  234. package/examples/jsm/renderers/nodes/accessors/SkinningNode.js +107 -0
  235. package/examples/jsm/renderers/nodes/accessors/UVNode.js +7 -5
  236. package/examples/jsm/renderers/nodes/core/ArrayInputNode.js +3 -3
  237. package/examples/jsm/renderers/nodes/core/AttributeNode.js +6 -0
  238. package/examples/jsm/renderers/nodes/core/BypassNode.js +38 -0
  239. package/examples/jsm/renderers/nodes/core/CodeNode.js +3 -3
  240. package/examples/jsm/renderers/nodes/core/ContextNode.js +4 -19
  241. package/examples/jsm/renderers/nodes/core/ExpressionNode.js +13 -2
  242. package/examples/jsm/renderers/nodes/core/FunctionNode.js +19 -132
  243. package/examples/jsm/renderers/nodes/core/InputNode.js +13 -3
  244. package/examples/jsm/renderers/nodes/core/Node.js +32 -10
  245. package/examples/jsm/renderers/nodes/core/NodeBuilder.js +146 -128
  246. package/examples/jsm/renderers/nodes/core/NodeFunction.js +22 -0
  247. package/examples/jsm/renderers/nodes/core/NodeFunctionInput.js +4 -4
  248. package/examples/jsm/renderers/nodes/core/NodeKeywords.js +18 -179
  249. package/examples/jsm/renderers/nodes/core/NodeParser.js +11 -0
  250. package/examples/jsm/renderers/nodes/core/NodeVar.js +2 -2
  251. package/examples/jsm/renderers/nodes/core/PropertyNode.js +14 -2
  252. package/examples/jsm/renderers/nodes/core/TempNode.js +10 -13
  253. package/examples/jsm/renderers/nodes/core/VarNode.js +12 -7
  254. package/examples/jsm/renderers/nodes/core/VaryNode.js +23 -8
  255. package/examples/jsm/renderers/nodes/core/constants.js +2 -0
  256. package/examples/jsm/renderers/nodes/display/ColorSpaceNode.js +43 -63
  257. package/examples/jsm/renderers/nodes/display/NormalMapNode.js +21 -22
  258. package/examples/jsm/renderers/nodes/functions/BSDFs.js +64 -101
  259. package/examples/jsm/renderers/nodes/functions/PhysicalMaterialFunctions.js +27 -0
  260. package/examples/jsm/renderers/nodes/inputs/BufferNode.js +25 -0
  261. package/examples/jsm/renderers/nodes/inputs/IntNode.js +17 -0
  262. package/examples/jsm/renderers/nodes/inputs/TextureNode.js +13 -10
  263. package/examples/jsm/renderers/nodes/lights/LightContextNode.js +20 -14
  264. package/examples/jsm/renderers/nodes/lights/LightNode.js +32 -36
  265. package/examples/jsm/renderers/nodes/math/CondNode.js +60 -0
  266. package/examples/jsm/renderers/nodes/math/MathNode.js +91 -37
  267. package/examples/jsm/renderers/nodes/math/OperatorNode.js +124 -31
  268. package/examples/jsm/renderers/nodes/parsers/GLSLNodeFunction.js +137 -0
  269. package/examples/jsm/renderers/nodes/parsers/GLSLNodeParser.js +14 -0
  270. package/examples/jsm/renderers/nodes/parsers/WGSLNodeFunction.js +89 -0
  271. package/examples/jsm/renderers/nodes/parsers/WGSLNodeParser.js +14 -0
  272. package/examples/jsm/renderers/nodes/procedural/CheckerNode.js +7 -9
  273. package/examples/jsm/renderers/nodes/utils/ArrayElementNode.js +31 -0
  274. package/examples/jsm/renderers/nodes/utils/ConvertNode.js +33 -0
  275. package/examples/jsm/renderers/nodes/utils/JoinNode.js +7 -7
  276. package/examples/jsm/renderers/nodes/utils/OscNode.js +58 -0
  277. package/examples/jsm/renderers/nodes/utils/SplitNode.js +18 -8
  278. package/examples/jsm/renderers/nodes/utils/SpriteSheetUVNode.js +7 -7
  279. package/examples/jsm/renderers/nodes/utils/TimerNode.js +1 -1
  280. package/examples/jsm/renderers/webgl/nodes/SlotNode.js +22 -0
  281. package/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +192 -95
  282. package/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +2 -2
  283. package/examples/jsm/renderers/webgl/nodes/WebGLPhysicalContextNode.js +2 -2
  284. package/examples/jsm/renderers/webgpu/WebGPUBackground.js +1 -1
  285. package/examples/jsm/renderers/webgpu/WebGPUBindings.js +10 -9
  286. package/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js +2 -4
  287. package/examples/jsm/renderers/webgpu/WebGPUObjects.js +1 -1
  288. package/examples/jsm/renderers/webgpu/WebGPUProgrammableStage.js +2 -4
  289. package/examples/jsm/renderers/webgpu/WebGPURenderPipeline.js +15 -23
  290. package/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js +44 -65
  291. package/examples/jsm/renderers/webgpu/WebGPURenderer.js +13 -31
  292. package/examples/jsm/renderers/webgpu/WebGPUTextureUtils.js +61 -28
  293. package/examples/jsm/renderers/webgpu/WebGPUTextures.js +35 -29
  294. package/examples/jsm/renderers/webgpu/constants.js +2 -2
  295. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +459 -95
  296. package/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js +1 -1
  297. package/examples/jsm/shaders/ConvolutionShader.js +0 -1
  298. package/examples/jsm/shaders/DigitalGlitch.js +3 -3
  299. package/examples/jsm/shaders/FXAAShader.js +1 -1
  300. package/examples/jsm/shaders/FilmShader.js +1 -1
  301. package/examples/jsm/shaders/GammaCorrectionShader.js +1 -1
  302. package/examples/jsm/shaders/HorizontalBlurShader.js +1 -2
  303. package/examples/jsm/shaders/MMDToonShader.js +0 -1
  304. package/examples/jsm/shaders/SSAOShader.js +1 -1
  305. package/examples/jsm/shaders/SSRShader.js +1 -1
  306. package/examples/jsm/shaders/SSRrShader.js +1 -1
  307. package/examples/jsm/shaders/VerticalBlurShader.js +1 -2
  308. package/examples/jsm/utils/GeometryUtils.js +1 -1
  309. package/examples/jsm/utils/LDrawUtils.js +195 -0
  310. package/examples/jsm/utils/WorkerPool.js +11 -11
  311. package/examples/jsm/webxr/OculusHandPointerModel.js +6 -6
  312. package/examples/jsm/webxr/VRButton.js +24 -0
  313. package/examples/jsm/webxr/XREstimatedLight.js +2 -0
  314. package/package.json +27 -18
  315. package/src/Three.Legacy.js +18 -0
  316. package/src/Three.js +1 -3
  317. package/src/animation/AnimationMixer.js +1 -0
  318. package/src/cameras/CubeCamera.js +2 -0
  319. package/src/cameras/StereoCamera.js +8 -7
  320. package/src/constants.js +3 -23
  321. package/src/core/Layers.js +7 -1
  322. package/src/core/Object3D.js +3 -1
  323. package/src/extras/ImageUtils.js +63 -0
  324. package/src/extras/PMREMGenerator.js +49 -185
  325. package/src/extras/core/Interpolations.js +1 -1
  326. package/src/geometries/LatheGeometry.js +70 -47
  327. package/src/helpers/CameraHelper.js +1 -1
  328. package/src/loaders/FileLoader.js +98 -144
  329. package/src/loaders/ImageLoader.js +9 -4
  330. package/src/loaders/LoaderUtils.js +26 -0
  331. package/src/loaders/MaterialLoader.js +8 -5
  332. package/src/loaders/ObjectLoader.js +2 -0
  333. package/src/materials/Material.js +8 -8
  334. package/src/materials/MeshPhysicalMaterial.js +20 -14
  335. package/src/math/Color.js +1 -39
  336. package/src/math/MathUtils.js +5 -16
  337. package/src/math/Quaternion.js +1 -1
  338. package/src/math/Ray.js +2 -2
  339. package/src/math/Sphere.js +10 -1
  340. package/src/objects/Skeleton.js +1 -0
  341. package/src/renderers/WebGLCubeRenderTarget.js +0 -2
  342. package/src/renderers/WebGLMultisampleRenderTarget.js +7 -1
  343. package/src/renderers/WebGLRenderer.js +106 -199
  344. package/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js +2 -2
  345. package/src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl.js +1 -1
  346. package/src/renderers/shaders/ShaderChunk/cube_uv_reflection_fragment.glsl.js +2 -24
  347. package/src/renderers/shaders/ShaderChunk/emissivemap_fragment.glsl.js +0 -2
  348. package/src/renderers/shaders/ShaderChunk/encodings_pars_fragment.glsl.js +0 -78
  349. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +0 -1
  350. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +0 -2
  351. package/src/renderers/shaders/ShaderChunk/gradientmap_pars_fragment.glsl.js +1 -1
  352. package/src/renderers/shaders/ShaderChunk/lightmap_fragment.glsl.js +1 -1
  353. package/src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js +1 -1
  354. package/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +19 -6
  355. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +28 -2
  356. package/src/renderers/shaders/ShaderChunk/map_fragment.glsl.js +10 -3
  357. package/src/renderers/shaders/ShaderChunk/map_particle_fragment.glsl.js +1 -2
  358. package/src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl.js +1 -1
  359. package/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js +1 -1
  360. package/src/renderers/shaders/ShaderChunk/transmission_pars_fragment.glsl.js +9 -8
  361. package/src/renderers/shaders/ShaderLib/background.glsl.js +1 -3
  362. package/src/renderers/shaders/ShaderLib/equirect.glsl.js +1 -3
  363. package/src/renderers/shaders/ShaderLib/meshbasic.glsl.js +1 -1
  364. package/src/renderers/shaders/ShaderLib/meshmatcap.glsl.js +1 -2
  365. package/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +23 -5
  366. package/src/renderers/shaders/ShaderLib/vsm.glsl.js +2 -1
  367. package/src/renderers/shaders/ShaderLib.js +8 -6
  368. package/src/renderers/shaders/UniformsLib.js +1 -2
  369. package/src/renderers/webgl/WebGLCapabilities.js +0 -2
  370. package/src/renderers/webgl/WebGLCubeMaps.js +0 -4
  371. package/src/renderers/webgl/WebGLCubeUVMaps.js +29 -16
  372. package/src/renderers/webgl/WebGLExtensions.js +1 -0
  373. package/src/renderers/webgl/WebGLMaterials.js +27 -15
  374. package/src/renderers/webgl/WebGLMorphtargets.js +13 -0
  375. package/src/renderers/webgl/WebGLProgram.js +12 -37
  376. package/src/renderers/webgl/WebGLPrograms.js +192 -70
  377. package/src/renderers/webgl/WebGLRenderLists.js +4 -14
  378. package/src/renderers/webgl/WebGLShaderCache.js +120 -0
  379. package/src/renderers/webgl/WebGLShadowMap.js +14 -5
  380. package/src/renderers/webgl/WebGLState.js +157 -17
  381. package/src/renderers/webgl/WebGLTextures.js +442 -83
  382. package/src/renderers/webgl/WebGLUniforms.js +45 -1
  383. package/src/renderers/webgl/WebGLUtils.js +86 -28
  384. package/src/renderers/webxr/WebXRManager.js +77 -109
  385. package/src/scenes/Scene.js +1 -1
  386. package/src/textures/DataTexture.js +0 -2
  387. package/src/textures/DataTexture2DArray.js +0 -2
  388. package/src/textures/DataTexture3D.js +0 -2
  389. package/src/textures/FramebufferTexture.js +25 -0
  390. package/src/textures/Texture.js +8 -1
  391. package/src/textures/VideoTexture.js +1 -3
  392. package/examples/js/WebGL.js +0 -90
  393. package/examples/js/controls/DeviceOrientationControls.js +0 -147
  394. package/examples/js/libs/dat.gui.min.js +0 -14
  395. package/examples/js/utils/RoughnessMipmapper.js +0 -278
  396. package/examples/jsm/controls/DeviceOrientationControls.js +0 -153
  397. package/examples/jsm/libs/dat.gui.module.js +0 -3575
  398. package/examples/jsm/libs/glslang.js +0 -78
  399. package/examples/jsm/libs/glslang.wasm +0 -0
  400. package/examples/jsm/renderers/nodes/consts/MathConsts.js +0 -7
  401. package/examples/jsm/renderers/nodes/core/ConstNode.js +0 -39
  402. package/examples/jsm/renderers/nodes/core/NodeSlot.js +0 -13
  403. package/examples/jsm/renderers/nodes/core/StructNode.js +0 -80
  404. package/examples/jsm/renderers/nodes/core/StructVarNode.js +0 -75
  405. package/examples/jsm/renderers/nodes/functions/EncodingFunctions.js +0 -99
  406. package/examples/jsm/renderers/nodes/functions/MathFunctions.js +0 -47
  407. package/examples/jsm/renderers/webgpu/nodes/ShaderLib.js +0 -152
  408. package/examples/jsm/utils/RoughnessMipmapper.js +0 -300
  409. package/src/extras/objects/ImmediateRenderObject.js +0 -31
@@ -7,14 +7,15 @@ import {
7
7
  LineBasicMaterial,
8
8
  LineSegments,
9
9
  Loader,
10
+ Matrix3,
10
11
  Matrix4,
11
12
  Mesh,
12
- MeshPhongMaterial,
13
13
  MeshStandardMaterial,
14
14
  ShaderMaterial,
15
15
  UniformsLib,
16
16
  UniformsUtils,
17
- Vector3
17
+ Vector3,
18
+ Ray
18
19
  } from 'three';
19
20
 
20
21
  // Special surface finish tag types.
@@ -168,16 +169,45 @@ class LDrawConditionalLineMaterial extends ShaderMaterial {
168
169
 
169
170
  }
170
171
 
171
- function smoothNormals( faces, lineSegments ) {
172
+ function generateFaceNormals( faces ) {
172
173
 
174
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
175
+
176
+ const face = faces[ i ];
177
+ const vertices = face.vertices;
178
+ const v0 = vertices[ 0 ];
179
+ const v1 = vertices[ 1 ];
180
+ const v2 = vertices[ 2 ];
181
+
182
+ _tempVec0.subVectors( v1, v0 );
183
+ _tempVec1.subVectors( v2, v1 );
184
+ face.faceNormal = new Vector3()
185
+ .crossVectors( _tempVec0, _tempVec1 )
186
+ .normalize();
187
+
188
+ }
189
+
190
+ }
191
+
192
+ const _ray = new Ray();
193
+ function smoothNormals( faces, lineSegments, checkSubSegments = false ) {
194
+
195
+ // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because
196
+ // it allows edges to be smoothed as expected (see minifig arms).
197
+ // --
198
+ // And the vector values are initialize multiplied by 1 + 1e-10 to account for floating
199
+ // point errors on vertices along quantization boundaries. Ie after matrix multiplication
200
+ // vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't
201
+ // get merged. This added epsilon attempts to push these error values to the same quantized
202
+ // value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169.
203
+
204
+ const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
173
205
  function hashVertex( v ) {
174
206
 
175
- // NOTE: 1e2 is pretty coarse but was chosen because it allows edges
176
- // to be smoothed as expected (see minifig arms). The errors between edges
177
- // could be due to matrix multiplication.
178
- const x = ~ ~ ( v.x * 1e2 );
179
- const y = ~ ~ ( v.y * 1e2 );
180
- const z = ~ ~ ( v.z * 1e2 );
207
+ const x = ~ ~ ( v.x * hashMultiplier );
208
+ const y = ~ ~ ( v.y * hashMultiplier );
209
+ const z = ~ ~ ( v.z * hashMultiplier );
210
+
181
211
  return `${ x },${ y },${ z }`;
182
212
 
183
213
  }
@@ -188,7 +218,27 @@ function smoothNormals( faces, lineSegments ) {
188
218
 
189
219
  }
190
220
 
221
+ // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected
222
+ // onto the original line.
223
+ function toNormalizedRay( v0, v1, targetRay ) {
224
+
225
+ targetRay.direction.subVectors( v1, v0 ).normalize();
226
+
227
+ const scalar = v0.dot( targetRay.direction );
228
+ targetRay.origin.copy( v0 ).addScaledVector( targetRay.direction, - scalar );
229
+
230
+ return targetRay;
231
+
232
+ }
233
+
234
+ function hashRay( ray ) {
235
+
236
+ return hashEdge( ray.origin, ray.direction );
237
+
238
+ }
239
+
191
240
  const hardEdges = new Set();
241
+ const hardEdgeRays = new Map();
192
242
  const halfEdgeList = {};
193
243
  const normals = [];
194
244
 
@@ -202,6 +252,43 @@ function smoothNormals( faces, lineSegments ) {
202
252
  hardEdges.add( hashEdge( v0, v1 ) );
203
253
  hardEdges.add( hashEdge( v1, v0 ) );
204
254
 
255
+ // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check
256
+ // and requires more memory.
257
+ if ( checkSubSegments ) {
258
+
259
+ // add both ray directions to the map
260
+ const ray = toNormalizedRay( v0, v1, new Ray() );
261
+ const rh1 = hashRay( ray );
262
+ if ( ! hardEdgeRays.has( rh1 ) ) {
263
+
264
+ toNormalizedRay( v1, v0, ray );
265
+ const rh2 = hashRay( ray );
266
+
267
+ const info = {
268
+ ray,
269
+ distances: [],
270
+ };
271
+
272
+ hardEdgeRays.set( rh1, info );
273
+ hardEdgeRays.set( rh2, info );
274
+
275
+ }
276
+
277
+ // store both segments ends in min, max order in the distances array to check if a face edge is a
278
+ // subsegment later.
279
+ const info = hardEdgeRays.get( rh1 );
280
+ let d0 = info.ray.direction.dot( v0 );
281
+ let d1 = info.ray.direction.dot( v1 );
282
+ if ( d0 > d1 ) {
283
+
284
+ [ d0, d1 ] = [ d1, d0 ];
285
+
286
+ }
287
+
288
+ info.distances.push( d0, d1 );
289
+
290
+ }
291
+
205
292
  }
206
293
 
207
294
  // track the half edges associated with each triangle
@@ -219,7 +306,53 @@ function smoothNormals( faces, lineSegments ) {
219
306
  const hash = hashEdge( v0, v1 );
220
307
 
221
308
  // don't add the triangle if the edge is supposed to be hard
222
- if ( hardEdges.has( hash ) ) continue;
309
+ if ( hardEdges.has( hash ) ) {
310
+
311
+ continue;
312
+
313
+ }
314
+
315
+ // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds
316
+ if ( checkSubSegments ) {
317
+
318
+ toNormalizedRay( v0, v1, _ray );
319
+
320
+ const rayHash = hashRay( _ray );
321
+ if ( hardEdgeRays.has( rayHash ) ) {
322
+
323
+ const info = hardEdgeRays.get( rayHash );
324
+ const { ray, distances } = info;
325
+ let d0 = ray.direction.dot( v0 );
326
+ let d1 = ray.direction.dot( v1 );
327
+
328
+ if ( d0 > d1 ) {
329
+
330
+ [ d0, d1 ] = [ d1, d0 ];
331
+
332
+ }
333
+
334
+ // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals
335
+ let found = false;
336
+ for ( let i = 0, l = distances.length; i < l; i += 2 ) {
337
+
338
+ if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) {
339
+
340
+ found = true;
341
+ break;
342
+
343
+ }
344
+
345
+ }
346
+
347
+ if ( found ) {
348
+
349
+ continue;
350
+
351
+ }
352
+
353
+ }
354
+
355
+ }
223
356
 
224
357
  const info = {
225
358
  index: index,
@@ -463,6 +596,12 @@ class LineParser {
463
596
 
464
597
  }
465
598
 
599
+ getVector() {
600
+
601
+ return new Vector3( parseFloat( this.getToken() ), parseFloat( this.getToken() ), parseFloat( this.getToken() ) );
602
+
603
+ }
604
+
466
605
  getRemainingString() {
467
606
 
468
607
  return this.line.substring( this.currentCharIndex, this.lineLength );
@@ -489,1343 +628,1454 @@ class LineParser {
489
628
 
490
629
  }
491
630
 
492
- class LDrawFileCache {
631
+ class LDrawParsedCache {
493
632
 
494
633
  constructor( loader ) {
495
634
 
496
- this.cache = {};
497
635
  this.loader = loader;
636
+ this.cache = {};
498
637
 
499
638
  }
500
639
 
501
- setData( key, contents ) {
502
-
503
- this.cache[ key.toLowerCase() ] = contents;
640
+ cloneResult( original ) {
504
641
 
505
- }
642
+ const result = {};
506
643
 
507
- async loadData( fileName ) {
644
+ // vertices are transformed and normals computed before being converted to geometry
645
+ // so these pieces must be cloned.
646
+ result.faces = original.faces.map( face => {
508
647
 
509
- const key = fileName.toLowerCase();
510
- if ( key in this.cache ) {
648
+ return {
649
+ colorCode: face.colorCode,
650
+ material: face.material,
651
+ vertices: face.vertices.map( v => v.clone() ),
652
+ normals: face.normals.map( () => null ),
653
+ faceNormal: null
654
+ };
511
655
 
512
- return this.cache[ key ];
656
+ } );
513
657
 
514
- }
658
+ result.conditionalSegments = original.conditionalSegments.map( face => {
515
659
 
516
- this.cache[ fileName ] = new Promise( async ( resolve, reject ) => {
660
+ return {
661
+ colorCode: face.colorCode,
662
+ material: face.material,
663
+ vertices: face.vertices.map( v => v.clone() ),
664
+ controlPoints: face.controlPoints.map( v => v.clone() )
665
+ };
517
666
 
518
- let triedLowerCase = false;
519
- let locationState = FILE_LOCATION_AS_IS;
520
- while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
667
+ } );
521
668
 
522
- let subobjectURL = fileName;
523
- switch ( locationState ) {
669
+ result.lineSegments = original.lineSegments.map( face => {
524
670
 
525
- case FILE_LOCATION_AS_IS:
526
- locationState = locationState + 1;
527
- break;
671
+ return {
672
+ colorCode: face.colorCode,
673
+ material: face.material,
674
+ vertices: face.vertices.map( v => v.clone() )
675
+ };
528
676
 
529
- case FILE_LOCATION_TRY_PARTS:
530
- subobjectURL = 'parts/' + subobjectURL;
531
- locationState = locationState + 1;
532
- break;
677
+ } );
533
678
 
534
- case FILE_LOCATION_TRY_P:
535
- subobjectURL = 'p/' + subobjectURL;
536
- locationState = locationState + 1;
537
- break;
679
+ // none if this is subsequently modified
680
+ result.type = original.type;
681
+ result.category = original.category;
682
+ result.keywords = original.keywords;
683
+ result.subobjects = original.subobjects;
684
+ result.totalFaces = original.totalFaces;
685
+ result.startingConstructionStep = original.startingConstructionStep;
686
+ result.materials = original.materials;
687
+ return result;
538
688
 
539
- case FILE_LOCATION_TRY_MODELS:
540
- subobjectURL = 'models/' + subobjectURL;
541
- locationState = locationState + 1;
542
- break;
689
+ }
543
690
 
544
- case FILE_LOCATION_TRY_RELATIVE:
545
- subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
546
- locationState = locationState + 1;
547
- break;
691
+ async fetchData( fileName ) {
548
692
 
549
- case FILE_LOCATION_TRY_ABSOLUTE:
693
+ let triedLowerCase = false;
694
+ let locationState = FILE_LOCATION_AS_IS;
695
+ while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
550
696
 
551
- if ( triedLowerCase ) {
697
+ let subobjectURL = fileName;
698
+ switch ( locationState ) {
552
699
 
553
- // Try absolute path
554
- locationState = FILE_LOCATION_NOT_FOUND;
700
+ case FILE_LOCATION_AS_IS:
701
+ locationState = locationState + 1;
702
+ break;
555
703
 
556
- } else {
704
+ case FILE_LOCATION_TRY_PARTS:
705
+ subobjectURL = 'parts/' + subobjectURL;
706
+ locationState = locationState + 1;
707
+ break;
557
708
 
558
- // Next attempt is lower case
559
- fileName = fileName.toLowerCase();
560
- subobjectURL = fileName;
561
- triedLowerCase = true;
562
- locationState = FILE_LOCATION_AS_IS;
709
+ case FILE_LOCATION_TRY_P:
710
+ subobjectURL = 'p/' + subobjectURL;
711
+ locationState = locationState + 1;
712
+ break;
563
713
 
564
- }
714
+ case FILE_LOCATION_TRY_MODELS:
715
+ subobjectURL = 'models/' + subobjectURL;
716
+ locationState = locationState + 1;
717
+ break;
565
718
 
566
- break;
719
+ case FILE_LOCATION_TRY_RELATIVE:
720
+ subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
721
+ locationState = locationState + 1;
722
+ break;
567
723
 
568
- }
724
+ case FILE_LOCATION_TRY_ABSOLUTE:
569
725
 
570
- const loader = this.loader;
571
- const fileLoader = new FileLoader( loader.manager );
572
- fileLoader.setPath( loader.partsLibraryPath );
573
- fileLoader.setRequestHeader( loader.requestHeader );
574
- fileLoader.setWithCredentials( loader.withCredentials );
726
+ if ( triedLowerCase ) {
575
727
 
576
- try {
728
+ // Try absolute path
729
+ locationState = FILE_LOCATION_NOT_FOUND;
577
730
 
578
- const text = await fileLoader.loadAsync( subobjectURL );
579
- this.setData( fileName, text );
580
- resolve( text );
581
- return;
731
+ } else {
582
732
 
583
- } catch {
733
+ // Next attempt is lower case
734
+ fileName = fileName.toLowerCase();
735
+ subobjectURL = fileName;
736
+ triedLowerCase = true;
737
+ locationState = FILE_LOCATION_AS_IS;
584
738
 
585
- continue;
739
+ }
586
740
 
587
- }
741
+ break;
588
742
 
589
743
  }
590
744
 
591
- reject();
745
+ const loader = this.loader;
746
+ const fileLoader = new FileLoader( loader.manager );
747
+ fileLoader.setPath( loader.partsLibraryPath );
748
+ fileLoader.setRequestHeader( loader.requestHeader );
749
+ fileLoader.setWithCredentials( loader.withCredentials );
592
750
 
593
- } );
751
+ try {
594
752
 
595
- return this.cache[ fileName ];
753
+ const text = await fileLoader.loadAsync( subobjectURL );
754
+ return text;
596
755
 
597
- }
756
+ } catch {
598
757
 
599
- }
758
+ continue;
600
759
 
601
- function sortByMaterial( a, b ) {
760
+ }
602
761
 
603
- if ( a.colourCode === b.colourCode ) {
762
+ }
604
763
 
605
- return 0;
764
+ throw new Error( 'LDrawLoader: Subobject "' + fileName + '" could not be loaded.' );
606
765
 
607
766
  }
608
767
 
609
- if ( a.colourCode < b.colourCode ) {
768
+ parse( text ) {
610
769
 
611
- return - 1;
770
+ const loader = this.loader;
612
771
 
613
- }
772
+ // final results
773
+ const faces = [];
774
+ const lineSegments = [];
775
+ const conditionalSegments = [];
776
+ const subobjects = [];
777
+ const materials = {};
614
778
 
615
- return 1;
779
+ const getLocalMaterial = colorCode => {
616
780
 
617
- }
781
+ return colorCode in materials ? materials[ colorCode ] : null;
618
782
 
619
- function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) {
783
+ };
620
784
 
621
- // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 )
622
- // With per face / segment material, implemented with mesh groups and materials array
785
+ let type = 'Model';
786
+ let category = null;
787
+ let keywords = null;
788
+ let totalFaces = 0;
623
789
 
624
- // Sort the faces or line segments by colour code to make later the mesh groups
625
- elements.sort( sortByMaterial );
790
+ // split into lines
791
+ if ( text.indexOf( '\r\n' ) !== - 1 ) {
626
792
 
627
- if ( totalElements === null ) {
793
+ // This is faster than String.split with regex that splits on both
794
+ text = text.replace( /\r\n/g, '\n' );
628
795
 
629
- totalElements = elements.length;
796
+ }
630
797
 
631
- }
798
+ const lines = text.split( '\n' );
799
+ const numLines = lines.length;
632
800
 
633
- const positions = new Float32Array( elementSize * totalElements * 3 );
634
- const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null;
635
- const materials = [];
801
+ let parsingEmbeddedFiles = false;
802
+ let currentEmbeddedFileName = null;
803
+ let currentEmbeddedText = null;
636
804
 
637
- const quadArray = new Array( 6 );
638
- const bufferGeometry = new BufferGeometry();
639
- let prevMaterial = null;
640
- let index0 = 0;
641
- let numGroupVerts = 0;
642
- let offset = 0;
805
+ let bfcCertified = false;
806
+ let bfcCCW = true;
807
+ let bfcInverted = false;
808
+ let bfcCull = true;
643
809
 
644
- for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
810
+ let startingConstructionStep = false;
645
811
 
646
- const elem = elements[ iElem ];
647
- let vertices = elem.vertices;
648
- if ( vertices.length === 4 ) {
812
+ // Parse all line commands
813
+ for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
649
814
 
650
- quadArray[ 0 ] = vertices[ 0 ];
651
- quadArray[ 1 ] = vertices[ 1 ];
652
- quadArray[ 2 ] = vertices[ 2 ];
653
- quadArray[ 3 ] = vertices[ 0 ];
654
- quadArray[ 4 ] = vertices[ 2 ];
655
- quadArray[ 5 ] = vertices[ 3 ];
656
- vertices = quadArray;
815
+ const line = lines[ lineIndex ];
657
816
 
658
- }
817
+ if ( line.length === 0 ) continue;
659
818
 
660
- for ( let j = 0, l = vertices.length; j < l; j ++ ) {
819
+ if ( parsingEmbeddedFiles ) {
661
820
 
662
- const v = vertices[ j ];
663
- const index = offset + j * 3;
664
- positions[ index + 0 ] = v.x;
665
- positions[ index + 1 ] = v.y;
666
- positions[ index + 2 ] = v.z;
821
+ if ( line.startsWith( '0 FILE ' ) ) {
667
822
 
668
- }
823
+ // Save previous embedded file in the cache
824
+ this.setData( currentEmbeddedFileName, currentEmbeddedText );
669
825
 
670
- if ( elementSize === 3 ) {
826
+ // New embedded text file
827
+ currentEmbeddedFileName = line.substring( 7 );
828
+ currentEmbeddedText = '';
671
829
 
672
- let elemNormals = elem.normals;
673
- if ( elemNormals.length === 4 ) {
830
+ } else {
674
831
 
675
- quadArray[ 0 ] = elemNormals[ 0 ];
676
- quadArray[ 1 ] = elemNormals[ 1 ];
677
- quadArray[ 2 ] = elemNormals[ 2 ];
678
- quadArray[ 3 ] = elemNormals[ 0 ];
679
- quadArray[ 4 ] = elemNormals[ 2 ];
680
- quadArray[ 5 ] = elemNormals[ 3 ];
681
- elemNormals = quadArray;
832
+ currentEmbeddedText += line + '\n';
682
833
 
683
- }
834
+ }
684
835
 
685
- for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
836
+ continue;
686
837
 
687
- let n = elem.faceNormal;
688
- if ( elemNormals[ j ] ) {
838
+ }
689
839
 
690
- n = elemNormals[ j ].norm;
840
+ const lp = new LineParser( line, lineIndex + 1 );
841
+ lp.seekNonSpace();
691
842
 
692
- }
843
+ if ( lp.isAtTheEnd() ) {
693
844
 
694
- const index = offset + j * 3;
695
- normals[ index + 0 ] = n.x;
696
- normals[ index + 1 ] = n.y;
697
- normals[ index + 2 ] = n.z;
845
+ // Empty line
846
+ continue;
698
847
 
699
848
  }
700
849
 
701
- }
850
+ // Parse the line type
851
+ const lineType = lp.getToken();
702
852
 
703
- if ( prevMaterial !== elem.material ) {
853
+ let material;
854
+ let colorCode;
855
+ let segment;
856
+ let ccw;
857
+ let doubleSided;
858
+ let v0, v1, v2, v3, c0, c1;
704
859
 
705
- if ( prevMaterial !== null ) {
860
+ switch ( lineType ) {
706
861
 
707
- bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
862
+ // Line type 0: Comment or META
863
+ case '0':
708
864
 
709
- }
865
+ // Parse meta directive
866
+ const meta = lp.getToken();
710
867
 
711
- materials.push( elem.material );
868
+ if ( meta ) {
712
869
 
713
- prevMaterial = elem.material;
714
- index0 = offset / 3;
715
- numGroupVerts = vertices.length;
870
+ switch ( meta ) {
716
871
 
717
- } else {
872
+ case '!LDRAW_ORG':
718
873
 
719
- numGroupVerts += vertices.length;
874
+ type = lp.getToken();
875
+ break;
720
876
 
721
- }
877
+ case '!COLOUR':
722
878
 
723
- offset += 3 * vertices.length;
879
+ material = loader.parseColorMetaDirective( lp );
880
+ if ( material ) {
724
881
 
725
- }
882
+ materials[ material.userData.code ] = material;
726
883
 
727
- if ( numGroupVerts > 0 ) {
884
+ } else {
728
885
 
729
- bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
886
+ console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
730
887
 
731
- }
888
+ }
732
889
 
733
- bufferGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
890
+ break;
734
891
 
735
- if ( normals !== null ) {
892
+ case '!CATEGORY':
736
893
 
737
- bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
894
+ category = lp.getToken();
895
+ break;
738
896
 
739
- }
897
+ case '!KEYWORDS':
740
898
 
741
- let object3d = null;
899
+ const newKeywords = lp.getRemainingString().split( ',' );
900
+ if ( newKeywords.length > 0 ) {
742
901
 
743
- if ( elementSize === 2 ) {
902
+ if ( ! keywords ) {
744
903
 
745
- object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
904
+ keywords = [];
746
905
 
747
- } else if ( elementSize === 3 ) {
906
+ }
748
907
 
749
- object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
908
+ newKeywords.forEach( function ( keyword ) {
750
909
 
751
- }
910
+ keywords.push( keyword.trim() );
752
911
 
753
- if ( isConditionalSegments ) {
912
+ } );
754
913
 
755
- object3d.isConditionalLine = true;
914
+ }
756
915
 
757
- const controlArray0 = new Float32Array( elements.length * 3 * 2 );
758
- const controlArray1 = new Float32Array( elements.length * 3 * 2 );
759
- const directionArray = new Float32Array( elements.length * 3 * 2 );
760
- for ( let i = 0, l = elements.length; i < l; i ++ ) {
916
+ break;
761
917
 
762
- const os = elements[ i ];
763
- const vertices = os.vertices;
764
- const controlPoints = os.controlPoints;
765
- const c0 = controlPoints[ 0 ];
766
- const c1 = controlPoints[ 1 ];
767
- const v0 = vertices[ 0 ];
768
- const v1 = vertices[ 1 ];
769
- const index = i * 3 * 2;
770
- controlArray0[ index + 0 ] = c0.x;
771
- controlArray0[ index + 1 ] = c0.y;
772
- controlArray0[ index + 2 ] = c0.z;
773
- controlArray0[ index + 3 ] = c0.x;
774
- controlArray0[ index + 4 ] = c0.y;
775
- controlArray0[ index + 5 ] = c0.z;
918
+ case 'FILE':
776
919
 
777
- controlArray1[ index + 0 ] = c1.x;
778
- controlArray1[ index + 1 ] = c1.y;
779
- controlArray1[ index + 2 ] = c1.z;
780
- controlArray1[ index + 3 ] = c1.x;
781
- controlArray1[ index + 4 ] = c1.y;
782
- controlArray1[ index + 5 ] = c1.z;
920
+ if ( lineIndex > 0 ) {
783
921
 
784
- directionArray[ index + 0 ] = v1.x - v0.x;
785
- directionArray[ index + 1 ] = v1.y - v0.y;
786
- directionArray[ index + 2 ] = v1.z - v0.z;
787
- directionArray[ index + 3 ] = v1.x - v0.x;
788
- directionArray[ index + 4 ] = v1.y - v0.y;
789
- directionArray[ index + 5 ] = v1.z - v0.z;
922
+ // Start embedded text files parsing
923
+ parsingEmbeddedFiles = true;
924
+ currentEmbeddedFileName = lp.getRemainingString();
925
+ currentEmbeddedText = '';
790
926
 
791
- }
927
+ bfcCertified = false;
928
+ bfcCCW = true;
792
929
 
793
- bufferGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) );
794
- bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) );
795
- bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) );
930
+ }
796
931
 
797
- }
932
+ break;
798
933
 
799
- return object3d;
934
+ case 'BFC':
800
935
 
801
- }
936
+ // Changes to the backface culling state
937
+ while ( ! lp.isAtTheEnd() ) {
802
938
 
803
- //
939
+ const token = lp.getToken();
804
940
 
805
- class LDrawLoader extends Loader {
941
+ switch ( token ) {
806
942
 
807
- constructor( manager ) {
943
+ case 'CERTIFY':
944
+ case 'NOCERTIFY':
808
945
 
809
- super( manager );
946
+ bfcCertified = token === 'CERTIFY';
947
+ bfcCCW = true;
810
948
 
811
- // Array of THREE.Material
812
- this.materials = [];
949
+ break;
813
950
 
814
- // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
815
- // This also allows to handle the embedded text files ("0 FILE" lines)
816
- this.cache = new LDrawFileCache( this );
951
+ case 'CW':
952
+ case 'CCW':
817
953
 
818
- // 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.
819
- this.fileMap = null;
954
+ bfcCCW = token === 'CCW';
820
955
 
821
- this.rootParseScope = this.newParseScopeLevel();
956
+ break;
822
957
 
823
- // Add default main triangle and line edge materials (used in pieces that can be coloured with a main color)
824
- this.setMaterials( [
825
- this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ),
826
- this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) )
827
- ] );
958
+ case 'INVERTNEXT':
828
959
 
829
- // If this flag is set to true, each subobject will be a Object.
830
- // If not (the default), only one object which contains all the merged primitives will be created.
831
- this.separateObjects = false;
960
+ bfcInverted = true;
832
961
 
833
- // If this flag is set to true the vertex normals will be smoothed.
834
- this.smoothNormals = true;
962
+ break;
835
963
 
836
- // The path to load parts from the LDraw parts library from.
837
- this.partsLibraryPath = '';
964
+ case 'CLIP':
965
+ case 'NOCLIP':
838
966
 
839
- }
967
+ bfcCull = token === 'CLIP';
840
968
 
841
- setPartsLibraryPath( path ) {
969
+ break;
842
970
 
843
- this.partsLibraryPath = path;
844
- return this;
971
+ default:
845
972
 
846
- }
973
+ console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
847
974
 
848
- async preloadMaterials( url ) {
975
+ break;
849
976
 
850
- const fileLoader = new FileLoader( this.manager );
851
- fileLoader.setPath( this.path );
852
- fileLoader.setRequestHeader( this.requestHeader );
853
- fileLoader.setWithCredentials( this.withCredentials );
977
+ }
854
978
 
855
- const text = await fileLoader.loadAsync( url );
856
- const colorLineRegex = /^0 !COLOUR/;
857
- const lines = text.split( /[\n\r]/g );
858
- const materials = [];
859
- for ( let i = 0, l = lines.length; i < l; i ++ ) {
979
+ }
860
980
 
861
- const line = lines[ i ];
862
- if ( colorLineRegex.test( line ) ) {
981
+ break;
863
982
 
864
- const directive = line.replace( colorLineRegex, '' );
865
- const material = this.parseColourMetaDirective( new LineParser( directive ) );
866
- materials.push( material );
983
+ case 'STEP':
867
984
 
868
- }
985
+ startingConstructionStep = true;
869
986
 
870
- }
987
+ break;
871
988
 
872
- this.setMaterials( materials );
989
+ default:
990
+ // Other meta directives are not implemented
991
+ break;
873
992
 
874
- }
993
+ }
875
994
 
876
- load( url, onLoad, onProgress, onError ) {
995
+ }
877
996
 
878
- if ( ! this.fileMap ) {
997
+ break;
879
998
 
880
- this.fileMap = {};
999
+ // Line type 1: Sub-object file
1000
+ case '1':
881
1001
 
882
- }
1002
+ colorCode = lp.getToken();
1003
+ material = getLocalMaterial( colorCode );
883
1004
 
884
- const fileLoader = new FileLoader( this.manager );
885
- fileLoader.setPath( this.path );
886
- fileLoader.setRequestHeader( this.requestHeader );
887
- fileLoader.setWithCredentials( this.withCredentials );
888
- fileLoader.load( url, text => {
1005
+ const posX = parseFloat( lp.getToken() );
1006
+ const posY = parseFloat( lp.getToken() );
1007
+ const posZ = parseFloat( lp.getToken() );
1008
+ const m0 = parseFloat( lp.getToken() );
1009
+ const m1 = parseFloat( lp.getToken() );
1010
+ const m2 = parseFloat( lp.getToken() );
1011
+ const m3 = parseFloat( lp.getToken() );
1012
+ const m4 = parseFloat( lp.getToken() );
1013
+ const m5 = parseFloat( lp.getToken() );
1014
+ const m6 = parseFloat( lp.getToken() );
1015
+ const m7 = parseFloat( lp.getToken() );
1016
+ const m8 = parseFloat( lp.getToken() );
889
1017
 
890
- this.processObject( text, null, url, this.rootParseScope )
891
- .then( function ( result ) {
1018
+ const matrix = new Matrix4().set(
1019
+ m0, m1, m2, posX,
1020
+ m3, m4, m5, posY,
1021
+ m6, m7, m8, posZ,
1022
+ 0, 0, 0, 1
1023
+ );
892
1024
 
893
- onLoad( result.groupObject );
1025
+ let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
894
1026
 
895
- } );
1027
+ if ( loader.fileMap[ fileName ] ) {
896
1028
 
897
- }, onProgress, onError );
1029
+ // Found the subobject path in the preloaded file path map
1030
+ fileName = loader.fileMap[ fileName ];
898
1031
 
899
- }
1032
+ } else {
900
1033
 
901
- parse( text, path, onLoad ) {
1034
+ // Standardized subfolders
1035
+ if ( fileName.startsWith( 's/' ) ) {
902
1036
 
903
- // Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
904
- this.processObject( text, null, path, this.rootParseScope )
905
- .then( function ( result ) {
1037
+ fileName = 'parts/' + fileName;
906
1038
 
907
- onLoad( result.groupObject );
1039
+ } else if ( fileName.startsWith( '48/' ) ) {
908
1040
 
909
- } );
1041
+ fileName = 'p/' + fileName;
910
1042
 
911
- }
1043
+ }
912
1044
 
913
- setMaterials( materials ) {
1045
+ }
914
1046
 
915
- // Clears parse scopes stack, adds new scope with material library
916
- this.rootParseScope = this.newParseScopeLevel( materials );
917
- this.rootParseScope.isFromParse = false;
1047
+ subobjects.push( {
1048
+ material: material,
1049
+ colorCode: colorCode,
1050
+ matrix: matrix,
1051
+ fileName: fileName,
1052
+ inverted: bfcInverted,
1053
+ startingConstructionStep: startingConstructionStep
1054
+ } );
918
1055
 
919
- this.materials = materials;
1056
+ bfcInverted = false;
920
1057
 
921
- return this;
1058
+ break;
922
1059
 
923
- }
1060
+ // Line type 2: Line segment
1061
+ case '2':
924
1062
 
925
- setFileMap( fileMap ) {
1063
+ colorCode = lp.getToken();
1064
+ material = getLocalMaterial( colorCode );
1065
+ v0 = lp.getVector();
1066
+ v1 = lp.getVector();
926
1067
 
927
- this.fileMap = fileMap;
1068
+ segment = {
1069
+ material: material,
1070
+ colorCode: colorCode,
1071
+ vertices: [ v0, v1 ],
1072
+ };
928
1073
 
929
- return this;
1074
+ lineSegments.push( segment );
930
1075
 
931
- }
1076
+ break;
932
1077
 
933
- newParseScopeLevel( materials = null, parentScope = null ) {
1078
+ // Line type 5: Conditional Line segment
1079
+ case '5':
934
1080
 
935
- // Adds a new scope level, assign materials to it and returns it
1081
+ colorCode = lp.getToken();
1082
+ material = getLocalMaterial( colorCode );
1083
+ v0 = lp.getVector();
1084
+ v1 = lp.getVector();
1085
+ c0 = lp.getVector();
1086
+ c1 = lp.getVector();
936
1087
 
937
- const matLib = {};
1088
+ segment = {
1089
+ material: material,
1090
+ colorCode: colorCode,
1091
+ vertices: [ v0, v1 ],
1092
+ controlPoints: [ c0, c1 ],
1093
+ };
938
1094
 
939
- if ( materials ) {
1095
+ conditionalSegments.push( segment );
940
1096
 
941
- for ( let i = 0, n = materials.length; i < n; i ++ ) {
1097
+ break;
942
1098
 
943
- const material = materials[ i ];
944
- matLib[ material.userData.code ] = material;
1099
+ // Line type 3: Triangle
1100
+ case '3':
1101
+
1102
+ colorCode = lp.getToken();
1103
+ material = getLocalMaterial( colorCode );
1104
+ ccw = bfcCCW;
1105
+ doubleSided = ! bfcCertified || ! bfcCull;
1106
+
1107
+ if ( ccw === true ) {
1108
+
1109
+ v0 = lp.getVector();
1110
+ v1 = lp.getVector();
1111
+ v2 = lp.getVector();
1112
+
1113
+ } else {
1114
+
1115
+ v2 = lp.getVector();
1116
+ v1 = lp.getVector();
1117
+ v0 = lp.getVector();
1118
+
1119
+ }
1120
+
1121
+ faces.push( {
1122
+ material: material,
1123
+ colorCode: colorCode,
1124
+ faceNormal: null,
1125
+ vertices: [ v0, v1, v2 ],
1126
+ normals: [ null, null, null ],
1127
+ } );
1128
+ totalFaces ++;
1129
+
1130
+ if ( doubleSided === true ) {
1131
+
1132
+ faces.push( {
1133
+ material: material,
1134
+ colorCode: colorCode,
1135
+ faceNormal: null,
1136
+ vertices: [ v2, v1, v0 ],
1137
+ normals: [ null, null, null ],
1138
+ } );
1139
+ totalFaces ++;
1140
+
1141
+ }
1142
+
1143
+ break;
1144
+
1145
+ // Line type 4: Quadrilateral
1146
+ case '4':
1147
+
1148
+ colorCode = lp.getToken();
1149
+ material = getLocalMaterial( colorCode );
1150
+ ccw = bfcCCW;
1151
+ doubleSided = ! bfcCertified || ! bfcCull;
1152
+
1153
+ if ( ccw === true ) {
1154
+
1155
+ v0 = lp.getVector();
1156
+ v1 = lp.getVector();
1157
+ v2 = lp.getVector();
1158
+ v3 = lp.getVector();
1159
+
1160
+ } else {
1161
+
1162
+ v3 = lp.getVector();
1163
+ v2 = lp.getVector();
1164
+ v1 = lp.getVector();
1165
+ v0 = lp.getVector();
1166
+
1167
+ }
1168
+
1169
+ // specifically place the triangle diagonal in the v0 and v1 slots so we can
1170
+ // account for the doubling of vertices later when smoothing normals.
1171
+ faces.push( {
1172
+ material: material,
1173
+ colorCode: colorCode,
1174
+ faceNormal: null,
1175
+ vertices: [ v0, v1, v2, v3 ],
1176
+ normals: [ null, null, null, null ],
1177
+ } );
1178
+ totalFaces += 2;
1179
+
1180
+ if ( doubleSided === true ) {
1181
+
1182
+ faces.push( {
1183
+ material: material,
1184
+ colorCode: colorCode,
1185
+ faceNormal: null,
1186
+ vertices: [ v3, v2, v1, v0 ],
1187
+ normals: [ null, null, null, null ],
1188
+ } );
1189
+ totalFaces += 2;
1190
+
1191
+ }
1192
+
1193
+ break;
1194
+
1195
+ default:
1196
+ throw new Error( 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.' );
945
1197
 
946
1198
  }
947
1199
 
948
1200
  }
949
1201
 
950
- const newParseScope = {
1202
+ if ( parsingEmbeddedFiles ) {
951
1203
 
952
- parentScope: parentScope,
953
- lib: matLib,
954
- url: null,
1204
+ this.setData( currentEmbeddedFileName, currentEmbeddedText );
955
1205
 
956
- // Subobjects
957
- subobjects: null,
958
- numSubobjects: 0,
959
- subobjectIndex: 0,
960
- inverted: false,
961
- category: null,
962
- keywords: null,
1206
+ }
963
1207
 
964
- // Current subobject
965
- currentFileName: null,
966
- mainColourCode: parentScope ? parentScope.mainColourCode : '16',
967
- mainEdgeColourCode: parentScope ? parentScope.mainEdgeColourCode : '24',
968
- currentMatrix: new Matrix4(),
969
- matrix: new Matrix4(),
1208
+ return {
1209
+ faces,
1210
+ conditionalSegments,
1211
+ lineSegments,
1212
+ type,
1213
+ category,
1214
+ keywords,
1215
+ subobjects,
1216
+ totalFaces,
1217
+ startingConstructionStep,
1218
+ materials
1219
+ };
970
1220
 
971
- // If false, it is a root material scope previous to parse
972
- isFromParse: true,
1221
+ }
973
1222
 
974
- faces: null,
975
- lineSegments: null,
976
- conditionalSegments: null,
977
- totalFaces: 0,
1223
+ loadData( fileName ) {
978
1224
 
979
- // If true, this object is the start of a construction step
980
- startingConstructionStep: false
981
- };
1225
+ const key = fileName.toLowerCase();
1226
+ if ( ! ( key in this.cache ) ) {
982
1227
 
983
- return newParseScope;
1228
+ this.cache[ key ] = this.fetchData( fileName ).then( text => {
1229
+
1230
+ return this.parse( text );
1231
+
1232
+ } );
1233
+
1234
+ }
1235
+
1236
+ return this.cache[ key ].then( result => {
1237
+
1238
+ return this.cloneResult( result );
1239
+
1240
+ } );
984
1241
 
985
1242
  }
986
1243
 
987
- addMaterial( material, parseScope ) {
1244
+ setData( fileName, text ) {
988
1245
 
989
- // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
1246
+ const key = fileName.toLowerCase();
1247
+ this.cache[ key ] = Promise.resolve( this.parse( text ) );
990
1248
 
991
- const matLib = parseScope.lib;
1249
+ }
1250
+
1251
+ }
1252
+
1253
+ function sortByMaterial( a, b ) {
1254
+
1255
+ if ( a.colorCode === b.colorCode ) {
1256
+
1257
+ return 0;
1258
+
1259
+ }
1260
+
1261
+ if ( a.colorCode < b.colorCode ) {
1262
+
1263
+ return - 1;
1264
+
1265
+ }
1266
+
1267
+ return 1;
1268
+
1269
+ }
992
1270
 
993
- if ( ! matLib[ material.userData.code ] ) {
1271
+ function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) {
994
1272
 
995
- this.materials.push( material );
1273
+ // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 )
1274
+ // With per face / segment material, implemented with mesh groups and materials array
996
1275
 
997
- }
1276
+ // Sort the faces or line segments by color code to make later the mesh groups
1277
+ elements.sort( sortByMaterial );
998
1278
 
999
- matLib[ material.userData.code ] = material;
1279
+ if ( totalElements === null ) {
1000
1280
 
1001
- return this;
1281
+ totalElements = elements.length;
1002
1282
 
1003
1283
  }
1004
1284
 
1005
- getMaterial( colourCode, parseScope = this.rootParseScope ) {
1006
-
1007
- // Given a colour code search its material in the parse scopes stack
1285
+ const positions = new Float32Array( elementSize * totalElements * 3 );
1286
+ const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null;
1287
+ const materials = [];
1008
1288
 
1009
- if ( colourCode.startsWith( '0x2' ) ) {
1289
+ const quadArray = new Array( 6 );
1290
+ const bufferGeometry = new BufferGeometry();
1291
+ let prevMaterial = null;
1292
+ let index0 = 0;
1293
+ let numGroupVerts = 0;
1294
+ let offset = 0;
1010
1295
 
1011
- // Special 'direct' material value (RGB colour)
1296
+ for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
1012
1297
 
1013
- const colour = colourCode.substring( 3 );
1298
+ const elem = elements[ iElem ];
1299
+ let vertices = elem.vertices;
1300
+ if ( vertices.length === 4 ) {
1014
1301
 
1015
- return this.parseColourMetaDirective( new LineParser( 'Direct_Color_' + colour + ' CODE -1 VALUE #' + colour + ' EDGE #' + colour + '' ) );
1302
+ quadArray[ 0 ] = vertices[ 0 ];
1303
+ quadArray[ 1 ] = vertices[ 1 ];
1304
+ quadArray[ 2 ] = vertices[ 2 ];
1305
+ quadArray[ 3 ] = vertices[ 0 ];
1306
+ quadArray[ 4 ] = vertices[ 2 ];
1307
+ quadArray[ 5 ] = vertices[ 3 ];
1308
+ vertices = quadArray;
1016
1309
 
1017
1310
  }
1018
1311
 
1019
- while ( parseScope ) {
1312
+ for ( let j = 0, l = vertices.length; j < l; j ++ ) {
1020
1313
 
1021
- const material = parseScope.lib[ colourCode ];
1314
+ const v = vertices[ j ];
1315
+ const index = offset + j * 3;
1316
+ positions[ index + 0 ] = v.x;
1317
+ positions[ index + 1 ] = v.y;
1318
+ positions[ index + 2 ] = v.z;
1022
1319
 
1023
- if ( material ) {
1320
+ }
1024
1321
 
1025
- return material;
1322
+ // create the normals array if this is a set of faces
1323
+ if ( elementSize === 3 ) {
1026
1324
 
1027
- } else {
1325
+ if ( ! elem.faceNormal ) {
1028
1326
 
1029
- parseScope = parseScope.parentScope;
1327
+ const v0 = vertices[ 0 ];
1328
+ const v1 = vertices[ 1 ];
1329
+ const v2 = vertices[ 2 ];
1330
+ _tempVec0.subVectors( v1, v0 );
1331
+ _tempVec1.subVectors( v2, v1 );
1332
+ elem.faceNormal = new Vector3()
1333
+ .crossVectors( _tempVec0, _tempVec1 )
1334
+ .normalize();
1030
1335
 
1031
1336
  }
1032
1337
 
1033
- }
1034
-
1035
- // Material was not found
1036
- return null;
1037
-
1038
- }
1039
-
1040
- parseColourMetaDirective( lineParser ) {
1338
+ let elemNormals = elem.normals;
1339
+ if ( elemNormals.length === 4 ) {
1041
1340
 
1042
- // Parses a colour definition and returns a THREE.Material
1341
+ quadArray[ 0 ] = elemNormals[ 0 ];
1342
+ quadArray[ 1 ] = elemNormals[ 1 ];
1343
+ quadArray[ 2 ] = elemNormals[ 2 ];
1344
+ quadArray[ 3 ] = elemNormals[ 0 ];
1345
+ quadArray[ 4 ] = elemNormals[ 2 ];
1346
+ quadArray[ 5 ] = elemNormals[ 3 ];
1347
+ elemNormals = quadArray;
1043
1348
 
1044
- let code = null;
1349
+ }
1045
1350
 
1046
- // Triangle and line colours
1047
- let colour = 0xFF00FF;
1048
- let edgeColour = 0xFF00FF;
1351
+ for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
1049
1352
 
1050
- // Transparency
1051
- let alpha = 1;
1052
- let isTransparent = false;
1053
- // Self-illumination:
1054
- let luminance = 0;
1353
+ // use face normal if a vertex normal is not provided
1354
+ let n = elem.faceNormal;
1355
+ if ( elemNormals[ j ] ) {
1055
1356
 
1056
- let finishType = FINISH_TYPE_DEFAULT;
1357
+ n = elemNormals[ j ].norm;
1057
1358
 
1058
- let edgeMaterial = null;
1359
+ }
1059
1360
 
1060
- const name = lineParser.getToken();
1061
- if ( ! name ) {
1361
+ const index = offset + j * 3;
1362
+ normals[ index + 0 ] = n.x;
1363
+ normals[ index + 1 ] = n.y;
1364
+ normals[ index + 2 ] = n.z;
1062
1365
 
1063
- throw 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.';
1366
+ }
1064
1367
 
1065
1368
  }
1066
1369
 
1067
- // Parse tag tokens and their parameters
1068
- let token = null;
1069
- while ( true ) {
1070
-
1071
- token = lineParser.getToken();
1370
+ if ( prevMaterial !== elem.material ) {
1072
1371
 
1073
- if ( ! token ) {
1372
+ if ( prevMaterial !== null ) {
1074
1373
 
1075
- break;
1374
+ bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
1076
1375
 
1077
1376
  }
1078
1377
 
1079
- switch ( token.toUpperCase() ) {
1080
-
1081
- case 'CODE':
1378
+ const material = elem.material;
1379
+ if ( elementSize === 3 ) {
1082
1380
 
1083
- code = lineParser.getToken();
1084
- break;
1381
+ materials.push( material );
1085
1382
 
1086
- case 'VALUE':
1383
+ } else if ( elementSize === 2 ) {
1087
1384
 
1088
- colour = lineParser.getToken();
1089
- if ( colour.startsWith( '0x' ) ) {
1385
+ if ( isConditionalSegments ) {
1090
1386
 
1091
- colour = '#' + colour.substring( 2 );
1387
+ materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial );
1092
1388
 
1093
- } else if ( ! colour.startsWith( '#' ) ) {
1389
+ } else {
1094
1390
 
1095
- throw 'LDrawLoader: Invalid colour while parsing material' + lineParser.getLineNumberString() + '.';
1391
+ materials.push( material.userData.edgeMaterial );
1096
1392
 
1097
- }
1393
+ }
1098
1394
 
1099
- break;
1395
+ }
1100
1396
 
1101
- case 'EDGE':
1397
+ prevMaterial = elem.material;
1398
+ index0 = offset / 3;
1399
+ numGroupVerts = vertices.length;
1102
1400
 
1103
- edgeColour = lineParser.getToken();
1104
- if ( edgeColour.startsWith( '0x' ) ) {
1401
+ } else {
1105
1402
 
1106
- edgeColour = '#' + edgeColour.substring( 2 );
1403
+ numGroupVerts += vertices.length;
1107
1404
 
1108
- } else if ( ! edgeColour.startsWith( '#' ) ) {
1405
+ }
1109
1406
 
1110
- // Try to see if edge colour is a colour code
1111
- edgeMaterial = this.getMaterial( edgeColour );
1112
- if ( ! edgeMaterial ) {
1407
+ offset += 3 * vertices.length;
1113
1408
 
1114
- throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + '.';
1409
+ }
1115
1410
 
1116
- }
1411
+ if ( numGroupVerts > 0 ) {
1117
1412
 
1118
- // Get the edge material for this triangle material
1119
- edgeMaterial = edgeMaterial.userData.edgeMaterial;
1413
+ bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
1120
1414
 
1121
- }
1415
+ }
1122
1416
 
1123
- break;
1417
+ bufferGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
1124
1418
 
1125
- case 'ALPHA':
1419
+ if ( normals !== null ) {
1126
1420
 
1127
- alpha = parseInt( lineParser.getToken() );
1421
+ bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
1128
1422
 
1129
- if ( isNaN( alpha ) ) {
1423
+ }
1130
1424
 
1131
- throw 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.';
1425
+ let object3d = null;
1132
1426
 
1133
- }
1427
+ if ( elementSize === 2 ) {
1134
1428
 
1135
- alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
1429
+ object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
1136
1430
 
1137
- if ( alpha < 1 ) {
1431
+ } else if ( elementSize === 3 ) {
1138
1432
 
1139
- isTransparent = true;
1433
+ object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials );
1140
1434
 
1141
- }
1435
+ }
1142
1436
 
1143
- break;
1437
+ if ( isConditionalSegments ) {
1144
1438
 
1145
- case 'LUMINANCE':
1439
+ object3d.isConditionalLine = true;
1146
1440
 
1147
- luminance = parseInt( lineParser.getToken() );
1441
+ const controlArray0 = new Float32Array( elements.length * 3 * 2 );
1442
+ const controlArray1 = new Float32Array( elements.length * 3 * 2 );
1443
+ const directionArray = new Float32Array( elements.length * 3 * 2 );
1444
+ for ( let i = 0, l = elements.length; i < l; i ++ ) {
1148
1445
 
1149
- if ( isNaN( luminance ) ) {
1446
+ const os = elements[ i ];
1447
+ const vertices = os.vertices;
1448
+ const controlPoints = os.controlPoints;
1449
+ const c0 = controlPoints[ 0 ];
1450
+ const c1 = controlPoints[ 1 ];
1451
+ const v0 = vertices[ 0 ];
1452
+ const v1 = vertices[ 1 ];
1453
+ const index = i * 3 * 2;
1454
+ controlArray0[ index + 0 ] = c0.x;
1455
+ controlArray0[ index + 1 ] = c0.y;
1456
+ controlArray0[ index + 2 ] = c0.z;
1457
+ controlArray0[ index + 3 ] = c0.x;
1458
+ controlArray0[ index + 4 ] = c0.y;
1459
+ controlArray0[ index + 5 ] = c0.z;
1150
1460
 
1151
- throw 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.';
1461
+ controlArray1[ index + 0 ] = c1.x;
1462
+ controlArray1[ index + 1 ] = c1.y;
1463
+ controlArray1[ index + 2 ] = c1.z;
1464
+ controlArray1[ index + 3 ] = c1.x;
1465
+ controlArray1[ index + 4 ] = c1.y;
1466
+ controlArray1[ index + 5 ] = c1.z;
1152
1467
 
1153
- }
1468
+ directionArray[ index + 0 ] = v1.x - v0.x;
1469
+ directionArray[ index + 1 ] = v1.y - v0.y;
1470
+ directionArray[ index + 2 ] = v1.z - v0.z;
1471
+ directionArray[ index + 3 ] = v1.x - v0.x;
1472
+ directionArray[ index + 4 ] = v1.y - v0.y;
1473
+ directionArray[ index + 5 ] = v1.z - v0.z;
1154
1474
 
1155
- luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
1475
+ }
1156
1476
 
1157
- break;
1477
+ bufferGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) );
1478
+ bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) );
1479
+ bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) );
1158
1480
 
1159
- case 'CHROME':
1160
- finishType = FINISH_TYPE_CHROME;
1161
- break;
1481
+ }
1162
1482
 
1163
- case 'PEARLESCENT':
1164
- finishType = FINISH_TYPE_PEARLESCENT;
1165
- break;
1483
+ return object3d;
1166
1484
 
1167
- case 'RUBBER':
1168
- finishType = FINISH_TYPE_RUBBER;
1169
- break;
1485
+ }
1170
1486
 
1171
- case 'MATTE_METALLIC':
1172
- finishType = FINISH_TYPE_MATTE_METALLIC;
1173
- break;
1487
+ //
1174
1488
 
1175
- case 'METAL':
1176
- finishType = FINISH_TYPE_METAL;
1177
- break;
1489
+ const MAIN_COLOUR_CODE = '16';
1490
+ const MAIN_EDGE_COLOUR_CODE = '24';
1178
1491
 
1179
- case 'MATERIAL':
1180
- // Not implemented
1181
- lineParser.setToEnd();
1182
- break;
1492
+ class LDrawLoader extends Loader {
1183
1493
 
1184
- default:
1185
- throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.';
1186
- break;
1494
+ constructor( manager ) {
1187
1495
 
1188
- }
1496
+ super( manager );
1189
1497
 
1190
- }
1498
+ // Array of THREE.Material
1499
+ this.materials = [];
1191
1500
 
1192
- let material = null;
1501
+ // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
1502
+ // This also allows to handle the embedded text files ("0 FILE" lines)
1503
+ this.parseCache = new LDrawParsedCache( this );
1193
1504
 
1194
- switch ( finishType ) {
1505
+ // 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.
1506
+ this.fileMap = {};
1195
1507
 
1196
- case FINISH_TYPE_DEFAULT:
1508
+ this.rootParseScope = this.newParseScopeLevel();
1197
1509
 
1198
- material = new MeshStandardMaterial( { color: colour, roughness: 0.3, metalness: 0 } );
1199
- break;
1510
+ // Add default main triangle and line edge materials (used in pieces that can be colored with a main color)
1511
+ this.setMaterials( [
1512
+ this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ),
1513
+ this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) )
1514
+ ] );
1200
1515
 
1201
- case FINISH_TYPE_PEARLESCENT:
1516
+ // If this flag is set to true the vertex normals will be smoothed.
1517
+ this.smoothNormals = true;
1202
1518
 
1203
- // Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
1204
- const specular = new Color( colour );
1205
- const hsl = specular.getHSL( { h: 0, s: 0, l: 0 } );
1206
- hsl.h = ( hsl.h + 0.5 ) % 1;
1207
- hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
1208
- specular.setHSL( hsl.h, hsl.s, hsl.l );
1519
+ // The path to load parts from the LDraw parts library from.
1520
+ this.partsLibraryPath = '';
1209
1521
 
1210
- material = new MeshPhongMaterial( { color: colour, specular: specular, shininess: 10, reflectivity: 0.3 } );
1211
- break;
1522
+ }
1212
1523
 
1213
- case FINISH_TYPE_CHROME:
1524
+ setPartsLibraryPath( path ) {
1214
1525
 
1215
- // Mirror finish surface
1216
- material = new MeshStandardMaterial( { color: colour, roughness: 0, metalness: 1 } );
1217
- break;
1526
+ this.partsLibraryPath = path;
1527
+ return this;
1218
1528
 
1219
- case FINISH_TYPE_RUBBER:
1529
+ }
1220
1530
 
1221
- // Rubber finish
1222
- material = new MeshStandardMaterial( { color: colour, roughness: 0.9, metalness: 0 } );
1223
- break;
1531
+ async preloadMaterials( url ) {
1224
1532
 
1225
- case FINISH_TYPE_MATTE_METALLIC:
1533
+ const fileLoader = new FileLoader( this.manager );
1534
+ fileLoader.setPath( this.path );
1535
+ fileLoader.setRequestHeader( this.requestHeader );
1536
+ fileLoader.setWithCredentials( this.withCredentials );
1226
1537
 
1227
- // Brushed metal finish
1228
- material = new MeshStandardMaterial( { color: colour, roughness: 0.8, metalness: 0.4 } );
1229
- break;
1538
+ const text = await fileLoader.loadAsync( url );
1539
+ const colorLineRegex = /^0 !COLOUR/;
1540
+ const lines = text.split( /[\n\r]/g );
1541
+ const materials = [];
1542
+ for ( let i = 0, l = lines.length; i < l; i ++ ) {
1230
1543
 
1231
- case FINISH_TYPE_METAL:
1544
+ const line = lines[ i ];
1545
+ if ( colorLineRegex.test( line ) ) {
1232
1546
 
1233
- // Average metal finish
1234
- material = new MeshStandardMaterial( { color: colour, roughness: 0.2, metalness: 0.85 } );
1235
- break;
1547
+ const directive = line.replace( colorLineRegex, '' );
1548
+ const material = this.parseColorMetaDirective( new LineParser( directive ) );
1549
+ materials.push( material );
1236
1550
 
1237
- default:
1238
- // Should not happen
1239
- break;
1551
+ }
1240
1552
 
1241
1553
  }
1242
1554
 
1243
- material.transparent = isTransparent;
1244
- material.premultipliedAlpha = true;
1245
- material.opacity = alpha;
1246
- material.depthWrite = ! isTransparent;
1247
-
1248
- material.polygonOffset = true;
1249
- material.polygonOffsetFactor = 1;
1555
+ this.setMaterials( materials );
1250
1556
 
1251
- if ( luminance !== 0 ) {
1557
+ }
1252
1558
 
1253
- material.emissive.set( material.color ).multiplyScalar( luminance );
1559
+ load( url, onLoad, onProgress, onError ) {
1254
1560
 
1255
- }
1561
+ const fileLoader = new FileLoader( this.manager );
1562
+ fileLoader.setPath( this.path );
1563
+ fileLoader.setRequestHeader( this.requestHeader );
1564
+ fileLoader.setWithCredentials( this.withCredentials );
1565
+ fileLoader.load( url, text => {
1256
1566
 
1257
- if ( ! edgeMaterial ) {
1567
+ const parsedInfo = this.parseCache.parse( text );
1568
+ this.processObject( parsedInfo, null, url, this.rootParseScope )
1569
+ .then( function ( result ) {
1258
1570
 
1259
- // This is the material used for edges
1260
- edgeMaterial = new LineBasicMaterial( {
1261
- color: edgeColour,
1262
- transparent: isTransparent,
1263
- opacity: alpha,
1264
- depthWrite: ! isTransparent
1265
- } );
1266
- edgeMaterial.userData.code = code;
1267
- edgeMaterial.name = name + ' - Edge';
1571
+ onLoad( result.groupObject );
1268
1572
 
1269
- // This is the material used for conditional edges
1270
- edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( {
1573
+ } );
1271
1574
 
1272
- fog: true,
1273
- transparent: isTransparent,
1274
- depthWrite: ! isTransparent,
1275
- color: edgeColour,
1276
- opacity: alpha,
1575
+ }, onProgress, onError );
1277
1576
 
1278
- } );
1577
+ }
1279
1578
 
1280
- }
1579
+ parse( text, path, onLoad ) {
1281
1580
 
1282
- material.userData.code = code;
1283
- material.name = name;
1581
+ // Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
1582
+ const parsedInfo = this.parseCache.parse( text );
1583
+ this.processObject( parsedInfo, null, path, this.rootParseScope )
1584
+ .then( function ( result ) {
1284
1585
 
1285
- material.userData.edgeMaterial = edgeMaterial;
1586
+ onLoad( result.groupObject );
1286
1587
 
1287
- return material;
1588
+ } );
1288
1589
 
1289
1590
  }
1290
1591
 
1291
- //
1592
+ setMaterials( materials ) {
1292
1593
 
1293
- objectParse( text, parseScope ) {
1594
+ // Clears parse scopes stack, adds new scope with material library
1595
+ this.rootParseScope = this.newParseScopeLevel( materials );
1596
+ this.rootParseScope.isFromParse = false;
1294
1597
 
1295
- // Retrieve data from the parent parse scope
1296
- const currentParseScope = parseScope;
1297
- const parentParseScope = currentParseScope.parentScope;
1598
+ this.materials = materials;
1298
1599
 
1299
- // Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
1300
- const mainColourCode = currentParseScope.mainColourCode;
1301
- const mainEdgeColourCode = currentParseScope.mainEdgeColourCode;
1600
+ return this;
1302
1601
 
1602
+ }
1303
1603
 
1304
- // Parse result variables
1305
- let faces;
1306
- let lineSegments;
1307
- let conditionalSegments;
1604
+ setFileMap( fileMap ) {
1308
1605
 
1309
- const subobjects = [];
1606
+ this.fileMap = fileMap;
1310
1607
 
1311
- let category = null;
1312
- let keywords = null;
1608
+ return this;
1313
1609
 
1314
- if ( text.indexOf( '\r\n' ) !== - 1 ) {
1610
+ }
1315
1611
 
1316
- // This is faster than String.split with regex that splits on both
1317
- text = text.replace( /\r\n/g, '\n' );
1612
+ newParseScopeLevel( materials = null, parentScope = null ) {
1318
1613
 
1319
- }
1614
+ // Adds a new scope level, assign materials to it and returns it
1320
1615
 
1321
- const lines = text.split( '\n' );
1322
- const numLines = lines.length;
1616
+ const matLib = {};
1323
1617
 
1324
- let parsingEmbeddedFiles = false;
1325
- let currentEmbeddedFileName = null;
1326
- let currentEmbeddedText = null;
1618
+ if ( materials ) {
1327
1619
 
1328
- let bfcCertified = false;
1329
- let bfcCCW = true;
1330
- let bfcInverted = false;
1331
- let bfcCull = true;
1332
- let type = '';
1620
+ for ( let i = 0, n = materials.length; i < n; i ++ ) {
1333
1621
 
1334
- let startingConstructionStep = false;
1622
+ const material = materials[ i ];
1623
+ matLib[ material.userData.code ] = material;
1335
1624
 
1336
- const scope = this;
1337
- function parseColourCode( lineParser, forEdge ) {
1625
+ }
1338
1626
 
1339
- // Parses next colour code and returns a THREE.Material
1627
+ }
1340
1628
 
1341
- let colourCode = lineParser.getToken();
1629
+ const newParseScope = {
1342
1630
 
1343
- if ( ! forEdge && colourCode === '16' ) {
1631
+ parentScope: parentScope,
1632
+ lib: matLib,
1633
+ url: null,
1344
1634
 
1345
- colourCode = mainColourCode;
1635
+ // Subobjects
1636
+ subobjects: null,
1637
+ inverted: false,
1638
+ category: null,
1639
+ keywords: null,
1346
1640
 
1347
- }
1641
+ // Current subobject
1642
+ currentFileName: null,
1643
+ mainColorCode: parentScope ? parentScope.mainColorCode : MAIN_COLOUR_CODE,
1644
+ mainEdgeColorCode: parentScope ? parentScope.mainEdgeColorCode : MAIN_EDGE_COLOUR_CODE,
1645
+ matrix: new Matrix4(),
1646
+ type: 'Model',
1647
+ groupObject: null,
1348
1648
 
1349
- if ( forEdge && colourCode === '24' ) {
1649
+ // If false, it is a root material scope previous to parse
1650
+ isFromParse: true,
1350
1651
 
1351
- colourCode = mainEdgeColourCode;
1652
+ faces: [],
1653
+ lineSegments: [],
1654
+ conditionalSegments: [],
1655
+ totalFaces: 0,
1656
+ faceMaterials: new Set(),
1352
1657
 
1353
- }
1658
+ // If true, this object is the start of a construction step
1659
+ startingConstructionStep: false
1660
+ };
1354
1661
 
1355
- const material = scope.getMaterial( colourCode, currentParseScope );
1662
+ return newParseScope;
1356
1663
 
1357
- if ( ! material ) {
1664
+ }
1358
1665
 
1359
- throw 'LDrawLoader: Unknown colour code "' + colourCode + '" is used' + lineParser.getLineNumberString() + ' but it was not defined previously.';
1666
+ addMaterial( material, parseScope ) {
1360
1667
 
1361
- }
1668
+ // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
1362
1669
 
1363
- return material;
1670
+ const matLib = parseScope.lib;
1364
1671
 
1365
- }
1672
+ if ( ! matLib[ material.userData.code ] ) {
1366
1673
 
1367
- function parseVector( lp ) {
1674
+ this.materials.push( material );
1368
1675
 
1369
- const v = new Vector3( parseFloat( lp.getToken() ), parseFloat( lp.getToken() ), parseFloat( lp.getToken() ) );
1676
+ }
1370
1677
 
1371
- if ( ! scope.separateObjects ) {
1678
+ matLib[ material.userData.code ] = material;
1372
1679
 
1373
- v.applyMatrix4( currentParseScope.currentMatrix );
1680
+ return this;
1374
1681
 
1375
- }
1682
+ }
1376
1683
 
1377
- return v;
1684
+ getMaterial( colorCode, parseScope = this.rootParseScope ) {
1378
1685
 
1379
- }
1686
+ // Given a color code search its material in the parse scopes stack
1380
1687
 
1381
- // Parse all line commands
1382
- for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
1688
+ if ( colorCode.startsWith( '0x2' ) ) {
1383
1689
 
1384
- const line = lines[ lineIndex ];
1690
+ // Special 'direct' material value (RGB color)
1385
1691
 
1386
- if ( line.length === 0 ) continue;
1692
+ const color = colorCode.substring( 3 );
1387
1693
 
1388
- if ( parsingEmbeddedFiles ) {
1694
+ return this.parseColorMetaDirective( new LineParser( 'Direct_Color_' + color + ' CODE -1 VALUE #' + color + ' EDGE #' + color + '' ) );
1389
1695
 
1390
- if ( line.startsWith( '0 FILE ' ) ) {
1696
+ }
1391
1697
 
1392
- // Save previous embedded file in the cache
1393
- this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
1698
+ while ( parseScope ) {
1394
1699
 
1395
- // New embedded text file
1396
- currentEmbeddedFileName = line.substring( 7 );
1397
- currentEmbeddedText = '';
1700
+ const material = parseScope.lib[ colorCode ];
1398
1701
 
1399
- } else {
1702
+ if ( material ) {
1400
1703
 
1401
- currentEmbeddedText += line + '\n';
1704
+ return material;
1402
1705
 
1403
- }
1706
+ } else {
1404
1707
 
1405
- continue;
1708
+ parseScope = parseScope.parentScope;
1406
1709
 
1407
1710
  }
1408
1711
 
1409
- const lp = new LineParser( line, lineIndex + 1 );
1712
+ }
1410
1713
 
1411
- lp.seekNonSpace();
1714
+ // Material was not found
1715
+ return null;
1412
1716
 
1413
- if ( lp.isAtTheEnd() ) {
1717
+ }
1414
1718
 
1415
- // Empty line
1416
- continue;
1719
+ getMainMaterial() {
1417
1720
 
1418
- }
1721
+ return this.getMaterial( MAIN_COLOUR_CODE );
1722
+ }
1419
1723
 
1420
- // Parse the line type
1421
- const lineType = lp.getToken();
1724
+ getMainEdgeMaterial() {
1422
1725
 
1423
- let material;
1424
- let segment;
1425
- let inverted;
1426
- let ccw;
1427
- let doubleSided;
1428
- let v0, v1, v2, v3, c0, c1, faceNormal;
1726
+ return this.getMaterial( MAIN_EDGE_COLOUR_CODE );;
1727
+ }
1429
1728
 
1430
- switch ( lineType ) {
1729
+ parseColorMetaDirective( lineParser ) {
1431
1730
 
1432
- // Line type 0: Comment or META
1433
- case '0':
1731
+ // Parses a color definition and returns a THREE.Material
1434
1732
 
1435
- // Parse meta directive
1436
- const meta = lp.getToken();
1733
+ let code = null;
1437
1734
 
1438
- if ( meta ) {
1735
+ // Triangle and line colors
1736
+ let color = 0xFF00FF;
1737
+ let edgeColor = 0xFF00FF;
1439
1738
 
1440
- switch ( meta ) {
1739
+ // Transparency
1740
+ let alpha = 1;
1741
+ let isTransparent = false;
1742
+ // Self-illumination:
1743
+ let luminance = 0;
1441
1744
 
1442
- case '!LDRAW_ORG':
1745
+ let finishType = FINISH_TYPE_DEFAULT;
1443
1746
 
1444
- type = lp.getToken();
1747
+ let edgeMaterial = null;
1445
1748
 
1446
- currentParseScope.faces = [];
1447
- currentParseScope.lineSegments = [];
1448
- currentParseScope.conditionalSegments = [];
1449
- currentParseScope.type = type;
1749
+ const name = lineParser.getToken();
1750
+ if ( ! name ) {
1450
1751
 
1451
- const isRoot = ! parentParseScope.isFromParse;
1452
- if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
1752
+ throw new Error( 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.' );
1453
1753
 
1454
- currentParseScope.groupObject = new Group();
1754
+ }
1455
1755
 
1456
- currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
1756
+ // Parse tag tokens and their parameters
1757
+ let token = null;
1758
+ while ( true ) {
1457
1759
 
1458
- }
1760
+ token = lineParser.getToken();
1459
1761
 
1460
- // If the scale of the object is negated then the triangle winding order
1461
- // needs to be flipped.
1462
- if (
1463
- currentParseScope.matrix.determinant() < 0 && (
1464
- scope.separateObjects && isPrimitiveType( type ) ||
1465
- ! scope.separateObjects
1466
- ) ) {
1762
+ if ( ! token ) {
1467
1763
 
1468
- currentParseScope.inverted = ! currentParseScope.inverted;
1764
+ break;
1469
1765
 
1470
- }
1766
+ }
1471
1767
 
1472
- faces = currentParseScope.faces;
1473
- lineSegments = currentParseScope.lineSegments;
1474
- conditionalSegments = currentParseScope.conditionalSegments;
1768
+ switch ( token.toUpperCase() ) {
1475
1769
 
1476
- break;
1770
+ case 'CODE':
1477
1771
 
1478
- case '!COLOUR':
1772
+ code = lineParser.getToken();
1773
+ break;
1479
1774
 
1480
- material = this.parseColourMetaDirective( lp );
1481
- if ( material ) {
1775
+ case 'VALUE':
1482
1776
 
1483
- this.addMaterial( material, parseScope );
1777
+ color = lineParser.getToken();
1778
+ if ( color.startsWith( '0x' ) ) {
1484
1779
 
1485
- } else {
1780
+ color = '#' + color.substring( 2 );
1486
1781
 
1487
- console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
1782
+ } else if ( ! color.startsWith( '#' ) ) {
1488
1783
 
1489
- }
1784
+ throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
1490
1785
 
1491
- break;
1786
+ }
1492
1787
 
1493
- case '!CATEGORY':
1788
+ break;
1494
1789
 
1495
- category = lp.getToken();
1496
- break;
1790
+ case 'EDGE':
1497
1791
 
1498
- case '!KEYWORDS':
1792
+ edgeColor = lineParser.getToken();
1793
+ if ( edgeColor.startsWith( '0x' ) ) {
1499
1794
 
1500
- const newKeywords = lp.getRemainingString().split( ',' );
1501
- if ( newKeywords.length > 0 ) {
1795
+ edgeColor = '#' + edgeColor.substring( 2 );
1502
1796
 
1503
- if ( ! keywords ) {
1797
+ } else if ( ! edgeColor.startsWith( '#' ) ) {
1504
1798
 
1505
- keywords = [];
1799
+ // Try to see if edge color is a color code
1800
+ edgeMaterial = this.getMaterial( edgeColor );
1801
+ if ( ! edgeMaterial ) {
1506
1802
 
1507
- }
1803
+ throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
1508
1804
 
1509
- newKeywords.forEach( function ( keyword ) {
1805
+ }
1510
1806
 
1511
- keywords.push( keyword.trim() );
1807
+ // Get the edge material for this triangle material
1808
+ edgeMaterial = edgeMaterial.userData.edgeMaterial;
1512
1809
 
1513
- } );
1810
+ }
1514
1811
 
1515
- }
1812
+ break;
1516
1813
 
1517
- break;
1814
+ case 'ALPHA':
1518
1815
 
1519
- case 'FILE':
1816
+ alpha = parseInt( lineParser.getToken() );
1520
1817
 
1521
- if ( lineIndex > 0 ) {
1818
+ if ( isNaN( alpha ) ) {
1522
1819
 
1523
- // Start embedded text files parsing
1524
- parsingEmbeddedFiles = true;
1525
- currentEmbeddedFileName = lp.getRemainingString();
1526
- currentEmbeddedText = '';
1820
+ throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
1527
1821
 
1528
- bfcCertified = false;
1529
- bfcCCW = true;
1822
+ }
1530
1823
 
1531
- }
1824
+ alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
1532
1825
 
1533
- break;
1826
+ if ( alpha < 1 ) {
1827
+
1828
+ isTransparent = true;
1534
1829
 
1535
- case 'BFC':
1830
+ }
1536
1831
 
1537
- // Changes to the backface culling state
1538
- while ( ! lp.isAtTheEnd() ) {
1832
+ break;
1539
1833
 
1540
- const token = lp.getToken();
1834
+ case 'LUMINANCE':
1541
1835
 
1542
- switch ( token ) {
1836
+ luminance = parseInt( lineParser.getToken() );
1543
1837
 
1544
- case 'CERTIFY':
1545
- case 'NOCERTIFY':
1838
+ if ( isNaN( luminance ) ) {
1546
1839
 
1547
- bfcCertified = token === 'CERTIFY';
1548
- bfcCCW = true;
1840
+ throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
1549
1841
 
1550
- break;
1842
+ }
1551
1843
 
1552
- case 'CW':
1553
- case 'CCW':
1844
+ luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
1554
1845
 
1555
- bfcCCW = token === 'CCW';
1846
+ break;
1556
1847
 
1557
- break;
1848
+ case 'CHROME':
1849
+ finishType = FINISH_TYPE_CHROME;
1850
+ break;
1558
1851
 
1559
- case 'INVERTNEXT':
1852
+ case 'PEARLESCENT':
1853
+ finishType = FINISH_TYPE_PEARLESCENT;
1854
+ break;
1560
1855
 
1561
- bfcInverted = true;
1856
+ case 'RUBBER':
1857
+ finishType = FINISH_TYPE_RUBBER;
1858
+ break;
1562
1859
 
1563
- break;
1860
+ case 'MATTE_METALLIC':
1861
+ finishType = FINISH_TYPE_MATTE_METALLIC;
1862
+ break;
1564
1863
 
1565
- case 'CLIP':
1566
- case 'NOCLIP':
1864
+ case 'METAL':
1865
+ finishType = FINISH_TYPE_METAL;
1866
+ break;
1567
1867
 
1568
- bfcCull = token === 'CLIP';
1868
+ case 'MATERIAL':
1869
+ // Not implemented
1870
+ lineParser.setToEnd();
1871
+ break;
1569
1872
 
1570
- break;
1873
+ default:
1874
+ throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
1571
1875
 
1572
- default:
1876
+ }
1573
1877
 
1574
- console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
1878
+ }
1575
1879
 
1576
- break;
1880
+ let material = null;
1577
1881
 
1578
- }
1882
+ switch ( finishType ) {
1579
1883
 
1580
- }
1884
+ case FINISH_TYPE_DEFAULT:
1581
1885
 
1582
- break;
1886
+ material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0 } );
1887
+ break;
1583
1888
 
1584
- case 'STEP':
1889
+ case FINISH_TYPE_PEARLESCENT:
1585
1890
 
1586
- startingConstructionStep = true;
1891
+ // Try to imitate pearlescency by making the surface glossy
1892
+ material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0.25 } );
1893
+ break;
1587
1894
 
1588
- break;
1895
+ case FINISH_TYPE_CHROME:
1589
1896
 
1590
- default:
1591
- // Other meta directives are not implemented
1592
- break;
1897
+ // Mirror finish surface
1898
+ material = new MeshStandardMaterial( { color: color, roughness: 0, metalness: 1 } );
1899
+ break;
1593
1900
 
1594
- }
1901
+ case FINISH_TYPE_RUBBER:
1595
1902
 
1596
- }
1903
+ // Rubber finish
1904
+ material = new MeshStandardMaterial( { color: color, roughness: 0.9, metalness: 0 } );
1905
+ break;
1597
1906
 
1598
- break;
1907
+ case FINISH_TYPE_MATTE_METALLIC:
1599
1908
 
1600
- // Line type 1: Sub-object file
1601
- case '1':
1909
+ // Brushed metal finish
1910
+ material = new MeshStandardMaterial( { color: color, roughness: 0.8, metalness: 0.4 } );
1911
+ break;
1602
1912
 
1603
- material = parseColourCode( lp );
1913
+ case FINISH_TYPE_METAL:
1604
1914
 
1605
- const posX = parseFloat( lp.getToken() );
1606
- const posY = parseFloat( lp.getToken() );
1607
- const posZ = parseFloat( lp.getToken() );
1608
- const m0 = parseFloat( lp.getToken() );
1609
- const m1 = parseFloat( lp.getToken() );
1610
- const m2 = parseFloat( lp.getToken() );
1611
- const m3 = parseFloat( lp.getToken() );
1612
- const m4 = parseFloat( lp.getToken() );
1613
- const m5 = parseFloat( lp.getToken() );
1614
- const m6 = parseFloat( lp.getToken() );
1615
- const m7 = parseFloat( lp.getToken() );
1616
- const m8 = parseFloat( lp.getToken() );
1915
+ // Average metal finish
1916
+ material = new MeshStandardMaterial( { color: color, roughness: 0.2, metalness: 0.85 } );
1917
+ break;
1617
1918
 
1618
- const matrix = new Matrix4().set(
1619
- m0, m1, m2, posX,
1620
- m3, m4, m5, posY,
1621
- m6, m7, m8, posZ,
1622
- 0, 0, 0, 1
1623
- );
1919
+ default:
1920
+ // Should not happen
1921
+ break;
1624
1922
 
1625
- let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
1923
+ }
1626
1924
 
1627
- if ( scope.fileMap[ fileName ] ) {
1925
+ material.transparent = isTransparent;
1926
+ material.premultipliedAlpha = true;
1927
+ material.opacity = alpha;
1928
+ material.depthWrite = ! isTransparent;
1628
1929
 
1629
- // Found the subobject path in the preloaded file path map
1630
- fileName = scope.fileMap[ fileName ];
1930
+ material.polygonOffset = true;
1931
+ material.polygonOffsetFactor = 1;
1631
1932
 
1632
- } else {
1933
+ if ( luminance !== 0 ) {
1633
1934
 
1634
- // Standardized subfolders
1635
- if ( fileName.startsWith( 's/' ) ) {
1935
+ material.emissive.set( material.color ).multiplyScalar( luminance );
1636
1936
 
1637
- fileName = 'parts/' + fileName;
1937
+ }
1638
1938
 
1639
- } else if ( fileName.startsWith( '48/' ) ) {
1939
+ if ( ! edgeMaterial ) {
1640
1940
 
1641
- fileName = 'p/' + fileName;
1941
+ // This is the material used for edges
1942
+ edgeMaterial = new LineBasicMaterial( {
1943
+ color: edgeColor,
1944
+ transparent: isTransparent,
1945
+ opacity: alpha,
1946
+ depthWrite: ! isTransparent
1947
+ } );
1948
+ edgeMaterial.userData.code = code;
1949
+ edgeMaterial.name = name + ' - Edge';
1642
1950
 
1643
- }
1951
+ // This is the material used for conditional edges
1952
+ edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( {
1644
1953
 
1645
- }
1954
+ fog: true,
1955
+ transparent: isTransparent,
1956
+ depthWrite: ! isTransparent,
1957
+ color: edgeColor,
1958
+ opacity: alpha,
1646
1959
 
1647
- subobjects.push( {
1648
- material: material,
1649
- matrix: matrix,
1650
- fileName: fileName,
1651
- inverted: bfcInverted !== currentParseScope.inverted,
1652
- startingConstructionStep: startingConstructionStep
1653
- } );
1960
+ } );
1654
1961
 
1655
- bfcInverted = false;
1962
+ }
1656
1963
 
1657
- break;
1964
+ material.userData.code = code;
1965
+ material.name = name;
1658
1966
 
1659
- // Line type 2: Line segment
1660
- case '2':
1967
+ material.userData.edgeMaterial = edgeMaterial;
1661
1968
 
1662
- material = parseColourCode( lp, true );
1663
- v0 = parseVector( lp );
1664
- v1 = parseVector( lp );
1969
+ return material;
1665
1970
 
1666
- segment = {
1667
- material: material.userData.edgeMaterial,
1668
- colourCode: material.userData.code,
1669
- v0: v0,
1670
- v1: v1,
1971
+ }
1671
1972
 
1672
- vertices: [ v0, v1 ],
1673
- };
1973
+ //
1674
1974
 
1675
- lineSegments.push( segment );
1975
+ objectParse( info, parseScope ) {
1676
1976
 
1677
- break;
1977
+ // Retrieve data from the parent parse scope
1978
+ const currentParseScope = parseScope;
1979
+ const parentParseScope = currentParseScope.parentScope;
1678
1980
 
1679
- // Line type 5: Conditional Line segment
1680
- case '5':
1981
+ // Main color codes passed to this subobject (or default codes 16 and 24 if it is the root object)
1982
+ const mainColorCode = currentParseScope.mainColorCode;
1983
+ const mainEdgeColorCode = currentParseScope.mainEdgeColorCode;
1681
1984
 
1682
- material = parseColourCode( lp, true );
1683
- v0 = parseVector( lp );
1684
- v1 = parseVector( lp );
1685
- c0 = parseVector( lp );
1686
- c1 = parseVector( lp );
1985
+ const parseColorCode = ( colorCode, forEdge ) => {
1687
1986
 
1688
- segment = {
1689
- material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
1690
- colourCode: material.userData.code,
1691
- vertices: [ v0, v1 ],
1692
- controlPoints: [ c0, c1 ],
1693
- };
1987
+ // Parses next color code and returns a THREE.Material
1694
1988
 
1695
- conditionalSegments.push( segment );
1989
+ if ( ! forEdge && colorCode === MAIN_COLOUR_CODE ) {
1696
1990
 
1697
- break;
1991
+ colorCode = mainColorCode;
1698
1992
 
1699
- // Line type 3: Triangle
1700
- case '3':
1993
+ }
1701
1994
 
1702
- material = parseColourCode( lp );
1995
+ if ( forEdge && colorCode === MAIN_EDGE_COLOUR_CODE ) {
1703
1996
 
1704
- inverted = currentParseScope.inverted;
1705
- ccw = bfcCCW !== inverted;
1706
- doubleSided = ! bfcCertified || ! bfcCull;
1997
+ colorCode = mainEdgeColorCode;
1707
1998
 
1708
- if ( ccw === true ) {
1999
+ }
1709
2000
 
1710
- v0 = parseVector( lp );
1711
- v1 = parseVector( lp );
1712
- v2 = parseVector( lp );
2001
+ const material = this.getMaterial( colorCode, currentParseScope );
1713
2002
 
1714
- } else {
2003
+ if ( ! material ) {
1715
2004
 
1716
- v2 = parseVector( lp );
1717
- v1 = parseVector( lp );
1718
- v0 = parseVector( lp );
2005
+ throw new Error( 'LDrawLoader: Unknown color code "' + colorCode + '" is used but it was not defined previously.' );
1719
2006
 
1720
- }
2007
+ }
1721
2008
 
1722
- _tempVec0.subVectors( v1, v0 );
1723
- _tempVec1.subVectors( v2, v1 );
1724
- faceNormal = new Vector3()
1725
- .crossVectors( _tempVec0, _tempVec1 )
1726
- .normalize();
2009
+ return material;
1727
2010
 
1728
- faces.push( {
1729
- material: material,
1730
- colourCode: material.userData.code,
1731
- faceNormal: faceNormal,
1732
- vertices: [ v0, v1, v2 ],
1733
- normals: [ null, null, null ],
1734
- } );
1735
- currentParseScope.totalFaces ++;
2011
+ };
1736
2012
 
1737
- if ( doubleSided === true ) {
2013
+ const faces = info.faces;
2014
+ const lineSegments = info.lineSegments;
2015
+ const conditionalSegments = info.conditionalSegments;
2016
+ const materials = info.materials;
2017
+ if ( currentParseScope.inverted ) {
1738
2018
 
1739
- faces.push( {
1740
- material: material,
1741
- colourCode: material.userData.code,
1742
- faceNormal: faceNormal,
1743
- vertices: [ v2, v1, v0 ],
1744
- normals: [ null, null, null ],
1745
- } );
1746
- currentParseScope.totalFaces ++;
2019
+ faces.reverse();
1747
2020
 
1748
- }
2021
+ }
1749
2022
 
1750
- break;
2023
+ for ( const colorCode in materials ) {
1751
2024
 
1752
- // Line type 4: Quadrilateral
1753
- case '4':
2025
+ this.addMaterial( materials[ colorCode ], currentParseScope );
1754
2026
 
1755
- material = parseColourCode( lp );
2027
+ }
1756
2028
 
1757
- inverted = currentParseScope.inverted;
1758
- ccw = bfcCCW !== inverted;
1759
- doubleSided = ! bfcCertified || ! bfcCull;
2029
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
1760
2030
 
1761
- if ( ccw === true ) {
2031
+ const face = faces[ i ];
2032
+ if ( face.material === null ) {
1762
2033
 
1763
- v0 = parseVector( lp );
1764
- v1 = parseVector( lp );
1765
- v2 = parseVector( lp );
1766
- v3 = parseVector( lp );
2034
+ face.material = parseColorCode( face.colorCode, false );
1767
2035
 
1768
- } else {
2036
+ }
1769
2037
 
1770
- v3 = parseVector( lp );
1771
- v2 = parseVector( lp );
1772
- v1 = parseVector( lp );
1773
- v0 = parseVector( lp );
2038
+ }
1774
2039
 
1775
- }
2040
+ for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
1776
2041
 
1777
- _tempVec0.subVectors( v1, v0 );
1778
- _tempVec1.subVectors( v2, v1 );
1779
- faceNormal = new Vector3()
1780
- .crossVectors( _tempVec0, _tempVec1 )
1781
- .normalize();
2042
+ const ls = lineSegments[ i ];
2043
+ if ( ls.material === null ) {
1782
2044
 
1783
- // specifically place the triangle diagonal in the v0 and v1 slots so we can
1784
- // account for the doubling of vertices later when smoothing normals.
1785
- faces.push( {
1786
- material: material,
1787
- colourCode: material.userData.code,
1788
- faceNormal: faceNormal,
1789
- vertices: [ v0, v1, v2, v3 ],
1790
- normals: [ null, null, null, null ],
1791
- } );
1792
- currentParseScope.totalFaces += 2;
2045
+ ls.material = parseColorCode( ls.colorCode, true );
1793
2046
 
1794
- if ( doubleSided === true ) {
2047
+ }
1795
2048
 
1796
- faces.push( {
1797
- material: material,
1798
- colourCode: material.userData.code,
1799
- faceNormal: faceNormal,
1800
- vertices: [ v3, v2, v1, v0 ],
1801
- normals: [ null, null, null, null ],
1802
- } );
1803
- currentParseScope.totalFaces += 2;
2049
+ }
1804
2050
 
1805
- }
2051
+ for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
1806
2052
 
1807
- break;
2053
+ const cs = conditionalSegments[ i ];
2054
+ if ( cs.material === null ) {
1808
2055
 
1809
- default:
1810
- throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
1811
- break;
2056
+ cs.material = parseColorCode( cs.colorCode, true );
1812
2057
 
1813
2058
  }
1814
2059
 
1815
2060
  }
1816
2061
 
1817
- if ( parsingEmbeddedFiles ) {
2062
+ currentParseScope.faces = info.faces;
2063
+ currentParseScope.conditionalSegments = info.conditionalSegments;
2064
+ currentParseScope.lineSegments = info.lineSegments;
2065
+ currentParseScope.category = info.category;
2066
+ currentParseScope.keywords = info.keywords;
2067
+ currentParseScope.subobjects = info.subobjects;
2068
+ currentParseScope.type = info.type;
2069
+ currentParseScope.totalFaces = info.totalFaces;
2070
+
2071
+ const isRoot = ! parentParseScope.isFromParse;
2072
+ if ( isRoot || ! isPrimitiveType( info.type ) ) {
1818
2073
 
1819
- this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
2074
+ currentParseScope.groupObject = new Group();
2075
+ currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
1820
2076
 
1821
2077
  }
1822
2078
 
1823
- currentParseScope.category = category;
1824
- currentParseScope.keywords = keywords;
1825
- currentParseScope.subobjects = subobjects;
1826
- currentParseScope.numSubobjects = subobjects.length;
1827
- currentParseScope.subobjectIndex = 0;
1828
-
1829
2079
  }
1830
2080
 
1831
2081
  computeConstructionSteps( model ) {
@@ -1856,6 +2106,13 @@ class LDrawLoader extends Loader {
1856
2106
 
1857
2107
  finalizeObject( subobjectParseScope ) {
1858
2108
 
2109
+ // fail gracefully if an object could not be loaded
2110
+ if ( subobjectParseScope === null ) {
2111
+
2112
+ return;
2113
+
2114
+ }
2115
+
1859
2116
  const parentParseScope = subobjectParseScope.parentScope;
1860
2117
 
1861
2118
  // Smooth the normals if this is a part or if this is a case where the subpart
@@ -1864,19 +2121,23 @@ class LDrawLoader extends Loader {
1864
2121
  const doSmooth =
1865
2122
  isPartType( subobjectParseScope.type ) ||
1866
2123
  (
1867
- ! isPartType( subobjectParseScope.type ) &&
1868
- ! isModelType( subobjectParseScope.type ) &&
2124
+ isPrimitiveType( subobjectParseScope.type ) &&
1869
2125
  isModelType( subobjectParseScope.parentScope.type )
1870
2126
  );
1871
2127
 
1872
2128
  if ( this.smoothNormals && doSmooth ) {
1873
2129
 
1874
- smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments );
2130
+ generateFaceNormals( subobjectParseScope.faces );
2131
+
2132
+ // only check subsetgments if we have multiple materials in a single part because this seems to be the case where it's needed most --
2133
+ // there may be cases where a single edge line crosses over polygon edges that are broken up by multiple materials.
2134
+ const checkSubSegments = subobjectParseScope.faceMaterials.size > 1;
2135
+ smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments, checkSubSegments );
1875
2136
 
1876
2137
  }
1877
2138
 
1878
2139
  const isRoot = ! parentParseScope.isFromParse;
1879
- if ( this.separateObjects && ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
2140
+ if ( ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
1880
2141
 
1881
2142
  const objGroup = subobjectParseScope.groupObject;
1882
2143
 
@@ -1911,26 +2172,24 @@ class LDrawLoader extends Loader {
1911
2172
 
1912
2173
  } else {
1913
2174
 
1914
- const separateObjects = this.separateObjects;
1915
2175
  const parentLineSegments = parentParseScope.lineSegments;
1916
2176
  const parentConditionalSegments = parentParseScope.conditionalSegments;
1917
2177
  const parentFaces = parentParseScope.faces;
2178
+ const parentFaceMaterials = parentParseScope.faceMaterials;
1918
2179
 
1919
2180
  const lineSegments = subobjectParseScope.lineSegments;
1920
2181
  const conditionalSegments = subobjectParseScope.conditionalSegments;
1921
2182
  const faces = subobjectParseScope.faces;
2183
+ const faceMaterials = subobjectParseScope.faceMaterials;
2184
+ const matrix = subobjectParseScope.matrix;
2185
+ const matrixScaleInverted = matrix.determinant() < 0;
1922
2186
 
1923
2187
  for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
1924
2188
 
1925
2189
  const ls = lineSegments[ i ];
1926
-
1927
- if ( separateObjects ) {
1928
-
1929
- const vertices = ls.vertices;
1930
- vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1931
- vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1932
-
1933
- }
2190
+ const vertices = ls.vertices;
2191
+ vertices[ 0 ].applyMatrix4( matrix );
2192
+ vertices[ 1 ].applyMatrix4( matrix );
1934
2193
 
1935
2194
  parentLineSegments.push( ls );
1936
2195
 
@@ -1939,17 +2198,12 @@ class LDrawLoader extends Loader {
1939
2198
  for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
1940
2199
 
1941
2200
  const os = conditionalSegments[ i ];
1942
-
1943
- if ( separateObjects ) {
1944
-
1945
- const vertices = os.vertices;
1946
- const controlPoints = os.controlPoints;
1947
- vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1948
- vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1949
- controlPoints[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1950
- controlPoints[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1951
-
1952
- }
2201
+ const vertices = os.vertices;
2202
+ const controlPoints = os.controlPoints;
2203
+ vertices[ 0 ].applyMatrix4( matrix );
2204
+ vertices[ 1 ].applyMatrix4( matrix );
2205
+ controlPoints[ 0 ].applyMatrix4( matrix );
2206
+ controlPoints[ 1 ].applyMatrix4( matrix );
1953
2207
 
1954
2208
  parentConditionalSegments.push( os );
1955
2209
 
@@ -1958,19 +2212,18 @@ class LDrawLoader extends Loader {
1958
2212
  for ( let i = 0, l = faces.length; i < l; i ++ ) {
1959
2213
 
1960
2214
  const tri = faces[ i ];
2215
+ const vertices = tri.vertices;
2216
+ for ( let i = 0, l = vertices.length; i < l; i ++ ) {
1961
2217
 
1962
- if ( separateObjects ) {
1963
-
1964
- const vertices = tri.vertices;
1965
- for ( let i = 0, l = vertices.length; i < l; i ++ ) {
2218
+ vertices[ i ].applyMatrix4( matrix );
1966
2219
 
1967
- vertices[ i ] = vertices[ i ].clone().applyMatrix4( subobjectParseScope.matrix );
2220
+ }
1968
2221
 
1969
- }
2222
+ // If the scale of the object is negated then the triangle winding order
2223
+ // needs to be flipped.
2224
+ if ( matrixScaleInverted !== subobjectParseScope.inverted ) {
1970
2225
 
1971
- _tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
1972
- _tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
1973
- tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
2226
+ vertices.reverse();
1974
2227
 
1975
2228
  }
1976
2229
 
@@ -1979,41 +2232,48 @@ class LDrawLoader extends Loader {
1979
2232
  }
1980
2233
 
1981
2234
  parentParseScope.totalFaces += subobjectParseScope.totalFaces;
2235
+ faceMaterials.forEach( material => parentFaceMaterials.add( material ) );
1982
2236
 
1983
2237
  }
1984
2238
 
1985
2239
  }
1986
2240
 
1987
- async processObject( text, subobject, url, parentScope ) {
2241
+ async processObject( parsedInfo, subobject, url, parentScope ) {
1988
2242
 
1989
2243
  const scope = this;
1990
2244
 
1991
2245
  const parseScope = this.newParseScopeLevel( null, parentScope );
1992
2246
  parseScope.url = url;
1993
2247
 
1994
- const parentParseScope = parseScope.parentScope;
1995
-
1996
2248
  // Set current matrix
1997
2249
  if ( subobject ) {
1998
2250
 
1999
- parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
2000
2251
  parseScope.matrix.copy( subobject.matrix );
2001
2252
  parseScope.inverted = subobject.inverted;
2002
2253
  parseScope.startingConstructionStep = subobject.startingConstructionStep;
2003
- parseScope.mainColourCode = subobject.material.userData.code;
2004
- parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
2005
2254
  parseScope.fileName = subobject.fileName;
2255
+ if ( subobject.colorCode === MAIN_COLOUR_CODE && parentScope ) {
2256
+
2257
+ parseScope.mainColorCode = parentScope.mainColorCode;
2258
+ parseScope.mainEdgeColorCode = parentScope.mainEdgeColorCode;
2259
+
2260
+ } else if ( subobject.colorCode !== MAIN_COLOUR_CODE ) {
2261
+
2262
+ parseScope.mainColorCode = subobject.colorCode;
2263
+ parseScope.mainEdgeColorCode = subobject.colorCode;
2264
+
2265
+ }
2006
2266
 
2007
2267
  }
2008
2268
 
2009
2269
  // Parse the object
2010
- this.objectParse( text, parseScope );
2270
+ this.objectParse( parsedInfo, parseScope );
2011
2271
 
2012
2272
  const subobjects = parseScope.subobjects;
2013
2273
  const promises = [];
2014
2274
  for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
2015
2275
 
2016
- promises.push( loadSubobject( parseScope.subobjects[ i ] ) );
2276
+ promises.push( loadSubobject( subobjects[ i ] ) );
2017
2277
 
2018
2278
  }
2019
2279
 
@@ -2027,7 +2287,7 @@ class LDrawLoader extends Loader {
2027
2287
  }
2028
2288
 
2029
2289
  // If it is root object then finalize this object and compute construction steps
2030
- if ( ! parentParseScope.isFromParse ) {
2290
+ if ( ! parentScope.isFromParse ) {
2031
2291
 
2032
2292
  this.finalizeObject( parseScope );
2033
2293
  this.computeConstructionSteps( parseScope.groupObject );
@@ -2038,13 +2298,14 @@ class LDrawLoader extends Loader {
2038
2298
 
2039
2299
  function loadSubobject( subobject ) {
2040
2300
 
2041
- return scope.cache.loadData( subobject.fileName ).then( function ( text ) {
2301
+ return scope.parseCache.loadData( subobject.fileName ).then( function ( parsedInfo ) {
2042
2302
 
2043
- return scope.processObject( text, subobject, url, parseScope );
2303
+ return scope.processObject( parsedInfo, subobject, url, parseScope );
2044
2304
 
2045
- } ).catch( function () {
2305
+ } ).catch( function ( err ) {
2046
2306
 
2047
- console.warn( 'LDrawLoader: Subobject "' + subobject.fileName + '" could not be found.' );
2307
+ console.warn( err );
2308
+ return null;
2048
2309
 
2049
2310
  } );
2050
2311