ldr 0.2.0 → 0.4.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 +10 -6
- package/dist/cli.js +1212 -289
- package/dist/index.d.ts +3 -3
- package/dist/index.js +876 -43
- package/dist/model.d.ts +30 -3
- package/dist/node/build-pack.d.ts +11 -0
- package/dist/node/compile.d.ts +29 -2
- package/dist/node/index.js +1401 -134
- package/dist/node/library.d.ts +19 -4
- package/dist/node/resolver.d.ts +29 -1
- package/dist/render.d.ts +26 -0
- package/dist/scene.d.ts +22 -0
- package/dist/spec.d.ts +166 -9
- package/package.json +1 -1
package/dist/node/library.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { type FlattenOptions } from "../model.js";
|
|
1
2
|
import { type Doc, type PartMesh, type Instance } from "../spec.js";
|
|
2
3
|
export declare class Library {
|
|
3
4
|
root: string;
|
|
4
5
|
unofficialRoot?: string | undefined;
|
|
5
6
|
cache: Map<string, Doc | null>;
|
|
6
7
|
meshCache: Map<string, PartMesh>;
|
|
8
|
+
/** Files registered from flattened documents (MPD files and synthetic parts), searched before the library (MPD spec: a document's
|
|
9
|
+
* files win over library files of the same name). The latest `flatten` wins; replacing or shadowing an entry clears the mesh cache. */
|
|
10
|
+
embedded: Map<string, Doc>;
|
|
11
|
+
/** Register `name` as a part whose source is file `file` of `doc` (or a parsed synthetic source). */
|
|
12
|
+
register(name: string, doc: Doc, file?: string): void;
|
|
7
13
|
/** Optional second root for unofficial parts (e.g. Unofficial/ next to the official tree). */
|
|
8
14
|
constructor(root: string, unofficialRoot?: string | undefined);
|
|
9
15
|
/**
|
|
@@ -27,9 +33,18 @@ export declare class Library {
|
|
|
27
33
|
alpha: number;
|
|
28
34
|
}>;
|
|
29
35
|
/** Flattened triangle mesh: positions + color code per tri (16 = inherit); edges as line segments.
|
|
30
|
-
* `cond` stores type-5 lines as 12 floats each: endpoint0, endpoint1, control0, control1
|
|
31
|
-
|
|
36
|
+
* `cond` stores type-5 lines as 12 floats each: endpoint0, endpoint1, control0, control1.
|
|
37
|
+
* Default = what a renderer without !TEXMAP support draws (`0 !:` geometry skipped, FALLBACK geometry drawn).
|
|
38
|
+
* `{ texmap: true }` = TEXMAP-aware geometry (START..FALLBACK incl. `0 !:` lines, FALLBACK..END skipped) plus `uv` / `texture`. */
|
|
39
|
+
mesh(name: string, opts?: {
|
|
40
|
+
texmap?: boolean;
|
|
41
|
+
}): PartMesh;
|
|
42
|
+
/** Texture PNG bytes for a !TEXMAP name: an MPD `!DATA` block in `doc` first, then the LDraw search path with a
|
|
43
|
+
* `textures/` prefix (parts/, p/, models/, official then unofficial), then without it. Case-insensitive. */
|
|
44
|
+
texture(name: string, doc?: Doc): Uint8Array | null;
|
|
32
45
|
private emit;
|
|
33
|
-
/** Flatten a model into part instances
|
|
34
|
-
|
|
46
|
+
/** Flatten a model into part instances (see `flatten` in the browser entry for the rules; `models/` library files are walked
|
|
47
|
+
* as submodels). 24 handled at render (edge color). Alias parts resolve to canonical names. MPD files and synthetic parts
|
|
48
|
+
* (loose model geometry, model-level !TEXMAP wrappers) are registered so `mesh(inst.part)` / `mesh(inst.texPart)` find them. */
|
|
49
|
+
flatten(doc: Doc, opts?: FlattenOptions): Instance[];
|
|
35
50
|
}
|
package/dist/node/resolver.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Doc } from '../spec.js';
|
|
2
|
+
import { type FlattenOptions, type PartInstance } from '../model.js';
|
|
1
3
|
export declare const TOP_STUD_RE: RegExp;
|
|
2
4
|
export declare const UNDER_STUD_RE: RegExp;
|
|
3
5
|
export declare const norm: (ref: any) => any;
|
|
@@ -17,6 +19,11 @@ export declare class Library {
|
|
|
17
19
|
text: Map<string, string>;
|
|
18
20
|
stats: Map<string, any>;
|
|
19
21
|
constructor(root: string);
|
|
22
|
+
/** Document files registered by `flatten` (MPD files, synthetic parts): name -> key `mpd/<name>`. Searched first (MPD spec:
|
|
23
|
+
* a document's files win over library files). First registration wins; a different text for the same name goes to `conflicts`. */
|
|
24
|
+
virtual: Map<string, string>;
|
|
25
|
+
conflicts: Set<string>;
|
|
26
|
+
addVirtual(name: string, text: string): string;
|
|
20
27
|
resolve(ref: any): string | null;
|
|
21
28
|
read(key: string): string;
|
|
22
29
|
isPart(key: any): any;
|
|
@@ -25,8 +32,29 @@ export declare class Library {
|
|
|
25
32
|
export declare function splitModel(text: any, fallbackName?: string): {
|
|
26
33
|
files: Map<string, string>;
|
|
27
34
|
main: string | null;
|
|
35
|
+
data: Map<string, string>;
|
|
28
36
|
};
|
|
29
|
-
|
|
37
|
+
/** Parse the files of a `splitModel` result into a document (same parser as the browser path). */
|
|
38
|
+
export declare function modelDoc(model: {
|
|
39
|
+
files: Map<string, string>;
|
|
40
|
+
main: string | null;
|
|
41
|
+
}): Doc;
|
|
42
|
+
export type ResolvedInstance = {
|
|
43
|
+
part: string;
|
|
44
|
+
ref: string;
|
|
45
|
+
color: string;
|
|
46
|
+
m: number[];
|
|
47
|
+
texPart?: string;
|
|
48
|
+
texRef?: string;
|
|
49
|
+
inst: PartInstance;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Flatten a `splitModel` result to library-resolved instances (`part` = library key, `ref` = name as referenced). Same walk as the
|
|
53
|
+
* browser `flatten` (shared code): MPD-embedded parts and synthetic parts (a model file's own geometry; with `textures`, parts wrapped
|
|
54
|
+
* by a model-level !TEXMAP) are registered on `lib` as virtual files (`mpd/<name>`) so the Compiler compiles them; every file of the
|
|
55
|
+
* document is registered, so MPD files also win over library files referenced from parts (MPD spec).
|
|
56
|
+
*/
|
|
57
|
+
export declare function flatten(model: any, lib: Library, onInstance: (i: ResolvedInstance) => void, opts?: FlattenOptions): {
|
|
30
58
|
missing: Map<any, any>;
|
|
31
59
|
};
|
|
32
60
|
export declare function collectDeps(lib: any, refs: any): Map<any, any>;
|
package/dist/render.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
+
/** `cond`: draw type-5 conditional lines when the pack carries them (pack v2, built with `cond: true`); with 'idx' or legacy `true` edges. Default true.
|
|
3
|
+
* `condStuds`: also draw them on stud batches (stud silhouettes). Default true. */
|
|
2
4
|
export type LdrParams = {
|
|
3
5
|
pack: string;
|
|
4
6
|
cullStuds: boolean;
|
|
@@ -6,6 +8,10 @@ export type LdrParams = {
|
|
|
6
8
|
edges: boolean | 'idx' | 'ss';
|
|
7
9
|
side: 'double' | 'front';
|
|
8
10
|
uncertDouble?: boolean;
|
|
11
|
+
cond?: boolean;
|
|
12
|
+
condStuds?: boolean;
|
|
13
|
+
/** Draw textured (!TEXMAP) parts with their PNGs when the pack carries them (pack v2 "tex", `ldr pack --textures`). Default true. */
|
|
14
|
+
tex?: boolean;
|
|
9
15
|
};
|
|
10
16
|
export type Inst = {
|
|
11
17
|
part: string;
|
|
@@ -21,8 +27,26 @@ export declare function loadPack(url: string): Promise<Pack>;
|
|
|
21
27
|
/** Decode an uncompressed parts pack (4-byte LE header length, JSON header, then aligned typed-array payload). */
|
|
22
28
|
export declare function decodePack(buf: ArrayBuffer): Pack;
|
|
23
29
|
export declare function makeMaterial(side: THREE.Side): THREE.MeshStandardMaterial;
|
|
30
|
+
/** Material for textured (!TEXMAP) triangles: the LDraw colour as base, the PNG blended over it by its alpha (LDraw spec),
|
|
31
|
+
* and no texture outside the projection's [0,1] range. `map` should have flipY = false (v = 0 is the image's top row). */
|
|
32
|
+
export declare function makeTexturedMaterial(side: THREE.Side, map: THREE.Texture, wrapU?: boolean): THREE.MeshStandardMaterial;
|
|
33
|
+
/** Decode a pack's embedded PNG into a texture (async; the texture fills in when decoded). Needs `createImageBitmap` (browsers, workers). */
|
|
34
|
+
export declare function packTexture(pack: Pack, i: number): {
|
|
35
|
+
texture: THREE.Texture;
|
|
36
|
+
ready: Promise<void>;
|
|
37
|
+
};
|
|
24
38
|
/** Edge lines for an InstancedMesh: welds + indexes `pos` (line-segment endpoints, part space) and shares the mesh's instanceMatrix buffer, so edges cost one extra draw and no extra per-instance memory. Keep `geometry.instanceCount` in sync with `im.count`. */
|
|
25
39
|
export declare function createInstancedEdges(pos: ArrayLike<number>, im: THREE.InstancedMesh, material?: THREE.Material): THREE.LineSegments;
|
|
40
|
+
/** Expand a pack entry's conditional lines (pack v2 `condl` body / `condlu` underside) to part-space floats, stride 12:
|
|
41
|
+
* p0, p1, control0, control1. `which` = 'all' (default; body then underside), 'body' or 'under'.
|
|
42
|
+
* Returns null for v1 packs / parts without conditional lines. */
|
|
43
|
+
export declare function conditionalLines(pack: Pack, entry: any, which?: 'all' | 'body' | 'under'): Float32Array | null;
|
|
44
|
+
/** CPU mirror of the conditional-line vertex shader: true if the line p0-p1 is drawn for this model-view-projection matrix. */
|
|
45
|
+
export declare function conditionalLineVisible(cond: ArrayLike<number>, i: number, mvp: THREE.Matrix4): boolean;
|
|
46
|
+
/** Conditional (type-5) lines for an InstancedMesh, sharing its instanceMatrix buffer (one extra draw, no per-instance memory).
|
|
47
|
+
* `cond` is part-space stride 12 (p0, p1, c0, c1), e.g. from `conditionalLines(pack, pack.header.parts[name])`.
|
|
48
|
+
* 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`. */
|
|
49
|
+
export declare function createInstancedConditionalEdges(cond: ArrayLike<number>, im: THREE.InstancedMesh, material?: THREE.Material): THREE.LineSegments;
|
|
26
50
|
export declare function computeCulling(insts: Inst[], H: any): {
|
|
27
51
|
studs: {
|
|
28
52
|
kind: string;
|
|
@@ -38,6 +62,8 @@ export declare function buildLdrScene(insts: Inst[], pack: Pack, P: LdrParams, c
|
|
|
38
62
|
setCull: (studs: boolean, under: boolean) => void;
|
|
39
63
|
setSide: (front: boolean) => void;
|
|
40
64
|
setEdges: (v: boolean) => void;
|
|
65
|
+
setCond: (v: boolean) => void;
|
|
41
66
|
swapStuds: (alt: any) => () => void;
|
|
42
67
|
box: THREE.Box3;
|
|
68
|
+
texturesReady: Promise<void>;
|
|
43
69
|
};
|
package/dist/scene.d.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { type Pack } from "./render.js";
|
|
3
3
|
import type { PartInstance, LDrawColor } from "./model.js";
|
|
4
|
+
import type { StepView } from "./spec.js";
|
|
4
5
|
export type ColorSource = Map<number, string | LDrawColor> | Record<number | string, string | LDrawColor> | ((code: number) => THREE.ColorRepresentation | undefined);
|
|
5
6
|
export interface LdrObjectOptions {
|
|
6
7
|
/** Draw LDraw type-2 edge lines (indexed, sharing each part's instance matrices). Default true. */
|
|
7
8
|
edges?: boolean;
|
|
9
|
+
/** Draw type-5 conditional lines (silhouettes of curved surfaces) when the pack carries them (`ldr pack --cond`, pack v2).
|
|
10
|
+
* Shares each part's instance matrices; follows `edges` on/off. Default true (no effect on packs without them). */
|
|
11
|
+
conditionalLines?: boolean;
|
|
12
|
+
/** Also draw conditional lines on studs (stud silhouettes). Default true. Round 17 (M4 Pro, Datsville 100k): stud lines are
|
|
13
|
+
* ~37% of conditional-line vertex work; turning them off saves ~3.7 ms/frame. Only read when the object is built. */
|
|
14
|
+
conditionalStudLines?: boolean;
|
|
15
|
+
/** Draw textured parts (printed / sticker parts using !TEXMAP) with their images when the pack carries them (`ldr pack --textures`).
|
|
16
|
+
* Default true; false draws the parts' FALLBACK geometry like a pack without textures. Only read when the object is built. */
|
|
17
|
+
textures?: boolean;
|
|
8
18
|
/** Hide studs fully covered by a part above. Default true. */
|
|
9
19
|
cullStuds?: boolean;
|
|
10
20
|
/** Hide undersides (tubes) sitting fully on studs below. Default true. */
|
|
@@ -33,6 +43,11 @@ export interface LdrStats {
|
|
|
33
43
|
undersTotal: number;
|
|
34
44
|
undersHidden: number;
|
|
35
45
|
buildMs: number;
|
|
46
|
+
/** Conditional-line batches (draw calls) created; 0 for packs without conditional lines. */
|
|
47
|
+
condBatches?: number;
|
|
48
|
+
/** Textured batches (one per part x texture) and distinct textures; 0 for packs without textures. */
|
|
49
|
+
texBatches?: number;
|
|
50
|
+
textures?: number;
|
|
36
51
|
}
|
|
37
52
|
export interface LdrObject {
|
|
38
53
|
/** Add this to your scene. */
|
|
@@ -40,9 +55,16 @@ export interface LdrObject {
|
|
|
40
55
|
stats: LdrStats;
|
|
41
56
|
/** Number of build steps (nested submodel steps are sequenced). */
|
|
42
57
|
stepCount: number;
|
|
58
|
+
/** Per build step, the view rotation set by MLCad `0 ROTSTEP` (null = default view). Get the 3x3 (LDraw space, row-major) with
|
|
59
|
+
* `stepViewMatrix(view, yourDefaultView)`; in three.js terms rotate the model by it inside the object's -Y-up flip. */
|
|
60
|
+
stepViews: (StepView | null)[];
|
|
43
61
|
/** Show instances up to and including `step` (null = all). Rebuilds batches; culling is recomputed for the partial model. */
|
|
44
62
|
setStep(step: number | null): void;
|
|
45
63
|
setEdges(on: boolean): void;
|
|
64
|
+
/** Resolves when the pack's textures (if any) are decoded and ready to render. Replaced by a new promise after `setStep`. */
|
|
65
|
+
texturesReady: Promise<void>;
|
|
66
|
+
/** Toggle conditional lines (only visible while edges are on). */
|
|
67
|
+
setConditionalLines(on: boolean): void;
|
|
46
68
|
setCulling(studs: boolean, undersides: boolean): void;
|
|
47
69
|
setSide(side: "front" | "double"): void;
|
|
48
70
|
/** Bounding box in the object's parent space. */
|
package/dist/spec.d.ts
CHANGED
|
@@ -9,7 +9,35 @@ export type BufExchg = {
|
|
|
9
9
|
op: "STORE" | "RETRIEVE";
|
|
10
10
|
buf: string;
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
/** TEXMAP projection (LDraw !TEXMAP extension). Points are in the coordinates of the file containing the START/NEXT. */
|
|
13
|
+
export type TexMapMethod = "PLANAR" | "CYLINDRICAL" | "SPHERICAL";
|
|
14
|
+
export interface TexMap {
|
|
15
|
+
method: TexMapMethod;
|
|
16
|
+
p1: number[];
|
|
17
|
+
p2: number[];
|
|
18
|
+
p3: number[];
|
|
19
|
+
/** CYLINDRICAL: angular extent (degrees); SPHERICAL: horizontal extent (degrees). */
|
|
20
|
+
a?: number;
|
|
21
|
+
/** SPHERICAL: vertical extent (degrees). */
|
|
22
|
+
b?: number;
|
|
23
|
+
/** PNG name as written (resolve via `textures/<name>` then `<name>`, or an MPD `!DATA` block). */
|
|
24
|
+
texture: string;
|
|
25
|
+
glossmap?: string;
|
|
26
|
+
/** true for `!TEXMAP NEXT` (applies to the next type 1-5 line only). */
|
|
27
|
+
next?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export type TexMapCmd = {
|
|
30
|
+
op: "START" | "NEXT" | "FALLBACK" | "END";
|
|
31
|
+
map?: TexMap;
|
|
32
|
+
};
|
|
33
|
+
/** Texture-mapping annotations on geometry (type 1-5) commands:
|
|
34
|
+
* `tex` = texture in effect for TEXMAP-aware renderers (START..FALLBACK or NEXT); `fallback` = line sits between
|
|
35
|
+
* FALLBACK and END, so TEXMAP-aware renderers skip it and everyone else draws it. */
|
|
36
|
+
export interface TexAnno {
|
|
37
|
+
tex?: TexMap;
|
|
38
|
+
fallback?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export type Cmd = ({
|
|
13
41
|
t: 0;
|
|
14
42
|
text: string;
|
|
15
43
|
meta?: string;
|
|
@@ -17,33 +45,87 @@ export type Cmd = {
|
|
|
17
45
|
step?: boolean;
|
|
18
46
|
rotstep?: RotStep;
|
|
19
47
|
bufexchg?: BufExchg;
|
|
20
|
-
|
|
48
|
+
/** `0 !TEXMAP ...` */
|
|
49
|
+
texmap?: TexMapCmd;
|
|
50
|
+
/** `0 !: <line>`: textured-only geometry, ignored by renderers without TEXMAP support (parsed here for those that have it). */
|
|
51
|
+
geom?: Cmd;
|
|
52
|
+
} & TexAnno) | ({
|
|
21
53
|
t: 1;
|
|
22
54
|
color: number;
|
|
23
55
|
m: M;
|
|
24
56
|
file: string;
|
|
25
|
-
} | {
|
|
57
|
+
} & TexAnno) | ({
|
|
26
58
|
t: 2 | 5;
|
|
27
59
|
color: number;
|
|
28
|
-
p: number[];
|
|
29
|
-
|
|
60
|
+
p: number[]; /** source line, only with `parse(..., { geometryText: true })` (then `p` is empty) */
|
|
61
|
+
src?: string;
|
|
62
|
+
} & TexAnno) | ({
|
|
30
63
|
t: 3 | 4;
|
|
31
64
|
color: number;
|
|
32
65
|
p: number[];
|
|
33
|
-
|
|
66
|
+
src?: string;
|
|
67
|
+
} & TexAnno);
|
|
34
68
|
export interface LFile {
|
|
35
69
|
name: string;
|
|
36
70
|
cmds: Cmd[];
|
|
37
|
-
movedTo?: string;
|
|
71
|
+
movedTo?: string; /** Every TEXMAP START/NEXT in this file, in order. */
|
|
72
|
+
texmaps?: TexMap[];
|
|
73
|
+
/** File type from the header (`0 !LDRAW_ORG <type>`, or legacy `0 LDRAW_ORG` / `0 Unofficial <type>` / `0 Official LCAD <type>`), lowercased
|
|
74
|
+
* with any `unofficial_` prefix removed, e.g. "part", "subpart", "primitive", "shortcut", "model". Only header lines before the file's
|
|
75
|
+
* first type 1-5 line count, and only known types are recorded. */
|
|
76
|
+
org?: string;
|
|
77
|
+
}
|
|
78
|
+
/** An MPD `0 !DATA` block (base64-decoded), e.g. an embedded PNG texture. */
|
|
79
|
+
export interface DataBlock {
|
|
80
|
+
name: string;
|
|
81
|
+
bytes: Uint8Array; /** "image/png" when the bytes carry the PNG signature. */
|
|
82
|
+
mime?: string;
|
|
38
83
|
}
|
|
39
84
|
export interface Doc {
|
|
40
85
|
main: string;
|
|
41
|
-
files: Map<string, LFile>;
|
|
86
|
+
files: Map<string, LFile>; /** MPD `!DATA` blocks keyed by normalized name. */
|
|
87
|
+
data?: Map<string, DataBlock>;
|
|
42
88
|
}
|
|
43
89
|
export declare const norm: (s: string) => string;
|
|
44
90
|
/** Parse LDraw color token: decimal code, or direct color 0x2RRGGBB (and related 0xN…… forms). */
|
|
45
91
|
export declare function parseColor(tok: string): number;
|
|
46
|
-
|
|
92
|
+
/** Parse the tail of `0 !TEXMAP START|NEXT <method> <params> <png> [GLOSSMAP <png>]`. Returns null when malformed. */
|
|
93
|
+
export declare function parseTexMap(tail: string, next?: boolean): TexMap | null;
|
|
94
|
+
/** `geometryText`: keep type 2-5 lines as their source text (`src`) with empty `p` instead of parsing their numbers (for tools that
|
|
95
|
+
* only walk references and pass geometry through as text, e.g. the node pack builder; ~3x faster on geometry-heavy files). */
|
|
96
|
+
export interface ParseOptions {
|
|
97
|
+
geometryText?: boolean;
|
|
98
|
+
}
|
|
99
|
+
export declare function parse(src: string, name?: string, popts?: ParseOptions): Doc;
|
|
100
|
+
/** Find an embedded texture in a parsed MPD: tries `name`, then without / with a `textures/` prefix (case-insensitive). */
|
|
101
|
+
export declare function resolveTexture(doc: Doc, name: string): DataBlock | null;
|
|
102
|
+
/**
|
|
103
|
+
* Texture coordinates of point `p` (same space as the map's points) per the LDraw !TEXMAP spec, with the sign and orientation
|
|
104
|
+
* conventions of LDView (the reference renderer the library's textured parts are checked against; the spec leaves them open).
|
|
105
|
+
* v = 0 is the image's top row for all three methods.
|
|
106
|
+
* PLANAR: u = distance from plane P1 (through p1, normal p1->p2) / |p2 - p1|, v likewise along p1->p3 (p1 = top-left corner).
|
|
107
|
+
* CYLINDRICAL: p1 bottom centre, p2 top centre, p3 on the bottom rim under the texture's centre. u = 0.5 + angle / a, the angle
|
|
108
|
+
* from p3's radial direction, right-handed about p1->p2; v = distance below the top plane / |p2 - p1| (0 at the top, 1 at the bottom;
|
|
109
|
+
* LDView 4.6 "cylindrical TEXMAP right side up").
|
|
110
|
+
* SPHERICAL: p1 centre, p2 = texture centre on the sphere, p3 fixes plane P1 (the texture's horizontal bisector). Its normal
|
|
111
|
+
* n = -((p1 - p2) x (p3 - p2)) is "north". u = 0.5 + longitude / a (angle within P1 from p1->p2, right-handed about n);
|
|
112
|
+
* v = 0.5 + latitude / b with latitude negative toward n, so north maps to the image's top. (LDView measures latitude after
|
|
113
|
+
* rotating the point to longitude 0, i.e. equirectangular, rather than the literal "projected to plane P2"; the official
|
|
114
|
+
* globe textures 61287p01..p04 are drawn for that.)
|
|
115
|
+
* `inside` is false when (u, v) falls outside [0, 1] (the spec leaves those points untextured: base colour shows).
|
|
116
|
+
* Use `texmapTriangleUV` for triangles: it unwraps angles across the seam and handles points on the axis / poles like LDView.
|
|
117
|
+
*/
|
|
118
|
+
export declare function texmapUV(map: TexMap, p: number[]): {
|
|
119
|
+
u: number;
|
|
120
|
+
v: number;
|
|
121
|
+
inside: boolean;
|
|
122
|
+
};
|
|
123
|
+
/** (u, v) for the 3 corners of a triangle, flat [u0, v0, u1, v1, u2, v2]. Angular u is unwrapped relative to the first corner
|
|
124
|
+
* (so a triangle across the seam of a 360-degree map stays continuous); corners on the axis (cylindrical) or at a pole
|
|
125
|
+
* (spherical) take the average u of the other corners. */
|
|
126
|
+
export declare function texmapTriangleUV(map: TexMap, pts: number[][]): number[];
|
|
127
|
+
/** Transform a TexMap's points by an LDraw matrix (e.g. into part space when the START lives in a subfile). */
|
|
128
|
+
export declare function transformTexMap(map: TexMap, m: M): TexMap;
|
|
47
129
|
export declare function mul(a: M, b: M): M;
|
|
48
130
|
export declare const I: M;
|
|
49
131
|
/** True if color is an LDraw direct color (0x2RRGGBB etc.), not a palette CODE. */
|
|
@@ -55,7 +137,13 @@ export interface PartMesh {
|
|
|
55
137
|
edges: Float32Array;
|
|
56
138
|
cond: Float32Array;
|
|
57
139
|
nrm: Float32Array;
|
|
140
|
+
/** Only with `mesh(name, { texmap: true })`: per-vertex (u, v) from the active !TEXMAP (NaN where untextured). */
|
|
141
|
+
uv?: Float32Array;
|
|
142
|
+
/** Only with texmap: per-triangle index into `textures` (-1 = untextured). */
|
|
143
|
+
texture?: Int32Array;
|
|
144
|
+
textures?: string[];
|
|
58
145
|
}
|
|
146
|
+
/** A placed part from `Library.flatten` (same fields as `PartInstance`, plus library resolution). */
|
|
59
147
|
export interface Instance {
|
|
60
148
|
part: string;
|
|
61
149
|
color: number;
|
|
@@ -66,8 +154,77 @@ export interface Instance {
|
|
|
66
154
|
path: string;
|
|
67
155
|
missing: boolean;
|
|
68
156
|
aliasOf?: string;
|
|
157
|
+
embedded?: boolean;
|
|
158
|
+
source?: (() => string) & {
|
|
159
|
+
file?: LFile;
|
|
160
|
+
step?: number;
|
|
161
|
+
};
|
|
162
|
+
texPart?: string;
|
|
163
|
+
texSource?: string;
|
|
164
|
+
view?: StepView;
|
|
69
165
|
}
|
|
70
166
|
/** Smooth normals that respect LDraw semantics: faces sharing an edge are smoothed together unless the
|
|
71
167
|
* edge is a type-2 line (hard). Conditional (type-5) lines force smooth; unmarked edges smooth only below creaseDeg.
|
|
72
168
|
* `cond` may be stride-6 (legacy endpoints only) or stride-12 (endpoints + 2 control points). */
|
|
73
169
|
export declare function smoothNormals(pos: number[], edges: number[], cond: number[], creaseDeg?: number): Float32Array;
|
|
170
|
+
export type FileKind = "part" | "model";
|
|
171
|
+
/**
|
|
172
|
+
* Is a file of an MPD document a part (render its geometry as one unit) or a model (walk its type-1 lines into instances)?
|
|
173
|
+
* The MPD spec (ldraw.org article 47) makes no distinction, so this follows the file headers, then the content:
|
|
174
|
+
* 1. A header type decides (`0 !LDRAW_ORG <type>`, legacy `0 LDRAW_ORG`, `0 Unofficial <type>`, `0 Official LCAD <type>`;
|
|
175
|
+
* an `Unofficial_` prefix is ignored): Part, Subpart, Primitive, 8_Primitive, 48_Primitive, Shortcut, Helper -> part;
|
|
176
|
+
* Model, Submodel, Configuration -> model.
|
|
177
|
+
* 2. Without a header, the main (first) file is a model, and any other file is a part iff it has its own geometry
|
|
178
|
+
* (a type 2-5 line, or a `0 !:` TEXMAP geometry line) or references a subpart / low-res primitive (`s/`, `8/`, `48/`).
|
|
179
|
+
* Everything else (only type-1 references) is a model, as before.
|
|
180
|
+
* Files not in the document are library files (parts). A model's own type 2-5 lines are rendered too, as synthetic
|
|
181
|
+
* parts (see `looseSource`).
|
|
182
|
+
*/
|
|
183
|
+
export declare function fileKind(doc: Doc, name: string): FileKind;
|
|
184
|
+
/** Local steps (as counted by `flatten`) in which a file has its own type 2-5 lines or `0 !:` geometry, ascending. */
|
|
185
|
+
export declare function looseSteps(f: LFile): number[];
|
|
186
|
+
/** LDraw colour token for a colour code: decimal, or `0x2RRGGBB` for direct colours. */
|
|
187
|
+
export declare const colorToken: (c: number) => string;
|
|
188
|
+
/** Name of the synthetic part holding a model file's own geometry in local step `step`: `<file>#<step>`. */
|
|
189
|
+
export declare const loosePartName: (file: string, step: number) => string;
|
|
190
|
+
/**
|
|
191
|
+
* The synthetic part for a model file's own type 2-5 lines in local step `step`, as a file: every `0 BFC` line of the file
|
|
192
|
+
* (winding / certification state, in order), the step's `!TEXMAP` and `0 !:` lines, and the step's type 2-5 lines. Type-1 lines
|
|
193
|
+
* are dropped (they are instances), together with a `!TEXMAP NEXT` that applied to one. Commands are shared with `f`.
|
|
194
|
+
*/
|
|
195
|
+
export declare function looseFile(f: LFile, step: number): LFile;
|
|
196
|
+
/** LDraw source text of `looseFile(f, step)`. */
|
|
197
|
+
export declare function looseSource(f: LFile, step: number): string;
|
|
198
|
+
/** FNV-1a 32-bit, hex. */
|
|
199
|
+
export declare function hash32(s: string): string;
|
|
200
|
+
/** Inverse of an affine LDraw matrix, or null when singular. */
|
|
201
|
+
export declare function invert(m: M): M | null;
|
|
202
|
+
/**
|
|
203
|
+
* Synthetic part for a part reference wrapped by a model-level `!TEXMAP` (the map is defined in the model, not in the part file).
|
|
204
|
+
* `map` must already be in the part's own coordinates. The wrapper is a certified file that applies the map to the part, so
|
|
205
|
+
* TEXMAP-aware compilers texture it exactly as if the START were written in the part, and plain compilers produce the part itself.
|
|
206
|
+
* Name: `<part>#tex-<hash of the source>` (identical maps on the same part share one synthetic part).
|
|
207
|
+
*/
|
|
208
|
+
export declare function texWrapper(part: string, map: TexMap): {
|
|
209
|
+
name: string;
|
|
210
|
+
source: string;
|
|
211
|
+
};
|
|
212
|
+
/** View rotation for a build step from MLCad `0 ROTSTEP` (MLCad LDraw command extensions 2.0, §6.1).
|
|
213
|
+
* `abs` false: view = <default view matrix> x `m`; true: view = `m`. `m` is 3x3 row-major. `rotstep` is the command that set it. */
|
|
214
|
+
export interface StepView {
|
|
215
|
+
abs: boolean;
|
|
216
|
+
m: number[];
|
|
217
|
+
rotstep: RotStep;
|
|
218
|
+
}
|
|
219
|
+
/** ROTSTEP rotation matrix for angles in degrees, MLCad 2.0 §6.1.5 (= Rx * Ry * Rz), 3x3 row-major [a b c d e f g h i]. */
|
|
220
|
+
export declare function rotStepMatrix(x: number, y: number, z: number): number[];
|
|
221
|
+
/** The view matrix for a step (3x3 row-major): REL/ADD-from-default = `defaultView` x m, ABS = m, no ROTSTEP / END = `defaultView`. */
|
|
222
|
+
export declare function stepViewMatrix(view: StepView | null | undefined, defaultView?: number[]): number[];
|
|
223
|
+
/**
|
|
224
|
+
* View per local step of a file (index = step number as counted by `flatten`; null = default view). MLCad semantics: STEP and
|
|
225
|
+
* ROTSTEP both end a step; a ROTSTEP sets the view of the step it ends (MLCad 2.0 §6.1: "drawing stops after executing the
|
|
226
|
+
* command"; put a plain STEP right before it to show the rotation on its own) and of every later step until the next ROTSTEP.
|
|
227
|
+
* REL: default x R; ADD: current x R (from the default view if none); ABS: R; END: back to the default view. Each file starts
|
|
228
|
+
* at the default view (submodel steps are viewed on their own, as LPub3D does).
|
|
229
|
+
*/
|
|
230
|
+
export declare function fileStepViews(f: LFile): (StepView | null)[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|