@aethervtt/shard-terrain 0.0.1

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/biomes.d.ts +65 -0
  4. package/dist/biomes.js +163 -0
  5. package/dist/chunk.d.ts +99 -0
  6. package/dist/chunk.js +304 -0
  7. package/dist/colliders.d.ts +86 -0
  8. package/dist/colliders.js +356 -0
  9. package/dist/components.d.ts +49 -0
  10. package/dist/components.js +137 -0
  11. package/dist/cube.d.ts +51 -0
  12. package/dist/cube.js +87 -0
  13. package/dist/frame.d.ts +21 -0
  14. package/dist/frame.js +63 -0
  15. package/dist/heights.d.ts +51 -0
  16. package/dist/heights.js +137 -0
  17. package/dist/index.d.ts +19 -0
  18. package/dist/index.js +193 -0
  19. package/dist/kernel.d.ts +47 -0
  20. package/dist/kernel.js +243 -0
  21. package/dist/lod.d.ts +22 -0
  22. package/dist/lod.js +44 -0
  23. package/dist/material.d.ts +14 -0
  24. package/dist/material.js +426 -0
  25. package/dist/methods.d.ts +12 -0
  26. package/dist/methods.js +259 -0
  27. package/dist/nav.d.ts +14 -0
  28. package/dist/nav.js +114 -0
  29. package/dist/overlays.d.ts +6 -0
  30. package/dist/overlays.js +67 -0
  31. package/dist/planet.d.ts +106 -0
  32. package/dist/planet.js +313 -0
  33. package/dist/plugin.d.ts +23 -0
  34. package/dist/plugin.js +98 -0
  35. package/dist/points.d.ts +62 -0
  36. package/dist/points.js +207 -0
  37. package/dist/quadtree.d.ts +153 -0
  38. package/dist/quadtree.js +587 -0
  39. package/dist/render.d.ts +179 -0
  40. package/dist/render.js +1222 -0
  41. package/dist/test-planet.d.ts +59 -0
  42. package/dist/test-planet.js +189 -0
  43. package/dist/testing.d.ts +42 -0
  44. package/dist/testing.js +96 -0
  45. package/dist/view.d.ts +12 -0
  46. package/dist/view.js +61 -0
  47. package/package.json +50 -0
package/dist/kernel.js ADDED
@@ -0,0 +1,243 @@
1
+ import {
2
+ generateWgsl,
3
+ registerNoiseLibrary
4
+ } from "@aethervtt/shard-noise";
5
+ const nodeGraphs = /* @__PURE__ */ new WeakMap();
6
+ function kernelGraph(graph, node) {
7
+ if (!node) {
8
+ return {
9
+ module: graph.module,
10
+ name: graph.name,
11
+ wgsl: graph.wgsl,
12
+ program: graph.program,
13
+ hash: graph.hash
14
+ };
15
+ }
16
+ let byNode = nodeGraphs.get(graph);
17
+ if (!byNode) {
18
+ byNode = /* @__PURE__ */ new Map();
19
+ nodeGraphs.set(graph, byNode);
20
+ }
21
+ const cached = byNode.get(node);
22
+ if (cached && cached.hash === `${graph.hash}:${node}`) return cached;
23
+ const name = `${graph.name}_${node}`;
24
+ const program = graph.programFor(node);
25
+ const out = {
26
+ module: `${graph.module}_${node}`,
27
+ name,
28
+ wgsl: generateWgsl(program, { name }),
29
+ program,
30
+ hash: `${graph.hash}:${node}`
31
+ };
32
+ byNode.set(node, out);
33
+ return out;
34
+ }
35
+ const POINT_FLOATS = 10;
36
+ const PARAMS_BYTES = 64;
37
+ const GEN_HEIGHT = 1;
38
+ const GEN_CLIMATE = 2;
39
+ const GEN_ROOT = 4;
40
+ const GEN_OCEAN = 8;
41
+ const PARAMS = `
42
+ struct GenParams {
43
+ count: u32,
44
+ side: u32,
45
+ n: u32,
46
+ surface: u32,
47
+ ring: u32,
48
+ seed: u32,
49
+ flags: u32,
50
+ /** The chunk's slot: its normal tile, packed into uv1.x (see packGrid). */
51
+ tile: u32,
52
+ /** Ellipsoid axis ratios (xyz), metres per height unit (w). */
53
+ shape: vec4f,
54
+ /** x: surface offset (m, the ocean's sea level), y: morph error, z: skirt depth. */
55
+ misc: vec4f,
56
+ }
57
+
58
+ @group(0) @binding(0) var<uniform> params: GenParams;
59
+ @group(0) @binding(1) var<storage, read> pts: array<f32>;
60
+ `;
61
+ function sampleKernel(height, temperature, moisture) {
62
+ const imports = [];
63
+ const bindings = [];
64
+ const body = [];
65
+ const add = (g, binding, target) => {
66
+ if (!g) return;
67
+ imports.push(`import ${g.module}::{ noise_${g.name}_at, NoiseOrigins_${g.name} };`);
68
+ bindings.push(
69
+ `@group(0) @binding(${binding}) var<storage, read> origins_${target}: array<NoiseOrigins_${g.name}>;`
70
+ );
71
+ body.push(
72
+ ` { var o = origins_${target}[g]; ${target} = noise_${g.name}_at(&o, local, params.seed); }`
73
+ );
74
+ };
75
+ add(height, 3, "h");
76
+ add(temperature, 4, "t");
77
+ add(moisture, 5, "m");
78
+ return `${imports.join("\n")}
79
+ ${PARAMS}
80
+ @group(0) @binding(2) var<storage, read_write> values: array<f32>;
81
+ ${bindings.join("\n")}
82
+
83
+ @compute @workgroup_size(64)
84
+ fn main(@builtin(global_invocation_id) id: vec3u) {
85
+ let k = id.x;
86
+ if (k >= params.count) { return; }
87
+ let p = k * ${POINT_FLOATS}u;
88
+ let local = vec3f(pts[p], pts[p + 1u], pts[p + 2u]);
89
+ let g = bitcast<u32>(pts[p + 9u]);
90
+ var h = 0.0;
91
+ var t = 0.0;
92
+ var m = 0.0;
93
+ ${body.join("\n")}
94
+ values[k * 3u] = h;
95
+ values[k * 3u + 1u] = t;
96
+ values[k * 3u + 2u] = m;
97
+ }
98
+ `;
99
+ }
100
+ const VERTEX_KERNEL = `${PARAMS}
101
+ @group(0) @binding(2) var<storage, read> values: array<f32>;
102
+ @group(0) @binding(3) var<storage, read_write> positions: array<f32>;
103
+ @group(0) @binding(4) var<storage, read_write> normals: array<f32>;
104
+ @group(0) @binding(5) var<storage, read_write> uvs: array<f32>;
105
+ @group(0) @binding(6) var<storage, read_write> uvs1: array<f32>;
106
+ @group(0) @binding(7) var<storage, read_write> tangents: array<f32>;
107
+ /** Min height, max height (mm), largest morph delta (mm). */
108
+ @group(0) @binding(8) var<storage, read_write> stats: array<atomic<i32>, 4>;
109
+
110
+ /** Vertex index of grid point (i, j): the border ring first (counter-clockwise), then rows. */
111
+ fn vertex_index(i: u32, j: u32, n: u32) -> u32 {
112
+ let s = n - 1u;
113
+ if (j == 0u) { return i; }
114
+ if (i == s) { return s + j; }
115
+ if (j == s) { return 2u * s + (s - i); }
116
+ if (i == 0u) { return 3u * s + (s - j); }
117
+ return 4u * s + (j - 1u) * (n - 2u) + (i - 1u);
118
+ }
119
+
120
+ fn terrain_height(k: u32) -> f32 {
121
+ return values[k * 3u] * params.shape.w;
122
+ }
123
+
124
+ fn surface_height(k: u32) -> f32 {
125
+ if ((params.flags & ${GEN_OCEAN}u) != 0u) { return params.misc.x; }
126
+ return terrain_height(k) + params.misc.x;
127
+ }
128
+
129
+ fn point_position(k: u32) -> vec3f {
130
+ let p = k * ${POINT_FLOATS}u;
131
+ let q = vec3f(pts[p + 3u], pts[p + 4u], pts[p + 5u]);
132
+ let d = vec3f(pts[p + 6u], pts[p + 7u], pts[p + 8u]);
133
+ return q + params.shape.xyz * d * surface_height(k);
134
+ }
135
+
136
+ fn write_vertex(v: u32, p: vec3f, n: vec3f, climate: vec2f, lock: vec2f, t: vec4f) {
137
+ positions[v * 3u] = p.x;
138
+ positions[v * 3u + 1u] = p.y;
139
+ positions[v * 3u + 2u] = p.z;
140
+ normals[v * 3u] = n.x;
141
+ normals[v * 3u + 1u] = n.y;
142
+ normals[v * 3u + 2u] = n.z;
143
+ uvs[v * 2u] = climate.x;
144
+ uvs[v * 2u + 1u] = climate.y;
145
+ uvs1[v * 2u] = lock.x;
146
+ uvs1[v * 2u + 1u] = lock.y;
147
+ tangents[v * 4u] = t.x;
148
+ tangents[v * 4u + 1u] = t.y;
149
+ tangents[v * 4u + 2u] = t.z;
150
+ tangents[v * 4u + 3u] = t.w;
151
+ }
152
+
153
+ @compute @workgroup_size(64)
154
+ fn main(@builtin(global_invocation_id) id: vec3u) {
155
+ let v = id.x;
156
+ if (v >= params.surface) { return; }
157
+ let n = params.n;
158
+ let b = params.side;
159
+ let i = v % n;
160
+ let j = v / n;
161
+ let k = (i + 1u) + (j + 1u) * b;
162
+ let p = point_position(k);
163
+ let normal = normalize(cross(point_position(k + 1u) - point_position(k - 1u), point_position(k + b) - point_position(k - b)));
164
+ var delta = vec3f(0.0);
165
+ let oi = (i & 1u) == 1u;
166
+ let oj = (j & 1u) == 1u;
167
+ if ((oi || oj) && (params.flags & ${GEN_ROOT}u) == 0u) {
168
+ let ka = select(k, k - 1u, oi) - select(0u, b, oj);
169
+ let kb = select(k, k + 1u, oi) + select(0u, b, oj);
170
+ delta = (point_position(ka) + point_position(kb)) * 0.5 - p;
171
+ atomicMax(&stats[2], i32(ceil(length(delta) * 1000.0)));
172
+ }
173
+ let h = terrain_height(k);
174
+ let surface = surface_height(k);
175
+ var w = h;
176
+ if ((params.flags & ${GEN_OCEAN}u) != 0u) { w = params.misc.x - h; }
177
+ let climate = vec2f(values[k * 3u + 1u], values[k * 3u + 2u]);
178
+ let vi = vertex_index(i, j, n);
179
+ let lock = vec2f(f32(i + j * n + n * n * params.tile), params.misc.y);
180
+ let tangent = vec4f(delta, w);
181
+ write_vertex(vi, p, normal, climate, lock, tangent);
182
+ if (vi < params.ring) {
183
+ let q = k * ${POINT_FLOATS}u;
184
+ let down = normalize(params.shape.xyz * vec3f(pts[q + 6u], pts[q + 7u], pts[q + 8u]));
185
+ write_vertex(params.surface + vi, p - down * params.misc.z, normal, climate, lock, tangent);
186
+ }
187
+ atomicMin(&stats[0], i32(floor(surface * 1000.0)));
188
+ atomicMax(&stats[1], i32(ceil(surface * 1000.0)));
189
+ }
190
+ `;
191
+ const NORMAL_KERNEL = `${PARAMS}
192
+ @group(0) @binding(2) var<storage, read> values: array<f32>;
193
+ @group(0) @binding(3) var tile_out: texture_storage_2d<rgba8unorm, write>;
194
+
195
+ fn point_position(k: u32) -> vec3f {
196
+ let p = k * ${POINT_FLOATS}u;
197
+ let q = vec3f(pts[p + 3u], pts[p + 4u], pts[p + 5u]);
198
+ let d = vec3f(pts[p + 6u], pts[p + 7u], pts[p + 8u]);
199
+ return q + params.shape.xyz * d * (values[k * 3u] * params.shape.w);
200
+ }
201
+
202
+ @compute @workgroup_size(64)
203
+ fn main(@builtin(global_invocation_id) id: vec3u) {
204
+ let t = params.n;
205
+ if (id.x >= t * t) { return; }
206
+ let b = params.side;
207
+ let i = id.x % t;
208
+ let j = id.x / t;
209
+ let k = (i + 1u) + (j + 1u) * b;
210
+ let normal = normalize(cross(point_position(k + 1u) - point_position(k - 1u), point_position(k + b) - point_position(k - b)));
211
+ // The tile's corner in its layer: surface (texel x) and ring (texel y) carry it.
212
+ textureStore(tile_out, vec2u(params.surface + i, params.ring + j), vec4f(normal * 0.5 + 0.5, 1.0));
213
+ }
214
+ `;
215
+ function packGrid(i, j, n, tile) {
216
+ return i + j * n + n * n * tile;
217
+ }
218
+ function registerKernel(library, key, height, temperature, moisture) {
219
+ registerNoiseLibrary(library);
220
+ for (const g of [height, temperature, moisture])
221
+ if (g) library.register(g.module, g.wgsl, g.module);
222
+ const sample = `terrain::gen::k${key}::sample`;
223
+ const vertices = "terrain::gen::vertices";
224
+ const normals = "terrain::gen::normals";
225
+ library.register(sample, sampleKernel(height, temperature, moisture), "terrain chunk sampling");
226
+ if (!library.has(vertices)) library.register(vertices, VERTEX_KERNEL, "terrain chunk vertices");
227
+ if (!library.has(normals)) library.register(normals, NORMAL_KERNEL, "terrain normal tiles");
228
+ return { sample, vertices, normals };
229
+ }
230
+ export {
231
+ GEN_CLIMATE,
232
+ GEN_HEIGHT,
233
+ GEN_OCEAN,
234
+ GEN_ROOT,
235
+ NORMAL_KERNEL,
236
+ PARAMS_BYTES,
237
+ POINT_FLOATS,
238
+ VERTEX_KERNEL,
239
+ kernelGraph,
240
+ packGrid,
241
+ registerKernel,
242
+ sampleKernel
243
+ };
package/dist/lod.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { NoiseGraph } from '@aethervtt/shard-noise';
2
+ export interface ErrorSpec {
3
+ radius: number;
4
+ shape: ArrayLike<number>;
5
+ heightScale: number;
6
+ seed: number;
7
+ resolution: number;
8
+ maxDepth: number;
9
+ height: NoiseGraph | undefined;
10
+ heightOffset?: number;
11
+ }
12
+ /**
13
+ * Each depth's geometric error (m): the largest distance between a vertex and where its parent
14
+ * level puts it, over sample chunks spread across the six faces, with some headroom. Depth 0 has no
15
+ * parent and gets depth 1's doubled. Errors never grow with depth, so split distances shrink with
16
+ * it. Deterministic for a (graph, seed, radius), and cheap enough to redo when the graph changes.
17
+ *
18
+ * Selection splits a depth-d node when `K × errors[d + 1] / distance` passes the planet's
19
+ * `errorPixels`; every chunk of depth d morphs over the band set by `errors[d]`. One error per depth
20
+ * (not per node) keeps neighbors at one depth in the same band, so their shared edges morph alike.
21
+ */
22
+ export declare function measureErrors(spec: ErrorSpec): Float32Array;
package/dist/lod.js ADDED
@@ -0,0 +1,44 @@
1
+ import { hash32 } from "@aethervtt/shard-core";
2
+ import { buildChunk } from "./chunk.js";
3
+ const SAMPLES_PER_DEPTH = 24;
4
+ const SAFETY = 1.25;
5
+ function measureErrors(spec) {
6
+ const out = new Float32Array(spec.maxDepth + 2);
7
+ for (let d = 1; d <= spec.maxDepth; d++) {
8
+ const n = 2 ** d;
9
+ let worst = 0;
10
+ for (let s = 0; s < SAMPLES_PER_DEPTH; s++) {
11
+ const face = s % 6;
12
+ const h = hash32(spec.seed ^ 8262049, d, s);
13
+ const x = h % n;
14
+ const y = hash32(h, d, s, 1) % n;
15
+ const chunk = buildChunk({
16
+ face,
17
+ depth: d,
18
+ x,
19
+ y,
20
+ radius: spec.radius,
21
+ shape: spec.shape,
22
+ heightScale: spec.heightScale,
23
+ seed: spec.seed,
24
+ resolution: spec.resolution,
25
+ height: spec.height,
26
+ climate: void 0,
27
+ morphError: 0,
28
+ skirtDepth: 0,
29
+ // Statistics only: coarse origins keep it quick (every vertex its own batch otherwise).
30
+ snap: 8192,
31
+ ...spec.heightOffset !== void 0 ? { heightOffset: spec.heightOffset } : {}
32
+ });
33
+ if (chunk.error > worst) worst = chunk.error;
34
+ }
35
+ out[d] = worst * SAFETY;
36
+ }
37
+ out[0] = out[1] * 2;
38
+ out[spec.maxDepth + 1] = 0;
39
+ for (let d = spec.maxDepth - 1; d >= 0; d--) out[d] = Math.max(out[d], out[d + 1]);
40
+ return out;
41
+ }
42
+ export {
43
+ measureErrors
44
+ };
@@ -0,0 +1,14 @@
1
+ /** Texture repeats line up every this many metres of planet space (layer scales divide it). */
2
+ export declare const TEXTURE_PERIOD = 1024;
3
+ /**
4
+ * The planet surface material (spec 0043), extending standard. Its vertex stage geomorphs each
5
+ * chunk toward its parent level by distance (or by the edge locks toward coarser and finer
6
+ * neighbors); its surface stage picks the four heaviest biomes per fragment from temperature,
7
+ * moisture, height, slope, and latitude, and samples their layers from the three texture arrays,
8
+ * triplanar in planet space (one top-down sample per biome far away). A project can replace the
9
+ * shader with its own `pbr_input` on a type that extends this one's fields.
10
+ */
11
+ export declare const PlanetMaterial: import("@aethervtt/shard-render").MaterialType;
12
+ /** The sea surface material: transparent, wave normals, absorption by water depth, Fresnel. */
13
+ export declare const OceanMaterial: import("@aethervtt/shard-render").MaterialType;
14
+ export declare const TERRAIN_SHADERS: Record<string, string>;