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
@@ -142,16 +142,45 @@
142
142
 
143
143
  }
144
144
 
145
- function smoothNormals( faces, lineSegments ) {
145
+ function generateFaceNormals( faces ) {
146
+
147
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
148
+
149
+ const face = faces[ i ];
150
+ const vertices = face.vertices;
151
+ const v0 = vertices[ 0 ];
152
+ const v1 = vertices[ 1 ];
153
+ const v2 = vertices[ 2 ];
154
+
155
+ _tempVec0.subVectors( v1, v0 );
156
+
157
+ _tempVec1.subVectors( v2, v1 );
158
+
159
+ face.faceNormal = new THREE.Vector3().crossVectors( _tempVec0, _tempVec1 ).normalize();
160
+
161
+ }
162
+
163
+ }
164
+
165
+ const _ray = new THREE.Ray();
166
+
167
+ function smoothNormals( faces, lineSegments, checkSubSegments = false ) {
168
+
169
+ // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because
170
+ // it allows edges to be smoothed as expected (see minifig arms).
171
+ // --
172
+ // And the vector values are initialize multiplied by 1 + 1e-10 to account for floating
173
+ // point errors on vertices along quantization boundaries. Ie after matrix multiplication
174
+ // vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't
175
+ // get merged. This added epsilon attempts to push these error values to the same quantized
176
+ // value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169.
177
+ const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
146
178
 
147
179
  function hashVertex( v ) {
148
180
 
149
- // NOTE: 1e2 is pretty coarse but was chosen because it allows edges
150
- // to be smoothed as expected (see minifig arms). The errors between edges
151
- // could be due to matrix multiplication.
152
- const x = ~ ~ ( v.x * 1e2 );
153
- const y = ~ ~ ( v.y * 1e2 );
154
- const z = ~ ~ ( v.z * 1e2 );
181
+ const x = ~ ~ ( v.x * hashMultiplier );
182
+ const y = ~ ~ ( v.y * hashMultiplier );
183
+ const z = ~ ~ ( v.z * hashMultiplier );
155
184
  return `${x},${y},${z}`;
156
185
 
157
186
  }
@@ -160,9 +189,27 @@
160
189
 
161
190
  return `${hashVertex( v0 )}_${hashVertex( v1 )}`;
162
191
 
192
+ } // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected
193
+ // onto the original line.
194
+
195
+
196
+ function toNormalizedRay( v0, v1, targetRay ) {
197
+
198
+ targetRay.direction.subVectors( v1, v0 ).normalize();
199
+ const scalar = v0.dot( targetRay.direction );
200
+ targetRay.origin.copy( v0 ).addScaledVector( targetRay.direction, - scalar );
201
+ return targetRay;
202
+
203
+ }
204
+
205
+ function hashRay( ray ) {
206
+
207
+ return hashEdge( ray.origin, ray.direction );
208
+
163
209
  }
164
210
 
165
211
  const hardEdges = new Set();
212
+ const hardEdgeRays = new Map();
166
213
  const halfEdgeList = {};
167
214
  const normals = []; // Save the list of hard edges by hash
168
215
 
@@ -173,7 +220,43 @@
173
220
  const v0 = vertices[ 0 ];
174
221
  const v1 = vertices[ 1 ];
175
222
  hardEdges.add( hashEdge( v0, v1 ) );
176
- hardEdges.add( hashEdge( v1, v0 ) );
223
+ hardEdges.add( hashEdge( v1, v0 ) ); // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check
224
+ // and requires more memory.
225
+
226
+ if ( checkSubSegments ) {
227
+
228
+ // add both ray directions to the map
229
+ const ray = toNormalizedRay( v0, v1, new THREE.Ray() );
230
+ const rh1 = hashRay( ray );
231
+
232
+ if ( ! hardEdgeRays.has( rh1 ) ) {
233
+
234
+ toNormalizedRay( v1, v0, ray );
235
+ const rh2 = hashRay( ray );
236
+ const info = {
237
+ ray,
238
+ distances: []
239
+ };
240
+ hardEdgeRays.set( rh1, info );
241
+ hardEdgeRays.set( rh2, info );
242
+
243
+ } // store both segments ends in min, max order in the distances array to check if a face edge is a
244
+ // subsegment later.
245
+
246
+
247
+ const info = hardEdgeRays.get( rh1 );
248
+ let d0 = info.ray.direction.dot( v0 );
249
+ let d1 = info.ray.direction.dot( v1 );
250
+
251
+ if ( d0 > d1 ) {
252
+
253
+ [ d0, d1 ] = [ d1, d0 ];
254
+
255
+ }
256
+
257
+ info.distances.push( d0, d1 );
258
+
259
+ }
177
260
 
178
261
  } // track the half edges associated with each triangle
179
262
 
@@ -192,7 +275,58 @@
192
275
  const v1 = vertices[ next ];
193
276
  const hash = hashEdge( v0, v1 ); // don't add the triangle if the edge is supposed to be hard
194
277
 
195
- if ( hardEdges.has( hash ) ) continue;
278
+ if ( hardEdges.has( hash ) ) {
279
+
280
+ continue;
281
+
282
+ } // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds
283
+
284
+
285
+ if ( checkSubSegments ) {
286
+
287
+ toNormalizedRay( v0, v1, _ray );
288
+ const rayHash = hashRay( _ray );
289
+
290
+ if ( hardEdgeRays.has( rayHash ) ) {
291
+
292
+ const info = hardEdgeRays.get( rayHash );
293
+ const {
294
+ ray,
295
+ distances
296
+ } = info;
297
+ let d0 = ray.direction.dot( v0 );
298
+ let d1 = ray.direction.dot( v1 );
299
+
300
+ if ( d0 > d1 ) {
301
+
302
+ [ d0, d1 ] = [ d1, d0 ];
303
+
304
+ } // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals
305
+
306
+
307
+ let found = false;
308
+
309
+ for ( let i = 0, l = distances.length; i < l; i += 2 ) {
310
+
311
+ if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) {
312
+
313
+ found = true;
314
+ break;
315
+
316
+ }
317
+
318
+ }
319
+
320
+ if ( found ) {
321
+
322
+ continue;
323
+
324
+ }
325
+
326
+ }
327
+
328
+ }
329
+
196
330
  const info = {
197
331
  index: index,
198
332
  tri: tri
@@ -433,6 +567,12 @@
433
567
 
434
568
  }
435
569
 
570
+ getVector() {
571
+
572
+ return new THREE.Vector3( parseFloat( this.getToken() ), parseFloat( this.getToken() ), parseFloat( this.getToken() ) );
573
+
574
+ }
575
+
436
576
  getRemainingString() {
437
577
 
438
578
  return this.line.substring( this.currentCharIndex, this.lineLength );
@@ -459,253 +599,750 @@
459
599
 
460
600
  }
461
601
 
462
- class LDrawFileCache {
602
+ class LDrawParsedCache {
463
603
 
464
604
  constructor( loader ) {
465
605
 
466
- this.cache = {};
467
606
  this.loader = loader;
607
+ this.cache = {};
468
608
 
469
609
  }
470
610
 
471
- setData( key, contents ) {
611
+ cloneResult( original ) {
472
612
 
473
- this.cache[ key.toLowerCase() ] = contents;
613
+ const result = {}; // vertices are transformed and normals computed before being converted to geometry
614
+ // so these pieces must be cloned.
474
615
 
475
- }
616
+ result.faces = original.faces.map( face => {
476
617
 
477
- async loadData( fileName ) {
618
+ return {
619
+ colorCode: face.colorCode,
620
+ material: face.material,
621
+ vertices: face.vertices.map( v => v.clone() ),
622
+ normals: face.normals.map( () => null ),
623
+ faceNormal: null
624
+ };
478
625
 
479
- const key = fileName.toLowerCase();
626
+ } );
627
+ result.conditionalSegments = original.conditionalSegments.map( face => {
480
628
 
481
- if ( key in this.cache ) {
629
+ return {
630
+ colorCode: face.colorCode,
631
+ material: face.material,
632
+ vertices: face.vertices.map( v => v.clone() ),
633
+ controlPoints: face.controlPoints.map( v => v.clone() )
634
+ };
482
635
 
483
- return this.cache[ key ];
636
+ } );
637
+ result.lineSegments = original.lineSegments.map( face => {
484
638
 
485
- }
639
+ return {
640
+ colorCode: face.colorCode,
641
+ material: face.material,
642
+ vertices: face.vertices.map( v => v.clone() )
643
+ };
486
644
 
487
- this.cache[ fileName ] = new Promise( async ( resolve, reject ) => {
645
+ } ); // none if this is subsequently modified
488
646
 
489
- let triedLowerCase = false;
490
- let locationState = FILE_LOCATION_AS_IS;
647
+ result.type = original.type;
648
+ result.category = original.category;
649
+ result.keywords = original.keywords;
650
+ result.subobjects = original.subobjects;
651
+ result.totalFaces = original.totalFaces;
652
+ result.startingConstructionStep = original.startingConstructionStep;
653
+ result.materials = original.materials;
654
+ return result;
491
655
 
492
- while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
656
+ }
493
657
 
494
- let subobjectURL = fileName;
658
+ async fetchData( fileName ) {
495
659
 
496
- switch ( locationState ) {
660
+ let triedLowerCase = false;
661
+ let locationState = FILE_LOCATION_AS_IS;
497
662
 
498
- case FILE_LOCATION_AS_IS:
499
- locationState = locationState + 1;
500
- break;
663
+ while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
501
664
 
502
- case FILE_LOCATION_TRY_PARTS:
503
- subobjectURL = 'parts/' + subobjectURL;
504
- locationState = locationState + 1;
505
- break;
665
+ let subobjectURL = fileName;
506
666
 
507
- case FILE_LOCATION_TRY_P:
508
- subobjectURL = 'p/' + subobjectURL;
509
- locationState = locationState + 1;
510
- break;
667
+ switch ( locationState ) {
511
668
 
512
- case FILE_LOCATION_TRY_MODELS:
513
- subobjectURL = 'models/' + subobjectURL;
514
- locationState = locationState + 1;
515
- break;
669
+ case FILE_LOCATION_AS_IS:
670
+ locationState = locationState + 1;
671
+ break;
516
672
 
517
- case FILE_LOCATION_TRY_RELATIVE:
518
- subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
519
- locationState = locationState + 1;
520
- break;
673
+ case FILE_LOCATION_TRY_PARTS:
674
+ subobjectURL = 'parts/' + subobjectURL;
675
+ locationState = locationState + 1;
676
+ break;
521
677
 
522
- case FILE_LOCATION_TRY_ABSOLUTE:
523
- if ( triedLowerCase ) {
678
+ case FILE_LOCATION_TRY_P:
679
+ subobjectURL = 'p/' + subobjectURL;
680
+ locationState = locationState + 1;
681
+ break;
524
682
 
525
- // Try absolute path
526
- locationState = FILE_LOCATION_NOT_FOUND;
683
+ case FILE_LOCATION_TRY_MODELS:
684
+ subobjectURL = 'models/' + subobjectURL;
685
+ locationState = locationState + 1;
686
+ break;
527
687
 
528
- } else {
688
+ case FILE_LOCATION_TRY_RELATIVE:
689
+ subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
690
+ locationState = locationState + 1;
691
+ break;
529
692
 
530
- // Next attempt is lower case
531
- fileName = fileName.toLowerCase();
532
- subobjectURL = fileName;
533
- triedLowerCase = true;
534
- locationState = FILE_LOCATION_AS_IS;
693
+ case FILE_LOCATION_TRY_ABSOLUTE:
694
+ if ( triedLowerCase ) {
535
695
 
536
- }
696
+ // Try absolute path
697
+ locationState = FILE_LOCATION_NOT_FOUND;
537
698
 
538
- break;
699
+ } else {
539
700
 
540
- }
701
+ // Next attempt is lower case
702
+ fileName = fileName.toLowerCase();
703
+ subobjectURL = fileName;
704
+ triedLowerCase = true;
705
+ locationState = FILE_LOCATION_AS_IS;
706
+
707
+ }
541
708
 
542
- const loader = this.loader;
543
- const fileLoader = new THREE.FileLoader( loader.manager );
544
- fileLoader.setPath( loader.partsLibraryPath );
545
- fileLoader.setRequestHeader( loader.requestHeader );
546
- fileLoader.setWithCredentials( loader.withCredentials );
709
+ break;
710
+
711
+ }
547
712
 
548
- try {
713
+ const loader = this.loader;
714
+ const fileLoader = new THREE.FileLoader( loader.manager );
715
+ fileLoader.setPath( loader.partsLibraryPath );
716
+ fileLoader.setRequestHeader( loader.requestHeader );
717
+ fileLoader.setWithCredentials( loader.withCredentials );
549
718
 
550
- const text = await fileLoader.loadAsync( subobjectURL );
551
- this.setData( fileName, text );
552
- resolve( text );
553
- return;
719
+ try {
554
720
 
555
- } catch {
721
+ const text = await fileLoader.loadAsync( subobjectURL );
722
+ return text;
556
723
 
557
- continue;
724
+ } catch {
558
725
 
559
- }
726
+ continue;
560
727
 
561
728
  }
562
729
 
563
- reject();
730
+ }
564
731
 
565
- } );
566
- return this.cache[ fileName ];
732
+ throw new Error( 'LDrawLoader: Subobject "' + fileName + '" could not be loaded.' );
567
733
 
568
734
  }
569
735
 
570
- }
571
-
572
- function sortByMaterial( a, b ) {
736
+ parse( text ) {
573
737
 
574
- if ( a.colourCode === b.colourCode ) {
738
+ const loader = this.loader; // final results
575
739
 
576
- return 0;
740
+ const faces = [];
741
+ const lineSegments = [];
742
+ const conditionalSegments = [];
743
+ const subobjects = [];
744
+ const materials = {};
577
745
 
578
- }
746
+ const getLocalMaterial = colorCode => {
579
747
 
580
- if ( a.colourCode < b.colourCode ) {
748
+ return colorCode in materials ? materials[ colorCode ] : null;
581
749
 
582
- return - 1;
750
+ };
583
751
 
584
- }
752
+ let type = 'Model';
753
+ let category = null;
754
+ let keywords = null;
755
+ let totalFaces = 0; // split into lines
585
756
 
586
- return 1;
757
+ if ( text.indexOf( '\r\n' ) !== - 1 ) {
587
758
 
588
- }
759
+ // This is faster than String.split with regex that splits on both
760
+ text = text.replace( /\r\n/g, '\n' );
589
761
 
590
- function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) {
762
+ }
591
763
 
592
- // Creates a THREE.LineSegments (elementSize = 2) or a THREE.Mesh (elementSize = 3 )
593
- // With per face / segment material, implemented with mesh groups and materials array
594
- // Sort the faces or line segments by colour code to make later the mesh groups
595
- elements.sort( sortByMaterial );
764
+ const lines = text.split( '\n' );
765
+ const numLines = lines.length;
766
+ let parsingEmbeddedFiles = false;
767
+ let currentEmbeddedFileName = null;
768
+ let currentEmbeddedText = null;
769
+ let bfcCertified = false;
770
+ let bfcCCW = true;
771
+ let bfcInverted = false;
772
+ let bfcCull = true;
773
+ let startingConstructionStep = false; // Parse all line commands
596
774
 
597
- if ( totalElements === null ) {
775
+ for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
598
776
 
599
- totalElements = elements.length;
777
+ const line = lines[ lineIndex ];
778
+ if ( line.length === 0 ) continue;
600
779
 
601
- }
780
+ if ( parsingEmbeddedFiles ) {
602
781
 
603
- const positions = new Float32Array( elementSize * totalElements * 3 );
604
- const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null;
605
- const materials = [];
606
- const quadArray = new Array( 6 );
607
- const bufferGeometry = new THREE.BufferGeometry();
608
- let prevMaterial = null;
609
- let index0 = 0;
610
- let numGroupVerts = 0;
611
- let offset = 0;
782
+ if ( line.startsWith( '0 FILE ' ) ) {
612
783
 
613
- for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
784
+ // Save previous embedded file in the cache
785
+ this.setData( currentEmbeddedFileName, currentEmbeddedText ); // New embedded text file
614
786
 
615
- const elem = elements[ iElem ];
616
- let vertices = elem.vertices;
787
+ currentEmbeddedFileName = line.substring( 7 );
788
+ currentEmbeddedText = '';
617
789
 
618
- if ( vertices.length === 4 ) {
790
+ } else {
619
791
 
620
- quadArray[ 0 ] = vertices[ 0 ];
621
- quadArray[ 1 ] = vertices[ 1 ];
622
- quadArray[ 2 ] = vertices[ 2 ];
623
- quadArray[ 3 ] = vertices[ 0 ];
624
- quadArray[ 4 ] = vertices[ 2 ];
625
- quadArray[ 5 ] = vertices[ 3 ];
626
- vertices = quadArray;
792
+ currentEmbeddedText += line + '\n';
627
793
 
628
- }
794
+ }
629
795
 
630
- for ( let j = 0, l = vertices.length; j < l; j ++ ) {
796
+ continue;
631
797
 
632
- const v = vertices[ j ];
633
- const index = offset + j * 3;
634
- positions[ index + 0 ] = v.x;
635
- positions[ index + 1 ] = v.y;
636
- positions[ index + 2 ] = v.z;
798
+ }
637
799
 
638
- }
800
+ const lp = new LineParser( line, lineIndex + 1 );
801
+ lp.seekNonSpace();
639
802
 
640
- if ( elementSize === 3 ) {
803
+ if ( lp.isAtTheEnd() ) {
641
804
 
642
- let elemNormals = elem.normals;
805
+ // Empty line
806
+ continue;
643
807
 
644
- if ( elemNormals.length === 4 ) {
808
+ } // Parse the line type
645
809
 
646
- quadArray[ 0 ] = elemNormals[ 0 ];
647
- quadArray[ 1 ] = elemNormals[ 1 ];
648
- quadArray[ 2 ] = elemNormals[ 2 ];
649
- quadArray[ 3 ] = elemNormals[ 0 ];
650
- quadArray[ 4 ] = elemNormals[ 2 ];
651
- quadArray[ 5 ] = elemNormals[ 3 ];
652
- elemNormals = quadArray;
653
810
 
654
- }
811
+ const lineType = lp.getToken();
812
+ let material;
813
+ let colorCode;
814
+ let segment;
815
+ let ccw;
816
+ let doubleSided;
817
+ let v0, v1, v2, v3, c0, c1;
655
818
 
656
- for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
819
+ switch ( lineType ) {
657
820
 
658
- let n = elem.faceNormal;
821
+ // Line type 0: Comment or META
822
+ case '0':
823
+ // Parse meta directive
824
+ const meta = lp.getToken();
659
825
 
660
- if ( elemNormals[ j ] ) {
826
+ if ( meta ) {
661
827
 
662
- n = elemNormals[ j ].norm;
828
+ switch ( meta ) {
663
829
 
664
- }
830
+ case '!LDRAW_ORG':
831
+ type = lp.getToken();
832
+ break;
665
833
 
666
- const index = offset + j * 3;
667
- normals[ index + 0 ] = n.x;
668
- normals[ index + 1 ] = n.y;
669
- normals[ index + 2 ] = n.z;
834
+ case '!COLOUR':
835
+ material = loader.parseColorMetaDirective( lp );
670
836
 
671
- }
837
+ if ( material ) {
672
838
 
673
- }
839
+ materials[ material.userData.code ] = material;
674
840
 
675
- if ( prevMaterial !== elem.material ) {
841
+ } else {
676
842
 
677
- if ( prevMaterial !== null ) {
843
+ console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
678
844
 
679
- bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
845
+ }
680
846
 
681
- }
847
+ break;
682
848
 
683
- materials.push( elem.material );
684
- prevMaterial = elem.material;
685
- index0 = offset / 3;
686
- numGroupVerts = vertices.length;
849
+ case '!CATEGORY':
850
+ category = lp.getToken();
851
+ break;
687
852
 
688
- } else {
853
+ case '!KEYWORDS':
854
+ const newKeywords = lp.getRemainingString().split( ',' );
689
855
 
690
- numGroupVerts += vertices.length;
856
+ if ( newKeywords.length > 0 ) {
691
857
 
692
- }
858
+ if ( ! keywords ) {
693
859
 
694
- offset += 3 * vertices.length;
860
+ keywords = [];
695
861
 
696
- }
862
+ }
697
863
 
698
- if ( numGroupVerts > 0 ) {
864
+ newKeywords.forEach( function ( keyword ) {
699
865
 
700
- bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
866
+ keywords.push( keyword.trim() );
701
867
 
702
- }
868
+ } );
703
869
 
704
- bufferGeometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
870
+ }
705
871
 
706
- if ( normals !== null ) {
872
+ break;
707
873
 
708
- bufferGeometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
874
+ case 'FILE':
875
+ if ( lineIndex > 0 ) {
876
+
877
+ // Start embedded text files parsing
878
+ parsingEmbeddedFiles = true;
879
+ currentEmbeddedFileName = lp.getRemainingString();
880
+ currentEmbeddedText = '';
881
+ bfcCertified = false;
882
+ bfcCCW = true;
883
+
884
+ }
885
+
886
+ break;
887
+
888
+ case 'BFC':
889
+ // Changes to the backface culling state
890
+ while ( ! lp.isAtTheEnd() ) {
891
+
892
+ const token = lp.getToken();
893
+
894
+ switch ( token ) {
895
+
896
+ case 'CERTIFY':
897
+ case 'NOCERTIFY':
898
+ bfcCertified = token === 'CERTIFY';
899
+ bfcCCW = true;
900
+ break;
901
+
902
+ case 'CW':
903
+ case 'CCW':
904
+ bfcCCW = token === 'CCW';
905
+ break;
906
+
907
+ case 'INVERTNEXT':
908
+ bfcInverted = true;
909
+ break;
910
+
911
+ case 'CLIP':
912
+ case 'NOCLIP':
913
+ bfcCull = token === 'CLIP';
914
+ break;
915
+
916
+ default:
917
+ console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
918
+ break;
919
+
920
+ }
921
+
922
+ }
923
+
924
+ break;
925
+
926
+ case 'STEP':
927
+ startingConstructionStep = true;
928
+ break;
929
+
930
+ default:
931
+ // Other meta directives are not implemented
932
+ break;
933
+
934
+ }
935
+
936
+ }
937
+
938
+ break;
939
+ // Line type 1: Sub-object file
940
+
941
+ case '1':
942
+ colorCode = lp.getToken();
943
+ material = getLocalMaterial( colorCode );
944
+ const posX = parseFloat( lp.getToken() );
945
+ const posY = parseFloat( lp.getToken() );
946
+ const posZ = parseFloat( lp.getToken() );
947
+ const m0 = parseFloat( lp.getToken() );
948
+ const m1 = parseFloat( lp.getToken() );
949
+ const m2 = parseFloat( lp.getToken() );
950
+ const m3 = parseFloat( lp.getToken() );
951
+ const m4 = parseFloat( lp.getToken() );
952
+ const m5 = parseFloat( lp.getToken() );
953
+ const m6 = parseFloat( lp.getToken() );
954
+ const m7 = parseFloat( lp.getToken() );
955
+ const m8 = parseFloat( lp.getToken() );
956
+ const matrix = new THREE.Matrix4().set( m0, m1, m2, posX, m3, m4, m5, posY, m6, m7, m8, posZ, 0, 0, 0, 1 );
957
+ let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
958
+
959
+ if ( loader.fileMap[ fileName ] ) {
960
+
961
+ // Found the subobject path in the preloaded file path map
962
+ fileName = loader.fileMap[ fileName ];
963
+
964
+ } else {
965
+
966
+ // Standardized subfolders
967
+ if ( fileName.startsWith( 's/' ) ) {
968
+
969
+ fileName = 'parts/' + fileName;
970
+
971
+ } else if ( fileName.startsWith( '48/' ) ) {
972
+
973
+ fileName = 'p/' + fileName;
974
+
975
+ }
976
+
977
+ }
978
+
979
+ subobjects.push( {
980
+ material: material,
981
+ colorCode: colorCode,
982
+ matrix: matrix,
983
+ fileName: fileName,
984
+ inverted: bfcInverted,
985
+ startingConstructionStep: startingConstructionStep
986
+ } );
987
+ bfcInverted = false;
988
+ break;
989
+ // Line type 2: Line segment
990
+
991
+ case '2':
992
+ colorCode = lp.getToken();
993
+ material = getLocalMaterial( colorCode );
994
+ v0 = lp.getVector();
995
+ v1 = lp.getVector();
996
+ segment = {
997
+ material: material,
998
+ colorCode: colorCode,
999
+ vertices: [ v0, v1 ]
1000
+ };
1001
+ lineSegments.push( segment );
1002
+ break;
1003
+ // Line type 5: Conditional Line segment
1004
+
1005
+ case '5':
1006
+ colorCode = lp.getToken();
1007
+ material = getLocalMaterial( colorCode );
1008
+ v0 = lp.getVector();
1009
+ v1 = lp.getVector();
1010
+ c0 = lp.getVector();
1011
+ c1 = lp.getVector();
1012
+ segment = {
1013
+ material: material,
1014
+ colorCode: colorCode,
1015
+ vertices: [ v0, v1 ],
1016
+ controlPoints: [ c0, c1 ]
1017
+ };
1018
+ conditionalSegments.push( segment );
1019
+ break;
1020
+ // Line type 3: Triangle
1021
+
1022
+ case '3':
1023
+ colorCode = lp.getToken();
1024
+ material = getLocalMaterial( colorCode );
1025
+ ccw = bfcCCW;
1026
+ doubleSided = ! bfcCertified || ! bfcCull;
1027
+
1028
+ if ( ccw === true ) {
1029
+
1030
+ v0 = lp.getVector();
1031
+ v1 = lp.getVector();
1032
+ v2 = lp.getVector();
1033
+
1034
+ } else {
1035
+
1036
+ v2 = lp.getVector();
1037
+ v1 = lp.getVector();
1038
+ v0 = lp.getVector();
1039
+
1040
+ }
1041
+
1042
+ faces.push( {
1043
+ material: material,
1044
+ colorCode: colorCode,
1045
+ faceNormal: null,
1046
+ vertices: [ v0, v1, v2 ],
1047
+ normals: [ null, null, null ]
1048
+ } );
1049
+ totalFaces ++;
1050
+
1051
+ if ( doubleSided === true ) {
1052
+
1053
+ faces.push( {
1054
+ material: material,
1055
+ colorCode: colorCode,
1056
+ faceNormal: null,
1057
+ vertices: [ v2, v1, v0 ],
1058
+ normals: [ null, null, null ]
1059
+ } );
1060
+ totalFaces ++;
1061
+
1062
+ }
1063
+
1064
+ break;
1065
+ // Line type 4: Quadrilateral
1066
+
1067
+ case '4':
1068
+ colorCode = lp.getToken();
1069
+ material = getLocalMaterial( colorCode );
1070
+ ccw = bfcCCW;
1071
+ doubleSided = ! bfcCertified || ! bfcCull;
1072
+
1073
+ if ( ccw === true ) {
1074
+
1075
+ v0 = lp.getVector();
1076
+ v1 = lp.getVector();
1077
+ v2 = lp.getVector();
1078
+ v3 = lp.getVector();
1079
+
1080
+ } else {
1081
+
1082
+ v3 = lp.getVector();
1083
+ v2 = lp.getVector();
1084
+ v1 = lp.getVector();
1085
+ v0 = lp.getVector();
1086
+
1087
+ } // specifically place the triangle diagonal in the v0 and v1 slots so we can
1088
+ // account for the doubling of vertices later when smoothing normals.
1089
+
1090
+
1091
+ faces.push( {
1092
+ material: material,
1093
+ colorCode: colorCode,
1094
+ faceNormal: null,
1095
+ vertices: [ v0, v1, v2, v3 ],
1096
+ normals: [ null, null, null, null ]
1097
+ } );
1098
+ totalFaces += 2;
1099
+
1100
+ if ( doubleSided === true ) {
1101
+
1102
+ faces.push( {
1103
+ material: material,
1104
+ colorCode: colorCode,
1105
+ faceNormal: null,
1106
+ vertices: [ v3, v2, v1, v0 ],
1107
+ normals: [ null, null, null, null ]
1108
+ } );
1109
+ totalFaces += 2;
1110
+
1111
+ }
1112
+
1113
+ break;
1114
+
1115
+ default:
1116
+ throw new Error( 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.' );
1117
+
1118
+ }
1119
+
1120
+ }
1121
+
1122
+ if ( parsingEmbeddedFiles ) {
1123
+
1124
+ this.setData( currentEmbeddedFileName, currentEmbeddedText );
1125
+
1126
+ }
1127
+
1128
+ return {
1129
+ faces,
1130
+ conditionalSegments,
1131
+ lineSegments,
1132
+ type,
1133
+ category,
1134
+ keywords,
1135
+ subobjects,
1136
+ totalFaces,
1137
+ startingConstructionStep,
1138
+ materials
1139
+ };
1140
+
1141
+ }
1142
+
1143
+ loadData( fileName ) {
1144
+
1145
+ const key = fileName.toLowerCase();
1146
+
1147
+ if ( ! ( key in this.cache ) ) {
1148
+
1149
+ this.cache[ key ] = this.fetchData( fileName ).then( text => {
1150
+
1151
+ return this.parse( text );
1152
+
1153
+ } );
1154
+
1155
+ }
1156
+
1157
+ return this.cache[ key ].then( result => {
1158
+
1159
+ return this.cloneResult( result );
1160
+
1161
+ } );
1162
+
1163
+ }
1164
+
1165
+ setData( fileName, text ) {
1166
+
1167
+ const key = fileName.toLowerCase();
1168
+ this.cache[ key ] = Promise.resolve( this.parse( text ) );
1169
+
1170
+ }
1171
+
1172
+ }
1173
+
1174
+ function sortByMaterial( a, b ) {
1175
+
1176
+ if ( a.colorCode === b.colorCode ) {
1177
+
1178
+ return 0;
1179
+
1180
+ }
1181
+
1182
+ if ( a.colorCode < b.colorCode ) {
1183
+
1184
+ return - 1;
1185
+
1186
+ }
1187
+
1188
+ return 1;
1189
+
1190
+ }
1191
+
1192
+ function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) {
1193
+
1194
+ // Creates a THREE.LineSegments (elementSize = 2) or a THREE.Mesh (elementSize = 3 )
1195
+ // With per face / segment material, implemented with mesh groups and materials array
1196
+ // Sort the faces or line segments by color code to make later the mesh groups
1197
+ elements.sort( sortByMaterial );
1198
+
1199
+ if ( totalElements === null ) {
1200
+
1201
+ totalElements = elements.length;
1202
+
1203
+ }
1204
+
1205
+ const positions = new Float32Array( elementSize * totalElements * 3 );
1206
+ const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null;
1207
+ const materials = [];
1208
+ const quadArray = new Array( 6 );
1209
+ const bufferGeometry = new THREE.BufferGeometry();
1210
+ let prevMaterial = null;
1211
+ let index0 = 0;
1212
+ let numGroupVerts = 0;
1213
+ let offset = 0;
1214
+
1215
+ for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
1216
+
1217
+ const elem = elements[ iElem ];
1218
+ let vertices = elem.vertices;
1219
+
1220
+ if ( vertices.length === 4 ) {
1221
+
1222
+ quadArray[ 0 ] = vertices[ 0 ];
1223
+ quadArray[ 1 ] = vertices[ 1 ];
1224
+ quadArray[ 2 ] = vertices[ 2 ];
1225
+ quadArray[ 3 ] = vertices[ 0 ];
1226
+ quadArray[ 4 ] = vertices[ 2 ];
1227
+ quadArray[ 5 ] = vertices[ 3 ];
1228
+ vertices = quadArray;
1229
+
1230
+ }
1231
+
1232
+ for ( let j = 0, l = vertices.length; j < l; j ++ ) {
1233
+
1234
+ const v = vertices[ j ];
1235
+ const index = offset + j * 3;
1236
+ positions[ index + 0 ] = v.x;
1237
+ positions[ index + 1 ] = v.y;
1238
+ positions[ index + 2 ] = v.z;
1239
+
1240
+ } // create the normals array if this is a set of faces
1241
+
1242
+
1243
+ if ( elementSize === 3 ) {
1244
+
1245
+ if ( ! elem.faceNormal ) {
1246
+
1247
+ const v0 = vertices[ 0 ];
1248
+ const v1 = vertices[ 1 ];
1249
+ const v2 = vertices[ 2 ];
1250
+
1251
+ _tempVec0.subVectors( v1, v0 );
1252
+
1253
+ _tempVec1.subVectors( v2, v1 );
1254
+
1255
+ elem.faceNormal = new THREE.Vector3().crossVectors( _tempVec0, _tempVec1 ).normalize();
1256
+
1257
+ }
1258
+
1259
+ let elemNormals = elem.normals;
1260
+
1261
+ if ( elemNormals.length === 4 ) {
1262
+
1263
+ quadArray[ 0 ] = elemNormals[ 0 ];
1264
+ quadArray[ 1 ] = elemNormals[ 1 ];
1265
+ quadArray[ 2 ] = elemNormals[ 2 ];
1266
+ quadArray[ 3 ] = elemNormals[ 0 ];
1267
+ quadArray[ 4 ] = elemNormals[ 2 ];
1268
+ quadArray[ 5 ] = elemNormals[ 3 ];
1269
+ elemNormals = quadArray;
1270
+
1271
+ }
1272
+
1273
+ for ( let j = 0, l = elemNormals.length; j < l; j ++ ) {
1274
+
1275
+ // use face normal if a vertex normal is not provided
1276
+ let n = elem.faceNormal;
1277
+
1278
+ if ( elemNormals[ j ] ) {
1279
+
1280
+ n = elemNormals[ j ].norm;
1281
+
1282
+ }
1283
+
1284
+ const index = offset + j * 3;
1285
+ normals[ index + 0 ] = n.x;
1286
+ normals[ index + 1 ] = n.y;
1287
+ normals[ index + 2 ] = n.z;
1288
+
1289
+ }
1290
+
1291
+ }
1292
+
1293
+ if ( prevMaterial !== elem.material ) {
1294
+
1295
+ if ( prevMaterial !== null ) {
1296
+
1297
+ bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
1298
+
1299
+ }
1300
+
1301
+ const material = elem.material;
1302
+
1303
+ if ( elementSize === 3 ) {
1304
+
1305
+ materials.push( material );
1306
+
1307
+ } else if ( elementSize === 2 ) {
1308
+
1309
+ if ( isConditionalSegments ) {
1310
+
1311
+ materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial );
1312
+
1313
+ } else {
1314
+
1315
+ materials.push( material.userData.edgeMaterial );
1316
+
1317
+ }
1318
+
1319
+ }
1320
+
1321
+ prevMaterial = elem.material;
1322
+ index0 = offset / 3;
1323
+ numGroupVerts = vertices.length;
1324
+
1325
+ } else {
1326
+
1327
+ numGroupVerts += vertices.length;
1328
+
1329
+ }
1330
+
1331
+ offset += 3 * vertices.length;
1332
+
1333
+ }
1334
+
1335
+ if ( numGroupVerts > 0 ) {
1336
+
1337
+ bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
1338
+
1339
+ }
1340
+
1341
+ bufferGeometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
1342
+
1343
+ if ( normals !== null ) {
1344
+
1345
+ bufferGeometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
709
1346
 
710
1347
  }
711
1348
 
@@ -779,15 +1416,12 @@
779
1416
  this.materials = []; // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
780
1417
  // This also allows to handle the embedded text files ("0 FILE" lines)
781
1418
 
782
- this.cache = new LDrawFileCache( this ); // 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.
783
-
784
- this.fileMap = null;
785
- this.rootParseScope = this.newParseScopeLevel(); // Add default main triangle and line edge materials (used in pieces that can be coloured with a main color)
1419
+ this.parseCache = new LDrawParsedCache( this ); // 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.
786
1420
 
787
- this.setMaterials( [ this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ), this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ] ); // If this flag is set to true, each subobject will be a Object.
788
- // If not (the default), only one object which contains all the merged primitives will be created.
1421
+ this.fileMap = {};
1422
+ this.rootParseScope = this.newParseScopeLevel(); // Add default main triangle and line edge materials (used in pieces that can be colored with a main color)
789
1423
 
790
- this.separateObjects = false; // If this flag is set to true the vertex normals will be smoothed.
1424
+ this.setMaterials( [ this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ), this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ] ); // If this flag is set to true the vertex normals will be smoothed.
791
1425
 
792
1426
  this.smoothNormals = true; // The path to load parts from the LDraw parts library from.
793
1427
 
@@ -820,7 +1454,7 @@
820
1454
  if ( colorLineRegex.test( line ) ) {
821
1455
 
822
1456
  const directive = line.replace( colorLineRegex, '' );
823
- const material = this.parseColourMetaDirective( new LineParser( directive ) );
1457
+ const material = this.parseColorMetaDirective( new LineParser( directive ) );
824
1458
  materials.push( material );
825
1459
 
826
1460
  }
@@ -833,19 +1467,14 @@
833
1467
 
834
1468
  load( url, onLoad, onProgress, onError ) {
835
1469
 
836
- if ( ! this.fileMap ) {
837
-
838
- this.fileMap = {};
839
-
840
- }
841
-
842
1470
  const fileLoader = new THREE.FileLoader( this.manager );
843
1471
  fileLoader.setPath( this.path );
844
1472
  fileLoader.setRequestHeader( this.requestHeader );
845
1473
  fileLoader.setWithCredentials( this.withCredentials );
846
1474
  fileLoader.load( url, text => {
847
1475
 
848
- this.processObject( text, null, url, this.rootParseScope ).then( function ( result ) {
1476
+ const parsedInfo = this.parseCache.parse( text );
1477
+ this.processObject( parsedInfo, null, url, this.rootParseScope ).then( function ( result ) {
849
1478
 
850
1479
  onLoad( result.groupObject );
851
1480
 
@@ -857,8 +1486,9 @@
857
1486
 
858
1487
  parse( text, path, onLoad ) {
859
1488
 
860
- // Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
861
- this.processObject( text, null, path, this.rootParseScope ).then( function ( result ) {
1489
+ // Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
1490
+ const parsedInfo = this.parseCache.parse( text );
1491
+ this.processObject( parsedInfo, null, path, this.rootParseScope ).then( function ( result ) {
862
1492
 
863
1493
  onLoad( result.groupObject );
864
1494
 
@@ -866,849 +1496,476 @@
866
1496
 
867
1497
  }
868
1498
 
869
- setMaterials( materials ) {
870
-
871
- // Clears parse scopes stack, adds new scope with material library
872
- this.rootParseScope = this.newParseScopeLevel( materials );
873
- this.rootParseScope.isFromParse = false;
874
- this.materials = materials;
875
- return this;
876
-
877
- }
878
-
879
- setFileMap( fileMap ) {
880
-
881
- this.fileMap = fileMap;
882
- return this;
883
-
884
- }
885
-
886
- newParseScopeLevel( materials = null, parentScope = null ) {
887
-
888
- // Adds a new scope level, assign materials to it and returns it
889
- const matLib = {};
890
-
891
- if ( materials ) {
892
-
893
- for ( let i = 0, n = materials.length; i < n; i ++ ) {
894
-
895
- const material = materials[ i ];
896
- matLib[ material.userData.code ] = material;
897
-
898
- }
899
-
900
- }
901
-
902
- const newParseScope = {
903
- parentScope: parentScope,
904
- lib: matLib,
905
- url: null,
906
- // Subobjects
907
- subobjects: null,
908
- numSubobjects: 0,
909
- subobjectIndex: 0,
910
- inverted: false,
911
- category: null,
912
- keywords: null,
913
- // Current subobject
914
- currentFileName: null,
915
- mainColourCode: parentScope ? parentScope.mainColourCode : '16',
916
- mainEdgeColourCode: parentScope ? parentScope.mainEdgeColourCode : '24',
917
- currentMatrix: new THREE.Matrix4(),
918
- matrix: new THREE.Matrix4(),
919
- // If false, it is a root material scope previous to parse
920
- isFromParse: true,
921
- faces: null,
922
- lineSegments: null,
923
- conditionalSegments: null,
924
- totalFaces: 0,
925
- // If true, this object is the start of a construction step
926
- startingConstructionStep: false
927
- };
928
- return newParseScope;
929
-
930
- }
931
-
932
- addMaterial( material, parseScope ) {
933
-
934
- // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
935
- const matLib = parseScope.lib;
936
-
937
- if ( ! matLib[ material.userData.code ] ) {
938
-
939
- this.materials.push( material );
940
-
941
- }
942
-
943
- matLib[ material.userData.code ] = material;
944
- return this;
945
-
946
- }
947
-
948
- getMaterial( colourCode, parseScope = this.rootParseScope ) {
949
-
950
- // Given a colour code search its material in the parse scopes stack
951
- if ( colourCode.startsWith( '0x2' ) ) {
952
-
953
- // Special 'direct' material value (RGB colour)
954
- const colour = colourCode.substring( 3 );
955
- return this.parseColourMetaDirective( new LineParser( 'Direct_Color_' + colour + ' CODE -1 VALUE #' + colour + ' EDGE #' + colour + '' ) );
956
-
957
- }
958
-
959
- while ( parseScope ) {
960
-
961
- const material = parseScope.lib[ colourCode ];
962
-
963
- if ( material ) {
964
-
965
- return material;
966
-
967
- } else {
968
-
969
- parseScope = parseScope.parentScope;
970
-
971
- }
972
-
973
- } // Material was not found
974
-
975
-
976
- return null;
977
-
978
- }
979
-
980
- parseColourMetaDirective( lineParser ) {
981
-
982
- // Parses a colour definition and returns a THREE.Material
983
- let code = null; // Triangle and line colours
984
-
985
- let colour = 0xFF00FF;
986
- let edgeColour = 0xFF00FF; // Transparency
987
-
988
- let alpha = 1;
989
- let isTransparent = false; // Self-illumination:
990
-
991
- let luminance = 0;
992
- let finishType = FINISH_TYPE_DEFAULT;
993
- let edgeMaterial = null;
994
- const name = lineParser.getToken();
995
-
996
- if ( ! name ) {
997
-
998
- throw 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.';
999
-
1000
- } // Parse tag tokens and their parameters
1001
-
1002
-
1003
- let token = null;
1004
-
1005
- while ( true ) {
1006
-
1007
- token = lineParser.getToken();
1008
-
1009
- if ( ! token ) {
1010
-
1011
- break;
1012
-
1013
- }
1014
-
1015
- switch ( token.toUpperCase() ) {
1016
-
1017
- case 'CODE':
1018
- code = lineParser.getToken();
1019
- break;
1020
-
1021
- case 'VALUE':
1022
- colour = lineParser.getToken();
1023
-
1024
- if ( colour.startsWith( '0x' ) ) {
1025
-
1026
- colour = '#' + colour.substring( 2 );
1027
-
1028
- } else if ( ! colour.startsWith( '#' ) ) {
1029
-
1030
- throw 'LDrawLoader: Invalid colour while parsing material' + lineParser.getLineNumberString() + '.';
1031
-
1032
- }
1033
-
1034
- break;
1035
-
1036
- case 'EDGE':
1037
- edgeColour = lineParser.getToken();
1038
-
1039
- if ( edgeColour.startsWith( '0x' ) ) {
1040
-
1041
- edgeColour = '#' + edgeColour.substring( 2 );
1042
-
1043
- } else if ( ! edgeColour.startsWith( '#' ) ) {
1044
-
1045
- // Try to see if edge colour is a colour code
1046
- edgeMaterial = this.getMaterial( edgeColour );
1047
-
1048
- if ( ! edgeMaterial ) {
1049
-
1050
- throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + '.';
1051
-
1052
- } // Get the edge material for this triangle material
1053
-
1054
-
1055
- edgeMaterial = edgeMaterial.userData.edgeMaterial;
1056
-
1057
- }
1058
-
1059
- break;
1060
-
1061
- case 'ALPHA':
1062
- alpha = parseInt( lineParser.getToken() );
1063
-
1064
- if ( isNaN( alpha ) ) {
1065
-
1066
- throw 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.';
1067
-
1068
- }
1069
-
1070
- alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
1071
-
1072
- if ( alpha < 1 ) {
1073
-
1074
- isTransparent = true;
1075
-
1076
- }
1077
-
1078
- break;
1079
-
1080
- case 'LUMINANCE':
1081
- luminance = parseInt( lineParser.getToken() );
1082
-
1083
- if ( isNaN( luminance ) ) {
1084
-
1085
- throw 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.';
1086
-
1087
- }
1088
-
1089
- luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
1090
- break;
1091
-
1092
- case 'CHROME':
1093
- finishType = FINISH_TYPE_CHROME;
1094
- break;
1095
-
1096
- case 'PEARLESCENT':
1097
- finishType = FINISH_TYPE_PEARLESCENT;
1098
- break;
1099
-
1100
- case 'RUBBER':
1101
- finishType = FINISH_TYPE_RUBBER;
1102
- break;
1103
-
1104
- case 'MATTE_METALLIC':
1105
- finishType = FINISH_TYPE_MATTE_METALLIC;
1106
- break;
1107
-
1108
- case 'METAL':
1109
- finishType = FINISH_TYPE_METAL;
1110
- break;
1111
-
1112
- case 'MATERIAL':
1113
- // Not implemented
1114
- lineParser.setToEnd();
1115
- break;
1116
-
1117
- default:
1118
- throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.';
1119
- break;
1120
-
1121
- }
1122
-
1123
- }
1124
-
1125
- let material = null;
1126
-
1127
- switch ( finishType ) {
1128
-
1129
- case FINISH_TYPE_DEFAULT:
1130
- material = new THREE.MeshStandardMaterial( {
1131
- color: colour,
1132
- roughness: 0.3,
1133
- metalness: 0
1134
- } );
1135
- break;
1136
-
1137
- case FINISH_TYPE_PEARLESCENT:
1138
- // Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
1139
- const specular = new THREE.Color( colour );
1140
- const hsl = specular.getHSL( {
1141
- h: 0,
1142
- s: 0,
1143
- l: 0
1144
- } );
1145
- hsl.h = ( hsl.h + 0.5 ) % 1;
1146
- hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
1147
- specular.setHSL( hsl.h, hsl.s, hsl.l );
1148
- material = new THREE.MeshPhongMaterial( {
1149
- color: colour,
1150
- specular: specular,
1151
- shininess: 10,
1152
- reflectivity: 0.3
1153
- } );
1154
- break;
1155
-
1156
- case FINISH_TYPE_CHROME:
1157
- // Mirror finish surface
1158
- material = new THREE.MeshStandardMaterial( {
1159
- color: colour,
1160
- roughness: 0,
1161
- metalness: 1
1162
- } );
1163
- break;
1164
-
1165
- case FINISH_TYPE_RUBBER:
1166
- // Rubber finish
1167
- material = new THREE.MeshStandardMaterial( {
1168
- color: colour,
1169
- roughness: 0.9,
1170
- metalness: 0
1171
- } );
1172
- break;
1499
+ setMaterials( materials ) {
1173
1500
 
1174
- case FINISH_TYPE_MATTE_METALLIC:
1175
- // Brushed metal finish
1176
- material = new THREE.MeshStandardMaterial( {
1177
- color: colour,
1178
- roughness: 0.8,
1179
- metalness: 0.4
1180
- } );
1181
- break;
1501
+ // Clears parse scopes stack, adds new scope with material library
1502
+ this.rootParseScope = this.newParseScopeLevel( materials );
1503
+ this.rootParseScope.isFromParse = false;
1504
+ this.materials = materials;
1505
+ return this;
1182
1506
 
1183
- case FINISH_TYPE_METAL:
1184
- // Average metal finish
1185
- material = new THREE.MeshStandardMaterial( {
1186
- color: colour,
1187
- roughness: 0.2,
1188
- metalness: 0.85
1189
- } );
1190
- break;
1507
+ }
1191
1508
 
1192
- default:
1193
- // Should not happen
1194
- break;
1509
+ setFileMap( fileMap ) {
1195
1510
 
1196
- }
1511
+ this.fileMap = fileMap;
1512
+ return this;
1197
1513
 
1198
- material.transparent = isTransparent;
1199
- material.premultipliedAlpha = true;
1200
- material.opacity = alpha;
1201
- material.depthWrite = ! isTransparent;
1202
- material.polygonOffset = true;
1203
- material.polygonOffsetFactor = 1;
1514
+ }
1204
1515
 
1205
- if ( luminance !== 0 ) {
1516
+ newParseScopeLevel( materials = null, parentScope = null ) {
1206
1517
 
1207
- material.emissive.set( material.color ).multiplyScalar( luminance );
1518
+ // Adds a new scope level, assign materials to it and returns it
1519
+ const matLib = {};
1208
1520
 
1209
- }
1521
+ if ( materials ) {
1210
1522
 
1211
- if ( ! edgeMaterial ) {
1523
+ for ( let i = 0, n = materials.length; i < n; i ++ ) {
1212
1524
 
1213
- // This is the material used for edges
1214
- edgeMaterial = new THREE.LineBasicMaterial( {
1215
- color: edgeColour,
1216
- transparent: isTransparent,
1217
- opacity: alpha,
1218
- depthWrite: ! isTransparent
1219
- } );
1220
- edgeMaterial.userData.code = code;
1221
- edgeMaterial.name = name + ' - Edge'; // This is the material used for conditional edges
1525
+ const material = materials[ i ];
1526
+ matLib[ material.userData.code ] = material;
1222
1527
 
1223
- edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( {
1224
- fog: true,
1225
- transparent: isTransparent,
1226
- depthWrite: ! isTransparent,
1227
- color: edgeColour,
1228
- opacity: alpha
1229
- } );
1528
+ }
1230
1529
 
1231
1530
  }
1232
1531
 
1233
- material.userData.code = code;
1234
- material.name = name;
1235
- material.userData.edgeMaterial = edgeMaterial;
1236
- return material;
1532
+ const newParseScope = {
1533
+ parentScope: parentScope,
1534
+ lib: matLib,
1535
+ url: null,
1536
+ // Subobjects
1537
+ subobjects: null,
1538
+ numSubobjects: 0,
1539
+ subobjectIndex: 0,
1540
+ inverted: false,
1541
+ category: null,
1542
+ keywords: null,
1543
+ // Current subobject
1544
+ currentFileName: null,
1545
+ mainColorCode: parentScope ? parentScope.mainColorCode : '16',
1546
+ mainEdgeColorCode: parentScope ? parentScope.mainEdgeColorCode : '24',
1547
+ matrix: new THREE.Matrix4(),
1548
+ type: 'Model',
1549
+ groupObject: null,
1550
+ // If false, it is a root material scope previous to parse
1551
+ isFromParse: true,
1552
+ faces: [],
1553
+ lineSegments: [],
1554
+ conditionalSegments: [],
1555
+ totalFaces: 0,
1556
+ faceMaterials: new Set(),
1557
+ // If true, this object is the start of a construction step
1558
+ startingConstructionStep: false
1559
+ };
1560
+ return newParseScope;
1237
1561
 
1238
- } //
1562
+ }
1563
+
1564
+ addMaterial( material, parseScope ) {
1239
1565
 
1566
+ // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
1567
+ const matLib = parseScope.lib;
1240
1568
 
1241
- objectParse( text, parseScope ) {
1569
+ if ( ! matLib[ material.userData.code ] ) {
1242
1570
 
1243
- // Retrieve data from the parent parse scope
1244
- const currentParseScope = parseScope;
1245
- const parentParseScope = currentParseScope.parentScope; // Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
1571
+ this.materials.push( material );
1246
1572
 
1247
- const mainColourCode = currentParseScope.mainColourCode;
1248
- const mainEdgeColourCode = currentParseScope.mainEdgeColourCode; // Parse result variables
1573
+ }
1249
1574
 
1250
- let faces;
1251
- let lineSegments;
1252
- let conditionalSegments;
1253
- const subobjects = [];
1254
- let category = null;
1255
- let keywords = null;
1575
+ matLib[ material.userData.code ] = material;
1576
+ return this;
1256
1577
 
1257
- if ( text.indexOf( '\r\n' ) !== - 1 ) {
1578
+ }
1258
1579
 
1259
- // This is faster than String.split with regex that splits on both
1260
- text = text.replace( /\r\n/g, '\n' );
1580
+ getMaterial( colorCode, parseScope = this.rootParseScope ) {
1261
1581
 
1262
- }
1582
+ // Given a color code search its material in the parse scopes stack
1583
+ if ( colorCode.startsWith( '0x2' ) ) {
1263
1584
 
1264
- const lines = text.split( '\n' );
1265
- const numLines = lines.length;
1266
- let parsingEmbeddedFiles = false;
1267
- let currentEmbeddedFileName = null;
1268
- let currentEmbeddedText = null;
1269
- let bfcCertified = false;
1270
- let bfcCCW = true;
1271
- let bfcInverted = false;
1272
- let bfcCull = true;
1273
- let type = '';
1274
- let startingConstructionStep = false;
1275
- const scope = this;
1585
+ // Special 'direct' material value (RGB color)
1586
+ const color = colorCode.substring( 3 );
1587
+ return this.parseColorMetaDirective( new LineParser( 'Direct_Color_' + color + ' CODE -1 VALUE #' + color + ' EDGE #' + color + '' ) );
1276
1588
 
1277
- function parseColourCode( lineParser, forEdge ) {
1589
+ }
1278
1590
 
1279
- // Parses next colour code and returns a THREE.Material
1280
- let colourCode = lineParser.getToken();
1591
+ while ( parseScope ) {
1281
1592
 
1282
- if ( ! forEdge && colourCode === '16' ) {
1593
+ const material = parseScope.lib[ colorCode ];
1283
1594
 
1284
- colourCode = mainColourCode;
1595
+ if ( material ) {
1285
1596
 
1286
- }
1597
+ return material;
1287
1598
 
1288
- if ( forEdge && colourCode === '24' ) {
1599
+ } else {
1289
1600
 
1290
- colourCode = mainEdgeColourCode;
1601
+ parseScope = parseScope.parentScope;
1291
1602
 
1292
1603
  }
1293
1604
 
1294
- const material = scope.getMaterial( colourCode, currentParseScope );
1605
+ } // Material was not found
1295
1606
 
1296
- if ( ! material ) {
1297
1607
 
1298
- throw 'LDrawLoader: Unknown colour code "' + colourCode + '" is used' + lineParser.getLineNumberString() + ' but it was not defined previously.';
1608
+ return null;
1299
1609
 
1300
- }
1610
+ }
1301
1611
 
1302
- return material;
1612
+ parseColorMetaDirective( lineParser ) {
1303
1613
 
1304
- }
1614
+ // Parses a color definition and returns a THREE.Material
1615
+ let code = null; // Triangle and line colors
1305
1616
 
1306
- function parseVector( lp ) {
1617
+ let color = 0xFF00FF;
1618
+ let edgeColor = 0xFF00FF; // Transparency
1307
1619
 
1308
- const v = new THREE.Vector3( parseFloat( lp.getToken() ), parseFloat( lp.getToken() ), parseFloat( lp.getToken() ) );
1620
+ let alpha = 1;
1621
+ let isTransparent = false; // Self-illumination:
1309
1622
 
1310
- if ( ! scope.separateObjects ) {
1623
+ let luminance = 0;
1624
+ let finishType = FINISH_TYPE_DEFAULT;
1625
+ let edgeMaterial = null;
1626
+ const name = lineParser.getToken();
1311
1627
 
1312
- v.applyMatrix4( currentParseScope.currentMatrix );
1628
+ if ( ! name ) {
1313
1629
 
1314
- }
1630
+ throw new Error( 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.' );
1315
1631
 
1316
- return v;
1632
+ } // Parse tag tokens and their parameters
1317
1633
 
1318
- } // Parse all line commands
1319
1634
 
1635
+ let token = null;
1320
1636
 
1321
- for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
1637
+ while ( true ) {
1322
1638
 
1323
- const line = lines[ lineIndex ];
1324
- if ( line.length === 0 ) continue;
1639
+ token = lineParser.getToken();
1325
1640
 
1326
- if ( parsingEmbeddedFiles ) {
1641
+ if ( ! token ) {
1327
1642
 
1328
- if ( line.startsWith( '0 FILE ' ) ) {
1643
+ break;
1329
1644
 
1330
- // Save previous embedded file in the cache
1331
- this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText ); // New embedded text file
1645
+ }
1332
1646
 
1333
- currentEmbeddedFileName = line.substring( 7 );
1334
- currentEmbeddedText = '';
1647
+ switch ( token.toUpperCase() ) {
1335
1648
 
1336
- } else {
1649
+ case 'CODE':
1650
+ code = lineParser.getToken();
1651
+ break;
1337
1652
 
1338
- currentEmbeddedText += line + '\n';
1653
+ case 'VALUE':
1654
+ color = lineParser.getToken();
1339
1655
 
1340
- }
1656
+ if ( color.startsWith( '0x' ) ) {
1341
1657
 
1342
- continue;
1658
+ color = '#' + color.substring( 2 );
1343
1659
 
1344
- }
1660
+ } else if ( ! color.startsWith( '#' ) ) {
1345
1661
 
1346
- const lp = new LineParser( line, lineIndex + 1 );
1347
- lp.seekNonSpace();
1662
+ throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
1348
1663
 
1349
- if ( lp.isAtTheEnd() ) {
1664
+ }
1350
1665
 
1351
- // Empty line
1352
- continue;
1666
+ break;
1353
1667
 
1354
- } // Parse the line type
1668
+ case 'EDGE':
1669
+ edgeColor = lineParser.getToken();
1355
1670
 
1671
+ if ( edgeColor.startsWith( '0x' ) ) {
1356
1672
 
1357
- const lineType = lp.getToken();
1358
- let material;
1359
- let segment;
1360
- let inverted;
1361
- let ccw;
1362
- let doubleSided;
1363
- let v0, v1, v2, v3, c0, c1, faceNormal;
1673
+ edgeColor = '#' + edgeColor.substring( 2 );
1364
1674
 
1365
- switch ( lineType ) {
1675
+ } else if ( ! edgeColor.startsWith( '#' ) ) {
1366
1676
 
1367
- // Line type 0: Comment or META
1368
- case '0':
1369
- // Parse meta directive
1370
- const meta = lp.getToken();
1677
+ // Try to see if edge color is a color code
1678
+ edgeMaterial = this.getMaterial( edgeColor );
1371
1679
 
1372
- if ( meta ) {
1680
+ if ( ! edgeMaterial ) {
1373
1681
 
1374
- switch ( meta ) {
1682
+ throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
1375
1683
 
1376
- case '!LDRAW_ORG':
1377
- type = lp.getToken();
1378
- currentParseScope.faces = [];
1379
- currentParseScope.lineSegments = [];
1380
- currentParseScope.conditionalSegments = [];
1381
- currentParseScope.type = type;
1382
- const isRoot = ! parentParseScope.isFromParse;
1684
+ } // Get the edge material for this triangle material
1383
1685
 
1384
- if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
1385
1686
 
1386
- currentParseScope.groupObject = new THREE.Group();
1387
- currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
1687
+ edgeMaterial = edgeMaterial.userData.edgeMaterial;
1388
1688
 
1389
- } // If the scale of the object is negated then the triangle winding order
1390
- // needs to be flipped.
1689
+ }
1391
1690
 
1691
+ break;
1392
1692
 
1393
- if ( currentParseScope.matrix.determinant() < 0 && ( scope.separateObjects && isPrimitiveType( type ) || ! scope.separateObjects ) ) {
1693
+ case 'ALPHA':
1694
+ alpha = parseInt( lineParser.getToken() );
1394
1695
 
1395
- currentParseScope.inverted = ! currentParseScope.inverted;
1696
+ if ( isNaN( alpha ) ) {
1396
1697
 
1397
- }
1698
+ throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
1398
1699
 
1399
- faces = currentParseScope.faces;
1400
- lineSegments = currentParseScope.lineSegments;
1401
- conditionalSegments = currentParseScope.conditionalSegments;
1402
- break;
1700
+ }
1403
1701
 
1404
- case '!COLOUR':
1405
- material = this.parseColourMetaDirective( lp );
1702
+ alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
1406
1703
 
1407
- if ( material ) {
1704
+ if ( alpha < 1 ) {
1408
1705
 
1409
- this.addMaterial( material, parseScope );
1706
+ isTransparent = true;
1410
1707
 
1411
- } else {
1708
+ }
1412
1709
 
1413
- console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
1710
+ break;
1414
1711
 
1415
- }
1712
+ case 'LUMINANCE':
1713
+ luminance = parseInt( lineParser.getToken() );
1416
1714
 
1417
- break;
1715
+ if ( isNaN( luminance ) ) {
1418
1716
 
1419
- case '!CATEGORY':
1420
- category = lp.getToken();
1421
- break;
1717
+ throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
1422
1718
 
1423
- case '!KEYWORDS':
1424
- const newKeywords = lp.getRemainingString().split( ',' );
1719
+ }
1425
1720
 
1426
- if ( newKeywords.length > 0 ) {
1721
+ luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
1722
+ break;
1427
1723
 
1428
- if ( ! keywords ) {
1724
+ case 'CHROME':
1725
+ finishType = FINISH_TYPE_CHROME;
1726
+ break;
1429
1727
 
1430
- keywords = [];
1728
+ case 'PEARLESCENT':
1729
+ finishType = FINISH_TYPE_PEARLESCENT;
1730
+ break;
1431
1731
 
1432
- }
1732
+ case 'RUBBER':
1733
+ finishType = FINISH_TYPE_RUBBER;
1734
+ break;
1433
1735
 
1434
- newKeywords.forEach( function ( keyword ) {
1736
+ case 'MATTE_METALLIC':
1737
+ finishType = FINISH_TYPE_MATTE_METALLIC;
1738
+ break;
1435
1739
 
1436
- keywords.push( keyword.trim() );
1740
+ case 'METAL':
1741
+ finishType = FINISH_TYPE_METAL;
1742
+ break;
1437
1743
 
1438
- } );
1744
+ case 'MATERIAL':
1745
+ // Not implemented
1746
+ lineParser.setToEnd();
1747
+ break;
1439
1748
 
1440
- }
1749
+ default:
1750
+ throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
1441
1751
 
1442
- break;
1752
+ }
1443
1753
 
1444
- case 'FILE':
1445
- if ( lineIndex > 0 ) {
1754
+ }
1446
1755
 
1447
- // Start embedded text files parsing
1448
- parsingEmbeddedFiles = true;
1449
- currentEmbeddedFileName = lp.getRemainingString();
1450
- currentEmbeddedText = '';
1451
- bfcCertified = false;
1452
- bfcCCW = true;
1756
+ let material = null;
1453
1757
 
1454
- }
1758
+ switch ( finishType ) {
1455
1759
 
1456
- break;
1760
+ case FINISH_TYPE_DEFAULT:
1761
+ material = new THREE.MeshStandardMaterial( {
1762
+ color: color,
1763
+ roughness: 0.3,
1764
+ metalness: 0
1765
+ } );
1766
+ break;
1457
1767
 
1458
- case 'BFC':
1459
- // Changes to the backface culling state
1460
- while ( ! lp.isAtTheEnd() ) {
1768
+ case FINISH_TYPE_PEARLESCENT:
1769
+ // Try to imitate pearlescency by making the surface glossy
1770
+ material = new THREE.MeshStandardMaterial( {
1771
+ color: color,
1772
+ roughness: 0.3,
1773
+ metalness: 0.25
1774
+ } );
1775
+ break;
1461
1776
 
1462
- const token = lp.getToken();
1777
+ case FINISH_TYPE_CHROME:
1778
+ // Mirror finish surface
1779
+ material = new THREE.MeshStandardMaterial( {
1780
+ color: color,
1781
+ roughness: 0,
1782
+ metalness: 1
1783
+ } );
1784
+ break;
1463
1785
 
1464
- switch ( token ) {
1786
+ case FINISH_TYPE_RUBBER:
1787
+ // Rubber finish
1788
+ material = new THREE.MeshStandardMaterial( {
1789
+ color: color,
1790
+ roughness: 0.9,
1791
+ metalness: 0
1792
+ } );
1793
+ break;
1465
1794
 
1466
- case 'CERTIFY':
1467
- case 'NOCERTIFY':
1468
- bfcCertified = token === 'CERTIFY';
1469
- bfcCCW = true;
1470
- break;
1795
+ case FINISH_TYPE_MATTE_METALLIC:
1796
+ // Brushed metal finish
1797
+ material = new THREE.MeshStandardMaterial( {
1798
+ color: color,
1799
+ roughness: 0.8,
1800
+ metalness: 0.4
1801
+ } );
1802
+ break;
1471
1803
 
1472
- case 'CW':
1473
- case 'CCW':
1474
- bfcCCW = token === 'CCW';
1475
- break;
1804
+ case FINISH_TYPE_METAL:
1805
+ // Average metal finish
1806
+ material = new THREE.MeshStandardMaterial( {
1807
+ color: color,
1808
+ roughness: 0.2,
1809
+ metalness: 0.85
1810
+ } );
1811
+ break;
1476
1812
 
1477
- case 'INVERTNEXT':
1478
- bfcInverted = true;
1479
- break;
1813
+ default:
1814
+ // Should not happen
1815
+ break;
1480
1816
 
1481
- case 'CLIP':
1482
- case 'NOCLIP':
1483
- bfcCull = token === 'CLIP';
1484
- break;
1817
+ }
1485
1818
 
1486
- default:
1487
- console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
1488
- break;
1819
+ material.transparent = isTransparent;
1820
+ material.premultipliedAlpha = true;
1821
+ material.opacity = alpha;
1822
+ material.depthWrite = ! isTransparent;
1823
+ material.polygonOffset = true;
1824
+ material.polygonOffsetFactor = 1;
1489
1825
 
1490
- }
1826
+ if ( luminance !== 0 ) {
1491
1827
 
1492
- }
1828
+ material.emissive.set( material.color ).multiplyScalar( luminance );
1493
1829
 
1494
- break;
1830
+ }
1495
1831
 
1496
- case 'STEP':
1497
- startingConstructionStep = true;
1498
- break;
1832
+ if ( ! edgeMaterial ) {
1499
1833
 
1500
- default:
1501
- // Other meta directives are not implemented
1502
- break;
1834
+ // This is the material used for edges
1835
+ edgeMaterial = new THREE.LineBasicMaterial( {
1836
+ color: edgeColor,
1837
+ transparent: isTransparent,
1838
+ opacity: alpha,
1839
+ depthWrite: ! isTransparent
1840
+ } );
1841
+ edgeMaterial.userData.code = code;
1842
+ edgeMaterial.name = name + ' - Edge'; // This is the material used for conditional edges
1503
1843
 
1504
- }
1844
+ edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( {
1845
+ fog: true,
1846
+ transparent: isTransparent,
1847
+ depthWrite: ! isTransparent,
1848
+ color: edgeColor,
1849
+ opacity: alpha
1850
+ } );
1505
1851
 
1506
- }
1852
+ }
1507
1853
 
1508
- break;
1509
- // Line type 1: Sub-object file
1854
+ material.userData.code = code;
1855
+ material.name = name;
1856
+ material.userData.edgeMaterial = edgeMaterial;
1857
+ return material;
1510
1858
 
1511
- case '1':
1512
- material = parseColourCode( lp );
1513
- const posX = parseFloat( lp.getToken() );
1514
- const posY = parseFloat( lp.getToken() );
1515
- const posZ = parseFloat( lp.getToken() );
1516
- const m0 = parseFloat( lp.getToken() );
1517
- const m1 = parseFloat( lp.getToken() );
1518
- const m2 = parseFloat( lp.getToken() );
1519
- const m3 = parseFloat( lp.getToken() );
1520
- const m4 = parseFloat( lp.getToken() );
1521
- const m5 = parseFloat( lp.getToken() );
1522
- const m6 = parseFloat( lp.getToken() );
1523
- const m7 = parseFloat( lp.getToken() );
1524
- const m8 = parseFloat( lp.getToken() );
1525
- const matrix = new THREE.Matrix4().set( m0, m1, m2, posX, m3, m4, m5, posY, m6, m7, m8, posZ, 0, 0, 0, 1 );
1526
- let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
1859
+ } //
1527
1860
 
1528
- if ( scope.fileMap[ fileName ] ) {
1529
1861
 
1530
- // Found the subobject path in the preloaded file path map
1531
- fileName = scope.fileMap[ fileName ];
1862
+ objectParse( info, parseScope ) {
1532
1863
 
1533
- } else {
1864
+ // Retrieve data from the parent parse scope
1865
+ const currentParseScope = parseScope;
1866
+ const parentParseScope = currentParseScope.parentScope; // Main color codes passed to this subobject (or default codes 16 and 24 if it is the root object)
1534
1867
 
1535
- // Standardized subfolders
1536
- if ( fileName.startsWith( 's/' ) ) {
1868
+ const mainColorCode = currentParseScope.mainColorCode;
1869
+ const mainEdgeColorCode = currentParseScope.mainEdgeColorCode;
1537
1870
 
1538
- fileName = 'parts/' + fileName;
1871
+ const parseColorCode = ( colorCode, forEdge ) => {
1539
1872
 
1540
- } else if ( fileName.startsWith( '48/' ) ) {
1873
+ // Parses next color code and returns a THREE.Material
1874
+ if ( ! forEdge && colorCode === '16' ) {
1541
1875
 
1542
- fileName = 'p/' + fileName;
1876
+ colorCode = mainColorCode;
1543
1877
 
1544
- }
1878
+ }
1545
1879
 
1546
- }
1880
+ if ( forEdge && colorCode === '24' ) {
1547
1881
 
1548
- subobjects.push( {
1549
- material: material,
1550
- matrix: matrix,
1551
- fileName: fileName,
1552
- inverted: bfcInverted !== currentParseScope.inverted,
1553
- startingConstructionStep: startingConstructionStep
1554
- } );
1555
- bfcInverted = false;
1556
- break;
1557
- // Line type 2: Line segment
1882
+ colorCode = mainEdgeColorCode;
1558
1883
 
1559
- case '2':
1560
- material = parseColourCode( lp, true );
1561
- v0 = parseVector( lp );
1562
- v1 = parseVector( lp );
1563
- segment = {
1564
- material: material.userData.edgeMaterial,
1565
- colourCode: material.userData.code,
1566
- v0: v0,
1567
- v1: v1,
1568
- vertices: [ v0, v1 ]
1569
- };
1570
- lineSegments.push( segment );
1571
- break;
1572
- // Line type 5: Conditional Line segment
1884
+ }
1573
1885
 
1574
- case '5':
1575
- material = parseColourCode( lp, true );
1576
- v0 = parseVector( lp );
1577
- v1 = parseVector( lp );
1578
- c0 = parseVector( lp );
1579
- c1 = parseVector( lp );
1580
- segment = {
1581
- material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
1582
- colourCode: material.userData.code,
1583
- vertices: [ v0, v1 ],
1584
- controlPoints: [ c0, c1 ]
1585
- };
1586
- conditionalSegments.push( segment );
1587
- break;
1588
- // Line type 3: Triangle
1886
+ const material = this.getMaterial( colorCode, currentParseScope );
1589
1887
 
1590
- case '3':
1591
- material = parseColourCode( lp );
1592
- inverted = currentParseScope.inverted;
1593
- ccw = bfcCCW !== inverted;
1594
- doubleSided = ! bfcCertified || ! bfcCull;
1888
+ if ( ! material ) {
1595
1889
 
1596
- if ( ccw === true ) {
1890
+ throw new Error( 'LDrawLoader: Unknown color code "' + colorCode + '" is used but it was not defined previously.' );
1597
1891
 
1598
- v0 = parseVector( lp );
1599
- v1 = parseVector( lp );
1600
- v2 = parseVector( lp );
1892
+ }
1601
1893
 
1602
- } else {
1894
+ return material;
1603
1895
 
1604
- v2 = parseVector( lp );
1605
- v1 = parseVector( lp );
1606
- v0 = parseVector( lp );
1896
+ };
1607
1897
 
1608
- }
1898
+ const faces = info.faces;
1899
+ const lineSegments = info.lineSegments;
1900
+ const conditionalSegments = info.conditionalSegments;
1901
+ const materials = info.materials;
1609
1902
 
1610
- _tempVec0.subVectors( v1, v0 );
1903
+ if ( currentParseScope.inverted ) {
1611
1904
 
1612
- _tempVec1.subVectors( v2, v1 );
1905
+ faces.reverse();
1613
1906
 
1614
- faceNormal = new THREE.Vector3().crossVectors( _tempVec0, _tempVec1 ).normalize();
1615
- faces.push( {
1616
- material: material,
1617
- colourCode: material.userData.code,
1618
- faceNormal: faceNormal,
1619
- vertices: [ v0, v1, v2 ],
1620
- normals: [ null, null, null ]
1621
- } );
1622
- currentParseScope.totalFaces ++;
1907
+ }
1623
1908
 
1624
- if ( doubleSided === true ) {
1909
+ for ( const colorCode in materials ) {
1625
1910
 
1626
- faces.push( {
1627
- material: material,
1628
- colourCode: material.userData.code,
1629
- faceNormal: faceNormal,
1630
- vertices: [ v2, v1, v0 ],
1631
- normals: [ null, null, null ]
1632
- } );
1633
- currentParseScope.totalFaces ++;
1911
+ this.addMaterial( materials[ colorCode ], currentParseScope );
1634
1912
 
1635
- }
1913
+ }
1636
1914
 
1637
- break;
1638
- // Line type 4: Quadrilateral
1915
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
1639
1916
 
1640
- case '4':
1641
- material = parseColourCode( lp );
1642
- inverted = currentParseScope.inverted;
1643
- ccw = bfcCCW !== inverted;
1644
- doubleSided = ! bfcCertified || ! bfcCull;
1917
+ const face = faces[ i ];
1645
1918
 
1646
- if ( ccw === true ) {
1919
+ if ( face.material === null ) {
1647
1920
 
1648
- v0 = parseVector( lp );
1649
- v1 = parseVector( lp );
1650
- v2 = parseVector( lp );
1651
- v3 = parseVector( lp );
1921
+ face.material = parseColorCode( face.colorCode, false );
1652
1922
 
1653
- } else {
1923
+ }
1654
1924
 
1655
- v3 = parseVector( lp );
1656
- v2 = parseVector( lp );
1657
- v1 = parseVector( lp );
1658
- v0 = parseVector( lp );
1925
+ }
1659
1926
 
1660
- }
1927
+ for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
1661
1928
 
1662
- _tempVec0.subVectors( v1, v0 );
1929
+ const ls = lineSegments[ i ];
1663
1930
 
1664
- _tempVec1.subVectors( v2, v1 );
1931
+ if ( ls.material === null ) {
1665
1932
 
1666
- faceNormal = new THREE.Vector3().crossVectors( _tempVec0, _tempVec1 ).normalize(); // specifically place the triangle diagonal in the v0 and v1 slots so we can
1667
- // account for the doubling of vertices later when smoothing normals.
1933
+ ls.material = parseColorCode( ls.colorCode, true );
1668
1934
 
1669
- faces.push( {
1670
- material: material,
1671
- colourCode: material.userData.code,
1672
- faceNormal: faceNormal,
1673
- vertices: [ v0, v1, v2, v3 ],
1674
- normals: [ null, null, null, null ]
1675
- } );
1676
- currentParseScope.totalFaces += 2;
1935
+ }
1677
1936
 
1678
- if ( doubleSided === true ) {
1937
+ }
1679
1938
 
1680
- faces.push( {
1681
- material: material,
1682
- colourCode: material.userData.code,
1683
- faceNormal: faceNormal,
1684
- vertices: [ v3, v2, v1, v0 ],
1685
- normals: [ null, null, null, null ]
1686
- } );
1687
- currentParseScope.totalFaces += 2;
1939
+ for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
1688
1940
 
1689
- }
1941
+ const cs = conditionalSegments[ i ];
1690
1942
 
1691
- break;
1943
+ if ( cs.material === null ) {
1692
1944
 
1693
- default:
1694
- throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
1695
- break;
1945
+ cs.material = parseColorCode( cs.colorCode, true );
1696
1946
 
1697
1947
  }
1698
1948
 
1699
1949
  }
1700
1950
 
1701
- if ( parsingEmbeddedFiles ) {
1951
+ currentParseScope.faces = info.faces;
1952
+ currentParseScope.conditionalSegments = info.conditionalSegments;
1953
+ currentParseScope.lineSegments = info.lineSegments;
1954
+ currentParseScope.category = info.category;
1955
+ currentParseScope.keywords = info.keywords;
1956
+ currentParseScope.subobjects = info.subobjects;
1957
+ currentParseScope.numSubobjects = info.subobjects.length;
1958
+ currentParseScope.subobjectIndex = 0;
1959
+ currentParseScope.type = info.type;
1960
+ currentParseScope.totalFaces = info.totalFaces;
1961
+ const isRoot = ! parentParseScope.isFromParse;
1702
1962
 
1703
- this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
1963
+ if ( isRoot || ! isPrimitiveType( info.type ) ) {
1704
1964
 
1705
- }
1965
+ currentParseScope.groupObject = new THREE.Group();
1966
+ currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
1706
1967
 
1707
- currentParseScope.category = category;
1708
- currentParseScope.keywords = keywords;
1709
- currentParseScope.subobjects = subobjects;
1710
- currentParseScope.numSubobjects = subobjects.length;
1711
- currentParseScope.subobjectIndex = 0;
1968
+ }
1712
1969
 
1713
1970
  }
1714
1971
 
@@ -1737,21 +1994,32 @@
1737
1994
 
1738
1995
  finalizeObject( subobjectParseScope ) {
1739
1996
 
1997
+ // fail gracefully if an object could not be loaded
1998
+ if ( subobjectParseScope === null ) {
1999
+
2000
+ return;
2001
+
2002
+ }
2003
+
1740
2004
  const parentParseScope = subobjectParseScope.parentScope; // Smooth the normals if this is a part or if this is a case where the subpart
1741
2005
  // is added directly into the parent model (meaning it will never get smoothed by
1742
2006
  // being added to a part)
1743
2007
 
1744
- const doSmooth = isPartType( subobjectParseScope.type ) || ! isPartType( subobjectParseScope.type ) && ! isModelType( subobjectParseScope.type ) && isModelType( subobjectParseScope.parentScope.type );
2008
+ const doSmooth = isPartType( subobjectParseScope.type ) || isPrimitiveType( subobjectParseScope.type ) && isModelType( subobjectParseScope.parentScope.type );
1745
2009
 
1746
2010
  if ( this.smoothNormals && doSmooth ) {
1747
2011
 
1748
- smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments );
2012
+ generateFaceNormals( subobjectParseScope.faces ); // only check subsetgments if we have multiple materials in a single part because this seems to be the case where it's needed most --
2013
+ // there may be cases where a single edge line crosses over polygon edges that are broken up by multiple materials.
2014
+
2015
+ const checkSubSegments = subobjectParseScope.faceMaterials.size > 1;
2016
+ smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments, checkSubSegments );
1749
2017
 
1750
2018
  }
1751
2019
 
1752
2020
  const isRoot = ! parentParseScope.isFromParse;
1753
2021
 
1754
- if ( this.separateObjects && ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
2022
+ if ( ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
1755
2023
 
1756
2024
  const objGroup = subobjectParseScope.groupObject;
1757
2025
 
@@ -1785,26 +2053,23 @@
1785
2053
 
1786
2054
  } else {
1787
2055
 
1788
- const separateObjects = this.separateObjects;
1789
2056
  const parentLineSegments = parentParseScope.lineSegments;
1790
2057
  const parentConditionalSegments = parentParseScope.conditionalSegments;
1791
2058
  const parentFaces = parentParseScope.faces;
2059
+ const parentFaceMaterials = parentParseScope.faceMaterials;
1792
2060
  const lineSegments = subobjectParseScope.lineSegments;
1793
2061
  const conditionalSegments = subobjectParseScope.conditionalSegments;
1794
2062
  const faces = subobjectParseScope.faces;
2063
+ const faceMaterials = subobjectParseScope.faceMaterials;
2064
+ const matrix = subobjectParseScope.matrix;
2065
+ const matrixScaleInverted = matrix.determinant() < 0;
1795
2066
 
1796
2067
  for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
1797
2068
 
1798
2069
  const ls = lineSegments[ i ];
1799
-
1800
- if ( separateObjects ) {
1801
-
1802
- const vertices = ls.vertices;
1803
- vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1804
- vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1805
-
1806
- }
1807
-
2070
+ const vertices = ls.vertices;
2071
+ vertices[ 0 ].applyMatrix4( matrix );
2072
+ vertices[ 1 ].applyMatrix4( matrix );
1808
2073
  parentLineSegments.push( ls );
1809
2074
 
1810
2075
  }
@@ -1812,18 +2077,12 @@
1812
2077
  for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
1813
2078
 
1814
2079
  const os = conditionalSegments[ i ];
1815
-
1816
- if ( separateObjects ) {
1817
-
1818
- const vertices = os.vertices;
1819
- const controlPoints = os.controlPoints;
1820
- vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1821
- vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1822
- controlPoints[ 0 ].applyMatrix4( subobjectParseScope.matrix );
1823
- controlPoints[ 1 ].applyMatrix4( subobjectParseScope.matrix );
1824
-
1825
- }
1826
-
2080
+ const vertices = os.vertices;
2081
+ const controlPoints = os.controlPoints;
2082
+ vertices[ 0 ].applyMatrix4( matrix );
2083
+ vertices[ 1 ].applyMatrix4( matrix );
2084
+ controlPoints[ 0 ].applyMatrix4( matrix );
2085
+ controlPoints[ 1 ].applyMatrix4( matrix );
1827
2086
  parentConditionalSegments.push( os );
1828
2087
 
1829
2088
  }
@@ -1831,22 +2090,19 @@
1831
2090
  for ( let i = 0, l = faces.length; i < l; i ++ ) {
1832
2091
 
1833
2092
  const tri = faces[ i ];
2093
+ const vertices = tri.vertices;
1834
2094
 
1835
- if ( separateObjects ) {
1836
-
1837
- const vertices = tri.vertices;
2095
+ for ( let i = 0, l = vertices.length; i < l; i ++ ) {
1838
2096
 
1839
- for ( let i = 0, l = vertices.length; i < l; i ++ ) {
2097
+ vertices[ i ].applyMatrix4( matrix );
1840
2098
 
1841
- vertices[ i ] = vertices[ i ].clone().applyMatrix4( subobjectParseScope.matrix );
2099
+ } // If the scale of the object is negated then the triangle winding order
2100
+ // needs to be flipped.
1842
2101
 
1843
- }
1844
-
1845
- _tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
1846
2102
 
1847
- _tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
2103
+ if ( matrixScaleInverted !== subobjectParseScope.inverted ) {
1848
2104
 
1849
- tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
2105
+ vertices.reverse();
1850
2106
 
1851
2107
  }
1852
2108
 
@@ -1855,12 +2111,13 @@
1855
2111
  }
1856
2112
 
1857
2113
  parentParseScope.totalFaces += subobjectParseScope.totalFaces;
2114
+ faceMaterials.forEach( material => parentFaceMaterials.add( material ) );
1858
2115
 
1859
2116
  }
1860
2117
 
1861
2118
  }
1862
2119
 
1863
- async processObject( text, subobject, url, parentScope ) {
2120
+ async processObject( parsedInfo, subobject, url, parentScope ) {
1864
2121
 
1865
2122
  const scope = this;
1866
2123
  const parseScope = this.newParseScopeLevel( null, parentScope );
@@ -1869,18 +2126,28 @@
1869
2126
 
1870
2127
  if ( subobject ) {
1871
2128
 
1872
- parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
1873
2129
  parseScope.matrix.copy( subobject.matrix );
1874
2130
  parseScope.inverted = subobject.inverted;
1875
2131
  parseScope.startingConstructionStep = subobject.startingConstructionStep;
1876
- parseScope.mainColourCode = subobject.material.userData.code;
1877
- parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
1878
2132
  parseScope.fileName = subobject.fileName;
1879
2133
 
2134
+ if ( subobject.colorCode === '16' && parseScope.parentScope ) {
2135
+
2136
+ const parentScope = parseScope.parentScope;
2137
+ parseScope.mainColorCode = parentScope.mainColorCode;
2138
+ parseScope.mainEdgeColorCode = parentScope.mainEdgeColorCode;
2139
+
2140
+ } else if ( subobject.colorCode !== '16' ) {
2141
+
2142
+ parseScope.mainColorCode = subobject.colorCode;
2143
+ parseScope.mainEdgeColorCode = subobject.colorCode;
2144
+
2145
+ }
2146
+
1880
2147
  } // Parse the object
1881
2148
 
1882
2149
 
1883
- this.objectParse( text, parseScope );
2150
+ this.objectParse( parsedInfo, parseScope );
1884
2151
  const subobjects = parseScope.subobjects;
1885
2152
  const promises = [];
1886
2153
 
@@ -1912,13 +2179,14 @@
1912
2179
 
1913
2180
  function loadSubobject( subobject ) {
1914
2181
 
1915
- return scope.cache.loadData( subobject.fileName ).then( function ( text ) {
2182
+ return scope.parseCache.loadData( subobject.fileName ).then( function ( parsedInfo ) {
1916
2183
 
1917
- return scope.processObject( text, subobject, url, parseScope );
2184
+ return scope.processObject( parsedInfo, subobject, url, parseScope );
1918
2185
 
1919
- } ).catch( function () {
2186
+ } ).catch( function ( err ) {
1920
2187
 
1921
- console.warn( 'LDrawLoader: Subobject "' + subobject.fileName + '" could not be found.' );
2188
+ console.warn( err );
2189
+ return null;
1922
2190
 
1923
2191
  } );
1924
2192