ldr 0.33.0 → 0.33.2

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 CHANGED
@@ -209,7 +209,7 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
209
209
 
210
210
  ## LDraw spec compliance
211
211
 
212
- - Line types 0–5. BFC: `CERTIFY`, `CCW`/`CW`, `INVERTNEXT`, `NOCLIP`, inverted matrices.
212
+ - Line types 0–5. BFC (ldraw.org article 415): `CERTIFY`/`NOCERTIFY` (decided by the first BFC statement before the file's first type 1-5 line, so `0 BFC CCW` alone certifies and a late `CERTIFY` doesn't), `CCW`/`CW`, `CLIP`/`NOCLIP` (polygons drawn while culling is off are visible from both sides, including in subfiles referenced then, which can't turn it back on), `INVERTNEXT` (the immediately following subfile reference only), inverted matrices, and degenerate (determinant 0) matrices: a subfile that lies flat at y = 0 is repaired the way LDView and L3P do (an all-zero row, or else the all-zero Y column, gets a 1, and the repaired determinant decides the winding); any other singular reference, including a part placed squashed flat in a model, is drawn without culling.
213
213
  - MPD: `0 FILE` / `0 NOFILE`, and `0 !DATA` (base64 PNGs). An MPD file is a part or a model by its `!LDRAW_ORG` type, otherwise by whether it has its own geometry. Embedded parts override library parts.
214
214
  - Geometry drawn directly in model files (type 2–5 lines outside parts), on by default.
215
215
  - `0 STEP`, nested submodel step sequencing, `0 ROTSTEP` (`REL`/`ADD`/`ABS`/`END`), and `0 BUFEXCHG`.
package/dist/cli.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../../../../../../tmp/ldr-build-efpWDR/src/cli.ts
3
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/cli.ts
4
4
  import fs4 from "node:fs";
5
5
  import zlib from "node:zlib";
6
6
 
7
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/build-pack.ts
7
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/build-pack.ts
8
8
  import fs3 from "node:fs";
9
9
  import path3 from "node:path";
10
10
 
11
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/resolver.ts
11
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/resolver.ts
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
 
15
- // ../../../../../../../tmp/ldr-build-efpWDR/src/spec.ts
15
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/spec.ts
16
16
  var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
17
17
  var COLOUR_ID_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
18
18
  var namedCodes = new Map;
@@ -671,6 +671,29 @@ function invert(m) {
671
671
  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];
672
672
  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];
673
673
  }
674
+ function singularFix(m, flatY) {
675
+ const det = (q) => q[3] * (q[7] * q[11] - q[8] * q[10]) - q[4] * (q[6] * q[11] - q[8] * q[9]) + q[5] * (q[6] * q[10] - q[7] * q[9]);
676
+ const rows = Math.hypot(m[3], m[4], m[5]) * Math.hypot(m[6], m[7], m[8]) * Math.hypot(m[9], m[10], m[11]);
677
+ if (!(Math.abs(det(m)) <= 0.000000001 * rows))
678
+ return null;
679
+ if (!flatY())
680
+ return { m, cull: false };
681
+ const q = [...m];
682
+ const ok = () => Math.abs(det(q)) > 0.000000001 * Math.hypot(q[3], q[4], q[5]) * Math.hypot(q[6], q[7], q[8]) * Math.hypot(q[9], q[10], q[11]);
683
+ for (let i = 0;i < 3; i++)
684
+ if (q[3 + 3 * i] === 0 && q[4 + 3 * i] === 0 && q[5 + 3 * i] === 0) {
685
+ q[4 + 3 * i] = 1;
686
+ if (ok())
687
+ return { m: q, cull: true };
688
+ }
689
+ if (q[4] === 0 && q[7] === 0 && q[10] === 0)
690
+ for (let i = 0;i < 3; i++) {
691
+ q[4 + 3 * i] = 1;
692
+ if (ok())
693
+ return { m: q, cull: true };
694
+ }
695
+ return { m, cull: false };
696
+ }
674
697
  var num = (x) => String(+x.toFixed(6) || 0);
675
698
  var quote = (s) => '"' + s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"';
676
699
  function texWrapper(part, map) {
@@ -723,7 +746,7 @@ function fileStepViews(f) {
723
746
  return out;
724
747
  }
725
748
 
726
- // ../../../../../../../tmp/ldr-build-efpWDR/src/model.ts
749
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/model.ts
727
750
  var CALLOUT_END = 999999;
728
751
  var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
729
752
  var defLine = new WeakMap;
@@ -1138,7 +1161,7 @@ function blendedColourHex(code) {
1138
1161
  return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
1139
1162
  }
1140
1163
 
1141
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/resolver.ts
1164
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/resolver.ts
1142
1165
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
1143
1166
  var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
1144
1167
  var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
@@ -1465,11 +1488,11 @@ function collectDeps(lib, refs) {
1465
1488
  return out;
1466
1489
  }
1467
1490
 
1468
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/compile.ts
1491
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/compile.ts
1469
1492
  import fs2 from "node:fs";
1470
1493
  import path2 from "node:path";
1471
1494
 
1472
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/lod.ts
1495
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/lod.ts
1473
1496
  import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
1474
1497
  await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
1475
1498
  function reorderMesh(P, N, C, I) {
@@ -1661,7 +1684,55 @@ function lodChain(P, C, I) {
1661
1684
  return out;
1662
1685
  }
1663
1686
 
1664
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/compile.ts
1687
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/compile.ts
1688
+ class BfcState {
1689
+ accum;
1690
+ ccw = true;
1691
+ certified = null;
1692
+ local = true;
1693
+ inv = false;
1694
+ constructor(accum = true) {
1695
+ this.accum = accum;
1696
+ }
1697
+ meta(t) {
1698
+ const o = t.split(/\s+/).slice(2);
1699
+ this.inv = false;
1700
+ if (this.certified === null)
1701
+ this.certified = !o.includes("NOCERTIFY");
1702
+ if (!this.certified)
1703
+ return;
1704
+ for (const w of o) {
1705
+ if (w === "CW")
1706
+ this.ccw = false;
1707
+ else if (w === "CCW")
1708
+ this.ccw = true;
1709
+ else if (w === "CLIP")
1710
+ this.local = true;
1711
+ else if (w === "NOCLIP")
1712
+ this.local = false;
1713
+ else if (w === "INVERTNEXT")
1714
+ this.inv = true;
1715
+ }
1716
+ }
1717
+ line(type) {
1718
+ if (type !== "0" && this.certified === null)
1719
+ this.certified = false;
1720
+ if (type !== "1")
1721
+ this.inv = false;
1722
+ }
1723
+ takeInvert() {
1724
+ const v = this.inv;
1725
+ this.inv = false;
1726
+ return v;
1727
+ }
1728
+ get cull() {
1729
+ return this.accum && this.local;
1730
+ }
1731
+ }
1732
+ var windOrder = (n, flip, cull) => {
1733
+ const f = n === 3 ? [0, 1, 2] : [0, 1, 2, 0, 2, 3], b = n === 3 ? [0, 2, 1] : [0, 2, 1, 0, 3, 2];
1734
+ return cull ? flip ? b : f : [...f, ...b];
1735
+ };
1665
1736
  function loadColors(root) {
1666
1737
  const map = new Map;
1667
1738
  for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
@@ -1705,6 +1776,40 @@ class Compiler {
1705
1776
  this.opts = opts;
1706
1777
  this.colors = loadColors(lib.root);
1707
1778
  }
1779
+ flat = new Map;
1780
+ flatY(key) {
1781
+ const hit = this.flat.get(key);
1782
+ if (hit !== undefined)
1783
+ return hit;
1784
+ const walk = (fk, M, depth) => {
1785
+ if (depth > 40)
1786
+ return true;
1787
+ for (const raw of this.lib.read(fk).split(/\r?\n/)) {
1788
+ const t = raw.trim();
1789
+ const c = t[0];
1790
+ if (c === "1") {
1791
+ const r = parseType1(t);
1792
+ if (!r)
1793
+ continue;
1794
+ const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
1795
+ const k = this.resolve(r.name, TOP_STUD_RE.test(base));
1796
+ if (k && !walk(k, compose(M, r.m), depth + 1))
1797
+ return false;
1798
+ continue;
1799
+ }
1800
+ if (c !== "2" && c !== "3" && c !== "4" && c !== "5")
1801
+ continue;
1802
+ const v = t.split(/\s+/).slice(2).map(Number), n = c === "2" ? 2 : c === "3" ? 3 : 4;
1803
+ for (let i = 0;i < n * 3; i += 3)
1804
+ if (Math.abs(M[1] + M[6] * v[i] + M[7] * v[i + 1] + M[8] * v[i + 2]) > 0.000001)
1805
+ return false;
1806
+ }
1807
+ return true;
1808
+ };
1809
+ const r = walk(key, IDENT, 0);
1810
+ this.flat.set(key, r);
1811
+ return r;
1812
+ }
1708
1813
  resolve(ref, isStud) {
1709
1814
  const key = this.lib.resolve(ref);
1710
1815
  if (!key)
@@ -1731,54 +1836,49 @@ class Compiler {
1731
1836
  return this.cache.get(ck);
1732
1837
  const out = { body: { pos: [], col: [] }, under: { pos: [], col: [] }, edges: [], condLines: 0, cond: this.opts.cond ? [] : undefined, condUnder: this.opts.cond ? [] : undefined, studs: [], bbox: [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity], fullBottom: false };
1733
1838
  const colorOf = (code) => code === "16" ? [1, 1, 1, 0] : [...this.colors.get(code) || (code.startsWith("0x2") ? [parseInt(code.slice(3, 5), 16) / 255, parseInt(code.slice(5, 7), 16) / 255, parseInt(code.slice(7, 9), 16) / 255] : blendedRgb(code) ?? [1, 0, 1]), 1];
1734
- const walk = (fk, M, color, invert, target, depth, inh = null, scope0 = null) => {
1839
+ const walk = (fk, M, color, invert, target, depth, inh = null, scope0 = null, cull = true) => {
1735
1840
  if (depth > 40)
1736
1841
  return;
1737
- let ccw = true, certified = false, invertNext = false, scope = scope0;
1842
+ const bfc = new BfcState(cull);
1843
+ let scope = scope0;
1738
1844
  for (const raw of this.lib.read(fk).split(/\r?\n/)) {
1739
1845
  const t = raw.trim();
1740
1846
  if (!t)
1741
1847
  continue;
1742
1848
  const c = t[0];
1849
+ if (c === "0" && /^0\s+BFC(\s|$)/.test(t)) {
1850
+ bfc.meta(t);
1851
+ continue;
1852
+ }
1853
+ bfc.line(c);
1743
1854
  if (c === "0") {
1744
1855
  if (t.includes("!COLOUR") && COLOUR_RE.test(t)) {
1745
1856
  scope = defineColour(t, scope);
1746
1857
  continue;
1747
1858
  }
1748
- if (/^0\s+BFC/.test(t)) {
1749
- if (/CERTIFY/.test(t) && !/NOCERTIFY/.test(t))
1750
- certified = true;
1751
- if (/\bCW\b/.test(t))
1752
- ccw = false;
1753
- if (/\bCCW\b/.test(t))
1754
- ccw = true;
1755
- if (/INVERTNEXT/.test(t))
1756
- invertNext = true;
1757
- }
1758
1859
  continue;
1759
1860
  }
1760
1861
  if (c === "1") {
1862
+ const invertNext = bfc.takeInvert();
1761
1863
  const r = parseType1(t);
1762
1864
  if (!r)
1763
1865
  continue;
1764
1866
  const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
1765
1867
  const isTop = TOP_STUD_RE.test(base), isUnder = UNDER_STUD_RE.test(base);
1766
1868
  const ck2 = this.resolve(r.name, isTop);
1767
- if (!ck2) {
1768
- invertNext = false;
1869
+ if (!ck2)
1769
1870
  continue;
1770
- }
1771
1871
  const childColor = r.color === "16" ? color : r.color, childInh = r.color === "16" ? inh : this.local(r.color, scope);
1772
- const Mc = compose(M, r.m);
1773
- const inv = invert !== invertNext !== det3(r.m) < 0;
1774
- invertNext = false;
1775
- if (isTop && separateStuds) {
1872
+ const fx = singularFix(r.m, () => this.flatY(ck2)), rm = fx ? fx.m : r.m, subCull = bfc.cull && (!fx || fx.cull);
1873
+ const Mc = compose(M, rm);
1874
+ const inv = invert !== invertNext !== det3(rm) < 0;
1875
+ if (isTop && separateStuds && (!fx || fx.cull)) {
1776
1876
  out.studs.push({ kind: ck2, color: childInh ? this.studToken(childColor, childInh) : childColor, m: Mc });
1777
1877
  fullKind.set(out.studs.length - 1, this.lib.resolve(r.name));
1778
1878
  growBox(out.bbox, Mc, this.compile(ck2, false).bbox);
1779
1879
  continue;
1780
1880
  }
1781
- walk(ck2, Mc, childColor, inv, isUnder ? "under" : target, depth + 1, childInh, scope);
1881
+ walk(ck2, Mc, childColor, inv, isUnder ? "under" : target, depth + 1, childInh, scope, subCull);
1782
1882
  continue;
1783
1883
  }
1784
1884
  const tok = t.split(/\s+/);
@@ -1806,22 +1906,40 @@ class Compiler {
1806
1906
  pts.push(xf(M, v.slice(i * 3, i * 3 + 3)));
1807
1907
  for (const p of pts)
1808
1908
  grow(out.bbox, p);
1809
- let order = n === 3 ? [0, 1, 2] : [0, 1, 2, 0, 2, 3];
1810
- if (!ccw !== invert)
1811
- order = n === 3 ? [0, 2, 1] : [0, 2, 1, 0, 3, 2];
1812
- const rgba = this.rgbaAt(tok[1], color, inh, scope, colorOf);
1813
- const b = out[target];
1814
- for (const i of order) {
1909
+ const flip = !bfc.ccw !== invert, rgba = this.rgbaAt(tok[1], color, inh, scope, colorOf), b = out[target];
1910
+ for (const i of windOrder(n, flip, true)) {
1815
1911
  b.pos.push(...pts[i]);
1816
1912
  b.col.push(...rgba);
1817
1913
  }
1914
+ if (bfc.cull)
1915
+ culled++;
1916
+ else {
1917
+ const bb = back[target];
1918
+ for (const i of windOrder(n, !flip, true)) {
1919
+ bb.pos.push(...pts[i]);
1920
+ bb.col.push(...rgba);
1921
+ }
1922
+ }
1818
1923
  }
1819
1924
  }
1820
- if (!certified)
1925
+ if (!bfc.certified)
1821
1926
  out.uncertified = true;
1822
1927
  };
1823
1928
  const fullKind = new Map;
1929
+ let culled = 0;
1930
+ const back = { body: { pos: [], col: [] }, under: { pos: [], col: [] } };
1824
1931
  walk(key, IDENT, "16", false, "body", 0);
1932
+ if (back.body.pos.length || back.under.pos.length) {
1933
+ if (!culled)
1934
+ out.uncertified = true;
1935
+ if (!out.uncertified)
1936
+ for (const t of ["body", "under"]) {
1937
+ for (const x of back[t].pos)
1938
+ out[t].pos.push(x);
1939
+ for (const x of back[t].col)
1940
+ out[t].col.push(x);
1941
+ }
1942
+ }
1825
1943
  seatStuds(out, fullKind, (k) => this.compile(k, false).bbox);
1826
1944
  if (this.opts.tex && this.hasTex(key))
1827
1945
  this.texPass(key, out, separateStuds, colorOf);
@@ -1906,10 +2024,11 @@ class Compiler {
1906
2024
  }
1907
2025
  texPass(key, out, separateStuds, colorOf) {
1908
2026
  const tbody = { pos: [], col: [] }, texb = new Map;
1909
- const walk = (fk, M, color, invert, target, depth, texIn, inh = null, scope0 = null) => {
2027
+ const walk = (fk, M, color, invert, target, depth, texIn, inh = null, scope0 = null, cull = true) => {
1910
2028
  if (depth > 40)
1911
2029
  return;
1912
- let ccw = true, invertNext = false, scope = scope0;
2030
+ const bfc = new BfcState(cull);
2031
+ let scope = scope0;
1913
2032
  let stack = [];
1914
2033
  let next = null;
1915
2034
  for (const raw of this.lib.read(fk).split(/\r?\n/)) {
@@ -1920,7 +2039,13 @@ class Compiler {
1920
2039
  if (t[0] === "0") {
1921
2040
  const m = /^0\s+(\S+)\s*(.*)$/.exec(t);
1922
2041
  const kw = (m?.[1] ?? "").toUpperCase(), rest = m?.[2] ?? "";
2042
+ if (m?.[1] === "BFC") {
2043
+ bfc.meta(t);
2044
+ next = null;
2045
+ continue;
2046
+ }
1923
2047
  if (kw === "!TEXMAP") {
2048
+ bfc.line("0");
1924
2049
  const op = (rest.split(/\s+/)[0] || "").toUpperCase();
1925
2050
  next = null;
1926
2051
  if (op === "START" || op === "NEXT") {
@@ -1944,25 +2069,23 @@ class Compiler {
1944
2069
  next = null;
1945
2070
  t = rest.trim();
1946
2071
  colon = true;
1947
- if (!t || t[0] === "0")
2072
+ if (!t || t[0] === "0") {
2073
+ bfc.line("0");
1948
2074
  continue;
2075
+ }
1949
2076
  } else {
2077
+ bfc.line("0");
1950
2078
  if (kw === "!COLOUR")
1951
2079
  scope = defineColour(t, scope);
1952
- if (kw === "BFC") {
1953
- if (/\bCW\b/.test(t))
1954
- ccw = false;
1955
- if (/\bCCW\b/.test(t))
1956
- ccw = true;
1957
- if (/INVERTNEXT/.test(t))
1958
- invertNext = true;
1959
- }
1960
2080
  if (kw === "STEP")
1961
2081
  stack = [];
1962
2082
  next = null;
1963
2083
  continue;
1964
2084
  }
1965
2085
  }
2086
+ const c = t[0];
2087
+ bfc.line(c);
2088
+ const invertNext = c === "1" && bfc.takeInvert();
1966
2089
  let tex = texIn;
1967
2090
  if (next) {
1968
2091
  tex = next;
@@ -1977,7 +2100,6 @@ class Compiler {
1977
2100
  tex = top.map;
1978
2101
  }
1979
2102
  }
1980
- const c = t[0];
1981
2103
  if (c === "1") {
1982
2104
  const r = parseType1(t);
1983
2105
  if (!r)
@@ -1985,15 +2107,13 @@ class Compiler {
1985
2107
  const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
1986
2108
  const isTop = TOP_STUD_RE.test(base), isUnder = UNDER_STUD_RE.test(base);
1987
2109
  const ck2 = this.resolve(r.name, isTop);
1988
- if (!ck2) {
1989
- invertNext = false;
2110
+ if (!ck2)
1990
2111
  continue;
1991
- }
1992
- const inv = invert !== invertNext !== det3(r.m) < 0;
1993
- invertNext = false;
1994
- if (isTop && separateStuds)
2112
+ const fx = singularFix(r.m, () => this.flatY(ck2)), rm = fx ? fx.m : r.m, subCull = bfc.cull && (!fx || fx.cull);
2113
+ const inv = invert !== invertNext !== det3(rm) < 0;
2114
+ if (isTop && separateStuds && (!fx || fx.cull))
1995
2115
  continue;
1996
- walk(ck2, compose(M, r.m), r.color === "16" ? color : r.color, inv, isUnder ? "under" : target, depth + 1, tex, r.color === "16" ? inh : this.local(r.color, scope), scope);
2116
+ walk(ck2, compose(M, rm), r.color === "16" ? color : r.color, inv, isUnder ? "under" : target, depth + 1, tex, r.color === "16" ? inh : this.local(r.color, scope), scope, subCull);
1997
2117
  continue;
1998
2118
  }
1999
2119
  if (c !== "3" && c !== "4")
@@ -2004,9 +2124,7 @@ class Compiler {
2004
2124
  const pts = [];
2005
2125
  for (let i = 0;i < n; i++)
2006
2126
  pts.push(xf(M, v.slice(i * 3, i * 3 + 3)));
2007
- let order = n === 3 ? [0, 1, 2] : [0, 1, 2, 0, 2, 3];
2008
- if (!ccw !== invert)
2009
- order = n === 3 ? [0, 2, 1] : [0, 2, 1, 0, 3, 2];
2127
+ const order = windOrder(n, !bfc.ccw !== invert, bfc.cull || !!out.uncertified);
2010
2128
  const rgba = this.rgbaAt(tok[1], color, inh, scope, colorOf);
2011
2129
  if (tex) {
2012
2130
  const tk = tex.glossmap ? tex.texture + "\x00" + tex.glossmap : tex.texture;
@@ -2376,7 +2494,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2376
2494
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2377
2495
  }
2378
2496
 
2379
- // ../../../../../../../tmp/ldr-build-efpWDR/src/node/build-pack.ts
2497
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/build-pack.ts
2380
2498
  function packCompilerOpts(o) {
2381
2499
  return { studs: o.studs ?? "low", prims: o.primitives ?? "full", weld: o.weld ?? true, separateStuds: true, cond: !!o.cond, tex: !!o.textures };
2382
2500
  }
@@ -2449,7 +2567,7 @@ function buildPack(o) {
2449
2567
  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 } : {} };
2450
2568
  }
2451
2569
 
2452
- // ../../../../../../../tmp/ldr-build-efpWDR/src/cli.ts
2570
+ // ../../../../../../../tmp/ldr-build-RWoXTz/src/cli.ts
2453
2571
  var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
2454
2572
 
2455
2573
  Usage:
package/dist/cull.d.ts CHANGED
@@ -19,6 +19,8 @@ export declare function prepPart(H: any, part: string): PrepPart | null;
19
19
  /** w = a * b for LDraw 12-number matrices (x y z a b c d e f g h i); same arithmetic as the instance flattener. */
20
20
  export declare function mul12To(a: ArrayLike<number>, b: ArrayLike<number>, bo: number, w: Float64Array, ao?: number): void;
21
21
  export declare const det12: (m: ArrayLike<number>) => number;
22
+ /** Singular placement (determinant ~0, relative to the row lengths): BFC can't cull it (article 415 "Degenerate Matrices"). */
23
+ export declare const singular12: (m: ArrayLike<number>, d?: number) => boolean;
22
24
  /** Open-addressing hash table from int32 triples to int32 values (-1 = absent); grows as needed. */
23
25
  export declare class Int3Table {
24
26
  a: Int32Array;