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/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
- const bucket = (b) => {
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
- const vcount = P.length / 3;
1516
- lastPos = P;
1517
- return { pos: put(new Float32Array(P)), nrm: put(new Float32Array(N)), col: put(new Float32Array(C)), idx: put(vcount < 65536 ? new Uint16Array(I) : new Uint32Array(I)), tris: n / 3 };
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";