@aethervtt/shard-terrain 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.
Files changed (76) hide show
  1. package/dist/biomes.d.ts +1 -0
  2. package/dist/biomes.js +3 -0
  3. package/dist/chunk.d.ts +0 -44
  4. package/dist/chunk.js +2 -127
  5. package/dist/colliders.d.ts +16 -1
  6. package/dist/colliders.js +39 -24
  7. package/dist/components.d.ts +5 -0
  8. package/dist/components.js +7 -2
  9. package/dist/cube-sphere.d.ts +50 -0
  10. package/dist/cube-sphere.js +224 -0
  11. package/dist/grid-mesh.d.ts +49 -0
  12. package/dist/grid-mesh.js +208 -0
  13. package/dist/heightfield/bake.d.ts +120 -0
  14. package/dist/heightfield/bake.js +494 -0
  15. package/dist/heightfield/colliders.d.ts +39 -0
  16. package/dist/heightfield/colliders.js +152 -0
  17. package/dist/heightfield/component.d.ts +14 -0
  18. package/dist/heightfield/component.js +59 -0
  19. package/dist/heightfield/deflate.d.ts +11 -0
  20. package/dist/heightfield/deflate.js +484 -0
  21. package/dist/heightfield/heightmap.d.ts +27 -0
  22. package/dist/heightfield/heightmap.js +160 -0
  23. package/dist/heightfield/kernel.d.ts +277 -0
  24. package/dist/heightfield/kernel.js +1033 -0
  25. package/dist/heightfield/material.d.ts +13 -0
  26. package/dist/heightfield/material.js +394 -0
  27. package/dist/heightfield/methods.d.ts +104 -0
  28. package/dist/heightfield/methods.js +218 -0
  29. package/dist/heightfield/pack.d.ts +59 -0
  30. package/dist/heightfield/pack.js +127 -0
  31. package/dist/heightfield/pages.d.ts +91 -0
  32. package/dist/heightfield/pages.js +244 -0
  33. package/dist/heightfield/project.d.ts +40 -0
  34. package/dist/heightfield/project.js +55 -0
  35. package/dist/heightfield/queries.d.ts +48 -0
  36. package/dist/heightfield/queries.js +150 -0
  37. package/dist/heightfield/render.d.ts +108 -0
  38. package/dist/heightfield/render.js +727 -0
  39. package/dist/heightfield/root-grid.d.ts +38 -0
  40. package/dist/heightfield/root-grid.js +109 -0
  41. package/dist/heightfield/runtime.d.ts +105 -0
  42. package/dist/heightfield/runtime.js +312 -0
  43. package/dist/heightfield/source-asset.d.ts +39 -0
  44. package/dist/heightfield/source-asset.js +372 -0
  45. package/dist/heightfield/source.d.ts +151 -0
  46. package/dist/heightfield/source.js +402 -0
  47. package/dist/heightfield/stack.d.ts +29 -0
  48. package/dist/heightfield/stack.js +158 -0
  49. package/dist/heightfield/system.d.ts +22 -0
  50. package/dist/heightfield/system.js +165 -0
  51. package/dist/heightfield/testing.d.ts +317 -0
  52. package/dist/heightfield/testing.js +441 -0
  53. package/dist/heightfield/worker.js +47 -0
  54. package/dist/heights.d.ts +8 -3
  55. package/dist/heights.js +8 -4
  56. package/dist/index.d.ts +24 -7
  57. package/dist/index.js +158 -8
  58. package/dist/lod.d.ts +27 -0
  59. package/dist/lod.js +23 -1
  60. package/dist/material.js +2 -1
  61. package/dist/methods.js +64 -16
  62. package/dist/overlays.d.ts +2 -0
  63. package/dist/overlays.js +48 -6
  64. package/dist/planet.d.ts +2 -7
  65. package/dist/planet.js +8 -18
  66. package/dist/plugin.d.ts +10 -3
  67. package/dist/plugin.js +48 -8
  68. package/dist/quadtree.d.ts +61 -39
  69. package/dist/quadtree.js +44 -208
  70. package/dist/render.d.ts +7 -1
  71. package/dist/render.js +18 -8
  72. package/dist/test-planet.d.ts +39 -0
  73. package/dist/test-planet.js +41 -5
  74. package/dist/testing.d.ts +2 -0
  75. package/dist/testing.js +43 -3
  76. package/package.json +16 -16
package/dist/plugin.d.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import { type World } from '@aethervtt/shard-core';
2
- import type { Workers } from '@aethervtt/shard-platform';
2
+ import type { PlatformFileSystem, Workers } from '@aethervtt/shard-platform';
3
3
  import { type Plugin } from '@aethervtt/shard-runtime';
4
+ import type { HeightfieldRuntime } from './heightfield/runtime.js';
4
5
  import { PlanetRuntime } from './planet.js';
5
6
  export interface TerrainPluginOptions {
6
- /** Where collider chunks sample (default: inline on the main thread). */
7
- workers?: Workers;
7
+ /** Where collider chunks sample and heightfield blocks bake (default: inline on the main thread). */
8
+ workers?: Workers | undefined;
9
+ /**
10
+ * Where heightfield packs are read from and baked into (`.shard/cache/terrain`). Without one,
11
+ * heightfields bake in memory on first use.
12
+ */
13
+ fs?: PlatformFileSystem | undefined;
8
14
  }
9
15
  /**
10
16
  * Keeps a runtime per Planet (spec 0043): resolves its graphs and biomes, measures per-depth
@@ -21,3 +27,4 @@ export declare function terrainPlugin(options?: TerrainPluginOptions): Plugin;
21
27
  export declare const terrainExtensions: ((app: Parameters<Plugin['build']>[0]) => void)[];
22
28
  export declare const terrainReadyHooks: ((app: Parameters<Plugin['build']>[0]) => Promise<void> | void)[];
23
29
  export declare const terrainCleanups: ((world: World, rt: PlanetRuntime) => void)[];
30
+ export declare const heightfieldCleanups: ((world: World, rt: HeightfieldRuntime) => void)[];
package/dist/plugin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AssetServerResource, assetServer } from "@aethervtt/shard-assets";
2
- import { defineSystem, onRemove, PostUpdate } from "@aethervtt/shard-core";
2
+ import { defineSystem, First, onRemove, PostUpdate } from "@aethervtt/shard-core";
3
3
  import { computeVisibility } from "@aethervtt/shard-render";
4
4
  import { definePlugin } from "@aethervtt/shard-runtime";
5
5
  import { TransformSystems } from "@aethervtt/shard-transform";
@@ -7,8 +7,20 @@ import * as biomesModule from "./biomes.js";
7
7
  import { anchorQueries, clearColliders, gatherAnchors, updateColliders } from "./colliders.js";
8
8
  import * as componentsModule from "./components.js";
9
9
  import { Planet, TerrainBudget } from "./components.js";
10
+ import { clearHeightfieldColliders } from "./heightfield/colliders.js";
11
+ import * as heightfieldComponentModule from "./heightfield/component.js";
12
+ import { Terrain } from "./heightfield/component.js";
13
+ import * as heightmapModule from "./heightfield/heightmap.js";
14
+ import * as heightfieldMaterialModule from "./heightfield/material.js";
15
+ import {
16
+ cleanupHeightfieldRender,
17
+ selectHeightfields,
18
+ uploadHeightfieldPages
19
+ } from "./heightfield/render.js";
20
+ import * as sourceModule from "./heightfield/source-asset.js";
21
+ import { registerHeightfield, updateHeightfields } from "./heightfield/system.js";
10
22
  import * as heightsModule from "./heights.js";
11
- import { Terrain } from "./heights.js";
23
+ import { TerrainWorld } from "./heights.js";
12
24
  import * as materialModule from "./material.js";
13
25
  import { terrainMethods } from "./methods.js";
14
26
  import { clearPlanetNav, updatePlanetNav } from "./nav.js";
@@ -21,7 +33,7 @@ function watchAssets(world) {
21
33
  watching.add(world);
22
34
  assetServer(world).onEvent((event) => {
23
35
  if (event.kind === "loaded" || event.kind === "modified" || event.kind === "failed")
24
- world.resource(Terrain).dirty = true;
36
+ world.resource(TerrainWorld).dirty = true;
25
37
  });
26
38
  }
27
39
  const updatePlanets = defineSystem({
@@ -32,7 +44,7 @@ const updatePlanets = defineSystem({
32
44
  anchors: anchorQueries(world)
33
45
  }),
34
46
  run: (s, world) => {
35
- const state = world.resource(Terrain);
47
+ const state = world.resource(TerrainWorld);
36
48
  watchAssets(world);
37
49
  state.frame++;
38
50
  const frame = state.frame;
@@ -59,29 +71,55 @@ const updatePlanets = defineSystem({
59
71
  function terrainPlugin(options = {}) {
60
72
  return definePlugin({
61
73
  name: "terrain",
62
- provides: [biomesModule, componentsModule, heightsModule, materialModule, overlaysModule],
74
+ provides: [
75
+ biomesModule,
76
+ componentsModule,
77
+ heightsModule,
78
+ heightfieldComponentModule,
79
+ heightfieldMaterialModule,
80
+ heightmapModule,
81
+ materialModule,
82
+ overlaysModule,
83
+ sourceModule
84
+ ],
63
85
  dependencies: ["core/transform"],
64
86
  build(app) {
65
87
  const world = app.world;
66
- const state = world.initResource(Terrain);
88
+ const state = world.initResource(TerrainWorld);
67
89
  state.workers = options.workers;
90
+ state.fs = options.fs;
68
91
  world.initResource(TerrainBudget);
69
92
  world.observe(onRemove(Planet), ({ entity, world: world2 }) => {
70
- const rt = world2.resource(Terrain).planets.get(entity);
93
+ const rt = world2.resource(TerrainWorld).planets.get(entity);
71
94
  if (!rt) return;
72
95
  clearColliders(world2, rt);
73
96
  clearPlanetNav(world2, rt);
74
97
  cleanupRender(world2, rt);
75
98
  for (const cleanup of terrainCleanups) cleanup(world2, rt);
76
- world2.resource(Terrain).planets.delete(entity);
99
+ world2.resource(TerrainWorld).planets.delete(entity);
100
+ });
101
+ world.observe(onRemove(Terrain), ({ entity, world: world2 }) => {
102
+ const rt = world2.resource(TerrainWorld).heightfields.get(entity);
103
+ if (!rt) return;
104
+ for (const cleanup of heightfieldCleanups) cleanup(world2, rt);
105
+ clearHeightfieldColliders(world2, rt);
106
+ cleanupHeightfieldRender(world2, rt);
107
+ world2.resource(TerrainWorld).heightfields.delete(entity);
77
108
  });
78
109
  app.addSystems(PostUpdate, updatePlanets.after(TransformSystems));
110
+ app.addSystems(PostUpdate, updateHeightfields.after(updatePlanets));
79
111
  app.addMethod(...terrainMethods);
80
112
  app.addSystems(PostUpdate, selectChunks.after(updatePlanets).before(computeVisibility));
113
+ app.addSystems(
114
+ PostUpdate,
115
+ selectHeightfields.after(updateHeightfields).before(computeVisibility)
116
+ );
117
+ app.addSystems(First, uploadHeightfieldPages);
81
118
  for (const extend of terrainExtensions) extend(app);
82
119
  },
83
120
  async ready(app) {
84
121
  registerNode(app);
122
+ registerHeightfield(app);
85
123
  for (const hook of terrainReadyHooks) await hook(app);
86
124
  }
87
125
  });
@@ -89,7 +127,9 @@ function terrainPlugin(options = {}) {
89
127
  const terrainExtensions = [];
90
128
  const terrainReadyHooks = [];
91
129
  const terrainCleanups = [];
130
+ const heightfieldCleanups = [];
92
131
  export {
132
+ heightfieldCleanups,
93
133
  terrainCleanups,
94
134
  terrainExtensions,
95
135
  terrainPlugin,
@@ -1,17 +1,53 @@
1
- /** The node's mesh exists (GPU dispatched, or a CPU mesh assigned): it can render. */
1
+ /** The node's mesh exists (GPU dispatched, a CPU mesh assigned, a page resident): it can render. */
2
2
  export declare const NODE_READY = 1;
3
- /** Heights are known exactly (CPU mesh, or the GPU readback arrived). */
3
+ /** Heights are known exactly (CPU mesh, the GPU readback arrived, or a baked page's range). */
4
4
  export declare const NODE_BOUNDS = 2;
5
5
  /**
6
- * One cube-sphere quadtree per planet surface (terrain or ocean), stored as flat typed arrays
7
- * indexed by node, so the per-frame walk allocates nothing. The six roots are nodes 0–5; children
8
- * are made on first split as a block of four consecutive nodes, and freed as a block when their
9
- * subtree hasn't been needed for a while.
6
+ * What a quadtree needs from the surface it covers (spec 0071): how many roots there are, each
7
+ * node's geometry (its bounding sphere and box at its height range), whether it's out of view or
8
+ * behind the horizon, and its neighbor across an edge. The cube-sphere (`CubeSphere`, six roots)
9
+ * and the heightfield's root grid (`RootGrid`) are the two surfaces; selection, 2:1 balance and
10
+ * edge locks are the same code for both.
10
11
  */
11
- export declare class NodeTree {
12
+ export interface QuadSurface {
13
+ /** Root nodes, 0 to roots − 1. */
14
+ readonly roots: number;
15
+ /** The tree grew to `capacity` nodes: per-node data the surface keeps grows with it. */
16
+ grow(capacity: number): void;
17
+ /**
18
+ * Sets up a new node's geometry; the tree has filled its root, depth, x, y and a height range
19
+ * (its parent's, or the tree's). A surface that knows the node's range (or that it's ready) may
20
+ * set it here, with its flags.
21
+ */
22
+ initNode(tree: QuadTree, n: number): void;
23
+ /** The node's bounding sphere at its height range, into `tree.sphere`. */
24
+ updateSphere(tree: QuadTree, n: number): void;
25
+ /** Axis-aligned bounds of the node relative to `center` (its chunk's reference point). */
26
+ bounds(tree: QuadTree, n: number, center: ArrayLike<number>, out: {
27
+ [i: number]: number;
28
+ }): void;
29
+ /** Whether the node is outside the view's frustum or below the horizon. */
30
+ culled(tree: QuadTree, n: number, view: SelectionView, params: SelectionParams): boolean;
31
+ /**
32
+ * The same-depth node across `edge` (EDGE_BOTTOM … EDGE_LEFT), written into `out` as
33
+ * [root, x, y]. False where the surface ends (a heightfield's border).
34
+ */
35
+ neighbor(root: number, depth: number, x: number, y: number, edge: number, out: {
36
+ [i: number]: number;
37
+ }): boolean;
38
+ }
39
+ /**
40
+ * A quadtree over a surface (spec 0043, shared with 0071), stored as flat typed arrays indexed by
41
+ * node, so the per-frame walk allocates nothing. The roots are nodes 0 to `surface.roots − 1`;
42
+ * children are made on first split as a block of four consecutive nodes, and freed as a block when
43
+ * their subtree hasn't been needed for a while.
44
+ */
45
+ export declare class QuadTree {
46
+ readonly surface: QuadSurface;
12
47
  capacity: number;
13
48
  count: number;
14
- face: Uint8Array<ArrayBuffer>;
49
+ /** The root each node is under (a cube face on a planet, a grid cell on a heightfield). */
50
+ root: Uint8Array<ArrayBuffer>;
15
51
  depth: Uint8Array<ArrayBuffer>;
16
52
  x: Uint32Array<ArrayBuffer>;
17
53
  y: Uint32Array<ArrayBuffer>;
@@ -19,13 +55,11 @@ export declare class NodeTree {
19
55
  /** First of four children (in order (2x, 2y), (2x+1, 2y), (2x, 2y+1), (2x+1, 2y+1)), or −1. */
20
56
  child: Int32Array<ArrayBuffer>;
21
57
  flags: Uint8Array<ArrayBuffer>;
22
- /** Bounding sphere in the planet frame (f64): center xyz, radius. */
58
+ /** Bounding sphere in the surface's frame (f64): center xyz, radius. */
23
59
  sphere: Float64Array<ArrayBuffer>;
24
- /** Height range (m above radius): exact once NODE_BOUNDS, else inherited or the planet's. */
60
+ /** Height range (m): exact once NODE_BOUNDS, else inherited or the surface's. */
25
61
  minH: Float32Array<ArrayBuffer>;
26
62
  maxH: Float32Array<ArrayBuffer>;
27
- /** Horizon test directions (unit, 9 per node). */
28
- dirs: Float64Array<ArrayBuffer>;
29
63
  /** Stamps: last walk (frame), rendered and culled (walk stamp), forced split, last needed (frame). */
30
64
  visited: Uint32Array<ArrayBuffer>;
31
65
  rendered: Uint32Array<ArrayBuffer>;
@@ -41,7 +75,7 @@ export declare class NodeTree {
41
75
  * Prefetched and out-of-view nodes are only `used`; a full pool evicts them first.
42
76
  */
43
77
  neededAt: Uint32Array<ArrayBuffer>;
44
- /** Render slot holding its mesh, or −1. */
78
+ /** Render slot holding its mesh (or its page), or −1. */
45
79
  slot: Int32Array<ArrayBuffer>;
46
80
  /** Projected error (px) and distance when last requested: generation priority. */
47
81
  priority: Float32Array<ArrayBuffer>;
@@ -50,26 +84,17 @@ export declare class NodeTree {
50
84
  locks: Int8Array<ArrayBuffer>;
51
85
  /** Quadrants drawn when rendered (15: all; fewer while a split waits for some children). */
52
86
  mask: Uint8Array<ArrayBuffer>;
53
- /** The planet version the node's mesh was made for: older is stale (it renders until replaced). */
87
+ /** The content version the node's mesh was made for: older is stale (it renders until replaced). */
54
88
  gen: Uint32Array<ArrayBuffer>;
55
- /**
56
- * 1 / cos(half the widest angle between neighboring test points, diagonals included): lifting
57
- * the points by it puts the surface between them inside their hull (horizon test, bulge).
58
- */
59
- lift: Float64Array<ArrayBuffer>;
60
89
  /** Starts of freed child blocks. */
61
90
  private blocks;
62
- radius: number;
63
- readonly shape: Float64Array<ArrayBuffer>;
64
91
  /** Height range for nodes whose heights aren't known yet. */
65
92
  lowest: number;
66
93
  highest: number;
67
- constructor(capacity?: number);
94
+ constructor(surface: QuadSurface, capacity?: number);
68
95
  private grow;
69
- /** Clears every node and makes the six roots (after a planet's shape or graphs change). */
70
- reset(radius: number, shape: ArrayLike<number>, lowest: number, highest: number): void;
71
- /** Root of a face (nodes 0–5). */
72
- root(face: number): number;
96
+ /** Clears every node and makes the roots (after the surface's shape or contents change). */
97
+ resetRoots(lowest: number, highest: number): void;
73
98
  private init;
74
99
  /** Children of `n`, made if needed. Returns the first (the others follow it). */
75
100
  split(n: number): number;
@@ -79,23 +104,20 @@ export declare class NodeTree {
79
104
  get live(): number;
80
105
  /** Sets a node's exact height range and recomputes its bounds. */
81
106
  setHeights(n: number, minH: number, maxH: number): void;
82
- /** Bounding sphere of the node's 9 test points at its lowest and highest heights, plus bulge. */
107
+ /** Recomputes the node's bounding sphere from its height range. */
83
108
  updateSphere(n: number): void;
84
- /**
85
- * Axis-aligned bounds of the node relative to `center` (its chunk's reference point), from its
86
- * test points at its lowest and highest heights plus the bulge between them.
87
- */
109
+ /** Axis-aligned bounds of the node relative to `center` (its chunk's reference point). */
88
110
  bounds(n: number, center: ArrayLike<number>, out: {
89
111
  [i: number]: number;
90
112
  }): void;
91
- /** The node at (face, depth, x, y) if the tree has it, else the deepest existing ancestor. */
92
- find(face: number, depth: number, x: number, y: number): number;
113
+ /** The node at (root, depth, x, y) if the tree has it, else the deepest existing ancestor. */
114
+ find(root: number, depth: number, x: number, y: number): number;
93
115
  /** Frees subtrees nobody has needed since frame `before`. */
94
116
  prune(before: number, release: (node: number) => void): void;
95
117
  /** Returns whether `n` and its whole subtree are idle. */
96
118
  private pruneNode;
97
119
  }
98
- /** What selection sees of the camera, in the planet frame (f64). */
120
+ /** What selection sees of the camera, in the surface's frame (f64). */
99
121
  export interface SelectionView {
100
122
  /** Camera position. */
101
123
  position: Float64Array;
@@ -119,7 +141,7 @@ export interface SelectionParams {
119
141
  occluder: number;
120
142
  /** Depth colliders use: forced near anchors, and the deepest rendered there. */
121
143
  colliderDepth: number;
122
- /** Anchor positions in the planet frame (xyz) and their radii; `anchors` of them. */
144
+ /** Anchor positions in the surface's frame (xyz) and their radii; `anchors` of them. */
123
145
  anchorPos: Float64Array;
124
146
  anchorRadius: Float64Array;
125
147
  anchors: number;
@@ -141,13 +163,13 @@ export interface Selection {
141
163
  }
142
164
  export declare function createSelection(): Selection;
143
165
  /**
144
- * Picks the nodes to render this frame (spec 0043): walks the six trees, culls nodes behind the
145
- * horizon or outside the frustum, splits a node when its children's geometric error projects past
166
+ * Picks the nodes to render this frame (spec 0043): walks the trees from every root, culls nodes
167
+ * behind the horizon or outside the frustum, splits a node when its children's geometric error projects past
146
168
  * `errorPixels` (or an anchor needs collider depth nearby, where nothing goes deeper), and renders
147
169
  * a split node's children only once all of the visible ones are ready, so a split never shows a
148
170
  * hole. Then enforces 2:1 balance between neighbors (marking coarse nodes to split and walking
149
171
  * again) and sets each rendered node's edge locks. Allocates nothing once its arrays are big enough.
150
172
  */
151
- export declare function selectNodes(tree: NodeTree, view: SelectionView, params: SelectionParams, frame: number, out: Selection): void;
173
+ export declare function selectNodes(tree: QuadTree, view: SelectionView, params: SelectionParams, frame: number, out: Selection): void;
152
174
  /** Whether any anchor is within its radius of the node's bounds. */
153
- export declare function nearAnchor(tree: NodeTree, n: number, params: SelectionParams): boolean;
175
+ export declare function nearAnchor(tree: QuadTree, n: number, params: SelectionParams): boolean;