ldr 0.5.1 → 0.6.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 +12 -10
- package/dist/cdn.d.ts +8 -41
- package/dist/cli.js +157 -30
- package/dist/cull.d.ts +68 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +844 -500
- package/dist/load-worker.d.ts +1 -0
- package/dist/load-worker.gen.d.ts +2 -0
- package/dist/node/index.js +970 -541
- package/dist/pack-io.d.ts +9 -0
- package/dist/parts-cdn.d.ts +64 -0
- package/dist/render.d.ts +23 -43
- package/dist/scene.d.ts +10 -1
- package/package.json +2 -2
package/dist/pack-io.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
/** Detect gzip / zstd magic and return raw pack bytes (an ArrayBuffer you can hand to `decodePack`). */
|
|
2
2
|
export declare function decompressPack(data: ArrayBuffer | Uint8Array): Promise<ArrayBuffer>;
|
|
3
|
+
/** A decoded parts pack. `buffer` is the raw (uncompressed) pack, used by `mergePacks`. */
|
|
4
|
+
export type Pack = {
|
|
5
|
+
header: any;
|
|
6
|
+
arr: (r: any) => any;
|
|
7
|
+
bytes: number;
|
|
8
|
+
buffer?: ArrayBuffer;
|
|
9
|
+
};
|
|
10
|
+
/** Decode an uncompressed parts pack (4-byte LE header length, JSON header, then aligned typed-array payload). */
|
|
11
|
+
export declare function decodePack(buf: ArrayBuffer): Pack;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Pack } from "./pack-io.js";
|
|
2
|
+
import { type PartInstance } from "./model.js";
|
|
3
|
+
import { type CullResult } from "./cull.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 16 KB. */
|
|
19
|
+
gap?: number;
|
|
20
|
+
/** Max parallel Range requests. Default 24 (HTTP/2 multiplexes them; browsers cap HTTP/1.1 at 6 per host themselves). */
|
|
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 PrepareOptions extends LoadPartsOptions {
|
|
39
|
+
/** Also compute stud/underside culling (for `createLdrObject`'s `precomputedCull`), using this `textures` setting (default true). */
|
|
40
|
+
cull?: boolean;
|
|
41
|
+
textures?: boolean;
|
|
42
|
+
/** Called with the flattened model as soon as it is parsed (before the parts are fetched). */
|
|
43
|
+
onParsed?: (insts: PartInstance[]) => void;
|
|
44
|
+
/** Treat `source` as LDraw text instead of a URL. */
|
|
45
|
+
text?: boolean;
|
|
46
|
+
/** File name used for the main model (MPD resolution). Default: last URL segment. */
|
|
47
|
+
name?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface PreparedModel {
|
|
50
|
+
insts: PartInstance[];
|
|
51
|
+
pack: Pack;
|
|
52
|
+
missing: string[];
|
|
53
|
+
requests: number;
|
|
54
|
+
bytes: number;
|
|
55
|
+
timings: {
|
|
56
|
+
fetchModel: number;
|
|
57
|
+
parse: number;
|
|
58
|
+
parts: number;
|
|
59
|
+
cull?: number;
|
|
60
|
+
};
|
|
61
|
+
cull?: CullResult;
|
|
62
|
+
}
|
|
63
|
+
/** Everything in `loadModel` before three.js: fetch + parse the model, fetch + inflate + merge its parts. No DOM/three (runs in a Worker). */
|
|
64
|
+
export declare function prepareModel(source: string, o?: PrepareOptions): Promise<PreparedModel>;
|
package/dist/render.d.ts
CHANGED
|
@@ -1,48 +1,36 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { decodePack, type Pack } from './pack-io.js';
|
|
3
|
+
export { decodePack, type Pack };
|
|
4
|
+
/** `edges`: LDraw edge lines, welded + indexed and sharing each batch's instance matrices (`'idx'` is the same as `true`).
|
|
5
|
+
* `cond`: draw type-5 conditional lines when the pack carries them (pack v2, built with `cond: true`) and edges are on. Default true.
|
|
6
|
+
* `condStuds`: also draw them on stud batches (stud silhouettes). Default true. Uncertified (no BFC) parts are always double-sided. */
|
|
4
7
|
export type LdrParams = {
|
|
5
|
-
|
|
8
|
+
cull?: CullResult;
|
|
6
9
|
cullStuds: boolean;
|
|
7
10
|
cullUnder: boolean;
|
|
8
|
-
edges
|
|
11
|
+
edges?: boolean | 'idx';
|
|
9
12
|
side: 'double' | 'front';
|
|
10
|
-
uncertDouble?: boolean;
|
|
11
13
|
cond?: boolean;
|
|
12
14
|
condStuds?: boolean;
|
|
13
15
|
/** Draw textured (!TEXMAP) parts with their PNGs when the pack carries them (pack v2 "tex", `ldr pack --textures`). Default true. */
|
|
14
16
|
tex?: boolean;
|
|
15
|
-
/** Compact vertex formats (int8 normals, uint8 colours). Default true. */
|
|
16
|
-
quant?: boolean;
|
|
17
17
|
/** Distance LOD: spatial clusters per part, simplified meshes from `lod` packs, studs dropped and edges hidden once sub-pixel. Default true. */
|
|
18
18
|
lod?: boolean;
|
|
19
19
|
/** LOD: max screen-space error in pixels (default 0.5). */
|
|
20
20
|
lodPx?: number;
|
|
21
21
|
/** LOD: draw edge lines only where one stud (20 LDU) spans at least this many pixels (default 0.5; 0 = always). */
|
|
22
22
|
lodEdgePx?: number;
|
|
23
|
-
/** LOD:
|
|
24
|
-
|
|
25
|
-
/** LOD:
|
|
26
|
-
|
|
23
|
+
/** LOD: draw only edge segments at least this many pixels long on screen (default 0.15; 0 = all). */
|
|
24
|
+
lodEdgeMinPx?: number;
|
|
25
|
+
/** LOD: skip upright parts' undersides while the camera is above their bottom faces and studs are < 4 px (default true). */
|
|
26
|
+
lodUnder?: boolean;
|
|
27
27
|
/** Global build step per instance (same order as `insts`). Enables `setStep`: steps become instance-buffer compaction with
|
|
28
28
|
* step-aware culling instead of a rebuild. */
|
|
29
29
|
steps?: ArrayLike<number>;
|
|
30
30
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
color: string;
|
|
34
|
-
m: number[];
|
|
35
|
-
};
|
|
36
|
-
/** A decoded parts pack. `buffer` is the raw (uncompressed) pack, used by `mergePacks`. */
|
|
37
|
-
export type Pack = {
|
|
38
|
-
header: any;
|
|
39
|
-
arr: (r: any) => any;
|
|
40
|
-
bytes: number;
|
|
41
|
-
buffer?: ArrayBuffer;
|
|
42
|
-
};
|
|
31
|
+
import { type Inst } from './cull.js';
|
|
32
|
+
export type { Inst };
|
|
43
33
|
export declare function loadPack(url: string): Promise<Pack>;
|
|
44
|
-
/** Decode an uncompressed parts pack (4-byte LE header length, JSON header, then aligned typed-array payload). */
|
|
45
|
-
export declare function decodePack(buf: ArrayBuffer): Pack;
|
|
46
34
|
export declare function makeMaterial(side: THREE.Side): THREE.MeshStandardMaterial;
|
|
47
35
|
/** Material for textured (!TEXMAP) triangles: the LDraw colour as base, the PNG blended over it by its alpha (LDraw spec),
|
|
48
36
|
* and no texture outside the projection's [0,1] range. `map` should have flipY = false (v = 0 is the image's top row). */
|
|
@@ -64,23 +52,11 @@ export declare function conditionalLineVisible(cond: ArrayLike<number>, i: numbe
|
|
|
64
52
|
* `cond` is part-space stride 12 (p0, p1, c0, c1), e.g. from `conditionalLines(pack, pack.header.parts[name])`.
|
|
65
53
|
* GPU cost: 2 vertices x 13 floats per line (104 B/line), per part, not per instance. Keep `geometry.instanceCount` in sync with `im.count`. */
|
|
66
54
|
export declare function createInstancedConditionalEdges(cond: ArrayLike<number>, im: THREE.InstancedMesh, material?: THREE.Material): THREE.LineSegments;
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
underHidden: Uint8Array;
|
|
73
|
-
studsHidden: number;
|
|
74
|
-
/** With `steps`: the first build step at which each stud / underside is hidden (STEP_NEVER = never), for step-aware culling. */
|
|
75
|
-
studHideStep?: Int32Array;
|
|
76
|
-
underHideStep?: Int32Array;
|
|
77
|
-
};
|
|
78
|
-
/** `studHideStep` / `underHideStep` value for "never hidden". */
|
|
79
|
-
export declare const STEP_NEVER = 2147483647;
|
|
80
|
-
/** Visit every stud (instance order, then part stud order) with its world matrix `w` (scratch; copy to keep). */
|
|
81
|
-
export declare function forEachStud(insts: Inst[], H: any, fn: (j: number, inst: number, w: Float64Array, kind: string, color: string, stud: number) => void): void;
|
|
82
|
-
/** `steps` (optional, per instance): also compute when each stud / underside becomes hidden as the model is built step by step. */
|
|
83
|
-
export declare function cullCore(insts: Inst[], H: any, steps?: ArrayLike<number>): CullResult;
|
|
55
|
+
/** InstancedMesh.computeBoundingSphere() (three r151-r186), ported to a loop over the instance array: same arithmetic, same result
|
|
56
|
+
* bit for bit, without a Matrix4 copy + Sphere clone per instance (1.8M studs on Datsville 100k). */
|
|
57
|
+
export declare function instancedBoundingSphere(im: THREE.InstancedMesh): THREE.Sphere;
|
|
58
|
+
import { forEachStud, cullCore, STEP_NEVER, type CullResult } from './cull.js';
|
|
59
|
+
export { forEachStud, cullCore, STEP_NEVER, type CullResult };
|
|
84
60
|
/** Object-shaped culling result (one entry per stud with its world matrix). Kept for API compatibility; `buildLdrScene` uses `cullCore`. */
|
|
85
61
|
export declare function computeCulling(insts: Inst[], H: any): {
|
|
86
62
|
studs: {
|
|
@@ -98,7 +74,6 @@ export declare function buildLdrScene(insts: Inst[], pack: Pack, P: LdrParams, c
|
|
|
98
74
|
setSide: (front: boolean) => void;
|
|
99
75
|
setEdges: (v: boolean) => void;
|
|
100
76
|
setCond: (v: boolean) => void;
|
|
101
|
-
swapStuds: (alt: any) => () => void;
|
|
102
77
|
setLod: (on: boolean) => void;
|
|
103
78
|
setStep: (s: number | null) => {
|
|
104
79
|
studsTotal: number;
|
|
@@ -109,3 +84,8 @@ export declare function buildLdrScene(insts: Inst[], pack: Pack, P: LdrParams, c
|
|
|
109
84
|
box: THREE.Box3;
|
|
110
85
|
texturesReady: Promise<void>;
|
|
111
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Builds (and caches on the pack) the shared BufferGeometries `buildLdrScene` will use, in time slices of about `sliceMs` that yield
|
|
89
|
+
* to the event loop, until done or `stop()` returns true. Lets the main thread do this useful work while the load worker culls.
|
|
90
|
+
*/
|
|
91
|
+
export declare function warmPackGeometry(pack: Pack, stop?: () => boolean, sliceMs?: number): Promise<void>;
|
package/dist/scene.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { type Pack } from "./render.js";
|
|
3
|
+
import { type CullResult } from "./cull.js";
|
|
3
4
|
import type { PartInstance, LDrawColor } from "./model.js";
|
|
4
5
|
import type { StepView } from "./spec.js";
|
|
5
6
|
export type ColorSource = Map<number, string | LDrawColor> | Record<number | string, string | LDrawColor> | ((code: number) => THREE.ColorRepresentation | undefined);
|
|
6
7
|
export interface LdrObjectOptions {
|
|
8
|
+
/** Culling computed ahead (e.g. by `prepareModel({ cull: true })` in a worker) for these same instances and `textures` setting. */
|
|
9
|
+
precomputedCull?: CullResult;
|
|
7
10
|
/** Draw LDraw type-2 edge lines (indexed, sharing each part's instance matrices). Default true. */
|
|
8
11
|
edges?: boolean;
|
|
9
12
|
/** Draw type-5 conditional lines (silhouettes of curved surfaces) when the pack carries them (`ldr pack --cond`, pack v2).
|
|
@@ -36,12 +39,18 @@ export interface LdrObjectOptions {
|
|
|
36
39
|
/** Distance LOD, re-selected for each camera you render with. Default true. Batches are split into spatial clusters (so off-screen
|
|
37
40
|
* ones are frustum-culled); each cluster draws the coarsest simplified mesh (from `lod` packs, the `buildPack` default) whose error
|
|
38
41
|
* stays under `lodPx`, drops studs whose 12-LDU disc is under `lodPx`, and hides edge lines where a stud spans under `lodEdgePx`.
|
|
39
|
-
* Datsville 100k on an M4 Pro: 22 ->
|
|
42
|
+
* Datsville 100k on an M4 Pro: 22 -> 4.3 ms per frame (57 -> 6.2 ms with edges); <=1.5% of pixels differ without edges, mostly stud moire. */
|
|
40
43
|
lod?: boolean;
|
|
41
44
|
/** LOD: max screen-space error in pixels. Default 0.5. */
|
|
42
45
|
lodPx?: number;
|
|
43
46
|
/** LOD: draw edge / conditional lines only where one stud (20 LDU) spans at least this many pixels. Default 0.5; 0 = always. */
|
|
44
47
|
lodEdgePx?: number;
|
|
48
|
+
/** LOD: draw only the edge segments at least this many pixels long on screen (a part's edges are sorted longest first). Default 0.15;
|
|
49
|
+
* 0 = every segment. Shorter segments still darken a pixel each, so far views drawn with edges get lighter as this grows. */
|
|
50
|
+
lodEdgeMinPx?: number;
|
|
51
|
+
/** LOD: skip undersides that can't be seen: while the camera is behind every bottom face of a cluster's (same-orientation) parts and
|
|
52
|
+
* studs are under 4 px, or where the body is drawn as its closed bounding box. Default true. */
|
|
53
|
+
lodUnder?: boolean;
|
|
45
54
|
}
|
|
46
55
|
export interface LdrStats {
|
|
47
56
|
instances: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "High-performance LEGO (LDraw) rendering for three.js: instanced parts packs, hidden stud/tube culling, low-detail studs, indexed edges, build steps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Evan Bacon",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"LICENSE"
|
|
50
50
|
],
|
|
51
51
|
"scripts": {
|
|
52
|
-
"build": "rm -rf dist && bun scripts/build.ts && tsc -p tsconfig.build.json && bun scripts/postbuild.ts && node scripts/check-dist.mjs",
|
|
52
|
+
"build": "rm -rf dist && bun scripts/gen-worker.ts && bun scripts/build.ts && tsc -p tsconfig.build.json && bun scripts/postbuild.ts && node scripts/check-dist.mjs",
|
|
53
53
|
"test": "bun test",
|
|
54
54
|
"prepublishOnly": "bun run build && bun test"
|
|
55
55
|
},
|