ldr 0.5.1 → 0.7.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 +23 -12
- package/dist/cdn.d.ts +8 -41
- package/dist/cli.js +201 -36
- package/dist/cull.d.ts +68 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +1325 -549
- package/dist/instructions.d.ts +80 -0
- package/dist/load-worker.d.ts +1 -0
- package/dist/load-worker.gen.d.ts +2 -0
- package/dist/model.d.ts +23 -1
- package/dist/node/index.js +1414 -545
- package/dist/pack-io.d.ts +9 -0
- package/dist/parts-cdn.d.ts +66 -0
- package/dist/render.d.ts +24 -44
- package/dist/scene.d.ts +13 -3
- package/dist/spec.d.ts +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Render 100,000-brick LEGO® (LDraw) models in three.js at interactive frame rates.**
|
|
4
4
|
|
|
5
|
-
`ldr` compiles the parts a model uses into a compact binary **parts pack** ahead of time. At runtime it draws them with GPU instancing, hidden-stud and underside culling, distance LOD (simplified meshes, sub-pixel studs dropped, per-cluster frustum culling), single-sided (BFC) faces and indexed edge lines. On an Apple M4 Pro that takes Datsville (392,514 bricks) from 410 ms to
|
|
5
|
+
`ldr` compiles the parts a model uses into a compact binary **parts pack** ahead of time. At runtime it draws them with GPU instancing, hidden-stud and underside culling, distance LOD (simplified meshes, sub-pixel studs dropped, per-cluster frustum culling), single-sided (BFC) faces and indexed edge lines. On an Apple M4 Pro that takes Datsville (392,514 bricks) from 410 ms to 12 ms per frame, and its load time from 24.7 s to 1.6 s.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/ldr) · MIT · ESM + TypeScript · `three` is a peer dependency
|
|
8
8
|
- **Spec-compliant LDraw reader:** `.ldr`, `.mpd` and `.dat`, including MPD-embedded parts, geometry drawn directly in models, BFC, `STEP`/`ROTSTEP`, `!TEXMAP` and `!DATA`.
|
|
@@ -58,7 +58,7 @@ import { parseModel, loadPack, createLdrObject } from "ldr";
|
|
|
58
58
|
|
|
59
59
|
const [text, pack] = await Promise.all([
|
|
60
60
|
fetch("/car.ldr").then((r) => r.text()),
|
|
61
|
-
loadPack("/car.pack.zst"), // .zst / .gz / .
|
|
61
|
+
loadPack("/car.pack.zst"), // .zst / .gz / raw .bin (detected from the bytes)
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
64
|
const model = createLdrObject(parseModel(text, "car.ldr"), pack, {
|
|
@@ -136,11 +136,22 @@ MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part
|
|
|
136
136
|
| `lod` | `true` | Distance LOD, re-selected for each camera you render with. Batches are split into spatial clusters (off-screen ones are frustum-culled). Each cluster draws the coarsest simplified mesh (from `lod` packs) whose error stays under `lodPx`, drops studs whose 12-LDU disc is under `lodPx`, and hides edges where a stud spans less than `lodEdgePx`. |
|
|
137
137
|
| `lodPx` | `0.5` | LOD: maximum screen-space error in pixels. |
|
|
138
138
|
| `lodEdgePx` | `0.5` | LOD: draw edge and conditional lines only where one stud (20 LDU) spans at least this many pixels. `0` = always. |
|
|
139
|
+
| `lodEdgeMinPx` | `0.15` | LOD: draw only edge segments at least this many pixels long on screen. `0` = every segment. |
|
|
140
|
+
| `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. |
|
|
139
141
|
|
|
140
142
|
`LdrObject`: `object`, `stats` (`instances`, `drawn`, `missing[]`, `studsHidden/Total`, `undersHidden/Total`, `buildMs`, `condBatches`, `texBatches`, `textures`, `clusters`), `stepCount`, `stepViews`, `texturesReady`, `setStep(n | null)`, `setEdges(on)`, `setConditionalLines(on)`, `setCulling(studs, undersides)`, `setSide(side)`, `setLod(on)`, `getBox(target?)`, `dispose()`.
|
|
141
143
|
|
|
142
144
|
`setStep` doesn't rebuild anything: it compacts instance buffers, and culling is step-aware (a stud covered only by a later step's part stays visible). `stats` reflect the shown step.
|
|
143
145
|
|
|
146
|
+
**Building instructions.** `parseModel(text, name, { callouts: true })` (or `loadModel(url, { callouts: true })`) sequences a
|
|
147
|
+
submodel placed N times in one step like a booklet: the first copy is built step by step (its instances get `mult = N`, the "xN"),
|
|
148
|
+
the other copies appear finished at the end of that step. `planStepViews(instances, pack.header.parts, stepOf, stepCount)` plans a
|
|
149
|
+
camera per step (`view(step)` → `{ dir, target, radius, minVis, meanVis, below, isolate }`, LDraw space): the booklet view (30° up,
|
|
150
|
+
45° round) while it shows the new parts at least half visible on average, otherwise the best of 24 views from above (or from below for
|
|
151
|
+
parts added underneath), preferring front three-quarter views and small turns (thresholds from Agrawala et al., SIGGRAPH 2003).
|
|
152
|
+
`isolate` is set while a sub-assembly is being built: show it on its own with `setStep(step, isolateMask(instances, view.isolate))`.
|
|
153
|
+
Visibility is measured on part bounding boxes, so it needs no GPU and also runs in Node.
|
|
154
|
+
|
|
144
155
|
### `ldr/node` (Node / Bun only)
|
|
145
156
|
|
|
146
157
|
Everything above, plus:
|
|
@@ -167,20 +178,20 @@ Sources: `bench/results/latest-perf.md` (the regression gate, `bench/baselines/d
|
|
|
167
178
|
| model | bricks | baseline frame | **ldr frame** | baseline load | **ldr load** |
|
|
168
179
|
|---|---|---|---|---|---|
|
|
169
180
|
| synthetic grid | 100,021 | 24.4 ms | **2.7 ms** | 0.2 s | 0.3 s |
|
|
170
|
-
| Datsville 10k | 10,000 | 45.9 ms | **
|
|
171
|
-
| Datsville 100k | 100,000 | 105.7 ms | **
|
|
172
|
-
| Datsville (full) | 392,514 | 409.6 ms | **
|
|
181
|
+
| Datsville 10k | 10,000 | 45.9 ms | **1.9 ms** | 13.5 s | **0.36 s** |
|
|
182
|
+
| Datsville 100k | 100,000 | 105.7 ms | **4.4 ms** | 19.5 s | **0.79 s** |
|
|
183
|
+
| Datsville (full) | 392,514 | 409.6 ms | **12.5 ms** | 24.7 s | **1.6 s** |
|
|
173
184
|
|
|
174
|
-
How much each optimization contributes on Datsville 100k, in ms per frame: welding 108.1 → 64.6, stud culling → 50.8, underside culling → 41.7, low-detail studs → 31.0, single-sided faces → 30.1. Triangles per frame drop from 142.7M to 59.1M. Distance LOD (
|
|
185
|
+
How much each optimization contributes on Datsville 100k, in ms per frame: welding 108.1 → 64.6, stud culling → 50.8, underside culling → 41.7, low-detail studs → 31.0, single-sided faces → 30.1. Triangles per frame drop from 142.7M to 59.1M. Distance LOD (Rounds 19–20: simplified and bounding-box levels with a silhouette-coverage error, sub-pixel studs dropped, undersides skipped where they can't be seen) then takes it to 4.4 ms and 4.0M triangles; ≤ 1.5% of pixels differ from full detail (mostly sub-pixel stud moiré).
|
|
175
186
|
|
|
176
|
-
- **Pack size:** Datsville 100k (290 unique parts) is
|
|
187
|
+
- **Pack size:** Datsville 100k (290 unique parts) is about 26 MB raw and **1.46 MB zstd** with LOD meshes (22.4 MB / 1.08 MB zstd with `lod: false`). Decoding a zstd pack in the browser (fzstd) takes about **60 ms**.
|
|
177
188
|
- **Stock `LDrawLoader`:** loading the scene directly crashes the renderer (OOM) on the 50k, 100k and 250k grids and on Datsville 10k. The 10k grid loads in 6.6 s and draws at 61.5 ms per frame (29,671 draw calls), against 0.15 s and 2.2 ms with ldr + edges.
|
|
178
|
-
- **
|
|
189
|
+
- **Original demo app** (retired; rAF frame median at 2880×1800 on the M4 Pro):
|
|
179
190
|
- These comparisons are between demo settings. "Before" has every effect on (shadows, AO, screen-space edges, PBR/IBL); "after" is the demo's big-model defaults, which turn those effects off above 50k bricks.
|
|
180
191
|
- Full Datsville: **1.7 → 9.9 fps**.
|
|
181
192
|
- Datsville 100k: **8 → 43.5 fps**.
|
|
182
193
|
- **Conditional lines** (opt-in `cond` packs) cost **+12–14 %** frame time on Datsville 10k and 100k (M4 Pro), and add 7–14 % to the zstd pack size.
|
|
183
|
-
- **Edges:** with LOD, edges cost Datsville 100k
|
|
194
|
+
- **Edges:** with LOD, edges cost Datsville 10k 1.9 → 3.3 ms and 100k 4.4 → 5.2 ms, and full Datsville nothing measurable, because each cluster draws only edge segments at least 0.15 px long (`lodEdgeMinPx`) and none where bricks are sub-pixel (`lodEdgePx`). Far views drawn with edges get lighter (≤ 4.7% of pixels on Datsville overviews); `lodEdgeMinPx: 0, lodEdgePx: 0` keeps every edge.
|
|
184
195
|
- **Build steps:** `setStep` takes 66 ms on Datsville 100k and 29 ms on 10k (it was a full rebuild: 752 / 347 ms).
|
|
185
196
|
- **Visual fidelity:** pixel diffs show culling and single-sided faces are lossless (≤ 0.009 % of pixels differ). Low-detail studs only change stud silhouettes.
|
|
186
197
|
|
|
@@ -200,14 +211,14 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
|
|
|
200
211
|
|
|
201
212
|
- **Transparency:** transparent colours render opaque.
|
|
202
213
|
- **Opt-in pack features:** conditional lines need packs built with `cond`. Printed parts show their images only from `textures` packs (the embedded PNGs dominate the pack size); default packs draw the FALLBACK geometry.
|
|
203
|
-
- **ROTSTEP** is reported (`stepViews`, `instance.view`), not applied
|
|
214
|
+
- **ROTSTEP** is reported (`stepViews`, `instance.view`), not applied; `planStepViews` takes a `fixed(step)` override if you want to honour it.
|
|
204
215
|
- **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`.
|
|
205
216
|
- **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.
|
|
206
|
-
- **LOD and edges:**
|
|
217
|
+
- **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 (up to 4.7% of pixels 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.
|
|
207
218
|
|
|
208
219
|
## Attribution and license
|
|
209
220
|
|
|
210
221
|
- **Code:** MIT © Evan Bacon.
|
|
211
222
|
- **LDraw parts:** packs you build contain geometry from the **LDraw.org Parts Library**, which is licensed **CC BY 2.0** (newer files CC BY 4.0, per file header). If you ship packs, credit "LDraw.org Parts Library (CC BY 2.0)" in your app. See <https://www.ldraw.org/legal-info>.
|
|
212
223
|
- **Datsville:** the benchmark model is by [mjhorvath/Datsville](https://github.com/mjhorvath/Datsville) (CC BY-SA 4.0). It is used for benchmarking only and is not redistributed.
|
|
213
|
-
- **Trademarks:** LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this project. LDraw™ is a trademark of the Estate of James Jessiman.
|
|
224
|
+
- **Trademarks:** LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this project; ldr is not affiliated with the LEGO Group. LDraw™ is a trademark of the Estate of James Jessiman.
|
package/dist/cdn.d.ts
CHANGED
|
@@ -1,45 +1,11 @@
|
|
|
1
|
-
import { type Pack } from "./
|
|
2
|
-
import {
|
|
1
|
+
import { type Pack } from "./pack-io.js";
|
|
2
|
+
import type { PartInstance } from "./model.js";
|
|
3
3
|
import { type LdrObject, type LdrObjectOptions } from "./scene.js";
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
export interface
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
license?: string;
|
|
10
|
-
archives: string[];
|
|
11
|
-
bytes: number;
|
|
12
|
-
colors: Record<string, string>;
|
|
13
|
-
parts: Record<string, [number, number, number]>;
|
|
14
|
-
}
|
|
15
|
-
export interface LoadPartsOptions {
|
|
16
|
-
/** Base URL of the parts CDN (ending in /). Default `DEFAULT_PARTS_CDN`. */
|
|
17
|
-
cdn?: string;
|
|
18
|
-
/** Merge two byte ranges into one request when the gap between them is at most this many bytes. Default 64 KB. */
|
|
19
|
-
gap?: number;
|
|
20
|
-
/** Max parallel Range requests. Default 6. */
|
|
21
|
-
concurrency?: number;
|
|
22
|
-
fetch?: typeof fetch;
|
|
23
|
-
}
|
|
24
|
-
/** Fetch (and cache) a parts CDN index. */
|
|
25
|
-
export declare function loadPartsIndex(cdn?: string, f?: typeof fetch): Promise<PartsCdnIndex>;
|
|
26
|
-
/** Merge several packs into one (parts and stud kinds; the first pack's definition wins on name clashes). */
|
|
27
|
-
export declare function mergePacks(packs: Pack[], extra?: Record<string, unknown>): Pack;
|
|
28
|
-
/**
|
|
29
|
-
* Fetch the given parts from a parts CDN and return them as one Pack (with the LDConfig colour table).
|
|
30
|
-
* Parts the CDN does not have are listed in `missing`.
|
|
31
|
-
*/
|
|
32
|
-
export declare function loadParts(names: Iterable<string>, o?: LoadPartsOptions): Promise<{
|
|
33
|
-
pack: Pack;
|
|
34
|
-
missing: string[];
|
|
35
|
-
requests: number;
|
|
36
|
-
bytes: number;
|
|
37
|
-
}>;
|
|
38
|
-
export interface LoadModelOptions extends LdrObjectOptions, LoadPartsOptions {
|
|
39
|
-
/** Treat `source` as LDraw text instead of a URL. */
|
|
40
|
-
text?: boolean;
|
|
41
|
-
/** File name used for the main model (MPD resolution). Default: last URL segment. */
|
|
42
|
-
name?: string;
|
|
4
|
+
import { type PrepareOptions } from "./parts-cdn.js";
|
|
5
|
+
export { DEFAULT_PARTS_CDN, loadPartsIndex, loadParts, mergePacks, prepareModel, type PartsCdnIndex, type LoadPartsOptions, type PrepareOptions, type PreparedModel } from "./parts-cdn.js";
|
|
6
|
+
export interface LoadModelOptions extends LdrObjectOptions, PrepareOptions {
|
|
7
|
+
/** Run parsing / part inflation / pack merging in a Web Worker (default true when available; ignored with a custom `fetch`). */
|
|
8
|
+
worker?: boolean;
|
|
43
9
|
}
|
|
44
10
|
export interface LoadedModel extends LdrObject {
|
|
45
11
|
pack: Pack; /** The flattened model (part instances). */
|
|
@@ -53,6 +19,7 @@ export interface LoadedModel extends LdrObject {
|
|
|
53
19
|
};
|
|
54
20
|
requests: number;
|
|
55
21
|
bytes: number;
|
|
22
|
+
/** Where the prepare step ran. */ ranIn: "worker" | "main";
|
|
56
23
|
}
|
|
57
24
|
/**
|
|
58
25
|
* One call from an `.ldr` / `.mpd` URL (or text) to a three.js object: fetches the model, fetches just the parts it uses from the
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// ../../../../../../../tmp/ldr-build-
|
|
3
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/cli.ts
|
|
4
4
|
import fs4 from "node:fs";
|
|
5
5
|
import zlib from "node:zlib";
|
|
6
6
|
|
|
7
|
-
// ../../../../../../../tmp/ldr-build-
|
|
7
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/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-
|
|
11
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/node/resolver.ts
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
|
|
15
|
-
// ../../../../../../../tmp/ldr-build-
|
|
15
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/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))
|
|
@@ -100,6 +100,36 @@ var b64 = (s) => {
|
|
|
100
100
|
out[i] = bin.charCodeAt(i);
|
|
101
101
|
return out;
|
|
102
102
|
};
|
|
103
|
+
function type1Fast(line) {
|
|
104
|
+
const n = line.length;
|
|
105
|
+
let i = 1;
|
|
106
|
+
const ws = (c) => c === 32 || c === 9;
|
|
107
|
+
const next = () => {
|
|
108
|
+
while (i < n && ws(line.charCodeAt(i)))
|
|
109
|
+
i++;
|
|
110
|
+
const a = i;
|
|
111
|
+
while (i < n && !ws(line.charCodeAt(i)))
|
|
112
|
+
i++;
|
|
113
|
+
return a < i ? line.slice(a, i) : null;
|
|
114
|
+
};
|
|
115
|
+
const ct = next();
|
|
116
|
+
if (ct === null)
|
|
117
|
+
return null;
|
|
118
|
+
const m = new Array(12);
|
|
119
|
+
for (let k = 0;k < 12; k++) {
|
|
120
|
+
const t = next();
|
|
121
|
+
if (t === null)
|
|
122
|
+
return null;
|
|
123
|
+
m[k] = +t;
|
|
124
|
+
}
|
|
125
|
+
while (i < n && ws(line.charCodeAt(i)))
|
|
126
|
+
i++;
|
|
127
|
+
if (i >= n)
|
|
128
|
+
return null;
|
|
129
|
+
const rest = line.slice(i);
|
|
130
|
+
const file = /[ \t]{2,}|\t/.test(rest) ? rest.split(/\s+/).join(" ") : rest;
|
|
131
|
+
return { t: 1, color: parseColor(ct), m, file: norm(file) };
|
|
132
|
+
}
|
|
103
133
|
function geomLine(tk) {
|
|
104
134
|
const t = +tk[0];
|
|
105
135
|
if (!(t >= 1 && t <= 5) || tk.length < 2)
|
|
@@ -168,7 +198,8 @@ function parse(src, name = "main.ldr", popts = {}) {
|
|
|
168
198
|
} else
|
|
169
199
|
c.tex = top.map;
|
|
170
200
|
};
|
|
171
|
-
for (const raw of src.split(
|
|
201
|
+
for (const raw of src.split(`
|
|
202
|
+
`)) {
|
|
172
203
|
const line = raw.trim();
|
|
173
204
|
if (!line)
|
|
174
205
|
continue;
|
|
@@ -219,6 +250,17 @@ function parse(src, name = "main.ldr", popts = {}) {
|
|
|
219
250
|
continue;
|
|
220
251
|
}
|
|
221
252
|
}
|
|
253
|
+
if (line.charCodeAt(0) === 49 && (line.charCodeAt(1) === 32 || line.charCodeAt(1) === 9) && !dataCur && !skip) {
|
|
254
|
+
const g = type1Fast(line);
|
|
255
|
+
if (g) {
|
|
256
|
+
if (!cur)
|
|
257
|
+
start(name);
|
|
258
|
+
annotate(g, false);
|
|
259
|
+
cur.cmds.push(g);
|
|
260
|
+
inHeader = false;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
222
264
|
const tk = line.split(/\s+/);
|
|
223
265
|
const t = +tk[0];
|
|
224
266
|
if (t === 0) {
|
|
@@ -427,6 +469,7 @@ function transformTexMap(map, m) {
|
|
|
427
469
|
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]];
|
|
428
470
|
return { ...map, p1: x(map.p1), p2: x(map.p2), p3: x(map.p3) };
|
|
429
471
|
}
|
|
472
|
+
var STUB_OVERRIDES = { "32531.dat": "32531a.dat" };
|
|
430
473
|
function mul(a, b) {
|
|
431
474
|
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;
|
|
432
475
|
return [
|
|
@@ -662,12 +705,15 @@ function fileStepViews(f) {
|
|
|
662
705
|
return out;
|
|
663
706
|
}
|
|
664
707
|
|
|
665
|
-
// ../../../../../../../tmp/ldr-build-
|
|
708
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/model.ts
|
|
709
|
+
var CALLOUT_END = 999999;
|
|
710
|
+
var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
|
|
666
711
|
function flatten(doc, opts = {}) {
|
|
667
712
|
const out = [];
|
|
668
713
|
const maxDepth = opts.maxDepth ?? 64;
|
|
669
|
-
const loose = opts.looseGeometry !== false, tx = !!opts.textures;
|
|
714
|
+
const loose = opts.looseGeometry !== false, tx = !!opts.textures, co = !!opts.callouts;
|
|
670
715
|
const src = new Map;
|
|
716
|
+
let subs = 0;
|
|
671
717
|
const lazy = (f, k) => {
|
|
672
718
|
const key = f.name + "#" + k;
|
|
673
719
|
let fn = src.get(key);
|
|
@@ -678,16 +724,44 @@ function flatten(doc, opts = {}) {
|
|
|
678
724
|
}
|
|
679
725
|
return fn;
|
|
680
726
|
};
|
|
681
|
-
const walk = (d, fname, m, color, path, stepPath, depth, texIn) => {
|
|
727
|
+
const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = []) => {
|
|
682
728
|
const f = d.files.get(fname);
|
|
683
729
|
if (!f || depth > maxDepth)
|
|
684
730
|
return;
|
|
685
731
|
let s = 0;
|
|
732
|
+
const subId = depth > 0 ? ++subs : 0;
|
|
733
|
+
const sp0 = co && subId ? [...subPath, subId] : subPath;
|
|
734
|
+
const sm0 = co && subId ? [...subMats, m] : subMats;
|
|
735
|
+
const groups = co && !fixed ? new Map : null;
|
|
736
|
+
let ordStep = -1, ord = 0;
|
|
737
|
+
const sub = (sd, file, mm, col, sp, lt) => {
|
|
738
|
+
if (ordStep !== s) {
|
|
739
|
+
ordStep = s;
|
|
740
|
+
ord = 0;
|
|
741
|
+
}
|
|
742
|
+
const g = groups?.get(s + "|" + file);
|
|
743
|
+
if (g) {
|
|
744
|
+
g.n++;
|
|
745
|
+
return walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, [...sp, CALLOUT_END], depth + 1, lt, [...sp, CALLOUT_END], sp0, sm0);
|
|
746
|
+
}
|
|
747
|
+
const spo = [...sp, ord++], from = out.length;
|
|
748
|
+
walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, spo, depth + 1, lt, fixed, sp0, sm0);
|
|
749
|
+
groups?.set(s + "|" + file, { n: 1, from, to: out.length });
|
|
750
|
+
};
|
|
686
751
|
const views = fileStepViews(f);
|
|
687
752
|
const ls = loose ? looseSteps(f) : [];
|
|
688
753
|
let li = 0;
|
|
689
754
|
const emit = (part, col, mm, sp, k, embedded, tex) => {
|
|
755
|
+
if (fixed)
|
|
756
|
+
sp = fixed;
|
|
690
757
|
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
758
|
+
if (subId) {
|
|
759
|
+
i.sub = subId;
|
|
760
|
+
if (co) {
|
|
761
|
+
i.subPath = sp0;
|
|
762
|
+
i.subMats = sm0;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
691
765
|
if (embedded)
|
|
692
766
|
i.embedded = true;
|
|
693
767
|
if (tex) {
|
|
@@ -733,7 +807,7 @@ function flatten(doc, opts = {}) {
|
|
|
733
807
|
const lt = tex && local ? transformTexMap(tex, local) : null;
|
|
734
808
|
if (d.files.has(c.file)) {
|
|
735
809
|
if (fileKind(d, c.file) === "model") {
|
|
736
|
-
|
|
810
|
+
sub(d, c.file, mm, col, sp, lt);
|
|
737
811
|
continue;
|
|
738
812
|
}
|
|
739
813
|
emit(c.file, col, mm, sp, s, true, lt);
|
|
@@ -741,12 +815,18 @@ function flatten(doc, opts = {}) {
|
|
|
741
815
|
}
|
|
742
816
|
const ext = opts.resolveModel?.(c.file);
|
|
743
817
|
if (ext) {
|
|
744
|
-
|
|
818
|
+
sub(ext, c.file, mm, col, sp, lt);
|
|
745
819
|
continue;
|
|
746
820
|
}
|
|
747
821
|
emit(c.file, col, mm, sp, s, false, lt);
|
|
748
822
|
}
|
|
749
823
|
flushLoose(Infinity);
|
|
824
|
+
if (groups) {
|
|
825
|
+
for (const g of groups.values())
|
|
826
|
+
if (g.n > 1)
|
|
827
|
+
for (let j = g.from;j < g.to; j++)
|
|
828
|
+
out[j].mult ??= g.n;
|
|
829
|
+
}
|
|
750
830
|
};
|
|
751
831
|
if (fileKind(doc, doc.main) === "part") {
|
|
752
832
|
const f = doc.files.get(doc.main);
|
|
@@ -758,10 +838,10 @@ function flatten(doc, opts = {}) {
|
|
|
758
838
|
return out;
|
|
759
839
|
}
|
|
760
840
|
function parseModel(text, name = "main.ldr", opts) {
|
|
761
|
-
return flatten(parse(text, name), opts);
|
|
841
|
+
return flatten(parse(text, name, { geometryText: true }), opts);
|
|
762
842
|
}
|
|
763
843
|
function sequenceSteps(insts) {
|
|
764
|
-
const key = (i) => i.steps
|
|
844
|
+
const key = (i) => stepKey(i.steps);
|
|
765
845
|
const uniq = [...new Set(insts.map(key))].sort();
|
|
766
846
|
const rank = new Map(uniq.map((k, j) => [k, j]));
|
|
767
847
|
const stepOf = new Int32Array(insts.length);
|
|
@@ -784,7 +864,7 @@ function parseLDConfig(text) {
|
|
|
784
864
|
return out;
|
|
785
865
|
}
|
|
786
866
|
|
|
787
|
-
// ../../../../../../../tmp/ldr-build-
|
|
867
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/node/resolver.ts
|
|
788
868
|
var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
|
|
789
869
|
var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
|
|
790
870
|
var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
@@ -854,7 +934,7 @@ class Library {
|
|
|
854
934
|
return key;
|
|
855
935
|
}
|
|
856
936
|
resolve(ref) {
|
|
857
|
-
const
|
|
937
|
+
const n0 = norm2(ref), n = STUB_OVERRIDES[n0] ?? n0;
|
|
858
938
|
if (this.virtual.size && this.virtual.has(n))
|
|
859
939
|
return "mpd/" + n;
|
|
860
940
|
for (const pre of ["parts/", "p/", "models/"])
|
|
@@ -1054,11 +1134,11 @@ function collectDeps(lib, refs) {
|
|
|
1054
1134
|
return out;
|
|
1055
1135
|
}
|
|
1056
1136
|
|
|
1057
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1137
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/node/compile.ts
|
|
1058
1138
|
import fs2 from "node:fs";
|
|
1059
1139
|
import path2 from "node:path";
|
|
1060
1140
|
|
|
1061
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1141
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/node/lod.ts
|
|
1062
1142
|
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
|
1063
1143
|
await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
|
|
1064
1144
|
function reorderMesh(P, N, C, I) {
|
|
@@ -1081,8 +1161,10 @@ function reorderMesh(P, N, C, I) {
|
|
|
1081
1161
|
}
|
|
1082
1162
|
return { P: P2, N: N2, C: C2, I: idx };
|
|
1083
1163
|
}
|
|
1084
|
-
var TARGETS = [0.25, 0.08];
|
|
1164
|
+
var TARGETS = [0.25, 0.08, 0.025];
|
|
1085
1165
|
var MIN_TRIS = 64;
|
|
1166
|
+
var MIN_BOX_TRIS = 24;
|
|
1167
|
+
var COVERAGE_WEIGHT = Number(process.env.LDR_LOD_COVERAGE ?? 0.5);
|
|
1086
1168
|
function flatMesh(tris) {
|
|
1087
1169
|
const map = new Map;
|
|
1088
1170
|
const P = [], N = [], C = [], I = [];
|
|
@@ -1112,9 +1194,62 @@ function flatMesh(tris) {
|
|
|
1112
1194
|
}
|
|
1113
1195
|
return { P: new Float32Array(P), N: new Float32Array(N), C: new Float32Array(C), I: new Uint32Array(I) };
|
|
1114
1196
|
}
|
|
1197
|
+
var G = 64;
|
|
1198
|
+
function silhouettes(P, I, box) {
|
|
1199
|
+
const masks = [0, 1, 2].map(() => new Uint8Array(G * G));
|
|
1200
|
+
for (let ax = 0;ax < 3; ax++) {
|
|
1201
|
+
const u = (ax + 1) % 3, v = (ax + 2) % 3;
|
|
1202
|
+
const m = masks[ax];
|
|
1203
|
+
const su = (box.hi[u] - box.lo[u]) / G || 1, sv = (box.hi[v] - box.lo[v]) / G || 1;
|
|
1204
|
+
for (let t = 0;t < I.length; t += 3) {
|
|
1205
|
+
const p = [I[t], I[t + 1], I[t + 2]].map((k) => [(P[k * 3 + u] - box.lo[u]) / su, (P[k * 3 + v] - box.lo[v]) / sv]);
|
|
1206
|
+
const area = (p[1][0] - p[0][0]) * (p[2][1] - p[0][1]) - (p[2][0] - p[0][0]) * (p[1][1] - p[0][1]);
|
|
1207
|
+
if (Math.abs(area) < 0.000000000001)
|
|
1208
|
+
continue;
|
|
1209
|
+
const x0 = Math.max(0, Math.floor(Math.min(p[0][0], p[1][0], p[2][0]))), x1 = Math.min(G - 1, Math.ceil(Math.max(p[0][0], p[1][0], p[2][0])));
|
|
1210
|
+
const y0 = Math.max(0, Math.floor(Math.min(p[0][1], p[1][1], p[2][1]))), y1 = Math.min(G - 1, Math.ceil(Math.max(p[0][1], p[1][1], p[2][1])));
|
|
1211
|
+
for (let y = y0;y <= y1; y++)
|
|
1212
|
+
for (let x = x0;x <= x1; x++) {
|
|
1213
|
+
if (m[y * G + x])
|
|
1214
|
+
continue;
|
|
1215
|
+
const cx = x + 0.5, cy = y + 0.5;
|
|
1216
|
+
let inside = true;
|
|
1217
|
+
for (let e = 0;e < 3 && inside; e++) {
|
|
1218
|
+
const a0 = p[e], a1 = p[(e + 1) % 3];
|
|
1219
|
+
const w = (a1[0] - a0[0]) * (cy - a0[1]) - (cx - a0[0]) * (a1[1] - a0[1]);
|
|
1220
|
+
if (w * area < 0)
|
|
1221
|
+
inside = false;
|
|
1222
|
+
}
|
|
1223
|
+
if (inside)
|
|
1224
|
+
m[y * G + x] = 1;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return masks;
|
|
1229
|
+
}
|
|
1230
|
+
function silhouetteDiff(a, b) {
|
|
1231
|
+
let worst = 0;
|
|
1232
|
+
for (let ax = 0;ax < 3; ax++) {
|
|
1233
|
+
let d = 0;
|
|
1234
|
+
for (let i = 0;i < G * G; i++)
|
|
1235
|
+
d += a[ax][i] ^ b[ax][i];
|
|
1236
|
+
worst = Math.max(worst, d / (G * G));
|
|
1237
|
+
}
|
|
1238
|
+
return worst;
|
|
1239
|
+
}
|
|
1240
|
+
function boxMesh(box, col) {
|
|
1241
|
+
const [x0, y0, z0] = box.lo, [x1, y1, z1] = box.hi;
|
|
1242
|
+
const c = [[x0, y0, z0], [x1, y0, z0], [x1, y1, z0], [x0, y1, z0], [x0, y0, z1], [x1, y0, z1], [x1, y1, z1], [x0, y1, z1]];
|
|
1243
|
+
const quads = [[0, 3, 2, 1], [4, 5, 6, 7], [0, 1, 5, 4], [3, 7, 6, 2], [0, 4, 7, 3], [1, 2, 6, 5]];
|
|
1244
|
+
const p = new Float32Array(c.flat());
|
|
1245
|
+
const idx = [];
|
|
1246
|
+
for (const q of quads)
|
|
1247
|
+
idx.push(q[0], q[1], q[2], q[0], q[2], q[3]);
|
|
1248
|
+
return flatMesh([{ p, i: Uint32Array.from(idx), col }]);
|
|
1249
|
+
}
|
|
1115
1250
|
function lodChain(P, C, I) {
|
|
1116
1251
|
const nTris = I.length / 3;
|
|
1117
|
-
if (nTris <
|
|
1252
|
+
if (nTris < MIN_BOX_TRIS)
|
|
1118
1253
|
return [];
|
|
1119
1254
|
const groups = new Map;
|
|
1120
1255
|
for (let t = 0;t < I.length; t += 3) {
|
|
@@ -1145,27 +1280,57 @@ function lodChain(P, C, I) {
|
|
|
1145
1280
|
});
|
|
1146
1281
|
const out = [];
|
|
1147
1282
|
let prevTris = nTris;
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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 });
|
|
1283
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
1284
|
+
for (let v = 0;v < P.length; v += 3)
|
|
1285
|
+
for (let k = 0;k < 3; k++) {
|
|
1286
|
+
lo[k] = Math.min(lo[k], P[v + k]);
|
|
1287
|
+
hi[k] = Math.max(hi[k], P[v + k]);
|
|
1157
1288
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1289
|
+
const box = { lo, hi };
|
|
1290
|
+
const diag = Math.hypot(hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]);
|
|
1291
|
+
const S0 = silhouettes(P, I, box);
|
|
1292
|
+
const coverageErr = (m) => COVERAGE_WEIGHT * silhouetteDiff(S0, silhouettes(m.P, m.I, box)) * diag;
|
|
1293
|
+
for (let li = 0;li < (nTris >= MIN_TRIS ? TARGETS.length : 0); li++) {
|
|
1294
|
+
let best = null;
|
|
1295
|
+
for (const sloppy of [false, true]) {
|
|
1296
|
+
let err = 0;
|
|
1297
|
+
const parts = [];
|
|
1298
|
+
for (const g of welded) {
|
|
1299
|
+
const target = Math.max(3, Math.floor(g.i.length * TARGETS[li] / 3) * 3);
|
|
1300
|
+
const [idx, e] = sloppy ? MeshoptSimplifier.simplifySloppy(g.i, g.p, 3, null, target, 1e9) : MeshoptSimplifier.simplify(g.i, g.p, 3, target, 1e9, li ? ["ErrorAbsolute", "Prune"] : ["ErrorAbsolute"]);
|
|
1301
|
+
err = Math.max(err, sloppy ? e * MeshoptSimplifier.getScale(g.p, 3) : e);
|
|
1302
|
+
if (idx.length)
|
|
1303
|
+
parts.push({ p: g.p, i: idx, col: g.col });
|
|
1304
|
+
}
|
|
1305
|
+
const m = flatMesh(parts);
|
|
1306
|
+
const t = m.I.length / 3;
|
|
1307
|
+
if (!t || t > prevTris * 0.7)
|
|
1308
|
+
continue;
|
|
1309
|
+
const e = +Math.max(err, coverageErr(m), 0.001).toFixed(3);
|
|
1310
|
+
if (!best || e < best.err)
|
|
1311
|
+
best = { ...m, err: e };
|
|
1312
|
+
}
|
|
1313
|
+
if (best) {
|
|
1314
|
+
out.push(best);
|
|
1315
|
+
prevTris = best.I.length / 3;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
if (prevTris * 0.7 >= 12) {
|
|
1319
|
+
const main = welded.reduce((a, g) => g.i.length > a.i.length ? g : a);
|
|
1320
|
+
const m = boxMesh(box, main.col);
|
|
1321
|
+
let inner = 0;
|
|
1322
|
+
for (let v = 0;v < P.length; v += 3) {
|
|
1323
|
+
let d = Infinity;
|
|
1324
|
+
for (let k = 0;k < 3; k++)
|
|
1325
|
+
d = Math.min(d, P[v + k] - lo[k], hi[k] - P[v + k]);
|
|
1326
|
+
inner = Math.max(inner, d);
|
|
1327
|
+
}
|
|
1328
|
+
out.push({ ...m, err: +Math.max(inner, coverageErr(m) / COVERAGE_WEIGHT, out.length ? out[out.length - 1].err : 0, 0.001).toFixed(3) });
|
|
1164
1329
|
}
|
|
1165
1330
|
return out;
|
|
1166
1331
|
}
|
|
1167
1332
|
|
|
1168
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1333
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/node/compile.ts
|
|
1169
1334
|
function loadColors(root) {
|
|
1170
1335
|
const map = new Map;
|
|
1171
1336
|
for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
|
|
@@ -1748,7 +1913,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
1748
1913
|
return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
|
|
1749
1914
|
}
|
|
1750
1915
|
|
|
1751
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1916
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/node/build-pack.ts
|
|
1752
1917
|
function buildPack(o) {
|
|
1753
1918
|
const lib = new Library(o.ldrawRoot);
|
|
1754
1919
|
const refs = new Map;
|
|
@@ -1811,7 +1976,7 @@ function buildPack(o) {
|
|
|
1811
1976
|
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 } : {} };
|
|
1812
1977
|
}
|
|
1813
1978
|
|
|
1814
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1979
|
+
// ../../../../../../../tmp/ldr-build-zzND7A/src/cli.ts
|
|
1815
1980
|
var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
|
|
1816
1981
|
|
|
1817
1982
|
Usage:
|
package/dist/cull.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { PartInstance } from './model.js';
|
|
2
|
+
export type Inst = {
|
|
3
|
+
part: string;
|
|
4
|
+
color: string;
|
|
5
|
+
m: number[];
|
|
6
|
+
};
|
|
7
|
+
export declare const upright: (m: ArrayLike<number>, o?: number) => boolean;
|
|
8
|
+
/** Per-part data used by culling and stud placement, prepared once per pack header. */
|
|
9
|
+
export type PrepPart = {
|
|
10
|
+
nStuds: number;
|
|
11
|
+
studM: Float64Array;
|
|
12
|
+
kind: string[];
|
|
13
|
+
color: string[];
|
|
14
|
+
mask: Uint8Array | null;
|
|
15
|
+
nx: number;
|
|
16
|
+
nz: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function prepPart(H: any, part: string): PrepPart | null;
|
|
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
|
+
export declare function mul12To(a: ArrayLike<number>, b: ArrayLike<number>, bo: number, w: Float64Array, ao?: number): void;
|
|
21
|
+
export declare const det12: (m: ArrayLike<number>) => number;
|
|
22
|
+
/** Open-addressing hash table from int32 triples to int32 values (-1 = absent); grows as needed. */
|
|
23
|
+
export declare class Int3Table {
|
|
24
|
+
a: Int32Array;
|
|
25
|
+
b: Int32Array;
|
|
26
|
+
c: Int32Array;
|
|
27
|
+
vals: Int32Array;
|
|
28
|
+
used: Uint8Array;
|
|
29
|
+
mask: number;
|
|
30
|
+
size: number;
|
|
31
|
+
constructor(cap?: number);
|
|
32
|
+
private slot;
|
|
33
|
+
get(x: number, y: number, z: number): number;
|
|
34
|
+
/** Sets (x, y, z) -> v and returns the previous value (-1 if none). */
|
|
35
|
+
put(x: number, y: number, z: number, v: number): number;
|
|
36
|
+
private grow;
|
|
37
|
+
}
|
|
38
|
+
export declare const inRange: (x: number, y: number, z: number) => boolean;
|
|
39
|
+
/** Culling result in flat arrays. Studs are enumerated in instance order, then each part's `studs` order (the order `forEachStud` visits). */
|
|
40
|
+
export type CullResult = {
|
|
41
|
+
nStuds: number;
|
|
42
|
+
studHidden: Uint8Array;
|
|
43
|
+
studUp: Uint8Array;
|
|
44
|
+
underHidden: Uint8Array;
|
|
45
|
+
studsHidden: number;
|
|
46
|
+
/** With `steps`: the first build step at which each stud / underside is hidden (STEP_NEVER = never), for step-aware culling. */
|
|
47
|
+
studHideStep?: Int32Array;
|
|
48
|
+
underHideStep?: Int32Array;
|
|
49
|
+
};
|
|
50
|
+
/** `studHideStep` / `underHideStep` value for "never hidden". */
|
|
51
|
+
export declare const STEP_NEVER = 2147483647;
|
|
52
|
+
/** Visit every stud (instance order, then part stud order) with its world matrix `w` (scratch; copy to keep). */
|
|
53
|
+
export declare function forEachStud(insts: Inst[], H: any, fn: (j: number, inst: number, w: Float64Array, kind: string, color: string, stud: number) => void): void;
|
|
54
|
+
/** `steps` (optional, per instance): also compute when each stud / underside becomes hidden as the model is built step by step. */
|
|
55
|
+
export declare function cullCore(insts: Inst[], H: any, steps?: ArrayLike<number>): CullResult;
|
|
56
|
+
/** Scene instances and per-instance step ranks as `createLdrObject` builds them (shared with the load worker). */
|
|
57
|
+
export declare function sceneInstances(instances: PartInstance[], H: any, useTex: boolean): {
|
|
58
|
+
all: {
|
|
59
|
+
part: string;
|
|
60
|
+
color: string;
|
|
61
|
+
m: import("./spec.js").M;
|
|
62
|
+
steps: number[];
|
|
63
|
+
}[];
|
|
64
|
+
stepOf: number[];
|
|
65
|
+
nSteps: number;
|
|
66
|
+
};
|
|
67
|
+
/** Culling for `createLdrObject(instances, pack, { textures })`, computable off the main thread and passed back as `precomputedCull`. */
|
|
68
|
+
export declare function precomputeCull(instances: PartInstance[], H: any, useTex?: boolean): CullResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export { parse, parseColor, isDirectColor, mul, I as IDENTITY, norm, smoothNormals, parseTexMap, texmapUV, texmapTriangleUV, transformTexMap, resolveTexture, fileKind, looseSteps, looseSource, looseFile, loosePartName, texWrapper, invert, hash32, colorToken, rotStepMatrix, stepViewMatrix, fileStepViews } from "./spec.js";
|
|
2
2
|
export type { M as Matrix12, Doc, LFile, Cmd, RotStep, BufExchg, TexMap, TexMapCmd, TexMapMethod, TexAnno, DataBlock, PartMesh, FileKind, StepView } from "./spec.js";
|
|
3
|
-
export { flatten, parseModel, sequenceSteps, parseLDConfig } from "./model.js";
|
|
3
|
+
export { flatten, parseModel, sequenceSteps, stepKey, parseLDConfig, CALLOUT_END } from "./model.js";
|
|
4
|
+
export { planStepViews, isolateMask, viewDir, instanceBoxes, MIN_VIS, REORIENT } from "./instructions.js";
|
|
5
|
+
export type { StepCameraView, StepCameraOptions, Vec3 } from "./instructions.js";
|
|
4
6
|
export type { PartInstance, FlattenOptions, LDrawColor } from "./model.js";
|
|
5
|
-
export { loadPack, decodePack, buildLdrScene, makeMaterial, createInstancedEdges, computeCulling, cullCore, forEachStud, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible } from "./render.js";
|
|
7
|
+
export { loadPack, decodePack, buildLdrScene, makeMaterial, createInstancedEdges, computeCulling, cullCore, forEachStud, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible, warmPackGeometry } from "./render.js";
|
|
8
|
+
export { precomputeCull, sceneInstances } from "./cull.js";
|
|
6
9
|
export type { Pack, LdrParams, Inst, CullResult } from "./render.js";
|
|
7
10
|
export { createLdrObject } from "./scene.js";
|
|
8
11
|
export type { LdrObject, LdrObjectOptions, LdrStats, ColorSource } from "./scene.js";
|
|
9
12
|
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";
|
|
13
|
+
export { loadModel, loadParts, loadPartsIndex, mergePacks, prepareModel, DEFAULT_PARTS_CDN } from "./cdn.js";
|
|
14
|
+
export type { LoadModelOptions, LoadedModel, LoadPartsOptions, PartsCdnIndex, PrepareOptions, PreparedModel } from "./cdn.js";
|