ldr 0.33.10 → 0.33.12

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
@@ -143,6 +143,7 @@ MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part
143
143
  | `lodPx` | `0.5` | LOD: maximum screen-space error in pixels. |
144
144
  | `lodEdgePx` | `0.5` | LOD: draw edge and conditional lines only where one stud (20 LDU) spans at least this many pixels. `0` = always. |
145
145
  | `lodEdgeMinPx` | `0.4` | LOD: draw only edge segments at least this many pixels long on screen. `0` = every segment. |
146
+ | `lodEdgeCap` | `0.75` | LOD: a part draws at most `lodEdgeCap` × d² of its edge segments (its longest), d = its size on screen in pixels. `0` = no cap. |
146
147
  | `lodUnder` | `true` | LOD: skip undersides that can't be seen (camera behind every bottom face of a cluster's parts, or the body drawn as its closed bounding box). Lossless in the fidelity checks. |
147
148
 
148
149
  `LdrObject`: `object`, `stats` (`instances`, `drawn`, `missing[]`, `studsHidden/Total`, `undersHidden/Total`, `buildMs`, `condBatches`, `texBatches`, `textures`, `glossmaps`, `clusters`), `stepCount`, `stepViews`, `texturesReady`, `setStep(n | null)`, `setEdges(on)`, `setConditionalLines(on)`, `setCulling(studs, undersides)`, `setSide(side)`, `setLod(on)`, `setGhost(on, { opacity, color, whiten }?)`, `getBox(target?)`, `dispose()`.
@@ -201,7 +202,7 @@ How much each optimization contributed on Datsville 100k (Rounds 1–20, before
201
202
  - Full Datsville: **1.7 → 9.9 fps**.
202
203
  - Datsville 100k: **8 → 43.5 fps**.
203
204
  - **Conditional lines** (opt-in `cond` packs) cost **+12–14 %** frame time on Datsville 10k and 100k (M4 Pro, before Round 60), and add 7–14 % to the zstd pack size.
204
- - **Edges:** with LOD, edges take the Falcon fleet from 9.8 → 12.9 ms (19 ships) and 18.3 → 22.9 ms (75 ships) on the Mac Mini M6 (Round 67); before Round 62 they cost 12.8 → 33.3 and 19.3 → 45.1 ms there. A simplified level draws its own outline: square studs a square per stud loop (8 segments instead of 16-32), bodies at their bounding-box level the box's 12 edges, and a line batch whose edge LOD leaves no segment issues no draw. Each cluster draws only edge segments at least 0.4 px long on screen (`lodEdgeMinPx`; 0.15 px until Round 62, when 85% of the segments drawn at the fleet overview were 0.15–0.5 px long and each darkened a whole pixel, so far ships looked charcoal) and none where bricks are sub-pixel (`lodEdgePx`). Far views drawn with edges get lighter: 2.6% of pixels differ from every-edge rendering on the 19-ship overview, 0.8% on the SF city, up to 4.7% on Datsville overviews. `lodEdgeMinPx: 0, lodEdgePx: 0` keeps every edge.
205
+ - **Edges:** with LOD, edges take the Falcon fleet from 9.8 → 12.9 ms (19 ships) and 18.3 → 22.9 ms (75 ships) on the Mac Mini M6 (Round 67); before Round 62 they cost 12.8 → 33.3 and 19.3 → 45.1 ms there. A simplified level draws its own outline: square studs a square per stud loop (8 segments instead of 16-32), bodies at their bounding-box level the box's 12 edges, and a line batch whose edge LOD leaves no segment issues no draw. Each cluster draws only edge segments at least 0.4 px long on screen (`lodEdgeMinPx`; 0.15 px until Round 62, when 85% of the segments drawn at the fleet overview were 0.15–0.5 px long and each darkened a whole pixel, so far ships looked charcoal) and none where bricks are sub-pixel (`lodEdgePx`). A part also draws at most 0.75 × d² of its segments, its longest, d = its size in pixels (`lodEdgeCap`, Round 72), so a 6 px technic beam no longer draws ~100 segments into ~20 pixels. Far views drawn with edges get lighter: 2.6% of pixels differ from every-edge rendering on the 19-ship overview, 0.8% on the SF city, up to 4.7% on Datsville overviews. `lodEdgeMinPx: 0, lodEdgePx: 0, lodEdgeCap: 0` keeps every edge.
205
206
  - **Build steps:** `setStep` takes 39 ms on the 19-ship fleet (26,619 steps); on Datsville 100k it went from a full rebuild (752 ms) to 66 ms.
206
207
  - **Visual fidelity:** pixel diffs show culling and single-sided faces are lossless (≤ 0.009 % of pixels differ). Low-detail studs only change stud silhouettes.
207
208
 
@@ -228,7 +229,7 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
228
229
  - **ROTSTEP** is reported (`stepViews`, `instance.view`), not applied; `planStepViews` takes a `fixed(step)` override if you want to honour it.
229
230
  - **Packs are static:** they hold only the parts they were built for. A model that uses parts missing from the pack lists them in `stats.missing`.
230
231
  - **Occlusion culling:** beyond hidden studs and undersides there is no occlusion culling (bricks enclosed by other bricks are still drawn). Frustum culling and LOD apply per spatial cluster.
231
- - **LOD and edges:** edge LOD drops sub-pixel edge segments and hides edges where bricks are a fraction of a pixel, which visibly lightens far-away views drawn with edges (2.6% of pixels on the 19-ship Falcon overview, up to 4.7% on Datsville overviews). Set `lodEdgeMinPx: 0, lodEdgePx: 0` to keep every edge (much slower on large models), or `lod: false` to turn LOD off entirely.
232
+ - **LOD and edges:** edge LOD drops sub-pixel edge segments and hides edges where bricks are a fraction of a pixel, which visibly lightens far-away views drawn with edges (2.6% of pixels on the 19-ship Falcon overview, up to 4.7% on Datsville overviews). Set `lodEdgeMinPx: 0, lodEdgePx: 0, lodEdgeCap: 0` to keep every edge (much slower on large models), or `lod: false` to turn LOD off entirely.
232
233
 
233
234
  ## Attribution and license
234
235
 
package/dist/cli.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/cli.ts
3
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/cli.ts
4
4
  import fs4 from "node:fs";
5
5
  import zlib from "node:zlib";
6
6
 
7
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/node/build-pack.ts
7
+ // ../../../../../../../tmp/ldr-build-HYKc29/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-Fw2uhS/src/node/resolver.ts
11
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/node/resolver.ts
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
 
15
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/spec.ts
15
+ // ../../../../../../../tmp/ldr-build-HYKc29/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;
@@ -817,7 +817,7 @@ function fileStepViews(f) {
817
817
  return out;
818
818
  }
819
819
 
820
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/model.ts
820
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/model.ts
821
821
  class ModelLimitError extends Error {
822
822
  limit;
823
823
  code = "ldr-model-limit";
@@ -1259,7 +1259,7 @@ function blendedColourHex(code) {
1259
1259
  return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
1260
1260
  }
1261
1261
 
1262
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/node/resolver.ts
1262
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/node/resolver.ts
1263
1263
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
1264
1264
  var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
1265
1265
  var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
@@ -1586,11 +1586,11 @@ function collectDeps(lib, refs) {
1586
1586
  return out;
1587
1587
  }
1588
1588
 
1589
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/node/compile.ts
1589
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/node/compile.ts
1590
1590
  import fs2 from "node:fs";
1591
1591
  import path2 from "node:path";
1592
1592
 
1593
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/node/lod.ts
1593
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/node/lod.ts
1594
1594
  import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
1595
1595
  await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
1596
1596
  function reorderMesh(P, N, C, I) {
@@ -1896,7 +1896,7 @@ function levelLines(P, I, E, err, always, tol = 0.01) {
1896
1896
  return out;
1897
1897
  }
1898
1898
 
1899
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/node/compile.ts
1899
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/node/compile.ts
1900
1900
  class BfcState {
1901
1901
  accum;
1902
1902
  ccw = true;
@@ -2840,7 +2840,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2840
2840
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2841
2841
  }
2842
2842
 
2843
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/node/build-pack.ts
2843
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/node/build-pack.ts
2844
2844
  function packCompilerOpts(o) {
2845
2845
  return { studs: o.studs ?? "low", prims: o.primitives ?? "full", weld: o.weld ?? true, separateStuds: true, cond: !!o.cond, tex: !!o.textures, ...o.partWorkLimit ? { workLimit: o.partWorkLimit } : {} };
2846
2846
  }
@@ -2913,7 +2913,7 @@ function buildPack(o) {
2913
2913
  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 } : {} };
2914
2914
  }
2915
2915
 
2916
- // ../../../../../../../tmp/ldr-build-Fw2uhS/src/cli.ts
2916
+ // ../../../../../../../tmp/ldr-build-HYKc29/src/cli.ts
2917
2917
  var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
2918
2918
 
2919
2919
  Usage:
package/dist/cull.d.ts CHANGED
@@ -21,17 +21,21 @@ export declare function mul12To(a: ArrayLike<number>, b: ArrayLike<number>, bo:
21
21
  export declare const det12: (m: ArrayLike<number>) => number;
22
22
  /** Singular placement (determinant ~0, relative to the row lengths): BFC can't cull it (article 415 "Degenerate Matrices"). */
23
23
  export declare const singular12: (m: ArrayLike<number>, d?: number) => boolean;
24
- /** Open-addressing hash table from int32 triples to int32 values (-1 = absent); grows as needed. */
24
+ /** Open-addressing hash table from int32 triples to int32 values (-1 = absent); grows as needed. Keys and value sit interleaved in one
25
+ * Int32Array (x, y, z, value), and a byte per slot holds 8 more bits of the key's hash (0 = empty), so a miss usually reads only the
26
+ * byte array and a hit one cache line of the keys, where five parallel arrays cost five. With `shift` >= 0, keys close in x / z (a
27
+ * 4 x 4 block of (1 << shift)-unit squares) start in neighbouring slots, so the studs of one brick, or the cells under it, share cache
28
+ * lines. The stud-top table of a 400k-brick city has millions of entries and its lookups were the culling pass's biggest cost
29
+ * (Round 73). */
25
30
  export declare class Int3Table {
26
- a: Int32Array;
27
- b: Int32Array;
28
- c: Int32Array;
29
- vals: Int32Array;
30
- used: Uint8Array;
31
+ t: Int32Array;
32
+ tag: Uint8Array;
31
33
  mask: number;
32
34
  size: number;
33
- constructor(cap?: number);
35
+ shift: number;
36
+ constructor(cap?: number, shift?: number);
34
37
  private slot;
38
+ private static tagOf;
35
39
  get(x: number, y: number, z: number): number;
36
40
  /** Sets (x, y, z) -> v and returns the previous value (-1 if none). */
37
41
  put(x: number, y: number, z: number, v: number): number;