ldr 0.4.0 → 0.5.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 +168 -80
- package/dist/cdn.d.ts +62 -0
- package/dist/cli.js +148 -19
- package/dist/index.d.ts +4 -2
- package/dist/index.js +962 -204
- package/dist/node/build-pack.d.ts +6 -0
- package/dist/node/compile.d.ts +4 -0
- package/dist/node/index.js +1098 -219
- package/dist/node/lod.d.ts +17 -0
- package/dist/render.d.ts +43 -1
- package/dist/scene.d.ts +16 -2
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/cli.ts
|
|
3
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/cli.ts
|
|
4
4
|
import fs4 from "node:fs";
|
|
5
5
|
import zlib from "node:zlib";
|
|
6
6
|
|
|
7
|
-
// src/node/build-pack.ts
|
|
7
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/build-pack.ts
|
|
8
8
|
import fs3 from "node:fs";
|
|
9
9
|
import path3 from "node:path";
|
|
10
10
|
|
|
11
|
-
// src/node/resolver.ts
|
|
11
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/resolver.ts
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
|
|
15
|
-
// src/spec.ts
|
|
15
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/spec.ts
|
|
16
16
|
var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
17
17
|
function parseColor(tok) {
|
|
18
18
|
if (/^0x/i.test(tok))
|
|
@@ -662,7 +662,7 @@ function fileStepViews(f) {
|
|
|
662
662
|
return out;
|
|
663
663
|
}
|
|
664
664
|
|
|
665
|
-
// src/model.ts
|
|
665
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/model.ts
|
|
666
666
|
function flatten(doc, opts = {}) {
|
|
667
667
|
const out = [];
|
|
668
668
|
const maxDepth = opts.maxDepth ?? 64;
|
|
@@ -784,7 +784,7 @@ function parseLDConfig(text) {
|
|
|
784
784
|
return out;
|
|
785
785
|
}
|
|
786
786
|
|
|
787
|
-
// src/node/resolver.ts
|
|
787
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/resolver.ts
|
|
788
788
|
var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
|
|
789
789
|
var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
|
|
790
790
|
var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
@@ -1054,9 +1054,118 @@ function collectDeps(lib, refs) {
|
|
|
1054
1054
|
return out;
|
|
1055
1055
|
}
|
|
1056
1056
|
|
|
1057
|
-
// src/node/compile.ts
|
|
1057
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/compile.ts
|
|
1058
1058
|
import fs2 from "node:fs";
|
|
1059
1059
|
import path2 from "node:path";
|
|
1060
|
+
|
|
1061
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/lod.ts
|
|
1062
|
+
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
|
1063
|
+
await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
|
|
1064
|
+
function reorderMesh(P, N, C, I) {
|
|
1065
|
+
const idx = Uint32Array.from(I);
|
|
1066
|
+
const n = P.length / 3;
|
|
1067
|
+
const [remap] = MeshoptEncoder.reorderMesh(idx, true, false);
|
|
1068
|
+
const P2 = new Float32Array(P.length), N2 = new Float32Array(N.length), C2 = new Float32Array(C.length);
|
|
1069
|
+
for (let v = 0;v < n; v++) {
|
|
1070
|
+
const r = remap[v];
|
|
1071
|
+
if (r === 4294967295)
|
|
1072
|
+
continue;
|
|
1073
|
+
P2[r * 3] = P[v * 3];
|
|
1074
|
+
P2[r * 3 + 1] = P[v * 3 + 1];
|
|
1075
|
+
P2[r * 3 + 2] = P[v * 3 + 2];
|
|
1076
|
+
N2[r * 3] = N[v * 3];
|
|
1077
|
+
N2[r * 3 + 1] = N[v * 3 + 1];
|
|
1078
|
+
N2[r * 3 + 2] = N[v * 3 + 2];
|
|
1079
|
+
for (let k = 0;k < 4; k++)
|
|
1080
|
+
C2[r * 4 + k] = C[v * 4 + k];
|
|
1081
|
+
}
|
|
1082
|
+
return { P: P2, N: N2, C: C2, I: idx };
|
|
1083
|
+
}
|
|
1084
|
+
var TARGETS = [0.25, 0.08];
|
|
1085
|
+
var MIN_TRIS = 64;
|
|
1086
|
+
function flatMesh(tris) {
|
|
1087
|
+
const map = new Map;
|
|
1088
|
+
const P = [], N = [], C = [], I = [];
|
|
1089
|
+
for (const { p, i: idx, col } of tris)
|
|
1090
|
+
for (let t = 0;t < idx.length; t += 3) {
|
|
1091
|
+
const a = idx[t] * 3, b = idx[t + 1] * 3, c = idx[t + 2] * 3;
|
|
1092
|
+
const ux = p[b] - p[a], uy = p[b + 1] - p[a + 1], uz = p[b + 2] - p[a + 2], vx = p[c] - p[a], vy = p[c + 1] - p[a + 1], vz = p[c + 2] - p[a + 2];
|
|
1093
|
+
let nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx;
|
|
1094
|
+
const l = Math.hypot(nx, ny, nz);
|
|
1095
|
+
if (!(l > 0.000000001))
|
|
1096
|
+
continue;
|
|
1097
|
+
nx /= l;
|
|
1098
|
+
ny /= l;
|
|
1099
|
+
nz /= l;
|
|
1100
|
+
for (const o of [a, b, c]) {
|
|
1101
|
+
const k = `${p[o].toFixed(3)},${p[o + 1].toFixed(3)},${p[o + 2].toFixed(3)},${nx.toFixed(3)},${ny.toFixed(3)},${nz.toFixed(3)},${col.join()}`;
|
|
1102
|
+
let j = map.get(k);
|
|
1103
|
+
if (j === undefined) {
|
|
1104
|
+
j = P.length / 3;
|
|
1105
|
+
map.set(k, j);
|
|
1106
|
+
P.push(p[o], p[o + 1], p[o + 2]);
|
|
1107
|
+
N.push(nx, ny, nz);
|
|
1108
|
+
C.push(...col);
|
|
1109
|
+
}
|
|
1110
|
+
I.push(j);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return { P: new Float32Array(P), N: new Float32Array(N), C: new Float32Array(C), I: new Uint32Array(I) };
|
|
1114
|
+
}
|
|
1115
|
+
function lodChain(P, C, I) {
|
|
1116
|
+
const nTris = I.length / 3;
|
|
1117
|
+
if (nTris < MIN_TRIS)
|
|
1118
|
+
return [];
|
|
1119
|
+
const groups = new Map;
|
|
1120
|
+
for (let t = 0;t < I.length; t += 3) {
|
|
1121
|
+
const v = I[t] * 4;
|
|
1122
|
+
const col = [C[v], C[v + 1], C[v + 2], C[v + 3]];
|
|
1123
|
+
const k = col.join();
|
|
1124
|
+
let g = groups.get(k);
|
|
1125
|
+
if (!g)
|
|
1126
|
+
groups.set(k, g = { col, tri: [] });
|
|
1127
|
+
g.tri.push(I[t], I[t + 1], I[t + 2]);
|
|
1128
|
+
}
|
|
1129
|
+
const welded = [...groups.values()].map(({ col, tri }) => {
|
|
1130
|
+
const map = new Map;
|
|
1131
|
+
const pos = [];
|
|
1132
|
+
const idx = new Uint32Array(tri.length);
|
|
1133
|
+
for (let i = 0;i < tri.length; i++) {
|
|
1134
|
+
const o = tri[i] * 3;
|
|
1135
|
+
const k = `${P[o].toFixed(3)},${P[o + 1].toFixed(3)},${P[o + 2].toFixed(3)}`;
|
|
1136
|
+
let j = map.get(k);
|
|
1137
|
+
if (j === undefined) {
|
|
1138
|
+
j = pos.length / 3;
|
|
1139
|
+
map.set(k, j);
|
|
1140
|
+
pos.push(P[o], P[o + 1], P[o + 2]);
|
|
1141
|
+
}
|
|
1142
|
+
idx[i] = j;
|
|
1143
|
+
}
|
|
1144
|
+
return { col, p: new Float32Array(pos), i: idx };
|
|
1145
|
+
});
|
|
1146
|
+
const out = [];
|
|
1147
|
+
let prevTris = nTris;
|
|
1148
|
+
for (let li = 0;li < TARGETS.length; li++) {
|
|
1149
|
+
let err = 0;
|
|
1150
|
+
const parts = [];
|
|
1151
|
+
for (const g of welded) {
|
|
1152
|
+
const target = Math.max(3, Math.floor(g.i.length * TARGETS[li] / 3) * 3);
|
|
1153
|
+
const [idx, e] = MeshoptSimplifier.simplify(g.i, g.p, 3, target, 1e9, li ? ["ErrorAbsolute", "Prune"] : ["ErrorAbsolute"]);
|
|
1154
|
+
err = Math.max(err, e);
|
|
1155
|
+
if (idx.length)
|
|
1156
|
+
parts.push({ p: g.p, i: idx, col: g.col });
|
|
1157
|
+
}
|
|
1158
|
+
const m = flatMesh(parts);
|
|
1159
|
+
const t = m.I.length / 3;
|
|
1160
|
+
if (t > prevTris * 0.7 || !t)
|
|
1161
|
+
continue;
|
|
1162
|
+
out.push({ ...m, err: +Math.max(err, 0.001).toFixed(3) });
|
|
1163
|
+
prevTris = t;
|
|
1164
|
+
}
|
|
1165
|
+
return out;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/compile.ts
|
|
1060
1169
|
function loadColors(root) {
|
|
1061
1170
|
const map = new Map;
|
|
1062
1171
|
for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
|
|
@@ -1473,7 +1582,8 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
1473
1582
|
return r;
|
|
1474
1583
|
};
|
|
1475
1584
|
let lastPos = [];
|
|
1476
|
-
|
|
1585
|
+
let anyLod = false;
|
|
1586
|
+
const bucket = (b, lod = false) => {
|
|
1477
1587
|
const n = b.pos.length / 3;
|
|
1478
1588
|
lastPos = [];
|
|
1479
1589
|
if (!n)
|
|
@@ -1512,9 +1622,20 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
1512
1622
|
}
|
|
1513
1623
|
I.push(j);
|
|
1514
1624
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1625
|
+
let m = { P, N, C, I };
|
|
1626
|
+
if (options.reorder && I.length >= 3)
|
|
1627
|
+
m = reorderMesh(P, N, C, I);
|
|
1628
|
+
const vcount = m.P.length / 3;
|
|
1629
|
+
lastPos = m.P;
|
|
1630
|
+
const out = { pos: put(Float32Array.from(m.P)), nrm: put(Float32Array.from(m.N)), col: put(Float32Array.from(m.C)), idx: put(vcount < 65536 ? Uint16Array.from(m.I) : Uint32Array.from(m.I)), tris: n / 3 };
|
|
1631
|
+
if (lod && options.lod) {
|
|
1632
|
+
const chain = lodChain(P, C, I).map((l) => options.reorder ? { ...l, ...reorderMesh(l.P, l.N, l.C, l.I) } : l);
|
|
1633
|
+
if (chain.length) {
|
|
1634
|
+
anyLod = true;
|
|
1635
|
+
out.lod = chain.map((l) => ({ pos: put(l.P), col: put(Uint8Array.from(l.C, (x) => Math.round(x * 255))), idx: put(l.P.length / 3 < 65536 ? new Uint16Array(l.I) : l.I), tris: l.I.length / 3, err: l.err }));
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return out;
|
|
1518
1639
|
};
|
|
1519
1640
|
let anyCond = false;
|
|
1520
1641
|
const condl = (a, pools) => {
|
|
@@ -1605,18 +1726,18 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
1605
1726
|
});
|
|
1606
1727
|
return { tbody: bucket(c.tbody), tex };
|
|
1607
1728
|
};
|
|
1608
|
-
const enc = (c) => {
|
|
1609
|
-
const body = bucket(c.body), bodyP = lastPos, under = bucket(c.under), underP = lastPos;
|
|
1729
|
+
const enc = (c, lod = false) => {
|
|
1730
|
+
const body = bucket(c.body, lod), bodyP = lastPos, under = bucket(c.under, lod), underP = lastPos;
|
|
1610
1731
|
const edges = c.edges.length ? put(new Float32Array(c.edges)) : null;
|
|
1611
1732
|
const condlB = condl(c.cond, [bodyP, underP, c.edges]), condlU = condl(c.condUnder, [bodyP, underP, c.edges]);
|
|
1612
1733
|
return { body, under, edges, cond: c.condLines, condl: condlB, condlu: condlU, studs: c.studs.map((s) => [s.kind, s.color, ...s.m.map((x) => +x.toFixed(4))]), bbox: c.bbox, fullBottom: c.fullBottom, uncert: !!c.uncertified, mask: c.bottomMask ? [c.bottomMask.nx, c.bottomMask.nz, c.bottomMask.bits.join("")] : null, ...encTex(c) };
|
|
1613
1734
|
};
|
|
1614
|
-
const encParts = Object.fromEntries([...parts].map(([k, c]) => [k, enc(c)])), encKinds = Object.fromEntries([...studKinds].map(([k, c]) => [k, enc(c)]));
|
|
1735
|
+
const encParts = Object.fromEntries([...parts].map(([k, c]) => [k, enc(c, true)])), encKinds = Object.fromEntries([...studKinds].map(([k, c]) => [k, enc(c)]));
|
|
1615
1736
|
const textures = texNames.map((name) => {
|
|
1616
1737
|
const b = options.texture?.(name);
|
|
1617
1738
|
return b ? { name, mime: "image/png", data: put(b) } : { name, missing: true };
|
|
1618
1739
|
});
|
|
1619
|
-
const features = [...anyCond ? ["cond"] : [], ...texNames.length ? ["tex"] : []];
|
|
1740
|
+
const features = [...anyCond ? ["cond"] : [], ...texNames.length ? ["tex"] : [], ...anyLod ? ["lod"] : []];
|
|
1620
1741
|
const header = { version: features.length ? 2 : 1, ...features.length ? { features } : {}, weld, parts: encParts, studKinds: encKinds, ...texNames.length ? { textures } : {}, ...extra };
|
|
1621
1742
|
const hj = new TextEncoder().encode(JSON.stringify(header));
|
|
1622
1743
|
const hpad = (4 - (4 + hj.length) % 4) % 4;
|
|
@@ -1627,7 +1748,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
1627
1748
|
return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
|
|
1628
1749
|
}
|
|
1629
1750
|
|
|
1630
|
-
// src/node/build-pack.ts
|
|
1751
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/node/build-pack.ts
|
|
1631
1752
|
function buildPack(o) {
|
|
1632
1753
|
const lib = new Library(o.ldrawRoot);
|
|
1633
1754
|
const refs = new Map;
|
|
@@ -1687,10 +1808,10 @@ function buildPack(o) {
|
|
|
1687
1808
|
return b ? new Uint8Array(Buffer.from(b, "base64")) : comp.texture(n);
|
|
1688
1809
|
};
|
|
1689
1810
|
const warnings = [...lib.conflicts].map((n) => `${n}: defined differently by two models; the first definition is used`);
|
|
1690
|
-
return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
|
|
1811
|
+
return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture, lod: o.lod !== false, reorder: !!o.reorder }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
|
|
1691
1812
|
}
|
|
1692
1813
|
|
|
1693
|
-
// src/cli.ts
|
|
1814
|
+
// ../../../../../../../tmp/ldr-build-qIdKSv/src/cli.ts
|
|
1694
1815
|
var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
|
|
1695
1816
|
|
|
1696
1817
|
Usage:
|
|
@@ -1707,6 +1828,8 @@ Options:
|
|
|
1707
1828
|
--cond Keep conditional (type-5) lines for curved-surface outlines (pack v2, ~+8-14% zstd)
|
|
1708
1829
|
--textures Keep printed/sticker parts' !TEXMAP geometry + UVs and embed their PNGs (pack v2; packs without
|
|
1709
1830
|
textured parts are unchanged). Textures come from model !DATA blocks, then <ldraw>/parts/textures
|
|
1831
|
+
--no-lod Skip distance-LOD meshes (simplified bodies drawn only where their error is under a pixel)
|
|
1832
|
+
--reorder Reorder triangles/vertices for vertex-cache reuse (~-3% frame time without LOD, +1% size)
|
|
1710
1833
|
--no-colors Don't embed the LDConfig colour table
|
|
1711
1834
|
--no-loose-geometry Skip a model file's own type 2-5 lines (drawn in the model, not in a part). By default they become
|
|
1712
1835
|
synthetic parts "<file>#<step>" so the model renders as the LDraw spec says
|
|
@@ -1729,6 +1852,8 @@ var all = false;
|
|
|
1729
1852
|
var cond = false;
|
|
1730
1853
|
var textures = false;
|
|
1731
1854
|
var loose = true;
|
|
1855
|
+
var lod = true;
|
|
1856
|
+
var reorder = false;
|
|
1732
1857
|
for (let i = 0;i < a.length; i++) {
|
|
1733
1858
|
const k = a[i];
|
|
1734
1859
|
const v = () => {
|
|
@@ -1761,6 +1886,10 @@ for (let i = 0;i < a.length; i++) {
|
|
|
1761
1886
|
loose = false;
|
|
1762
1887
|
else if (k === "--all-parts")
|
|
1763
1888
|
all = true;
|
|
1889
|
+
else if (k === "--no-lod")
|
|
1890
|
+
lod = false;
|
|
1891
|
+
else if (k === "--reorder")
|
|
1892
|
+
reorder = true;
|
|
1764
1893
|
else if (k.startsWith("-")) {
|
|
1765
1894
|
console.error(`unknown option ${k}
|
|
1766
1895
|
|
|
@@ -1778,7 +1907,7 @@ if (!fs4.existsSync(ldraw + "/LDConfig.ldr")) {
|
|
|
1778
1907
|
process.exit(1);
|
|
1779
1908
|
}
|
|
1780
1909
|
var t0 = performance.now();
|
|
1781
|
-
var r = buildPack({ ldrawRoot: ldraw, models, parts, allParts: all, studs, primitives: prims, weld, colors, cond, textures, looseGeometry: loose });
|
|
1910
|
+
var r = buildPack({ ldrawRoot: ldraw, models, parts, allParts: all, studs, primitives: prims, weld, colors, cond, textures, looseGeometry: loose, lod, reorder });
|
|
1782
1911
|
var data = r.data;
|
|
1783
1912
|
if (out.endsWith(".gz"))
|
|
1784
1913
|
data = zlib.gzipSync(data, { level: 9 });
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,10 @@ export { parse, parseColor, isDirectColor, mul, I as IDENTITY, norm, smoothNorma
|
|
|
2
2
|
export type { M as Matrix12, Doc, LFile, Cmd, RotStep, BufExchg, TexMap, TexMapCmd, TexMapMethod, TexAnno, DataBlock, PartMesh, FileKind, StepView } from "./spec.js";
|
|
3
3
|
export { flatten, parseModel, sequenceSteps, parseLDConfig } from "./model.js";
|
|
4
4
|
export type { PartInstance, FlattenOptions, LDrawColor } from "./model.js";
|
|
5
|
-
export { loadPack, decodePack, buildLdrScene, makeMaterial, createInstancedEdges, computeCulling, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible } from "./render.js";
|
|
6
|
-
export type { Pack, LdrParams, Inst } from "./render.js";
|
|
5
|
+
export { loadPack, decodePack, buildLdrScene, makeMaterial, createInstancedEdges, computeCulling, cullCore, forEachStud, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible } from "./render.js";
|
|
6
|
+
export type { Pack, LdrParams, Inst, CullResult } from "./render.js";
|
|
7
7
|
export { createLdrObject } from "./scene.js";
|
|
8
8
|
export type { LdrObject, LdrObjectOptions, LdrStats, ColorSource } from "./scene.js";
|
|
9
9
|
export { decompressPack } from "./pack-io.js";
|
|
10
|
+
export { loadModel, loadParts, loadPartsIndex, mergePacks, DEFAULT_PARTS_CDN } from "./cdn.js";
|
|
11
|
+
export type { LoadModelOptions, LoadedModel, LoadPartsOptions, PartsCdnIndex } from "./cdn.js";
|