@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/cube.js ADDED
@@ -0,0 +1,87 @@
1
+ import { directionToFace, faceToDirection } from "@aethervtt/shard-noise";
2
+ const MAX_DEPTH = 24;
3
+ const MAX_RADIUS = 5e7;
4
+ const EDGE_BOTTOM = 0;
5
+ const EDGE_RIGHT = 1;
6
+ const EDGE_TOP = 2;
7
+ const EDGE_LEFT = 3;
8
+ function nodeExtent(depth) {
9
+ return 2 / 2 ** depth;
10
+ }
11
+ function nodeSpacing(radius, depth, resolution) {
12
+ return radius * Math.PI / 2 / 2 ** depth / (resolution - 1);
13
+ }
14
+ function maxDepthFor(radius, resolution, minSpacing) {
15
+ let d = 0;
16
+ while (d < MAX_DEPTH && nodeSpacing(radius, d, resolution) > minSpacing) d++;
17
+ return d;
18
+ }
19
+ function packKey(face, depth, x, y, out, offset = 0) {
20
+ out[offset] = (face << 29 | depth << 24 | x) >>> 0;
21
+ out[offset + 1] = y >>> 0;
22
+ return out;
23
+ }
24
+ function unpackKey(k0, k1) {
25
+ return { face: k0 >>> 29, depth: k0 >>> 24 & 31, x: k0 & 16777215, y: k1 >>> 0 };
26
+ }
27
+ function keyString(face, depth, x, y) {
28
+ return `${face}/${depth}/${x}/${y}`;
29
+ }
30
+ function parseKey(key) {
31
+ const parts = key.split("/").map(Number);
32
+ if (parts.length !== 4 || parts.some((p) => !Number.isInteger(p) || p < 0)) return void 0;
33
+ const [face, depth, x, y] = parts;
34
+ if (face > 5 || depth > MAX_DEPTH || x >= 2 ** depth || y >= 2 ** depth) return void 0;
35
+ return { face, depth, x, y };
36
+ }
37
+ const dir = new Float64Array(3);
38
+ const uv = new Float64Array(2);
39
+ function nodeAt(x, y, z, depth, out) {
40
+ const face = directionToFace(x, y, z, uv);
41
+ const n = 2 ** depth;
42
+ out[0] = face;
43
+ out[1] = Math.min(n - 1, Math.max(0, Math.floor((uv[0] + 1) / 2 * n)));
44
+ out[2] = Math.min(n - 1, Math.max(0, Math.floor((uv[1] + 1) / 2 * n)));
45
+ return out;
46
+ }
47
+ function neighborNode(face, depth, x, y, edge, out) {
48
+ const n = 2 ** depth;
49
+ let nx = x;
50
+ let ny = y;
51
+ if (edge === EDGE_BOTTOM) ny--;
52
+ else if (edge === EDGE_RIGHT) nx++;
53
+ else if (edge === EDGE_TOP) ny++;
54
+ else nx--;
55
+ if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
56
+ out[0] = face;
57
+ out[1] = nx;
58
+ out[2] = ny;
59
+ return out;
60
+ }
61
+ const ext = 2 / n;
62
+ const u = -1 + (nx + 0.5) * ext;
63
+ const v = -1 + (ny + 0.5) * ext;
64
+ const pu = edge === EDGE_RIGHT || edge === EDGE_LEFT ? u + (edge === EDGE_RIGHT ? -0.25 : 0.25) * ext : u;
65
+ const pv = edge === EDGE_TOP || edge === EDGE_BOTTOM ? v + (edge === EDGE_TOP ? -0.25 : 0.25) * ext : v;
66
+ faceToDirection(face, pu, pv, dir);
67
+ return nodeAt(dir[0], dir[1], dir[2], depth, out);
68
+ }
69
+ export {
70
+ EDGE_BOTTOM,
71
+ EDGE_LEFT,
72
+ EDGE_RIGHT,
73
+ EDGE_TOP,
74
+ MAX_DEPTH,
75
+ MAX_RADIUS,
76
+ directionToFace,
77
+ faceToDirection,
78
+ keyString,
79
+ maxDepthFor,
80
+ neighborNode,
81
+ nodeAt,
82
+ nodeExtent,
83
+ nodeSpacing,
84
+ packKey,
85
+ parseKey,
86
+ unpackKey
87
+ };
@@ -0,0 +1,21 @@
1
+ import { type Entity, type World } from '@aethervtt/shard-core';
2
+ /**
3
+ * A planet's frame against the floating origin, in f64: planet coordinates are its grid's
4
+ * (`cell × cellSize + offset`), which is where chunks, anchors, and the camera are compared.
5
+ * Taken from the grid frames transform propagation solved this frame (spec 0040).
6
+ */
7
+ export declare class PlanetFrame {
8
+ /** Planet frame → origin frame (affine, 12 f64). */
9
+ readonly toOrigin: Float64Array<ArrayBufferLike>;
10
+ /** Origin frame → planet frame. */
11
+ readonly toPlanet: Float64Array<ArrayBufferLike>;
12
+ update(world: World, planet: Entity): void;
13
+ /** An origin-frame point into the planet frame, at `out[o]`. */
14
+ pointToPlanet(x: number, y: number, z: number, out: Float64Array, o?: number): Float64Array;
15
+ /** An origin-frame direction into the planet frame (rotation only). */
16
+ vectorToPlanet(x: number, y: number, z: number, out: Float64Array, o?: number): Float64Array;
17
+ /** A planet-frame point into the origin frame. */
18
+ pointToOrigin(x: number, y: number, z: number, out: Float64Array | Float32Array, o?: number): Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike>;
19
+ /** A planet-frame direction into the origin frame. */
20
+ vectorToOrigin(x: number, y: number, z: number, out: Float64Array | Float32Array, o?: number): Float32Array<ArrayBufferLike> | Float64Array<ArrayBufferLike>;
21
+ }
package/dist/frame.js ADDED
@@ -0,0 +1,63 @@
1
+ import { affine64 } from "@aethervtt/shard-core";
2
+ import { GlobalTransform, GridFramesResource } from "@aethervtt/shard-transform";
3
+ class PlanetFrame {
4
+ /** Planet frame → origin frame (affine, 12 f64). */
5
+ toOrigin = affine64.create();
6
+ /** Origin frame → planet frame. */
7
+ toPlanet = affine64.create();
8
+ update(world, planet) {
9
+ const frames = world.tryResource(GridFramesResource);
10
+ const slot = frames?.active ? frames.slotOf.get(planet) : void 0;
11
+ if (frames && slot !== void 0 && !Number.isNaN(frames.a[slot * 12])) {
12
+ const cs = frames.cellSize[slot];
13
+ affine64.translateAt(
14
+ this.toOrigin,
15
+ 0,
16
+ frames.a,
17
+ slot * 12,
18
+ -frames.ref[slot * 3] * cs,
19
+ -frames.ref[slot * 3 + 1] * cs,
20
+ -frames.ref[slot * 3 + 2] * cs
21
+ );
22
+ } else if (world.has(planet, GlobalTransform)) {
23
+ const m = world.get(planet, GlobalTransform).matrix;
24
+ for (let i = 0; i < 12; i++) this.toOrigin[i] = m[i];
25
+ } else affine64.identity(this.toOrigin);
26
+ if (!affine64.invert(this.toPlanet, this.toOrigin)) affine64.identity(this.toPlanet);
27
+ }
28
+ /** An origin-frame point into the planet frame, at `out[o]`. */
29
+ pointToPlanet(x, y, z, out, o = 0) {
30
+ const m = this.toPlanet;
31
+ out[o] = m[0] * x + m[1] * y + m[2] * z + m[3];
32
+ out[o + 1] = m[4] * x + m[5] * y + m[6] * z + m[7];
33
+ out[o + 2] = m[8] * x + m[9] * y + m[10] * z + m[11];
34
+ return out;
35
+ }
36
+ /** An origin-frame direction into the planet frame (rotation only). */
37
+ vectorToPlanet(x, y, z, out, o = 0) {
38
+ const m = this.toPlanet;
39
+ out[o] = m[0] * x + m[1] * y + m[2] * z;
40
+ out[o + 1] = m[4] * x + m[5] * y + m[6] * z;
41
+ out[o + 2] = m[8] * x + m[9] * y + m[10] * z;
42
+ return out;
43
+ }
44
+ /** A planet-frame point into the origin frame. */
45
+ pointToOrigin(x, y, z, out, o = 0) {
46
+ const m = this.toOrigin;
47
+ out[o] = m[0] * x + m[1] * y + m[2] * z + m[3];
48
+ out[o + 1] = m[4] * x + m[5] * y + m[6] * z + m[7];
49
+ out[o + 2] = m[8] * x + m[9] * y + m[10] * z + m[11];
50
+ return out;
51
+ }
52
+ /** A planet-frame direction into the origin frame. */
53
+ vectorToOrigin(x, y, z, out, o = 0) {
54
+ const m = this.toOrigin;
55
+ out[o] = m[0] * x + m[1] * y + m[2] * z;
56
+ out[o + 1] = m[4] * x + m[5] * y + m[6] * z;
57
+ out[o + 2] = m[8] * x + m[9] * y + m[10] * z;
58
+ return out;
59
+ }
60
+ }
61
+ export {
62
+ PlanetFrame
63
+ };
@@ -0,0 +1,51 @@
1
+ import { type Entity, type World } from '@aethervtt/shard-core';
2
+ import type { Workers } from '@aethervtt/shard-platform';
3
+ import type { PlanetRuntime } from './planet.js';
4
+ /** Every planet's runtime state, and the frame counter terrain systems stamp with. */
5
+ export declare class TerrainState {
6
+ readonly planets: Map<number, PlanetRuntime>;
7
+ frame: number;
8
+ workers: Workers | undefined;
9
+ /** Set when a graph or biome asset (re)loaded: planets re-resolve their assets. */
10
+ dirty: boolean;
11
+ }
12
+ export declare const Terrain: import("@aethervtt/shard-core").ResourceDef<TerrainState>;
13
+ /** The runtime for a planet entity, ready to sample, or a `terrain/not-ready` error. */
14
+ export declare function planetRuntime(world: World, planet: Entity): PlanetRuntime;
15
+ /** Height-graph metres above radius at a unit direction in the planet's frame. Sync, CPU. */
16
+ export declare function heightAt(rt: PlanetRuntime, x: number, y: number, z: number): number;
17
+ /**
18
+ * The surface height (metres above the planet's radius) in `direction` (the planet's frame, any
19
+ * length), from the canonical CPU noise (0041) the colliders use. Headless-safe and sync; allocates
20
+ * nothing. Between vertices the collider surface is flat triangles, so it can differ from this by
21
+ * the terrain's roughness below the vertex spacing.
22
+ */
23
+ export declare function planetHeightAt(world: World, planet: Entity, direction: ArrayLike<number>): number;
24
+ export interface PlanetSurface {
25
+ /** Metres above radius. */
26
+ height: number;
27
+ /** Below sea level (planets with an ocean). */
28
+ underwater: boolean;
29
+ /** Metres of water above the ground (0 on land). */
30
+ depth: number;
31
+ }
32
+ /** Height, and whether and how deep it's under water, in `direction`. */
33
+ export declare function planetSurfaceAt(world: World, planet: Entity, direction: ArrayLike<number>, out?: PlanetSurface): PlanetSurface;
34
+ export interface TerrainSample extends PlanetSurface {
35
+ /** Degrees from flat. */
36
+ slope: number;
37
+ /** Climate at the point, before latitude and altitude. */
38
+ temperature: number;
39
+ moisture: number;
40
+ /** Sine of the latitude. */
41
+ latitude: number;
42
+ /** Biome weights (summing to 1), by index in the BiomeSet. */
43
+ biomes: number[];
44
+ /** The heaviest biome's index. */
45
+ biome: number;
46
+ }
47
+ /**
48
+ * Everything the surface is at a direction: height, water, slope (from two nearby heights), climate,
49
+ * and biome weights, as the terrain shader computes them.
50
+ */
51
+ export declare function terrainSample(rt: PlanetRuntime, direction: ArrayLike<number>): TerrainSample;
@@ -0,0 +1,137 @@
1
+ import { defineResource, ShardError } from "@aethervtt/shard-core";
2
+ import { biomeWeights, dominantBiome, MAX_BIOMES } from "./biomes.js";
3
+ import { samplePoint } from "./points.js";
4
+ class TerrainState {
5
+ planets = /* @__PURE__ */ new Map();
6
+ frame = 0;
7
+ workers;
8
+ /** Set when a graph or biome asset (re)loaded: planets re-resolve their assets. */
9
+ dirty = true;
10
+ }
11
+ const Terrain = defineResource("terrain/Terrain", {
12
+ description: "Per-planet terrain state: quadtrees, collider chunks, and resolved assets.",
13
+ init: () => new TerrainState()
14
+ });
15
+ function planetRuntime(world, planet) {
16
+ const rt = world.tryResource(Terrain)?.planets.get(planet);
17
+ if (!rt) {
18
+ throw new ShardError("terrain/not-a-planet", `Entity ${planet} has no terrain/Planet`, {
19
+ hint: "Add terrain/Planet (and a Grid) to the planet entity, and the terrain plugin to the app."
20
+ });
21
+ }
22
+ if (rt.problem) throw rt.problem;
23
+ if (!rt.ready) {
24
+ throw new ShardError(
25
+ "terrain/not-ready",
26
+ `Planet ${planet} is waiting for ${rt.waiting ?? "its settings"}`,
27
+ {
28
+ hint: "Its graphs and biomes load asynchronously; step a frame (or await the asset loads) first."
29
+ }
30
+ );
31
+ }
32
+ return rt;
33
+ }
34
+ const one = new Float32Array(1);
35
+ function heightAt(rt, x, y, z) {
36
+ const s = rt.settings;
37
+ if (!rt.height) return 0;
38
+ return samplePoint(rt.height, s.seed, x, y, z, s.radius, one) * s.heightScale;
39
+ }
40
+ function unit(direction, out) {
41
+ const x = direction[0];
42
+ const y = direction[1];
43
+ const z = direction[2];
44
+ const len = Math.sqrt(x * x + y * y + z * z);
45
+ if (!(len > 0)) {
46
+ throw new ShardError("terrain/bad-direction", "The direction is zero or not a number", {
47
+ hint: "Pass a direction from the planet\u2019s center in its frame, e.g. the position of a point on it."
48
+ });
49
+ }
50
+ out[0] = x / len;
51
+ out[1] = y / len;
52
+ out[2] = z / len;
53
+ return out;
54
+ }
55
+ const d = new Float64Array(3);
56
+ function planetHeightAt(world, planet, direction) {
57
+ const rt = planetRuntime(world, planet);
58
+ unit(direction, d);
59
+ return heightAt(rt, d[0], d[1], d[2]);
60
+ }
61
+ function planetSurfaceAt(world, planet, direction, out = { height: 0, underwater: false, depth: 0 }) {
62
+ const rt = planetRuntime(world, planet);
63
+ unit(direction, d);
64
+ const h = heightAt(rt, d[0], d[1], d[2]);
65
+ const s = rt.settings;
66
+ out.height = h;
67
+ out.underwater = s.ocean && h < s.seaLevel;
68
+ out.depth = out.underwater ? s.seaLevel - h : 0;
69
+ return out;
70
+ }
71
+ const weights = new Float32Array(MAX_BIOMES);
72
+ const t1 = new Float64Array(3);
73
+ const t2 = new Float64Array(3);
74
+ function terrainSample(rt, direction) {
75
+ const s = rt.settings;
76
+ unit(direction, d);
77
+ const [x, y, z] = [d[0], d[1], d[2]];
78
+ const h = heightAt(rt, x, y, z);
79
+ const step = Math.max(rt.spacing(rt.colliderDepth), 0.25) / s.radius;
80
+ const ax = Math.abs(y) < 0.9 ? 0 : 1;
81
+ const ay = Math.abs(y) < 0.9 ? 1 : 0;
82
+ let e1x = ay * z;
83
+ let e1y = -ax * z;
84
+ let e1z = ax * y - ay * x;
85
+ const l1 = Math.hypot(e1x, e1y, e1z);
86
+ e1x /= l1;
87
+ e1y /= l1;
88
+ e1z /= l1;
89
+ const e2x = y * e1z - z * e1y;
90
+ const e2y = z * e1x - x * e1z;
91
+ const e2z = x * e1y - y * e1x;
92
+ unit([x + e1x * step, y + e1y * step, z + e1z * step], t1);
93
+ unit([x + e2x * step, y + e2y * step, z + e2z * step], t2);
94
+ const h1 = heightAt(rt, t1[0], t1[1], t1[2]);
95
+ const h2 = heightAt(rt, t2[0], t2[1], t2[2]);
96
+ const r0 = s.radius + h;
97
+ const p0 = [x * r0, y * r0, z * r0];
98
+ const r1 = s.radius + h1;
99
+ const r2 = s.radius + h2;
100
+ const a = [t1[0] * r1 - p0[0], t1[1] * r1 - p0[1], t1[2] * r1 - p0[2]];
101
+ const b = [t2[0] * r2 - p0[0], t2[1] * r2 - p0[1], t2[2] * r2 - p0[2]];
102
+ const nx = a[1] * b[2] - a[2] * b[1];
103
+ const ny = a[2] * b[0] - a[0] * b[2];
104
+ const nz = a[0] * b[1] - a[1] * b[0];
105
+ const nl = Math.hypot(nx, ny, nz);
106
+ const cos = Math.abs(nx * x + ny * y + nz * z) / nl;
107
+ const slope = Math.acos(Math.min(1, cos)) * 180 / Math.PI;
108
+ let temperature = 0;
109
+ let moisture = 0;
110
+ if (rt.climate) {
111
+ temperature = samplePoint(rt.climate, s.seed, x, y, z, s.radius, one, "temperature");
112
+ moisture = samplePoint(rt.climate, s.seed, x, y, z, s.radius, one, "moisture");
113
+ }
114
+ const table = rt.table;
115
+ biomeWeights(table, { temperature, moisture, height: h, slope, latitude: y }, weights);
116
+ const underwater = s.ocean && h < s.seaLevel;
117
+ return {
118
+ height: h,
119
+ underwater,
120
+ depth: underwater ? s.seaLevel - h : 0,
121
+ slope,
122
+ temperature,
123
+ moisture,
124
+ latitude: y,
125
+ biomes: Array.from(weights.subarray(0, table.count)),
126
+ biome: dominantBiome(weights, table.count)
127
+ };
128
+ }
129
+ export {
130
+ Terrain,
131
+ TerrainState,
132
+ heightAt,
133
+ planetHeightAt,
134
+ planetRuntime,
135
+ planetSurfaceAt,
136
+ terrainSample
137
+ };
@@ -0,0 +1,19 @@
1
+ export { Biome, type BiomeInputs, BiomeSet, type BiomeSetValue, type BiomeTable, type BiomeValue, BLENDED_BIOMES, biomeTable, biomeWeights, biomeWindow, dominantBiome, effectiveTemperature, MAX_BIOMES, MAX_LAYERS, } from './biomes.js';
2
+ export { assembleChunk, buildChunk, type ChunkLayout, type ChunkMesh, type ChunkSpec, checkResolution, chunkCenter, chunkLayout, } from './chunk.js';
3
+ export { COLLIDER_DELAY, type ColliderChunk, ColliderSet, collidersOf, skirtDepth, } from './colliders.js';
4
+ export { Chunk, Planet, PlanetNav, TerrainAnchor, TerrainBudget, type TerrainBudgetValue, } from './components.js';
5
+ export { directionToFace, EDGE_BOTTOM, EDGE_LEFT, EDGE_RIGHT, EDGE_TOP, faceToDirection, keyString, MAX_DEPTH, MAX_RADIUS, maxDepthFor, type NodeAddress, neighborNode, nodeAt, nodeExtent, nodeSpacing, packKey, parseKey, unpackKey, } from './cube.js';
6
+ export { PlanetFrame } from './frame.js';
7
+ export { heightAt, type PlanetSurface, planetHeightAt, planetRuntime, planetSurfaceAt, Terrain, type TerrainSample, TerrainState, terrainSample, } from './heights.js';
8
+ export { GEN_CLIMATE, GEN_HEIGHT, GEN_OCEAN, GEN_ROOT, type KernelGraph, kernelGraph, PARAMS_BYTES, POINT_FLOATS, sampleKernel, VERTEX_KERNEL, } from './kernel.js';
9
+ export { measureErrors } from './lod.js';
10
+ export { OceanMaterial, PlanetMaterial, TERRAIN_SHADERS, TEXTURE_PERIOD } from './material.js';
11
+ export { resolvePlanet, terrainMap, terrainMethods } from './methods.js';
12
+ export { clearPlanetNav, planetAgents, planetNavMesh, updatePlanetNav } from './nav.js';
13
+ export { terrainBiomesOverlay, terrainCollidersOverlay, terrainLodOverlay } from './overlays.js';
14
+ export { COLLIDER_SPACING, PlanetRuntime, type PlanetSettings } from './planet.js';
15
+ export { type TerrainPluginOptions, terrainPlugin, updatePlanets } from './plugin.js';
16
+ export { type ChunkPoints, createChunkPoints, gridDirection, prepareChunkPoints, SNAP, sampleChunkPoints, samplePoint, } from './points.js';
17
+ export { createSelection, NODE_BOUNDS, NODE_READY, NodeTree, type Selection, type SelectionParams, type SelectionView, selectNodes, } from './quadtree.js';
18
+ export { PlanetRender, renderOf, selectChunks, TerrainDebug } from './render.js';
19
+ export { createView, omniView, perspectiveView } from './view.js';
package/dist/index.js ADDED
@@ -0,0 +1,193 @@
1
+ import {
2
+ Biome,
3
+ BiomeSet,
4
+ BLENDED_BIOMES,
5
+ biomeTable,
6
+ biomeWeights,
7
+ biomeWindow,
8
+ dominantBiome,
9
+ effectiveTemperature,
10
+ MAX_BIOMES,
11
+ MAX_LAYERS
12
+ } from "./biomes.js";
13
+ import {
14
+ assembleChunk,
15
+ buildChunk,
16
+ checkResolution,
17
+ chunkCenter,
18
+ chunkLayout
19
+ } from "./chunk.js";
20
+ import {
21
+ COLLIDER_DELAY,
22
+ ColliderSet,
23
+ collidersOf,
24
+ skirtDepth
25
+ } from "./colliders.js";
26
+ import {
27
+ Chunk,
28
+ Planet,
29
+ PlanetNav,
30
+ TerrainAnchor,
31
+ TerrainBudget
32
+ } from "./components.js";
33
+ import {
34
+ directionToFace,
35
+ EDGE_BOTTOM,
36
+ EDGE_LEFT,
37
+ EDGE_RIGHT,
38
+ EDGE_TOP,
39
+ faceToDirection,
40
+ keyString,
41
+ MAX_DEPTH,
42
+ MAX_RADIUS,
43
+ maxDepthFor,
44
+ neighborNode,
45
+ nodeAt,
46
+ nodeExtent,
47
+ nodeSpacing,
48
+ packKey,
49
+ parseKey,
50
+ unpackKey
51
+ } from "./cube.js";
52
+ import { PlanetFrame } from "./frame.js";
53
+ import {
54
+ heightAt,
55
+ planetHeightAt,
56
+ planetRuntime,
57
+ planetSurfaceAt,
58
+ Terrain,
59
+ TerrainState,
60
+ terrainSample
61
+ } from "./heights.js";
62
+ import {
63
+ GEN_CLIMATE,
64
+ GEN_HEIGHT,
65
+ GEN_OCEAN,
66
+ GEN_ROOT,
67
+ kernelGraph,
68
+ PARAMS_BYTES,
69
+ POINT_FLOATS,
70
+ sampleKernel,
71
+ VERTEX_KERNEL
72
+ } from "./kernel.js";
73
+ import { measureErrors } from "./lod.js";
74
+ import { OceanMaterial, PlanetMaterial, TERRAIN_SHADERS, TEXTURE_PERIOD } from "./material.js";
75
+ import { resolvePlanet, terrainMap, terrainMethods } from "./methods.js";
76
+ import { clearPlanetNav, planetAgents, planetNavMesh, updatePlanetNav } from "./nav.js";
77
+ import { terrainBiomesOverlay, terrainCollidersOverlay, terrainLodOverlay } from "./overlays.js";
78
+ import { COLLIDER_SPACING, PlanetRuntime } from "./planet.js";
79
+ import { terrainPlugin, updatePlanets } from "./plugin.js";
80
+ import {
81
+ createChunkPoints,
82
+ gridDirection,
83
+ prepareChunkPoints,
84
+ SNAP,
85
+ sampleChunkPoints,
86
+ samplePoint
87
+ } from "./points.js";
88
+ import {
89
+ createSelection,
90
+ NODE_BOUNDS,
91
+ NODE_READY,
92
+ NodeTree,
93
+ selectNodes
94
+ } from "./quadtree.js";
95
+ import { PlanetRender, renderOf, selectChunks, TerrainDebug } from "./render.js";
96
+ import { createView, omniView, perspectiveView } from "./view.js";
97
+ export {
98
+ BLENDED_BIOMES,
99
+ Biome,
100
+ BiomeSet,
101
+ COLLIDER_DELAY,
102
+ COLLIDER_SPACING,
103
+ Chunk,
104
+ ColliderSet,
105
+ EDGE_BOTTOM,
106
+ EDGE_LEFT,
107
+ EDGE_RIGHT,
108
+ EDGE_TOP,
109
+ GEN_CLIMATE,
110
+ GEN_HEIGHT,
111
+ GEN_OCEAN,
112
+ GEN_ROOT,
113
+ MAX_BIOMES,
114
+ MAX_DEPTH,
115
+ MAX_LAYERS,
116
+ MAX_RADIUS,
117
+ NODE_BOUNDS,
118
+ NODE_READY,
119
+ NodeTree,
120
+ OceanMaterial,
121
+ PARAMS_BYTES,
122
+ POINT_FLOATS,
123
+ Planet,
124
+ PlanetFrame,
125
+ PlanetMaterial,
126
+ PlanetNav,
127
+ PlanetRender,
128
+ PlanetRuntime,
129
+ SNAP,
130
+ TERRAIN_SHADERS,
131
+ TEXTURE_PERIOD,
132
+ Terrain,
133
+ TerrainAnchor,
134
+ TerrainBudget,
135
+ TerrainDebug,
136
+ TerrainState,
137
+ VERTEX_KERNEL,
138
+ assembleChunk,
139
+ biomeTable,
140
+ biomeWeights,
141
+ biomeWindow,
142
+ buildChunk,
143
+ checkResolution,
144
+ chunkCenter,
145
+ chunkLayout,
146
+ clearPlanetNav,
147
+ collidersOf,
148
+ createChunkPoints,
149
+ createSelection,
150
+ createView,
151
+ directionToFace,
152
+ dominantBiome,
153
+ effectiveTemperature,
154
+ faceToDirection,
155
+ gridDirection,
156
+ heightAt,
157
+ kernelGraph,
158
+ keyString,
159
+ maxDepthFor,
160
+ measureErrors,
161
+ neighborNode,
162
+ nodeAt,
163
+ nodeExtent,
164
+ nodeSpacing,
165
+ omniView,
166
+ packKey,
167
+ parseKey,
168
+ perspectiveView,
169
+ planetAgents,
170
+ planetHeightAt,
171
+ planetNavMesh,
172
+ planetRuntime,
173
+ planetSurfaceAt,
174
+ prepareChunkPoints,
175
+ renderOf,
176
+ resolvePlanet,
177
+ sampleChunkPoints,
178
+ sampleKernel,
179
+ samplePoint,
180
+ selectChunks,
181
+ selectNodes,
182
+ skirtDepth,
183
+ terrainBiomesOverlay,
184
+ terrainCollidersOverlay,
185
+ terrainLodOverlay,
186
+ terrainMap,
187
+ terrainMethods,
188
+ terrainPlugin,
189
+ terrainSample,
190
+ unpackKey,
191
+ updatePlanetNav,
192
+ updatePlanets
193
+ };
@@ -0,0 +1,47 @@
1
+ import { type NoiseGraph, type NoiseProgram } from '@aethervtt/shard-noise';
2
+ import type { ShaderLibrary } from '@aethervtt/shard-shader';
3
+ /** A graph (or one node of it) as the kernel imports it: a WGSL module and its program. */
4
+ export interface KernelGraph {
5
+ module: string;
6
+ name: string;
7
+ wgsl: string;
8
+ program: NoiseProgram;
9
+ hash: string;
10
+ }
11
+ /** The graph's own output, or one node of it (the climate graph's temperature and moisture). */
12
+ export declare function kernelGraph(graph: NoiseGraph, node?: string): KernelGraph;
13
+ /** Floats per point in the uploaded point buffer: local xyz, q xyz, dir xyz, group (u32 bits). */
14
+ export declare const POINT_FLOATS = 10;
15
+ /** Bytes of the kernel's uniform. */
16
+ export declare const PARAMS_BYTES = 64;
17
+ /** Flags in GenParams.flags. */
18
+ export declare const GEN_HEIGHT = 1;
19
+ export declare const GEN_CLIMATE = 2;
20
+ export declare const GEN_ROOT = 4;
21
+ export declare const GEN_OCEAN = 8;
22
+ /** The sampling pass: each point's height and climate from its lattice origin. */
23
+ export declare function sampleKernel(height: KernelGraph | undefined, temperature: KernelGraph | undefined, moisture: KernelGraph | undefined): string;
24
+ /**
25
+ * The vertex pass, one invocation per surface vertex: position (the point's offset from the chunk
26
+ * center plus its direction times its height), central-difference normal from the bordered grid,
27
+ * climate, morph delta to the parent level, and the skirt copy of ring vertices. Heights, water
28
+ * depth, and morph error go into the stats for the readback. Same layout and math as `buildChunk`.
29
+ * uv1 is (grid point i + j·n + n²·tile, morph error): the vertex stage derives lock codes from the
30
+ * grid point, the surface stage finds its normal tile (`packGrid`).
31
+ */
32
+ export declare const VERTEX_KERNEL = "\nstruct GenParams {\n count: u32,\n side: u32,\n n: u32,\n surface: u32,\n ring: u32,\n seed: u32,\n flags: u32,\n /** The chunk's slot: its normal tile, packed into uv1.x (see packGrid). */\n tile: u32,\n /** Ellipsoid axis ratios (xyz), metres per height unit (w). */\n shape: vec4f,\n /** x: surface offset (m, the ocean's sea level), y: morph error, z: skirt depth. */\n misc: vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params: GenParams;\n@group(0) @binding(1) var<storage, read> pts: array<f32>;\n\n@group(0) @binding(2) var<storage, read> values: array<f32>;\n@group(0) @binding(3) var<storage, read_write> positions: array<f32>;\n@group(0) @binding(4) var<storage, read_write> normals: array<f32>;\n@group(0) @binding(5) var<storage, read_write> uvs: array<f32>;\n@group(0) @binding(6) var<storage, read_write> uvs1: array<f32>;\n@group(0) @binding(7) var<storage, read_write> tangents: array<f32>;\n/** Min height, max height (mm), largest morph delta (mm). */\n@group(0) @binding(8) var<storage, read_write> stats: array<atomic<i32>, 4>;\n\n/** Vertex index of grid point (i, j): the border ring first (counter-clockwise), then rows. */\nfn vertex_index(i: u32, j: u32, n: u32) -> u32 {\n let s = n - 1u;\n if (j == 0u) { return i; }\n if (i == s) { return s + j; }\n if (j == s) { return 2u * s + (s - i); }\n if (i == 0u) { return 3u * s + (s - j); }\n return 4u * s + (j - 1u) * (n - 2u) + (i - 1u);\n}\n\nfn terrain_height(k: u32) -> f32 {\n return values[k * 3u] * params.shape.w;\n}\n\nfn surface_height(k: u32) -> f32 {\n if ((params.flags & 8u) != 0u) { return params.misc.x; }\n return terrain_height(k) + params.misc.x;\n}\n\nfn point_position(k: u32) -> vec3f {\n let p = k * 10u;\n let q = vec3f(pts[p + 3u], pts[p + 4u], pts[p + 5u]);\n let d = vec3f(pts[p + 6u], pts[p + 7u], pts[p + 8u]);\n return q + params.shape.xyz * d * surface_height(k);\n}\n\nfn write_vertex(v: u32, p: vec3f, n: vec3f, climate: vec2f, lock: vec2f, t: vec4f) {\n positions[v * 3u] = p.x;\n positions[v * 3u + 1u] = p.y;\n positions[v * 3u + 2u] = p.z;\n normals[v * 3u] = n.x;\n normals[v * 3u + 1u] = n.y;\n normals[v * 3u + 2u] = n.z;\n uvs[v * 2u] = climate.x;\n uvs[v * 2u + 1u] = climate.y;\n uvs1[v * 2u] = lock.x;\n uvs1[v * 2u + 1u] = lock.y;\n tangents[v * 4u] = t.x;\n tangents[v * 4u + 1u] = t.y;\n tangents[v * 4u + 2u] = t.z;\n tangents[v * 4u + 3u] = t.w;\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let v = id.x;\n if (v >= params.surface) { return; }\n let n = params.n;\n let b = params.side;\n let i = v % n;\n let j = v / n;\n let k = (i + 1u) + (j + 1u) * b;\n let p = point_position(k);\n let normal = normalize(cross(point_position(k + 1u) - point_position(k - 1u), point_position(k + b) - point_position(k - b)));\n var delta = vec3f(0.0);\n let oi = (i & 1u) == 1u;\n let oj = (j & 1u) == 1u;\n if ((oi || oj) && (params.flags & 4u) == 0u) {\n let ka = select(k, k - 1u, oi) - select(0u, b, oj);\n let kb = select(k, k + 1u, oi) + select(0u, b, oj);\n delta = (point_position(ka) + point_position(kb)) * 0.5 - p;\n atomicMax(&stats[2], i32(ceil(length(delta) * 1000.0)));\n }\n let h = terrain_height(k);\n let surface = surface_height(k);\n var w = h;\n if ((params.flags & 8u) != 0u) { w = params.misc.x - h; }\n let climate = vec2f(values[k * 3u + 1u], values[k * 3u + 2u]);\n let vi = vertex_index(i, j, n);\n let lock = vec2f(f32(i + j * n + n * n * params.tile), params.misc.y);\n let tangent = vec4f(delta, w);\n write_vertex(vi, p, normal, climate, lock, tangent);\n if (vi < params.ring) {\n let q = k * 10u;\n let down = normalize(params.shape.xyz * vec3f(pts[q + 6u], pts[q + 7u], pts[q + 8u]));\n write_vertex(params.surface + vi, p - down * params.misc.z, normal, climate, lock, tangent);\n }\n atomicMin(&stats[0], i32(floor(surface * 1000.0)));\n atomicMax(&stats[1], i32(ceil(surface * 1000.0)));\n}\n";
33
+ /**
34
+ * The normal-tile pass (spec 0044 follow-up to 0043): over a chunk's grid at twice the vertex
35
+ * density (sampled like any chunk grid, bordered), one planet-space normal per texel into the
36
+ * chunk's tile of the planet's tile array. The same central differences as the vertex pass, so a
37
+ * tile texel on a vertex has that vertex's normal (to 8 bits).
38
+ */
39
+ export declare const NORMAL_KERNEL = "\nstruct GenParams {\n count: u32,\n side: u32,\n n: u32,\n surface: u32,\n ring: u32,\n seed: u32,\n flags: u32,\n /** The chunk's slot: its normal tile, packed into uv1.x (see packGrid). */\n tile: u32,\n /** Ellipsoid axis ratios (xyz), metres per height unit (w). */\n shape: vec4f,\n /** x: surface offset (m, the ocean's sea level), y: morph error, z: skirt depth. */\n misc: vec4f,\n}\n\n@group(0) @binding(0) var<uniform> params: GenParams;\n@group(0) @binding(1) var<storage, read> pts: array<f32>;\n\n@group(0) @binding(2) var<storage, read> values: array<f32>;\n@group(0) @binding(3) var tile_out: texture_storage_2d<rgba8unorm, write>;\n\nfn point_position(k: u32) -> vec3f {\n let p = k * 10u;\n let q = vec3f(pts[p + 3u], pts[p + 4u], pts[p + 5u]);\n let d = vec3f(pts[p + 6u], pts[p + 7u], pts[p + 8u]);\n return q + params.shape.xyz * d * (values[k * 3u] * params.shape.w);\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) id: vec3u) {\n let t = params.n;\n if (id.x >= t * t) { return; }\n let b = params.side;\n let i = id.x % t;\n let j = id.x / t;\n let k = (i + 1u) + (j + 1u) * b;\n let normal = normalize(cross(point_position(k + 1u) - point_position(k - 1u), point_position(k + b) - point_position(k - b)));\n // The tile's corner in its layer: surface (texel x) and ring (texel y) carry it.\n textureStore(tile_out, vec2u(params.surface + i, params.ring + j), vec4f(normal * 0.5 + 0.5, 1.0));\n}\n";
40
+ /** uv1.x of grid point (i, j) in a chunk of resolution n drawn from normal tile `tile`. */
41
+ export declare function packGrid(i: number, j: number, n: number, tile: number): number;
42
+ /** Registers a kernel's graph modules and its two roots; returns the root module paths. */
43
+ export declare function registerKernel(library: ShaderLibrary, key: string, height: KernelGraph | undefined, temperature: KernelGraph | undefined, moisture: KernelGraph | undefined): {
44
+ sample: string;
45
+ vertices: string;
46
+ normals: string;
47
+ };