@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/quadtree.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { EDGE_LEFT
|
|
1
|
+
import { EDGE_LEFT } from "./cube.js";
|
|
2
2
|
const NODE_READY = 1;
|
|
3
3
|
const NODE_BOUNDS = 2;
|
|
4
4
|
const NONE = -1;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
class QuadTree {
|
|
6
|
+
surface;
|
|
7
7
|
capacity = 0;
|
|
8
8
|
count = 0;
|
|
9
|
-
face
|
|
9
|
+
/** The root each node is under (a cube face on a planet, a grid cell on a heightfield). */
|
|
10
|
+
root = new Uint8Array(0);
|
|
10
11
|
depth = new Uint8Array(0);
|
|
11
12
|
x = new Uint32Array(0);
|
|
12
13
|
y = new Uint32Array(0);
|
|
@@ -14,13 +15,11 @@ class NodeTree {
|
|
|
14
15
|
/** First of four children (in order (2x, 2y), (2x+1, 2y), (2x, 2y+1), (2x+1, 2y+1)), or −1. */
|
|
15
16
|
child = new Int32Array(0);
|
|
16
17
|
flags = new Uint8Array(0);
|
|
17
|
-
/** Bounding sphere in the
|
|
18
|
+
/** Bounding sphere in the surface's frame (f64): center xyz, radius. */
|
|
18
19
|
sphere = new Float64Array(0);
|
|
19
|
-
/** Height range (m
|
|
20
|
+
/** Height range (m): exact once NODE_BOUNDS, else inherited or the surface's. */
|
|
20
21
|
minH = new Float32Array(0);
|
|
21
22
|
maxH = new Float32Array(0);
|
|
22
|
-
/** Horizon test directions (unit, 9 per node). */
|
|
23
|
-
dirs = new Float64Array(0);
|
|
24
23
|
/** Stamps: last walk (frame), rendered and culled (walk stamp), forced split, last needed (frame). */
|
|
25
24
|
visited = new Uint32Array(0);
|
|
26
25
|
rendered = new Uint32Array(0);
|
|
@@ -36,7 +35,7 @@ class NodeTree {
|
|
|
36
35
|
* Prefetched and out-of-view nodes are only `used`; a full pool evicts them first.
|
|
37
36
|
*/
|
|
38
37
|
neededAt = new Uint32Array(0);
|
|
39
|
-
/** Render slot holding its mesh, or −1. */
|
|
38
|
+
/** Render slot holding its mesh (or its page), or −1. */
|
|
40
39
|
slot = new Int32Array(0);
|
|
41
40
|
/** Projected error (px) and distance when last requested: generation priority. */
|
|
42
41
|
priority = new Float32Array(0);
|
|
@@ -45,21 +44,15 @@ class NodeTree {
|
|
|
45
44
|
locks = new Int8Array(0);
|
|
46
45
|
/** Quadrants drawn when rendered (15: all; fewer while a split waits for some children). */
|
|
47
46
|
mask = new Uint8Array(0);
|
|
48
|
-
/** The
|
|
47
|
+
/** The content version the node's mesh was made for: older is stale (it renders until replaced). */
|
|
49
48
|
gen = new Uint32Array(0);
|
|
50
|
-
/**
|
|
51
|
-
* 1 / cos(half the widest angle between neighboring test points, diagonals included): lifting
|
|
52
|
-
* the points by it puts the surface between them inside their hull (horizon test, bulge).
|
|
53
|
-
*/
|
|
54
|
-
lift = new Float64Array(0);
|
|
55
49
|
/** Starts of freed child blocks. */
|
|
56
50
|
blocks = [];
|
|
57
|
-
radius = 1;
|
|
58
|
-
shape = new Float64Array([1, 1, 1]);
|
|
59
51
|
/** Height range for nodes whose heights aren't known yet. */
|
|
60
52
|
lowest = 0;
|
|
61
53
|
highest = 0;
|
|
62
|
-
constructor(capacity = 1024) {
|
|
54
|
+
constructor(surface, capacity = 1024) {
|
|
55
|
+
this.surface = surface;
|
|
63
56
|
this.grow(capacity);
|
|
64
57
|
}
|
|
65
58
|
grow(capacity) {
|
|
@@ -68,7 +61,7 @@ class NodeTree {
|
|
|
68
61
|
next.set(old);
|
|
69
62
|
return next;
|
|
70
63
|
};
|
|
71
|
-
this.
|
|
64
|
+
this.root = re(this.root);
|
|
72
65
|
this.depth = re(this.depth);
|
|
73
66
|
this.x = re(this.x);
|
|
74
67
|
this.y = re(this.y);
|
|
@@ -78,7 +71,6 @@ class NodeTree {
|
|
|
78
71
|
this.sphere = re(this.sphere, 4);
|
|
79
72
|
this.minH = re(this.minH);
|
|
80
73
|
this.maxH = re(this.maxH);
|
|
81
|
-
this.dirs = re(this.dirs, POINTS * 3);
|
|
82
74
|
this.visited = re(this.visited);
|
|
83
75
|
this.rendered = re(this.rendered);
|
|
84
76
|
this.forced = re(this.forced);
|
|
@@ -93,27 +85,21 @@ class NodeTree {
|
|
|
93
85
|
this.locks = re(this.locks, 4);
|
|
94
86
|
this.mask = re(this.mask);
|
|
95
87
|
this.gen = re(this.gen);
|
|
96
|
-
this.
|
|
88
|
+
this.surface.grow(capacity);
|
|
97
89
|
this.capacity = capacity;
|
|
98
90
|
}
|
|
99
|
-
/** Clears every node and makes the
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
/** Clears every node and makes the roots (after the surface's shape or contents change). */
|
|
92
|
+
resetRoots(lowest, highest) {
|
|
93
|
+
const roots = this.surface.roots;
|
|
94
|
+
if (roots > this.capacity) this.grow(Math.max(roots, this.capacity * 2));
|
|
95
|
+
this.count = roots;
|
|
102
96
|
this.blocks.length = 0;
|
|
103
|
-
this.radius = radius;
|
|
104
|
-
this.shape[0] = shape[0];
|
|
105
|
-
this.shape[1] = shape[1];
|
|
106
|
-
this.shape[2] = shape[2];
|
|
107
97
|
this.lowest = lowest;
|
|
108
98
|
this.highest = highest;
|
|
109
|
-
for (let
|
|
99
|
+
for (let r = 0; r < roots; r++) this.init(r, r, 0, 0, 0, NONE);
|
|
110
100
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return face;
|
|
114
|
-
}
|
|
115
|
-
init(n, face, depth, x, y, parent) {
|
|
116
|
-
this.face[n] = face;
|
|
101
|
+
init(n, root, depth, x, y, parent) {
|
|
102
|
+
this.root[n] = root;
|
|
117
103
|
this.depth[n] = depth;
|
|
118
104
|
this.x[n] = x;
|
|
119
105
|
this.y[n] = y;
|
|
@@ -134,28 +120,6 @@ class NodeTree {
|
|
|
134
120
|
this.gen[n] = 0;
|
|
135
121
|
this.mask[n] = 15;
|
|
136
122
|
this.locks.fill(-1, n * 4, n * 4 + 4);
|
|
137
|
-
const ext = nodeExtent(depth);
|
|
138
|
-
const u0 = -1 + x * ext;
|
|
139
|
-
const v0 = -1 + y * ext;
|
|
140
|
-
let k = n * POINTS * 3;
|
|
141
|
-
for (let j = 0; j < 3; j++) {
|
|
142
|
-
for (let i = 0; i < 3; i++) {
|
|
143
|
-
faceToDirection(face, u0 + ext * i / 2, v0 + ext * j / 2, this.dirs, k);
|
|
144
|
-
k += 3;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
let widest = 0;
|
|
148
|
-
const base = n * POINTS * 3;
|
|
149
|
-
for (let a = 0; a < POINTS; a++) {
|
|
150
|
-
for (let b = a + 1; b < POINTS; b++) {
|
|
151
|
-
if (Math.abs(a % 3 - b % 3) > 1 || Math.abs(Math.floor(a / 3) - Math.floor(b / 3)) > 1)
|
|
152
|
-
continue;
|
|
153
|
-
const d = this.dirs;
|
|
154
|
-
const cos = d[base + a * 3] * d[base + b * 3] + d[base + a * 3 + 1] * d[base + b * 3 + 1] + d[base + a * 3 + 2] * d[base + b * 3 + 2];
|
|
155
|
-
widest = Math.max(widest, Math.acos(Math.min(1, cos)));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
this.lift[n] = 1 / Math.cos(widest / 2) + 1e-9;
|
|
159
123
|
if (parent !== NONE && this.flags[parent] & NODE_BOUNDS) {
|
|
160
124
|
this.minH[n] = this.minH[parent];
|
|
161
125
|
this.maxH[n] = this.maxH[parent];
|
|
@@ -163,7 +127,8 @@ class NodeTree {
|
|
|
163
127
|
this.minH[n] = this.lowest;
|
|
164
128
|
this.maxH[n] = this.highest;
|
|
165
129
|
}
|
|
166
|
-
this.
|
|
130
|
+
this.surface.initNode(this, n);
|
|
131
|
+
this.surface.updateSphere(this, n);
|
|
167
132
|
}
|
|
168
133
|
/** Children of `n`, made if needed. Returns the first (the others follow it). */
|
|
169
134
|
split(n) {
|
|
@@ -176,11 +141,11 @@ class NodeTree {
|
|
|
176
141
|
c = this.count;
|
|
177
142
|
this.count += 4;
|
|
178
143
|
}
|
|
179
|
-
const
|
|
144
|
+
const root = this.root[n];
|
|
180
145
|
const depth = this.depth[n] + 1;
|
|
181
146
|
const x = this.x[n] * 2;
|
|
182
147
|
const y = this.y[n] * 2;
|
|
183
|
-
for (let i = 0; i < 4; i++) this.init(c + i,
|
|
148
|
+
for (let i = 0; i < 4; i++) this.init(c + i, root, depth, x + (i & 1), y + (i >> 1), n);
|
|
184
149
|
this.child[n] = c;
|
|
185
150
|
return c;
|
|
186
151
|
}
|
|
@@ -208,96 +173,17 @@ class NodeTree {
|
|
|
208
173
|
this.flags[n] |= NODE_BOUNDS;
|
|
209
174
|
this.updateSphere(n);
|
|
210
175
|
}
|
|
211
|
-
/**
|
|
176
|
+
/** Recomputes the node's bounding sphere from its height range. */
|
|
212
177
|
updateSphere(n) {
|
|
213
|
-
|
|
214
|
-
const s = this.shape;
|
|
215
|
-
const lo = this.radius + this.minH[n];
|
|
216
|
-
const hi = this.radius + this.maxH[n];
|
|
217
|
-
let minX = Infinity;
|
|
218
|
-
let minY = Infinity;
|
|
219
|
-
let minZ = Infinity;
|
|
220
|
-
let maxX = -Infinity;
|
|
221
|
-
let maxY = -Infinity;
|
|
222
|
-
let maxZ = -Infinity;
|
|
223
|
-
for (let p = 0; p < POINTS; p++) {
|
|
224
|
-
const o2 = (n * POINTS + p) * 3;
|
|
225
|
-
for (let k = 0; k < 2; k++) {
|
|
226
|
-
const r = k === 0 ? lo : hi;
|
|
227
|
-
const x = d[o2] * s[0] * r;
|
|
228
|
-
const y = d[o2 + 1] * s[1] * r;
|
|
229
|
-
const z = d[o2 + 2] * s[2] * r;
|
|
230
|
-
if (x < minX) minX = x;
|
|
231
|
-
if (y < minY) minY = y;
|
|
232
|
-
if (z < minZ) minZ = z;
|
|
233
|
-
if (x > maxX) maxX = x;
|
|
234
|
-
if (y > maxY) maxY = y;
|
|
235
|
-
if (z > maxZ) maxZ = z;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const cx = (minX + maxX) / 2;
|
|
239
|
-
const cy = (minY + maxY) / 2;
|
|
240
|
-
const cz = (minZ + maxZ) / 2;
|
|
241
|
-
let r2 = 0;
|
|
242
|
-
for (let p = 0; p < POINTS; p++) {
|
|
243
|
-
const o2 = (n * POINTS + p) * 3;
|
|
244
|
-
for (let k = 0; k < 2; k++) {
|
|
245
|
-
const r = k === 0 ? lo : hi;
|
|
246
|
-
const x = d[o2] * s[0] * r - cx;
|
|
247
|
-
const y = d[o2 + 1] * s[1] * r - cy;
|
|
248
|
-
const z = d[o2 + 2] * s[2] * r - cz;
|
|
249
|
-
const q = x * x + y * y + z * z;
|
|
250
|
-
if (q > r2) r2 = q;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const bulge = hi * (this.lift[n] - 1) + 1e-3;
|
|
254
|
-
const o = n * 4;
|
|
255
|
-
this.sphere[o] = cx;
|
|
256
|
-
this.sphere[o + 1] = cy;
|
|
257
|
-
this.sphere[o + 2] = cz;
|
|
258
|
-
this.sphere[o + 3] = Math.sqrt(r2) + bulge;
|
|
178
|
+
this.surface.updateSphere(this, n);
|
|
259
179
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Axis-aligned bounds of the node relative to `center` (its chunk's reference point), from its
|
|
262
|
-
* test points at its lowest and highest heights plus the bulge between them.
|
|
263
|
-
*/
|
|
180
|
+
/** Axis-aligned bounds of the node relative to `center` (its chunk's reference point). */
|
|
264
181
|
bounds(n, center, out) {
|
|
265
|
-
|
|
266
|
-
const s = this.shape;
|
|
267
|
-
const lo = this.radius + this.minH[n];
|
|
268
|
-
const hi = this.radius + this.maxH[n];
|
|
269
|
-
let minX = Infinity;
|
|
270
|
-
let minY = Infinity;
|
|
271
|
-
let minZ = Infinity;
|
|
272
|
-
let maxX = -Infinity;
|
|
273
|
-
let maxY = -Infinity;
|
|
274
|
-
let maxZ = -Infinity;
|
|
275
|
-
for (let p = 0; p < POINTS; p++) {
|
|
276
|
-
const o = (n * POINTS + p) * 3;
|
|
277
|
-
for (let k = 0; k < 2; k++) {
|
|
278
|
-
const r = k === 0 ? lo : hi;
|
|
279
|
-
const x = d[o] * s[0] * r - center[0];
|
|
280
|
-
const y = d[o + 1] * s[1] * r - center[1];
|
|
281
|
-
const z = d[o + 2] * s[2] * r - center[2];
|
|
282
|
-
if (x < minX) minX = x;
|
|
283
|
-
if (y < minY) minY = y;
|
|
284
|
-
if (z < minZ) minZ = z;
|
|
285
|
-
if (x > maxX) maxX = x;
|
|
286
|
-
if (y > maxY) maxY = y;
|
|
287
|
-
if (z > maxZ) maxZ = z;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const bulge = hi * (this.lift[n] - 1) + 1e-3;
|
|
291
|
-
out[0] = minX - bulge;
|
|
292
|
-
out[1] = minY - bulge;
|
|
293
|
-
out[2] = minZ - bulge;
|
|
294
|
-
out[3] = maxX + bulge;
|
|
295
|
-
out[4] = maxY + bulge;
|
|
296
|
-
out[5] = maxZ + bulge;
|
|
182
|
+
this.surface.bounds(this, n, center, out);
|
|
297
183
|
}
|
|
298
|
-
/** The node at (
|
|
299
|
-
find(
|
|
300
|
-
let n =
|
|
184
|
+
/** The node at (root, depth, x, y) if the tree has it, else the deepest existing ancestor. */
|
|
185
|
+
find(root, depth, x, y) {
|
|
186
|
+
let n = root;
|
|
301
187
|
for (let k = 0; k < depth; k++) {
|
|
302
188
|
const c = this.child[n];
|
|
303
189
|
if (c === NONE) return n;
|
|
@@ -308,7 +194,7 @@ class NodeTree {
|
|
|
308
194
|
}
|
|
309
195
|
/** Frees subtrees nobody has needed since frame `before`. */
|
|
310
196
|
prune(before, release) {
|
|
311
|
-
for (let
|
|
197
|
+
for (let r = 0; r < this.surface.roots; r++) this.pruneNode(r, before, release);
|
|
312
198
|
}
|
|
313
199
|
/** Returns whether `n` and its whole subtree are idle. */
|
|
314
200
|
pruneNode(n, before, release) {
|
|
@@ -349,67 +235,17 @@ function selectNodes(tree, view, params, frame, out) {
|
|
|
349
235
|
out.requestedCount = 0;
|
|
350
236
|
out.waiting = 0;
|
|
351
237
|
out.passes++;
|
|
352
|
-
for (let
|
|
353
|
-
tree.visited[
|
|
354
|
-
tree.used[
|
|
355
|
-
if (
|
|
356
|
-
else visit(tree,
|
|
238
|
+
for (let r = 0; r < tree.surface.roots; r++) {
|
|
239
|
+
tree.visited[r] = frame;
|
|
240
|
+
tree.used[r] = frame;
|
|
241
|
+
if (tree.surface.culled(tree, r, view, params)) tree.culled[r] = passStamp;
|
|
242
|
+
else visit(tree, r, view, params, frame, out);
|
|
357
243
|
}
|
|
358
244
|
if (!balance(tree, frame, out)) break;
|
|
359
245
|
}
|
|
360
246
|
out.stamp = passStamp;
|
|
361
247
|
setLocks(tree, out);
|
|
362
248
|
}
|
|
363
|
-
function culledNode(tree, n, view, params) {
|
|
364
|
-
const s = tree.sphere;
|
|
365
|
-
const o = n * 4;
|
|
366
|
-
const cx = s[o];
|
|
367
|
-
const cy = s[o + 1];
|
|
368
|
-
const cz = s[o + 2];
|
|
369
|
-
const r = s[o + 3];
|
|
370
|
-
const f = view.frustum;
|
|
371
|
-
const d = tree.dirs;
|
|
372
|
-
const sh = tree.shape;
|
|
373
|
-
const lo = tree.radius + tree.minH[n];
|
|
374
|
-
const hi = (tree.radius + tree.maxH[n]) * tree.lift[n];
|
|
375
|
-
for (let p = 0; p < view.planes; p++) {
|
|
376
|
-
const q = p * 4;
|
|
377
|
-
const nx = f[q];
|
|
378
|
-
const ny = f[q + 1];
|
|
379
|
-
const nz = f[q + 2];
|
|
380
|
-
const dist = nx * cx + ny * cy + nz * cz + f[q + 3];
|
|
381
|
-
if (dist < -r) return true;
|
|
382
|
-
if (dist > r) continue;
|
|
383
|
-
let inside = false;
|
|
384
|
-
for (let k = 0; k < POINTS && !inside; k++) {
|
|
385
|
-
const i = (n * POINTS + k) * 3;
|
|
386
|
-
const dx = d[i] * sh[0];
|
|
387
|
-
const dy = d[i + 1] * sh[1];
|
|
388
|
-
const dz = d[i + 2] * sh[2];
|
|
389
|
-
const along = nx * dx + ny * dy + nz * dz;
|
|
390
|
-
if (along * lo + f[q + 3] >= 0 || along * hi + f[q + 3] >= 0) inside = true;
|
|
391
|
-
}
|
|
392
|
-
if (!inside) return true;
|
|
393
|
-
}
|
|
394
|
-
if (params.occluder <= 0) return false;
|
|
395
|
-
const inv = 1 / params.occluder;
|
|
396
|
-
const px = view.position[0] / sh[0] * inv;
|
|
397
|
-
const py = view.position[1] / sh[1] * inv;
|
|
398
|
-
const pz = view.position[2] / sh[2] * inv;
|
|
399
|
-
const vh2 = px * px + py * py + pz * pz - 1;
|
|
400
|
-
if (vh2 <= 0) return false;
|
|
401
|
-
const top = hi * inv;
|
|
402
|
-
for (let k = 0; k < POINTS; k++) {
|
|
403
|
-
const i = (n * POINTS + k) * 3;
|
|
404
|
-
const tx = d[i] * top - px;
|
|
405
|
-
const ty = d[i + 1] * top - py;
|
|
406
|
-
const tz = d[i + 2] * top - pz;
|
|
407
|
-
const dot = -(tx * px + ty * py + tz * pz);
|
|
408
|
-
if (dot <= vh2) return false;
|
|
409
|
-
if (dot * dot / (tx * tx + ty * ty + tz * tz) <= vh2) return false;
|
|
410
|
-
}
|
|
411
|
-
return true;
|
|
412
|
-
}
|
|
413
249
|
function nearView(tree, n, view) {
|
|
414
250
|
const s = tree.sphere;
|
|
415
251
|
const o = n * 4;
|
|
@@ -489,7 +325,7 @@ function visit(tree, n, view, params, frame, out) {
|
|
|
489
325
|
tree.visited[k] = frame;
|
|
490
326
|
tree.used[k] = frame;
|
|
491
327
|
tree.neededAt[k] = frame;
|
|
492
|
-
if (
|
|
328
|
+
if (tree.surface.culled(tree, k, view, params)) {
|
|
493
329
|
tree.culled[k] = passStamp;
|
|
494
330
|
if (!(tree.flags[k] & NODE_READY)) {
|
|
495
331
|
const d = nodeDistance(tree, k, view.position);
|
|
@@ -527,7 +363,7 @@ function visit(tree, n, view, params, frame, out) {
|
|
|
527
363
|
if (tree.flags[k] & NODE_READY) continue;
|
|
528
364
|
const d = nodeDistance(tree, k, view.position);
|
|
529
365
|
const e = view.pixelsPerRadian * params.errors[depth + 1] / Math.max(d, 1e-3);
|
|
530
|
-
const scale = !
|
|
366
|
+
const scale = !tree.surface.culled(tree, k, view, params) ? 1 : nearView(tree, k, view) ? NEAR_VIEW : OUT_OF_VIEW;
|
|
531
367
|
request(tree, k, e * scale, d, frame, out);
|
|
532
368
|
}
|
|
533
369
|
}
|
|
@@ -544,7 +380,8 @@ function balance(tree, frame, out) {
|
|
|
544
380
|
const depth = tree.depth[n];
|
|
545
381
|
if (depth < 2) continue;
|
|
546
382
|
for (let edge = 0; edge <= EDGE_LEFT; edge++) {
|
|
547
|
-
|
|
383
|
+
if (!tree.surface.neighbor(tree.root[n], depth, tree.x[n], tree.y[n], edge, neighbor))
|
|
384
|
+
continue;
|
|
548
385
|
const m = tree.find(neighbor[0], depth, neighbor[1], neighbor[2]);
|
|
549
386
|
const k = renderedAbove(tree, m);
|
|
550
387
|
if (k === NONE) continue;
|
|
@@ -566,8 +403,7 @@ function setLocks(tree, out) {
|
|
|
566
403
|
const depth = tree.depth[n];
|
|
567
404
|
for (let edge = 0; edge <= EDGE_LEFT; edge++) {
|
|
568
405
|
let lock = -1;
|
|
569
|
-
if (depth > 0) {
|
|
570
|
-
neighborNode(tree.face[n], depth, tree.x[n], tree.y[n], edge, neighbor);
|
|
406
|
+
if (depth > 0 && tree.surface.neighbor(tree.root[n], depth, tree.x[n], tree.y[n], edge, neighbor)) {
|
|
571
407
|
const m = tree.find(neighbor[0], depth, neighbor[1], neighbor[2]);
|
|
572
408
|
const k = renderedAbove(tree, m);
|
|
573
409
|
if (k !== NONE) lock = tree.depth[k] < depth ? 1 : -1;
|
|
@@ -580,7 +416,7 @@ function setLocks(tree, out) {
|
|
|
580
416
|
export {
|
|
581
417
|
NODE_BOUNDS,
|
|
582
418
|
NODE_READY,
|
|
583
|
-
|
|
419
|
+
QuadTree,
|
|
584
420
|
createSelection,
|
|
585
421
|
nearAnchor,
|
|
586
422
|
selectNodes
|
package/dist/render.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { MaterialAsset } from '@aethervtt/shard-render';
|
|
|
5
5
|
import type { App } from '@aethervtt/shard-runtime';
|
|
6
6
|
import { Texture } from '@aethervtt/shard-texture';
|
|
7
7
|
import { type ColliderChunk } from './colliders.js';
|
|
8
|
+
import type { NodeTree } from './cube-sphere.js';
|
|
8
9
|
import { type KernelGraph } from './kernel.js';
|
|
9
10
|
import type { PlanetRuntime } from './planet.js';
|
|
10
|
-
import { type NodeTree } from './quadtree.js';
|
|
11
11
|
/**
|
|
12
12
|
* One render chunk: an entity drawing its range of an arena's vertex buffers, which a GPU job
|
|
13
13
|
* writes (or a collider chunk's vertices are copied into).
|
|
@@ -138,6 +138,12 @@ export declare class PlanetRender {
|
|
|
138
138
|
nextJobId(): number;
|
|
139
139
|
}
|
|
140
140
|
export declare function renderOf(world: World, rt: PlanetRuntime): PlanetRender;
|
|
141
|
+
/** The camera that drives selection: the active one with the lowest `order`, then the lowest entity. */
|
|
142
|
+
export declare function pickCamera(q: Query): {
|
|
143
|
+
entity: Entity;
|
|
144
|
+
table: number;
|
|
145
|
+
row: number;
|
|
146
|
+
} | undefined;
|
|
141
147
|
/**
|
|
142
148
|
* Chooses each planet's chunks for the selecting camera (spec 0043): walks both quadtrees, gives
|
|
143
149
|
* new nodes pool slots and queues their GPU jobs within the frame's budget (nodes at collider depth
|
package/dist/render.js
CHANGED
|
@@ -34,11 +34,12 @@ import {
|
|
|
34
34
|
propagateSubtree,
|
|
35
35
|
Transform
|
|
36
36
|
} from "@aethervtt/shard-transform";
|
|
37
|
-
import { chunkCenter
|
|
37
|
+
import { chunkCenter } from "./chunk.js";
|
|
38
38
|
import { collidersOf, skirtDepth } from "./colliders.js";
|
|
39
39
|
import { Chunk, Planet, TerrainBudget } from "./components.js";
|
|
40
40
|
import { keyString } from "./cube.js";
|
|
41
|
-
import {
|
|
41
|
+
import { chunkIndices, chunkLayout } from "./grid-mesh.js";
|
|
42
|
+
import { TerrainWorld } from "./heights.js";
|
|
42
43
|
import {
|
|
43
44
|
GEN_CLIMATE,
|
|
44
45
|
GEN_HEIGHT,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
POINT_FLOATS,
|
|
50
51
|
registerKernel
|
|
51
52
|
} from "./kernel.js";
|
|
53
|
+
import { adaptLodBias } from "./lod.js";
|
|
52
54
|
import { OceanMaterial, PlanetMaterial, TERRAIN_SHADERS, TEXTURE_PERIOD } from "./material.js";
|
|
53
55
|
import { createChunkPoints, prepareChunkPoints, SNAP } from "./points.js";
|
|
54
56
|
import { NODE_READY, selectNodes } from "./quadtree.js";
|
|
@@ -187,8 +189,7 @@ function selectionParams(rt, kind) {
|
|
|
187
189
|
const chunkTriangles = (n) => 2 * (n - 1) * (n - 1);
|
|
188
190
|
function adaptDetail(rt, triangles) {
|
|
189
191
|
const drawn = rt.selection.renderedCount * chunkTriangles(rt.settings.resolution);
|
|
190
|
-
|
|
191
|
-
else if (triangles <= 0 || drawn < triangles * 0.85) rt.lodBias = Math.max(1, rt.lodBias / 1.005);
|
|
192
|
+
rt.lodBias = adaptLodBias(rt.lodBias, drawn, triangles);
|
|
192
193
|
}
|
|
193
194
|
const selectChunks = defineSystem({
|
|
194
195
|
name: "terrain/select",
|
|
@@ -196,7 +197,7 @@ const selectChunks = defineSystem({
|
|
|
196
197
|
setup: (world) => ({ cameras: world.query({ with: [Camera3d, GlobalTransform] }) }),
|
|
197
198
|
run: (s, world) => {
|
|
198
199
|
if (!world.tryResource(GpuAssetsResource)) return;
|
|
199
|
-
const state = world.resource(
|
|
200
|
+
const state = world.resource(TerrainWorld);
|
|
200
201
|
const frame = state.frame;
|
|
201
202
|
const budget = world.resource(TerrainBudget);
|
|
202
203
|
let jobs = budget.chunksPerFrame;
|
|
@@ -1177,7 +1178,7 @@ function registerNode(app) {
|
|
|
1177
1178
|
const frame = world.resource(Time).frame;
|
|
1178
1179
|
if (frame === lastFrame) return;
|
|
1179
1180
|
lastFrame = frame;
|
|
1180
|
-
const state = world.tryResource(
|
|
1181
|
+
const state = world.tryResource(TerrainWorld);
|
|
1181
1182
|
if (!state) return;
|
|
1182
1183
|
for (const rt of state.planets.values()) {
|
|
1183
1184
|
const pr = rt.parts.get("render");
|
|
@@ -1222,6 +1223,7 @@ export {
|
|
|
1222
1223
|
PlanetRender,
|
|
1223
1224
|
TerrainDebug,
|
|
1224
1225
|
cleanupRender,
|
|
1226
|
+
pickCamera,
|
|
1225
1227
|
registerNode,
|
|
1226
1228
|
renderOf,
|
|
1227
1229
|
selectChunks,
|
package/dist/test-planet.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface PlanetOptions {
|
|
|
19
19
|
errorPixels?: number;
|
|
20
20
|
/** MSAA samples (default 1). */
|
|
21
21
|
msaa?: 1 | 4;
|
|
22
|
+
/** A fixed render scale (0051): the view renders at it and is upscaled, as a window view is. */
|
|
23
|
+
renderScale?: number;
|
|
22
24
|
/** Planet.vertexPixels (default: the component's). */
|
|
23
25
|
vertexPixels?: number;
|
|
24
26
|
minSpacing?: number;
|
|
@@ -57,3 +59,40 @@ export declare function sunOver(p: PlanetApp, direction: ArrayLike<number>, elev
|
|
|
57
59
|
* sphere of the lowest possible surface (radius − heightScale) is terrain whatever the LOD.
|
|
58
60
|
*/
|
|
59
61
|
export declare function holes(data: Uint8Array, W: number, H: number, FOV: number, eye: ArrayLike<number>, right: number[], up: number[], forward: number[], lowest: number): number;
|
|
62
|
+
/**
|
|
63
|
+
* 0043's Earth descent (the bench's `terrain/descent-frame` and 0075's `planet-descent` scenario):
|
|
64
|
+
* the height graph of an Earth-sized planet with 3 km of relief.
|
|
65
|
+
*/
|
|
66
|
+
export declare const EARTH_HEIGHT: {
|
|
67
|
+
output: string;
|
|
68
|
+
nodes: {
|
|
69
|
+
continents: {
|
|
70
|
+
fbm: {
|
|
71
|
+
source: string;
|
|
72
|
+
octaves: number;
|
|
73
|
+
frequency: number;
|
|
74
|
+
seed: number;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
ridges: {
|
|
78
|
+
ridged: {
|
|
79
|
+
source: string;
|
|
80
|
+
octaves: number;
|
|
81
|
+
frequency: number;
|
|
82
|
+
seed: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
h: {
|
|
86
|
+
add: (string | {
|
|
87
|
+
multiply: (string | number)[];
|
|
88
|
+
})[];
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* The Earth descent's camera path: from 40 000 km to 2 m over `frames` frames, exponentially,
|
|
94
|
+
* looking at the ground ahead. `look(f)` places the camera at frame `f` (held at 2 m after).
|
|
95
|
+
*/
|
|
96
|
+
export declare function earthDescent(p: PlanetApp, radius: number, frames: number): {
|
|
97
|
+
look(frame: number): void;
|
|
98
|
+
};
|
package/dist/test-planet.js
CHANGED
|
@@ -23,12 +23,18 @@ import {
|
|
|
23
23
|
TransformPlugin
|
|
24
24
|
} from "@aethervtt/shard-transform";
|
|
25
25
|
import { Planet, TerrainBudget } from "./components.js";
|
|
26
|
-
import {
|
|
26
|
+
import { heightAt, TerrainWorld } from "./heights.js";
|
|
27
27
|
import { terrainPlugin } from "./plugin.js";
|
|
28
28
|
async function planetApp(gpu, o) {
|
|
29
29
|
const app = new App().addPlugin(TransformPlugin);
|
|
30
30
|
if (gpu)
|
|
31
|
-
app.addPlugin(
|
|
31
|
+
app.addPlugin(
|
|
32
|
+
renderPlugin({ gpu, windowView: false }),
|
|
33
|
+
forwardPlugin({
|
|
34
|
+
msaa: o.msaa ?? 1,
|
|
35
|
+
...o.renderScale ? { renderScale: { mode: "fixed", scale: o.renderScale } } : {}
|
|
36
|
+
})
|
|
37
|
+
);
|
|
32
38
|
if (o.physics) app.addPlugin(physics3dPlugin());
|
|
33
39
|
app.addPlugin(terrainPlugin(), ...o.extra ?? []);
|
|
34
40
|
await app.init();
|
|
@@ -66,7 +72,8 @@ async function planetApp(gpu, o) {
|
|
|
66
72
|
label: "terrain-test",
|
|
67
73
|
width: o.width ?? 96,
|
|
68
74
|
height: o.heightPx ?? 64,
|
|
69
|
-
pixelRatio: o.pixelRatio ?? 1
|
|
75
|
+
pixelRatio: o.pixelRatio ?? 1,
|
|
76
|
+
renderScale: o.renderScale !== void 0
|
|
70
77
|
});
|
|
71
78
|
const targetRef = world.resource(RenderTargets).add(target, "terrain-test");
|
|
72
79
|
camera = world.spawn(
|
|
@@ -103,7 +110,7 @@ async function planetApp(gpu, o) {
|
|
|
103
110
|
camera,
|
|
104
111
|
sun,
|
|
105
112
|
view,
|
|
106
|
-
runtime: () => world.resource(
|
|
113
|
+
runtime: () => world.resource(TerrainWorld).planets.get(planet)
|
|
107
114
|
};
|
|
108
115
|
}
|
|
109
116
|
function placeCamera(p, eye, target) {
|
|
@@ -179,8 +186,37 @@ function holes(data, W, H, FOV, eye, right, up, forward, lowest) {
|
|
|
179
186
|
}
|
|
180
187
|
return count;
|
|
181
188
|
}
|
|
189
|
+
const EARTH_HEIGHT = {
|
|
190
|
+
output: "h",
|
|
191
|
+
nodes: {
|
|
192
|
+
continents: { fbm: { source: "simplex", octaves: 7, frequency: 3e-6, seed: 1 } },
|
|
193
|
+
ridges: { ridged: { source: "simplex", octaves: 8, frequency: 3e-4, seed: 2 } },
|
|
194
|
+
h: { add: ["continents", { multiply: ["ridges", 0.15] }] }
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
function earthDescent(p, radius, frames) {
|
|
198
|
+
const rt = p.runtime();
|
|
199
|
+
const n = [0.3, 0.9, 0.3].map((v, _, a) => v / Math.hypot(a[0], a[1], a[2]));
|
|
200
|
+
const east = [n[2], 0, -n[0]].map((v, _, a) => v / Math.hypot(a[0], a[1], a[2]));
|
|
201
|
+
const ground = Math.max(0, heightAt(rt, n[0], n[1], n[2]));
|
|
202
|
+
const ratio = (2 / 4e7) ** (1 / frames);
|
|
203
|
+
return {
|
|
204
|
+
look(frame) {
|
|
205
|
+
const altitude = 4e7 * ratio ** Math.min(frame, frames);
|
|
206
|
+
const eye = n.map((v) => v * (radius + ground + altitude));
|
|
207
|
+
const ahead = Math.min(altitude * 1.2, radius * 0.5);
|
|
208
|
+
placeCamera(
|
|
209
|
+
p,
|
|
210
|
+
eye,
|
|
211
|
+
n.map((v, k) => v * (radius + ground) + east[k] * ahead)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
182
216
|
export {
|
|
217
|
+
EARTH_HEIGHT,
|
|
183
218
|
capture,
|
|
219
|
+
earthDescent,
|
|
184
220
|
holes,
|
|
185
221
|
placeCamera,
|
|
186
222
|
planetApp,
|
package/dist/testing.d.ts
CHANGED
|
@@ -40,3 +40,5 @@ export interface WalkResult {
|
|
|
40
40
|
* all deterministic.
|
|
41
41
|
*/
|
|
42
42
|
export declare function walkChecksum(characters?: number, seconds?: number): Promise<WalkResult>;
|
|
43
|
+
export { capture as captureHeightfield, type HeightfieldApp, type HeightfieldOptions, type HeightfieldWalk, heightfieldApp, heightfieldWalk, holes as heightfieldHoles, lookAt as lookAtTerrain, settleHeightfield, sourceAsset, untilStreaming, VALLEY_HILLS, valleySource, } from './heightfield/testing.js';
|
|
44
|
+
export { capture, EARTH_HEIGHT, earthDescent, holes, type PlanetApp, type PlanetOptions, placeCamera, planetApp, settleTerrain, sunOver, } from './test-planet.js';
|
package/dist/testing.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "@aethervtt/shard-transform";
|
|
20
20
|
import { collidersOf } from "./colliders.js";
|
|
21
21
|
import { Planet } from "./components.js";
|
|
22
|
-
import { planetHeightAt,
|
|
22
|
+
import { planetHeightAt, TerrainWorld } from "./heights.js";
|
|
23
23
|
import { terrainPlugin } from "./plugin.js";
|
|
24
24
|
const WALK_PLANET = {
|
|
25
25
|
output: "h",
|
|
@@ -79,7 +79,7 @@ async function walkChecksum(characters = 4, seconds = 3) {
|
|
|
79
79
|
f64.set(p, k * 3);
|
|
80
80
|
w.despawn(c);
|
|
81
81
|
}
|
|
82
|
-
const colliders = collidersOf(w.resource(
|
|
82
|
+
const colliders = collidersOf(w.resource(TerrainWorld).planets.get(planet)).chunks.size;
|
|
83
83
|
new DataView(bytes.buffer).setUint32(characters * 24, colliders, true);
|
|
84
84
|
let hash = 2166136261;
|
|
85
85
|
for (let i = 0; i < bytes.length; i++) hash = Math.imul(hash ^ bytes[i], 16777619);
|
|
@@ -90,7 +90,47 @@ async function walkChecksum(characters = 4, seconds = 3) {
|
|
|
90
90
|
ms: performance.now() - t0
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
import {
|
|
94
|
+
capture,
|
|
95
|
+
heightfieldApp,
|
|
96
|
+
heightfieldWalk,
|
|
97
|
+
holes,
|
|
98
|
+
lookAt,
|
|
99
|
+
settleHeightfield,
|
|
100
|
+
sourceAsset,
|
|
101
|
+
untilStreaming,
|
|
102
|
+
VALLEY_HILLS,
|
|
103
|
+
valleySource
|
|
104
|
+
} from "./heightfield/testing.js";
|
|
105
|
+
import {
|
|
106
|
+
capture as capture2,
|
|
107
|
+
EARTH_HEIGHT,
|
|
108
|
+
earthDescent,
|
|
109
|
+
holes as holes2,
|
|
110
|
+
placeCamera,
|
|
111
|
+
planetApp,
|
|
112
|
+
settleTerrain,
|
|
113
|
+
sunOver
|
|
114
|
+
} from "./test-planet.js";
|
|
93
115
|
export {
|
|
116
|
+
EARTH_HEIGHT,
|
|
117
|
+
VALLEY_HILLS,
|
|
94
118
|
WALK_PLANET,
|
|
119
|
+
capture2 as capture,
|
|
120
|
+
capture as captureHeightfield,
|
|
121
|
+
earthDescent,
|
|
122
|
+
heightfieldApp,
|
|
123
|
+
holes as heightfieldHoles,
|
|
124
|
+
heightfieldWalk,
|
|
125
|
+
holes2 as holes,
|
|
126
|
+
lookAt as lookAtTerrain,
|
|
127
|
+
placeCamera,
|
|
128
|
+
planetApp,
|
|
129
|
+
settleHeightfield,
|
|
130
|
+
settleTerrain,
|
|
131
|
+
sourceAsset,
|
|
132
|
+
sunOver,
|
|
133
|
+
untilStreaming,
|
|
134
|
+
valleySource,
|
|
95
135
|
walkChecksum
|
|
96
136
|
};
|