@aethervtt/shard-mesh 0.0.1 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export { MeshBuilder, perpendicular } from './builder.js';
1
2
  export { decodeMesh, encodeMesh } from './codec.js';
2
3
  export { type GpuMeshDescriptor, MAX_MORPH_TARGETS, MESH_ATTRIBUTE_WIDTH, MESH_ATTRIBUTES, Mesh, type MeshAttribute, type MeshData, type MorphTarget, } from './mesh.js';
3
- export { box, capsule, cone, cube, cylinder, plane, sphere, torus } from './primitives.js';
4
+ export { bevelBox, box, capsule, cone, cube, cylinder, plane, sphere, torus, } from './primitives.js';
5
+ export { loadMeshSimplifier, meshSimplifierLoaded, type Simplified, type SimplifyOptions, simplifyLods, simplifyMesh, } from './simplify.js';
6
+ export { type LeafCardOptions, leafCards, type TreeBranch, type TreeSkeleton, type TreeSkeletonOptions, treeSkeleton, tubeAlong, } from './tree.js';
7
+ export { DEG, dcos, dsin, sinCos } from './trig.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { MeshBuilder, perpendicular } from "./builder.js";
1
2
  import { decodeMesh, encodeMesh } from "./codec.js";
2
3
  import {
3
4
  MAX_MORPH_TARGETS,
@@ -5,20 +6,56 @@ import {
5
6
  MESH_ATTRIBUTES,
6
7
  Mesh
7
8
  } from "./mesh.js";
8
- import { box, capsule, cone, cube, cylinder, plane, sphere, torus } from "./primitives.js";
9
+ import {
10
+ bevelBox,
11
+ box,
12
+ capsule,
13
+ cone,
14
+ cube,
15
+ cylinder,
16
+ plane,
17
+ sphere,
18
+ torus
19
+ } from "./primitives.js";
20
+ import {
21
+ loadMeshSimplifier,
22
+ meshSimplifierLoaded,
23
+ simplifyLods,
24
+ simplifyMesh
25
+ } from "./simplify.js";
26
+ import {
27
+ leafCards,
28
+ treeSkeleton,
29
+ tubeAlong
30
+ } from "./tree.js";
31
+ import { DEG, dcos, dsin, sinCos } from "./trig.js";
9
32
  export {
33
+ DEG,
10
34
  MAX_MORPH_TARGETS,
11
35
  MESH_ATTRIBUTES,
12
36
  MESH_ATTRIBUTE_WIDTH,
13
37
  Mesh,
38
+ MeshBuilder,
39
+ bevelBox,
14
40
  box,
15
41
  capsule,
16
42
  cone,
17
43
  cube,
18
44
  cylinder,
45
+ dcos,
19
46
  decodeMesh,
47
+ dsin,
20
48
  encodeMesh,
49
+ leafCards,
50
+ loadMeshSimplifier,
51
+ meshSimplifierLoaded,
52
+ perpendicular,
21
53
  plane,
54
+ simplifyLods,
55
+ simplifyMesh,
56
+ sinCos,
22
57
  sphere,
23
- torus
58
+ torus,
59
+ treeSkeleton,
60
+ tubeAlong
24
61
  };
package/dist/mesh.d.ts CHANGED
@@ -75,6 +75,8 @@ export declare class Mesh {
75
75
  readonly bounds: Float32Array<ArrayBufferLike>;
76
76
  /** Increments on `update`, so GPU copies know to re-upload. */
77
77
  version: number;
78
+ /** The stand-in for a mesh that failed to load (0061): drawn with the missing material. */
79
+ missing: boolean;
78
80
  /**
79
81
  * Set for meshes the GPU writes (`Mesh.gpu`): their vertex buffers are storage buffers with no
80
82
  * CPU copy; `positions` is empty.
package/dist/mesh.js CHANGED
@@ -36,6 +36,8 @@ class Mesh {
36
36
  bounds = aabb.create();
37
37
  /** Increments on `update`, so GPU copies know to re-upload. */
38
38
  version = 0;
39
+ /** The stand-in for a mesh that failed to load (0061): drawn with the missing material. */
40
+ missing = false;
39
41
  /**
40
42
  * Set for meshes the GPU writes (`Mesh.gpu`): their vertex buffers are storage buffers with no
41
43
  * CPU copy; `positions` is empty.
@@ -46,3 +46,13 @@ export declare function torus(options?: {
46
46
  radialSegments?: number;
47
47
  tubularSegments?: number;
48
48
  }): Mesh;
49
+ /**
50
+ * A box with chamfered edges and corners: the stand-in for a mesh that failed to load (0061).
51
+ * `bevel` is the chamfer width, clamped below half the smallest side.
52
+ */
53
+ export declare function bevelBox(options?: {
54
+ x?: number;
55
+ y?: number;
56
+ z?: number;
57
+ bevel?: number;
58
+ }): Mesh;
@@ -229,7 +229,96 @@ function torus(options = {}) {
229
229
  }
230
230
  return b.build();
231
231
  }
232
+ function bevelBox(options = {}) {
233
+ const h = [(options.x ?? 1) / 2, (options.y ?? 1) / 2, (options.z ?? 1) / 2];
234
+ const bevel = Math.min(options.bevel ?? 0.08, Math.min(h[0], h[1], h[2]) * 0.9);
235
+ const inner = h.map((v) => v - bevel);
236
+ const b = new Builder();
237
+ const unit = (v) => {
238
+ const l = Math.hypot(v[0], v[1], v[2]);
239
+ return v.map((c) => c / l);
240
+ };
241
+ const polygon = (points, n) => {
242
+ const [a, c, d] = [points[0], points[1], points[2]];
243
+ const e1 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
244
+ const e2 = [d[0] - a[0], d[1] - a[1], d[2] - a[2]];
245
+ const cross = [
246
+ e1[1] * e2[2] - e1[2] * e2[1],
247
+ e1[2] * e2[0] - e1[0] * e2[2],
248
+ e1[0] * e2[1] - e1[1] * e2[0]
249
+ ];
250
+ const ordered = cross[0] * n[0] + cross[1] * n[1] + cross[2] * n[2] < 0 ? [...points].reverse() : points;
251
+ const axis = n.map((c2) => Math.abs(c2)).indexOf(Math.max(...n.map((c2) => Math.abs(c2))));
252
+ const [ua, va] = axis === 0 ? [2, 1] : axis === 1 ? [0, 2] : [0, 1];
253
+ const ids = ordered.map(
254
+ (p) => b.vertex(p, n, [0.5 + p[ua] / (2 * h[ua]), 0.5 - p[va] / (2 * h[va])])
255
+ );
256
+ for (let i = 1; i + 1 < ids.length; i++) b.triangle(ids[0], ids[i], ids[i + 1]);
257
+ };
258
+ const point = (axis, value) => [0, 1, 2].map((k) => axis[k] * value[k]);
259
+ const signs = [-1, 1];
260
+ for (let a = 0; a < 3; a++) {
261
+ for (const s of signs) {
262
+ const u = (a + 1) % 3;
263
+ const v = (a + 2) % 3;
264
+ const n = [0, 0, 0];
265
+ n[a] = s;
266
+ const corners = [
267
+ [-1, -1],
268
+ [1, -1],
269
+ [1, 1],
270
+ [-1, 1]
271
+ ].map(([su, sv]) => {
272
+ const p = [0, 0, 0];
273
+ p[a] = s * h[a];
274
+ p[u] = su * inner[u];
275
+ p[v] = sv * inner[v];
276
+ return p;
277
+ });
278
+ polygon(corners, n);
279
+ }
280
+ }
281
+ for (let a = 0; a < 3; a++) {
282
+ const u = (a + 1) % 3;
283
+ const v = (a + 2) % 3;
284
+ for (const sa of signs) {
285
+ for (const su of signs) {
286
+ const n = [0, 0, 0];
287
+ n[a] = sa;
288
+ n[u] = su;
289
+ const quad = [-1, 1].flatMap((sv) => {
290
+ const p1 = [0, 0, 0];
291
+ p1[a] = sa * h[a];
292
+ p1[u] = su * inner[u];
293
+ p1[v] = sv * inner[v];
294
+ const p2 = [0, 0, 0];
295
+ p2[a] = sa * inner[a];
296
+ p2[u] = su * h[u];
297
+ p2[v] = sv * inner[v];
298
+ return [p1, p2];
299
+ });
300
+ polygon([quad[0], quad[1], quad[3], quad[2]], unit(n));
301
+ }
302
+ }
303
+ }
304
+ for (const sx of signs) {
305
+ for (const sy of signs) {
306
+ for (const sz of signs) {
307
+ const s = [sx, sy, sz];
308
+ const tri = [0, 1, 2].map(
309
+ (k) => point(
310
+ s,
311
+ k === 0 ? [h[0], inner[1], inner[2]] : k === 1 ? [inner[0], h[1], inner[2]] : [inner[0], inner[1], h[2]]
312
+ )
313
+ );
314
+ polygon(tri, unit(s));
315
+ }
316
+ }
317
+ }
318
+ return b.build();
319
+ }
232
320
  export {
321
+ bevelBox,
233
322
  box,
234
323
  capsule,
235
324
  cone,
@@ -0,0 +1,34 @@
1
+ import type { MeshData } from './mesh.js';
2
+ /**
3
+ * Loads meshoptimizer's simplifier (WASM, embedded in its module). `simplifyMesh` and
4
+ * `simplifyLods` are synchronous and need this to have resolved once in the thread that calls
5
+ * them. Only code that simplifies imports it, so it never reaches a runtime bundle that doesn't.
6
+ */
7
+ export declare function loadMeshSimplifier(): Promise<void>;
8
+ /** Whether `loadMeshSimplifier` has finished in this thread. */
9
+ export declare function meshSimplifierLoaded(): boolean;
10
+ export interface SimplifyOptions {
11
+ /** Keep border edges (open edges) in place, so neighboring pieces still meet (default false). */
12
+ lockBorder?: boolean;
13
+ /**
14
+ * Largest error allowed, as a fraction of the mesh's size (or metres with `absolute`). The
15
+ * default, 1, lets the triangle target decide alone.
16
+ */
17
+ error?: number;
18
+ /** `error` is in mesh units, not relative to its size. */
19
+ absolute?: boolean;
20
+ }
21
+ export interface Simplified {
22
+ mesh: MeshData;
23
+ /** The error reached, in the same units as `SimplifyOptions.error`. */
24
+ error: number;
25
+ }
26
+ /**
27
+ * About `fraction` of the mesh's triangles, chosen by meshoptimizer's quadric simplifier. Vertices
28
+ * aren't moved, only dropped, so UVs, colors, and normals stay the source's; UV seams and borders
29
+ * (with `lockBorder`) are kept. If seams stop it short of the target, it retries allowed to
30
+ * collapse across them, and then as a last resort with the positions-only (sloppy) simplifier.
31
+ */
32
+ export declare function simplifyMesh(mesh: MeshData, fraction: number, options?: SimplifyOptions): Simplified;
33
+ /** One simplified mesh per fraction (each from the source, not from the previous level). */
34
+ export declare function simplifyLods(mesh: MeshData, fractions: readonly number[], options?: SimplifyOptions): MeshData[];
@@ -0,0 +1,140 @@
1
+ import { ShardError } from "@aethervtt/shard-core";
2
+ let simplifier;
3
+ let loading;
4
+ function loadMeshSimplifier() {
5
+ loading ??= import("meshoptimizer/simplifier").then(async (mod) => {
6
+ await mod.MeshoptSimplifier.ready;
7
+ simplifier = mod.MeshoptSimplifier;
8
+ });
9
+ return loading;
10
+ }
11
+ function meshSimplifierLoaded() {
12
+ return simplifier !== void 0;
13
+ }
14
+ function loaded() {
15
+ if (!simplifier) {
16
+ throw new ShardError("mesh/simplifier-not-loaded", "The mesh simplifier is not loaded", {
17
+ hint: "await loadMeshSimplifier() once (generators get it loaded for them) before simplifying."
18
+ });
19
+ }
20
+ return simplifier;
21
+ }
22
+ function simplifyMesh(mesh, fraction, options = {}) {
23
+ const s = loaded();
24
+ if (!(fraction > 0 && fraction <= 1)) {
25
+ throw new ShardError("mesh/bad-lod", `A LOD fraction must be in (0, 1], got ${fraction}`, {
26
+ hint: "lods: [0.5, 0.2] keeps about half, then a fifth, of the triangles."
27
+ });
28
+ }
29
+ const count = mesh.positions.length / 3;
30
+ const source = mesh.indices ? Uint32Array.from(mesh.indices) : sequence(count);
31
+ const triangles = source.length / 3;
32
+ const target = Math.max(1, Math.floor(triangles * fraction)) * 3;
33
+ if (target >= source.length) return { mesh, error: 0 };
34
+ const attrStride = 5;
35
+ const attrs = new Float32Array(count * attrStride);
36
+ for (let v = 0; v < count; v++) {
37
+ if (mesh.normals) {
38
+ attrs[v * 5] = mesh.normals[v * 3];
39
+ attrs[v * 5 + 1] = mesh.normals[v * 3 + 1];
40
+ attrs[v * 5 + 2] = mesh.normals[v * 3 + 2];
41
+ }
42
+ if (mesh.uvs) {
43
+ attrs[v * 5 + 3] = mesh.uvs[v * 2];
44
+ attrs[v * 5 + 4] = mesh.uvs[v * 2 + 1];
45
+ }
46
+ }
47
+ const weights = [0.25, 0.25, 0.25, mesh.uvs ? 0.5 : 0, mesh.uvs ? 0.5 : 0];
48
+ const flags = [];
49
+ if (options.lockBorder) flags.push("LockBorder");
50
+ if (options.absolute) flags.push("ErrorAbsolute");
51
+ const maxError = options.error ?? 1;
52
+ const slack = Math.floor(target / 3 * 1.1) * 3;
53
+ let [indices, error] = s.simplifyWithAttributes(
54
+ source,
55
+ mesh.positions,
56
+ 3,
57
+ attrs,
58
+ attrStride,
59
+ weights,
60
+ null,
61
+ target,
62
+ maxError,
63
+ flags
64
+ );
65
+ if (indices.length > slack && options.error === void 0) {
66
+ ;
67
+ [indices, error] = s.simplifyWithAttributes(
68
+ source,
69
+ mesh.positions,
70
+ 3,
71
+ attrs,
72
+ attrStride,
73
+ weights,
74
+ null,
75
+ target,
76
+ maxError,
77
+ [...flags, "Permissive"]
78
+ );
79
+ }
80
+ if (indices.length > slack && options.error === void 0 && !options.lockBorder) {
81
+ ;
82
+ [indices, error] = s.simplifySloppy(source, mesh.positions, 3, null, target, maxError);
83
+ }
84
+ return { mesh: compact(mesh, indices), error };
85
+ }
86
+ function simplifyLods(mesh, fractions, options = {}) {
87
+ const out = [];
88
+ for (const f of fractions) {
89
+ if (!(f > 0 && f < 1)) {
90
+ throw new ShardError("mesh/bad-lod", `LOD fractions are in (0, 1), got ${f}`, {
91
+ hint: "lods: [0.5, 0.2] keeps about half, then a fifth, of the triangles."
92
+ });
93
+ }
94
+ out.push(simplifyMesh(mesh, f, options).mesh);
95
+ }
96
+ return out;
97
+ }
98
+ function sequence(n) {
99
+ const out = new Uint32Array(n);
100
+ for (let i = 0; i < n; i++) out[i] = i;
101
+ return out;
102
+ }
103
+ function compact(mesh, indices) {
104
+ const count = mesh.positions.length / 3;
105
+ const remap = new Int32Array(count).fill(-1);
106
+ let next = 0;
107
+ for (let k = 0; k < indices.length; k++) {
108
+ const v = indices[k];
109
+ if (remap[v] === -1) remap[v] = next++;
110
+ }
111
+ const keep = new Int32Array(next);
112
+ for (let v = 0; v < count; v++) if (remap[v] >= 0) keep[remap[v]] = v;
113
+ const pick = (src, width) => {
114
+ if (!src) return void 0;
115
+ const out2 = new src.constructor(next * width);
116
+ for (let i = 0; i < next; i++)
117
+ for (let c = 0; c < width; c++) out2[i * width + c] = src[keep[i] * width + c];
118
+ return out2;
119
+ };
120
+ const tri = next > 65535 ? new Uint32Array(indices.length) : new Uint16Array(indices.length);
121
+ for (let k = 0; k < indices.length; k++) tri[k] = remap[indices[k]];
122
+ const out = { positions: pick(mesh.positions, 3), indices: tri };
123
+ const normals = pick(mesh.normals, 3);
124
+ if (normals) out.normals = normals;
125
+ const uvs = pick(mesh.uvs, 2);
126
+ if (uvs) out.uvs = uvs;
127
+ const colors = pick(mesh.colors, 4);
128
+ if (colors) out.colors = colors;
129
+ const tangents = pick(mesh.tangents, 4);
130
+ if (tangents) out.tangents = tangents;
131
+ const uvs1 = pick(mesh.uvs1, 2);
132
+ if (uvs1) out.uvs1 = uvs1;
133
+ return out;
134
+ }
135
+ export {
136
+ loadMeshSimplifier,
137
+ meshSimplifierLoaded,
138
+ simplifyLods,
139
+ simplifyMesh
140
+ };
package/dist/tree.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { type MeshBuilder } from './builder.js';
2
+ export interface TreeSkeletonOptions {
3
+ /** The trunk: length (m), base radius (m), and segments along it. */
4
+ trunk?: {
5
+ length?: number;
6
+ radius?: number;
7
+ segments?: number;
8
+ };
9
+ /** Children per branch (default 4). */
10
+ branches?: number;
11
+ /** Levels of branches below the trunk (default 2; 0 is a bare trunk). */
12
+ levels?: number;
13
+ /** Degrees between a child and its parent (default 45). */
14
+ spread?: number;
15
+ /** A child's length as a fraction of its parent's (default 0.55). */
16
+ lengthScale?: number;
17
+ /** A child's base radius as a fraction of its parent's radius where it starts (default 0.7). */
18
+ radiusScale?: number;
19
+ /** Tip radius as a fraction of the base radius (default 0.35). */
20
+ taper?: number;
21
+ /** Downward bend per metre of branch (default 0.08; negative bends up). */
22
+ gravity?: number;
23
+ /** Random wander of a branch's direction per segment (default 0.15). */
24
+ wobble?: number;
25
+ /** Where along its parent the first child starts, 0 to 1 (default 0.35). */
26
+ start?: number;
27
+ seed: number;
28
+ }
29
+ /** One branch: a polyline with a radius per point. */
30
+ export interface TreeBranch {
31
+ /** 0 is the trunk. */
32
+ level: number;
33
+ /** Index of the parent branch, or −1 for the trunk. */
34
+ parent: number;
35
+ /** xyz per point, base first. */
36
+ points: Float64Array;
37
+ radii: Float64Array;
38
+ }
39
+ export interface TreeSkeleton {
40
+ branches: TreeBranch[];
41
+ /** Highest point (m). */
42
+ height: number;
43
+ }
44
+ /**
45
+ * A recursive branch graph (spec 0045): a trunk up +Y from the origin, each branch sprouting
46
+ * `branches` children along its upper part, bent by `gravity` and a seeded wobble. Deterministic
47
+ * per seed on every host (seeded RNG, trig through `sinCos`).
48
+ */
49
+ export declare function treeSkeleton(options: TreeSkeletonOptions): TreeSkeleton;
50
+ /**
51
+ * Skins a skeleton with tubes (`sides` around each; fewer on deeper levels), appended to `b`.
52
+ * Returns the index of the first vertex added.
53
+ */
54
+ export declare function tubeAlong(b: MeshBuilder, skeleton: TreeSkeleton, options?: {
55
+ sides?: number;
56
+ minSides?: number;
57
+ vScale?: number;
58
+ }): number;
59
+ export interface LeafCardOptions {
60
+ /** Cards per metre of branch (default 6). */
61
+ density?: number;
62
+ /** Card size in metres (default 0.35). */
63
+ size?: number;
64
+ /** Branch levels that carry leaves (default: the deepest level). */
65
+ levels?: readonly number[];
66
+ /**
67
+ * `quad`: a square with UVs into a leaf atlas cell (alpha-tested texture). `leaf`: a pointed
68
+ * six-vertex leaf shape that needs no texture (default).
69
+ */
70
+ shape?: 'quad' | 'leaf';
71
+ /** Atlas columns and rows: each card picks a cell (default [1, 1]). */
72
+ atlas?: readonly [number, number];
73
+ /** How far cards droop, 0 (facing out) to 1 (hanging) (default 0.3). */
74
+ droop?: number;
75
+ seed: number;
76
+ }
77
+ /**
78
+ * Leaf cards along the given branch levels, appended to `b`: each card is two-sided (front and
79
+ * back faces), so it lights correctly with any material. Returns the index of the first vertex.
80
+ */
81
+ export declare function leafCards(b: MeshBuilder, skeleton: TreeSkeleton, options: LeafCardOptions): number;
package/dist/tree.js ADDED
@@ -0,0 +1,211 @@
1
+ import { Rng } from "@aethervtt/shard-core";
2
+ import { perpendicular } from "./builder.js";
3
+ import { DEG, sinCos } from "./trig.js";
4
+ const sc = new Float64Array(2);
5
+ function treeSkeleton(options) {
6
+ const rng = new Rng(options.seed);
7
+ const trunkLength = options.trunk?.length ?? 6;
8
+ const trunkRadius = options.trunk?.radius ?? 0.25;
9
+ const segments = Math.max(2, options.trunk?.segments ?? 8);
10
+ const children = Math.max(0, Math.floor(options.branches ?? 4));
11
+ const levels = Math.max(0, Math.floor(options.levels ?? 2));
12
+ const spread = (options.spread ?? 45) * DEG;
13
+ const lengthScale = options.lengthScale ?? 0.55;
14
+ const radiusScale = options.radiusScale ?? 0.7;
15
+ const taper = options.taper ?? 0.35;
16
+ const gravity = options.gravity ?? 0.08;
17
+ const wobble = options.wobble ?? 0.15;
18
+ const start = options.start ?? 0.35;
19
+ const branches = [];
20
+ let height = 0;
21
+ const grow = (level, parent, ox, oy, oz, dx, dy, dz, length, radius) => {
22
+ const n = Math.max(2, Math.ceil(segments / (level + 1)));
23
+ const points = new Float64Array((n + 1) * 3);
24
+ const radii = new Float64Array(n + 1);
25
+ const step = length / n;
26
+ let x = ox;
27
+ let y = oy;
28
+ let z = oz;
29
+ for (let i = 0; i <= n; i++) {
30
+ points[i * 3] = x;
31
+ points[i * 3 + 1] = y;
32
+ points[i * 3 + 2] = z;
33
+ radii[i] = radius * (1 - (1 - taper) * (i / n));
34
+ if (y > height) height = y;
35
+ if (i === n) break;
36
+ x += dx * step;
37
+ y += dy * step;
38
+ z += dz * step;
39
+ dy -= gravity * step * (level === 0 ? 0.2 : 1);
40
+ dx += (rng.float() - 0.5) * wobble;
41
+ dy += (rng.float() - 0.5) * wobble * 0.5;
42
+ dz += (rng.float() - 0.5) * wobble;
43
+ const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
44
+ dx /= len;
45
+ dy /= len;
46
+ dz /= len;
47
+ }
48
+ const index = branches.length;
49
+ branches.push({ level, parent, points, radii });
50
+ if (level >= levels || children === 0) return;
51
+ const side = new Float64Array(3);
52
+ const other = new Float64Array(3);
53
+ const azimuth0 = rng.float() * Math.PI * 2;
54
+ for (let c = 0; c < children; c++) {
55
+ const t = start + (1 - start) * ((c + 0.3 + rng.float() * 0.4) / children);
56
+ const f = t * n;
57
+ const i = Math.min(n - 1, Math.floor(f));
58
+ const k = f - i;
59
+ const px = points[i * 3] + (points[i * 3 + 3] - points[i * 3]) * k;
60
+ const py = points[i * 3 + 1] + (points[i * 3 + 4] - points[i * 3 + 1]) * k;
61
+ const pz = points[i * 3 + 2] + (points[i * 3 + 5] - points[i * 3 + 2]) * k;
62
+ let tx = points[i * 3 + 3] - points[i * 3];
63
+ let ty = points[i * 3 + 4] - points[i * 3 + 1];
64
+ let tz = points[i * 3 + 5] - points[i * 3 + 2];
65
+ const tl = Math.sqrt(tx * tx + ty * ty + tz * tz) || 1;
66
+ tx /= tl;
67
+ ty /= tl;
68
+ tz /= tl;
69
+ perpendicular(tx, ty, tz, side);
70
+ other[0] = ty * side[2] - tz * side[1];
71
+ other[1] = tz * side[0] - tx * side[2];
72
+ other[2] = tx * side[1] - ty * side[0];
73
+ sinCos(azimuth0 + c * 2.399963229728653 + (rng.float() - 0.5) * 0.5, sc);
74
+ const sx = side[0] * sc[1] + other[0] * sc[0];
75
+ const sy = side[1] * sc[1] + other[1] * sc[0];
76
+ const sz = side[2] * sc[1] + other[2] * sc[0];
77
+ sinCos(spread * (0.8 + rng.float() * 0.4), sc);
78
+ let cx = tx * sc[1] + sx * sc[0];
79
+ let cy = ty * sc[1] + sy * sc[0];
80
+ let cz = tz * sc[1] + sz * sc[0];
81
+ const cl = Math.sqrt(cx * cx + cy * cy + cz * cz) || 1;
82
+ cx /= cl;
83
+ cy /= cl;
84
+ cz /= cl;
85
+ const r = radii[i] + (radii[i + 1] - radii[i]) * k;
86
+ const len = length * lengthScale * (1.15 - 0.4 * t) * (0.85 + rng.float() * 0.3);
87
+ grow(level + 1, index, px, py, pz, cx, cy, cz, len, r * radiusScale);
88
+ }
89
+ };
90
+ grow(0, -1, 0, 0, 0, 0, 1, 0, trunkLength, trunkRadius);
91
+ return { branches, height };
92
+ }
93
+ function tubeAlong(b, skeleton, options = {}) {
94
+ const first = b.vertexCount;
95
+ const sides = options.sides ?? 8;
96
+ const minSides = options.minSides ?? 3;
97
+ for (const branch of skeleton.branches) {
98
+ const s = Math.max(minSides, sides >> branch.level);
99
+ b.tube(branch.points, branch.radii, { sides: s, caps: true, vScale: options.vScale ?? 1 });
100
+ }
101
+ return first;
102
+ }
103
+ function leafCards(b, skeleton, options) {
104
+ const first = b.vertexCount;
105
+ const rng = new Rng(options.seed);
106
+ const deepest = skeleton.branches.reduce((m, br) => Math.max(m, br.level), 0);
107
+ const levels = new Set(options.levels ?? [deepest]);
108
+ const density = options.density ?? 6;
109
+ const size = options.size ?? 0.35;
110
+ const shape = options.shape ?? "leaf";
111
+ const cols = options.atlas?.[0] ?? 1;
112
+ const rows = options.atlas?.[1] ?? 1;
113
+ const droop = options.droop ?? 0.3;
114
+ const side = new Float64Array(3);
115
+ for (const branch of skeleton.branches) {
116
+ if (!levels.has(branch.level)) continue;
117
+ const p = branch.points;
118
+ const n = p.length / 3 - 1;
119
+ for (let i = 0; i < n; i++) {
120
+ const ax = p[i * 3];
121
+ const ay = p[i * 3 + 1];
122
+ const az = p[i * 3 + 2];
123
+ let tx = p[i * 3 + 3] - ax;
124
+ let ty = p[i * 3 + 4] - ay;
125
+ let tz = p[i * 3 + 5] - az;
126
+ const segment = Math.sqrt(tx * tx + ty * ty + tz * tz);
127
+ if (!(segment > 0)) continue;
128
+ tx /= segment;
129
+ ty /= segment;
130
+ tz /= segment;
131
+ const want = segment * density;
132
+ const cards = Math.floor(want) + (rng.float() < want - Math.floor(want) ? 1 : 0);
133
+ for (let c = 0; c < cards; c++) {
134
+ const t = rng.float();
135
+ const ox = ax + tx * segment * t;
136
+ const oy = ay + ty * segment * t;
137
+ const oz = az + tz * segment * t;
138
+ perpendicular(tx, ty, tz, side);
139
+ sinCos(rng.float() * Math.PI * 2, sc);
140
+ const bx = ty * side[2] - tz * side[1];
141
+ const by = tz * side[0] - tx * side[2];
142
+ const bz = tx * side[1] - ty * side[0];
143
+ let ux = side[0] * sc[1] + bx * sc[0] + tx * 0.5;
144
+ let uy = side[1] * sc[1] + by * sc[0] + ty * 0.5 - droop;
145
+ let uz = side[2] * sc[1] + bz * sc[0] + tz * 0.5;
146
+ const ul = Math.sqrt(ux * ux + uy * uy + uz * uz) || 1;
147
+ ux /= ul;
148
+ uy /= ul;
149
+ uz /= ul;
150
+ let wx = uy * 0 - uz * 1;
151
+ let wy = uz * 0 - ux * 0;
152
+ let wz = ux * 1 - uy * 0;
153
+ let wl = Math.sqrt(wx * wx + wy * wy + wz * wz);
154
+ if (wl < 1e-6) {
155
+ wx = 1;
156
+ wy = 0;
157
+ wz = 0;
158
+ wl = 1;
159
+ }
160
+ wx /= wl;
161
+ wy /= wl;
162
+ wz /= wl;
163
+ let nx = wy * uz - wz * uy;
164
+ let ny = wz * ux - wx * uz;
165
+ let nz = wx * uy - wy * ux;
166
+ if (ny < 0) {
167
+ nx = -nx;
168
+ ny = -ny;
169
+ nz = -nz;
170
+ wx = -wx;
171
+ wy = -wy;
172
+ wz = -wz;
173
+ }
174
+ const s = size * (0.75 + rng.float() * 0.5);
175
+ const cell = Math.floor(rng.float() * cols * rows);
176
+ const cu = cell % cols / cols;
177
+ const cv = Math.floor(cell / cols) / rows;
178
+ const outline = shape === "quad" ? [-0.5, 0, 0.5, 0, 0.5, 1, -0.5, 1] : [0, 0, 0.3, 0.3, 0.32, 0.65, 0, 1, -0.32, 0.65, -0.3, 0.3];
179
+ const count = outline.length / 2;
180
+ for (let face = 0; face < 2; face++) {
181
+ const sign = face === 0 ? 1 : -1;
182
+ const base = b.vertexCount;
183
+ for (let k = 0; k < count; k++) {
184
+ const a = outline[k * 2] * s;
185
+ const l = outline[k * 2 + 1] * s;
186
+ b.vertex(
187
+ ox + wx * a + ux * l,
188
+ oy + wy * a + uy * l,
189
+ oz + wz * a + uz * l,
190
+ nx * sign,
191
+ ny * sign,
192
+ nz * sign,
193
+ cu + (outline[k * 2] + 0.5) / cols,
194
+ cv + (1 - outline[k * 2 + 1]) / rows
195
+ );
196
+ }
197
+ for (let k = 1; k < count - 1; k++) {
198
+ if (face === 0) b.triangle(base, base + k, base + k + 1);
199
+ else b.triangle(base, base + k + 1, base + k);
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ return first;
206
+ }
207
+ export {
208
+ leafCards,
209
+ treeSkeleton,
210
+ tubeAlong
211
+ };
package/dist/trig.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Sine and cosine from + and * only, so mesh code gets the same bits in V8, JavaScriptCore, and
3
+ * SpiderMonkey (their `Math.sin` differ in the last place). Accurate to about 1e-15 over any
4
+ * argument a mesh uses.
5
+ */
6
+ /** Writes [sin t, cos t] into `out` (from index `o`). */
7
+ export declare function sinCos(t: number, out: {
8
+ [i: number]: number;
9
+ }, o?: number): void;
10
+ /** Deterministic sine (see `sinCos`). */
11
+ export declare function dsin(t: number): number;
12
+ /** Deterministic cosine (see `sinCos`). */
13
+ export declare function dcos(t: number): number;
14
+ /** Degrees to radians. */
15
+ export declare const DEG: number;