ldr 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/dist/cli.js +1212 -289
- package/dist/index.d.ts +3 -3
- package/dist/index.js +876 -43
- package/dist/model.d.ts +30 -3
- package/dist/node/build-pack.d.ts +11 -0
- package/dist/node/compile.d.ts +29 -2
- package/dist/node/index.js +1401 -134
- package/dist/node/library.d.ts +19 -4
- package/dist/node/resolver.d.ts +29 -1
- package/dist/render.d.ts +26 -0
- package/dist/scene.d.ts +22 -0
- package/dist/spec.d.ts +166 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,41 +6,250 @@ function parseColor(tok) {
|
|
|
6
6
|
return +tok;
|
|
7
7
|
}
|
|
8
8
|
var MOVED_RE = /^~?\s*moved\s+to\s+(.+)$/i;
|
|
9
|
-
|
|
9
|
+
var SPECIAL0 = new Set(["FILE", "NOFILE", "!DATA", "!:", "STEP", "BFC", "ROTSTEP", "BUFEXCHG", "!TEXMAP"]);
|
|
10
|
+
var PART_ORG = new Set(["part", "subpart", "primitive", "8_primitive", "48_primitive", "shortcut", "helper"]);
|
|
11
|
+
var MODEL_ORG = new Set(["model", "submodel", "configuration"]);
|
|
12
|
+
var ORG_RE = /^(?:!?LDRAW_ORG|Unofficial|Official\s+LCAD)\s+(\S+)/i;
|
|
13
|
+
function orgType(text) {
|
|
14
|
+
const m = ORG_RE.exec(text);
|
|
15
|
+
if (!m)
|
|
16
|
+
return;
|
|
17
|
+
const t = m[1].toLowerCase().replace(/^unofficial_/, "");
|
|
18
|
+
return PART_ORG.has(t) || MODEL_ORG.has(t) ? t : undefined;
|
|
19
|
+
}
|
|
20
|
+
function texNames(rest) {
|
|
21
|
+
const out = [];
|
|
22
|
+
let i = 0;
|
|
23
|
+
while (i < rest.length) {
|
|
24
|
+
while (rest[i] === " " || rest[i] === "\t")
|
|
25
|
+
i++;
|
|
26
|
+
if (i >= rest.length)
|
|
27
|
+
break;
|
|
28
|
+
if (rest[i] === '"') {
|
|
29
|
+
let v = "";
|
|
30
|
+
i++;
|
|
31
|
+
while (i < rest.length && rest[i] !== '"') {
|
|
32
|
+
if (rest[i] === "\\" && i + 1 < rest.length)
|
|
33
|
+
i++;
|
|
34
|
+
v += rest[i++];
|
|
35
|
+
}
|
|
36
|
+
i++;
|
|
37
|
+
out.push(v);
|
|
38
|
+
} else {
|
|
39
|
+
let j = i;
|
|
40
|
+
while (j < rest.length && rest[j] !== " " && rest[j] !== "\t")
|
|
41
|
+
j++;
|
|
42
|
+
out.push(rest.slice(i, j));
|
|
43
|
+
i = j;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function parseTexMap(tail, next = false) {
|
|
49
|
+
const m = /^\s*(\S+)\s+(.*)$/.exec(tail);
|
|
50
|
+
if (!m)
|
|
51
|
+
return null;
|
|
52
|
+
const method = m[1].toUpperCase();
|
|
53
|
+
const n = method === "PLANAR" ? 9 : method === "CYLINDRICAL" ? 10 : method === "SPHERICAL" ? 11 : 0;
|
|
54
|
+
if (!n)
|
|
55
|
+
return null;
|
|
56
|
+
const parts = m[2].trim().split(/\s+/);
|
|
57
|
+
if (parts.length < n + 1)
|
|
58
|
+
return null;
|
|
59
|
+
const v = parts.slice(0, n).map(Number);
|
|
60
|
+
if (!v.every(Number.isFinite))
|
|
61
|
+
return null;
|
|
62
|
+
let rest = m[2].trim();
|
|
63
|
+
for (let k = 0;k < n; k++)
|
|
64
|
+
rest = rest.replace(/^\S+\s*/, "");
|
|
65
|
+
const names = texNames(rest);
|
|
66
|
+
if (!names.length)
|
|
67
|
+
return null;
|
|
68
|
+
const t = { method, p1: v.slice(0, 3), p2: v.slice(3, 6), p3: v.slice(6, 9), texture: names[0] };
|
|
69
|
+
if (n >= 10)
|
|
70
|
+
t.a = v[9];
|
|
71
|
+
if (n === 11)
|
|
72
|
+
t.b = v[10];
|
|
73
|
+
const g = names.findIndex((x, i) => i > 0 && x.toUpperCase() === "GLOSSMAP");
|
|
74
|
+
if (g > 0 && names[g + 1])
|
|
75
|
+
t.glossmap = names[g + 1];
|
|
76
|
+
if (next)
|
|
77
|
+
t.next = true;
|
|
78
|
+
return t;
|
|
79
|
+
}
|
|
80
|
+
var b64 = (s) => {
|
|
81
|
+
const clean = s.replace(/[^A-Za-z0-9+/]/g, "");
|
|
82
|
+
const pad = clean + "=".repeat((4 - clean.length % 4) % 4);
|
|
83
|
+
const bin = atob(pad);
|
|
84
|
+
const out = new Uint8Array(bin.length);
|
|
85
|
+
for (let i = 0;i < bin.length; i++)
|
|
86
|
+
out[i] = bin.charCodeAt(i);
|
|
87
|
+
return out;
|
|
88
|
+
};
|
|
89
|
+
function geomLine(tk) {
|
|
90
|
+
const t = +tk[0];
|
|
91
|
+
if (!(t >= 1 && t <= 5) || tk.length < 2)
|
|
92
|
+
return null;
|
|
93
|
+
const color = parseColor(tk[1]);
|
|
94
|
+
const nums = tk.slice(2).map(Number);
|
|
95
|
+
if (t === 1)
|
|
96
|
+
return { t: 1, color, m: nums.slice(0, 12), file: norm(tk.slice(14).join(" ")) };
|
|
97
|
+
if (t === 2)
|
|
98
|
+
return { t: 2, color, p: nums.slice(0, 6) };
|
|
99
|
+
if (t === 3)
|
|
100
|
+
return { t: 3, color, p: nums.slice(0, 9) };
|
|
101
|
+
if (t === 4)
|
|
102
|
+
return { t: 4, color, p: nums.slice(0, 12) };
|
|
103
|
+
return { t: 5, color, p: nums.slice(0, 12) };
|
|
104
|
+
}
|
|
105
|
+
function parse(src, name = "main.ldr", popts = {}) {
|
|
106
|
+
const gtext = !!popts.geometryText;
|
|
10
107
|
const files = new Map;
|
|
11
108
|
let cur = null;
|
|
12
109
|
let main = "";
|
|
110
|
+
let data;
|
|
111
|
+
let dataCur = null;
|
|
112
|
+
let texStack = [];
|
|
113
|
+
let texNext = null;
|
|
114
|
+
const closeData = () => {
|
|
115
|
+
if (!dataCur)
|
|
116
|
+
return;
|
|
117
|
+
const bytes = b64(dataCur.chunks.join(""));
|
|
118
|
+
const png = bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
119
|
+
(data ??= new Map).set(norm(dataCur.name), { name: dataCur.name, bytes, ...png ? { mime: "image/png" } : {} });
|
|
120
|
+
dataCur = null;
|
|
121
|
+
};
|
|
122
|
+
let inHeader = true;
|
|
13
123
|
const start = (n) => {
|
|
124
|
+
closeData();
|
|
14
125
|
cur = { name: norm(n), cmds: [] };
|
|
15
126
|
files.set(cur.name, cur);
|
|
16
127
|
if (!main)
|
|
17
128
|
main = cur.name;
|
|
129
|
+
texStack = [];
|
|
130
|
+
texNext = null;
|
|
131
|
+
inHeader = true;
|
|
132
|
+
};
|
|
133
|
+
let skip = false, sawBlock = false;
|
|
134
|
+
const firstBlock = () => {
|
|
135
|
+
if (sawBlock)
|
|
136
|
+
return;
|
|
137
|
+
sawBlock = true;
|
|
138
|
+
files.clear();
|
|
139
|
+
cur = null;
|
|
140
|
+
main = "";
|
|
141
|
+
};
|
|
142
|
+
const annotate = (c, fromColon) => {
|
|
143
|
+
if (texNext) {
|
|
144
|
+
c.tex = texNext;
|
|
145
|
+
texNext = null;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const top = texStack[texStack.length - 1];
|
|
149
|
+
if (!top)
|
|
150
|
+
return;
|
|
151
|
+
if (top.fallback) {
|
|
152
|
+
if (!fromColon)
|
|
153
|
+
c.fallback = true;
|
|
154
|
+
} else
|
|
155
|
+
c.tex = top.map;
|
|
18
156
|
};
|
|
19
157
|
for (const raw of src.split(/\r?\n/)) {
|
|
20
158
|
const line = raw.trim();
|
|
21
159
|
if (!line)
|
|
22
160
|
continue;
|
|
161
|
+
if (gtext && line.charCodeAt(0) >= 50 && line.charCodeAt(0) <= 53 && (line.charCodeAt(1) === 32 || line.charCodeAt(1) === 9)) {
|
|
162
|
+
if (dataCur || skip)
|
|
163
|
+
continue;
|
|
164
|
+
if (!cur)
|
|
165
|
+
start(name);
|
|
166
|
+
const sp = line.slice(2).trimStart();
|
|
167
|
+
const e = sp.search(/\s/);
|
|
168
|
+
const g = { t: line.charCodeAt(0) - 48, color: parseColor(e < 0 ? sp : sp.slice(0, e)), p: [], src: line };
|
|
169
|
+
annotate(g, false);
|
|
170
|
+
cur.cmds.push(g);
|
|
171
|
+
inHeader = false;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (line.charCodeAt(0) === 48 && (line.charCodeAt(1) === 32 || line.charCodeAt(1) === 9)) {
|
|
175
|
+
let a = 2;
|
|
176
|
+
while (line.charCodeAt(a) === 32 || line.charCodeAt(a) === 9)
|
|
177
|
+
a++;
|
|
178
|
+
let b = a;
|
|
179
|
+
while (b < line.length && line.charCodeAt(b) !== 32 && line.charCodeAt(b) !== 9)
|
|
180
|
+
b++;
|
|
181
|
+
const kw = line.slice(a, b);
|
|
182
|
+
if (!SPECIAL0.has(kw)) {
|
|
183
|
+
if (skip || dataCur)
|
|
184
|
+
continue;
|
|
185
|
+
if (!cur)
|
|
186
|
+
start(name);
|
|
187
|
+
const text = line.slice(a);
|
|
188
|
+
const c = { t: 0, text };
|
|
189
|
+
if (kw.charCodeAt(0) === 33)
|
|
190
|
+
c.meta = kw;
|
|
191
|
+
texNext = null;
|
|
192
|
+
if (inHeader && !cur.org) {
|
|
193
|
+
const o = orgType(text);
|
|
194
|
+
if (o)
|
|
195
|
+
cur.org = o;
|
|
196
|
+
}
|
|
197
|
+
const moved = text.match(MOVED_RE);
|
|
198
|
+
if (moved && !cur.movedTo) {
|
|
199
|
+
let target = moved[1].trim().replace(/\\/g, "/");
|
|
200
|
+
if (!/\.(dat|ldr|mpd)$/i.test(target))
|
|
201
|
+
target += ".dat";
|
|
202
|
+
cur.movedTo = norm(target);
|
|
203
|
+
}
|
|
204
|
+
cur.cmds.push(c);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
23
208
|
const tk = line.split(/\s+/);
|
|
24
209
|
const t = +tk[0];
|
|
25
210
|
if (t === 0) {
|
|
26
211
|
const text = line.slice(1).trim();
|
|
27
212
|
if (tk[1] === "FILE") {
|
|
213
|
+
firstBlock();
|
|
214
|
+
skip = false;
|
|
28
215
|
start(text.slice(5));
|
|
29
216
|
continue;
|
|
30
217
|
}
|
|
31
218
|
if (tk[1] === "NOFILE") {
|
|
219
|
+
closeData();
|
|
220
|
+
cur = null;
|
|
221
|
+
skip = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (tk[1] === "!DATA") {
|
|
225
|
+
firstBlock();
|
|
226
|
+
closeData();
|
|
32
227
|
cur = null;
|
|
228
|
+
skip = false;
|
|
229
|
+
dataCur = { name: text.slice(5).trim(), chunks: [] };
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (skip)
|
|
233
|
+
continue;
|
|
234
|
+
if (dataCur) {
|
|
235
|
+
if (tk[1] === "!:")
|
|
236
|
+
dataCur.chunks.push(tk.slice(2).join(""));
|
|
33
237
|
continue;
|
|
34
238
|
}
|
|
35
239
|
if (!cur)
|
|
36
240
|
start(name);
|
|
37
241
|
const c = { t: 0, text };
|
|
38
|
-
if (tk[1] === "STEP")
|
|
242
|
+
if (tk[1] === "STEP") {
|
|
39
243
|
c.step = true;
|
|
40
|
-
|
|
244
|
+
texStack = [];
|
|
245
|
+
texNext = null;
|
|
246
|
+
} else if (tk[1] === "BFC")
|
|
41
247
|
c.bfc = tk.slice(2).join(" ");
|
|
42
248
|
else if (tk[1] === "ROTSTEP") {
|
|
43
249
|
c.meta = "ROTSTEP";
|
|
250
|
+
c.step = true;
|
|
251
|
+
texStack = [];
|
|
252
|
+
texNext = null;
|
|
44
253
|
if ((tk[2] ?? "").toUpperCase() === "END")
|
|
45
254
|
c.rotstep = { x: 0, y: 0, z: 0, mode: "END" };
|
|
46
255
|
else {
|
|
@@ -53,6 +262,42 @@ function parse(src, name = "main.ldr") {
|
|
|
53
262
|
c.bufexchg = { op: op === "RETRIEVE" ? "RETRIEVE" : "STORE", buf: tk[3] ?? "A" };
|
|
54
263
|
} else if (tk[1]?.startsWith("!"))
|
|
55
264
|
c.meta = tk[1];
|
|
265
|
+
if (inHeader && !cur.org) {
|
|
266
|
+
const o = orgType(text);
|
|
267
|
+
if (o)
|
|
268
|
+
cur.org = o;
|
|
269
|
+
}
|
|
270
|
+
if (tk[1] === "!TEXMAP") {
|
|
271
|
+
const op = (tk[2] ?? "").toUpperCase();
|
|
272
|
+
texNext = null;
|
|
273
|
+
if (op === "START" || op === "NEXT") {
|
|
274
|
+
const map = parseTexMap(text.replace(/^!TEXMAP\s+\S+/i, ""), op === "NEXT");
|
|
275
|
+
c.texmap = { op, ...map ? { map } : {} };
|
|
276
|
+
if (map) {
|
|
277
|
+
(cur.texmaps ??= []).push(map);
|
|
278
|
+
if (op === "START")
|
|
279
|
+
texStack.push({ map, fallback: false });
|
|
280
|
+
else
|
|
281
|
+
texNext = map;
|
|
282
|
+
}
|
|
283
|
+
} else if (op === "FALLBACK") {
|
|
284
|
+
c.texmap = { op };
|
|
285
|
+
const top = texStack[texStack.length - 1];
|
|
286
|
+
if (top)
|
|
287
|
+
top.fallback = true;
|
|
288
|
+
} else if (op === "END") {
|
|
289
|
+
c.texmap = { op };
|
|
290
|
+
texStack.pop();
|
|
291
|
+
}
|
|
292
|
+
} else if (tk[1] === "!:") {
|
|
293
|
+
texNext = null;
|
|
294
|
+
const g = geomLine(tk.slice(2));
|
|
295
|
+
if (g) {
|
|
296
|
+
annotate(g, true);
|
|
297
|
+
c.geom = g;
|
|
298
|
+
}
|
|
299
|
+
} else
|
|
300
|
+
texNext = null;
|
|
56
301
|
const moved = text.match(MOVED_RE);
|
|
57
302
|
if (moved && !cur.movedTo) {
|
|
58
303
|
let target = moved[1].trim().replace(/\\/g, "/");
|
|
@@ -63,22 +308,110 @@ function parse(src, name = "main.ldr") {
|
|
|
63
308
|
cur.cmds.push(c);
|
|
64
309
|
continue;
|
|
65
310
|
}
|
|
311
|
+
if (dataCur || skip)
|
|
312
|
+
continue;
|
|
66
313
|
if (!cur)
|
|
67
314
|
start(name);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
315
|
+
const g = geomLine(tk);
|
|
316
|
+
if (!g)
|
|
317
|
+
continue;
|
|
318
|
+
annotate(g, false);
|
|
319
|
+
cur.cmds.push(g);
|
|
320
|
+
inHeader = false;
|
|
321
|
+
}
|
|
322
|
+
closeData();
|
|
323
|
+
return data ? { main, files, data } : { main, files };
|
|
324
|
+
}
|
|
325
|
+
function resolveTexture(doc, name) {
|
|
326
|
+
if (!doc.data)
|
|
327
|
+
return null;
|
|
328
|
+
const n = norm(name);
|
|
329
|
+
return doc.data.get(n) ?? doc.data.get(n.replace(/^textures\//, "")) ?? doc.data.get("textures/" + n) ?? null;
|
|
330
|
+
}
|
|
331
|
+
var sub3 = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
332
|
+
var dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
333
|
+
var cross3 = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
334
|
+
var unit3 = (a) => {
|
|
335
|
+
const l = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
336
|
+
return [a[0] / l, a[1] / l, a[2] / l];
|
|
337
|
+
};
|
|
338
|
+
var DEG = Math.PI / 180;
|
|
339
|
+
function texmapUV(map, p) {
|
|
340
|
+
const w = texmapTriangleUV(map, [p, p, p]);
|
|
341
|
+
const u = w[0], v = w[1];
|
|
342
|
+
return { u, v, inside: u >= -0.000001 && u <= 1 + 0.000001 && v >= -0.000001 && v <= 1 + 0.000001 };
|
|
343
|
+
}
|
|
344
|
+
var radial = (p, c, n) => {
|
|
345
|
+
const d = sub3(p, c);
|
|
346
|
+
const k = dot3(d, n);
|
|
347
|
+
const r = [d[0] - n[0] * k, d[1] - n[1] * k, d[2] - n[2] * k];
|
|
348
|
+
return Math.hypot(r[0], r[1], r[2]) < 0.00001 ? null : unit3(r);
|
|
349
|
+
};
|
|
350
|
+
function texmapTriangleUV(map, pts) {
|
|
351
|
+
const { p1, p2, p3 } = map;
|
|
352
|
+
const out = new Array(pts.length * 2).fill(0);
|
|
353
|
+
if (map.method === "PLANAR") {
|
|
354
|
+
const e1 = sub3(p2, p1), e2 = sub3(p3, p1);
|
|
355
|
+
const l1 = dot3(e1, e1) || 1, l2 = dot3(e2, e2) || 1;
|
|
356
|
+
pts.forEach((p, i) => {
|
|
357
|
+
const d = sub3(p, p1);
|
|
358
|
+
out[i * 2] = dot3(d, e1) / l1;
|
|
359
|
+
out[i * 2 + 1] = dot3(d, e2) / l2;
|
|
360
|
+
});
|
|
361
|
+
return out;
|
|
80
362
|
}
|
|
81
|
-
|
|
363
|
+
const cyl = map.method === "CYLINDRICAL";
|
|
364
|
+
const n = cyl ? unit3(sub3(p2, p1)) : unit3(cross3(sub3(p1, p2), sub3(p3, p2)).map((x) => -x));
|
|
365
|
+
const dir = cyl ? radial(p3, p1, n) : unit3(sub3(p2, p1));
|
|
366
|
+
const A = (map.a ?? 360) * DEG;
|
|
367
|
+
let base = null, baseAng = 0;
|
|
368
|
+
const sAng = (q) => {
|
|
369
|
+
if (!base) {
|
|
370
|
+
base = q;
|
|
371
|
+
baseAng = Math.atan2(dot3(cross3(dir, q), n), dot3(q, dir));
|
|
372
|
+
return baseAng;
|
|
373
|
+
}
|
|
374
|
+
return Math.atan2(dot3(cross3(base, q), n), dot3(q, base)) + baseAng;
|
|
375
|
+
};
|
|
376
|
+
const miss = [];
|
|
377
|
+
let uSum = 0;
|
|
378
|
+
const h = cyl ? Math.hypot(...sub3(p2, p1)) || 1 : 0, n2 = cyl ? null : unit3(cross3(n, sub3(p2, p1))), B = (map.b ?? 180) * DEG;
|
|
379
|
+
pts.forEach((p, i) => {
|
|
380
|
+
if (cyl) {
|
|
381
|
+
const q = dir ? radial(p, p1, n) : null;
|
|
382
|
+
if (q) {
|
|
383
|
+
const a = sAng(q);
|
|
384
|
+
out[i * 2] = 0.5 + a / A;
|
|
385
|
+
uSum += out[i * 2];
|
|
386
|
+
} else
|
|
387
|
+
miss.push(i);
|
|
388
|
+
out[i * 2 + 1] = dot3(n, sub3(p2, p)) / h;
|
|
389
|
+
} else {
|
|
390
|
+
const pd = unit3(sub3(p, p1));
|
|
391
|
+
const c = dot3(pd, n);
|
|
392
|
+
if (Math.abs(c) > 1 - 0.000000001) {
|
|
393
|
+
miss.push(i);
|
|
394
|
+
out[i * 2 + 1] = 0.5 + (c > 0 ? -Math.PI / 2 : Math.PI / 2) / B;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const a = sAng(radial(p, p1, n));
|
|
398
|
+
uSum += a;
|
|
399
|
+
out[i * 2] = 0.5 + a / A;
|
|
400
|
+
const th = -a, ct = Math.cos(th), st = Math.sin(th), cr = cross3(n, pd), k = dot3(n, pd) * (1 - ct);
|
|
401
|
+
const r = unit3([pd[0] * ct + cr[0] * st + n[0] * k, pd[1] * ct + cr[1] * st + n[1] * k, pd[2] * ct + cr[2] * st + n[2] * k]);
|
|
402
|
+
out[i * 2 + 1] = 0.5 + Math.atan2(dot3(cross3(dir, r), n2), dot3(r, dir)) / B;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
if (miss.length && miss.length < pts.length) {
|
|
406
|
+
const avg = cyl ? uSum / (pts.length - miss.length) : 0.5 + uSum / (pts.length - miss.length) / A;
|
|
407
|
+
for (const i of miss)
|
|
408
|
+
out[i * 2] = avg;
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
function transformTexMap(map, m) {
|
|
413
|
+
const x = (q) => [m[3] * q[0] + m[4] * q[1] + m[5] * q[2] + m[0], m[6] * q[0] + m[7] * q[1] + m[8] * q[2] + m[1], m[9] * q[0] + m[10] * q[1] + m[11] * q[2] + m[2]];
|
|
414
|
+
return { ...map, p1: x(map.p1), p2: x(map.p2), p3: x(map.p3) };
|
|
82
415
|
}
|
|
83
416
|
function mul(a, b) {
|
|
84
417
|
const [ax, ay, az, a0, a1, a2, a3, a4, a5, a6, a7, a8] = a, [bx, by, bz, b0, b1, b2, b3, b4, b5, b6, b7, b8] = b;
|
|
@@ -164,34 +497,249 @@ function smoothNormals(pos, edges, cond, creaseDeg = 35) {
|
|
|
164
497
|
}
|
|
165
498
|
return out;
|
|
166
499
|
}
|
|
500
|
+
var kindMemo = new WeakMap;
|
|
501
|
+
var SUBREF_RE = /^(s|8|48)\//;
|
|
502
|
+
function fileKind(doc, name) {
|
|
503
|
+
const f = doc.files.get(norm(name));
|
|
504
|
+
if (!f)
|
|
505
|
+
return "part";
|
|
506
|
+
let k = kindMemo.get(f);
|
|
507
|
+
if (k)
|
|
508
|
+
return k;
|
|
509
|
+
if (f.org)
|
|
510
|
+
k = PART_ORG.has(f.org) ? "part" : "model";
|
|
511
|
+
else if (f.name === doc.main)
|
|
512
|
+
k = "model";
|
|
513
|
+
else
|
|
514
|
+
k = f.cmds.some((c) => c.t >= 2 || c.t === 0 && !!c.geom || c.t === 1 && SUBREF_RE.test(c.file)) ? "part" : "model";
|
|
515
|
+
kindMemo.set(f, k);
|
|
516
|
+
return k;
|
|
517
|
+
}
|
|
518
|
+
var looseMemo = new WeakMap;
|
|
519
|
+
function looseSteps(f) {
|
|
520
|
+
let r = looseMemo.get(f);
|
|
521
|
+
if (r)
|
|
522
|
+
return r;
|
|
523
|
+
r = [];
|
|
524
|
+
let s = 0;
|
|
525
|
+
for (const c of f.cmds) {
|
|
526
|
+
if (c.t === 0 && c.step) {
|
|
527
|
+
s++;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if ((c.t >= 2 || c.t === 0 && c.geom && c.geom.t >= 2) && r[r.length - 1] !== s)
|
|
531
|
+
r.push(s);
|
|
532
|
+
}
|
|
533
|
+
looseMemo.set(f, r);
|
|
534
|
+
return r;
|
|
535
|
+
}
|
|
536
|
+
var colorToken = (c) => c > 16777215 ? "0x" + c.toString(16).toUpperCase() : String(c);
|
|
537
|
+
var lineOf = (c) => c.t === 0 ? "0 " + c.text : c.t === 1 ? `1 ${colorToken(c.color)} ${c.m.join(" ")} ${c.file}` : c.src ? c.src : `${c.t} ${colorToken(c.color)} ${c.p.join(" ")}`;
|
|
538
|
+
var loosePartName = (file, step) => `${norm(file)}#${step}`;
|
|
539
|
+
function looseFile(f, step) {
|
|
540
|
+
const out = [];
|
|
541
|
+
let s = 0;
|
|
542
|
+
const isNext = (c) => !!c && c.t === 0 && c.texmap?.op === "NEXT";
|
|
543
|
+
for (const c of f.cmds) {
|
|
544
|
+
if (c.t === 0) {
|
|
545
|
+
if (c.step) {
|
|
546
|
+
s++;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (c.bfc !== undefined) {
|
|
550
|
+
out.push(c);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (s !== step)
|
|
554
|
+
continue;
|
|
555
|
+
if (c.texmap)
|
|
556
|
+
out.push(c);
|
|
557
|
+
else if (c.geom) {
|
|
558
|
+
if (c.geom.t >= 2)
|
|
559
|
+
out.push(c);
|
|
560
|
+
else if (isNext(out[out.length - 1]))
|
|
561
|
+
out.pop();
|
|
562
|
+
}
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (s !== step)
|
|
566
|
+
continue;
|
|
567
|
+
if (c.t === 1) {
|
|
568
|
+
if (isNext(out[out.length - 1]))
|
|
569
|
+
out.pop();
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
out.push(c);
|
|
573
|
+
}
|
|
574
|
+
return { name: loosePartName(f.name, step), cmds: out };
|
|
575
|
+
}
|
|
576
|
+
function looseSource(f, step) {
|
|
577
|
+
return [`0 ldr: own geometry of ${f.name}, step ${step}`, ...looseFile(f, step).cmds.map(lineOf)].join(`
|
|
578
|
+
`) + `
|
|
579
|
+
`;
|
|
580
|
+
}
|
|
581
|
+
function hash32(s) {
|
|
582
|
+
let h = 2166136261;
|
|
583
|
+
for (let i = 0;i < s.length; i++) {
|
|
584
|
+
h ^= s.charCodeAt(i);
|
|
585
|
+
h = Math.imul(h, 16777619);
|
|
586
|
+
}
|
|
587
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
588
|
+
}
|
|
589
|
+
function invert(m) {
|
|
590
|
+
const [x, y, z, a, b, c, d, e, f, g, h, i] = m;
|
|
591
|
+
const A = e * i - f * h, B = -(d * i - f * g), C = d * h - e * g;
|
|
592
|
+
const det = a * A + b * B + c * C;
|
|
593
|
+
if (!Number.isFinite(det) || Math.abs(det) < 0.000000000001)
|
|
594
|
+
return null;
|
|
595
|
+
const k = 1 / det;
|
|
596
|
+
const r = [A * k, -(b * i - c * h) * k, (b * f - c * e) * k, B * k, (a * i - c * g) * k, -(a * f - c * d) * k, C * k, -(a * h - b * g) * k, (a * e - b * d) * k];
|
|
597
|
+
return [-(r[0] * x + r[1] * y + r[2] * z), -(r[3] * x + r[4] * y + r[5] * z), -(r[6] * x + r[7] * y + r[8] * z), ...r];
|
|
598
|
+
}
|
|
599
|
+
var num = (x) => String(+x.toFixed(6) || 0);
|
|
600
|
+
var quote = (s) => '"' + s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"';
|
|
601
|
+
function texWrapper(part, map) {
|
|
602
|
+
const v = [...map.p1, ...map.p2, ...map.p3, ...map.method !== "PLANAR" ? [map.a ?? 360] : [], ...map.method === "SPHERICAL" ? [map.b ?? 180] : []].map(num).join(" ");
|
|
603
|
+
const source = `0 ldr: ${part} with a model-level !TEXMAP
|
|
604
|
+
0 BFC CERTIFY CCW
|
|
605
|
+
0 !TEXMAP START ${map.method} ${v} ${quote(map.texture)}${map.glossmap ? " GLOSSMAP " + quote(map.glossmap) : ""}
|
|
606
|
+
1 16 0 0 0 1 0 0 0 1 0 0 0 1 ${norm(part)}
|
|
607
|
+
0 !TEXMAP END
|
|
608
|
+
`;
|
|
609
|
+
return { name: `${norm(part)}#tex-${hash32(source)}`, source };
|
|
610
|
+
}
|
|
611
|
+
var mul3 = (a, b) => {
|
|
612
|
+
const r = new Array(9);
|
|
613
|
+
for (let i = 0;i < 3; i++)
|
|
614
|
+
for (let j = 0;j < 3; j++)
|
|
615
|
+
r[i * 3 + j] = a[i * 3] * b[j] + a[i * 3 + 1] * b[3 + j] + a[i * 3 + 2] * b[6 + j];
|
|
616
|
+
return r;
|
|
617
|
+
};
|
|
618
|
+
function rotStepMatrix(x, y, z) {
|
|
619
|
+
const D = Math.PI / 180, s1 = Math.sin(x * D), c1 = Math.cos(x * D), s2 = Math.sin(y * D), c2 = Math.cos(y * D), s3 = Math.sin(z * D), c3 = Math.cos(z * D);
|
|
620
|
+
return [c2 * c3, -c2 * s3, s2, c1 * s3 + s1 * s2 * c3, c1 * c3 - s1 * s2 * s3, -s1 * c2, s1 * s3 - c1 * s2 * c3, s1 * c3 + c1 * s2 * s3, c1 * c2];
|
|
621
|
+
}
|
|
622
|
+
function stepViewMatrix(view, defaultView = [1, 0, 0, 0, 1, 0, 0, 0, 1]) {
|
|
623
|
+
return !view ? [...defaultView] : view.abs ? [...view.m] : mul3(defaultView, view.m);
|
|
624
|
+
}
|
|
625
|
+
var viewMemo = new WeakMap;
|
|
626
|
+
function fileStepViews(f) {
|
|
627
|
+
const hit = viewMemo.get(f);
|
|
628
|
+
if (hit)
|
|
629
|
+
return hit;
|
|
630
|
+
const out = [];
|
|
631
|
+
let cur = null;
|
|
632
|
+
for (const c of f.cmds) {
|
|
633
|
+
if (c.t !== 0 || !c.step)
|
|
634
|
+
continue;
|
|
635
|
+
const r = c.rotstep;
|
|
636
|
+
if (r) {
|
|
637
|
+
if (r.mode === "END")
|
|
638
|
+
cur = null;
|
|
639
|
+
else {
|
|
640
|
+
const R = rotStepMatrix(r.x, r.y, r.z);
|
|
641
|
+
cur = r.mode === "ABS" ? { abs: true, m: R, rotstep: r } : r.mode === "ADD" && cur ? { abs: cur.abs, m: mul3(cur.m, R), rotstep: r } : { abs: false, m: R, rotstep: r };
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
out.push(cur);
|
|
645
|
+
}
|
|
646
|
+
out.push(cur);
|
|
647
|
+
viewMemo.set(f, out);
|
|
648
|
+
return out;
|
|
649
|
+
}
|
|
167
650
|
// src/model.ts
|
|
168
651
|
function flatten(doc, opts = {}) {
|
|
169
652
|
const out = [];
|
|
170
653
|
const maxDepth = opts.maxDepth ?? 64;
|
|
171
|
-
const
|
|
654
|
+
const loose = opts.looseGeometry !== false, tx = !!opts.textures;
|
|
655
|
+
const src = new Map;
|
|
656
|
+
const lazy = (f, k) => {
|
|
657
|
+
const key = f.name + "#" + k;
|
|
658
|
+
let fn = src.get(key);
|
|
659
|
+
if (!fn) {
|
|
660
|
+
let s;
|
|
661
|
+
fn = Object.assign(() => s ??= looseSource(f, k), { file: f, step: k });
|
|
662
|
+
src.set(key, fn);
|
|
663
|
+
}
|
|
664
|
+
return fn;
|
|
665
|
+
};
|
|
666
|
+
const walk = (d, fname, m, color, path, stepPath, depth, texIn) => {
|
|
172
667
|
const f = d.files.get(fname);
|
|
173
668
|
if (!f || depth > maxDepth)
|
|
174
669
|
return;
|
|
175
670
|
let s = 0;
|
|
176
|
-
|
|
177
|
-
|
|
671
|
+
const views = fileStepViews(f);
|
|
672
|
+
const ls = loose ? looseSteps(f) : [];
|
|
673
|
+
let li = 0;
|
|
674
|
+
const emit = (part, col, mm, sp, k, embedded, tex) => {
|
|
675
|
+
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
676
|
+
if (embedded)
|
|
677
|
+
i.embedded = true;
|
|
678
|
+
if (tex) {
|
|
679
|
+
const w = texWrapper(part, tex);
|
|
680
|
+
i.texPart = w.name;
|
|
681
|
+
i.texSource = w.source;
|
|
682
|
+
}
|
|
683
|
+
const v = views[k];
|
|
684
|
+
if (v)
|
|
685
|
+
i.view = v;
|
|
686
|
+
out.push(i);
|
|
687
|
+
return i;
|
|
688
|
+
};
|
|
689
|
+
const flushLoose = (upTo) => {
|
|
690
|
+
while (li < ls.length && ls[li] <= upTo) {
|
|
691
|
+
const k = ls[li++];
|
|
692
|
+
emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null).source = lazy(f, k);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
for (const c0 of f.cmds) {
|
|
696
|
+
if (c0.t === 0 && c0.step) {
|
|
178
697
|
s++;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
let c = c0;
|
|
701
|
+
if (c.t >= 2 || c.t === 0 && c.geom && c.geom.t >= 2) {
|
|
702
|
+
flushLoose(s);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (c.t === 0) {
|
|
706
|
+
if (tx && c.geom?.t === 1)
|
|
707
|
+
c = c.geom;
|
|
708
|
+
else
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
179
711
|
if (c.t !== 1)
|
|
180
712
|
continue;
|
|
713
|
+
if (tx && c.fallback)
|
|
714
|
+
continue;
|
|
181
715
|
const col = c.color === 16 ? color : c.color, mm = mul(m, c.m), sp = [...stepPath, s];
|
|
716
|
+
const tex = tx ? c.tex ?? texIn : null;
|
|
717
|
+
const local = tex ? invert(c.m) : null;
|
|
718
|
+
const lt = tex && local ? transformTexMap(tex, local) : null;
|
|
182
719
|
if (d.files.has(c.file)) {
|
|
183
|
-
|
|
720
|
+
if (fileKind(d, c.file) === "model") {
|
|
721
|
+
walk(d, c.file, mm, col, path + "/" + c.file, sp, depth + 1, lt);
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
emit(c.file, col, mm, sp, s, true, lt);
|
|
184
725
|
continue;
|
|
185
726
|
}
|
|
186
727
|
const ext = opts.resolveModel?.(c.file);
|
|
187
728
|
if (ext) {
|
|
188
|
-
walk(ext, ext.main, mm, col, path + "/" + c.file, sp, depth + 1);
|
|
729
|
+
walk(ext, ext.main, mm, col, path + "/" + c.file, sp, depth + 1, lt);
|
|
189
730
|
continue;
|
|
190
731
|
}
|
|
191
|
-
|
|
732
|
+
emit(c.file, col, mm, sp, s, false, lt);
|
|
192
733
|
}
|
|
734
|
+
flushLoose(Infinity);
|
|
193
735
|
};
|
|
194
|
-
|
|
736
|
+
if (fileKind(doc, doc.main) === "part") {
|
|
737
|
+
const f = doc.files.get(doc.main);
|
|
738
|
+
if (f)
|
|
739
|
+
out.push({ part: doc.main, color: 16, m: [...I], step: 0, steps: [0], localStep: 0, path: doc.main, embedded: true });
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
walk(doc, doc.main, I, 16, doc.main, [], 0, null);
|
|
195
743
|
return out;
|
|
196
744
|
}
|
|
197
745
|
function parseModel(text, name = "main.ldr", opts) {
|
|
@@ -202,8 +750,14 @@ function sequenceSteps(insts) {
|
|
|
202
750
|
const uniq = [...new Set(insts.map(key))].sort();
|
|
203
751
|
const rank = new Map(uniq.map((k, j) => [k, j]));
|
|
204
752
|
const stepOf = new Int32Array(insts.length);
|
|
205
|
-
|
|
206
|
-
|
|
753
|
+
const views = new Array(uniq.length).fill(null);
|
|
754
|
+
insts.forEach((i, k) => {
|
|
755
|
+
const r = rank.get(key(i));
|
|
756
|
+
stepOf[k] = r;
|
|
757
|
+
if (i.view)
|
|
758
|
+
views[r] = i.view;
|
|
759
|
+
});
|
|
760
|
+
return { count: uniq.length, stepOf, views };
|
|
207
761
|
}
|
|
208
762
|
function parseLDConfig(text) {
|
|
209
763
|
const out = new Map;
|
|
@@ -241,7 +795,7 @@ function decodePack(buf) {
|
|
|
241
795
|
const hl = new DataView(buf).getUint32(0, true);
|
|
242
796
|
const header = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, hl)));
|
|
243
797
|
const base = 4 + hl;
|
|
244
|
-
const arr = (r) => r && (r[2] === "f32" ? new Float32Array(buf, base + r[0], r[1]) : r[2] === "u16" ? new Uint16Array(buf, base + r[0], r[1]) : new Uint32Array(buf, base + r[0], r[1]));
|
|
798
|
+
const arr = (r) => r && (r[2] === "f32" ? new Float32Array(buf, base + r[0], r[1]) : r[2] === "u16" ? new Uint16Array(buf, base + r[0], r[1]) : r[2] === "u8" ? new Uint8Array(buf, base + r[0], r[1]) : new Uint32Array(buf, base + r[0], r[1]));
|
|
245
799
|
return { header, arr, bytes: buf.byteLength };
|
|
246
800
|
}
|
|
247
801
|
function geom(b, arr) {
|
|
@@ -253,6 +807,8 @@ function geom(b, arr) {
|
|
|
253
807
|
g.setAttribute("ldrColor", new THREE.BufferAttribute(arr(b.col), 4));
|
|
254
808
|
if (b.idx)
|
|
255
809
|
g.setIndex(new THREE.BufferAttribute(arr(b.idx), 1));
|
|
810
|
+
if (b.uv)
|
|
811
|
+
g.setAttribute("ldrUV", new THREE.BufferAttribute(arr(b.uv), 2));
|
|
256
812
|
return g;
|
|
257
813
|
}
|
|
258
814
|
function makeMaterial(side) {
|
|
@@ -268,6 +824,41 @@ function makeMaterial(side) {
|
|
|
268
824
|
m.customProgramCacheKey = () => "ldrColor";
|
|
269
825
|
return m;
|
|
270
826
|
}
|
|
827
|
+
function makeTexturedMaterial(side, map, wrapU = false) {
|
|
828
|
+
const m = makeMaterial(side);
|
|
829
|
+
const base = m.onBeforeCompile;
|
|
830
|
+
m.onBeforeCompile = (sh, r) => {
|
|
831
|
+
base.call(m, sh, r);
|
|
832
|
+
sh.uniforms.ldrMap = { value: map };
|
|
833
|
+
sh.uniforms.ldrWrapU = { value: wrapU ? 1 : 0 };
|
|
834
|
+
sh.vertexShader = `attribute vec2 ldrUV; varying vec2 vLdrUV;
|
|
835
|
+
` + sh.vertexShader.replace("#include <uv_vertex>", `#include <uv_vertex>
|
|
836
|
+
vLdrUV = ldrUV;`);
|
|
837
|
+
sh.fragmentShader = `uniform sampler2D ldrMap; uniform int ldrWrapU; varying vec2 vLdrUV;
|
|
838
|
+
` + sh.fragmentShader.replace("#include <color_fragment>", `#include <color_fragment>
|
|
839
|
+
{ vec2 uv = vec2(ldrWrapU == 1 ? fract(vLdrUV.x) : vLdrUV.x, vLdrUV.y); vec4 t = texture2D(ldrMap, uv);
|
|
840
|
+
float inside = step(0.0, uv.x) * step(uv.x, 1.0) * step(0.0, uv.y) * step(uv.y, 1.0);
|
|
841
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, t.rgb, t.a * inside); }`);
|
|
842
|
+
};
|
|
843
|
+
m.customProgramCacheKey = () => "ldrColorTex";
|
|
844
|
+
m.userData.ldrMap = map;
|
|
845
|
+
return m;
|
|
846
|
+
}
|
|
847
|
+
function packTexture(pack, i) {
|
|
848
|
+
const t = new THREE.Texture;
|
|
849
|
+
t.flipY = false;
|
|
850
|
+
t.colorSpace = THREE.SRGBColorSpace;
|
|
851
|
+
t.wrapS = t.wrapT = THREE.ClampToEdgeWrapping;
|
|
852
|
+
const e = pack.header.textures?.[i];
|
|
853
|
+
const bytes = e?.data ? pack.arr(e.data) : null;
|
|
854
|
+
if (!bytes || typeof createImageBitmap === "undefined")
|
|
855
|
+
return { texture: t, ready: Promise.resolve() };
|
|
856
|
+
const ready = createImageBitmap(new Blob([bytes], { type: e.mime || "image/png" }), { premultiplyAlpha: "none", colorSpaceConversion: "none" }).then((img) => {
|
|
857
|
+
t.image = img;
|
|
858
|
+
t.needsUpdate = true;
|
|
859
|
+
}).catch(() => {});
|
|
860
|
+
return { texture: t, ready };
|
|
861
|
+
}
|
|
271
862
|
var lineMat = () => new THREE.ShaderMaterial({
|
|
272
863
|
vertexShader: `attribute vec4 im0; attribute vec4 im1; attribute vec4 im2; attribute vec4 im3;
|
|
273
864
|
void main(){ gl_Position = projectionMatrix * modelViewMatrix * mat4(im0,im1,im2,im3) * vec4(position,1.0); }`,
|
|
@@ -296,6 +887,109 @@ var weldLines = (pos) => {
|
|
|
296
887
|
function createInstancedEdges(pos, im, material = sharedLineMat()) {
|
|
297
888
|
return sharedLines(pos, im, material);
|
|
298
889
|
}
|
|
890
|
+
function conditionalLines(pack, entry, which = "all") {
|
|
891
|
+
if (which === "all") {
|
|
892
|
+
const a = conditionalLines(pack, entry, "body"), b = conditionalLines(pack, entry, "under");
|
|
893
|
+
if (!a || !b)
|
|
894
|
+
return a ?? b;
|
|
895
|
+
const o = new Float32Array(a.length + b.length);
|
|
896
|
+
o.set(a);
|
|
897
|
+
o.set(b, a.length);
|
|
898
|
+
return o;
|
|
899
|
+
}
|
|
900
|
+
const c = which === "under" ? entry?.condlu : entry?.condl;
|
|
901
|
+
if (!c)
|
|
902
|
+
return null;
|
|
903
|
+
const { arr } = pack;
|
|
904
|
+
if (c.raw)
|
|
905
|
+
return arr(c.raw);
|
|
906
|
+
const pools = [entry.body ? arr(entry.body.pos) : [], entry.under ? arr(entry.under.pos) : [], entry.edges ? arr(entry.edges) : [], c.x ? arr(c.x) : []];
|
|
907
|
+
const idx = arr(c.idx);
|
|
908
|
+
const out = new Float32Array(idx.length * 3);
|
|
909
|
+
const starts = [];
|
|
910
|
+
let base = 0;
|
|
911
|
+
for (const p of pools) {
|
|
912
|
+
starts.push(base);
|
|
913
|
+
base += p.length / 3;
|
|
914
|
+
}
|
|
915
|
+
let prev = 0;
|
|
916
|
+
for (let i = 0;i < idx.length; i++) {
|
|
917
|
+
const z = idx[i];
|
|
918
|
+
const j = prev + (z & 1 ? -(z + 1) / 2 : z / 2);
|
|
919
|
+
prev = j;
|
|
920
|
+
let k = 3;
|
|
921
|
+
while (k > 0 && j < starts[k])
|
|
922
|
+
k--;
|
|
923
|
+
const p = pools[k], o = (j - starts[k]) * 3;
|
|
924
|
+
out[i * 3] = p[o];
|
|
925
|
+
out[i * 3 + 1] = p[o + 1];
|
|
926
|
+
out[i * 3 + 2] = p[o + 2];
|
|
927
|
+
}
|
|
928
|
+
return out;
|
|
929
|
+
}
|
|
930
|
+
var condLineMat = () => new THREE.ShaderMaterial({
|
|
931
|
+
uniforms: { ldrViewProj: { value: new THREE.Matrix4 } },
|
|
932
|
+
vertexShader: `uniform mat4 ldrViewProj; attribute mat4 instanceMatrix; attribute vec3 ldrP1; attribute vec3 ldrC0; attribute vec3 ldrC1; attribute float ldrSide;
|
|
933
|
+
void main(){
|
|
934
|
+
mat4 mvp = ldrViewProj * instanceMatrix;
|
|
935
|
+
vec4 a = mvp * vec4(position, 1.0), b = mvp * vec4(ldrP1, 1.0), c0 = mvp * vec4(ldrC0, 1.0), c1 = mvp * vec4(ldrC1, 1.0);
|
|
936
|
+
vec2 A = a.xy / a.w, B = b.xy / b.w, C0 = c0.xy / c0.w, C1 = c1.xy / c1.w, d = B - A;
|
|
937
|
+
float s0 = d.x * (C0.y - A.y) - d.y * (C0.x - A.x), s1 = d.x * (C1.y - A.y) - d.y * (C1.x - A.x);
|
|
938
|
+
gl_Position = s0 * s1 >= 0.0 ? (ldrSide > 0.5 ? b : a) : vec4(2.0, 2.0, 2.0, 1.0);
|
|
939
|
+
}`,
|
|
940
|
+
fragmentShader: `void main(){ gl_FragColor = vec4(0.13,0.13,0.13,1.0); }`
|
|
941
|
+
});
|
|
942
|
+
function conditionalLineVisible(cond, i, mvp) {
|
|
943
|
+
const e = mvp.elements;
|
|
944
|
+
const pr = (o) => {
|
|
945
|
+
const x = cond[o], y = cond[o + 1], z = cond[o + 2];
|
|
946
|
+
const w = e[3] * x + e[7] * y + e[11] * z + e[15];
|
|
947
|
+
return [(e[0] * x + e[4] * y + e[8] * z + e[12]) / w, (e[1] * x + e[5] * y + e[9] * z + e[13]) / w];
|
|
948
|
+
};
|
|
949
|
+
const o = i * 12;
|
|
950
|
+
const A = pr(o), B = pr(o + 3), C0 = pr(o + 6), C1 = pr(o + 9);
|
|
951
|
+
const dx = B[0] - A[0], dy = B[1] - A[1];
|
|
952
|
+
const s0 = dx * (C0[1] - A[1]) - dy * (C0[0] - A[0]), s1 = dx * (C1[1] - A[1]) - dy * (C1[0] - A[0]);
|
|
953
|
+
return s0 * s1 >= 0;
|
|
954
|
+
}
|
|
955
|
+
function createInstancedConditionalEdges(cond, im, material = condLineMat()) {
|
|
956
|
+
return condEdges(condBuffers(cond), im, material);
|
|
957
|
+
}
|
|
958
|
+
function condBuffers(cond) {
|
|
959
|
+
const n = Math.floor(cond.length / 12);
|
|
960
|
+
const buf = new Float32Array(n * 24), side = new Float32Array(n * 2);
|
|
961
|
+
for (let l = 0;l < n; l++) {
|
|
962
|
+
for (let k = 0;k < 12; k++) {
|
|
963
|
+
const v = cond[l * 12 + k];
|
|
964
|
+
buf[l * 24 + k] = v;
|
|
965
|
+
buf[l * 24 + 12 + k] = v;
|
|
966
|
+
}
|
|
967
|
+
side[l * 2 + 1] = 1;
|
|
968
|
+
}
|
|
969
|
+
return { ib: new THREE.InterleavedBuffer(buf, 12), side: new THREE.BufferAttribute(side, 1) };
|
|
970
|
+
}
|
|
971
|
+
function condEdges({ ib, side }, im, material) {
|
|
972
|
+
const g = new THREE.InstancedBufferGeometry;
|
|
973
|
+
g.setAttribute("position", new THREE.InterleavedBufferAttribute(ib, 3, 0));
|
|
974
|
+
g.setAttribute("ldrP1", new THREE.InterleavedBufferAttribute(ib, 3, 3));
|
|
975
|
+
g.setAttribute("ldrC0", new THREE.InterleavedBufferAttribute(ib, 3, 6));
|
|
976
|
+
g.setAttribute("ldrC1", new THREE.InterleavedBufferAttribute(ib, 3, 9));
|
|
977
|
+
g.setAttribute("ldrSide", side);
|
|
978
|
+
g.setAttribute("instanceMatrix", im.instanceMatrix);
|
|
979
|
+
g.instanceCount = im.count;
|
|
980
|
+
const l = new THREE.LineSegments(g, material);
|
|
981
|
+
l.frustumCulled = false;
|
|
982
|
+
l.name = "ldrCondLines";
|
|
983
|
+
l.onBeforeRender = (_r, _s, cam, _g, mat) => {
|
|
984
|
+
const u = mat.uniforms?.ldrViewProj;
|
|
985
|
+
if (!u)
|
|
986
|
+
return;
|
|
987
|
+
l.modelViewMatrix.multiplyMatrices(cam.matrixWorldInverse, l.matrixWorld);
|
|
988
|
+
u.value.multiplyMatrices(cam.projectionMatrix, l.modelViewMatrix);
|
|
989
|
+
mat.uniformsNeedUpdate = true;
|
|
990
|
+
};
|
|
991
|
+
return l;
|
|
992
|
+
}
|
|
299
993
|
function sharedLines(pos, im, material = sharedLineMat()) {
|
|
300
994
|
const { P, I } = weldLines(pos);
|
|
301
995
|
const g = new THREE.InstancedBufferGeometry;
|
|
@@ -426,29 +1120,96 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
426
1120
|
const cull = computeCulling(insts, H);
|
|
427
1121
|
mark("culling");
|
|
428
1122
|
const edgeObjs = [];
|
|
1123
|
+
const condObjs = [];
|
|
1124
|
+
const wantCond = P.cond !== false && (P.edges === "idx" || P.edges === true);
|
|
1125
|
+
let condMat = null;
|
|
1126
|
+
const cmat = () => condMat ??= condLineMat();
|
|
1127
|
+
const condCache = new Map;
|
|
1128
|
+
const addCond = (entry, im, which = "body") => {
|
|
1129
|
+
if (!wantCond)
|
|
1130
|
+
return null;
|
|
1131
|
+
const ck = which === "under" ? entry.condlu : entry.condl;
|
|
1132
|
+
if (!ck)
|
|
1133
|
+
return null;
|
|
1134
|
+
let cb = condCache.get(ck);
|
|
1135
|
+
if (cb === undefined) {
|
|
1136
|
+
const c = conditionalLines(pack, entry, which);
|
|
1137
|
+
cb = c && c.length ? condBuffers(c) : null;
|
|
1138
|
+
condCache.set(ck, cb);
|
|
1139
|
+
}
|
|
1140
|
+
if (!cb)
|
|
1141
|
+
return null;
|
|
1142
|
+
const l = condEdges(cb, im, cmat());
|
|
1143
|
+
edgeObjs.push(l);
|
|
1144
|
+
condObjs.push(l);
|
|
1145
|
+
root.add(l);
|
|
1146
|
+
return l;
|
|
1147
|
+
};
|
|
429
1148
|
const stats = { studsTotal: cull.studs.length, studsHidden: cull.studs.filter((s) => s.hidden).length, undersTotal: 0, undersHidden: 0 };
|
|
430
1149
|
const toggles = [];
|
|
431
1150
|
const studMeshes = [];
|
|
1151
|
+
const wantTex = P.tex !== false && !!H.textures;
|
|
1152
|
+
const texCache = new Map;
|
|
1153
|
+
const texMats = new Map;
|
|
1154
|
+
const pending = [];
|
|
1155
|
+
const texMat = (t, base, wrapU) => {
|
|
1156
|
+
const role = base === matF ? "F" : base === matB ? "B" : "D";
|
|
1157
|
+
const k = t + ":" + role + ":" + (wrapU ? 1 : 0);
|
|
1158
|
+
let m = texMats.get(k);
|
|
1159
|
+
if (!m) {
|
|
1160
|
+
let tx = texCache.get(t);
|
|
1161
|
+
if (!tx) {
|
|
1162
|
+
const r = packTexture(pack, t);
|
|
1163
|
+
tx = r.texture;
|
|
1164
|
+
pending.push(r.ready);
|
|
1165
|
+
texCache.set(t, tx);
|
|
1166
|
+
}
|
|
1167
|
+
texMats.set(k, m = makeTexturedMaterial(base.side, tx, wrapU));
|
|
1168
|
+
}
|
|
1169
|
+
return m;
|
|
1170
|
+
};
|
|
1171
|
+
let texBatches = 0;
|
|
432
1172
|
for (const [pk, ids] of byPart) {
|
|
433
1173
|
const part = pk.replace(/#m$/, "");
|
|
434
1174
|
const p = H.parts[part];
|
|
435
1175
|
const mat = P.uncertDouble !== false && p.uncert ? (nUncert++, matD) : pk.endsWith("#m") ? matB : matF;
|
|
436
|
-
const
|
|
1176
|
+
const textured = wantTex && !!p.tex;
|
|
1177
|
+
const body = geom(textured ? p.tbody : p.body, arr);
|
|
437
1178
|
let bodyIm = null;
|
|
438
|
-
|
|
439
|
-
const im = bodyIm = new THREE.InstancedMesh(body, mat, ids.length);
|
|
1179
|
+
const place = (im) => {
|
|
440
1180
|
ids.forEach((id, k) => {
|
|
441
1181
|
im.setMatrixAt(k, toM4(insts[id].m));
|
|
442
1182
|
im.setColorAt(k, color(insts[id].color));
|
|
443
1183
|
});
|
|
444
1184
|
im.computeBoundingSphere();
|
|
445
1185
|
root.add(im);
|
|
1186
|
+
};
|
|
1187
|
+
if (body) {
|
|
1188
|
+
const im = bodyIm = new THREE.InstancedMesh(body, mat, ids.length);
|
|
1189
|
+
place(im);
|
|
446
1190
|
}
|
|
1191
|
+
if (textured)
|
|
1192
|
+
for (const tb of p.tex) {
|
|
1193
|
+
const g = geom(tb, arr);
|
|
1194
|
+
const im = new THREE.InstancedMesh(g, texMat(tb.t, mat, !!tb.wrapU), ids.length);
|
|
1195
|
+
texBatches++;
|
|
1196
|
+
if (bodyIm) {
|
|
1197
|
+
im.instanceMatrix = bodyIm.instanceMatrix;
|
|
1198
|
+
im.instanceColor = bodyIm.instanceColor;
|
|
1199
|
+
im.boundingSphere = bodyIm.boundingSphere;
|
|
1200
|
+
root.add(im);
|
|
1201
|
+
} else {
|
|
1202
|
+
place(im);
|
|
1203
|
+
bodyIm = im;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
447
1206
|
if (P.edges === "idx" && p.edges && bodyIm) {
|
|
448
1207
|
const l = sharedLines(arr(p.edges), bodyIm);
|
|
449
1208
|
edgeObjs.push(l);
|
|
450
1209
|
root.add(l);
|
|
451
1210
|
}
|
|
1211
|
+
if (bodyIm)
|
|
1212
|
+
addCond(p, bodyIm);
|
|
452
1213
|
const under = geom(p.under, arr);
|
|
453
1214
|
if (under) {
|
|
454
1215
|
const ord = [...ids.filter((i) => !cull.underHidden[i]), ...ids.filter((i) => cull.underHidden[i])];
|
|
@@ -464,6 +1225,15 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
464
1225
|
im.count = P.cullUnder ? vis : ids.length;
|
|
465
1226
|
root.add(im);
|
|
466
1227
|
toggles.push({ mesh: im, all: ids.length, visible: vis, flag: "under" });
|
|
1228
|
+
{
|
|
1229
|
+
const cl = addCond(p, im, "under");
|
|
1230
|
+
if (cl) {
|
|
1231
|
+
toggles.push({ mesh: { set count(n) {
|
|
1232
|
+
cl.geometry.instanceCount = n;
|
|
1233
|
+
} }, all: ids.length, visible: vis, flag: "under" });
|
|
1234
|
+
cl.geometry.instanceCount = im.count;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
467
1237
|
}
|
|
468
1238
|
if (P.edges === true && p.edges) {
|
|
469
1239
|
const mats = new Float32Array(ids.length * 16);
|
|
@@ -508,6 +1278,15 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
508
1278
|
} }, all: ord.length, visible: vis, flag: "studs" });
|
|
509
1279
|
l.geometry.instanceCount = im.count;
|
|
510
1280
|
}
|
|
1281
|
+
{
|
|
1282
|
+
const cl = P.condStuds === false ? null : addCond(k, im);
|
|
1283
|
+
if (cl) {
|
|
1284
|
+
toggles.push({ mesh: { set count(n) {
|
|
1285
|
+
cl.geometry.instanceCount = n;
|
|
1286
|
+
} }, all: ord.length, visible: vis, flag: "studs" });
|
|
1287
|
+
cl.geometry.instanceCount = im.count;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
511
1290
|
if (P.edges === true && k.edges) {
|
|
512
1291
|
const mats = new Float32Array(ord.length * 16);
|
|
513
1292
|
ord.forEach((s, i) => toM4(s.w).toArray(mats, i * 16));
|
|
@@ -521,7 +1300,19 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
521
1300
|
}
|
|
522
1301
|
stats.uncertParts = Object.values(H.parts).filter((x) => x.uncert).length;
|
|
523
1302
|
stats.uncertBatches = nUncert;
|
|
524
|
-
|
|
1303
|
+
let condOn = true, edgesOn = true;
|
|
1304
|
+
const setEdges = (v) => {
|
|
1305
|
+
edgesOn = v;
|
|
1306
|
+
edgeObjs.forEach((o) => o.visible = v && (condOn || !condObjs.includes(o)));
|
|
1307
|
+
};
|
|
1308
|
+
const setCond = (v) => {
|
|
1309
|
+
condOn = v;
|
|
1310
|
+
condObjs.forEach((o) => o.visible = v && edgesOn);
|
|
1311
|
+
};
|
|
1312
|
+
stats.condBatches = condObjs.length;
|
|
1313
|
+
stats.texBatches = texBatches;
|
|
1314
|
+
stats.textures = texCache.size;
|
|
1315
|
+
const texturesReady = Promise.all(pending).then(() => {});
|
|
525
1316
|
const setCull = (studs, under) => {
|
|
526
1317
|
for (const t of toggles)
|
|
527
1318
|
t.mesh.count = (t.flag === "studs" ? studs : under) ? t.visible : t.all;
|
|
@@ -537,6 +1328,11 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
537
1328
|
matF.side = front ? THREE.FrontSide : THREE.DoubleSide;
|
|
538
1329
|
matB.side = front ? THREE.BackSide : THREE.DoubleSide;
|
|
539
1330
|
matF.needsUpdate = matB.needsUpdate = true;
|
|
1331
|
+
for (const [k, m] of texMats) {
|
|
1332
|
+
const role = k.split(":")[1];
|
|
1333
|
+
m.side = role === "D" || !front ? THREE.DoubleSide : role === "B" ? THREE.BackSide : THREE.FrontSide;
|
|
1334
|
+
m.needsUpdate = true;
|
|
1335
|
+
}
|
|
540
1336
|
};
|
|
541
1337
|
const swapStuds = (alt) => {
|
|
542
1338
|
const saved = studMeshes.map((s) => s.im.geometry);
|
|
@@ -547,7 +1343,7 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
547
1343
|
}
|
|
548
1344
|
return () => studMeshes.forEach((s, i) => s.im.geometry = saved[i]);
|
|
549
1345
|
};
|
|
550
|
-
return { root, stats, setCull, setSide, setEdges, swapStuds, box };
|
|
1346
|
+
return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, box, texturesReady };
|
|
551
1347
|
}
|
|
552
1348
|
// src/scene.ts
|
|
553
1349
|
import * as THREE2 from "three";
|
|
@@ -584,19 +1380,27 @@ function toPlastic(old) {
|
|
|
584
1380
|
if (!m) {
|
|
585
1381
|
const p = new THREE2.MeshPhysicalMaterial({ vertexColors: true, roughness: 0.3, clearcoat: 0.3, clearcoatRoughness: 0.2, specularIntensity: 0.6, side: old.side });
|
|
586
1382
|
p.onBeforeCompile = old.onBeforeCompile;
|
|
587
|
-
p.customProgramCacheKey = () => "
|
|
1383
|
+
p.customProgramCacheKey = () => "Phys" + old.customProgramCacheKey() + old.side;
|
|
1384
|
+
p.userData = old.userData;
|
|
588
1385
|
plasticCache.set(old, m = p);
|
|
589
1386
|
}
|
|
590
1387
|
return m;
|
|
591
1388
|
}
|
|
592
1389
|
function createLdrObject(instances, pack, options = {}) {
|
|
593
|
-
const o = { edges: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic", ...options };
|
|
1390
|
+
const o = { edges: true, conditionalLines: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic", ...options };
|
|
594
1391
|
const color = colorFn(o.colors, pack, new THREE2.Color(o.fallbackColor ?? 10528169));
|
|
595
|
-
const
|
|
1392
|
+
const useTex = o.textures !== false;
|
|
1393
|
+
const pick = (i) => useTex && i.texPart && pack.header.parts[i.texPart] ? i.texPart : i.part;
|
|
1394
|
+
const all = instances.map((i) => ({ part: pick(i), color: String(i.color), m: i.m, steps: i.steps }));
|
|
596
1395
|
const key = (i) => i.steps.map((x) => String(x).padStart(6, "0")).join(".");
|
|
597
1396
|
const uniq = [...new Set(all.map(key))].sort();
|
|
598
1397
|
const rank = new Map(uniq.map((k, j) => [k, j]));
|
|
599
1398
|
const stepOf = all.map((i) => rank.get(key(i)));
|
|
1399
|
+
const stepViews = new Array(uniq.length).fill(null);
|
|
1400
|
+
instances.forEach((i, k) => {
|
|
1401
|
+
if (i.view)
|
|
1402
|
+
stepViews[stepOf[k]] = i.view;
|
|
1403
|
+
});
|
|
600
1404
|
const missing = [...new Set(all.filter((i) => !pack.header.parts[i.part]).map((i) => i.part))];
|
|
601
1405
|
const object = new THREE2.Group;
|
|
602
1406
|
object.name = "ldr";
|
|
@@ -606,7 +1410,7 @@ function createLdrObject(instances, pack, options = {}) {
|
|
|
606
1410
|
object.scale.setScalar(o.scale);
|
|
607
1411
|
const pairs = new Map;
|
|
608
1412
|
let cur = null;
|
|
609
|
-
let { edges, cullStuds: cs, cullUndersides: cu, side } = o;
|
|
1413
|
+
let { edges, conditionalLines: cond, cullStuds: cs, cullUndersides: cu, side } = o;
|
|
610
1414
|
const stats = { instances: all.length, drawn: 0, missing, studsTotal: 0, studsHidden: 0, undersTotal: 0, undersHidden: 0, buildMs: 0 };
|
|
611
1415
|
const disposeCur = () => {
|
|
612
1416
|
if (!cur)
|
|
@@ -620,17 +1424,17 @@ function createLdrObject(instances, pack, options = {}) {
|
|
|
620
1424
|
if (x.dispose && x.isInstancedMesh)
|
|
621
1425
|
x.dispose();
|
|
622
1426
|
});
|
|
623
|
-
for (const m of mats)
|
|
1427
|
+
for (const m of [...mats, ...pairs.values()]) {
|
|
1428
|
+
m.userData.ldrMap?.dispose();
|
|
624
1429
|
m.dispose();
|
|
625
|
-
|
|
626
|
-
old.dispose();
|
|
1430
|
+
}
|
|
627
1431
|
cur = null;
|
|
628
1432
|
};
|
|
629
1433
|
const build = (step) => {
|
|
630
1434
|
const t0 = performance.now();
|
|
631
1435
|
disposeCur();
|
|
632
1436
|
const insts = step == null ? all : all.filter((_, k) => stepOf[k] <= step);
|
|
633
|
-
cur = buildLdrScene(insts, pack, { pack: "", cullStuds: cs, cullUnder: cu, edges: edges ? "idx" : false, side }, color, () => {});
|
|
1437
|
+
cur = buildLdrScene(insts, pack, { pack: "", cullStuds: cs, cullUnder: cu, edges: edges ? "idx" : false, side, cond, condStuds: o.conditionalStudLines !== false, tex: o.textures !== false }, color, () => {});
|
|
634
1438
|
pairs.clear();
|
|
635
1439
|
if (o.material === "plastic")
|
|
636
1440
|
cur.root.traverse((x) => {
|
|
@@ -642,19 +1446,28 @@ function createLdrObject(instances, pack, options = {}) {
|
|
|
642
1446
|
});
|
|
643
1447
|
if (!edges)
|
|
644
1448
|
cur.setEdges(false);
|
|
1449
|
+
if (!cond)
|
|
1450
|
+
cur.setCond(false);
|
|
645
1451
|
object.add(cur.root);
|
|
646
|
-
Object.assign(stats, { drawn: insts.filter((i) => pack.header.parts[i.part]).length, studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, buildMs: performance.now() - t0 });
|
|
1452
|
+
Object.assign(stats, { drawn: insts.filter((i) => pack.header.parts[i.part]).length, studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, condBatches: cur.stats.condBatches ?? 0, texBatches: cur.stats.texBatches ?? 0, textures: cur.stats.textures ?? 0, buildMs: performance.now() - t0 });
|
|
1453
|
+
api.texturesReady = cur.texturesReady;
|
|
647
1454
|
};
|
|
1455
|
+
const api = { texturesReady: Promise.resolve() };
|
|
648
1456
|
build(o.step ?? null);
|
|
649
|
-
return {
|
|
1457
|
+
return Object.assign(api, {
|
|
650
1458
|
object,
|
|
651
1459
|
stats,
|
|
652
1460
|
stepCount: uniq.length,
|
|
1461
|
+
stepViews,
|
|
653
1462
|
setStep: (s) => build(s),
|
|
654
1463
|
setEdges: (v) => {
|
|
655
1464
|
edges = v;
|
|
656
1465
|
cur.setEdges(v);
|
|
657
1466
|
},
|
|
1467
|
+
setConditionalLines: (v) => {
|
|
1468
|
+
cond = v;
|
|
1469
|
+
cur.setCond(v);
|
|
1470
|
+
},
|
|
658
1471
|
setCulling: (a, b) => {
|
|
659
1472
|
cs = a;
|
|
660
1473
|
cu = b;
|
|
@@ -681,7 +1494,7 @@ function createLdrObject(instances, pack, options = {}) {
|
|
|
681
1494
|
return target;
|
|
682
1495
|
},
|
|
683
1496
|
dispose: disposeCur
|
|
684
|
-
};
|
|
1497
|
+
});
|
|
685
1498
|
}
|
|
686
1499
|
// src/pack-io.ts
|
|
687
1500
|
import { decompress } from "fzstd";
|
|
@@ -697,14 +1510,26 @@ async function decompressPack(data) {
|
|
|
697
1510
|
export {
|
|
698
1511
|
I as IDENTITY,
|
|
699
1512
|
buildLdrScene,
|
|
1513
|
+
colorToken,
|
|
700
1514
|
computeCulling,
|
|
1515
|
+
conditionalLineVisible,
|
|
1516
|
+
conditionalLines,
|
|
1517
|
+
createInstancedConditionalEdges,
|
|
701
1518
|
createInstancedEdges,
|
|
702
1519
|
createLdrObject,
|
|
703
1520
|
decodePack,
|
|
704
1521
|
decompressPack,
|
|
1522
|
+
fileKind,
|
|
1523
|
+
fileStepViews,
|
|
705
1524
|
flatten,
|
|
1525
|
+
hash32,
|
|
1526
|
+
invert,
|
|
706
1527
|
isDirectColor,
|
|
707
1528
|
loadPack,
|
|
1529
|
+
looseFile,
|
|
1530
|
+
loosePartName,
|
|
1531
|
+
looseSource,
|
|
1532
|
+
looseSteps,
|
|
708
1533
|
makeMaterial,
|
|
709
1534
|
mul,
|
|
710
1535
|
norm,
|
|
@@ -712,6 +1537,14 @@ export {
|
|
|
712
1537
|
parseColor,
|
|
713
1538
|
parseLDConfig,
|
|
714
1539
|
parseModel,
|
|
1540
|
+
parseTexMap,
|
|
1541
|
+
resolveTexture,
|
|
1542
|
+
rotStepMatrix,
|
|
715
1543
|
sequenceSteps,
|
|
716
|
-
smoothNormals
|
|
1544
|
+
smoothNormals,
|
|
1545
|
+
stepViewMatrix,
|
|
1546
|
+
texWrapper,
|
|
1547
|
+
texmapTriangleUV,
|
|
1548
|
+
texmapUV,
|
|
1549
|
+
transformTexMap
|
|
717
1550
|
};
|