@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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/biomes.d.ts +65 -0
- package/dist/biomes.js +163 -0
- package/dist/chunk.d.ts +99 -0
- package/dist/chunk.js +304 -0
- package/dist/colliders.d.ts +86 -0
- package/dist/colliders.js +356 -0
- package/dist/components.d.ts +49 -0
- package/dist/components.js +137 -0
- package/dist/cube.d.ts +51 -0
- package/dist/cube.js +87 -0
- package/dist/frame.d.ts +21 -0
- package/dist/frame.js +63 -0
- package/dist/heights.d.ts +51 -0
- package/dist/heights.js +137 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +193 -0
- package/dist/kernel.d.ts +47 -0
- package/dist/kernel.js +243 -0
- package/dist/lod.d.ts +22 -0
- package/dist/lod.js +44 -0
- package/dist/material.d.ts +14 -0
- package/dist/material.js +426 -0
- package/dist/methods.d.ts +12 -0
- package/dist/methods.js +259 -0
- package/dist/nav.d.ts +14 -0
- package/dist/nav.js +114 -0
- package/dist/overlays.d.ts +6 -0
- package/dist/overlays.js +67 -0
- package/dist/planet.d.ts +106 -0
- package/dist/planet.js +313 -0
- package/dist/plugin.d.ts +23 -0
- package/dist/plugin.js +98 -0
- package/dist/points.d.ts +62 -0
- package/dist/points.js +207 -0
- package/dist/quadtree.d.ts +153 -0
- package/dist/quadtree.js +587 -0
- package/dist/render.d.ts +179 -0
- package/dist/render.js +1222 -0
- package/dist/test-planet.d.ts +59 -0
- package/dist/test-planet.js +189 -0
- package/dist/testing.d.ts +42 -0
- package/dist/testing.js +96 -0
- package/dist/view.d.ts +12 -0
- package/dist/view.js +61 -0
- package/package.json +50 -0
package/dist/quadtree.js
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { EDGE_LEFT, faceToDirection, neighborNode, nodeExtent } from "./cube.js";
|
|
2
|
+
const NODE_READY = 1;
|
|
3
|
+
const NODE_BOUNDS = 2;
|
|
4
|
+
const NONE = -1;
|
|
5
|
+
const POINTS = 9;
|
|
6
|
+
class NodeTree {
|
|
7
|
+
capacity = 0;
|
|
8
|
+
count = 0;
|
|
9
|
+
face = new Uint8Array(0);
|
|
10
|
+
depth = new Uint8Array(0);
|
|
11
|
+
x = new Uint32Array(0);
|
|
12
|
+
y = new Uint32Array(0);
|
|
13
|
+
parent = new Int32Array(0);
|
|
14
|
+
/** First of four children (in order (2x, 2y), (2x+1, 2y), (2x, 2y+1), (2x+1, 2y+1)), or −1. */
|
|
15
|
+
child = new Int32Array(0);
|
|
16
|
+
flags = new Uint8Array(0);
|
|
17
|
+
/** Bounding sphere in the planet frame (f64): center xyz, radius. */
|
|
18
|
+
sphere = new Float64Array(0);
|
|
19
|
+
/** Height range (m above radius): exact once NODE_BOUNDS, else inherited or the planet's. */
|
|
20
|
+
minH = new Float32Array(0);
|
|
21
|
+
maxH = new Float32Array(0);
|
|
22
|
+
/** Horizon test directions (unit, 9 per node). */
|
|
23
|
+
dirs = new Float64Array(0);
|
|
24
|
+
/** Stamps: last walk (frame), rendered and culled (walk stamp), forced split, last needed (frame). */
|
|
25
|
+
visited = new Uint32Array(0);
|
|
26
|
+
rendered = new Uint32Array(0);
|
|
27
|
+
forced = new Uint32Array(0);
|
|
28
|
+
culled = new Uint32Array(0);
|
|
29
|
+
used = new Uint32Array(0);
|
|
30
|
+
/** Pass stamp when the node was last requested, so each pass lists it once. */
|
|
31
|
+
requestedAt = new Uint32Array(0);
|
|
32
|
+
/** Frame the node was last split (children walked): merging then takes a margin. */
|
|
33
|
+
splitAt = new Uint32Array(0);
|
|
34
|
+
/**
|
|
35
|
+
* Frame the node was last needed on screen: rendered, or a visible child a split waits for.
|
|
36
|
+
* Prefetched and out-of-view nodes are only `used`; a full pool evicts them first.
|
|
37
|
+
*/
|
|
38
|
+
neededAt = new Uint32Array(0);
|
|
39
|
+
/** Render slot holding its mesh, or −1. */
|
|
40
|
+
slot = new Int32Array(0);
|
|
41
|
+
/** Projected error (px) and distance when last requested: generation priority. */
|
|
42
|
+
priority = new Float32Array(0);
|
|
43
|
+
distance = new Float64Array(0);
|
|
44
|
+
/** Edge locks when rendered: bottom, right, top, left (−1, 0, 1). */
|
|
45
|
+
locks = new Int8Array(0);
|
|
46
|
+
/** Quadrants drawn when rendered (15: all; fewer while a split waits for some children). */
|
|
47
|
+
mask = new Uint8Array(0);
|
|
48
|
+
/** The planet version the node's mesh was made for: older is stale (it renders until replaced). */
|
|
49
|
+
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
|
+
/** Starts of freed child blocks. */
|
|
56
|
+
blocks = [];
|
|
57
|
+
radius = 1;
|
|
58
|
+
shape = new Float64Array([1, 1, 1]);
|
|
59
|
+
/** Height range for nodes whose heights aren't known yet. */
|
|
60
|
+
lowest = 0;
|
|
61
|
+
highest = 0;
|
|
62
|
+
constructor(capacity = 1024) {
|
|
63
|
+
this.grow(capacity);
|
|
64
|
+
}
|
|
65
|
+
grow(capacity) {
|
|
66
|
+
const re = (old, width = 1) => {
|
|
67
|
+
const next = new old.constructor(capacity * width);
|
|
68
|
+
next.set(old);
|
|
69
|
+
return next;
|
|
70
|
+
};
|
|
71
|
+
this.face = re(this.face);
|
|
72
|
+
this.depth = re(this.depth);
|
|
73
|
+
this.x = re(this.x);
|
|
74
|
+
this.y = re(this.y);
|
|
75
|
+
this.parent = re(this.parent);
|
|
76
|
+
this.child = re(this.child);
|
|
77
|
+
this.flags = re(this.flags);
|
|
78
|
+
this.sphere = re(this.sphere, 4);
|
|
79
|
+
this.minH = re(this.minH);
|
|
80
|
+
this.maxH = re(this.maxH);
|
|
81
|
+
this.dirs = re(this.dirs, POINTS * 3);
|
|
82
|
+
this.visited = re(this.visited);
|
|
83
|
+
this.rendered = re(this.rendered);
|
|
84
|
+
this.forced = re(this.forced);
|
|
85
|
+
this.culled = re(this.culled);
|
|
86
|
+
this.used = re(this.used);
|
|
87
|
+
this.requestedAt = re(this.requestedAt);
|
|
88
|
+
this.splitAt = re(this.splitAt);
|
|
89
|
+
this.neededAt = re(this.neededAt);
|
|
90
|
+
this.slot = re(this.slot);
|
|
91
|
+
this.priority = re(this.priority);
|
|
92
|
+
this.distance = re(this.distance);
|
|
93
|
+
this.locks = re(this.locks, 4);
|
|
94
|
+
this.mask = re(this.mask);
|
|
95
|
+
this.gen = re(this.gen);
|
|
96
|
+
this.lift = re(this.lift);
|
|
97
|
+
this.capacity = capacity;
|
|
98
|
+
}
|
|
99
|
+
/** Clears every node and makes the six roots (after a planet's shape or graphs change). */
|
|
100
|
+
reset(radius, shape, lowest, highest) {
|
|
101
|
+
this.count = 6;
|
|
102
|
+
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
|
+
this.lowest = lowest;
|
|
108
|
+
this.highest = highest;
|
|
109
|
+
for (let f = 0; f < 6; f++) this.init(f, f, 0, 0, 0, NONE);
|
|
110
|
+
}
|
|
111
|
+
/** Root of a face (nodes 0–5). */
|
|
112
|
+
root(face) {
|
|
113
|
+
return face;
|
|
114
|
+
}
|
|
115
|
+
init(n, face, depth, x, y, parent) {
|
|
116
|
+
this.face[n] = face;
|
|
117
|
+
this.depth[n] = depth;
|
|
118
|
+
this.x[n] = x;
|
|
119
|
+
this.y[n] = y;
|
|
120
|
+
this.parent[n] = parent;
|
|
121
|
+
this.child[n] = NONE;
|
|
122
|
+
this.flags[n] = 0;
|
|
123
|
+
this.slot[n] = NONE;
|
|
124
|
+
this.visited[n] = 0;
|
|
125
|
+
this.rendered[n] = 0;
|
|
126
|
+
this.forced[n] = 0;
|
|
127
|
+
this.culled[n] = 0;
|
|
128
|
+
this.used[n] = 0;
|
|
129
|
+
this.requestedAt[n] = 0;
|
|
130
|
+
this.splitAt[n] = 0;
|
|
131
|
+
this.neededAt[n] = 0;
|
|
132
|
+
this.priority[n] = 0;
|
|
133
|
+
this.distance[n] = 0;
|
|
134
|
+
this.gen[n] = 0;
|
|
135
|
+
this.mask[n] = 15;
|
|
136
|
+
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
|
+
if (parent !== NONE && this.flags[parent] & NODE_BOUNDS) {
|
|
160
|
+
this.minH[n] = this.minH[parent];
|
|
161
|
+
this.maxH[n] = this.maxH[parent];
|
|
162
|
+
} else {
|
|
163
|
+
this.minH[n] = this.lowest;
|
|
164
|
+
this.maxH[n] = this.highest;
|
|
165
|
+
}
|
|
166
|
+
this.updateSphere(n);
|
|
167
|
+
}
|
|
168
|
+
/** Children of `n`, made if needed. Returns the first (the others follow it). */
|
|
169
|
+
split(n) {
|
|
170
|
+
let c = this.child[n];
|
|
171
|
+
if (c !== NONE) return c;
|
|
172
|
+
const reused = this.blocks.pop();
|
|
173
|
+
if (reused !== void 0) c = reused;
|
|
174
|
+
else {
|
|
175
|
+
if (this.count + 4 > this.capacity) this.grow(this.capacity * 2);
|
|
176
|
+
c = this.count;
|
|
177
|
+
this.count += 4;
|
|
178
|
+
}
|
|
179
|
+
const face = this.face[n];
|
|
180
|
+
const depth = this.depth[n] + 1;
|
|
181
|
+
const x = this.x[n] * 2;
|
|
182
|
+
const y = this.y[n] * 2;
|
|
183
|
+
for (let i = 0; i < 4; i++) this.init(c + i, face, depth, x + (i & 1), y + (i >> 1), n);
|
|
184
|
+
this.child[n] = c;
|
|
185
|
+
return c;
|
|
186
|
+
}
|
|
187
|
+
/** Frees `n`'s subtree. `release` is called for every freed node that holds a render slot. */
|
|
188
|
+
merge(n, release) {
|
|
189
|
+
const c = this.child[n];
|
|
190
|
+
if (c === NONE) return;
|
|
191
|
+
for (let i = 0; i < 4; i++) {
|
|
192
|
+
this.merge(c + i, release);
|
|
193
|
+
if (this.slot[c + i] !== NONE) release(c + i);
|
|
194
|
+
this.slot[c + i] = NONE;
|
|
195
|
+
this.flags[c + i] = 0;
|
|
196
|
+
}
|
|
197
|
+
this.child[n] = NONE;
|
|
198
|
+
this.blocks.push(c);
|
|
199
|
+
}
|
|
200
|
+
/** Live nodes (roots plus allocated blocks). */
|
|
201
|
+
get live() {
|
|
202
|
+
return this.count - this.blocks.length * 4;
|
|
203
|
+
}
|
|
204
|
+
/** Sets a node's exact height range and recomputes its bounds. */
|
|
205
|
+
setHeights(n, minH, maxH) {
|
|
206
|
+
this.minH[n] = minH;
|
|
207
|
+
this.maxH[n] = maxH;
|
|
208
|
+
this.flags[n] |= NODE_BOUNDS;
|
|
209
|
+
this.updateSphere(n);
|
|
210
|
+
}
|
|
211
|
+
/** Bounding sphere of the node's 9 test points at its lowest and highest heights, plus bulge. */
|
|
212
|
+
updateSphere(n) {
|
|
213
|
+
const d = this.dirs;
|
|
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;
|
|
259
|
+
}
|
|
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
|
+
*/
|
|
264
|
+
bounds(n, center, out) {
|
|
265
|
+
const d = this.dirs;
|
|
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;
|
|
297
|
+
}
|
|
298
|
+
/** The node at (face, depth, x, y) if the tree has it, else the deepest existing ancestor. */
|
|
299
|
+
find(face, depth, x, y) {
|
|
300
|
+
let n = face;
|
|
301
|
+
for (let k = 0; k < depth; k++) {
|
|
302
|
+
const c = this.child[n];
|
|
303
|
+
if (c === NONE) return n;
|
|
304
|
+
const shift = depth - k - 1;
|
|
305
|
+
n = c + (x >>> shift & 1) + 2 * (y >>> shift & 1);
|
|
306
|
+
}
|
|
307
|
+
return n;
|
|
308
|
+
}
|
|
309
|
+
/** Frees subtrees nobody has needed since frame `before`. */
|
|
310
|
+
prune(before, release) {
|
|
311
|
+
for (let f = 0; f < 6; f++) this.pruneNode(f, before, release);
|
|
312
|
+
}
|
|
313
|
+
/** Returns whether `n` and its whole subtree are idle. */
|
|
314
|
+
pruneNode(n, before, release) {
|
|
315
|
+
const c = this.child[n];
|
|
316
|
+
let idle = this.used[n] < before;
|
|
317
|
+
if (c === NONE) return idle;
|
|
318
|
+
let childrenIdle = true;
|
|
319
|
+
for (let i = 0; i < 4; i++) if (!this.pruneNode(c + i, before, release)) childrenIdle = false;
|
|
320
|
+
if (childrenIdle) this.merge(n, release);
|
|
321
|
+
idle = idle && childrenIdle;
|
|
322
|
+
return idle;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function createSelection() {
|
|
326
|
+
return {
|
|
327
|
+
rendered: new Int32Array(1024),
|
|
328
|
+
renderedCount: 0,
|
|
329
|
+
requested: new Int32Array(256),
|
|
330
|
+
requestedCount: 0,
|
|
331
|
+
waiting: 0,
|
|
332
|
+
missing: new Int32Array(64),
|
|
333
|
+
passes: 0,
|
|
334
|
+
stamp: 0
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const neighbor = new Float64Array(3);
|
|
338
|
+
let passStamp = 0;
|
|
339
|
+
const PREFETCH = 0.7;
|
|
340
|
+
const FORCE_HOLD = 30;
|
|
341
|
+
const MERGE_MARGIN = 0.9;
|
|
342
|
+
const NEAR_VIEW = 0.5;
|
|
343
|
+
const OUT_OF_VIEW = 1e-3;
|
|
344
|
+
function selectNodes(tree, view, params, frame, out) {
|
|
345
|
+
out.passes = 0;
|
|
346
|
+
for (let pass = 0; pass < 8; pass++) {
|
|
347
|
+
passStamp = passStamp + 1 >>> 0 || 1;
|
|
348
|
+
out.renderedCount = 0;
|
|
349
|
+
out.requestedCount = 0;
|
|
350
|
+
out.waiting = 0;
|
|
351
|
+
out.passes++;
|
|
352
|
+
for (let f = 0; f < 6; f++) {
|
|
353
|
+
tree.visited[f] = frame;
|
|
354
|
+
tree.used[f] = frame;
|
|
355
|
+
if (culledNode(tree, f, view, params)) tree.culled[f] = passStamp;
|
|
356
|
+
else visit(tree, f, view, params, frame, out);
|
|
357
|
+
}
|
|
358
|
+
if (!balance(tree, frame, out)) break;
|
|
359
|
+
}
|
|
360
|
+
out.stamp = passStamp;
|
|
361
|
+
setLocks(tree, out);
|
|
362
|
+
}
|
|
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
|
+
function nearView(tree, n, view) {
|
|
414
|
+
const s = tree.sphere;
|
|
415
|
+
const o = n * 4;
|
|
416
|
+
const f = view.wide;
|
|
417
|
+
for (let p = 0; p < view.planes; p++) {
|
|
418
|
+
const q = p * 4;
|
|
419
|
+
if (f[q] * s[o] + f[q + 1] * s[o + 1] + f[q + 2] * s[o + 2] + f[q + 3] < -s[o + 3])
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
function nodeDistance(tree, n, p) {
|
|
425
|
+
const s = tree.sphere;
|
|
426
|
+
const o = n * 4;
|
|
427
|
+
const dx = p[0] - s[o];
|
|
428
|
+
const dy = p[1] - s[o + 1];
|
|
429
|
+
const dz = p[2] - s[o + 2];
|
|
430
|
+
return Math.max(0, Math.sqrt(dx * dx + dy * dy + dz * dz) - s[o + 3]);
|
|
431
|
+
}
|
|
432
|
+
function nearAnchor(tree, n, params) {
|
|
433
|
+
if (params.anchors === 0) return false;
|
|
434
|
+
const s = tree.sphere;
|
|
435
|
+
const o = n * 4;
|
|
436
|
+
const a = params.anchorPos;
|
|
437
|
+
for (let i = 0; i < params.anchors; i++) {
|
|
438
|
+
const dx = a[i * 3] - s[o];
|
|
439
|
+
const dy = a[i * 3 + 1] - s[o + 1];
|
|
440
|
+
const dz = a[i * 3 + 2] - s[o + 2];
|
|
441
|
+
const reach = params.anchorRadius[i] + s[o + 3];
|
|
442
|
+
if (dx * dx + dy * dy + dz * dz < reach * reach) return true;
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
function request(tree, n, priority, distance, frame, out) {
|
|
447
|
+
tree.used[n] = frame;
|
|
448
|
+
if (tree.requestedAt[n] === passStamp) return;
|
|
449
|
+
tree.requestedAt[n] = passStamp;
|
|
450
|
+
tree.priority[n] = priority;
|
|
451
|
+
tree.distance[n] = distance;
|
|
452
|
+
if (out.requestedCount === out.requested.length) {
|
|
453
|
+
const next = new Int32Array(out.requested.length * 2);
|
|
454
|
+
next.set(out.requested);
|
|
455
|
+
out.requested = next;
|
|
456
|
+
}
|
|
457
|
+
out.requested[out.requestedCount++] = n;
|
|
458
|
+
}
|
|
459
|
+
function render(tree, n, frame, out, mask) {
|
|
460
|
+
tree.rendered[n] = passStamp;
|
|
461
|
+
tree.neededAt[n] = frame;
|
|
462
|
+
tree.mask[n] = mask;
|
|
463
|
+
if (out.renderedCount === out.rendered.length) {
|
|
464
|
+
const next = new Int32Array(out.rendered.length * 2);
|
|
465
|
+
next.set(out.rendered);
|
|
466
|
+
out.rendered = next;
|
|
467
|
+
}
|
|
468
|
+
out.rendered[out.renderedCount++] = n;
|
|
469
|
+
}
|
|
470
|
+
function visit(tree, n, view, params, frame, out) {
|
|
471
|
+
const depth = tree.depth[n];
|
|
472
|
+
const dist = nodeDistance(tree, n, view.position);
|
|
473
|
+
const error = view.pixelsPerRadian * params.errors[depth + 1] / Math.max(dist, 1e-3);
|
|
474
|
+
let split = false;
|
|
475
|
+
if (depth < params.maxDepth) {
|
|
476
|
+
if (tree.forced[n] !== 0 && frame - tree.forced[n] < FORCE_HOLD) split = true;
|
|
477
|
+
else if (nearAnchor(tree, n, params)) split = depth < params.colliderDepth;
|
|
478
|
+
else {
|
|
479
|
+
const wasSplit = tree.splitAt[n] !== 0 && frame - tree.splitAt[n] <= 1;
|
|
480
|
+
split = error > params.errorPixels * (wasSplit ? MERGE_MARGIN : 1);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (split) {
|
|
484
|
+
tree.splitAt[n] = frame;
|
|
485
|
+
const c = tree.split(n);
|
|
486
|
+
let missing = 0;
|
|
487
|
+
for (let i = 0; i < 4; i++) {
|
|
488
|
+
const k = c + i;
|
|
489
|
+
tree.visited[k] = frame;
|
|
490
|
+
tree.used[k] = frame;
|
|
491
|
+
tree.neededAt[k] = frame;
|
|
492
|
+
if (culledNode(tree, k, view, params)) {
|
|
493
|
+
tree.culled[k] = passStamp;
|
|
494
|
+
if (!(tree.flags[k] & NODE_READY)) {
|
|
495
|
+
const d = nodeDistance(tree, k, view.position);
|
|
496
|
+
const e = view.pixelsPerRadian * params.errors[depth + 1] / Math.max(d, 1e-3);
|
|
497
|
+
request(tree, k, e * (nearView(tree, k, view) ? NEAR_VIEW : OUT_OF_VIEW), d, frame, out);
|
|
498
|
+
}
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (!(tree.flags[k] & NODE_READY)) {
|
|
502
|
+
if (missing === 0 && out.waiting < out.missing.length) out.missing[out.waiting] = k;
|
|
503
|
+
missing |= 1 << i;
|
|
504
|
+
const d = nodeDistance(tree, k, view.position);
|
|
505
|
+
const e = view.pixelsPerRadian * params.errors[depth + 1] / Math.max(d, 1e-3);
|
|
506
|
+
request(tree, k, e, d, frame, out);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (missing !== 0) out.waiting++;
|
|
510
|
+
if (missing === 0 || tree.flags[n] & NODE_READY) {
|
|
511
|
+
for (let i = 0; i < 4; i++) {
|
|
512
|
+
const k = c + i;
|
|
513
|
+
if (tree.culled[k] !== passStamp && !(missing & 1 << i))
|
|
514
|
+
visit(tree, k, view, params, frame, out);
|
|
515
|
+
}
|
|
516
|
+
if (missing !== 0) render(tree, n, frame, out, missing);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
tree.neededAt[n] = frame;
|
|
520
|
+
request(tree, n, 1e9 - depth, dist, frame, out);
|
|
521
|
+
return;
|
|
522
|
+
} else if (depth < params.maxDepth && error > params.errorPixels * PREFETCH && !nearAnchor(tree, n, params)) {
|
|
523
|
+
const c = tree.split(n);
|
|
524
|
+
for (let i = 0; i < 4; i++) {
|
|
525
|
+
const k = c + i;
|
|
526
|
+
tree.used[k] = frame;
|
|
527
|
+
if (tree.flags[k] & NODE_READY) continue;
|
|
528
|
+
const d = nodeDistance(tree, k, view.position);
|
|
529
|
+
const e = view.pixelsPerRadian * params.errors[depth + 1] / Math.max(d, 1e-3);
|
|
530
|
+
const scale = !culledNode(tree, k, view, params) ? 1 : nearView(tree, k, view) ? NEAR_VIEW : OUT_OF_VIEW;
|
|
531
|
+
request(tree, k, e * scale, d, frame, out);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (tree.flags[n] & NODE_READY) render(tree, n, frame, out, 15);
|
|
535
|
+
else {
|
|
536
|
+
tree.neededAt[n] = frame;
|
|
537
|
+
request(tree, n, 1e9 - depth, dist, frame, out);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function balance(tree, frame, out) {
|
|
541
|
+
let marked = false;
|
|
542
|
+
for (let r = 0; r < out.renderedCount; r++) {
|
|
543
|
+
const n = out.rendered[r];
|
|
544
|
+
const depth = tree.depth[n];
|
|
545
|
+
if (depth < 2) continue;
|
|
546
|
+
for (let edge = 0; edge <= EDGE_LEFT; edge++) {
|
|
547
|
+
neighborNode(tree.face[n], depth, tree.x[n], tree.y[n], edge, neighbor);
|
|
548
|
+
const m = tree.find(neighbor[0], depth, neighbor[1], neighbor[2]);
|
|
549
|
+
const k = renderedAbove(tree, m);
|
|
550
|
+
if (k === NONE) continue;
|
|
551
|
+
if (tree.depth[k] < depth - 1 && tree.forced[k] !== frame && tree.mask[k] === 15) {
|
|
552
|
+
tree.forced[k] = frame;
|
|
553
|
+
marked = true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return marked;
|
|
558
|
+
}
|
|
559
|
+
function renderedAbove(tree, m) {
|
|
560
|
+
for (let k = m; k !== NONE; k = tree.parent[k]) if (tree.rendered[k] === passStamp) return k;
|
|
561
|
+
return NONE;
|
|
562
|
+
}
|
|
563
|
+
function setLocks(tree, out) {
|
|
564
|
+
for (let r = 0; r < out.renderedCount; r++) {
|
|
565
|
+
const n = out.rendered[r];
|
|
566
|
+
const depth = tree.depth[n];
|
|
567
|
+
for (let edge = 0; edge <= EDGE_LEFT; edge++) {
|
|
568
|
+
let lock = -1;
|
|
569
|
+
if (depth > 0) {
|
|
570
|
+
neighborNode(tree.face[n], depth, tree.x[n], tree.y[n], edge, neighbor);
|
|
571
|
+
const m = tree.find(neighbor[0], depth, neighbor[1], neighbor[2]);
|
|
572
|
+
const k = renderedAbove(tree, m);
|
|
573
|
+
if (k !== NONE) lock = tree.depth[k] < depth ? 1 : -1;
|
|
574
|
+
else if (tree.depth[m] === depth && tree.child[m] !== NONE) lock = 0;
|
|
575
|
+
}
|
|
576
|
+
tree.locks[n * 4 + edge] = lock;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
export {
|
|
581
|
+
NODE_BOUNDS,
|
|
582
|
+
NODE_READY,
|
|
583
|
+
NodeTree,
|
|
584
|
+
createSelection,
|
|
585
|
+
nearAnchor,
|
|
586
|
+
selectNodes
|
|
587
|
+
};
|