three-stdlib 2.7.0 → 2.8.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.
- package/{Nodes-ec4e1143.js → Nodes-894ac9dc.js} +0 -0
- package/{Nodes-427f68b0.js → Nodes-af575af7.js} +0 -0
- package/cameras/CinematicCamera.cjs.js +1 -1
- package/cameras/CinematicCamera.js +3 -8
- package/controls/ArcballControls.cjs.js +1 -1
- package/controls/ArcballControls.d.ts +6 -9
- package/controls/ArcballControls.js +188 -234
- package/controls/FirstPersonControls.cjs.js +1 -1
- package/controls/FirstPersonControls.d.ts +4 -5
- package/controls/FirstPersonControls.js +36 -45
- package/controls/TransformControls.cjs.js +1 -1
- package/controls/TransformControls.d.ts +2 -1
- package/controls/TransformControls.js +25 -26
- package/exporters/GLTFExporter.cjs.js +1 -1
- package/exporters/GLTFExporter.js +9 -18
- package/geometries/TeapotGeometry.js +2 -2
- package/index.cjs.js +1 -1
- package/loaders/EXRLoader.cjs.js +1 -1
- package/loaders/EXRLoader.js +21 -10
- package/loaders/GLTFLoader.cjs.js +1 -1
- package/loaders/GLTFLoader.js +5 -6
- package/loaders/HDRCubeTextureLoader.cjs.js +1 -1
- package/loaders/HDRCubeTextureLoader.js +1 -3
- package/loaders/LDrawLoader.cjs.js +1 -1
- package/loaders/LDrawLoader.js +1450 -1105
- package/loaders/LUT3dlLoader.cjs.js +1 -1
- package/loaders/LUT3dlLoader.js +18 -11
- package/loaders/LUTCubeLoader.cjs.js +1 -1
- package/loaders/LUTCubeLoader.js +4 -5
- package/loaders/NodeMaterialLoader.cjs.js +1 -1
- package/loaders/PCDLoader.cjs.js +1 -1
- package/loaders/PCDLoader.js +2 -2
- package/loaders/RGBELoader.cjs.js +1 -1
- package/loaders/RGBELoader.js +6 -6
- package/loaders/STLLoader.js +7 -7
- package/loaders/VRMLLoader.cjs.js +1 -1
- package/loaders/VRMLLoader.js +10 -18
- package/modifiers/CurveModifier.cjs.js +1 -1
- package/modifiers/CurveModifier.js +9 -8
- package/nodes/accessors/CameraNode.js +12 -12
- package/nodes/accessors/PositionNode.js +3 -3
- package/nodes/accessors/ReflectNode.js +3 -3
- package/nodes/core/FunctionNode.js +3 -3
- package/nodes/core/InputNode.js +3 -3
- package/nodes/core/Node.js +6 -6
- package/nodes/core/TempNode.js +6 -6
- package/nodes/effects/BlurNode.js +3 -3
- package/nodes/math/MathNode.js +3 -3
- package/nodes/utils/VelocityNode.js +6 -6
- package/objects/Lensflare.cjs.js +1 -1
- package/objects/Lensflare.js +3 -11
- package/objects/Reflector.cjs.js +1 -1
- package/objects/Reflector.js +16 -12
- package/objects/ReflectorForSSRPass.cjs.js +1 -1
- package/objects/ReflectorForSSRPass.js +1 -9
- package/objects/Refractor.cjs.js +1 -1
- package/objects/Refractor.js +7 -12
- package/objects/Water.cjs.js +1 -1
- package/objects/Water.js +5 -16
- package/package.json +2 -2
- package/postprocessing/GlitchPass.cjs.js +1 -1
- package/postprocessing/GlitchPass.js +36 -33
- package/postprocessing/SMAAPass.cjs.js +1 -1
- package/postprocessing/SMAAPass.js +93 -96
- package/postprocessing/SSAOPass.cjs.js +1 -1
- package/postprocessing/SSAOPass.js +151 -152
- package/postprocessing/SavePass.cjs.js +1 -1
- package/postprocessing/SavePass.js +27 -28
- package/renderers/nodes/accessors/UVNode.js +1 -3
- package/renderers/nodes/core/AttributeNode.js +1 -3
- package/renderers/nodes/core/Node.js +4 -12
- package/renderers/nodes/core/NodeBuilder.js +6 -18
- package/renderers/webgpu/WebGPUTextures.cjs.js +1 -1
- package/renderers/webgpu/WebGPUTextures.js +1 -2
- package/utils/LDrawUtils.cjs.js +1 -0
- package/utils/LDrawUtils.js +144 -0
- package/webxr/ARButton.js +6 -6
- package/webxr/VRButton.js +6 -6
package/loaders/LDrawLoader.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Vector3, Loader, FileLoader,
|
1
|
+
import { Vector3, Ray, Loader, FileLoader, MeshStandardMaterial, LineBasicMaterial, ShaderMaterial, UniformsUtils, UniformsLib, Color, BufferGeometry, BufferAttribute, LineSegments, Mesh, Matrix4, Group } from 'three';
|
2
2
|
|
3
3
|
// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented
|
4
4
|
|
@@ -17,186 +17,335 @@ const FILE_LOCATION_TRY_MODELS = 3;
|
|
17
17
|
const FILE_LOCATION_TRY_RELATIVE = 4;
|
18
18
|
const FILE_LOCATION_TRY_ABSOLUTE = 5;
|
19
19
|
const FILE_LOCATION_NOT_FOUND = 6;
|
20
|
-
const
|
21
|
-
|
22
|
-
`
|
23
|
-
attribute vec3 control0;
|
24
|
-
attribute vec3 control1;
|
25
|
-
attribute vec3 direction;
|
26
|
-
varying float discardFlag;
|
27
|
-
|
28
|
-
#include <common>
|
29
|
-
#include <color_pars_vertex>
|
30
|
-
#include <fog_pars_vertex>
|
31
|
-
#include <logdepthbuf_pars_vertex>
|
32
|
-
#include <clipping_planes_pars_vertex>
|
33
|
-
void main() {
|
34
|
-
#include <color_vertex>
|
35
|
-
|
36
|
-
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
37
|
-
gl_Position = projectionMatrix * mvPosition;
|
38
|
-
|
39
|
-
// Transform the line segment ends and control points into camera clip space
|
40
|
-
vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 );
|
41
|
-
vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 );
|
42
|
-
vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
43
|
-
vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 );
|
44
|
-
|
45
|
-
c0.xy /= c0.w;
|
46
|
-
c1.xy /= c1.w;
|
47
|
-
p0.xy /= p0.w;
|
48
|
-
p1.xy /= p1.w;
|
49
|
-
|
50
|
-
// Get the direction of the segment and an orthogonal vector
|
51
|
-
vec2 dir = p1.xy - p0.xy;
|
52
|
-
vec2 norm = vec2( -dir.y, dir.x );
|
53
|
-
|
54
|
-
// Get control point directions from the line
|
55
|
-
vec2 c0dir = c0.xy - p1.xy;
|
56
|
-
vec2 c1dir = c1.xy - p1.xy;
|
57
|
-
|
58
|
-
// If the vectors to the controls points are pointed in different directions away
|
59
|
-
// from the line segment then the line should not be drawn.
|
60
|
-
float d0 = dot( normalize( norm ), normalize( c0dir ) );
|
61
|
-
float d1 = dot( normalize( norm ), normalize( c1dir ) );
|
62
|
-
discardFlag = float( sign( d0 ) != sign( d1 ) );
|
63
|
-
|
64
|
-
#include <logdepthbuf_vertex>
|
65
|
-
#include <clipping_planes_vertex>
|
66
|
-
#include <fog_vertex>
|
67
|
-
}
|
68
|
-
`;
|
69
|
-
const conditionalLineFragShader =
|
70
|
-
/* glsl */
|
71
|
-
`
|
72
|
-
uniform vec3 diffuse;
|
73
|
-
uniform float opacity;
|
74
|
-
varying float discardFlag;
|
75
|
-
|
76
|
-
#include <common>
|
77
|
-
#include <color_pars_fragment>
|
78
|
-
#include <fog_pars_fragment>
|
79
|
-
#include <logdepthbuf_pars_fragment>
|
80
|
-
#include <clipping_planes_pars_fragment>
|
81
|
-
void main() {
|
82
|
-
|
83
|
-
if ( discardFlag > 0.5 ) discard;
|
84
|
-
|
85
|
-
#include <clipping_planes_fragment>
|
86
|
-
vec3 outgoingLight = vec3( 0.0 );
|
87
|
-
vec4 diffuseColor = vec4( diffuse, opacity );
|
88
|
-
#include <logdepthbuf_fragment>
|
89
|
-
#include <color_fragment>
|
90
|
-
outgoingLight = diffuseColor.rgb; // simple shader
|
91
|
-
gl_FragColor = vec4( outgoingLight, diffuseColor.a );
|
92
|
-
#include <tonemapping_fragment>
|
93
|
-
#include <encodings_fragment>
|
94
|
-
#include <fog_fragment>
|
95
|
-
#include <premultiplied_alpha_fragment>
|
96
|
-
}
|
97
|
-
`;
|
20
|
+
const MAIN_COLOUR_CODE = '16';
|
21
|
+
const MAIN_EDGE_COLOUR_CODE = '24';
|
98
22
|
|
99
23
|
const _tempVec0 = new Vector3();
|
100
24
|
|
101
25
|
const _tempVec1 = new Vector3();
|
102
26
|
|
103
|
-
|
27
|
+
class LDrawConditionalLineMaterial extends ShaderMaterial {
|
28
|
+
constructor(parameters) {
|
29
|
+
super({
|
30
|
+
uniforms: UniformsUtils.merge([UniformsLib.fog, {
|
31
|
+
diffuse: {
|
32
|
+
value: new Color()
|
33
|
+
},
|
34
|
+
opacity: {
|
35
|
+
value: 1.0
|
36
|
+
}
|
37
|
+
}]),
|
38
|
+
vertexShader:
|
39
|
+
/* glsl */
|
40
|
+
`
|
41
|
+
attribute vec3 control0;
|
42
|
+
attribute vec3 control1;
|
43
|
+
attribute vec3 direction;
|
44
|
+
varying float discardFlag;
|
45
|
+
|
46
|
+
#include <common>
|
47
|
+
#include <color_pars_vertex>
|
48
|
+
#include <fog_pars_vertex>
|
49
|
+
#include <logdepthbuf_pars_vertex>
|
50
|
+
#include <clipping_planes_pars_vertex>
|
51
|
+
|
52
|
+
void main() {
|
53
|
+
#include <color_vertex>
|
54
|
+
|
55
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
56
|
+
gl_Position = projectionMatrix * mvPosition;
|
57
|
+
|
58
|
+
// Transform the line segment ends and control points into camera clip space
|
59
|
+
vec4 c0 = projectionMatrix * modelViewMatrix * vec4(control0, 1.0);
|
60
|
+
vec4 c1 = projectionMatrix * modelViewMatrix * vec4(control1, 1.0);
|
61
|
+
vec4 p0 = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
62
|
+
vec4 p1 = projectionMatrix * modelViewMatrix * vec4(position + direction, 1.0);
|
63
|
+
|
64
|
+
c0.xy /= c0.w;
|
65
|
+
c1.xy /= c1.w;
|
66
|
+
p0.xy /= p0.w;
|
67
|
+
p1.xy /= p1.w;
|
68
|
+
|
69
|
+
// Get the direction of the segment and an orthogonal vector
|
70
|
+
vec2 dir = p1.xy - p0.xy;
|
71
|
+
vec2 norm = vec2(-dir.y, dir.x);
|
72
|
+
|
73
|
+
// Get control point directions from the line
|
74
|
+
vec2 c0dir = c0.xy - p1.xy;
|
75
|
+
vec2 c1dir = c1.xy - p1.xy;
|
76
|
+
|
77
|
+
// If the vectors to the controls points are pointed in different directions away
|
78
|
+
// from the line segment then the line should not be drawn.
|
79
|
+
float d0 = dot(normalize(norm), normalize(c0dir));
|
80
|
+
float d1 = dot(normalize(norm), normalize(c1dir));
|
81
|
+
discardFlag = float(sign(d0) != sign(d1));
|
82
|
+
|
83
|
+
#include <logdepthbuf_vertex>
|
84
|
+
#include <clipping_planes_vertex>
|
85
|
+
#include <fog_vertex>
|
86
|
+
}
|
87
|
+
`,
|
88
|
+
fragmentShader:
|
89
|
+
/* glsl */
|
90
|
+
`
|
91
|
+
uniform vec3 diffuse;
|
92
|
+
uniform float opacity;
|
93
|
+
varying float discardFlag;
|
94
|
+
|
95
|
+
#include <common>
|
96
|
+
#include <color_pars_fragment>
|
97
|
+
#include <fog_pars_fragment>
|
98
|
+
#include <logdepthbuf_pars_fragment>
|
99
|
+
#include <clipping_planes_pars_fragment>
|
100
|
+
|
101
|
+
void main() {
|
102
|
+
if (discardFlag > 0.5) discard;
|
103
|
+
|
104
|
+
#include <clipping_planes_fragment>
|
105
|
+
vec3 outgoingLight = vec3(0.0);
|
106
|
+
vec4 diffuseColor = vec4(diffuse, opacity);
|
107
|
+
#include <logdepthbuf_fragment>
|
108
|
+
#include <color_fragment>
|
109
|
+
outgoingLight = diffuseColor.rgb; // simple shader
|
110
|
+
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
|
111
|
+
#include <tonemapping_fragment>
|
112
|
+
#include <encodings_fragment>
|
113
|
+
#include <fog_fragment>
|
114
|
+
#include <premultiplied_alpha_fragment>
|
115
|
+
}
|
116
|
+
`
|
117
|
+
});
|
118
|
+
Object.defineProperties(this, {
|
119
|
+
opacity: {
|
120
|
+
get: function () {
|
121
|
+
return this.uniforms.opacity.value;
|
122
|
+
},
|
123
|
+
set: function (value) {
|
124
|
+
this.uniforms.opacity.value = value;
|
125
|
+
}
|
126
|
+
},
|
127
|
+
color: {
|
128
|
+
get: function () {
|
129
|
+
return this.uniforms.diffuse.value;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
});
|
133
|
+
this.setValues(parameters);
|
134
|
+
this.isLDrawConditionalLineMaterial = true;
|
135
|
+
}
|
136
|
+
|
137
|
+
}
|
138
|
+
|
139
|
+
class ConditionalLineSegments extends LineSegments {
|
140
|
+
constructor(geometry, material) {
|
141
|
+
super(geometry, material);
|
142
|
+
this.isConditionalLine = true;
|
143
|
+
}
|
144
|
+
|
145
|
+
}
|
146
|
+
|
147
|
+
function generateFaceNormals(faces) {
|
148
|
+
for (let i = 0, l = faces.length; i < l; i++) {
|
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 Vector3().crossVectors(_tempVec0, _tempVec1).normalize();
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
const _ray = new Ray();
|
164
|
+
|
165
|
+
function smoothNormals(faces, lineSegments, checkSubSegments = false) {
|
166
|
+
// NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because
|
167
|
+
// it allows edges to be smoothed as expected (see minifig arms).
|
168
|
+
// --
|
169
|
+
// And the vector values are initialize multiplied by 1 + 1e-10 to account for floating
|
170
|
+
// point errors on vertices along quantization boundaries. Ie after matrix multiplication
|
171
|
+
// vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't
|
172
|
+
// get merged. This added epsilon attempts to push these error values to the same quantized
|
173
|
+
// value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169.
|
174
|
+
const hashMultiplier = (1 + 1e-10) * 1e2;
|
175
|
+
|
104
176
|
function hashVertex(v) {
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
const x = ~~(v.x * 1e2);
|
109
|
-
const y = ~~(v.y * 1e2);
|
110
|
-
const z = ~~(v.z * 1e2);
|
177
|
+
const x = ~~(v.x * hashMultiplier);
|
178
|
+
const y = ~~(v.y * hashMultiplier);
|
179
|
+
const z = ~~(v.z * hashMultiplier);
|
111
180
|
return `${x},${y},${z}`;
|
112
181
|
}
|
113
182
|
|
114
183
|
function hashEdge(v0, v1) {
|
115
184
|
return `${hashVertex(v0)}_${hashVertex(v1)}`;
|
185
|
+
} // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected
|
186
|
+
// onto the original line.
|
187
|
+
|
188
|
+
|
189
|
+
function toNormalizedRay(v0, v1, targetRay) {
|
190
|
+
targetRay.direction.subVectors(v1, v0).normalize();
|
191
|
+
const scalar = v0.dot(targetRay.direction);
|
192
|
+
targetRay.origin.copy(v0).addScaledVector(targetRay.direction, -scalar);
|
193
|
+
return targetRay;
|
194
|
+
}
|
195
|
+
|
196
|
+
function hashRay(ray) {
|
197
|
+
return hashEdge(ray.origin, ray.direction);
|
116
198
|
}
|
117
199
|
|
118
200
|
const hardEdges = new Set();
|
201
|
+
const hardEdgeRays = new Map();
|
119
202
|
const halfEdgeList = {};
|
120
|
-
const fullHalfEdgeList = {};
|
121
203
|
const normals = []; // Save the list of hard edges by hash
|
122
204
|
|
123
205
|
for (let i = 0, l = lineSegments.length; i < l; i++) {
|
124
206
|
const ls = lineSegments[i];
|
125
|
-
const
|
126
|
-
const
|
207
|
+
const vertices = ls.vertices;
|
208
|
+
const v0 = vertices[0];
|
209
|
+
const v1 = vertices[1];
|
127
210
|
hardEdges.add(hashEdge(v0, v1));
|
128
|
-
hardEdges.add(hashEdge(v1, v0));
|
211
|
+
hardEdges.add(hashEdge(v1, v0)); // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check
|
212
|
+
// and requires more memory.
|
213
|
+
|
214
|
+
if (checkSubSegments) {
|
215
|
+
// add both ray directions to the map
|
216
|
+
const ray = toNormalizedRay(v0, v1, new Ray());
|
217
|
+
const rh1 = hashRay(ray);
|
218
|
+
|
219
|
+
if (!hardEdgeRays.has(rh1)) {
|
220
|
+
toNormalizedRay(v1, v0, ray);
|
221
|
+
const rh2 = hashRay(ray);
|
222
|
+
const info = {
|
223
|
+
ray,
|
224
|
+
distances: []
|
225
|
+
};
|
226
|
+
hardEdgeRays.set(rh1, info);
|
227
|
+
hardEdgeRays.set(rh2, info);
|
228
|
+
} // store both segments ends in min, max order in the distances array to check if a face edge is a
|
229
|
+
// subsegment later.
|
230
|
+
|
231
|
+
|
232
|
+
const info = hardEdgeRays.get(rh1);
|
233
|
+
let d0 = info.ray.direction.dot(v0);
|
234
|
+
let d1 = info.ray.direction.dot(v1);
|
235
|
+
|
236
|
+
if (d0 > d1) {
|
237
|
+
[d0, d1] = [d1, d0];
|
238
|
+
}
|
239
|
+
|
240
|
+
info.distances.push(d0, d1);
|
241
|
+
}
|
129
242
|
} // track the half edges associated with each triangle
|
130
243
|
|
131
244
|
|
132
|
-
for (let i = 0, l =
|
133
|
-
const tri =
|
245
|
+
for (let i = 0, l = faces.length; i < l; i++) {
|
246
|
+
const tri = faces[i];
|
247
|
+
const vertices = tri.vertices;
|
248
|
+
const vertCount = vertices.length;
|
134
249
|
|
135
|
-
for (let i2 = 0
|
250
|
+
for (let i2 = 0; i2 < vertCount; i2++) {
|
136
251
|
const index = i2;
|
137
|
-
const next = (i2 + 1) %
|
138
|
-
const v0 =
|
139
|
-
const v1 =
|
252
|
+
const next = (i2 + 1) % vertCount;
|
253
|
+
const v0 = vertices[index];
|
254
|
+
const v1 = vertices[next];
|
140
255
|
const hash = hashEdge(v0, v1); // don't add the triangle if the edge is supposed to be hard
|
141
256
|
|
142
|
-
if (hardEdges.has(hash))
|
143
|
-
|
144
|
-
|
257
|
+
if (hardEdges.has(hash)) {
|
258
|
+
continue;
|
259
|
+
} // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds
|
260
|
+
|
261
|
+
|
262
|
+
if (checkSubSegments) {
|
263
|
+
toNormalizedRay(v0, v1, _ray);
|
264
|
+
const rayHash = hashRay(_ray);
|
265
|
+
|
266
|
+
if (hardEdgeRays.has(rayHash)) {
|
267
|
+
const info = hardEdgeRays.get(rayHash);
|
268
|
+
const {
|
269
|
+
ray,
|
270
|
+
distances
|
271
|
+
} = info;
|
272
|
+
let d0 = ray.direction.dot(v0);
|
273
|
+
let d1 = ray.direction.dot(v1);
|
274
|
+
|
275
|
+
if (d0 > d1) {
|
276
|
+
[d0, d1] = [d1, d0];
|
277
|
+
} // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals
|
278
|
+
|
279
|
+
|
280
|
+
let found = false;
|
281
|
+
|
282
|
+
for (let i = 0, l = distances.length; i < l; i += 2) {
|
283
|
+
if (d0 >= distances[i] && d1 <= distances[i + 1]) {
|
284
|
+
found = true;
|
285
|
+
break;
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
if (found) {
|
290
|
+
continue;
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
const info = {
|
296
|
+
index: index,
|
297
|
+
tri: tri
|
298
|
+
};
|
299
|
+
halfEdgeList[hash] = info;
|
145
300
|
}
|
146
|
-
} //
|
147
|
-
// quads provide more "influence" to some vertex normals than a triangle due to
|
148
|
-
// the fact that a quad is made up of two triangles and all triangles are weighted
|
149
|
-
// equally. To fix this quads could be tracked separately so their vertex normals
|
150
|
-
// are weighted appropriately or we could try only adding a normal direction
|
151
|
-
// once per normal.
|
152
|
-
// Iterate until we've tried to connect all triangles to share normals
|
301
|
+
} // Iterate until we've tried to connect all faces to share normals
|
153
302
|
|
154
303
|
|
155
304
|
while (true) {
|
156
|
-
// Stop if there are no more
|
157
|
-
|
158
|
-
if (halfEdges.length === 0) break; // Exhaustively find all connected triangles
|
305
|
+
// Stop if there are no more faces left
|
306
|
+
let halfEdge = null;
|
159
307
|
|
160
|
-
|
161
|
-
|
308
|
+
for (const key in halfEdgeList) {
|
309
|
+
halfEdge = halfEdgeList[key];
|
310
|
+
break;
|
311
|
+
}
|
162
312
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
i++;
|
167
|
-
const faceNormal = tri.faceNormal;
|
313
|
+
if (halfEdge === null) {
|
314
|
+
break;
|
315
|
+
} // Exhaustively find all connected faces
|
168
316
|
|
169
|
-
if (tri.n0 === null) {
|
170
|
-
tri.n0 = faceNormal.clone();
|
171
|
-
normals.push(tri.n0);
|
172
|
-
}
|
173
317
|
|
174
|
-
|
175
|
-
tri.n1 = faceNormal.clone();
|
176
|
-
normals.push(tri.n1);
|
177
|
-
}
|
318
|
+
const queue = [halfEdge];
|
178
319
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
320
|
+
while (queue.length > 0) {
|
321
|
+
// initialize all vertex normals in this triangle
|
322
|
+
const tri = queue.pop().tri;
|
323
|
+
const vertices = tri.vertices;
|
324
|
+
const vertNormals = tri.normals;
|
325
|
+
const faceNormal = tri.faceNormal; // Check if any edge is connected to another triangle edge
|
183
326
|
|
327
|
+
const vertCount = vertices.length;
|
184
328
|
|
185
|
-
for (let i2 = 0
|
329
|
+
for (let i2 = 0; i2 < vertCount; i2++) {
|
186
330
|
const index = i2;
|
187
|
-
const next = (i2 + 1) %
|
188
|
-
const v0 =
|
189
|
-
const v1 =
|
331
|
+
const next = (i2 + 1) % vertCount;
|
332
|
+
const v0 = vertices[index];
|
333
|
+
const v1 = vertices[next]; // delete this triangle from the list so it won't be found again
|
190
334
|
|
191
335
|
const hash = hashEdge(v0, v1);
|
192
336
|
delete halfEdgeList[hash];
|
193
337
|
const reverseHash = hashEdge(v1, v0);
|
194
|
-
const
|
195
|
-
|
196
|
-
if (
|
197
|
-
|
338
|
+
const otherInfo = halfEdgeList[reverseHash];
|
339
|
+
|
340
|
+
if (otherInfo) {
|
341
|
+
const otherTri = otherInfo.tri;
|
342
|
+
const otherIndex = otherInfo.index;
|
343
|
+
const otherNormals = otherTri.normals;
|
344
|
+
const otherVertCount = otherNormals.length;
|
345
|
+
const otherFaceNormal = otherTri.faceNormal; // NOTE: If the angle between faces is > 67.5 degrees then assume it's
|
198
346
|
// hard edge. There are some cases where the line segments do not line up exactly
|
199
347
|
// with or span multiple triangle edges (see Lunar Vehicle wheels).
|
348
|
+
|
200
349
|
if (Math.abs(otherTri.faceNormal.dot(tri.faceNormal)) < 0.25) {
|
201
350
|
continue;
|
202
351
|
} // if this triangle has already been traversed then it won't be in
|
@@ -205,33 +354,63 @@ function smoothNormals(triangles, lineSegments) {
|
|
205
354
|
|
206
355
|
|
207
356
|
if (reverseHash in halfEdgeList) {
|
208
|
-
queue.push(
|
357
|
+
queue.push(otherInfo);
|
209
358
|
delete halfEdgeList[reverseHash];
|
210
|
-
} //
|
359
|
+
} // share the first normal
|
360
|
+
|
211
361
|
|
362
|
+
const otherNext = (otherIndex + 1) % otherVertCount;
|
212
363
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
const otherV1 = otherTri[`v${otherNext}`];
|
218
|
-
const otherHash = hashEdge(otherV0, otherV1);
|
364
|
+
if (vertNormals[index] && otherNormals[otherNext] && vertNormals[index] !== otherNormals[otherNext]) {
|
365
|
+
otherNormals[otherNext].norm.add(vertNormals[index].norm);
|
366
|
+
vertNormals[index].norm = otherNormals[otherNext].norm;
|
367
|
+
}
|
219
368
|
|
220
|
-
|
221
|
-
if (otherTri[`n${otherIndex}`] === null) {
|
222
|
-
const norm = tri[`n${next}`];
|
223
|
-
otherTri[`n${otherIndex}`] = norm;
|
224
|
-
norm.add(otherTri.faceNormal);
|
225
|
-
}
|
369
|
+
let sharedNormal1 = vertNormals[index] || otherNormals[otherNext];
|
226
370
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
371
|
+
if (sharedNormal1 === null) {
|
372
|
+
// it's possible to encounter an edge of a triangle that has already been traversed meaning
|
373
|
+
// both edges already have different normals defined and shared. To work around this we create
|
374
|
+
// a wrapper object so when those edges are merged the normals can be updated everywhere.
|
375
|
+
sharedNormal1 = {
|
376
|
+
norm: new Vector3()
|
377
|
+
};
|
378
|
+
normals.push(sharedNormal1.norm);
|
379
|
+
}
|
232
380
|
|
233
|
-
|
234
|
-
|
381
|
+
if (vertNormals[index] === null) {
|
382
|
+
vertNormals[index] = sharedNormal1;
|
383
|
+
sharedNormal1.norm.add(faceNormal);
|
384
|
+
}
|
385
|
+
|
386
|
+
if (otherNormals[otherNext] === null) {
|
387
|
+
otherNormals[otherNext] = sharedNormal1;
|
388
|
+
sharedNormal1.norm.add(otherFaceNormal);
|
389
|
+
} // share the second normal
|
390
|
+
|
391
|
+
|
392
|
+
if (vertNormals[next] && otherNormals[otherIndex] && vertNormals[next] !== otherNormals[otherIndex]) {
|
393
|
+
otherNormals[otherIndex].norm.add(vertNormals[next].norm);
|
394
|
+
vertNormals[next].norm = otherNormals[otherIndex].norm;
|
395
|
+
}
|
396
|
+
|
397
|
+
let sharedNormal2 = vertNormals[next] || otherNormals[otherIndex];
|
398
|
+
|
399
|
+
if (sharedNormal2 === null) {
|
400
|
+
sharedNormal2 = {
|
401
|
+
norm: new Vector3()
|
402
|
+
};
|
403
|
+
normals.push(sharedNormal2.norm);
|
404
|
+
}
|
405
|
+
|
406
|
+
if (vertNormals[next] === null) {
|
407
|
+
vertNormals[next] = sharedNormal2;
|
408
|
+
sharedNormal2.norm.add(faceNormal);
|
409
|
+
}
|
410
|
+
|
411
|
+
if (otherNormals[otherIndex] === null) {
|
412
|
+
otherNormals[otherIndex] = sharedNormal2;
|
413
|
+
sharedNormal2.norm.add(otherFaceNormal);
|
235
414
|
}
|
236
415
|
}
|
237
416
|
}
|
@@ -244,6 +423,10 @@ function smoothNormals(triangles, lineSegments) {
|
|
244
423
|
}
|
245
424
|
}
|
246
425
|
|
426
|
+
function isPartType(type) {
|
427
|
+
return type === 'Part' || type === 'Unofficial_Part';
|
428
|
+
}
|
429
|
+
|
247
430
|
function isPrimitiveType(type) {
|
248
431
|
return /primitive/i.test(type) || type === 'Subpart';
|
249
432
|
}
|
@@ -287,6 +470,10 @@ class LineParser {
|
|
287
470
|
return this.line.substring(pos0, pos1);
|
288
471
|
}
|
289
472
|
|
473
|
+
getVector() {
|
474
|
+
return new Vector3(parseFloat(this.getToken()), parseFloat(this.getToken()), parseFloat(this.getToken()));
|
475
|
+
}
|
476
|
+
|
290
477
|
getRemainingString() {
|
291
478
|
return this.line.substring(this.currentCharIndex, this.lineLength);
|
292
479
|
}
|
@@ -303,1193 +490,1351 @@ class LineParser {
|
|
303
490
|
return this.lineNumber >= 0 ? ' at line ' + this.lineNumber : '';
|
304
491
|
}
|
305
492
|
|
306
|
-
}
|
493
|
+
} // Fetches and parses an intermediate representation of LDraw parts files.
|
307
494
|
|
308
|
-
|
309
|
-
|
310
|
-
|
495
|
+
|
496
|
+
class LDrawParsedCache {
|
497
|
+
constructor(loader) {
|
498
|
+
this.loader = loader;
|
499
|
+
this._cache = {};
|
311
500
|
}
|
312
501
|
|
313
|
-
|
314
|
-
|
502
|
+
cloneResult(original) {
|
503
|
+
const result = {}; // vertices are transformed and normals computed before being converted to geometry
|
504
|
+
// so these pieces must be cloned.
|
505
|
+
|
506
|
+
result.faces = original.faces.map(face => {
|
507
|
+
return {
|
508
|
+
colorCode: face.colorCode,
|
509
|
+
material: face.material,
|
510
|
+
vertices: face.vertices.map(v => v.clone()),
|
511
|
+
normals: face.normals.map(() => null),
|
512
|
+
faceNormal: null
|
513
|
+
};
|
514
|
+
});
|
515
|
+
result.conditionalSegments = original.conditionalSegments.map(face => {
|
516
|
+
return {
|
517
|
+
colorCode: face.colorCode,
|
518
|
+
material: face.material,
|
519
|
+
vertices: face.vertices.map(v => v.clone()),
|
520
|
+
controlPoints: face.controlPoints.map(v => v.clone())
|
521
|
+
};
|
522
|
+
});
|
523
|
+
result.lineSegments = original.lineSegments.map(face => {
|
524
|
+
return {
|
525
|
+
colorCode: face.colorCode,
|
526
|
+
material: face.material,
|
527
|
+
vertices: face.vertices.map(v => v.clone())
|
528
|
+
};
|
529
|
+
}); // none if this is subsequently modified
|
530
|
+
|
531
|
+
result.type = original.type;
|
532
|
+
result.category = original.category;
|
533
|
+
result.keywords = original.keywords;
|
534
|
+
result.subobjects = original.subobjects;
|
535
|
+
result.totalFaces = original.totalFaces;
|
536
|
+
result.startingConstructionStep = original.startingConstructionStep;
|
537
|
+
result.materials = original.materials;
|
538
|
+
result.group = null;
|
539
|
+
return result;
|
315
540
|
}
|
316
541
|
|
317
|
-
|
318
|
-
|
542
|
+
async fetchData(fileName) {
|
543
|
+
let triedLowerCase = false;
|
544
|
+
let locationState = FILE_LOCATION_AS_IS;
|
319
545
|
|
320
|
-
|
321
|
-
|
322
|
-
// With per face / segment material, implemented with mesh groups and materials array
|
323
|
-
// Sort the triangles or line segments by colour code to make later the mesh groups
|
324
|
-
elements.sort(sortByMaterial);
|
325
|
-
const positions = [];
|
326
|
-
const normals = [];
|
327
|
-
const materials = [];
|
328
|
-
const bufferGeometry = new BufferGeometry();
|
329
|
-
let prevMaterial = null;
|
330
|
-
let index0 = 0;
|
331
|
-
let numGroupVerts = 0;
|
546
|
+
while (locationState !== FILE_LOCATION_NOT_FOUND) {
|
547
|
+
let subobjectURL = fileName;
|
332
548
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
549
|
+
switch (locationState) {
|
550
|
+
case FILE_LOCATION_AS_IS:
|
551
|
+
locationState = locationState + 1;
|
552
|
+
break;
|
337
553
|
|
338
|
-
|
554
|
+
case FILE_LOCATION_TRY_PARTS:
|
555
|
+
subobjectURL = 'parts/' + subobjectURL;
|
556
|
+
locationState = locationState + 1;
|
557
|
+
break;
|
339
558
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
const n2 = elem.n2 || elem.faceNormal;
|
345
|
-
normals.push(n0.x, n0.y, n0.z);
|
346
|
-
normals.push(n1.x, n1.y, n1.z);
|
347
|
-
normals.push(n2.x, n2.y, n2.z);
|
348
|
-
}
|
559
|
+
case FILE_LOCATION_TRY_P:
|
560
|
+
subobjectURL = 'p/' + subobjectURL;
|
561
|
+
locationState = locationState + 1;
|
562
|
+
break;
|
349
563
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
564
|
+
case FILE_LOCATION_TRY_MODELS:
|
565
|
+
subobjectURL = 'models/' + subobjectURL;
|
566
|
+
locationState = locationState + 1;
|
567
|
+
break;
|
354
568
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
} else {
|
360
|
-
numGroupVerts += elementSize;
|
361
|
-
}
|
362
|
-
}
|
569
|
+
case FILE_LOCATION_TRY_RELATIVE:
|
570
|
+
subobjectURL = fileName.substring(0, fileName.lastIndexOf('/') + 1) + subobjectURL;
|
571
|
+
locationState = locationState + 1;
|
572
|
+
break;
|
363
573
|
|
364
|
-
|
365
|
-
|
366
|
-
|
574
|
+
case FILE_LOCATION_TRY_ABSOLUTE:
|
575
|
+
if (triedLowerCase) {
|
576
|
+
// Try absolute path
|
577
|
+
locationState = FILE_LOCATION_NOT_FOUND;
|
578
|
+
} else {
|
579
|
+
// Next attempt is lower case
|
580
|
+
fileName = fileName.toLowerCase();
|
581
|
+
subobjectURL = fileName;
|
582
|
+
triedLowerCase = true;
|
583
|
+
locationState = FILE_LOCATION_AS_IS;
|
584
|
+
}
|
367
585
|
|
368
|
-
|
586
|
+
break;
|
587
|
+
}
|
369
588
|
|
370
|
-
|
371
|
-
|
372
|
-
|
589
|
+
const loader = this.loader;
|
590
|
+
const fileLoader = new FileLoader(loader.manager);
|
591
|
+
fileLoader.setPath(loader.partsLibraryPath);
|
592
|
+
fileLoader.setRequestHeader(loader.requestHeader);
|
593
|
+
fileLoader.setWithCredentials(loader.withCredentials);
|
373
594
|
|
374
|
-
|
595
|
+
try {
|
596
|
+
const text = await fileLoader.loadAsync(subobjectURL);
|
597
|
+
return text;
|
598
|
+
} catch {
|
599
|
+
continue;
|
600
|
+
}
|
601
|
+
}
|
375
602
|
|
376
|
-
|
377
|
-
object3d = new LineSegments(bufferGeometry, materials);
|
378
|
-
} else if (elementSize === 3) {
|
379
|
-
object3d = new Mesh(bufferGeometry, materials);
|
603
|
+
throw new Error('LDrawLoader: Subobject "' + fileName + '" could not be loaded.');
|
380
604
|
}
|
381
605
|
|
382
|
-
|
383
|
-
|
384
|
-
const controlArray0 = new Float32Array(elements.length * 3 * 2);
|
385
|
-
const controlArray1 = new Float32Array(elements.length * 3 * 2);
|
386
|
-
const directionArray = new Float32Array(elements.length * 3 * 2);
|
606
|
+
parse(text, fileName = null) {
|
607
|
+
const loader = this.loader; // final results
|
387
608
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
const v1 = os.v1;
|
394
|
-
const index = i * 3 * 2;
|
395
|
-
controlArray0[index + 0] = c0.x;
|
396
|
-
controlArray0[index + 1] = c0.y;
|
397
|
-
controlArray0[index + 2] = c0.z;
|
398
|
-
controlArray0[index + 3] = c0.x;
|
399
|
-
controlArray0[index + 4] = c0.y;
|
400
|
-
controlArray0[index + 5] = c0.z;
|
401
|
-
controlArray1[index + 0] = c1.x;
|
402
|
-
controlArray1[index + 1] = c1.y;
|
403
|
-
controlArray1[index + 2] = c1.z;
|
404
|
-
controlArray1[index + 3] = c1.x;
|
405
|
-
controlArray1[index + 4] = c1.y;
|
406
|
-
controlArray1[index + 5] = c1.z;
|
407
|
-
directionArray[index + 0] = v1.x - v0.x;
|
408
|
-
directionArray[index + 1] = v1.y - v0.y;
|
409
|
-
directionArray[index + 2] = v1.z - v0.z;
|
410
|
-
directionArray[index + 3] = v1.x - v0.x;
|
411
|
-
directionArray[index + 4] = v1.y - v0.y;
|
412
|
-
directionArray[index + 5] = v1.z - v0.z;
|
413
|
-
}
|
609
|
+
const faces = [];
|
610
|
+
const lineSegments = [];
|
611
|
+
const conditionalSegments = [];
|
612
|
+
const subobjects = [];
|
613
|
+
const materials = {};
|
414
614
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
}
|
615
|
+
const getLocalMaterial = colorCode => {
|
616
|
+
return materials[colorCode] || null;
|
617
|
+
};
|
419
618
|
|
420
|
-
|
421
|
-
|
619
|
+
let type = 'Model';
|
620
|
+
let category = null;
|
621
|
+
let keywords = null;
|
622
|
+
let totalFaces = 0; // split into lines
|
422
623
|
|
624
|
+
if (text.indexOf('\r\n') !== -1) {
|
625
|
+
// This is faster than String.split with regex that splits on both
|
626
|
+
text = text.replace(/\r\n/g, '\n');
|
627
|
+
}
|
423
628
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
629
|
+
const lines = text.split('\n');
|
630
|
+
const numLines = lines.length;
|
631
|
+
let parsingEmbeddedFiles = false;
|
632
|
+
let currentEmbeddedFileName = null;
|
633
|
+
let currentEmbeddedText = null;
|
634
|
+
let bfcCertified = false;
|
635
|
+
let bfcCCW = true;
|
636
|
+
let bfcInverted = false;
|
637
|
+
let bfcCull = true;
|
638
|
+
let startingConstructionStep = false; // Parse all line commands
|
430
639
|
|
431
|
-
|
640
|
+
for (let lineIndex = 0; lineIndex < numLines; lineIndex++) {
|
641
|
+
const line = lines[lineIndex];
|
642
|
+
if (line.length === 0) continue;
|
432
643
|
|
433
|
-
|
434
|
-
|
644
|
+
if (parsingEmbeddedFiles) {
|
645
|
+
if (line.startsWith('0 FILE ')) {
|
646
|
+
// Save previous embedded file in the cache
|
647
|
+
this.setData(currentEmbeddedFileName, currentEmbeddedText); // New embedded text file
|
435
648
|
|
436
|
-
|
649
|
+
currentEmbeddedFileName = line.substring(7);
|
650
|
+
currentEmbeddedText = '';
|
651
|
+
} else {
|
652
|
+
currentEmbeddedText += line + '\n';
|
653
|
+
}
|
437
654
|
|
438
|
-
|
655
|
+
continue;
|
656
|
+
}
|
439
657
|
|
440
|
-
|
441
|
-
|
658
|
+
const lp = new LineParser(line, lineIndex + 1);
|
659
|
+
lp.seekNonSpace();
|
442
660
|
|
443
|
-
|
661
|
+
if (lp.isAtTheEnd()) {
|
662
|
+
// Empty line
|
663
|
+
continue;
|
664
|
+
} // Parse the line type
|
444
665
|
|
445
|
-
this.smoothNormals = true;
|
446
|
-
}
|
447
666
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
667
|
+
const lineType = lp.getToken();
|
668
|
+
let material;
|
669
|
+
let colorCode;
|
670
|
+
let segment;
|
671
|
+
let ccw;
|
672
|
+
let doubleSided;
|
673
|
+
let v0, v1, v2, v3, c0, c1;
|
452
674
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
fileLoader.load(url, function (text) {
|
459
|
-
scope.processObject(text, onLoad, null, url);
|
460
|
-
}, onProgress, onError);
|
461
|
-
}
|
675
|
+
switch (lineType) {
|
676
|
+
// Line type 0: Comment or META
|
677
|
+
case '0':
|
678
|
+
// Parse meta directive
|
679
|
+
const meta = lp.getToken();
|
462
680
|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
681
|
+
if (meta) {
|
682
|
+
switch (meta) {
|
683
|
+
case '!LDRAW_ORG':
|
684
|
+
type = lp.getToken();
|
685
|
+
break;
|
467
686
|
|
468
|
-
|
469
|
-
|
470
|
-
this.parseScopesStack = [];
|
471
|
-
this.newParseScopeLevel(materials);
|
472
|
-
this.getCurrentParseScope().isFromParse = false;
|
473
|
-
this.materials = materials;
|
474
|
-
return this;
|
475
|
-
}
|
687
|
+
case '!COLOUR':
|
688
|
+
material = loader.parseColorMetaDirective(lp);
|
476
689
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
690
|
+
if (material) {
|
691
|
+
materials[material.userData.code] = material;
|
692
|
+
} else {
|
693
|
+
console.warn('LDrawLoader: Error parsing material' + lp.getLineNumberString());
|
694
|
+
}
|
481
695
|
|
482
|
-
|
483
|
-
// Adds a new scope level, assign materials to it and returns it
|
484
|
-
const matLib = {};
|
696
|
+
break;
|
485
697
|
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
matLib[material.userData.code] = material;
|
490
|
-
}
|
491
|
-
}
|
698
|
+
case '!CATEGORY':
|
699
|
+
category = lp.getToken();
|
700
|
+
break;
|
492
701
|
|
493
|
-
|
494
|
-
|
495
|
-
lib: matLib,
|
496
|
-
url: null,
|
497
|
-
// Subobjects
|
498
|
-
subobjects: null,
|
499
|
-
numSubobjects: 0,
|
500
|
-
subobjectIndex: 0,
|
501
|
-
inverted: false,
|
502
|
-
category: null,
|
503
|
-
keywords: null,
|
504
|
-
// Current subobject
|
505
|
-
currentFileName: null,
|
506
|
-
mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
|
507
|
-
mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
|
508
|
-
currentMatrix: new Matrix4(),
|
509
|
-
matrix: new Matrix4(),
|
510
|
-
// If false, it is a root material scope previous to parse
|
511
|
-
isFromParse: true,
|
512
|
-
triangles: null,
|
513
|
-
lineSegments: null,
|
514
|
-
conditionalSegments: null,
|
515
|
-
// If true, this object is the start of a construction step
|
516
|
-
startingConstructionStep: false
|
517
|
-
};
|
518
|
-
this.parseScopesStack.push(newParseScope);
|
519
|
-
return newParseScope;
|
520
|
-
}
|
702
|
+
case '!KEYWORDS':
|
703
|
+
const newKeywords = lp.getRemainingString().split(',');
|
521
704
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
705
|
+
if (newKeywords.length > 0) {
|
706
|
+
if (!keywords) {
|
707
|
+
keywords = [];
|
708
|
+
}
|
526
709
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
if (!matLib[material.userData.code]) {
|
532
|
-
this.materials.push(material);
|
533
|
-
}
|
534
|
-
|
535
|
-
matLib[material.userData.code] = material;
|
536
|
-
return this;
|
537
|
-
}
|
710
|
+
newKeywords.forEach(function (keyword) {
|
711
|
+
keywords.push(keyword.trim());
|
712
|
+
});
|
713
|
+
}
|
538
714
|
|
539
|
-
|
540
|
-
// Given a colour code search its material in the parse scopes stack
|
541
|
-
if (colourCode.startsWith('0x2')) {
|
542
|
-
// Special 'direct' material value (RGB colour)
|
543
|
-
const colour = colourCode.substring(3);
|
544
|
-
return this.parseColourMetaDirective(new LineParser('Direct_Color_' + colour + ' CODE -1 VALUE #' + colour + ' EDGE #' + colour + ''));
|
545
|
-
}
|
715
|
+
break;
|
546
716
|
|
547
|
-
|
548
|
-
|
717
|
+
case 'FILE':
|
718
|
+
if (lineIndex > 0) {
|
719
|
+
// Start embedded text files parsing
|
720
|
+
parsingEmbeddedFiles = true;
|
721
|
+
currentEmbeddedFileName = lp.getRemainingString();
|
722
|
+
currentEmbeddedText = '';
|
723
|
+
bfcCertified = false;
|
724
|
+
bfcCCW = true;
|
725
|
+
}
|
549
726
|
|
550
|
-
|
551
|
-
return material;
|
552
|
-
}
|
553
|
-
} // Material was not found
|
727
|
+
break;
|
554
728
|
|
729
|
+
case 'BFC':
|
730
|
+
// Changes to the backface culling state
|
731
|
+
while (!lp.isAtTheEnd()) {
|
732
|
+
const token = lp.getToken();
|
555
733
|
|
556
|
-
|
557
|
-
|
734
|
+
switch (token) {
|
735
|
+
case 'CERTIFY':
|
736
|
+
case 'NOCERTIFY':
|
737
|
+
bfcCertified = token === 'CERTIFY';
|
738
|
+
bfcCCW = true;
|
739
|
+
break;
|
558
740
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
741
|
+
case 'CW':
|
742
|
+
case 'CCW':
|
743
|
+
bfcCCW = token === 'CCW';
|
744
|
+
break;
|
563
745
|
|
564
|
-
|
565
|
-
|
746
|
+
case 'INVERTNEXT':
|
747
|
+
bfcInverted = true;
|
748
|
+
break;
|
566
749
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
750
|
+
case 'CLIP':
|
751
|
+
case 'NOCLIP':
|
752
|
+
bfcCull = token === 'CLIP';
|
753
|
+
break;
|
571
754
|
|
572
|
-
|
573
|
-
|
755
|
+
default:
|
756
|
+
console.warn('THREE.LDrawLoader: BFC directive "' + token + '" is unknown.');
|
757
|
+
break;
|
758
|
+
}
|
759
|
+
}
|
574
760
|
|
575
|
-
|
576
|
-
// Parses a colour definition and returns a THREE.Material or null if error
|
577
|
-
let code = null; // Triangle and line colours
|
761
|
+
break;
|
578
762
|
|
579
|
-
|
580
|
-
|
763
|
+
case 'STEP':
|
764
|
+
startingConstructionStep = true;
|
765
|
+
break;
|
766
|
+
}
|
767
|
+
}
|
581
768
|
|
582
|
-
|
583
|
-
|
769
|
+
break;
|
770
|
+
// Line type 1: Sub-object file
|
584
771
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
772
|
+
case '1':
|
773
|
+
colorCode = lp.getToken();
|
774
|
+
material = getLocalMaterial(colorCode);
|
775
|
+
const posX = parseFloat(lp.getToken());
|
776
|
+
const posY = parseFloat(lp.getToken());
|
777
|
+
const posZ = parseFloat(lp.getToken());
|
778
|
+
const m0 = parseFloat(lp.getToken());
|
779
|
+
const m1 = parseFloat(lp.getToken());
|
780
|
+
const m2 = parseFloat(lp.getToken());
|
781
|
+
const m3 = parseFloat(lp.getToken());
|
782
|
+
const m4 = parseFloat(lp.getToken());
|
783
|
+
const m5 = parseFloat(lp.getToken());
|
784
|
+
const m6 = parseFloat(lp.getToken());
|
785
|
+
const m7 = parseFloat(lp.getToken());
|
786
|
+
const m8 = parseFloat(lp.getToken());
|
787
|
+
const matrix = new Matrix4().set(m0, m1, m2, posX, m3, m4, m5, posY, m6, m7, m8, posZ, 0, 0, 0, 1);
|
788
|
+
let fileName = lp.getRemainingString().trim().replace(/\\/g, '/');
|
590
789
|
|
591
|
-
|
592
|
-
|
593
|
-
|
790
|
+
if (loader.fileMap[fileName]) {
|
791
|
+
// Found the subobject path in the preloaded file path map
|
792
|
+
fileName = loader.fileMap[fileName];
|
793
|
+
} else {
|
794
|
+
// Standardized subfolders
|
795
|
+
if (fileName.startsWith('s/')) {
|
796
|
+
fileName = 'parts/' + fileName;
|
797
|
+
} else if (fileName.startsWith('48/')) {
|
798
|
+
fileName = 'p/' + fileName;
|
799
|
+
}
|
800
|
+
}
|
594
801
|
|
802
|
+
subobjects.push({
|
803
|
+
material: material,
|
804
|
+
colorCode: colorCode,
|
805
|
+
matrix: matrix,
|
806
|
+
fileName: fileName,
|
807
|
+
inverted: bfcInverted,
|
808
|
+
startingConstructionStep: startingConstructionStep
|
809
|
+
});
|
810
|
+
bfcInverted = false;
|
811
|
+
break;
|
812
|
+
// Line type 2: Line segment
|
595
813
|
|
596
|
-
|
814
|
+
case '2':
|
815
|
+
colorCode = lp.getToken();
|
816
|
+
material = getLocalMaterial(colorCode);
|
817
|
+
v0 = lp.getVector();
|
818
|
+
v1 = lp.getVector();
|
819
|
+
segment = {
|
820
|
+
material: material,
|
821
|
+
colorCode: colorCode,
|
822
|
+
vertices: [v0, v1]
|
823
|
+
};
|
824
|
+
lineSegments.push(segment);
|
825
|
+
break;
|
826
|
+
// Line type 5: Conditional Line segment
|
597
827
|
|
598
|
-
|
599
|
-
|
828
|
+
case '5':
|
829
|
+
colorCode = lp.getToken();
|
830
|
+
material = getLocalMaterial(colorCode);
|
831
|
+
v0 = lp.getVector();
|
832
|
+
v1 = lp.getVector();
|
833
|
+
c0 = lp.getVector();
|
834
|
+
c1 = lp.getVector();
|
835
|
+
segment = {
|
836
|
+
material: material,
|
837
|
+
colorCode: colorCode,
|
838
|
+
vertices: [v0, v1],
|
839
|
+
controlPoints: [c0, c1]
|
840
|
+
};
|
841
|
+
conditionalSegments.push(segment);
|
842
|
+
break;
|
843
|
+
// Line type 3: Triangle
|
600
844
|
|
601
|
-
|
602
|
-
|
603
|
-
|
845
|
+
case '3':
|
846
|
+
colorCode = lp.getToken();
|
847
|
+
material = getLocalMaterial(colorCode);
|
848
|
+
ccw = bfcCCW;
|
849
|
+
doubleSided = !bfcCertified || !bfcCull;
|
604
850
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
851
|
+
if (ccw === true) {
|
852
|
+
v0 = lp.getVector();
|
853
|
+
v1 = lp.getVector();
|
854
|
+
v2 = lp.getVector();
|
855
|
+
} else {
|
856
|
+
v2 = lp.getVector();
|
857
|
+
v1 = lp.getVector();
|
858
|
+
v0 = lp.getVector();
|
859
|
+
}
|
609
860
|
|
610
|
-
|
611
|
-
|
861
|
+
faces.push({
|
862
|
+
material: material,
|
863
|
+
colorCode: colorCode,
|
864
|
+
faceNormal: null,
|
865
|
+
vertices: [v0, v1, v2],
|
866
|
+
normals: [null, null, null]
|
867
|
+
});
|
868
|
+
totalFaces++;
|
612
869
|
|
613
|
-
if (
|
614
|
-
|
615
|
-
|
616
|
-
|
870
|
+
if (doubleSided === true) {
|
871
|
+
faces.push({
|
872
|
+
material: material,
|
873
|
+
colorCode: colorCode,
|
874
|
+
faceNormal: null,
|
875
|
+
vertices: [v2, v1, v0],
|
876
|
+
normals: [null, null, null]
|
877
|
+
});
|
878
|
+
totalFaces++;
|
617
879
|
}
|
618
880
|
|
619
881
|
break;
|
882
|
+
// Line type 4: Quadrilateral
|
620
883
|
|
621
|
-
case '
|
622
|
-
|
884
|
+
case '4':
|
885
|
+
colorCode = lp.getToken();
|
886
|
+
material = getLocalMaterial(colorCode);
|
887
|
+
ccw = bfcCCW;
|
888
|
+
doubleSided = !bfcCertified || !bfcCull;
|
623
889
|
|
624
|
-
if (
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
890
|
+
if (ccw === true) {
|
891
|
+
v0 = lp.getVector();
|
892
|
+
v1 = lp.getVector();
|
893
|
+
v2 = lp.getVector();
|
894
|
+
v3 = lp.getVector();
|
895
|
+
} else {
|
896
|
+
v3 = lp.getVector();
|
897
|
+
v2 = lp.getVector();
|
898
|
+
v1 = lp.getVector();
|
899
|
+
v0 = lp.getVector();
|
900
|
+
} // specifically place the triangle diagonal in the v0 and v1 slots so we can
|
901
|
+
// account for the doubling of vertices later when smoothing normals.
|
629
902
|
|
630
|
-
if (!edgeMaterial) {
|
631
|
-
throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + '.';
|
632
|
-
} // Get the edge material for this triangle material
|
633
903
|
|
904
|
+
faces.push({
|
905
|
+
material: material,
|
906
|
+
colorCode: colorCode,
|
907
|
+
faceNormal: null,
|
908
|
+
vertices: [v0, v1, v2, v3],
|
909
|
+
normals: [null, null, null, null]
|
910
|
+
});
|
911
|
+
totalFaces += 2;
|
634
912
|
|
635
|
-
|
913
|
+
if (doubleSided === true) {
|
914
|
+
faces.push({
|
915
|
+
material: material,
|
916
|
+
colorCode: colorCode,
|
917
|
+
faceNormal: null,
|
918
|
+
vertices: [v3, v2, v1, v0],
|
919
|
+
normals: [null, null, null, null]
|
920
|
+
});
|
921
|
+
totalFaces += 2;
|
636
922
|
}
|
637
923
|
|
638
924
|
break;
|
639
925
|
|
640
|
-
|
641
|
-
|
926
|
+
default:
|
927
|
+
throw new Error('LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.');
|
928
|
+
}
|
929
|
+
}
|
642
930
|
|
643
|
-
|
644
|
-
|
645
|
-
|
931
|
+
if (parsingEmbeddedFiles) {
|
932
|
+
this.setData(currentEmbeddedFileName, currentEmbeddedText);
|
933
|
+
}
|
646
934
|
|
647
|
-
|
935
|
+
return {
|
936
|
+
faces,
|
937
|
+
conditionalSegments,
|
938
|
+
lineSegments,
|
939
|
+
type,
|
940
|
+
category,
|
941
|
+
keywords,
|
942
|
+
subobjects,
|
943
|
+
totalFaces,
|
944
|
+
startingConstructionStep,
|
945
|
+
materials,
|
946
|
+
fileName,
|
947
|
+
group: null
|
948
|
+
};
|
949
|
+
} // returns an (optionally cloned) instance of the data
|
648
950
|
|
649
|
-
if (alpha < 1) {
|
650
|
-
isTransparent = true;
|
651
|
-
}
|
652
951
|
|
653
|
-
|
952
|
+
getData(fileName, clone = true) {
|
953
|
+
const key = fileName.toLowerCase();
|
954
|
+
const result = this._cache[key];
|
654
955
|
|
655
|
-
|
656
|
-
|
956
|
+
if (result === null || result instanceof Promise) {
|
957
|
+
return null;
|
958
|
+
}
|
657
959
|
|
658
|
-
|
659
|
-
|
660
|
-
|
960
|
+
if (clone) {
|
961
|
+
return this.cloneResult(result);
|
962
|
+
} else {
|
963
|
+
return result;
|
964
|
+
}
|
965
|
+
} // kicks off a fetch and parse of the requested data if it hasn't already been loaded. Returns when
|
966
|
+
// the data is ready to use and can be retrieved synchronously with "getData".
|
661
967
|
|
662
|
-
luminance = Math.max(0, Math.min(1, luminance / 255));
|
663
|
-
break;
|
664
968
|
|
665
|
-
|
666
|
-
|
667
|
-
break;
|
969
|
+
async ensureDataLoaded(fileName) {
|
970
|
+
const key = fileName.toLowerCase();
|
668
971
|
|
669
|
-
|
670
|
-
|
671
|
-
|
972
|
+
if (!(key in this._cache)) {
|
973
|
+
// replace the promise with a copy of the parsed data for immediate processing
|
974
|
+
this._cache[key] = this.fetchData(fileName).then(text => {
|
975
|
+
const info = this.parse(text, fileName);
|
976
|
+
this._cache[key] = info;
|
977
|
+
return info;
|
978
|
+
});
|
979
|
+
}
|
672
980
|
|
673
|
-
|
674
|
-
|
675
|
-
break;
|
981
|
+
await this._cache[key];
|
982
|
+
} // sets the data in the cache from parsed data
|
676
983
|
|
677
|
-
case 'MATTE_METALLIC':
|
678
|
-
finishType = FINISH_TYPE_MATTE_METALLIC;
|
679
|
-
break;
|
680
984
|
|
681
|
-
|
682
|
-
|
683
|
-
|
985
|
+
setData(fileName, text) {
|
986
|
+
const key = fileName.toLowerCase();
|
987
|
+
this._cache[key] = this.parse(text, fileName);
|
988
|
+
}
|
684
989
|
|
685
|
-
|
686
|
-
|
687
|
-
lineParser.setToEnd();
|
688
|
-
break;
|
990
|
+
} // returns the material for an associated color code. If the color code is 16 for a face or 24 for
|
991
|
+
// an edge then the passthroughColorCode is used.
|
689
992
|
|
690
|
-
default:
|
691
|
-
throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.';
|
692
|
-
}
|
693
|
-
}
|
694
993
|
|
695
|
-
|
994
|
+
function getMaterialFromCode(colorCode, parentColorCode, materialHierarchy, forEdge) {
|
995
|
+
const isPassthrough = !forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE;
|
696
996
|
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
color: colour,
|
701
|
-
roughness: 0.3,
|
702
|
-
envMapIntensity: 0.3,
|
703
|
-
metalness: 0
|
704
|
-
});
|
705
|
-
break;
|
997
|
+
if (isPassthrough) {
|
998
|
+
colorCode = parentColorCode;
|
999
|
+
}
|
706
1000
|
|
707
|
-
|
708
|
-
|
709
|
-
const specular = new Color(colour);
|
710
|
-
const hsl = specular.getHSL({
|
711
|
-
h: 0,
|
712
|
-
s: 0,
|
713
|
-
l: 0
|
714
|
-
});
|
715
|
-
hsl.h = (hsl.h + 0.5) % 1;
|
716
|
-
hsl.l = Math.min(1, hsl.l + (1 - hsl.l) * 0.7);
|
717
|
-
specular.setHSL(hsl.h, hsl.s, hsl.l);
|
718
|
-
material = new MeshPhongMaterial({
|
719
|
-
color: colour,
|
720
|
-
specular: specular,
|
721
|
-
shininess: 10,
|
722
|
-
reflectivity: 0.3
|
723
|
-
});
|
724
|
-
break;
|
1001
|
+
return materialHierarchy[colorCode] || null;
|
1002
|
+
} // Class used to parse and build LDraw parts as three.js objects and cache them if they're a "Part" type.
|
725
1003
|
|
726
|
-
case FINISH_TYPE_CHROME:
|
727
|
-
// Mirror finish surface
|
728
|
-
material = new MeshStandardMaterial({
|
729
|
-
color: colour,
|
730
|
-
roughness: 0,
|
731
|
-
metalness: 1
|
732
|
-
});
|
733
|
-
break;
|
734
1004
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
});
|
742
|
-
canHaveEnvMap = false;
|
743
|
-
break;
|
1005
|
+
class LDrawPartsGeometryCache {
|
1006
|
+
constructor(loader) {
|
1007
|
+
this.loader = loader;
|
1008
|
+
this.parseCache = new LDrawParsedCache(loader);
|
1009
|
+
this._cache = {};
|
1010
|
+
} // Convert the given file information into a mesh by processing subobjects.
|
744
1011
|
|
745
|
-
case FINISH_TYPE_MATTE_METALLIC:
|
746
|
-
// Brushed metal finish
|
747
|
-
material = new MeshStandardMaterial({
|
748
|
-
color: colour,
|
749
|
-
roughness: 0.8,
|
750
|
-
metalness: 0.4
|
751
|
-
});
|
752
|
-
break;
|
753
1012
|
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
metalness: 0.85
|
760
|
-
});
|
761
|
-
break;
|
762
|
-
}
|
1013
|
+
async processIntoMesh(info) {
|
1014
|
+
const loader = this.loader;
|
1015
|
+
const parseCache = this.parseCache;
|
1016
|
+
const faceMaterials = new Set(); // Processes the part subobject information to load child parts and merge geometry onto part
|
1017
|
+
// piece object.
|
763
1018
|
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
material.polygonOffset = true;
|
769
|
-
material.polygonOffsetFactor = 1;
|
770
|
-
material.userData.canHaveEnvMap = canHaveEnvMap;
|
1019
|
+
const processInfoSubobjects = async (info, subobject = null) => {
|
1020
|
+
const subobjects = info.subobjects;
|
1021
|
+
const promises = []; // Trigger load of all subobjects. If a subobject isn't a primitive then load it as a separate
|
1022
|
+
// group which lets instruction steps apply correctly.
|
771
1023
|
|
772
|
-
|
773
|
-
|
774
|
-
|
1024
|
+
for (let i = 0, l = subobjects.length; i < l; i++) {
|
1025
|
+
const subobject = subobjects[i];
|
1026
|
+
const promise = parseCache.ensureDataLoaded(subobject.fileName).then(() => {
|
1027
|
+
const subobjectInfo = parseCache.getData(subobject.fileName, false);
|
775
1028
|
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
opacity: alpha,
|
782
|
-
depthWrite: !isTransparent
|
783
|
-
});
|
784
|
-
edgeMaterial.userData.code = code;
|
785
|
-
edgeMaterial.name = name + ' - Edge';
|
786
|
-
edgeMaterial.userData.canHaveEnvMap = false; // This is the material used for conditional edges
|
787
|
-
|
788
|
-
edgeMaterial.userData.conditionalEdgeMaterial = new ShaderMaterial({
|
789
|
-
vertexShader: conditionalLineVertShader,
|
790
|
-
fragmentShader: conditionalLineFragShader,
|
791
|
-
uniforms: UniformsUtils.merge([UniformsLib.fog, {
|
792
|
-
diffuse: {
|
793
|
-
value: new Color(edgeColour)
|
794
|
-
},
|
795
|
-
opacity: {
|
796
|
-
value: alpha
|
1029
|
+
if (!isPrimitiveType(subobjectInfo.type)) {
|
1030
|
+
return this.loadModel(subobject.fileName).catch(error => {
|
1031
|
+
console.warn(error);
|
1032
|
+
return null;
|
1033
|
+
});
|
797
1034
|
}
|
798
|
-
}]),
|
799
|
-
fog: true,
|
800
|
-
transparent: isTransparent,
|
801
|
-
depthWrite: !isTransparent
|
802
|
-
});
|
803
|
-
edgeMaterial.userData.conditionalEdgeMaterial.userData.canHaveEnvMap = false;
|
804
|
-
}
|
805
1035
|
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
} //
|
1036
|
+
return processInfoSubobjects(parseCache.getData(subobject.fileName), subobject);
|
1037
|
+
});
|
1038
|
+
promises.push(promise);
|
1039
|
+
}
|
811
1040
|
|
1041
|
+
const group = new Group();
|
1042
|
+
group.userData.category = info.category;
|
1043
|
+
group.userData.keywords = info.keywords;
|
1044
|
+
info.group = group;
|
1045
|
+
const subobjectInfos = await Promise.all(promises);
|
1046
|
+
|
1047
|
+
for (let i = 0, l = subobjectInfos.length; i < l; i++) {
|
1048
|
+
const subobject = info.subobjects[i];
|
1049
|
+
const subobjectInfo = subobjectInfos[i];
|
1050
|
+
|
1051
|
+
if (subobjectInfo === null) {
|
1052
|
+
// the subobject failed to load
|
1053
|
+
continue;
|
1054
|
+
} // if the subobject was loaded as a separate group then apply the parent scopes materials
|
1055
|
+
|
1056
|
+
|
1057
|
+
if (subobjectInfo.isGroup) {
|
1058
|
+
const subobjectGroup = subobjectInfo;
|
1059
|
+
subobject.matrix.decompose(subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale);
|
1060
|
+
subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep;
|
1061
|
+
subobjectGroup.name = subobject.fileName;
|
1062
|
+
loader.applyMaterialsToMesh(subobjectGroup, subobject.colorCode, info.materials);
|
1063
|
+
group.add(subobjectGroup);
|
1064
|
+
continue;
|
1065
|
+
} // add the subobject group if it has children in case it has both children and primitives
|
1066
|
+
|
1067
|
+
|
1068
|
+
if (subobjectInfo.group.children.length) {
|
1069
|
+
group.add(subobjectInfo.group);
|
1070
|
+
} // transform the primitives into the local space of the parent piece and append them to
|
1071
|
+
// to the parent primitives list.
|
1072
|
+
|
1073
|
+
|
1074
|
+
const parentLineSegments = info.lineSegments;
|
1075
|
+
const parentConditionalSegments = info.conditionalSegments;
|
1076
|
+
const parentFaces = info.faces;
|
1077
|
+
const lineSegments = subobjectInfo.lineSegments;
|
1078
|
+
const conditionalSegments = subobjectInfo.conditionalSegments;
|
1079
|
+
const faces = subobjectInfo.faces;
|
1080
|
+
const matrix = subobject.matrix;
|
1081
|
+
const inverted = subobject.inverted;
|
1082
|
+
const matrixScaleInverted = matrix.determinant() < 0;
|
1083
|
+
const colorCode = subobject.colorCode;
|
1084
|
+
const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode;
|
812
1085
|
|
813
|
-
|
814
|
-
|
815
|
-
|
1086
|
+
for (let i = 0, l = lineSegments.length; i < l; i++) {
|
1087
|
+
const ls = lineSegments[i];
|
1088
|
+
const vertices = ls.vertices;
|
1089
|
+
vertices[0].applyMatrix4(matrix);
|
1090
|
+
vertices[1].applyMatrix4(matrix);
|
1091
|
+
ls.colorCode = ls.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : ls.colorCode;
|
1092
|
+
ls.material = ls.material || getMaterialFromCode(ls.colorCode, ls.colorCode, info.materials, true);
|
1093
|
+
parentLineSegments.push(ls);
|
1094
|
+
}
|
816
1095
|
|
817
|
-
|
818
|
-
|
819
|
-
|
1096
|
+
for (let i = 0, l = conditionalSegments.length; i < l; i++) {
|
1097
|
+
const os = conditionalSegments[i];
|
1098
|
+
const vertices = os.vertices;
|
1099
|
+
const controlPoints = os.controlPoints;
|
1100
|
+
vertices[0].applyMatrix4(matrix);
|
1101
|
+
vertices[1].applyMatrix4(matrix);
|
1102
|
+
controlPoints[0].applyMatrix4(matrix);
|
1103
|
+
controlPoints[1].applyMatrix4(matrix);
|
1104
|
+
os.colorCode = os.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : os.colorCode;
|
1105
|
+
os.material = os.material || getMaterialFromCode(os.colorCode, os.colorCode, info.materials, true);
|
1106
|
+
parentConditionalSegments.push(os);
|
1107
|
+
}
|
820
1108
|
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
const subobjects = [];
|
825
|
-
let category = null;
|
826
|
-
let keywords = null;
|
1109
|
+
for (let i = 0, l = faces.length; i < l; i++) {
|
1110
|
+
const tri = faces[i];
|
1111
|
+
const vertices = tri.vertices;
|
827
1112
|
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
}
|
1113
|
+
for (let i = 0, l = vertices.length; i < l; i++) {
|
1114
|
+
vertices[i].applyMatrix4(matrix);
|
1115
|
+
}
|
832
1116
|
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
let currentEmbeddedText = null;
|
838
|
-
let bfcCertified = false;
|
839
|
-
let bfcCCW = true;
|
840
|
-
let bfcInverted = false;
|
841
|
-
let bfcCull = true;
|
842
|
-
let type = '';
|
843
|
-
let startingConstructionStep = false;
|
844
|
-
const scope = this;
|
1117
|
+
tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode;
|
1118
|
+
tri.material = tri.material || getMaterialFromCode(tri.colorCode, colorCode, info.materials, false);
|
1119
|
+
faceMaterials.add(tri.colorCode); // If the scale of the object is negated then the triangle winding order
|
1120
|
+
// needs to be flipped.
|
845
1121
|
|
846
|
-
|
847
|
-
|
848
|
-
|
1122
|
+
if (matrixScaleInverted !== inverted) {
|
1123
|
+
vertices.reverse();
|
1124
|
+
}
|
849
1125
|
|
850
|
-
|
851
|
-
|
852
|
-
}
|
1126
|
+
parentFaces.push(tri);
|
1127
|
+
}
|
853
1128
|
|
854
|
-
|
855
|
-
|
856
|
-
|
1129
|
+
info.totalFaces += subobjectInfo.totalFaces;
|
1130
|
+
} // Apply the parent subobjects pass through material code to this object. This is done several times due
|
1131
|
+
// to material scoping.
|
857
1132
|
|
858
|
-
const material = scope.getMaterial(colourCode);
|
859
1133
|
|
860
|
-
if (
|
861
|
-
|
1134
|
+
if (subobject) {
|
1135
|
+
loader.applyMaterialsToMesh(group, subobject.colorCode, info.materials);
|
862
1136
|
}
|
863
1137
|
|
864
|
-
return
|
1138
|
+
return info;
|
1139
|
+
}; // Track material use to see if we need to use the normal smooth slow path for hard edges.
|
1140
|
+
|
1141
|
+
|
1142
|
+
for (let i = 0, l = info.faces; i < l; i++) {
|
1143
|
+
faceMaterials.add(info.faces[i].colorCode);
|
865
1144
|
}
|
866
1145
|
|
867
|
-
|
868
|
-
const v = new Vector3(parseFloat(lp.getToken()), parseFloat(lp.getToken()), parseFloat(lp.getToken()));
|
1146
|
+
await processInfoSubobjects(info);
|
869
1147
|
|
870
|
-
|
871
|
-
|
872
|
-
|
1148
|
+
if (loader.smoothNormals) {
|
1149
|
+
const checkSubSegments = faceMaterials.size > 1;
|
1150
|
+
generateFaceNormals(info.faces);
|
1151
|
+
smoothNormals(info.faces, info.lineSegments, checkSubSegments);
|
1152
|
+
} // Add the primitive objects and metadata.
|
873
1153
|
|
874
|
-
return v;
|
875
|
-
} // Parse all line commands
|
876
1154
|
|
1155
|
+
const group = info.group;
|
877
1156
|
|
878
|
-
|
879
|
-
|
880
|
-
|
1157
|
+
if (info.faces.length > 0) {
|
1158
|
+
group.add(createObject(info.faces, 3, false, info.totalFaces));
|
1159
|
+
}
|
881
1160
|
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
this.subobjectCache[currentEmbeddedFileName.toLowerCase()] = currentEmbeddedText; // New embedded text file
|
1161
|
+
if (info.lineSegments.length > 0) {
|
1162
|
+
group.add(createObject(info.lineSegments, 2));
|
1163
|
+
}
|
886
1164
|
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
currentEmbeddedText += line + '\n';
|
891
|
-
}
|
1165
|
+
if (info.conditionalSegments.length > 0) {
|
1166
|
+
group.add(createObject(info.conditionalSegments, 2, true));
|
1167
|
+
}
|
892
1168
|
|
893
|
-
|
894
|
-
|
1169
|
+
return group;
|
1170
|
+
}
|
895
1171
|
|
896
|
-
|
897
|
-
|
1172
|
+
hasCachedModel(fileName) {
|
1173
|
+
return fileName !== null && fileName.toLowerCase() in this._cache;
|
1174
|
+
}
|
898
1175
|
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
1176
|
+
async getCachedModel(fileName) {
|
1177
|
+
if (fileName !== null && this.hasCachedModel(fileName)) {
|
1178
|
+
const key = fileName.toLowerCase();
|
1179
|
+
const group = await this._cache[key];
|
1180
|
+
return group.clone();
|
1181
|
+
} else {
|
1182
|
+
return null;
|
1183
|
+
}
|
1184
|
+
} // Loads and parses the model with the given file name. Returns a cached copy if available.
|
903
1185
|
|
904
1186
|
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
let inverted;
|
909
|
-
let ccw;
|
910
|
-
let doubleSided;
|
911
|
-
let v0, v1, v2, v3, faceNormal;
|
1187
|
+
async loadModel(fileName) {
|
1188
|
+
const parseCache = this.parseCache;
|
1189
|
+
const key = fileName.toLowerCase();
|
912
1190
|
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
1191
|
+
if (this.hasCachedModel(fileName)) {
|
1192
|
+
// Return cached model if available.
|
1193
|
+
return this.getCachedModel(fileName);
|
1194
|
+
} else {
|
1195
|
+
// Otherwise parse a new model.
|
1196
|
+
// Ensure the file data is loaded and pre parsed.
|
1197
|
+
await parseCache.ensureDataLoaded(fileName);
|
1198
|
+
const info = parseCache.getData(fileName);
|
1199
|
+
const promise = this.processIntoMesh(info); // Now that the file has loaded it's possible that another part parse has been waiting in parallel
|
1200
|
+
// so check the cache again to see if it's been added since the last async operation so we don't
|
1201
|
+
// do unnecessary work.
|
918
1202
|
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
type = lp.getToken();
|
923
|
-
currentParseScope.triangles = [];
|
924
|
-
currentParseScope.lineSegments = [];
|
925
|
-
currentParseScope.conditionalSegments = [];
|
926
|
-
currentParseScope.type = type;
|
927
|
-
const isRoot = !parentParseScope.isFromParse;
|
1203
|
+
if (this.hasCachedModel(fileName)) {
|
1204
|
+
return this.getCachedModel(fileName);
|
1205
|
+
} // Cache object if it's a part so it can be reused later.
|
928
1206
|
|
929
|
-
if (isRoot || scope.separateObjects && !isPrimitiveType(type)) {
|
930
|
-
currentParseScope.groupObject = new Group();
|
931
|
-
currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
|
932
|
-
} // If the scale of the object is negated then the triangle winding order
|
933
|
-
// needs to be flipped.
|
934
1207
|
|
1208
|
+
if (isPartType(info.type)) {
|
1209
|
+
this._cache[key] = promise;
|
1210
|
+
} // return a copy
|
935
1211
|
|
936
|
-
if (currentParseScope.matrix.determinant() < 0 && (scope.separateObjects && isPrimitiveType(type) || !scope.separateObjects)) {
|
937
|
-
currentParseScope.inverted = !currentParseScope.inverted;
|
938
|
-
}
|
939
1212
|
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
1213
|
+
const group = await promise;
|
1214
|
+
return group.clone();
|
1215
|
+
}
|
1216
|
+
} // parses the given model text into a renderable object. Returns cached copy if available.
|
944
1217
|
|
945
|
-
case '!COLOUR':
|
946
|
-
material = this.parseColourMetaDirective(lp);
|
947
1218
|
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
console.warn('LDrawLoader: Error parsing material' + lp.getLineNumberString());
|
952
|
-
}
|
1219
|
+
async parseModel(text) {
|
1220
|
+
const parseCache = this.parseCache;
|
1221
|
+
const info = parseCache.parse(text);
|
953
1222
|
|
954
|
-
|
1223
|
+
if (isPartType(info.type) && this.hasCachedModel(info.fileName)) {
|
1224
|
+
return this.getCachedModel(info.fileName);
|
1225
|
+
}
|
955
1226
|
|
956
|
-
|
957
|
-
|
958
|
-
break;
|
1227
|
+
return this.processIntoMesh(info);
|
1228
|
+
}
|
959
1229
|
|
960
|
-
|
961
|
-
const newKeywords = lp.getRemainingString().split(',');
|
1230
|
+
}
|
962
1231
|
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
1232
|
+
function sortByMaterial(a, b) {
|
1233
|
+
if (a.colorCode === b.colorCode) {
|
1234
|
+
return 0;
|
1235
|
+
}
|
967
1236
|
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
}
|
1237
|
+
if (a.colorCode < b.colorCode) {
|
1238
|
+
return -1;
|
1239
|
+
}
|
972
1240
|
|
973
|
-
|
1241
|
+
return 1;
|
1242
|
+
}
|
974
1243
|
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
currentEmbeddedText = '';
|
981
|
-
bfcCertified = false;
|
982
|
-
bfcCCW = true;
|
983
|
-
}
|
1244
|
+
function createObject(elements, elementSize, isConditionalSegments = false, totalElements = null) {
|
1245
|
+
// Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 )
|
1246
|
+
// With per face / segment material, implemented with mesh groups and materials array
|
1247
|
+
// Sort the faces or line segments by color code to make later the mesh groups
|
1248
|
+
elements.sort(sortByMaterial);
|
984
1249
|
|
985
|
-
|
1250
|
+
if (totalElements === null) {
|
1251
|
+
totalElements = elements.length;
|
1252
|
+
}
|
986
1253
|
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
1254
|
+
const positions = new Float32Array(elementSize * totalElements * 3);
|
1255
|
+
const normals = elementSize === 3 ? new Float32Array(elementSize * totalElements * 3) : null;
|
1256
|
+
const materials = [];
|
1257
|
+
const quadArray = new Array(6);
|
1258
|
+
const bufferGeometry = new BufferGeometry();
|
1259
|
+
let prevMaterial = null;
|
1260
|
+
let index0 = 0;
|
1261
|
+
let numGroupVerts = 0;
|
1262
|
+
let offset = 0;
|
991
1263
|
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
1264
|
+
for (let iElem = 0, nElem = elements.length; iElem < nElem; iElem++) {
|
1265
|
+
const elem = elements[iElem];
|
1266
|
+
let vertices = elem.vertices;
|
1267
|
+
|
1268
|
+
if (vertices.length === 4) {
|
1269
|
+
quadArray[0] = vertices[0];
|
1270
|
+
quadArray[1] = vertices[1];
|
1271
|
+
quadArray[2] = vertices[2];
|
1272
|
+
quadArray[3] = vertices[0];
|
1273
|
+
quadArray[4] = vertices[2];
|
1274
|
+
quadArray[5] = vertices[3];
|
1275
|
+
vertices = quadArray;
|
1276
|
+
}
|
998
1277
|
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1278
|
+
for (let j = 0, l = vertices.length; j < l; j++) {
|
1279
|
+
const v = vertices[j];
|
1280
|
+
const index = offset + j * 3;
|
1281
|
+
positions[index + 0] = v.x;
|
1282
|
+
positions[index + 1] = v.y;
|
1283
|
+
positions[index + 2] = v.z;
|
1284
|
+
} // create the normals array if this is a set of faces
|
1003
1285
|
|
1004
|
-
case 'INVERTNEXT':
|
1005
|
-
bfcInverted = true;
|
1006
|
-
break;
|
1007
1286
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1287
|
+
if (elementSize === 3) {
|
1288
|
+
if (!elem.faceNormal) {
|
1289
|
+
const v0 = vertices[0];
|
1290
|
+
const v1 = vertices[1];
|
1291
|
+
const v2 = vertices[2];
|
1012
1292
|
|
1013
|
-
|
1014
|
-
console.warn('THREE.LDrawLoader: BFC directive "' + token + '" is unknown.');
|
1015
|
-
break;
|
1016
|
-
}
|
1017
|
-
}
|
1293
|
+
_tempVec0.subVectors(v1, v0);
|
1018
1294
|
|
1019
|
-
|
1295
|
+
_tempVec1.subVectors(v2, v1);
|
1020
1296
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
break;
|
1024
|
-
}
|
1025
|
-
}
|
1297
|
+
elem.faceNormal = new Vector3().crossVectors(_tempVec0, _tempVec1).normalize();
|
1298
|
+
}
|
1026
1299
|
|
1027
|
-
|
1028
|
-
// Line type 1: Sub-object file
|
1300
|
+
let elemNormals = elem.normals;
|
1029
1301
|
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
const m4 = parseFloat(lp.getToken());
|
1040
|
-
const m5 = parseFloat(lp.getToken());
|
1041
|
-
const m6 = parseFloat(lp.getToken());
|
1042
|
-
const m7 = parseFloat(lp.getToken());
|
1043
|
-
const m8 = parseFloat(lp.getToken());
|
1044
|
-
const matrix = new Matrix4().set(m0, m1, m2, posX, m3, m4, m5, posY, m6, m7, m8, posZ, 0, 0, 0, 1);
|
1045
|
-
let fileName = lp.getRemainingString().trim().replace(/\\/g, '/');
|
1302
|
+
if (elemNormals.length === 4) {
|
1303
|
+
quadArray[0] = elemNormals[0];
|
1304
|
+
quadArray[1] = elemNormals[1];
|
1305
|
+
quadArray[2] = elemNormals[2];
|
1306
|
+
quadArray[3] = elemNormals[0];
|
1307
|
+
quadArray[4] = elemNormals[2];
|
1308
|
+
quadArray[5] = elemNormals[3];
|
1309
|
+
elemNormals = quadArray;
|
1310
|
+
}
|
1046
1311
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1312
|
+
for (let j = 0, l = elemNormals.length; j < l; j++) {
|
1313
|
+
// use face normal if a vertex normal is not provided
|
1314
|
+
let n = elem.faceNormal;
|
1315
|
+
|
1316
|
+
if (elemNormals[j]) {
|
1317
|
+
n = elemNormals[j].norm;
|
1318
|
+
}
|
1319
|
+
|
1320
|
+
const index = offset + j * 3;
|
1321
|
+
normals[index + 0] = n.x;
|
1322
|
+
normals[index + 1] = n.y;
|
1323
|
+
normals[index + 2] = n.z;
|
1324
|
+
}
|
1325
|
+
}
|
1326
|
+
|
1327
|
+
if (prevMaterial !== elem.colorCode) {
|
1328
|
+
if (prevMaterial !== null) {
|
1329
|
+
bufferGeometry.addGroup(index0, numGroupVerts, materials.length - 1);
|
1330
|
+
}
|
1331
|
+
|
1332
|
+
const material = elem.material;
|
1333
|
+
|
1334
|
+
if (material !== null) {
|
1335
|
+
if (elementSize === 3) {
|
1336
|
+
materials.push(material);
|
1337
|
+
} else if (elementSize === 2) {
|
1338
|
+
if (material !== null) {
|
1339
|
+
if (isConditionalSegments) {
|
1340
|
+
materials.push(material.userData.edgeMaterial.userData.conditionalEdgeMaterial);
|
1341
|
+
} else {
|
1342
|
+
materials.push(material.userData.edgeMaterial);
|
1056
1343
|
}
|
1344
|
+
} else {
|
1345
|
+
materials.push(null);
|
1057
1346
|
}
|
1347
|
+
}
|
1348
|
+
} else {
|
1349
|
+
// If a material has not been made available yet then keep the color code string in the material array
|
1350
|
+
// to save the spot for the material once a parent scopes materials are being applied to the object.
|
1351
|
+
materials.push(elem.colorCode);
|
1352
|
+
}
|
1058
1353
|
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
url: null,
|
1066
|
-
triedLowerCase: false,
|
1067
|
-
inverted: bfcInverted !== currentParseScope.inverted,
|
1068
|
-
startingConstructionStep: startingConstructionStep
|
1069
|
-
});
|
1070
|
-
bfcInverted = false;
|
1071
|
-
break;
|
1072
|
-
// Line type 2: Line segment
|
1354
|
+
prevMaterial = elem.colorCode;
|
1355
|
+
index0 = offset / 3;
|
1356
|
+
numGroupVerts = vertices.length;
|
1357
|
+
} else {
|
1358
|
+
numGroupVerts += vertices.length;
|
1359
|
+
}
|
1073
1360
|
|
1074
|
-
|
1075
|
-
|
1076
|
-
segment = {
|
1077
|
-
material: material.userData.edgeMaterial,
|
1078
|
-
colourCode: material.userData.code,
|
1079
|
-
v0: parseVector(lp),
|
1080
|
-
v1: parseVector(lp)
|
1081
|
-
};
|
1082
|
-
lineSegments.push(segment);
|
1083
|
-
break;
|
1084
|
-
// Line type 5: Conditional Line segment
|
1361
|
+
offset += 3 * vertices.length;
|
1362
|
+
}
|
1085
1363
|
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
|
1090
|
-
colourCode: material.userData.code,
|
1091
|
-
v0: parseVector(lp),
|
1092
|
-
v1: parseVector(lp),
|
1093
|
-
c0: parseVector(lp),
|
1094
|
-
c1: parseVector(lp)
|
1095
|
-
};
|
1096
|
-
conditionalSegments.push(segment);
|
1097
|
-
break;
|
1098
|
-
// Line type 3: Triangle
|
1364
|
+
if (numGroupVerts > 0) {
|
1365
|
+
bufferGeometry.addGroup(index0, Infinity, materials.length - 1);
|
1366
|
+
}
|
1099
1367
|
|
1100
|
-
|
1101
|
-
material = parseColourCode(lp);
|
1102
|
-
inverted = currentParseScope.inverted;
|
1103
|
-
ccw = bfcCCW !== inverted;
|
1104
|
-
doubleSided = !bfcCertified || !bfcCull;
|
1368
|
+
bufferGeometry.setAttribute('position', new BufferAttribute(positions, 3));
|
1105
1369
|
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
v2 = parseVector(lp);
|
1110
|
-
} else {
|
1111
|
-
v2 = parseVector(lp);
|
1112
|
-
v1 = parseVector(lp);
|
1113
|
-
v0 = parseVector(lp);
|
1114
|
-
}
|
1370
|
+
if (normals !== null) {
|
1371
|
+
bufferGeometry.setAttribute('normal', new BufferAttribute(normals, 3));
|
1372
|
+
}
|
1115
1373
|
|
1116
|
-
|
1374
|
+
let object3d = null;
|
1117
1375
|
|
1118
|
-
|
1376
|
+
if (elementSize === 2) {
|
1377
|
+
if (isConditionalSegments) {
|
1378
|
+
object3d = new ConditionalLineSegments(bufferGeometry, materials.length === 1 ? materials[0] : materials);
|
1379
|
+
} else {
|
1380
|
+
object3d = new LineSegments(bufferGeometry, materials.length === 1 ? materials[0] : materials);
|
1381
|
+
}
|
1382
|
+
} else if (elementSize === 3) {
|
1383
|
+
object3d = new Mesh(bufferGeometry, materials.length === 1 ? materials[0] : materials);
|
1384
|
+
}
|
1119
1385
|
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
v1: v1,
|
1126
|
-
v2: v2,
|
1127
|
-
faceNormal: faceNormal,
|
1128
|
-
n0: null,
|
1129
|
-
n1: null,
|
1130
|
-
n2: null
|
1131
|
-
});
|
1386
|
+
if (isConditionalSegments) {
|
1387
|
+
object3d.isConditionalLine = true;
|
1388
|
+
const controlArray0 = new Float32Array(elements.length * 3 * 2);
|
1389
|
+
const controlArray1 = new Float32Array(elements.length * 3 * 2);
|
1390
|
+
const directionArray = new Float32Array(elements.length * 3 * 2);
|
1132
1391
|
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1392
|
+
for (let i = 0, l = elements.length; i < l; i++) {
|
1393
|
+
const os = elements[i];
|
1394
|
+
const vertices = os.vertices;
|
1395
|
+
const controlPoints = os.controlPoints;
|
1396
|
+
const c0 = controlPoints[0];
|
1397
|
+
const c1 = controlPoints[1];
|
1398
|
+
const v0 = vertices[0];
|
1399
|
+
const v1 = vertices[1];
|
1400
|
+
const index = i * 3 * 2;
|
1401
|
+
controlArray0[index + 0] = c0.x;
|
1402
|
+
controlArray0[index + 1] = c0.y;
|
1403
|
+
controlArray0[index + 2] = c0.z;
|
1404
|
+
controlArray0[index + 3] = c0.x;
|
1405
|
+
controlArray0[index + 4] = c0.y;
|
1406
|
+
controlArray0[index + 5] = c0.z;
|
1407
|
+
controlArray1[index + 0] = c1.x;
|
1408
|
+
controlArray1[index + 1] = c1.y;
|
1409
|
+
controlArray1[index + 2] = c1.z;
|
1410
|
+
controlArray1[index + 3] = c1.x;
|
1411
|
+
controlArray1[index + 4] = c1.y;
|
1412
|
+
controlArray1[index + 5] = c1.z;
|
1413
|
+
directionArray[index + 0] = v1.x - v0.x;
|
1414
|
+
directionArray[index + 1] = v1.y - v0.y;
|
1415
|
+
directionArray[index + 2] = v1.z - v0.z;
|
1416
|
+
directionArray[index + 3] = v1.x - v0.x;
|
1417
|
+
directionArray[index + 4] = v1.y - v0.y;
|
1418
|
+
directionArray[index + 5] = v1.z - v0.z;
|
1419
|
+
}
|
1420
|
+
|
1421
|
+
bufferGeometry.setAttribute('control0', new BufferAttribute(controlArray0, 3, false));
|
1422
|
+
bufferGeometry.setAttribute('control1', new BufferAttribute(controlArray1, 3, false));
|
1423
|
+
bufferGeometry.setAttribute('direction', new BufferAttribute(directionArray, 3, false));
|
1424
|
+
}
|
1425
|
+
|
1426
|
+
return object3d;
|
1427
|
+
} //
|
1428
|
+
|
1429
|
+
|
1430
|
+
class LDrawLoader extends Loader {
|
1431
|
+
constructor(manager) {
|
1432
|
+
super(manager); // Array of THREE.Material
|
1433
|
+
|
1434
|
+
this.materials = [];
|
1435
|
+
this.materialLibrary = {}; // This also allows to handle the embedded text files ("0 FILE" lines)
|
1436
|
+
|
1437
|
+
this.partsCache = new LDrawPartsGeometryCache(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.
|
1438
|
+
|
1439
|
+
this.fileMap = {}; // Initializes the materials library with default materials
|
1440
|
+
|
1441
|
+
this.setMaterials([]); // If this flag is set to true the vertex normals will be smoothed.
|
1442
|
+
|
1443
|
+
this.smoothNormals = true; // The path to load parts from the LDraw parts library from.
|
1444
|
+
|
1445
|
+
this.partsLibraryPath = '';
|
1446
|
+
}
|
1447
|
+
|
1448
|
+
setPartsLibraryPath(path) {
|
1449
|
+
this.partsLibraryPath = path;
|
1450
|
+
return this;
|
1451
|
+
}
|
1452
|
+
|
1453
|
+
async preloadMaterials(url) {
|
1454
|
+
const fileLoader = new FileLoader(this.manager);
|
1455
|
+
fileLoader.setPath(this.path);
|
1456
|
+
fileLoader.setRequestHeader(this.requestHeader);
|
1457
|
+
fileLoader.setWithCredentials(this.withCredentials);
|
1458
|
+
const text = await fileLoader.loadAsync(url);
|
1459
|
+
const colorLineRegex = /^0 !COLOUR/;
|
1460
|
+
const lines = text.split(/[\n\r]/g);
|
1461
|
+
const materials = [];
|
1462
|
+
|
1463
|
+
for (let i = 0, l = lines.length; i < l; i++) {
|
1464
|
+
const line = lines[i];
|
1465
|
+
|
1466
|
+
if (colorLineRegex.test(line)) {
|
1467
|
+
const directive = line.replace(colorLineRegex, '');
|
1468
|
+
const material = this.parseColorMetaDirective(new LineParser(directive));
|
1469
|
+
materials.push(material);
|
1470
|
+
}
|
1471
|
+
}
|
1146
1472
|
|
1147
|
-
|
1148
|
-
|
1473
|
+
this.setMaterials(materials);
|
1474
|
+
}
|
1149
1475
|
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1476
|
+
load(url, onLoad, onProgress, onError) {
|
1477
|
+
const fileLoader = new FileLoader(this.manager);
|
1478
|
+
fileLoader.setPath(this.path);
|
1479
|
+
fileLoader.setRequestHeader(this.requestHeader);
|
1480
|
+
fileLoader.setWithCredentials(this.withCredentials);
|
1481
|
+
fileLoader.load(url, text => {
|
1482
|
+
this.partsCache.parseModel(text, this.materialLibrary).then(group => {
|
1483
|
+
this.applyMaterialsToMesh(group, MAIN_COLOUR_CODE, this.materialLibrary, true);
|
1484
|
+
this.computeConstructionSteps(group);
|
1485
|
+
onLoad(group);
|
1486
|
+
}).catch(onError);
|
1487
|
+
}, onProgress, onError);
|
1488
|
+
}
|
1155
1489
|
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
v3 = parseVector(lp);
|
1163
|
-
v2 = parseVector(lp);
|
1164
|
-
v1 = parseVector(lp);
|
1165
|
-
v0 = parseVector(lp);
|
1166
|
-
}
|
1490
|
+
parse(text, onLoad) {
|
1491
|
+
this.partsCache.parseModel(text, this.materialLibrary).then(group => {
|
1492
|
+
this.computeConstructionSteps(group);
|
1493
|
+
onLoad(group);
|
1494
|
+
});
|
1495
|
+
}
|
1167
1496
|
|
1168
|
-
|
1497
|
+
setMaterials(materials) {
|
1498
|
+
this.materialLibrary = {};
|
1499
|
+
this.materials = [];
|
1169
1500
|
|
1170
|
-
|
1501
|
+
for (let i = 0, l = materials.length; i < l; i++) {
|
1502
|
+
this.addMaterial(materials[i]);
|
1503
|
+
} // Add default main triangle and line edge materials (used in pieces that can be colored with a main color)
|
1171
1504
|
|
1172
|
-
faceNormal = new Vector3().crossVectors(_tempVec0, _tempVec1).normalize();
|
1173
|
-
triangles.push({
|
1174
|
-
material: material,
|
1175
|
-
colourCode: material.userData.code,
|
1176
|
-
v0: v0,
|
1177
|
-
v1: v1,
|
1178
|
-
v2: v2,
|
1179
|
-
faceNormal: faceNormal,
|
1180
|
-
n0: null,
|
1181
|
-
n1: null,
|
1182
|
-
n2: null
|
1183
|
-
});
|
1184
|
-
triangles.push({
|
1185
|
-
material: material,
|
1186
|
-
colourCode: material.userData.code,
|
1187
|
-
v0: v0,
|
1188
|
-
v1: v2,
|
1189
|
-
v2: v3,
|
1190
|
-
faceNormal: faceNormal,
|
1191
|
-
n0: null,
|
1192
|
-
n1: null,
|
1193
|
-
n2: null
|
1194
|
-
});
|
1195
1505
|
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
v0: v0,
|
1201
|
-
v1: v2,
|
1202
|
-
v2: v1,
|
1203
|
-
faceNormal: faceNormal,
|
1204
|
-
n0: null,
|
1205
|
-
n1: null,
|
1206
|
-
n2: null
|
1207
|
-
});
|
1208
|
-
triangles.push({
|
1209
|
-
material: material,
|
1210
|
-
colourCode: material.userData.code,
|
1211
|
-
v0: v0,
|
1212
|
-
v1: v3,
|
1213
|
-
v2: v2,
|
1214
|
-
faceNormal: faceNormal,
|
1215
|
-
n0: null,
|
1216
|
-
n1: null,
|
1217
|
-
n2: null
|
1218
|
-
});
|
1219
|
-
}
|
1506
|
+
this.addMaterial(this.parseColorMetaDirective(new LineParser('Main_Colour CODE 16 VALUE #FF8080 EDGE #333333')));
|
1507
|
+
this.addMaterial(this.parseColorMetaDirective(new LineParser('Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333')));
|
1508
|
+
return this;
|
1509
|
+
}
|
1220
1510
|
|
1221
|
-
|
1511
|
+
setFileMap(fileMap) {
|
1512
|
+
this.fileMap = fileMap;
|
1513
|
+
return this;
|
1514
|
+
}
|
1222
1515
|
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
}
|
1516
|
+
addMaterial(material) {
|
1517
|
+
// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
|
1518
|
+
const matLib = this.materialLibrary;
|
1227
1519
|
|
1228
|
-
if (
|
1229
|
-
this.
|
1520
|
+
if (!matLib[material.userData.code]) {
|
1521
|
+
this.materials.push(material);
|
1522
|
+
matLib[material.userData.code] = material;
|
1230
1523
|
}
|
1231
1524
|
|
1232
|
-
|
1233
|
-
currentParseScope.keywords = keywords;
|
1234
|
-
currentParseScope.subobjects = subobjects;
|
1235
|
-
currentParseScope.numSubobjects = subobjects.length;
|
1236
|
-
currentParseScope.subobjectIndex = 0;
|
1525
|
+
return this;
|
1237
1526
|
}
|
1238
1527
|
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
stepNumber++;
|
1246
|
-
}
|
1528
|
+
getMaterial(colorCode) {
|
1529
|
+
if (colorCode.startsWith('0x2')) {
|
1530
|
+
// Special 'direct' material value (RGB color)
|
1531
|
+
const color = colorCode.substring(3);
|
1532
|
+
return this.parseColorMetaDirective(new LineParser('Direct_Color_' + color + ' CODE -1 VALUE #' + color + ' EDGE #' + color + ''));
|
1533
|
+
}
|
1247
1534
|
|
1248
|
-
|
1535
|
+
return this.materialLibrary[colorCode] || null;
|
1536
|
+
} // Applies the appropriate materials to a prebuilt hierarchy of geometry. Assumes that color codes are present
|
1537
|
+
// in the material array if they need to be filled in.
|
1538
|
+
|
1539
|
+
|
1540
|
+
applyMaterialsToMesh(group, parentColorCode, materialHierarchy, finalMaterialPass = false) {
|
1541
|
+
// find any missing materials as indicated by a color code string and replace it with a material from the current material lib
|
1542
|
+
const loader = this;
|
1543
|
+
const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE;
|
1544
|
+
group.traverse(c => {
|
1545
|
+
if (c.isMesh || c.isLineSegments) {
|
1546
|
+
if (Array.isArray(c.material)) {
|
1547
|
+
for (let i = 0, l = c.material.length; i < l; i++) {
|
1548
|
+
if (!c.material[i].isMaterial) {
|
1549
|
+
c.material[i] = getMaterial(c, c.material[i]);
|
1550
|
+
}
|
1551
|
+
}
|
1552
|
+
} else if (!c.material.isMaterial) {
|
1553
|
+
c.material = getMaterial(c, c.material);
|
1554
|
+
}
|
1555
|
+
}
|
1556
|
+
}); // Returns the appropriate material for the object (line or face) given color code. If the code is "pass through"
|
1557
|
+
// (24 for lines, 16 for edges) then the pass through color code is used. If that is also pass through then it's
|
1558
|
+
// simply returned for the subsequent material application.
|
1559
|
+
|
1560
|
+
function getMaterial(c, colorCode) {
|
1561
|
+
// if our parent is a passthrough color code and we don't have the current material color available then
|
1562
|
+
// return early.
|
1563
|
+
if (parentIsPassthrough && !(colorCode in materialHierarchy) && !finalMaterialPass) {
|
1564
|
+
return colorCode;
|
1249
1565
|
}
|
1250
|
-
});
|
1251
|
-
model.userData.numConstructionSteps = stepNumber + 1;
|
1252
|
-
}
|
1253
1566
|
|
1254
|
-
|
1255
|
-
|
1256
|
-
const parseScope = scope.newParseScopeLevel();
|
1257
|
-
parseScope.url = url;
|
1258
|
-
const parentParseScope = scope.getParentParseScope(); // Set current matrix
|
1567
|
+
const forEdge = c.isLineSegments || c.isConditionalLine;
|
1568
|
+
const isPassthrough = !forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE;
|
1259
1569
|
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
parseScope.inverted = subobject.inverted;
|
1264
|
-
parseScope.startingConstructionStep = subobject.startingConstructionStep;
|
1265
|
-
} // Add to cache
|
1570
|
+
if (isPassthrough) {
|
1571
|
+
colorCode = parentColorCode;
|
1572
|
+
}
|
1266
1573
|
|
1574
|
+
let material = null;
|
1267
1575
|
|
1268
|
-
|
1576
|
+
if (colorCode in materialHierarchy) {
|
1577
|
+
material = materialHierarchy[colorCode];
|
1578
|
+
} else if (finalMaterialPass) {
|
1579
|
+
// see if we can get the final material from from the "getMaterial" function which will attempt to
|
1580
|
+
// parse the "direct" colors
|
1581
|
+
material = loader.getMaterial(colorCode);
|
1269
1582
|
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1583
|
+
if (material === null) {
|
1584
|
+
// otherwise throw an error if this is final opportunity to set the material
|
1585
|
+
throw new Error(`LDrawLoader: Material properties for code ${colorCode} not available.`);
|
1586
|
+
}
|
1587
|
+
} else {
|
1588
|
+
return colorCode;
|
1589
|
+
}
|
1273
1590
|
|
1274
|
-
|
1275
|
-
|
1276
|
-
} // Parse the object (returns a Group)
|
1591
|
+
if (c.isLineSegments) {
|
1592
|
+
material = material.userData.edgeMaterial;
|
1277
1593
|
|
1594
|
+
if (c.isConditionalLine) {
|
1595
|
+
material = material.userData.conditionalEdgeMaterial;
|
1596
|
+
}
|
1597
|
+
}
|
1278
1598
|
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1599
|
+
return material;
|
1600
|
+
}
|
1601
|
+
}
|
1282
1602
|
|
1283
|
-
|
1284
|
-
|
1603
|
+
getMainMaterial() {
|
1604
|
+
return this.getMaterial(MAIN_COLOUR_CODE);
|
1605
|
+
}
|
1285
1606
|
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
// Once the previous subobject has finished we can start processing the next one in the list.
|
1290
|
-
// The subobject processing shares scope in processing so it's important that they be loaded serially
|
1291
|
-
// to avoid race conditions.
|
1292
|
-
// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
|
1293
|
-
// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
|
1294
|
-
// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
|
1295
|
-
const subobject = parseScope.subobjects[parseScope.subobjectIndex];
|
1296
|
-
Promise.resolve().then(function () {
|
1297
|
-
loadSubobject(subobject);
|
1298
|
-
});
|
1299
|
-
parseScope.subobjectIndex++;
|
1300
|
-
}
|
1301
|
-
}
|
1607
|
+
getMainEdgeMaterial() {
|
1608
|
+
return this.getMaterial(MAIN_EDGE_COLOUR_CODE);
|
1609
|
+
}
|
1302
1610
|
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
}
|
1611
|
+
parseColorMetaDirective(lineParser) {
|
1612
|
+
// Parses a color definition and returns a THREE.Material
|
1613
|
+
let code = null; // Triangle and line colors
|
1307
1614
|
|
1308
|
-
|
1615
|
+
let color = 0xff00ff;
|
1616
|
+
let edgeColor = 0xff00ff; // Transparency
|
1309
1617
|
|
1310
|
-
|
1311
|
-
|
1618
|
+
let alpha = 1;
|
1619
|
+
let isTransparent = false; // Self-illumination:
|
1312
1620
|
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1621
|
+
let luminance = 0;
|
1622
|
+
let finishType = FINISH_TYPE_DEFAULT;
|
1623
|
+
let edgeMaterial = null;
|
1624
|
+
const name = lineParser.getToken();
|
1316
1625
|
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1626
|
+
if (!name) {
|
1627
|
+
throw new Error('LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.');
|
1628
|
+
} // Parse tag tokens and their parameters
|
1320
1629
|
|
1321
|
-
if (parseScope.conditionalSegments.length > 0) {
|
1322
|
-
objGroup.add(createObject(parseScope.conditionalSegments, 2, true));
|
1323
|
-
}
|
1324
1630
|
|
1325
|
-
|
1326
|
-
objGroup.name = parseScope.fileName;
|
1327
|
-
objGroup.userData.category = parseScope.category;
|
1328
|
-
objGroup.userData.keywords = parseScope.keywords;
|
1329
|
-
parseScope.matrix.decompose(objGroup.position, objGroup.quaternion, objGroup.scale);
|
1330
|
-
parentParseScope.groupObject.add(objGroup);
|
1331
|
-
}
|
1332
|
-
} else {
|
1333
|
-
const separateObjects = scope.separateObjects;
|
1334
|
-
const parentLineSegments = parentParseScope.lineSegments;
|
1335
|
-
const parentConditionalSegments = parentParseScope.conditionalSegments;
|
1336
|
-
const parentTriangles = parentParseScope.triangles;
|
1337
|
-
const lineSegments = parseScope.lineSegments;
|
1338
|
-
const conditionalSegments = parseScope.conditionalSegments;
|
1339
|
-
const triangles = parseScope.triangles;
|
1631
|
+
let token = null;
|
1340
1632
|
|
1341
|
-
|
1342
|
-
|
1633
|
+
while (true) {
|
1634
|
+
token = lineParser.getToken();
|
1343
1635
|
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
}
|
1636
|
+
if (!token) {
|
1637
|
+
break;
|
1638
|
+
}
|
1348
1639
|
|
1349
|
-
|
1350
|
-
|
1640
|
+
switch (token.toUpperCase()) {
|
1641
|
+
case 'CODE':
|
1642
|
+
code = lineParser.getToken();
|
1643
|
+
break;
|
1351
1644
|
|
1352
|
-
|
1353
|
-
|
1645
|
+
case 'VALUE':
|
1646
|
+
color = lineParser.getToken();
|
1354
1647
|
|
1355
|
-
if (
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
os.c1.applyMatrix4(parseScope.matrix);
|
1648
|
+
if (color.startsWith('0x')) {
|
1649
|
+
color = '#' + color.substring(2);
|
1650
|
+
} else if (!color.startsWith('#')) {
|
1651
|
+
throw new Error('LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.');
|
1360
1652
|
}
|
1361
1653
|
|
1362
|
-
|
1363
|
-
}
|
1654
|
+
break;
|
1364
1655
|
|
1365
|
-
|
1366
|
-
|
1656
|
+
case 'EDGE':
|
1657
|
+
edgeColor = lineParser.getToken();
|
1367
1658
|
|
1368
|
-
if (
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1659
|
+
if (edgeColor.startsWith('0x')) {
|
1660
|
+
edgeColor = '#' + edgeColor.substring(2);
|
1661
|
+
} else if (!edgeColor.startsWith('#')) {
|
1662
|
+
// Try to see if edge color is a color code
|
1663
|
+
edgeMaterial = this.getMaterial(edgeColor);
|
1372
1664
|
|
1373
|
-
|
1665
|
+
if (!edgeMaterial) {
|
1666
|
+
throw new Error('LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.');
|
1667
|
+
} // Get the edge material for this triangle material
|
1374
1668
|
|
1375
|
-
_tempVec1.subVectors(tri.v2, tri.v1);
|
1376
1669
|
|
1377
|
-
|
1670
|
+
edgeMaterial = edgeMaterial.userData.edgeMaterial;
|
1378
1671
|
}
|
1379
1672
|
|
1380
|
-
|
1381
|
-
}
|
1382
|
-
}
|
1383
|
-
|
1384
|
-
scope.removeScopeLevel(); // If it is root object, compute construction steps
|
1673
|
+
break;
|
1385
1674
|
|
1386
|
-
|
1387
|
-
|
1388
|
-
}
|
1675
|
+
case 'ALPHA':
|
1676
|
+
alpha = parseInt(lineParser.getToken());
|
1389
1677
|
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
}
|
1678
|
+
if (isNaN(alpha)) {
|
1679
|
+
throw new Error('LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.');
|
1680
|
+
}
|
1394
1681
|
|
1395
|
-
|
1396
|
-
parseScope.mainColourCode = subobject.material.userData.code;
|
1397
|
-
parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
|
1398
|
-
parseScope.currentFileName = subobject.originalFileName; // If subobject was cached previously, use the cached one
|
1682
|
+
alpha = Math.max(0, Math.min(1, alpha / 255));
|
1399
1683
|
|
1400
|
-
|
1684
|
+
if (alpha < 1) {
|
1685
|
+
isTransparent = true;
|
1686
|
+
}
|
1401
1687
|
|
1402
|
-
|
1403
|
-
scope.processObject(cached, function (subobjectGroup) {
|
1404
|
-
onSubobjectLoaded(subobjectGroup, subobject);
|
1405
|
-
onSubobjectFinish();
|
1406
|
-
}, subobject, url);
|
1407
|
-
return;
|
1408
|
-
} // Adjust file name to locate the subobject file path in standard locations (always under directory scope.path)
|
1409
|
-
// Update also subobject.locationState for the next try if this load fails.
|
1688
|
+
break;
|
1410
1689
|
|
1690
|
+
case 'LUMINANCE':
|
1691
|
+
luminance = parseInt(lineParser.getToken());
|
1411
1692
|
|
1412
|
-
|
1413
|
-
|
1693
|
+
if (isNaN(luminance)) {
|
1694
|
+
throw new Error('LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.');
|
1695
|
+
}
|
1414
1696
|
|
1415
|
-
|
1416
|
-
case FILE_LOCATION_AS_IS:
|
1417
|
-
newLocationState = subobject.locationState + 1;
|
1697
|
+
luminance = Math.max(0, Math.min(1, luminance / 255));
|
1418
1698
|
break;
|
1419
1699
|
|
1420
|
-
case
|
1421
|
-
|
1422
|
-
newLocationState = subobject.locationState + 1;
|
1700
|
+
case 'CHROME':
|
1701
|
+
finishType = FINISH_TYPE_CHROME;
|
1423
1702
|
break;
|
1424
1703
|
|
1425
|
-
case
|
1426
|
-
|
1427
|
-
newLocationState = subobject.locationState + 1;
|
1704
|
+
case 'PEARLESCENT':
|
1705
|
+
finishType = FINISH_TYPE_PEARLESCENT;
|
1428
1706
|
break;
|
1429
1707
|
|
1430
|
-
case
|
1431
|
-
|
1432
|
-
newLocationState = subobject.locationState + 1;
|
1708
|
+
case 'RUBBER':
|
1709
|
+
finishType = FINISH_TYPE_RUBBER;
|
1433
1710
|
break;
|
1434
1711
|
|
1435
|
-
case
|
1436
|
-
|
1437
|
-
newLocationState = subobject.locationState + 1;
|
1712
|
+
case 'MATTE_METALLIC':
|
1713
|
+
finishType = FINISH_TYPE_MATTE_METALLIC;
|
1438
1714
|
break;
|
1439
1715
|
|
1440
|
-
case
|
1441
|
-
|
1442
|
-
|
1443
|
-
newLocationState = FILE_LOCATION_NOT_FOUND;
|
1444
|
-
} else {
|
1445
|
-
// Next attempt is lower case
|
1446
|
-
subobject.fileName = subobject.fileName.toLowerCase();
|
1447
|
-
subobjectURL = subobject.fileName;
|
1448
|
-
subobject.triedLowerCase = true;
|
1449
|
-
newLocationState = FILE_LOCATION_AS_IS;
|
1450
|
-
}
|
1716
|
+
case 'METAL':
|
1717
|
+
finishType = FINISH_TYPE_METAL;
|
1718
|
+
break;
|
1451
1719
|
|
1720
|
+
case 'MATERIAL':
|
1721
|
+
// Not implemented
|
1722
|
+
lineParser.setToEnd();
|
1452
1723
|
break;
|
1453
1724
|
|
1454
|
-
|
1455
|
-
|
1456
|
-
console.warn('LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.');
|
1457
|
-
return;
|
1725
|
+
default:
|
1726
|
+
throw new Error('LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.');
|
1458
1727
|
}
|
1728
|
+
}
|
1729
|
+
|
1730
|
+
let material = null;
|
1731
|
+
|
1732
|
+
switch (finishType) {
|
1733
|
+
case FINISH_TYPE_DEFAULT:
|
1734
|
+
material = new MeshStandardMaterial({
|
1735
|
+
color: color,
|
1736
|
+
roughness: 0.3,
|
1737
|
+
metalness: 0
|
1738
|
+
});
|
1739
|
+
break;
|
1740
|
+
|
1741
|
+
case FINISH_TYPE_PEARLESCENT:
|
1742
|
+
// Try to imitate pearlescency by making the surface glossy
|
1743
|
+
material = new MeshStandardMaterial({
|
1744
|
+
color: color,
|
1745
|
+
roughness: 0.3,
|
1746
|
+
metalness: 0.25
|
1747
|
+
});
|
1748
|
+
break;
|
1749
|
+
|
1750
|
+
case FINISH_TYPE_CHROME:
|
1751
|
+
// Mirror finish surface
|
1752
|
+
material = new MeshStandardMaterial({
|
1753
|
+
color: color,
|
1754
|
+
roughness: 0,
|
1755
|
+
metalness: 1
|
1756
|
+
});
|
1757
|
+
break;
|
1758
|
+
|
1759
|
+
case FINISH_TYPE_RUBBER:
|
1760
|
+
// Rubber finish
|
1761
|
+
material = new MeshStandardMaterial({
|
1762
|
+
color: color,
|
1763
|
+
roughness: 0.9,
|
1764
|
+
metalness: 0
|
1765
|
+
});
|
1766
|
+
break;
|
1767
|
+
|
1768
|
+
case FINISH_TYPE_MATTE_METALLIC:
|
1769
|
+
// Brushed metal finish
|
1770
|
+
material = new MeshStandardMaterial({
|
1771
|
+
color: color,
|
1772
|
+
roughness: 0.8,
|
1773
|
+
metalness: 0.4
|
1774
|
+
});
|
1775
|
+
break;
|
1459
1776
|
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
fileLoader.setWithCredentials(scope.withCredentials);
|
1469
|
-
fileLoader.load(subobjectURL, function (text) {
|
1470
|
-
scope.processObject(text, function (subobjectGroup) {
|
1471
|
-
onSubobjectLoaded(subobjectGroup, subobject);
|
1472
|
-
onSubobjectFinish();
|
1473
|
-
}, subobject, url);
|
1474
|
-
}, undefined, function (err) {
|
1475
|
-
onSubobjectError(err, subobject);
|
1476
|
-
}, subobject);
|
1777
|
+
case FINISH_TYPE_METAL:
|
1778
|
+
// Average metal finish
|
1779
|
+
material = new MeshStandardMaterial({
|
1780
|
+
color: color,
|
1781
|
+
roughness: 0.2,
|
1782
|
+
metalness: 0.85
|
1783
|
+
});
|
1784
|
+
break;
|
1477
1785
|
}
|
1478
1786
|
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1787
|
+
material.transparent = isTransparent;
|
1788
|
+
material.premultipliedAlpha = true;
|
1789
|
+
material.opacity = alpha;
|
1790
|
+
material.depthWrite = !isTransparent;
|
1791
|
+
material.polygonOffset = true;
|
1792
|
+
material.polygonOffsetFactor = 1;
|
1485
1793
|
|
1486
|
-
|
1794
|
+
if (luminance !== 0) {
|
1795
|
+
material.emissive.set(material.color).multiplyScalar(luminance);
|
1487
1796
|
}
|
1488
1797
|
|
1489
|
-
|
1490
|
-
//
|
1491
|
-
|
1798
|
+
if (!edgeMaterial) {
|
1799
|
+
// This is the material used for edges
|
1800
|
+
edgeMaterial = new LineBasicMaterial({
|
1801
|
+
color: edgeColor,
|
1802
|
+
transparent: isTransparent,
|
1803
|
+
opacity: alpha,
|
1804
|
+
depthWrite: !isTransparent
|
1805
|
+
});
|
1806
|
+
edgeMaterial.userData.code = code;
|
1807
|
+
edgeMaterial.name = name + ' - Edge'; // This is the material used for conditional edges
|
1808
|
+
|
1809
|
+
edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial({
|
1810
|
+
fog: true,
|
1811
|
+
transparent: isTransparent,
|
1812
|
+
depthWrite: !isTransparent,
|
1813
|
+
color: edgeColor,
|
1814
|
+
opacity: alpha
|
1815
|
+
});
|
1492
1816
|
}
|
1817
|
+
|
1818
|
+
material.userData.code = code;
|
1819
|
+
material.name = name;
|
1820
|
+
material.userData.edgeMaterial = edgeMaterial;
|
1821
|
+
this.addMaterial(material);
|
1822
|
+
return material;
|
1823
|
+
}
|
1824
|
+
|
1825
|
+
computeConstructionSteps(model) {
|
1826
|
+
// Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object.
|
1827
|
+
let stepNumber = 0;
|
1828
|
+
model.traverse(c => {
|
1829
|
+
if (c.isGroup) {
|
1830
|
+
if (c.userData.startingConstructionStep) {
|
1831
|
+
stepNumber++;
|
1832
|
+
}
|
1833
|
+
|
1834
|
+
c.userData.constructionStep = stepNumber;
|
1835
|
+
}
|
1836
|
+
});
|
1837
|
+
model.userData.numConstructionSteps = stepNumber + 1;
|
1493
1838
|
}
|
1494
1839
|
|
1495
1840
|
}
|