@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.
- 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 +18 -8
- package/dist/test-planet.d.ts +39 -0
- package/dist/test-planet.js +41 -5
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +43 -3
- package/package.json +16 -16
package/dist/index.js
CHANGED
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
assembleChunk,
|
|
15
15
|
buildChunk,
|
|
16
|
-
|
|
17
|
-
chunkCenter,
|
|
18
|
-
chunkLayout
|
|
16
|
+
chunkCenter
|
|
19
17
|
} from "./chunk.js";
|
|
20
18
|
import {
|
|
21
19
|
COLLIDER_DELAY,
|
|
22
20
|
ColliderSet,
|
|
21
|
+
chunkSpec,
|
|
23
22
|
collidersOf,
|
|
23
|
+
sampleChunkAsync,
|
|
24
24
|
skirtDepth
|
|
25
25
|
} from "./colliders.js";
|
|
26
26
|
import {
|
|
@@ -49,14 +49,87 @@ import {
|
|
|
49
49
|
parseKey,
|
|
50
50
|
unpackKey
|
|
51
51
|
} from "./cube.js";
|
|
52
|
+
import { CubeSphere, NodeTree } from "./cube-sphere.js";
|
|
52
53
|
import { PlanetFrame } from "./frame.js";
|
|
54
|
+
import {
|
|
55
|
+
checkResolution,
|
|
56
|
+
chunkIndices,
|
|
57
|
+
chunkLayout,
|
|
58
|
+
lockCode
|
|
59
|
+
} from "./grid-mesh.js";
|
|
60
|
+
import {
|
|
61
|
+
bakeTerrain,
|
|
62
|
+
blockKeys,
|
|
63
|
+
packHash,
|
|
64
|
+
readManifest,
|
|
65
|
+
terrainStats
|
|
66
|
+
} from "./heightfield/bake.js";
|
|
67
|
+
import {
|
|
68
|
+
tilesOf
|
|
69
|
+
} from "./heightfield/colliders.js";
|
|
70
|
+
import { Terrain, TerrainChunk } from "./heightfield/component.js";
|
|
71
|
+
import {
|
|
72
|
+
decodeHeightPng,
|
|
73
|
+
HeightmapAssetType,
|
|
74
|
+
HeightmapImporter,
|
|
75
|
+
Heightmaps
|
|
76
|
+
} from "./heightfield/heightmap.js";
|
|
77
|
+
import {
|
|
78
|
+
BAKE_VERSION,
|
|
79
|
+
BLOCK,
|
|
80
|
+
deflate,
|
|
81
|
+
inflate,
|
|
82
|
+
LEAF_SIDE,
|
|
83
|
+
PAGE,
|
|
84
|
+
SIDE
|
|
85
|
+
} from "./heightfield/kernel.js";
|
|
86
|
+
import { TerrainSurfaceMaterial } from "./heightfield/material.js";
|
|
87
|
+
import {
|
|
88
|
+
fsPackStore,
|
|
89
|
+
memoryPackStore,
|
|
90
|
+
PACK,
|
|
91
|
+
packOf,
|
|
92
|
+
readIndex,
|
|
93
|
+
writePack
|
|
94
|
+
} from "./heightfield/pack.js";
|
|
95
|
+
import {
|
|
96
|
+
bakeIsCurrent,
|
|
97
|
+
bakeProjectTerrains,
|
|
98
|
+
stackFor,
|
|
99
|
+
terrainCacheDir
|
|
100
|
+
} from "./heightfield/project.js";
|
|
101
|
+
import {
|
|
102
|
+
finestPage,
|
|
103
|
+
heightfieldRuntime,
|
|
104
|
+
heightfieldSample,
|
|
105
|
+
loadTerrainRegion,
|
|
106
|
+
pageHeight,
|
|
107
|
+
terrainHeightAt
|
|
108
|
+
} from "./heightfield/queries.js";
|
|
109
|
+
import { HeightfieldDebug } from "./heightfield/render.js";
|
|
110
|
+
import { HeightfieldRuntime } from "./heightfield/runtime.js";
|
|
111
|
+
import {
|
|
112
|
+
MAX_MATERIAL_LAYERS,
|
|
113
|
+
parseTerrainSource,
|
|
114
|
+
sourceDependencies,
|
|
115
|
+
terrainLayout
|
|
116
|
+
} from "./heightfield/source.js";
|
|
117
|
+
import {
|
|
118
|
+
TerrainSourceAsset,
|
|
119
|
+
TerrainSourceAssetType,
|
|
120
|
+
TerrainSourceImporter,
|
|
121
|
+
TerrainSources,
|
|
122
|
+
terrainJsonSchema
|
|
123
|
+
} from "./heightfield/source-asset.js";
|
|
124
|
+
import { compileStack, mainNoise } from "./heightfield/stack.js";
|
|
125
|
+
import { heightfieldUpdates, updateHeightfields } from "./heightfield/system.js";
|
|
53
126
|
import {
|
|
54
127
|
heightAt,
|
|
55
128
|
planetHeightAt,
|
|
56
129
|
planetRuntime,
|
|
57
130
|
planetSurfaceAt,
|
|
58
|
-
Terrain,
|
|
59
131
|
TerrainState,
|
|
132
|
+
TerrainWorld,
|
|
60
133
|
terrainSample
|
|
61
134
|
} from "./heights.js";
|
|
62
135
|
import {
|
|
@@ -70,16 +143,29 @@ import {
|
|
|
70
143
|
sampleKernel,
|
|
71
144
|
VERTEX_KERNEL
|
|
72
145
|
} from "./kernel.js";
|
|
73
|
-
import {
|
|
146
|
+
import {
|
|
147
|
+
adaptLodBias,
|
|
148
|
+
capErrors,
|
|
149
|
+
MAX_LOD_BIAS,
|
|
150
|
+
MORPH_WGSL,
|
|
151
|
+
measureErrors,
|
|
152
|
+
morphFactor
|
|
153
|
+
} from "./lod.js";
|
|
74
154
|
import { OceanMaterial, PlanetMaterial, TERRAIN_SHADERS, TEXTURE_PERIOD } from "./material.js";
|
|
75
155
|
import { resolvePlanet, terrainMap, terrainMethods } from "./methods.js";
|
|
76
156
|
import { clearPlanetNav, planetAgents, planetNavMesh, updatePlanetNav } from "./nav.js";
|
|
77
|
-
import {
|
|
157
|
+
import {
|
|
158
|
+
terrainBiomesOverlay,
|
|
159
|
+
terrainCollidersOverlay,
|
|
160
|
+
terrainLodOverlay,
|
|
161
|
+
terrainPagesOverlay
|
|
162
|
+
} from "./overlays.js";
|
|
78
163
|
import { COLLIDER_SPACING, PlanetRuntime } from "./planet.js";
|
|
79
164
|
import { terrainPlugin, updatePlanets } from "./plugin.js";
|
|
80
165
|
import {
|
|
81
166
|
createChunkPoints,
|
|
82
167
|
gridDirection,
|
|
168
|
+
NOISE_OFFSET,
|
|
83
169
|
prepareChunkPoints,
|
|
84
170
|
SNAP,
|
|
85
171
|
sampleChunkPoints,
|
|
@@ -89,19 +175,22 @@ import {
|
|
|
89
175
|
createSelection,
|
|
90
176
|
NODE_BOUNDS,
|
|
91
177
|
NODE_READY,
|
|
92
|
-
|
|
178
|
+
QuadTree,
|
|
93
179
|
selectNodes
|
|
94
180
|
} from "./quadtree.js";
|
|
95
181
|
import { PlanetRender, renderOf, selectChunks, TerrainDebug } from "./render.js";
|
|
96
182
|
import { createView, omniView, perspectiveView } from "./view.js";
|
|
97
183
|
export {
|
|
184
|
+
BAKE_VERSION,
|
|
98
185
|
BLENDED_BIOMES,
|
|
186
|
+
BLOCK,
|
|
99
187
|
Biome,
|
|
100
188
|
BiomeSet,
|
|
101
189
|
COLLIDER_DELAY,
|
|
102
190
|
COLLIDER_SPACING,
|
|
103
191
|
Chunk,
|
|
104
192
|
ColliderSet,
|
|
193
|
+
CubeSphere,
|
|
105
194
|
EDGE_BOTTOM,
|
|
106
195
|
EDGE_LEFT,
|
|
107
196
|
EDGE_RIGHT,
|
|
@@ -110,14 +199,26 @@ export {
|
|
|
110
199
|
GEN_HEIGHT,
|
|
111
200
|
GEN_OCEAN,
|
|
112
201
|
GEN_ROOT,
|
|
202
|
+
HeightfieldDebug,
|
|
203
|
+
HeightfieldRuntime,
|
|
204
|
+
HeightmapAssetType,
|
|
205
|
+
HeightmapImporter,
|
|
206
|
+
Heightmaps,
|
|
207
|
+
LEAF_SIDE,
|
|
113
208
|
MAX_BIOMES,
|
|
114
209
|
MAX_DEPTH,
|
|
115
210
|
MAX_LAYERS,
|
|
211
|
+
MAX_LOD_BIAS,
|
|
212
|
+
MAX_MATERIAL_LAYERS,
|
|
116
213
|
MAX_RADIUS,
|
|
214
|
+
MORPH_WGSL,
|
|
117
215
|
NODE_BOUNDS,
|
|
118
216
|
NODE_READY,
|
|
217
|
+
NOISE_OFFSET,
|
|
119
218
|
NodeTree,
|
|
120
219
|
OceanMaterial,
|
|
220
|
+
PACK,
|
|
221
|
+
PAGE,
|
|
121
222
|
PARAMS_BYTES,
|
|
122
223
|
POINT_FLOATS,
|
|
123
224
|
Planet,
|
|
@@ -126,45 +227,80 @@ export {
|
|
|
126
227
|
PlanetNav,
|
|
127
228
|
PlanetRender,
|
|
128
229
|
PlanetRuntime,
|
|
230
|
+
QuadTree,
|
|
231
|
+
SIDE,
|
|
129
232
|
SNAP,
|
|
130
233
|
TERRAIN_SHADERS,
|
|
131
234
|
TEXTURE_PERIOD,
|
|
132
235
|
Terrain,
|
|
133
236
|
TerrainAnchor,
|
|
134
237
|
TerrainBudget,
|
|
238
|
+
TerrainChunk,
|
|
135
239
|
TerrainDebug,
|
|
240
|
+
TerrainSourceAsset,
|
|
241
|
+
TerrainSourceAssetType,
|
|
242
|
+
TerrainSourceImporter,
|
|
243
|
+
TerrainSources,
|
|
136
244
|
TerrainState,
|
|
245
|
+
TerrainSurfaceMaterial,
|
|
246
|
+
TerrainWorld,
|
|
137
247
|
VERTEX_KERNEL,
|
|
248
|
+
adaptLodBias,
|
|
138
249
|
assembleChunk,
|
|
250
|
+
bakeIsCurrent,
|
|
251
|
+
bakeProjectTerrains,
|
|
252
|
+
bakeTerrain,
|
|
139
253
|
biomeTable,
|
|
140
254
|
biomeWeights,
|
|
141
255
|
biomeWindow,
|
|
256
|
+
blockKeys,
|
|
142
257
|
buildChunk,
|
|
258
|
+
capErrors,
|
|
143
259
|
checkResolution,
|
|
144
260
|
chunkCenter,
|
|
261
|
+
chunkIndices,
|
|
145
262
|
chunkLayout,
|
|
263
|
+
chunkSpec,
|
|
146
264
|
clearPlanetNav,
|
|
147
265
|
collidersOf,
|
|
266
|
+
compileStack,
|
|
148
267
|
createChunkPoints,
|
|
149
268
|
createSelection,
|
|
150
269
|
createView,
|
|
270
|
+
decodeHeightPng,
|
|
271
|
+
deflate,
|
|
151
272
|
directionToFace,
|
|
152
273
|
dominantBiome,
|
|
153
274
|
effectiveTemperature,
|
|
154
275
|
faceToDirection,
|
|
276
|
+
finestPage,
|
|
277
|
+
fsPackStore,
|
|
155
278
|
gridDirection,
|
|
156
279
|
heightAt,
|
|
280
|
+
heightfieldRuntime,
|
|
281
|
+
heightfieldSample,
|
|
282
|
+
heightfieldUpdates,
|
|
283
|
+
inflate,
|
|
157
284
|
kernelGraph,
|
|
158
285
|
keyString,
|
|
286
|
+
loadTerrainRegion,
|
|
287
|
+
lockCode,
|
|
288
|
+
mainNoise,
|
|
159
289
|
maxDepthFor,
|
|
160
290
|
measureErrors,
|
|
291
|
+
memoryPackStore,
|
|
292
|
+
morphFactor,
|
|
161
293
|
neighborNode,
|
|
162
294
|
nodeAt,
|
|
163
295
|
nodeExtent,
|
|
164
296
|
nodeSpacing,
|
|
165
297
|
omniView,
|
|
298
|
+
packHash,
|
|
166
299
|
packKey,
|
|
300
|
+
packOf,
|
|
301
|
+
pageHeight,
|
|
167
302
|
parseKey,
|
|
303
|
+
parseTerrainSource,
|
|
168
304
|
perspectiveView,
|
|
169
305
|
planetAgents,
|
|
170
306
|
planetHeightAt,
|
|
@@ -172,22 +308,36 @@ export {
|
|
|
172
308
|
planetRuntime,
|
|
173
309
|
planetSurfaceAt,
|
|
174
310
|
prepareChunkPoints,
|
|
311
|
+
readIndex,
|
|
312
|
+
readManifest,
|
|
175
313
|
renderOf,
|
|
176
314
|
resolvePlanet,
|
|
315
|
+
sampleChunkAsync,
|
|
177
316
|
sampleChunkPoints,
|
|
178
317
|
sampleKernel,
|
|
179
318
|
samplePoint,
|
|
180
319
|
selectChunks,
|
|
181
320
|
selectNodes,
|
|
182
321
|
skirtDepth,
|
|
322
|
+
sourceDependencies,
|
|
323
|
+
stackFor,
|
|
183
324
|
terrainBiomesOverlay,
|
|
325
|
+
terrainCacheDir,
|
|
184
326
|
terrainCollidersOverlay,
|
|
327
|
+
terrainHeightAt,
|
|
328
|
+
terrainJsonSchema,
|
|
329
|
+
terrainLayout,
|
|
185
330
|
terrainLodOverlay,
|
|
186
331
|
terrainMap,
|
|
187
332
|
terrainMethods,
|
|
333
|
+
terrainPagesOverlay,
|
|
188
334
|
terrainPlugin,
|
|
189
335
|
terrainSample,
|
|
336
|
+
terrainStats,
|
|
337
|
+
tilesOf,
|
|
190
338
|
unpackKey,
|
|
339
|
+
updateHeightfields,
|
|
191
340
|
updatePlanetNav,
|
|
192
|
-
updatePlanets
|
|
341
|
+
updatePlanets,
|
|
342
|
+
writePack
|
|
193
343
|
};
|
package/dist/lod.d.ts
CHANGED
|
@@ -20,3 +20,30 @@ export interface ErrorSpec {
|
|
|
20
20
|
* (not per node) keeps neighbors at one depth in the same band, so their shared edges morph alike.
|
|
21
21
|
*/
|
|
22
22
|
export declare function measureErrors(spec: ErrorSpec): Float32Array;
|
|
23
|
+
/**
|
|
24
|
+
* Caps each depth's error at the spacing that projects to `vertexPixels` wherever it projects to
|
|
25
|
+
* `errorPixels` (spec 0043): a node then splits only while its children's vertices stay that far
|
|
26
|
+
* apart on screen. Rough terrain otherwise keeps splitting into sub-pixel triangles. Selection and
|
|
27
|
+
* morphing read the same capped table, so morph bands still end where splits happen. Errors never
|
|
28
|
+
* grow with depth, like the measured ones. `vertexPixels` 0 leaves the errors as they are.
|
|
29
|
+
*/
|
|
30
|
+
export declare function capErrors(raw: Float32Array, spacing: (depth: number) => number, errorPixels: number, vertexPixels: number, out?: Float32Array): Float32Array;
|
|
31
|
+
/** Largest LOD bias the triangle budget may set (errorPixels × 16). */
|
|
32
|
+
export declare const MAX_LOD_BIAS = 16;
|
|
33
|
+
/**
|
|
34
|
+
* Steers a LOD bias (a multiplier on errorPixels for selection and morphing) toward a triangle
|
|
35
|
+
* budget: up 1% a frame while `drawn` is over `budget`, back down 0.5% a frame once under 85% of
|
|
36
|
+
* it, never below 1 (the surface's own settings) or above MAX_LOD_BIAS. Slow on purpose: the bias
|
|
37
|
+
* moves split distances, so morphs shift with it (2× in about a second). `budget` 0: no limit.
|
|
38
|
+
*/
|
|
39
|
+
export declare function adaptLodBias(bias: number, drawn: number, budget: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Where a vertex is in its morph toward the parent level (0 its own, 1 the parent's), by its
|
|
42
|
+
* distance from the camera: across [0.5, 0.95] of `split`, the distance at which the parent would
|
|
43
|
+
* split (`errors[depth] × pixelsPerRadian / (errorPixels × lodBias)`). Merging takes a 10% margin
|
|
44
|
+
* past that, so a chunk is fully morphed before it goes. The vertex shaders inline the same
|
|
45
|
+
* formula (`MORPH_WGSL`); CPU replays of the vertex stage call this.
|
|
46
|
+
*/
|
|
47
|
+
export declare function morphFactor(distance: number, split: number): number;
|
|
48
|
+
/** `morphFactor` in WGSL, for a `d` (distance) and `split` in scope. */
|
|
49
|
+
export declare const MORPH_WGSL = "clamp((d - 0.5 * split) / (0.45 * split), 0.0, 1.0)";
|
package/dist/lod.js
CHANGED
|
@@ -39,6 +39,28 @@ function measureErrors(spec) {
|
|
|
39
39
|
for (let d = spec.maxDepth - 1; d >= 0; d--) out[d] = Math.max(out[d], out[d + 1]);
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
|
+
function capErrors(raw, spacing, errorPixels, vertexPixels, out = new Float32Array(raw.length)) {
|
|
43
|
+
const k = vertexPixels > 0 ? errorPixels / vertexPixels : Number.POSITIVE_INFINITY;
|
|
44
|
+
for (let d = 0; d < raw.length; d++) out[d] = Math.min(raw[d], k * spacing(d));
|
|
45
|
+
for (let d = raw.length - 2; d >= 0; d--) out[d] = Math.max(out[d], out[d + 1]);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const MAX_LOD_BIAS = 16;
|
|
49
|
+
function adaptLodBias(bias, drawn, budget) {
|
|
50
|
+
if (budget > 0 && drawn > budget) return Math.min(MAX_LOD_BIAS, bias * 1.01);
|
|
51
|
+
if (budget <= 0 || drawn < budget * 0.85) return Math.max(1, bias / 1.005);
|
|
52
|
+
return bias;
|
|
53
|
+
}
|
|
54
|
+
function morphFactor(distance, split) {
|
|
55
|
+
if (!(split > 0)) return 0;
|
|
56
|
+
return Math.min(1, Math.max(0, (distance - 0.5 * split) / (0.45 * split)));
|
|
57
|
+
}
|
|
58
|
+
const MORPH_WGSL = "clamp((d - 0.5 * split) / (0.45 * split), 0.0, 1.0)";
|
|
42
59
|
export {
|
|
43
|
-
|
|
60
|
+
MAX_LOD_BIAS,
|
|
61
|
+
MORPH_WGSL,
|
|
62
|
+
adaptLodBias,
|
|
63
|
+
capErrors,
|
|
64
|
+
measureErrors,
|
|
65
|
+
morphFactor
|
|
44
66
|
};
|
package/dist/material.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t } from "@aethervtt/shard-core";
|
|
2
2
|
import { defineMaterial } from "@aethervtt/shard-render";
|
|
3
|
+
import { MORPH_WGSL } from "./lod.js";
|
|
3
4
|
const TEXTURE_PERIOD = 1024;
|
|
4
5
|
const frame = {
|
|
5
6
|
center: t.vec4({
|
|
@@ -109,7 +110,7 @@ fn terrain_morph(position: vec3f) -> f32 {
|
|
|
109
110
|
let split = lock.y * ${u}.camera.w;
|
|
110
111
|
if (split > 0.0) {
|
|
111
112
|
let d = distance(vertex_world(position), ${u}.camera.xyz);
|
|
112
|
-
t =
|
|
113
|
+
t = ${MORPH_WGSL};
|
|
113
114
|
}
|
|
114
115
|
let code = terrain_lock(grid.x, grid.y);
|
|
115
116
|
if (code >= 0 && code < 4) {
|
package/dist/methods.js
CHANGED
|
@@ -6,7 +6,13 @@ import {
|
|
|
6
6
|
} from "@aethervtt/shard-core";
|
|
7
7
|
import { encodePng, toBase64 } from "@aethervtt/shard-protocol";
|
|
8
8
|
import { collidersOf } from "./colliders.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
describeHeightfield,
|
|
11
|
+
heightfieldMap,
|
|
12
|
+
resolveHeightfield,
|
|
13
|
+
sampleHeightfield
|
|
14
|
+
} from "./heightfield/methods.js";
|
|
15
|
+
import { TerrainWorld, terrainSample } from "./heights.js";
|
|
10
16
|
function scenePath(world, entity) {
|
|
11
17
|
const member = findComponent("scene/SceneMember");
|
|
12
18
|
if (!member || !world.isAlive(entity)) return null;
|
|
@@ -14,7 +20,7 @@ function scenePath(world, entity) {
|
|
|
14
20
|
return v?.path || null;
|
|
15
21
|
}
|
|
16
22
|
function resolvePlanet(world, ref) {
|
|
17
|
-
const planets = world.tryResource(
|
|
23
|
+
const planets = world.tryResource(TerrainWorld)?.planets;
|
|
18
24
|
if (!planets || planets.size === 0) {
|
|
19
25
|
throw new ShardError("terrain/no-planet", "There is no terrain/Planet in the world", {
|
|
20
26
|
hint: "Add terrain/Planet (with a Grid) to an entity, and the terrain plugin to the app."
|
|
@@ -181,28 +187,65 @@ function terrainMap(rt, width, mode) {
|
|
|
181
187
|
const planetField = t.json({
|
|
182
188
|
description: "The planet: entity id or scene path (default: the only planet)."
|
|
183
189
|
});
|
|
190
|
+
const named = (ref) => ref !== void 0 && ref !== null && ref !== "";
|
|
191
|
+
function wantsHeightfield(world, p) {
|
|
192
|
+
if (named(p.terrain)) return true;
|
|
193
|
+
if (named(p.planet)) return false;
|
|
194
|
+
const state = world.tryResource(TerrainWorld);
|
|
195
|
+
return (state?.planets.size ?? 0) === 0 && (state?.heightfields.size ?? 0) > 0;
|
|
196
|
+
}
|
|
197
|
+
const terrainField = t.json({
|
|
198
|
+
description: "A heightfield terrain (0071): entity id or scene path (default: the only one)."
|
|
199
|
+
});
|
|
184
200
|
const terrainMethods = [
|
|
185
201
|
{
|
|
186
202
|
name: "terrain.describe",
|
|
187
|
-
description: "
|
|
188
|
-
params: defineSchema("terrain/DescribeParams", {
|
|
203
|
+
description: "Terrain as data. Per planet: the chunks selected by depth (and partial ones waiting for children), requests and chunks in flight, pool usage, generation counts, collider chunks and anchors, the vertex spacing at the finest and collider depths, and the height, biome, and slope under the camera. Per heightfield terrain (0071): roots and depths, the bake (blocks current, stale, baking; the last bake\u2019s rebuilt blocks, time and heights clipped by heightRange), chunks selected by depth, pages resident, cached and read, the GPU pool, and collider tiles with their anchors. Problems and what each waits for are here too.",
|
|
204
|
+
params: defineSchema("terrain/DescribeParams", {
|
|
205
|
+
planet: planetField,
|
|
206
|
+
terrain: terrainField
|
|
207
|
+
}),
|
|
189
208
|
handler: ({ world }, p) => {
|
|
190
|
-
const
|
|
191
|
-
if (!
|
|
209
|
+
const state = world.tryResource(TerrainWorld);
|
|
210
|
+
if (!state) return { planets: [], terrains: [] };
|
|
211
|
+
if (p.terrain !== void 0 && p.terrain !== null && p.terrain !== "")
|
|
212
|
+
return {
|
|
213
|
+
planets: [],
|
|
214
|
+
terrains: [describeHeightfield(world, resolveHeightfield(world, p.terrain))]
|
|
215
|
+
};
|
|
192
216
|
if (p.planet !== void 0 && p.planet !== null && p.planet !== "")
|
|
193
|
-
return { planets: [describePlanet(world, resolvePlanet(world, p.planet))] };
|
|
194
|
-
return {
|
|
217
|
+
return { planets: [describePlanet(world, resolvePlanet(world, p.planet))], terrains: [] };
|
|
218
|
+
return {
|
|
219
|
+
planets: [...state.planets.values()].map((rt) => describePlanet(world, rt)),
|
|
220
|
+
terrains: [...state.heightfields.values()].map((rt) => describeHeightfield(world, rt))
|
|
221
|
+
};
|
|
195
222
|
}
|
|
196
223
|
},
|
|
197
224
|
{
|
|
198
225
|
name: "terrain.sample",
|
|
199
|
-
description: "The
|
|
226
|
+
description: "The ground at points. On a planet, from the CPU noise the colliders use (headless-safe): height (m above radius), underwater and water depth, slope (degrees), climate, and biome weights with the dominant biome; points are directions from the center in its frame ([x, y, z], any length) or [lat, lon] in degrees. On a heightfield terrain (0071, `terrain` or `points`): height, normal, slope, and the two heaviest paint layers with the second\u2019s share, from its leaf pages (loaded first, so exactly the colliders\u2019 ground); points are [x, z] metres from its corner.",
|
|
200
227
|
params: defineSchema("terrain/SampleParams", {
|
|
201
228
|
planet: planetField,
|
|
202
|
-
|
|
203
|
-
|
|
229
|
+
terrain: terrainField,
|
|
230
|
+
directions: t.json({ description: "Planets: directions [[x, y, z], \u2026] (at most 4096)." }),
|
|
231
|
+
latlon: t.json({
|
|
232
|
+
description: "Planets: latitudes and longitudes in degrees: [[lat, lon], \u2026]."
|
|
233
|
+
}),
|
|
234
|
+
points: t.json({
|
|
235
|
+
description: "Heightfields: [[x, z], \u2026] metres from the corner (at most 4096)."
|
|
236
|
+
})
|
|
204
237
|
}),
|
|
205
|
-
handler: ({ world }, p) => {
|
|
238
|
+
handler: async ({ world }, p) => {
|
|
239
|
+
if (wantsHeightfield(world, p) || Array.isArray(p.points) && !named(p.planet)) {
|
|
240
|
+
const points = Array.isArray(p.points) ? p.points : [];
|
|
241
|
+
if (points.length === 0 || points.length > 4096) {
|
|
242
|
+
throw new ShardError("terrain/bad-points", "Pass 1 to 4096 points as [[x, z], \u2026]", {
|
|
243
|
+
hint: 'Points are metres from the terrain\u2019s corner, e.g. { "points": [[120, 300]] }.'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const rt2 = resolveHeightfield(world, p.terrain);
|
|
247
|
+
return { samples: await sampleHeightfield(world, rt2, points) };
|
|
248
|
+
}
|
|
206
249
|
const rt = resolvePlanet(world, p.planet);
|
|
207
250
|
usable(rt);
|
|
208
251
|
const dirs = [];
|
|
@@ -238,15 +281,20 @@ const terrainMethods = [
|
|
|
238
281
|
},
|
|
239
282
|
{
|
|
240
283
|
name: "terrain.map",
|
|
241
|
-
description: '
|
|
284
|
+
description: 'A PNG of terrain. A planet: equirectangular (width \xD7 width/2; latitude +90 at the top, longitude \u2212180 to 180 left to right), "biomes" colors land by its dominant biome\u2019s tint (shaded by slope), "height" dark lowlands to white peaks; water is blue, darker when deeper. A heightfield terrain (0071): top-down (+X right, +Z down), "height", "slope" (flat black to 60\xB0 white), "layers" (the heaviest paint layer\u2019s tint), or "bake" (blocks green when current, amber when stale, cyan where the last bake rebuilt them).',
|
|
242
285
|
params: defineSchema("terrain/MapParams", {
|
|
243
286
|
planet: planetField,
|
|
287
|
+
terrain: terrainField,
|
|
244
288
|
size: t.u32({ default: 256, min: 16, max: 1024, description: "Width in pixels." }),
|
|
245
|
-
mode: t.enum(["biomes", "height"])
|
|
289
|
+
mode: t.enum(["biomes", "height", "layers", "slope", "bake"])
|
|
246
290
|
}),
|
|
247
291
|
handler: async ({ world }, p) => {
|
|
248
|
-
const
|
|
249
|
-
const image =
|
|
292
|
+
const mode = p.mode;
|
|
293
|
+
const image = wantsHeightfield(world, p) || (mode === "layers" || mode === "slope" || mode === "bake") && !named(p.planet) ? heightfieldMap(resolveHeightfield(world, p.terrain), p.size, mode) : terrainMap(
|
|
294
|
+
resolvePlanet(world, p.planet),
|
|
295
|
+
p.size,
|
|
296
|
+
mode === "height" ? "height" : "biomes"
|
|
297
|
+
);
|
|
250
298
|
const png = await encodePng(image.data, image.width, image.height);
|
|
251
299
|
return { width: image.width, height: image.height, data: toBase64(png) };
|
|
252
300
|
}
|
package/dist/overlays.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Chunk shading by depth: what `terrain-lod` turns on (the material's debug mode 3). */
|
|
2
2
|
export declare const terrainLodOverlay: import("@aethervtt/shard-render").OverlayDef;
|
|
3
|
+
/** Heightfield chunks shaded by the page each draws, with page streaming in the colliders' colors. */
|
|
4
|
+
export declare const terrainPagesOverlay: import("@aethervtt/shard-render").OverlayDef;
|
|
3
5
|
/** Chunk shading by dominant biome: what `terrain-biomes` turns on (debug mode 1). */
|
|
4
6
|
export declare const terrainBiomesOverlay: import("@aethervtt/shard-render").OverlayDef;
|
|
5
7
|
/** Collider chunks (their borders, on the surface) and the anchors that keep them. */
|
package/dist/overlays.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { defineOverlay } from "@aethervtt/shard-render";
|
|
2
|
-
import { chunkLayout } from "./chunk.js";
|
|
3
2
|
import { collidersOf } from "./colliders.js";
|
|
4
|
-
import {
|
|
3
|
+
import { chunkLayout } from "./grid-mesh.js";
|
|
4
|
+
import { tilesOf } from "./heightfield/colliders.js";
|
|
5
|
+
import { TerrainWorld } from "./heights.js";
|
|
5
6
|
const terrainLodOverlay = defineOverlay({
|
|
6
7
|
name: "terrain-lod",
|
|
7
|
-
description: "
|
|
8
|
+
description: "Terrain (planets and heightfields) shaded by LOD depth (a hue per level), to see where chunks split.",
|
|
9
|
+
draw: () => {
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
const terrainPagesOverlay = defineOverlay({
|
|
13
|
+
name: "terrain-pages",
|
|
14
|
+
description: "Heightfield terrain (0071) shaded by the GPU pool page each chunk draws (a hue per slot): pages arriving show as new hues, coarse resident pages as the large patches. terrain.describe lists resident pages and reads in flight.",
|
|
8
15
|
draw: () => {
|
|
9
16
|
}
|
|
10
17
|
});
|
|
@@ -21,9 +28,9 @@ const a = new Float64Array(3);
|
|
|
21
28
|
const b = new Float64Array(3);
|
|
22
29
|
const terrainCollidersOverlay = defineOverlay({
|
|
23
30
|
name: "terrain-colliders",
|
|
24
|
-
description: "
|
|
31
|
+
description: "Collider chunks (planets) and tiles (heightfields): each one\u2019s border on the ground (green; yellow while cached but not in use), and a circle per anchor showing the radius it keeps colliders within.",
|
|
25
32
|
draw: (world, g) => {
|
|
26
|
-
const state = world.tryResource(
|
|
33
|
+
const state = world.tryResource(TerrainWorld);
|
|
27
34
|
if (!state) return;
|
|
28
35
|
for (const rt of state.planets.values()) {
|
|
29
36
|
if (!rt.ready) continue;
|
|
@@ -58,10 +65,45 @@ const terrainCollidersOverlay = defineOverlay({
|
|
|
58
65
|
g.sphere(a, rt.anchorRadius[i], ANCHOR);
|
|
59
66
|
}
|
|
60
67
|
}
|
|
68
|
+
for (const rt of state.heightfields.values()) {
|
|
69
|
+
if (!rt.ready || !rt.layout) continue;
|
|
70
|
+
const size = rt.layout.leafSize;
|
|
71
|
+
const n = 65;
|
|
72
|
+
for (const tile of tilesOf(rt).tiles.values()) {
|
|
73
|
+
const color = tile.entity >= 0 ? COLLIDER : PENDING;
|
|
74
|
+
const x0 = tile.x * size;
|
|
75
|
+
const z0 = tile.z * size;
|
|
76
|
+
const point = (i, j, out) => rt.frame.pointToOrigin(
|
|
77
|
+
x0 + i / (n - 1) * size,
|
|
78
|
+
tile.heights[j * n + i] + 0.05,
|
|
79
|
+
z0 + j / (n - 1) * size,
|
|
80
|
+
out
|
|
81
|
+
);
|
|
82
|
+
for (let k = 0; k < n - 1; k += 4) {
|
|
83
|
+
const k1 = Math.min(n - 1, k + 4);
|
|
84
|
+
for (const [i0, j0, i1, j1] of [
|
|
85
|
+
[k, 0, k1, 0],
|
|
86
|
+
[k, n - 1, k1, n - 1],
|
|
87
|
+
[0, k, 0, k1],
|
|
88
|
+
[n - 1, k, n - 1, k1]
|
|
89
|
+
]) {
|
|
90
|
+
point(i0, j0, a);
|
|
91
|
+
point(i1, j1, b);
|
|
92
|
+
g.line(a, b, color);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (let i = 0; i < rt.anchors; i++) {
|
|
97
|
+
const o = i * 3;
|
|
98
|
+
rt.frame.pointToOrigin(rt.anchorPos[o], rt.anchorPos[o + 1], rt.anchorPos[o + 2], a);
|
|
99
|
+
g.sphere(a, rt.anchorRadius[i], ANCHOR);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
61
102
|
}
|
|
62
103
|
});
|
|
63
104
|
export {
|
|
64
105
|
terrainBiomesOverlay,
|
|
65
106
|
terrainCollidersOverlay,
|
|
66
|
-
terrainLodOverlay
|
|
107
|
+
terrainLodOverlay,
|
|
108
|
+
terrainPagesOverlay
|
|
67
109
|
};
|
package/dist/planet.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type AssetRef, type Entity, ShardError, type World } from '@aethervtt/shard-core';
|
|
2
2
|
import { type NoiseGraph } from '@aethervtt/shard-noise';
|
|
3
3
|
import { type BiomeSetValue, type BiomeTable, type BiomeValue } from './biomes.js';
|
|
4
|
+
import { NodeTree } from './cube-sphere.js';
|
|
4
5
|
import { PlanetFrame } from './frame.js';
|
|
5
|
-
import { NodeTree } from './quadtree.js';
|
|
6
6
|
/** Collider chunks are the first depth whose vertices are at most this far apart (m). */
|
|
7
7
|
export declare const COLLIDER_SPACING = 1;
|
|
8
8
|
/** A planet's settings as numbers, read from its component when it changes. */
|
|
@@ -94,12 +94,7 @@ export declare class PlanetRuntime {
|
|
|
94
94
|
private heightVersion;
|
|
95
95
|
private climateVersion;
|
|
96
96
|
private report;
|
|
97
|
-
/**
|
|
98
|
-
* Caps each depth's error at the spacing that projects to `vertexPixels` wherever it projects to
|
|
99
|
-
* `errorPixels`: a node then splits only while its children's vertices stay that far apart on
|
|
100
|
-
* screen. Rough terrain otherwise keeps splitting into sub-pixel triangles. Selection and
|
|
101
|
-
* morphing read the same capped table, so morph bands still end where splits happen.
|
|
102
|
-
*/
|
|
97
|
+
/** Caps the measured errors by vertex spacing (`vertexPixels`, see `capErrors`). */
|
|
103
98
|
private capErrors;
|
|
104
99
|
/** Vertex spacing at a depth (m). */
|
|
105
100
|
spacing(depth: number): number;
|
package/dist/planet.js
CHANGED
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
BiomeSet,
|
|
8
8
|
biomeTable
|
|
9
9
|
} from "./biomes.js";
|
|
10
|
-
import { checkResolution } from "./chunk.js";
|
|
11
10
|
import { Planet } from "./components.js";
|
|
12
11
|
import { MAX_RADIUS, maxDepthFor, nodeSpacing } from "./cube.js";
|
|
12
|
+
import { NodeTree } from "./cube-sphere.js";
|
|
13
13
|
import { PlanetFrame } from "./frame.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { checkResolution } from "./grid-mesh.js";
|
|
15
|
+
import { capErrors, measureErrors } from "./lod.js";
|
|
16
|
+
import { createSelection, NODE_BOUNDS } from "./quadtree.js";
|
|
16
17
|
const COLLIDER_SPACING = 1;
|
|
17
18
|
class PlanetRuntime {
|
|
18
19
|
entity;
|
|
@@ -221,26 +222,15 @@ class PlanetRuntime {
|
|
|
221
222
|
this.problem = err;
|
|
222
223
|
world.tryResource(LogResource)?.error(err);
|
|
223
224
|
}
|
|
224
|
-
/**
|
|
225
|
-
* Caps each depth's error at the spacing that projects to `vertexPixels` wherever it projects to
|
|
226
|
-
* `errorPixels`: a node then splits only while its children's vertices stay that far apart on
|
|
227
|
-
* screen. Rough terrain otherwise keeps splitting into sub-pixel triangles. Selection and
|
|
228
|
-
* morphing read the same capped table, so morph bands still end where splits happen.
|
|
229
|
-
*/
|
|
225
|
+
/** Caps the measured errors by vertex spacing (`vertexPixels`, see `capErrors`). */
|
|
230
226
|
capErrors(bump = true) {
|
|
231
227
|
const s = this.settings;
|
|
232
228
|
if (!s) return;
|
|
233
|
-
const
|
|
234
|
-
const cap = (raw, out) => {
|
|
235
|
-
const o = out.length === raw.length ? out : new Float32Array(raw.length);
|
|
236
|
-
for (let d = 0; d < raw.length; d++) o[d] = Math.min(raw[d], k * this.spacing(d));
|
|
237
|
-
for (let d = raw.length - 2; d >= 0; d--) o[d] = Math.max(o[d], o[d + 1]);
|
|
238
|
-
return o;
|
|
239
|
-
};
|
|
229
|
+
const spacing = (d) => this.spacing(d);
|
|
240
230
|
const before = this.errors;
|
|
241
231
|
const same = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
242
|
-
const errors =
|
|
243
|
-
const ocean =
|
|
232
|
+
const errors = capErrors(this.rawErrors, spacing, s.errorPixels, s.vertexPixels);
|
|
233
|
+
const ocean = capErrors(this.rawOceanErrors, spacing, s.errorPixels, s.vertexPixels);
|
|
244
234
|
if (same(errors, before) && same(ocean, this.oceanErrors)) return;
|
|
245
235
|
this.errors = errors;
|
|
246
236
|
this.oceanErrors = ocean;
|