@aethervtt/shard-terrain 0.1.0 → 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/biomes.d.ts +1 -0
- package/dist/biomes.js +3 -0
- package/dist/chunk.d.ts +0 -44
- package/dist/chunk.js +2 -127
- package/dist/colliders.d.ts +16 -1
- package/dist/colliders.js +39 -24
- package/dist/components.d.ts +5 -0
- package/dist/components.js +7 -2
- package/dist/cube-sphere.d.ts +50 -0
- package/dist/cube-sphere.js +224 -0
- package/dist/grid-mesh.d.ts +49 -0
- package/dist/grid-mesh.js +208 -0
- package/dist/heightfield/bake.d.ts +120 -0
- package/dist/heightfield/bake.js +494 -0
- package/dist/heightfield/colliders.d.ts +39 -0
- package/dist/heightfield/colliders.js +152 -0
- package/dist/heightfield/component.d.ts +14 -0
- package/dist/heightfield/component.js +59 -0
- package/dist/heightfield/deflate.d.ts +11 -0
- package/dist/heightfield/deflate.js +484 -0
- package/dist/heightfield/heightmap.d.ts +27 -0
- package/dist/heightfield/heightmap.js +160 -0
- package/dist/heightfield/kernel.d.ts +277 -0
- package/dist/heightfield/kernel.js +1033 -0
- package/dist/heightfield/material.d.ts +13 -0
- package/dist/heightfield/material.js +394 -0
- package/dist/heightfield/methods.d.ts +104 -0
- package/dist/heightfield/methods.js +218 -0
- package/dist/heightfield/pack.d.ts +59 -0
- package/dist/heightfield/pack.js +127 -0
- package/dist/heightfield/pages.d.ts +91 -0
- package/dist/heightfield/pages.js +244 -0
- package/dist/heightfield/project.d.ts +40 -0
- package/dist/heightfield/project.js +55 -0
- package/dist/heightfield/queries.d.ts +48 -0
- package/dist/heightfield/queries.js +150 -0
- package/dist/heightfield/render.d.ts +108 -0
- package/dist/heightfield/render.js +727 -0
- package/dist/heightfield/root-grid.d.ts +38 -0
- package/dist/heightfield/root-grid.js +109 -0
- package/dist/heightfield/runtime.d.ts +105 -0
- package/dist/heightfield/runtime.js +312 -0
- package/dist/heightfield/source-asset.d.ts +39 -0
- package/dist/heightfield/source-asset.js +372 -0
- package/dist/heightfield/source.d.ts +151 -0
- package/dist/heightfield/source.js +402 -0
- package/dist/heightfield/stack.d.ts +29 -0
- package/dist/heightfield/stack.js +158 -0
- package/dist/heightfield/system.d.ts +22 -0
- package/dist/heightfield/system.js +165 -0
- package/dist/heightfield/testing.d.ts +317 -0
- package/dist/heightfield/testing.js +441 -0
- package/dist/heightfield/worker.js +47 -0
- package/dist/heights.d.ts +8 -3
- package/dist/heights.js +8 -4
- package/dist/index.d.ts +24 -7
- package/dist/index.js +158 -8
- package/dist/lod.d.ts +27 -0
- package/dist/lod.js +23 -1
- package/dist/material.js +2 -1
- package/dist/methods.js +64 -16
- package/dist/overlays.d.ts +2 -0
- package/dist/overlays.js +48 -6
- package/dist/planet.d.ts +2 -7
- package/dist/planet.js +8 -18
- package/dist/plugin.d.ts +10 -3
- package/dist/plugin.js +48 -8
- package/dist/quadtree.d.ts +61 -39
- package/dist/quadtree.js +44 -208
- package/dist/render.d.ts +7 -1
- package/dist/render.js +8 -6
- package/dist/test-planet.d.ts +39 -0
- package/dist/test-planet.js +40 -4
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +42 -2
- package/package.json +16 -16
package/dist/biomes.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const Biome: import("@aethervtt/shard-assets").DataType<{
|
|
|
15
15
|
readonly slope: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
|
|
16
16
|
readonly blend: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
17
17
|
readonly tint: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
18
|
+
readonly scatter: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"scatter/ScatterSet"> | null, "object">;
|
|
18
19
|
}, "terrain/Biome">;
|
|
19
20
|
export declare const BiomeSet: import("@aethervtt/shard-assets").DataType<{
|
|
20
21
|
readonly biomes: import("@aethervtt/shard-core").FieldType<(import("@aethervtt/shard-core").AssetRef<"terrain/Biome"> | null)[], "object">;
|
package/dist/biomes.js
CHANGED
|
@@ -38,6 +38,9 @@ const Biome = defineDataType(
|
|
|
38
38
|
tint: t.color({
|
|
39
39
|
default: [1, 1, 1, 1],
|
|
40
40
|
description: "Multiplies the albedo (the whole color when the set has no textures)."
|
|
41
|
+
}),
|
|
42
|
+
scatter: t.handle("scatter/ScatterSet", {
|
|
43
|
+
description: "Props and foliage where this biome is the heaviest (*.scatter.json; needs the scatter plugin)."
|
|
41
44
|
})
|
|
42
45
|
},
|
|
43
46
|
{
|
package/dist/chunk.d.ts
CHANGED
|
@@ -1,50 +1,6 @@
|
|
|
1
1
|
import type { MeshData } from '@aethervtt/shard-mesh';
|
|
2
2
|
import type { NoiseGraph } from '@aethervtt/shard-noise';
|
|
3
3
|
import { type ChunkPoints } from './points.js';
|
|
4
|
-
/**
|
|
5
|
-
* How a chunk's vertices are laid out, per resolution `n` (vertices per edge, 2^k + 1). The border
|
|
6
|
-
* ring comes first (so edge locks rewrite one contiguous block), then the interior row by row, then
|
|
7
|
-
* one skirt vertex under each ring vertex. Every chunk of a resolution shares one index buffer.
|
|
8
|
-
*/
|
|
9
|
-
export interface ChunkLayout {
|
|
10
|
-
resolution: number;
|
|
11
|
-
/** Ring vertices: 4 (n − 1), counter-clockwise from (0, 0) seen from outside. */
|
|
12
|
-
ring: number;
|
|
13
|
-
/** Surface vertices: n². */
|
|
14
|
-
surface: number;
|
|
15
|
-
/** Surface plus skirt: n² + 4 (n − 1). */
|
|
16
|
-
vertexCount: number;
|
|
17
|
-
/** Vertex index of grid point (i, j), at `i + j * n`. */
|
|
18
|
-
index: Uint16Array;
|
|
19
|
-
/** Grid point of each ring vertex: i, j pairs. */
|
|
20
|
-
ringPoints: Uint16Array;
|
|
21
|
-
/** Triangles: the surface, then the skirts (`chunkIndices` with every quadrant, no stitching). */
|
|
22
|
-
indices: Uint16Array;
|
|
23
|
-
/** Triangles without skirts (the surface only): colliders, and planets with skirts off. */
|
|
24
|
-
surfaceIndices: Uint16Array;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* A chunk's triangles (cached per combination, so chunks share the array and its GPU index buffer).
|
|
28
|
-
* `mask` picks quadrants (bit q: quadrant q, in child order (x, y), (x+1, y), (x, y+1),
|
|
29
|
-
* (x+1, y+1)): a split waiting for children draws its own quadrants where they're missing and the
|
|
30
|
-
* children elsewhere. `stitch` bit e marks edge e (0 bottom, 1 right, 2 top, 3 left) as meeting a
|
|
31
|
-
* coarser neighbor: that edge's triangles use only its even vertices, the neighbor's, so there's no
|
|
32
|
-
* T-junction (a vertex on the neighbor's edge only up to rounding shows pixel-sized holes).
|
|
33
|
-
*
|
|
34
|
-
* Quads fill the interior; each edge's strip (between the border and the first inner row) is
|
|
35
|
-
* zipped per half, so no triangle crosses a quadrant boundary. Skirts follow the drawn border.
|
|
36
|
-
*/
|
|
37
|
-
export declare function chunkIndices(n: number, mask: number, stitch: number, skirts: boolean): Uint16Array;
|
|
38
|
-
/** Throws `terrain/bad-resolution` unless `n` is 2^k + 1 (5 to 129). */
|
|
39
|
-
export declare function checkResolution(n: number): void;
|
|
40
|
-
export declare function chunkLayout(n: number): ChunkLayout;
|
|
41
|
-
/**
|
|
42
|
-
* A vertex's lock code (uv1.x): its edge for ring vertices (0 bottom, 1 right, 2 top, 3 left, where
|
|
43
|
-
* the instance's lock bits apply), the quadrant boundary for vertices on the center lines (4 and 5
|
|
44
|
-
* the vertical line's lower and upper half, 6 and 7 the horizontal line's left and right half, 8
|
|
45
|
-
* the center; locked to this level where a partial draw meets a child), else −1.
|
|
46
|
-
*/
|
|
47
|
-
export declare function lockCode(i: number, j: number, n: number, vi: number, ring: number): number;
|
|
48
4
|
/** What a chunk is made from: its node, the planet's numbers, and its graphs. */
|
|
49
5
|
export interface ChunkSpec {
|
|
50
6
|
face: number;
|
package/dist/chunk.js
CHANGED
|
@@ -1,131 +1,10 @@
|
|
|
1
|
-
import { ShardError } from "@aethervtt/shard-core";
|
|
2
1
|
import { faceToDirection, nodeExtent } from "./cube.js";
|
|
2
|
+
import { chunkLayout } from "./grid-mesh.js";
|
|
3
3
|
import {
|
|
4
4
|
createChunkPoints,
|
|
5
5
|
prepareChunkPoints,
|
|
6
6
|
sampleChunkPoints
|
|
7
7
|
} from "./points.js";
|
|
8
|
-
const layouts = /* @__PURE__ */ new Map();
|
|
9
|
-
const sets = /* @__PURE__ */ new Map();
|
|
10
|
-
function chunkIndices(n, mask, stitch, skirts) {
|
|
11
|
-
const key = `${n}:${mask}:${stitch}:${skirts}`;
|
|
12
|
-
let out = sets.get(key);
|
|
13
|
-
if (out) return out;
|
|
14
|
-
const layout = chunkLayout(n);
|
|
15
|
-
const index = layout.index;
|
|
16
|
-
const side = n - 1;
|
|
17
|
-
const h = side / 2;
|
|
18
|
-
const parts = [];
|
|
19
|
-
const quadrant = (i, j) => (i >= h ? 1 : 0) + (j >= h ? 2 : 0);
|
|
20
|
-
for (let j = 1; j < side - 1; j++) {
|
|
21
|
-
for (let i = 1; i < side - 1; i++) {
|
|
22
|
-
if (!(mask & 1 << quadrant(i, j))) continue;
|
|
23
|
-
const a = index[i + j * n];
|
|
24
|
-
const b = index[i + 1 + j * n];
|
|
25
|
-
const c = index[i + 1 + (j + 1) * n];
|
|
26
|
-
const d = index[i + (j + 1) * n];
|
|
27
|
-
parts.push(a, b, c, a, c, d);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const at = (e, s, depth) => {
|
|
31
|
-
const i = e === 0 ? s : e === 1 ? side - depth : e === 2 ? side - s : depth;
|
|
32
|
-
const j = e === 0 ? depth : e === 1 ? s : e === 2 ? side - depth : side - s;
|
|
33
|
-
return index[i + j * n];
|
|
34
|
-
};
|
|
35
|
-
const skirt = [];
|
|
36
|
-
const outer = [];
|
|
37
|
-
const inner = [];
|
|
38
|
-
for (let e = 0; e < 4; e++) {
|
|
39
|
-
const step = stitch & 1 << e ? 2 : 1;
|
|
40
|
-
for (let half = 0; half < 2; half++) {
|
|
41
|
-
const s0 = half * h;
|
|
42
|
-
const s1 = s0 + h;
|
|
43
|
-
const m = s0 + h / 2;
|
|
44
|
-
const q = e === 0 ? quadrant(m, 0) : e === 1 ? quadrant(side, m) : e === 2 ? quadrant(side - m, side) : quadrant(0, side - m);
|
|
45
|
-
if (!(mask & 1 << q)) continue;
|
|
46
|
-
outer.length = 0;
|
|
47
|
-
inner.length = 0;
|
|
48
|
-
for (let s = s0; s <= s1; s += step) outer.push(s);
|
|
49
|
-
for (let s = Math.max(1, s0); s <= Math.min(side - 1, s1); s++) inner.push(s);
|
|
50
|
-
let a = 0;
|
|
51
|
-
let b = 0;
|
|
52
|
-
while (a < outer.length - 1 || b < inner.length - 1) {
|
|
53
|
-
const advanceOuter = b === inner.length - 1 || a < outer.length - 1 && outer[a + 1] <= inner[b + 1];
|
|
54
|
-
if (advanceOuter) {
|
|
55
|
-
parts.push(at(e, outer[a], 0), at(e, outer[a + 1], 0), at(e, inner[b], 1));
|
|
56
|
-
a++;
|
|
57
|
-
} else {
|
|
58
|
-
parts.push(at(e, outer[a], 0), at(e, inner[b + 1], 1), at(e, inner[b], 1));
|
|
59
|
-
b++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (!skirts) continue;
|
|
63
|
-
for (let k = 0; k + 1 < outer.length; k++) {
|
|
64
|
-
const va = at(e, outer[k], 0);
|
|
65
|
-
const vb = at(e, outer[k + 1], 0);
|
|
66
|
-
const sa = layout.surface + va;
|
|
67
|
-
const sb = layout.surface + vb;
|
|
68
|
-
skirt.push(va, sa, vb, vb, sa, sb, va, vb, sa, vb, sb, sa);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const v of skirt) parts.push(v);
|
|
73
|
-
out = Uint16Array.from(parts);
|
|
74
|
-
sets.set(key, out);
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
function checkResolution(n) {
|
|
78
|
-
const k = Math.log2(n - 1);
|
|
79
|
-
if (!Number.isInteger(k) || k < 2 || k > 7) {
|
|
80
|
-
throw new ShardError("terrain/bad-resolution", `Planet resolution ${n} isn't 2^n + 1`, {
|
|
81
|
-
hint: "Use 17, 33 (the default), 65, or 129 vertices per chunk edge."
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function chunkLayout(n) {
|
|
86
|
-
let layout = layouts.get(n);
|
|
87
|
-
if (layout) return layout;
|
|
88
|
-
checkResolution(n);
|
|
89
|
-
const ring = 4 * (n - 1);
|
|
90
|
-
const surface = n * n;
|
|
91
|
-
const index = new Uint16Array(surface);
|
|
92
|
-
const ringPoints = new Uint16Array(ring * 2);
|
|
93
|
-
let r = 0;
|
|
94
|
-
const put = (i, j) => {
|
|
95
|
-
index[i + j * n] = r;
|
|
96
|
-
ringPoints[r * 2] = i;
|
|
97
|
-
ringPoints[r * 2 + 1] = j;
|
|
98
|
-
r++;
|
|
99
|
-
};
|
|
100
|
-
for (let i = 0; i < n - 1; i++) put(i, 0);
|
|
101
|
-
for (let j = 0; j < n - 1; j++) put(n - 1, j);
|
|
102
|
-
for (let i = n - 1; i > 0; i--) put(i, n - 1);
|
|
103
|
-
for (let j = n - 1; j > 0; j--) put(0, j);
|
|
104
|
-
let v = ring;
|
|
105
|
-
for (let j = 1; j < n - 1; j++) for (let i = 1; i < n - 1; i++) index[i + j * n] = v++;
|
|
106
|
-
layout = {
|
|
107
|
-
resolution: n,
|
|
108
|
-
ring,
|
|
109
|
-
surface,
|
|
110
|
-
vertexCount: surface + ring,
|
|
111
|
-
index,
|
|
112
|
-
ringPoints,
|
|
113
|
-
indices: new Uint16Array(0),
|
|
114
|
-
surfaceIndices: new Uint16Array(0)
|
|
115
|
-
};
|
|
116
|
-
layouts.set(n, layout);
|
|
117
|
-
layout.indices = chunkIndices(n, 15, 0, true);
|
|
118
|
-
layout.surfaceIndices = chunkIndices(n, 15, 0, false);
|
|
119
|
-
return layout;
|
|
120
|
-
}
|
|
121
|
-
function lockCode(i, j, n, vi, ring) {
|
|
122
|
-
if (vi < ring) return Math.floor(vi / (n - 1));
|
|
123
|
-
const h = (n - 1) / 2;
|
|
124
|
-
if (i === h && j === h) return 8;
|
|
125
|
-
if (i === h) return j < h ? 4 : 5;
|
|
126
|
-
if (j === h) return i < h ? 6 : 7;
|
|
127
|
-
return -1;
|
|
128
|
-
}
|
|
129
8
|
let values = new Float32Array(0);
|
|
130
9
|
let temps = new Float32Array(0);
|
|
131
10
|
let moist = new Float32Array(0);
|
|
@@ -296,9 +175,5 @@ function assembleChunk(spec, pts, values2, temps2, moist2) {
|
|
|
296
175
|
export {
|
|
297
176
|
assembleChunk,
|
|
298
177
|
buildChunk,
|
|
299
|
-
|
|
300
|
-
chunkCenter,
|
|
301
|
-
chunkIndices,
|
|
302
|
-
chunkLayout,
|
|
303
|
-
lockCode
|
|
178
|
+
chunkCenter
|
|
304
179
|
};
|
package/dist/colliders.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Entity, type Query, type World } from '@aethervtt/shard-core';
|
|
2
2
|
import { Mesh } from '@aethervtt/shard-mesh';
|
|
3
3
|
import type { Workers } from '@aethervtt/shard-platform';
|
|
4
|
-
import { type ChunkMesh } from './chunk.js';
|
|
4
|
+
import { type ChunkMesh, type ChunkSpec } from './chunk.js';
|
|
5
5
|
import type { PlanetRuntime } from './planet.js';
|
|
6
6
|
import { type ChunkPoints } from './points.js';
|
|
7
7
|
/** Frames between asking for a collider chunk and it appearing. Always this many, pool or not. */
|
|
@@ -71,6 +71,7 @@ export declare function anchorQueries(world: World): AnchorQueries;
|
|
|
71
71
|
export declare function gatherAnchors(world: World, planet: PlanetRuntime, q: AnchorQueries): void;
|
|
72
72
|
/** The collider nodes within reach of the planet's anchors, as sorted keys. */
|
|
73
73
|
export declare function wantedColliders(planet: PlanetRuntime): string[];
|
|
74
|
+
export declare function chunkSpec(planet: PlanetRuntime, face: number, depth: number, x: number, y: number): ChunkSpec;
|
|
74
75
|
/** How far skirts hang: past any morph mismatch between neighbors at this depth (true errors). */
|
|
75
76
|
export declare function skirtDepth(planet: PlanetRuntime, depth: number): number;
|
|
76
77
|
/**
|
|
@@ -81,6 +82,20 @@ export declare function skirtDepth(planet: PlanetRuntime, depth: number): number
|
|
|
81
82
|
* but stay cached (least recently used first out).
|
|
82
83
|
*/
|
|
83
84
|
export declare function updateColliders(world: World, planet: PlanetRuntime, workers: Workers | undefined, frame: number, cacheSize: number): void;
|
|
85
|
+
/** A chunk's sample points with their graphs sampled on the pool (`done` resolves when all are in). */
|
|
86
|
+
export interface ChunkSampling {
|
|
87
|
+
pts: ChunkPoints;
|
|
88
|
+
values: Float32Array;
|
|
89
|
+
temps: Float32Array;
|
|
90
|
+
moist: Float32Array;
|
|
91
|
+
done: Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Samples a chunk's height and climate on the worker pool: `assembleChunk(chunkSpec(…), pts, values,
|
|
95
|
+
* temps, moist)` then gives the same mesh `buildChunk` does inline. Colliders and scatter's foliage
|
|
96
|
+
* patches (0045) use it.
|
|
97
|
+
*/
|
|
98
|
+
export declare function sampleChunkAsync(planet: PlanetRuntime, workers: Workers, face: number, depth: number, x: number, y: number): ChunkSampling;
|
|
84
99
|
/** Drops every collider chunk (planet despawned). */
|
|
85
100
|
export declare function clearColliders(world: World, planet: PlanetRuntime): void;
|
|
86
101
|
export {};
|
package/dist/colliders.js
CHANGED
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
propagateSubtree,
|
|
11
11
|
Transform
|
|
12
12
|
} from "@aethervtt/shard-transform";
|
|
13
|
-
import { assembleChunk, buildChunk
|
|
13
|
+
import { assembleChunk, buildChunk } from "./chunk.js";
|
|
14
14
|
import { Chunk, PlanetNav, TerrainAnchor } from "./components.js";
|
|
15
15
|
import { faceToDirection, keyString, nodeAt, nodeExtent } from "./cube.js";
|
|
16
|
+
import { chunkLayout } from "./grid-mesh.js";
|
|
16
17
|
import { createChunkPoints, prepareChunkPoints } from "./points.js";
|
|
17
18
|
const COLLIDER_DELAY = 2;
|
|
18
19
|
class ColliderSet {
|
|
@@ -148,7 +149,7 @@ function wantedColliders(planet) {
|
|
|
148
149
|
}
|
|
149
150
|
return [...wantedKeys].sort();
|
|
150
151
|
}
|
|
151
|
-
function
|
|
152
|
+
function chunkSpec(planet, face, depth, x, y) {
|
|
152
153
|
const s = planet.settings;
|
|
153
154
|
return {
|
|
154
155
|
face,
|
|
@@ -195,7 +196,7 @@ function updateColliders(world, planet, workers, frame, cacheSize) {
|
|
|
195
196
|
}
|
|
196
197
|
if (frame < job.due) continue;
|
|
197
198
|
set.jobs.delete(key);
|
|
198
|
-
const spec =
|
|
199
|
+
const spec = chunkSpec(planet, job.face, job.depth, job.x, job.y);
|
|
199
200
|
let mesh;
|
|
200
201
|
if (job.ready && job.pts && job.values && job.temps && job.moist) {
|
|
201
202
|
mesh = assembleChunk(spec, job.pts, job.values, job.temps, job.moist);
|
|
@@ -244,13 +245,31 @@ function startJob(planet, workers, key, face, depth, x, y, due) {
|
|
|
244
245
|
cancelled: false
|
|
245
246
|
};
|
|
246
247
|
if (!workers || workers.size === 0) return job;
|
|
248
|
+
const sampling = sampleChunkAsync(planet, workers, face, depth, x, y);
|
|
249
|
+
job.pts = sampling.pts;
|
|
250
|
+
job.values = sampling.values;
|
|
251
|
+
job.temps = sampling.temps;
|
|
252
|
+
job.moist = sampling.moist;
|
|
253
|
+
sampling.done.then(
|
|
254
|
+
() => {
|
|
255
|
+
if (!job.cancelled) job.ready = true;
|
|
256
|
+
},
|
|
257
|
+
() => {
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
return job;
|
|
261
|
+
}
|
|
262
|
+
function sampleChunkAsync(planet, workers, face, depth, x, y) {
|
|
247
263
|
const s = planet.settings;
|
|
248
264
|
const pts = prepareChunkPoints(face, depth, x, y, s.resolution, s.radius, createChunkPoints());
|
|
249
|
-
job.pts = pts;
|
|
250
265
|
const count = pts.count;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
266
|
+
const out = {
|
|
267
|
+
pts,
|
|
268
|
+
values: new Float32Array(count),
|
|
269
|
+
temps: new Float32Array(count),
|
|
270
|
+
moist: new Float32Array(count),
|
|
271
|
+
done: Promise.resolve()
|
|
272
|
+
};
|
|
254
273
|
const parts = [];
|
|
255
274
|
const origin = new Float64Array(4);
|
|
256
275
|
for (let g = 0; g < pts.groups; g++) {
|
|
@@ -267,31 +286,25 @@ function startJob(planet, workers, key, face, depth, x, y, due) {
|
|
|
267
286
|
origin[1] = pts.origins[g * 4 + 1];
|
|
268
287
|
origin[2] = pts.origins[g * 4 + 2];
|
|
269
288
|
const o = Float64Array.from(origin);
|
|
270
|
-
const scatter = (target) => (values) => {
|
|
271
|
-
for (let i = a; i < b; i++) target[pts.order[i]] = values[i - a];
|
|
272
|
-
};
|
|
273
289
|
const run = (graph, target, node2) => {
|
|
274
290
|
if (!graph) return;
|
|
275
|
-
const
|
|
291
|
+
const values = new Float32Array(b - a);
|
|
276
292
|
parts.push(
|
|
277
|
-
sampleNoiseAsync(workers, graph, s.seed, local,
|
|
293
|
+
sampleNoiseAsync(workers, graph, s.seed, local, values, {
|
|
278
294
|
origin: o,
|
|
279
295
|
...node2 ? { node: node2 } : {}
|
|
280
|
-
}).then(() =>
|
|
296
|
+
}).then(() => {
|
|
297
|
+
for (let i = a; i < b; i++) target[pts.order[i]] = values[i - a];
|
|
298
|
+
})
|
|
281
299
|
);
|
|
282
300
|
};
|
|
283
|
-
run(planet.height,
|
|
284
|
-
run(planet.climate,
|
|
285
|
-
run(planet.climate,
|
|
301
|
+
run(planet.height, out.values);
|
|
302
|
+
run(planet.climate, out.temps, "temperature");
|
|
303
|
+
run(planet.climate, out.moist, "moisture");
|
|
286
304
|
}
|
|
287
|
-
Promise.all(parts).then(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
},
|
|
291
|
-
() => {
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
return job;
|
|
305
|
+
out.done = Promise.all(parts).then(() => {
|
|
306
|
+
});
|
|
307
|
+
return out;
|
|
295
308
|
}
|
|
296
309
|
function makeChunk(world, key, job, mesh, version, frame) {
|
|
297
310
|
const layout = chunkLayout(Math.round(Math.sqrt(mesh.heights.length)));
|
|
@@ -347,9 +360,11 @@ export {
|
|
|
347
360
|
COLLIDER_DELAY,
|
|
348
361
|
ColliderSet,
|
|
349
362
|
anchorQueries,
|
|
363
|
+
chunkSpec,
|
|
350
364
|
clearColliders,
|
|
351
365
|
collidersOf,
|
|
352
366
|
gatherAnchors,
|
|
367
|
+
sampleChunkAsync,
|
|
353
368
|
skirtDepth,
|
|
354
369
|
updateColliders,
|
|
355
370
|
wantedColliders
|
package/dist/components.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const Planet: import("@aethervtt/shard-core").ComponentDef<{
|
|
|
14
14
|
readonly vertexPixels: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
15
15
|
readonly colliderRadius: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
16
16
|
readonly skirts: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
17
|
+
readonly scatter: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"scatter/ScatterSet"> | null, "object">;
|
|
17
18
|
}>;
|
|
18
19
|
export declare const TerrainAnchor: import("@aethervtt/shard-core").ComponentDef<{
|
|
19
20
|
readonly enabled: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
@@ -45,5 +46,9 @@ export interface TerrainBudgetValue {
|
|
|
45
46
|
pool: number;
|
|
46
47
|
/** Collider chunks cached per planet. */
|
|
47
48
|
colliderCache: number;
|
|
49
|
+
/** Heightfields (0071): GPU page pool slots per terrain (about 22 KB each). */
|
|
50
|
+
pages: number;
|
|
51
|
+
/** Heightfields: pages uploaded to the pool per frame, at most. */
|
|
52
|
+
pagesPerFrame: number;
|
|
48
53
|
}
|
|
49
54
|
export declare const TerrainBudget: import("@aethervtt/shard-core").ResourceDef<TerrainBudgetValue>;
|
package/dist/components.js
CHANGED
|
@@ -64,6 +64,9 @@ const Planet = defineComponent(
|
|
|
64
64
|
skirts: t.bool({
|
|
65
65
|
default: true,
|
|
66
66
|
description: "Walls under chunk edges that hide cracks while neighbors change level."
|
|
67
|
+
}),
|
|
68
|
+
scatter: t.handle("scatter/ScatterSet", {
|
|
69
|
+
description: "Props and foliage everywhere on the planet (*.scatter.json; needs the scatter plugin). Each biome can add its own (Biome.scatter)."
|
|
67
70
|
})
|
|
68
71
|
},
|
|
69
72
|
{
|
|
@@ -119,13 +122,15 @@ const Chunk = defineComponent(
|
|
|
119
122
|
}
|
|
120
123
|
);
|
|
121
124
|
const TerrainBudget = defineResource("terrain/Budget", {
|
|
122
|
-
description: "How much terrain work a frame may do: chunk generations, GPU milliseconds, and how many chunks stay cached.",
|
|
125
|
+
description: "How much terrain work a frame may do: chunk generations, GPU milliseconds, page uploads, and how many chunks and pages stay cached.",
|
|
123
126
|
init: () => ({
|
|
124
127
|
chunksPerFrame: 8,
|
|
125
128
|
triangles: 2e6,
|
|
126
129
|
msPerFrame: 1.5,
|
|
127
130
|
pool: 2048,
|
|
128
|
-
colliderCache: 256
|
|
131
|
+
colliderCache: 256,
|
|
132
|
+
pages: 1024,
|
|
133
|
+
pagesPerFrame: 16
|
|
129
134
|
})
|
|
130
135
|
});
|
|
131
136
|
export {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type QuadSurface, QuadTree, type SelectionParams, type SelectionView } from './quadtree.js';
|
|
2
|
+
/**
|
|
3
|
+
* The cube-sphere as a quadtree surface (spec 0043): six roots, one per cube face, projected onto
|
|
4
|
+
* an ellipsoid of `radius` and `shape`. Each node keeps 9 test directions (corners, edge middles,
|
|
5
|
+
* center) for its bounds and the horizon test, and how far to lift them over the surface's bulge.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CubeSphere implements QuadSurface {
|
|
8
|
+
readonly roots = 6;
|
|
9
|
+
radius: number;
|
|
10
|
+
readonly shape: Float64Array<ArrayBuffer>;
|
|
11
|
+
/** Horizon test directions (unit, 9 per node). */
|
|
12
|
+
dirs: Float64Array<ArrayBuffer>;
|
|
13
|
+
/**
|
|
14
|
+
* 1 / cos(half the widest angle between neighboring test points, diagonals included): lifting
|
|
15
|
+
* the points by it puts the surface between them inside their hull (horizon test, bulge).
|
|
16
|
+
*/
|
|
17
|
+
lift: Float64Array<ArrayBuffer>;
|
|
18
|
+
grow(capacity: number): void;
|
|
19
|
+
initNode(tree: QuadTree, n: number): void;
|
|
20
|
+
/** Bounding sphere of the node's 9 test points at its lowest and highest heights, plus bulge. */
|
|
21
|
+
updateSphere(tree: QuadTree, n: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* Axis-aligned bounds of the node relative to `center`, from its test points at its lowest and
|
|
24
|
+
* highest heights plus the bulge between them.
|
|
25
|
+
*/
|
|
26
|
+
bounds(tree: QuadTree, n: number, center: ArrayLike<number>, out: {
|
|
27
|
+
[i: number]: number;
|
|
28
|
+
}): void;
|
|
29
|
+
culled(tree: QuadTree, n: number, view: SelectionView, params: SelectionParams): boolean;
|
|
30
|
+
/** Across a cube edge the neighbor is on another face: there always is one. */
|
|
31
|
+
neighbor(root: number, depth: number, x: number, y: number, edge: number, out: {
|
|
32
|
+
[i: number]: number;
|
|
33
|
+
}): boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A planet surface's quadtree (terrain or ocean): a `QuadTree` on a `CubeSphere`. The six roots
|
|
37
|
+
* are nodes 0–5, one per face.
|
|
38
|
+
*/
|
|
39
|
+
export declare class NodeTree extends QuadTree {
|
|
40
|
+
readonly cube: CubeSphere;
|
|
41
|
+
constructor(capacity?: number);
|
|
42
|
+
/** Each node's cube face (its root). */
|
|
43
|
+
get face(): Uint8Array;
|
|
44
|
+
get radius(): number;
|
|
45
|
+
get shape(): Float64Array;
|
|
46
|
+
/** Clears every node and makes the six roots (after a planet's shape or graphs change). */
|
|
47
|
+
reset(radius: number, shape: ArrayLike<number>, lowest: number, highest: number): void;
|
|
48
|
+
/** Root of a face (nodes 0–5). */
|
|
49
|
+
rootOf(face: number): number;
|
|
50
|
+
}
|