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/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# ldr
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Render 100,000-brick LEGO® (LDraw) models in three.js at interactive frame rates.**
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
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 27 ms per frame, and its load time from 24.7 s to 1.6 s.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/ldr) · MIT · ESM + TypeScript · `three` is a peer dependency
|
|
8
|
+
- **Spec-compliant LDraw reader:** `.ldr`, `.mpd` and `.dat`, including MPD-embedded parts, geometry drawn directly in models, BFC, `STEP`/`ROTSTEP`, `!TEXMAP` and `!DATA`.
|
|
9
|
+
- **`createLdrObject()`** returns a `THREE.Group` you can drop into any scene, plus a build-steps API.
|
|
10
|
+
- **`ldr pack` CLI and `buildPack()`** build parts packs from your LDraw library, compressed with zstd or gzip for serving.
|
|
11
|
+
- **Tree-shakeable** (`sideEffects: false`). The browser entry is `ldr`; the filesystem tools are in `ldr/node`.
|
|
9
12
|
|
|
10
13
|
## Install
|
|
11
14
|
|
|
@@ -13,15 +16,41 @@ High-performance LEGO® (LDraw) rendering for [three.js](https://threejs.org). R
|
|
|
13
16
|
bun add ldr three # or: npm i ldr three
|
|
14
17
|
```
|
|
15
18
|
|
|
19
|
+
You also need the LDraw parts library, which is not bundled, to *build* packs. Download `complete.zip` from <https://library.ldraw.org> and unzip it to an `ldraw/` folder containing `LDConfig.ldr`, `parts/` and `p/`. Apps only need the pack at runtime, not the library.
|
|
20
|
+
|
|
16
21
|
## Quickstart
|
|
17
22
|
|
|
18
|
-
1.
|
|
23
|
+
### 1. Build a parts pack (CLI, once at build time)
|
|
19
24
|
|
|
20
25
|
```bash
|
|
21
26
|
bunx ldr pack --ldraw ./ldraw -o public/car.pack.zst models/car.ldr
|
|
22
27
|
```
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
```
|
|
30
|
+
ldr pack --ldraw <dir> -o <out.bin|.zst|.gz> [model.ldr ...] [--part 3001.dat ...] [--all-parts]
|
|
31
|
+
[--studs low|full] [--prims full|low] [--no-weld] [--no-colors]
|
|
32
|
+
[--cond] [--textures] [--no-loose-geometry] [--no-lod] [--reorder]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The pack holds only the parts your models reference, plus the LDConfig colour table. A `.zst` or `.gz` extension compresses the output.
|
|
36
|
+
|
|
37
|
+
### 2. …or build it from code (Node / Bun)
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { buildPack } from "ldr/node";
|
|
41
|
+
|
|
42
|
+
const { data, parts, missing } = buildPack({
|
|
43
|
+
ldrawRoot: "./ldraw",
|
|
44
|
+
models: ["models/car.ldr"], // paths, or { name, text }
|
|
45
|
+
studs: "low", // default
|
|
46
|
+
cond: false, // conditional lines (opt-in)
|
|
47
|
+
textures: false, // printed/sticker parts (opt-in)
|
|
48
|
+
});
|
|
49
|
+
await Bun.write("public/car.pack.bin", data); // raw; compress with zstd/gzip for serving
|
|
50
|
+
console.log(`${parts} parts`, missing.length ? `missing: ${missing}` : "");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Render it (browser)
|
|
25
54
|
|
|
26
55
|
```ts
|
|
27
56
|
import * as THREE from "three";
|
|
@@ -29,97 +58,156 @@ import { parseModel, loadPack, createLdrObject } from "ldr";
|
|
|
29
58
|
|
|
30
59
|
const [text, pack] = await Promise.all([
|
|
31
60
|
fetch("/car.ldr").then((r) => r.text()),
|
|
32
|
-
loadPack("/car.pack.zst"),
|
|
61
|
+
loadPack("/car.pack.zst"), // .zst / .gz / .br / raw .bin
|
|
33
62
|
]);
|
|
63
|
+
|
|
34
64
|
const model = createLdrObject(parseModel(text, "car.ldr"), pack, {
|
|
35
65
|
edges: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic",
|
|
36
66
|
});
|
|
37
|
-
scene.add(model.object);
|
|
38
|
-
|
|
39
|
-
// build steps
|
|
40
|
-
model.setStep(0); // show step 0 only … model.stepCount steps total
|
|
41
|
-
model.setStep(null); // show everything
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Add lights (or `scene.environment`) as for any `MeshStandardMaterial`/`MeshPhysicalMaterial`.
|
|
45
|
-
|
|
46
|
-
## Getting parts
|
|
47
|
-
|
|
48
|
-
The LDraw parts library is **not bundled** in this package. Download it from <https://library.ldraw.org> (`complete.zip`, ~90 MB unzipped to an `ldraw/` folder containing `LDConfig.ldr`, `parts/`, `p/`).
|
|
49
|
-
The LDraw library is licensed **CC BY 2.0** — if you ship packs built from it, credit "LDraw.org Parts Library" (CC BY 2.0) in your app.
|
|
67
|
+
scene.add(model.object); // +Y up, 1 unit = 1 LDU (see `scale`)
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[--studs low|full] [--prims full|low] [--no-weld] [--no-colors] [--cond] [--textures] [--no-loose-geometry]
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Or from code (Node/Bun):
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
import { buildPack } from "ldr/node";
|
|
62
|
-
const { data, parts, missing } = buildPack({ ldrawRoot: "./ldraw", models: ["car.ldr"], studs: "low" });
|
|
63
|
-
await Bun.write("car.pack.bin", data);
|
|
69
|
+
// Build steps
|
|
70
|
+
model.setStep(0); // show step 0 … model.stepCount - 1
|
|
71
|
+
model.setStep(null); // show everything
|
|
72
|
+
const view = model.stepViews[3]; // ROTSTEP view for step 3 (or null)
|
|
64
73
|
```
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
Light the scene (or set `scene.environment`) as you would for any `MeshStandardMaterial` or `MeshPhysicalMaterial`.
|
|
67
76
|
|
|
68
|
-
## API
|
|
77
|
+
## API reference
|
|
69
78
|
|
|
70
79
|
### `ldr` (browser-safe)
|
|
71
80
|
|
|
81
|
+
**Parsing and model**
|
|
82
|
+
|
|
72
83
|
| export | description |
|
|
73
84
|
|---|---|
|
|
74
|
-
| `parse(text, name?) → Doc` |
|
|
75
|
-
| `flatten(doc,
|
|
76
|
-
| `parseModel(text, name?,
|
|
77
|
-
| `sequenceSteps(instances)` |
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
| `parse(text, name?, { geometryText? }) → Doc` | Parses LDraw text into files and commands. MPD-aware: `0 FILE`/`0 NOFILE` and `0 !DATA` blocks. |
|
|
86
|
+
| `flatten(doc, options?) → PartInstance[]` | Expands submodels into placed parts. Each instance has `part`, `color`, a 12-number matrix `m`, `step`, nested `steps`, `path` and the ROTSTEP `view` of its step. |
|
|
87
|
+
| `parseModel(text, name?, options?)` | `parse` followed by `flatten`. |
|
|
88
|
+
| `sequenceSteps(instances) → { count, stepOf, views }` | Gives each instance a global build-step index, sequencing nested submodel steps. `views` holds the ROTSTEP view per global step. |
|
|
89
|
+
| `parseLDConfig(text) → Map<code, LDrawColor>` | Reads a `!COLOUR` table into `{ code, name, hex, edge, alpha }` entries. |
|
|
90
|
+
| `fileKind(doc, name)` | Decides whether an MPD file is a part or a model: by its header type, otherwise by whether it has its own geometry. |
|
|
91
|
+
| `looseSteps` / `looseFile` / `looseSource` / `loosePartName` | A model's own type 2–5 geometry, per step, as synthetic parts named `<file>#<step>`. |
|
|
92
|
+
| `rotStepMatrix` / `stepViewMatrix` / `fileStepViews` | ROTSTEP views, following MLCad: `REL`, `ADD`, `ABS`, `END`. A ROTSTEP ends its step. |
|
|
93
|
+
| `parseTexMap` / `texmapUV` / `texmapTriangleUV` / `transformTexMap` / `resolveTexture` / `texWrapper` | `!TEXMAP` support (`PLANAR`, `CYLINDRICAL`, `SPHERICAL`). UV conventions follow LDView: v = 0 is the image's top row. |
|
|
94
|
+
| `parseColor`, `isDirectColor`, `colorToken`, `mul`, `invert`, `IDENTITY`, `norm`, `smoothNormals`, `hash32` | Low-level helpers. |
|
|
95
|
+
|
|
96
|
+
`FlattenOptions`:
|
|
97
|
+
|
|
98
|
+
| option | default | description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `looseGeometry` | `true` | Emits a model file's own type 2–5 lines as synthetic parts `<file>#<step>`. LDraw allows geometry in any file. |
|
|
101
|
+
| `textures` | `false` | TEXMAP-aware view of the model. References wrapped by a model-level `!TEXMAP` get a `texPart`, and `!TEXMAP FALLBACK` references are skipped. Use with `textures: true` packs. |
|
|
102
|
+
| `resolveModel(name)` | – | Resolves an external reference to a `Doc`, such as a model loaded separately. Return `null` to treat it as a part. |
|
|
103
|
+
| `maxDepth` | `64` | Submodel nesting limit (cycle guard). |
|
|
104
|
+
|
|
105
|
+
MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part instances (`embedded: true`) and override library parts of the same name.
|
|
106
|
+
|
|
107
|
+
**Packs and rendering**
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
| export | description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `loadPack(url) → Promise<Pack>` | Fetches, decompresses and decodes a pack. zstd and gzip are detected automatically. |
|
|
112
|
+
| `decompressPack(bytes)` / `decodePack(buffer)` | Same, from bytes you already have. |
|
|
113
|
+
| `createLdrObject(instances, pack, options?) → LdrObject` | High-level three.js object (see below). |
|
|
114
|
+
| `createInstancedEdges(pos, instancedMesh, material?)` | Welded, indexed edge lines that share an `InstancedMesh`'s instance matrices. |
|
|
115
|
+
| `conditionalLines(pack, entry, which?)` / `createInstancedConditionalEdges(cond, instancedMesh, material?)` / `conditionalLineVisible` | Type-5 conditional lines from a `cond` pack. A vertex shader keeps a segment only where both control points project to the same side of the line. |
|
|
116
|
+
| `makeTexturedMaterial(side, texture, wrapU?)` / `packTexture(pack, i)` | Printed and sticker parts from a `textures` pack: a UV material and the decoded embedded PNG. |
|
|
117
|
+
| `buildLdrScene`, `computeCulling`, `cullCore`, `makeMaterial` | Low-level renderer used by the benchmarks. `cullCore(instances, header, steps?)` is the typed-array culling core (with `steps`, also the step at which each stud and underside becomes hidden). |
|
|
118
|
+
|
|
119
|
+
`LdrObjectOptions`:
|
|
120
|
+
|
|
121
|
+
| option | default | description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `edges` | `true` | LDraw type-2 edge lines, indexed and sharing each part's instance matrices. |
|
|
124
|
+
| `cullStuds` | `true` | Hides studs fully covered by a part above. |
|
|
125
|
+
| `cullUndersides` | `true` | Hides undersides (tubes) sitting fully on studs below. |
|
|
126
|
+
| `side` | `"front"` | `"front"` draws single-sided BFC faces; `"double"` draws both sides. Uncertified parts are always double-sided. |
|
|
127
|
+
| `material` | `"plastic"` | `"plastic"` is `MeshPhysicalMaterial` with clearcoat; `"standard"` is `MeshStandardMaterial`, the cheapest option. |
|
|
128
|
+
| `conditionalLines` | `true` | Conditional (silhouette) lines. Only takes effect with `cond` packs, and follows `edges`. |
|
|
129
|
+
| `conditionalStudLines` | `true` | Conditional lines on studs. `false` roughly halves the conditional-line cost on large scenes. |
|
|
130
|
+
| `textures` | `true` | Renders `!TEXMAP` images from `textures` packs; `false` draws the FALLBACK geometry. `object.texturesReady` resolves once the images are decoded. |
|
|
131
|
+
| `colors` | pack table | Colour override: a `Map`, a record, or `(code) => color`. |
|
|
132
|
+
| `fallbackColor` | `#A0A5A9` | Colour used for unknown codes. |
|
|
133
|
+
| `ldrawSpace` | `false` | `true` keeps native LDraw space (-Y up). |
|
|
134
|
+
| `scale` | `1` | Uniform scale (1 = 1 LDU; 1 stud = 20 LDU). |
|
|
135
|
+
| `step` | all | Initial build step (inclusive, 0-based). |
|
|
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
|
+
| `lodPx` | `0.5` | LOD: maximum screen-space error in pixels. |
|
|
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
|
+
|
|
140
|
+
`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
|
+
|
|
142
|
+
`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
|
+
|
|
144
|
+
### `ldr/node` (Node / Bun only)
|
|
145
|
+
|
|
146
|
+
Everything above, plus:
|
|
89
147
|
|
|
90
|
-
|
|
148
|
+
| export | description |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `buildPack(options) → { data, parts, studKinds, missing, warnings? }` | Builds a pack. Options: `ldrawRoot`, `models`, `parts`, `allParts`, `studs` (`"low"` \| `"full"`), `primitives` (`"full"` \| `"low"`), `weld` (`true`), `cond` (`false`), `textures` (`false`), `looseGeometry` (`true`), `colors` (`true`), `lod` (`true`: meshoptimizer-simplified LOD meshes, pack feature `lod`), `reorder` (`false`: vertex-cache order), `onProgress`. |
|
|
151
|
+
| `Library(root, unofficialRoot?)` | Filesystem LDraw resolver: `where`, `flatten`, `mesh`, `colors`, `texture`, `register`. Handles case-insensitive lookup and `~Moved to` aliases. |
|
|
152
|
+
| `Compiler`, `encodePack`, `writePack`, `loadColors` | Lower-level pack compiler. |
|
|
153
|
+
| `ResolverLibrary`, `splitModel`, `flattenWithLibrary`, `collectDeps` | The resolver that `buildPack` uses. |
|
|
91
154
|
|
|
92
|
-
|
|
155
|
+
## Benchmarks
|
|
93
156
|
|
|
94
|
-
|
|
157
|
+
All numbers below were measured with the harness in [`bench/`](https://github.com/EvanBacon/ldr/tree/main/bench/) and are taken from the result files there.
|
|
95
158
|
|
|
96
|
-
|
|
159
|
+
- **Hardware and browser:** Apple **M4 Pro** MacBook Pro, Chrome with native GL (ANGLE → Metal), Bun 1.3.
|
|
160
|
+
- **Frame time:** the **median synced frame time** (`gl.finish` per frame) while orbiting the camera.
|
|
161
|
+
- **Load:** from fetch to the first rendered frame.
|
|
162
|
+
- **Baseline:** three.js geometry from `LDrawLoader` merged per part and drawn with `InstancedMesh`, which is the fastest stock approach. Stock `LDrawLoader` scenes crash the tab (OOM) at 50k bricks and above, and already on Datsville 10k.
|
|
163
|
+
- **ldr settings:** the defaults: LOD pack, distance LOD, low-detail studs, stud and underside culling, single-sided faces, no edges.
|
|
97
164
|
|
|
98
|
-
|
|
165
|
+
Sources: `bench/results/latest-perf.md` (the regression gate, `bench/baselines/darwin-apple-m4-pro.json`), `latest-mac-ablation.md`, and `research.md` §7 and Round 19.
|
|
99
166
|
|
|
100
|
-
| model |
|
|
167
|
+
| model | bricks | baseline frame | **ldr frame** | baseline load | **ldr load** |
|
|
101
168
|
|---|---|---|---|---|---|
|
|
102
|
-
|
|
|
103
|
-
| Datsville
|
|
104
|
-
| Datsville
|
|
105
|
-
| Datsville
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
169
|
+
| synthetic grid | 100,021 | 24.4 ms | **2.7 ms** | 0.2 s | 0.3 s |
|
|
170
|
+
| Datsville 10k | 10,000 | 45.9 ms | **2.2 ms** | 13.5 s | **0.34 s** |
|
|
171
|
+
| Datsville 100k | 100,000 | 105.7 ms | **6.2 ms** | 19.5 s | **0.79 s** |
|
|
172
|
+
| Datsville (full) | 392,514 | 409.6 ms | **27.2 ms** | 24.7 s | **1.6 s** |
|
|
173
|
+
|
|
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 (Round 19) then takes it to 6.2 ms and 6.6M triangles; ≤ 1.6% of pixels differ from full detail (mostly sub-pixel stud moiré).
|
|
175
|
+
|
|
176
|
+
- **Pack size:** Datsville 100k (290 unique parts) is 25.8 MB raw, 3.98 MB gzip and **1.49 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
|
+
- **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
|
+
- **Demo app** (`demo/`; rAF frame median at 2880×1800 on the M4 Pro):
|
|
179
|
+
- 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
|
+
- Full Datsville: **1.7 → 9.9 fps**.
|
|
181
|
+
- Datsville 100k: **8 → 43.5 fps**.
|
|
182
|
+
- **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 6.2 → 9.2 ms and full Datsville nothing measurable (27.2 ms), because edges are hidden where bricks are sub-pixel (`lodEdgePx`). Without edge LOD they cost 35 ms (100k) and 153 ms (full).
|
|
184
|
+
- **Build steps:** `setStep` takes 66 ms on Datsville 100k and 29 ms on 10k (it was a full rebuild: 752 / 347 ms).
|
|
185
|
+
- **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
|
+
|
|
187
|
+
Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun run bench` (see [`bench/README.md`](https://github.com/EvanBacon/ldr/blob/main/bench/README.md)).
|
|
188
|
+
|
|
189
|
+
## LDraw spec compliance
|
|
190
|
+
|
|
191
|
+
- Line types 0–5. BFC: `CERTIFY`, `CCW`/`CW`, `INVERTNEXT`, `NOCLIP`, inverted matrices.
|
|
192
|
+
- 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.
|
|
193
|
+
- Geometry drawn directly in model files (type 2–5 lines outside parts), on by default.
|
|
194
|
+
- `0 STEP`, nested submodel step sequencing, `0 ROTSTEP` (`REL`/`ADD`/`ABS`/`END`), and `0 BUFEXCHG`.
|
|
195
|
+
- `!TEXMAP` `START`/`NEXT`/`FALLBACK`/`END`, with `PLANAR`, `CYLINDRICAL` and `SPHERICAL` projections (orientation matches LDView), and model-level `!TEXMAP` around part references.
|
|
196
|
+
- Colours: LDConfig `!COLOUR`, edge colours, main/edge colour inheritance (16/24), and direct colours (`0x2RRGGBB`).
|
|
197
|
+
- Case-insensitive, backslash-normalized file lookup; `~Moved to` alias parts.
|
|
198
|
+
|
|
199
|
+
## Limitations
|
|
200
|
+
|
|
201
|
+
- **Transparency:** transparent colours render opaque.
|
|
202
|
+
- **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. Rotating the camera per step is up to your app.
|
|
204
|
+
- **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
|
+
- **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:** at the default `lodEdgePx` edges disappear where bricks are a few pixels wide, which visibly lightens far-away views drawn with edges (4–7% of pixels on Datsville overviews). Set `lodEdgePx: 0` to keep every edge (much slower on large models), or `lod: false` to turn LOD off entirely.
|
|
207
|
+
|
|
208
|
+
## Attribution and license
|
|
209
|
+
|
|
210
|
+
- **Code:** MIT © Evan Bacon.
|
|
211
|
+
- **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
|
+
- **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.
|
package/dist/cdn.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Pack } from "./render.js";
|
|
2
|
+
import { type PartInstance } from "./model.js";
|
|
3
|
+
import { type LdrObject, type LdrObjectOptions } from "./scene.js";
|
|
4
|
+
/** Default parts CDN (LDraw library parts, CC BY 2.0 / 4.0, compiled by ldr; hosted with the demo site). */
|
|
5
|
+
export declare const DEFAULT_PARTS_CDN = "https://ldr-demo.vercel.app/cdn/v1/";
|
|
6
|
+
export interface PartsCdnIndex {
|
|
7
|
+
format: string;
|
|
8
|
+
release?: string;
|
|
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;
|
|
43
|
+
}
|
|
44
|
+
export interface LoadedModel extends LdrObject {
|
|
45
|
+
pack: Pack; /** The flattened model (part instances). */
|
|
46
|
+
instances: PartInstance[];
|
|
47
|
+
timings: {
|
|
48
|
+
fetchModel: number;
|
|
49
|
+
parse: number;
|
|
50
|
+
parts: number;
|
|
51
|
+
build: number;
|
|
52
|
+
total: number;
|
|
53
|
+
};
|
|
54
|
+
requests: number;
|
|
55
|
+
bytes: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 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
|
|
59
|
+
* parts CDN and builds an instanced `LdrObject`. No LDraw library or parts pack needed.
|
|
60
|
+
* Limits (v1): parts defined inside the model (MPD-embedded parts) and loose geometry in model files are skipped (listed in `stats.missing`).
|
|
61
|
+
*/
|
|
62
|
+
export declare function loadModel(source: string, o?: LoadModelOptions): Promise<LoadedModel>;
|