@aethervtt/shard-terrain 0.1.0 → 0.2.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/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
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AssetStore,
|
|
3
|
+
defineAssetSchema,
|
|
4
|
+
defineAssetType,
|
|
5
|
+
defineImporter,
|
|
6
|
+
sha256Hex
|
|
7
|
+
} from "@aethervtt/shard-assets";
|
|
8
|
+
import { defineResource, defineSchema, ShardError } from "@aethervtt/shard-core";
|
|
9
|
+
import {
|
|
10
|
+
parseTerrainSource,
|
|
11
|
+
sourceDependencies,
|
|
12
|
+
terrainLayout
|
|
13
|
+
} from "./source.js";
|
|
14
|
+
class TerrainSourceAsset {
|
|
15
|
+
source;
|
|
16
|
+
layout;
|
|
17
|
+
deps;
|
|
18
|
+
hash;
|
|
19
|
+
/** Bumps on hot reload. */
|
|
20
|
+
version = 0;
|
|
21
|
+
constructor(a) {
|
|
22
|
+
this.source = a.source;
|
|
23
|
+
this.layout = a.layout;
|
|
24
|
+
this.deps = a.deps;
|
|
25
|
+
this.hash = a.hash;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const TerrainSources = defineResource("terrain/TerrainSources", {
|
|
29
|
+
description: "Loaded heightfield terrain sources (*.terrain.json) by guid.",
|
|
30
|
+
init: () => new AssetStore("terrain/TerrainSource")
|
|
31
|
+
});
|
|
32
|
+
const TerrainSourceAssetType = defineAssetType("terrain/TerrainSource", {
|
|
33
|
+
store: TerrainSources,
|
|
34
|
+
load: (artifact) => new TerrainSourceAsset(artifact.json),
|
|
35
|
+
update: (existing, next) => {
|
|
36
|
+
existing.source = next.source;
|
|
37
|
+
existing.layout = next.layout;
|
|
38
|
+
existing.deps = next.deps;
|
|
39
|
+
existing.hash = next.hash;
|
|
40
|
+
existing.version++;
|
|
41
|
+
},
|
|
42
|
+
references: (item) => Object.values(item.deps).filter((d) => d.type === "Texture").map((d) => ({ guid: d.guid }))
|
|
43
|
+
});
|
|
44
|
+
const NoSettings = defineSchema(
|
|
45
|
+
"terrain/TerrainSourceSettings",
|
|
46
|
+
{},
|
|
47
|
+
{ description: "None: the terrain is the file." }
|
|
48
|
+
);
|
|
49
|
+
const EXPECTED = {
|
|
50
|
+
noise: "NoiseGraph",
|
|
51
|
+
heightmaps: "Heightmap",
|
|
52
|
+
textures: "Texture"
|
|
53
|
+
};
|
|
54
|
+
const TerrainSourceImporter = defineImporter({
|
|
55
|
+
name: "terrain",
|
|
56
|
+
version: 2,
|
|
57
|
+
extensions: [".terrain.json"],
|
|
58
|
+
settings: NoSettings,
|
|
59
|
+
async import(file, ctx) {
|
|
60
|
+
let json;
|
|
61
|
+
try {
|
|
62
|
+
json = JSON.parse(file.text());
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
throw new ShardError("assets/import-failed", `${file.path} isn't valid JSON`, {
|
|
65
|
+
path: file.path,
|
|
66
|
+
cause
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const source = parseTerrainSource(json);
|
|
70
|
+
const layout = terrainLayout(source);
|
|
71
|
+
const deps = {};
|
|
72
|
+
const lists = sourceDependencies(source);
|
|
73
|
+
for (const [kind, paths] of Object.entries(lists)) {
|
|
74
|
+
for (const path of paths) {
|
|
75
|
+
const dep = await ctx.asset(path);
|
|
76
|
+
if (dep.type !== EXPECTED[kind]) {
|
|
77
|
+
throw new ShardError(
|
|
78
|
+
kind === "heightmaps" ? "terrain/heightmap-format" : "terrain/bad-source",
|
|
79
|
+
`${path} is a ${dep.type}, not a ${EXPECTED[kind]}`,
|
|
80
|
+
{
|
|
81
|
+
path,
|
|
82
|
+
hint: kind === "heightmaps" ? 'Image layers and masks read Heightmaps: a 16-bit grayscale PNG with "importer": "heightmap" in its .meta (or named *.height.png), or a .r16/.r32 file.' : `Point it at a ${EXPECTED[kind]}.`
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
deps[path] = { guid: dep.guid, type: dep.type, hash: dep.hash };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const fill = (r) => {
|
|
90
|
+
if (r && deps[r.path]) r.guid = deps[r.path].guid;
|
|
91
|
+
};
|
|
92
|
+
for (const l of source.height) {
|
|
93
|
+
if (l.kind === "noise") fill(l.noise);
|
|
94
|
+
if (l.kind === "image") fill(l.image);
|
|
95
|
+
}
|
|
96
|
+
for (const p of source.paint) {
|
|
97
|
+
fill(p.noise?.ref ?? null);
|
|
98
|
+
fill(p.mask?.ref ?? null);
|
|
99
|
+
}
|
|
100
|
+
fill(source.textures.albedo);
|
|
101
|
+
fill(source.textures.normal);
|
|
102
|
+
fill(source.textures.orm);
|
|
103
|
+
const hash = await sha256Hex(
|
|
104
|
+
new TextEncoder().encode(JSON.stringify({ source, deps: Object.entries(deps).sort() }))
|
|
105
|
+
);
|
|
106
|
+
const artifact = { format: 1, source, layout, deps, hash };
|
|
107
|
+
return {
|
|
108
|
+
assets: [
|
|
109
|
+
{
|
|
110
|
+
label: "",
|
|
111
|
+
type: "terrain/TerrainSource",
|
|
112
|
+
json: artifact,
|
|
113
|
+
dependencies: Object.keys(deps),
|
|
114
|
+
info: {
|
|
115
|
+
size: source.size,
|
|
116
|
+
spacing: source.spacing,
|
|
117
|
+
roots: layout.rootsX * layout.rootsZ,
|
|
118
|
+
depth: layout.depth,
|
|
119
|
+
blocks: layout.blocksX * layout.blocksZ,
|
|
120
|
+
heightLayers: source.height.length,
|
|
121
|
+
materialLayers: source.layers.length
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const num = (description, extra = {}) => ({
|
|
129
|
+
type: "number",
|
|
130
|
+
description,
|
|
131
|
+
...extra
|
|
132
|
+
});
|
|
133
|
+
const pairOf = (description, examples) => ({
|
|
134
|
+
type: "array",
|
|
135
|
+
items: { type: "number" },
|
|
136
|
+
minItems: 2,
|
|
137
|
+
maxItems: 2,
|
|
138
|
+
description,
|
|
139
|
+
examples
|
|
140
|
+
});
|
|
141
|
+
const refOf = (description, example) => ({
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: { path: { type: "string", minLength: 1 }, guid: { type: "string" } },
|
|
144
|
+
required: ["path"],
|
|
145
|
+
description,
|
|
146
|
+
examples: [{ path: example }]
|
|
147
|
+
});
|
|
148
|
+
const rect = {
|
|
149
|
+
at: pairOf("Center of the rectangle, [x, z] metres from the terrain\u2019s corner.", [[2048, 1800]]),
|
|
150
|
+
size: pairOf("Width (along X before rotation) and depth (along Z), metres.", [[1200, 900]]),
|
|
151
|
+
rotation: num("Degrees about +Y (right-handed: positive turns +X toward \u2212Z). Default 0.", {
|
|
152
|
+
examples: [20]
|
|
153
|
+
})
|
|
154
|
+
};
|
|
155
|
+
const blend = {
|
|
156
|
+
enum: ["add", "max", "min", "replace"],
|
|
157
|
+
description: "How the layer combines with the height below, mixed by its falloff mask: add (default for noise), max, min, replace (default for images)."
|
|
158
|
+
};
|
|
159
|
+
function terrainJsonSchema() {
|
|
160
|
+
return {
|
|
161
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
162
|
+
title: "Heightfield terrain (spec 0071)",
|
|
163
|
+
description: "A bounded landscape as an ordered stack of height layers (noise, heightmap images, splines that flatten, raise or carve) and paint layers (material weights by height, slope, noise, masks and splines). `shard import` (or the first run) bakes it into page packs in .shard/cache/terrain; a Terrain component on an entity draws, streams and collides with it. Coordinates are metres from the terrain\u2019s corner, x along +X and z along +Z.",
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
$schema: { type: "string" },
|
|
167
|
+
size: pairOf(
|
|
168
|
+
"Size [x, z] in metres, 256 m \u2013 64 km a side, a whole number of roots: 64 \xD7 spacing \xD7 2^k, at most 64 roots (2048, 4096, 16384 at 1 m spacing).",
|
|
169
|
+
[[4096, 4096]]
|
|
170
|
+
),
|
|
171
|
+
spacing: num("Leaf sample spacing (m), 0.25\u20134. 1 for most open worlds, 0.5 for detail.", {
|
|
172
|
+
minimum: 0.25,
|
|
173
|
+
maximum: 4,
|
|
174
|
+
examples: [1]
|
|
175
|
+
}),
|
|
176
|
+
paintSpacing: num("Control (paint) sample spacing: 1, 2 (default) or 4 times spacing.", {
|
|
177
|
+
examples: [2]
|
|
178
|
+
}),
|
|
179
|
+
heightRange: pairOf(
|
|
180
|
+
"Lowest and highest height the bake stores (m); heights are 16-bit over it, so a 1 km range keeps 1.5 cm steps. A baked height outside it clips and is reported as terrain/out-of-range.",
|
|
181
|
+
[[-200, 800]]
|
|
182
|
+
),
|
|
183
|
+
seed: num("Mixed into every noise layer and mask: the same graphs make another terrain.", {
|
|
184
|
+
examples: [7]
|
|
185
|
+
}),
|
|
186
|
+
splines: {
|
|
187
|
+
type: "object",
|
|
188
|
+
description: "Named splines (Catmull-Rom through their points): height layers flatten, raise or carve along them; paint layers paint along them; scatter can avoid them.",
|
|
189
|
+
additionalProperties: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
points: {
|
|
193
|
+
type: "array",
|
|
194
|
+
minItems: 2,
|
|
195
|
+
items: {
|
|
196
|
+
type: "array",
|
|
197
|
+
minItems: 3,
|
|
198
|
+
maxItems: 3,
|
|
199
|
+
items: [
|
|
200
|
+
{ type: "number" },
|
|
201
|
+
{ anyOf: [{ type: "number" }, { const: "ground" }] },
|
|
202
|
+
{ type: "number" }
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
description: '[x, y, z] points; y may be "ground": the height of the layers below the one using the spline at that point.',
|
|
206
|
+
examples: [
|
|
207
|
+
[
|
|
208
|
+
[120, "ground", 300],
|
|
209
|
+
[900, 40, 420]
|
|
210
|
+
]
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
width: num("Full width of the flat part (m).", { examples: [8] }),
|
|
214
|
+
falloff: num("Metres beyond the width over which a height layer using it fades out.", {
|
|
215
|
+
examples: [14]
|
|
216
|
+
})
|
|
217
|
+
},
|
|
218
|
+
required: ["points", "width"],
|
|
219
|
+
additionalProperties: false
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
height: {
|
|
223
|
+
type: "array",
|
|
224
|
+
description: "Height layers, applied in order from 0 m.",
|
|
225
|
+
items: {
|
|
226
|
+
oneOf: [
|
|
227
|
+
{
|
|
228
|
+
type: "object",
|
|
229
|
+
description: "Noise (0041) over the whole terrain, or over a rectangle with a falloff.",
|
|
230
|
+
properties: {
|
|
231
|
+
noise: refOf("A noise graph.", "noise/hills.noise.json"),
|
|
232
|
+
scale: num("Metres per unit of the graph\u2019s output (default 1).", {
|
|
233
|
+
examples: [260]
|
|
234
|
+
}),
|
|
235
|
+
offset: num("Metres added after scaling (default 0)."),
|
|
236
|
+
blend,
|
|
237
|
+
falloff: num("Metres outside the rectangle over which it fades (default 0)."),
|
|
238
|
+
...rect
|
|
239
|
+
},
|
|
240
|
+
required: ["noise"],
|
|
241
|
+
additionalProperties: false
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: "object",
|
|
245
|
+
description: "A heightmap image over a rotated rectangle.",
|
|
246
|
+
properties: {
|
|
247
|
+
image: refOf(
|
|
248
|
+
"A Heightmap (16-bit grayscale PNG, .r16 or .r32).",
|
|
249
|
+
"terrain/valley.height.png"
|
|
250
|
+
),
|
|
251
|
+
range: pairOf("Metres the image\u2019s 0 and 1 map to.", [[0, 140]]),
|
|
252
|
+
blend,
|
|
253
|
+
falloff: num("Metres outside the rectangle over which it fades (default 0).", {
|
|
254
|
+
examples: [80]
|
|
255
|
+
}),
|
|
256
|
+
...rect
|
|
257
|
+
},
|
|
258
|
+
required: ["image", "at", "size", "range"],
|
|
259
|
+
additionalProperties: false
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
type: "object",
|
|
263
|
+
description: "A spline: flatten to its height, raise up to it (embankments), or carve down to it (cuttings, river beds).",
|
|
264
|
+
properties: {
|
|
265
|
+
spline: { type: "string", description: 'A name from "splines".' },
|
|
266
|
+
mode: { enum: ["flatten", "raise", "carve"], description: "Default flatten." },
|
|
267
|
+
offset: num("Metres added to the spline\u2019s height (a carve\u2019s depth: \u22122)."),
|
|
268
|
+
falloff: num("Overrides the spline\u2019s falloff for this layer.")
|
|
269
|
+
},
|
|
270
|
+
required: ["spline"],
|
|
271
|
+
additionalProperties: false
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
layers: {
|
|
277
|
+
type: "array",
|
|
278
|
+
maxItems: 32,
|
|
279
|
+
description: "Material layers (at most 32), sampled from the texture arrays.",
|
|
280
|
+
items: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
name: { type: "string", description: "Paint layers name it.", examples: ["grass"] },
|
|
284
|
+
albedo: num("Layer of the albedo array (default: this layer\u2019s index)."),
|
|
285
|
+
normal: num("Layer of the normal array (default: albedo\u2019s)."),
|
|
286
|
+
orm: num("Layer of the ORM array (default: albedo\u2019s)."),
|
|
287
|
+
scale: num("Metres one texture repeat covers (default 4).", { examples: [4] }),
|
|
288
|
+
triplanar: {
|
|
289
|
+
type: "boolean",
|
|
290
|
+
description: "Projected from three axes where the slope passes 45\xB0 (rock on cliffs)."
|
|
291
|
+
},
|
|
292
|
+
tint: {
|
|
293
|
+
type: "array",
|
|
294
|
+
items: { type: "number" },
|
|
295
|
+
minItems: 3,
|
|
296
|
+
maxItems: 3,
|
|
297
|
+
description: "Linear RGB multiplying the albedo, and the whole color when the terrain has no texture arrays (default: a palette by index: grass, rock, gravel, sand, snow\u2026).",
|
|
298
|
+
examples: [[0.16, 0.3, 0.08]]
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
required: ["name"],
|
|
302
|
+
additionalProperties: false
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
textures: {
|
|
306
|
+
type: "object",
|
|
307
|
+
description: "Texture arrays (0043\u2019s *.texarray.json), one layer per surface texture.",
|
|
308
|
+
properties: {
|
|
309
|
+
albedo: refOf("Albedo array.", "terrain/albedo.texarray.json"),
|
|
310
|
+
normal: refOf("Normal map array.", "terrain/normal.texarray.json"),
|
|
311
|
+
orm: refOf("Occlusion / roughness / metallic array.", "terrain/orm.texarray.json")
|
|
312
|
+
},
|
|
313
|
+
additionalProperties: false
|
|
314
|
+
},
|
|
315
|
+
paint: {
|
|
316
|
+
type: "array",
|
|
317
|
+
description: "Paint layers, in order: the first is the base; each later one paints its layer over what\u2019s below where all its masks pass.",
|
|
318
|
+
items: {
|
|
319
|
+
type: "object",
|
|
320
|
+
properties: {
|
|
321
|
+
layer: { type: "string", description: "A material layer\u2019s name." },
|
|
322
|
+
height: pairOf("Where the height is in [min, max] (m).", [[600, 2e3]]),
|
|
323
|
+
slope: pairOf("Where the slope is in [min, max] degrees (0 flat, 90 a cliff).", [
|
|
324
|
+
[32, 90]
|
|
325
|
+
]),
|
|
326
|
+
noise: {
|
|
327
|
+
...refOf("A noise graph above a threshold.", "noise/patches.noise.json"),
|
|
328
|
+
properties: {
|
|
329
|
+
path: { type: "string" },
|
|
330
|
+
guid: { type: "string" },
|
|
331
|
+
above: num("Paints where the graph is above this (default 0).", {
|
|
332
|
+
examples: [0.2]
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
mask: {
|
|
337
|
+
type: "object",
|
|
338
|
+
description: "A Heightmap over a rectangle: paints by its value (0\u20131).",
|
|
339
|
+
properties: {
|
|
340
|
+
path: { type: "string" },
|
|
341
|
+
guid: { type: "string" },
|
|
342
|
+
...rect
|
|
343
|
+
},
|
|
344
|
+
required: ["path", "at", "size"]
|
|
345
|
+
},
|
|
346
|
+
spline: {
|
|
347
|
+
type: "string",
|
|
348
|
+
description: "Paints along a spline, out to half its width."
|
|
349
|
+
},
|
|
350
|
+
blend: num(
|
|
351
|
+
"Softens the masks\u2019 edges: metres for height, masks and splines, degrees for slope (default 0, hard).",
|
|
352
|
+
{ examples: [6] }
|
|
353
|
+
)
|
|
354
|
+
},
|
|
355
|
+
required: ["layer"],
|
|
356
|
+
additionalProperties: false
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
required: ["size", "spacing", "heightRange", "height"],
|
|
361
|
+
additionalProperties: false
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const terrainSchema = defineAssetSchema("terrain.schema.json", terrainJsonSchema);
|
|
365
|
+
export {
|
|
366
|
+
TerrainSourceAsset,
|
|
367
|
+
TerrainSourceAssetType,
|
|
368
|
+
TerrainSourceImporter,
|
|
369
|
+
TerrainSources,
|
|
370
|
+
terrainJsonSchema,
|
|
371
|
+
terrainSchema
|
|
372
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/** Most material layers a terrain may have (the control page stores a layer in a byte; 0043's arrays hold 32). */
|
|
2
|
+
export declare const MAX_MATERIAL_LAYERS = 32;
|
|
3
|
+
/** Sides from 256 m to 64 km; sample spacing from 0.25 to 4 m. */
|
|
4
|
+
export declare const MIN_SIZE = 256;
|
|
5
|
+
export declare const MAX_SIZE = 65536;
|
|
6
|
+
export declare const MIN_SPACING = 0.25;
|
|
7
|
+
export declare const MAX_SPACING = 4;
|
|
8
|
+
/** Roots a terrain may have, at most (the importer picks the root depth to stay under it). */
|
|
9
|
+
export declare const MAX_ROOTS = 64;
|
|
10
|
+
/** Deepest root depth: a block's samples hold every level's lattice up to 2^10. */
|
|
11
|
+
export declare const MAX_DEPTH = 10;
|
|
12
|
+
export type BlendOp = 'add' | 'max' | 'min' | 'replace';
|
|
13
|
+
export type SplineMode = 'flatten' | 'raise' | 'carve';
|
|
14
|
+
/** An asset reference in a terrain file: by path, and its guid once imported. */
|
|
15
|
+
export interface SourceRef {
|
|
16
|
+
path: string;
|
|
17
|
+
guid?: string;
|
|
18
|
+
}
|
|
19
|
+
/** A rotated rectangle on the terrain: center, size (width along X before rotation, depth along Z), degrees about +Y. */
|
|
20
|
+
export interface SourceRect {
|
|
21
|
+
at: [number, number];
|
|
22
|
+
size: [number, number];
|
|
23
|
+
rotation: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SourceSpline {
|
|
26
|
+
/** [x, y, z] in terrain metres; y may be "ground" (the stack's height below the layer using it). */
|
|
27
|
+
points: [number, number | 'ground', number][];
|
|
28
|
+
/** Full width of the flat part (m). */
|
|
29
|
+
width: number;
|
|
30
|
+
/** Metres past the width over which a layer using it fades out. */
|
|
31
|
+
falloff: number;
|
|
32
|
+
}
|
|
33
|
+
export type SourceHeightLayer = ({
|
|
34
|
+
kind: 'noise';
|
|
35
|
+
noise: SourceRef;
|
|
36
|
+
/** Metres per unit of the graph's output. */
|
|
37
|
+
scale: number;
|
|
38
|
+
offset: number;
|
|
39
|
+
blend: BlendOp;
|
|
40
|
+
falloff: number;
|
|
41
|
+
} & {
|
|
42
|
+
region: SourceRect | null;
|
|
43
|
+
}) | ({
|
|
44
|
+
kind: 'image';
|
|
45
|
+
image: SourceRef;
|
|
46
|
+
/** Metres the image's 0 and 1 map to. */
|
|
47
|
+
range: [number, number];
|
|
48
|
+
blend: BlendOp;
|
|
49
|
+
falloff: number;
|
|
50
|
+
} & SourceRect) | {
|
|
51
|
+
kind: 'spline';
|
|
52
|
+
spline: string;
|
|
53
|
+
mode: SplineMode;
|
|
54
|
+
/** Metres added to the spline's height (a carve's depth below it, say). */
|
|
55
|
+
offset: number;
|
|
56
|
+
/** The spline's falloff unless set here. */
|
|
57
|
+
falloff: number;
|
|
58
|
+
};
|
|
59
|
+
export interface SourceMaterialLayer {
|
|
60
|
+
name: string;
|
|
61
|
+
/** Layers of the texture arrays (0043's `*.texarray.json`): albedo, and normal and ORM (default: the albedo's). */
|
|
62
|
+
albedo: number;
|
|
63
|
+
normal: number;
|
|
64
|
+
orm: number;
|
|
65
|
+
/** Metres one texture repeat covers. */
|
|
66
|
+
scale: number;
|
|
67
|
+
/** Projected from three axes where the slope passes 45° (cliffs). */
|
|
68
|
+
triplanar: boolean;
|
|
69
|
+
/** Linear RGB multiplying the albedo; the whole color without texture arrays. */
|
|
70
|
+
tint: [number, number, number];
|
|
71
|
+
}
|
|
72
|
+
/** Tints for layers that don't set one, by index: grass, rock, gravel, sand, snow, soil, … */
|
|
73
|
+
export declare const LAYER_TINTS: [number, number, number][];
|
|
74
|
+
export interface SourcePaint {
|
|
75
|
+
/** Material layer name. */
|
|
76
|
+
layer: string;
|
|
77
|
+
height: [number, number] | null;
|
|
78
|
+
slope: [number, number] | null;
|
|
79
|
+
noise: {
|
|
80
|
+
ref: SourceRef;
|
|
81
|
+
above: number;
|
|
82
|
+
} | null;
|
|
83
|
+
mask: ({
|
|
84
|
+
ref: SourceRef;
|
|
85
|
+
} & SourceRect) | null;
|
|
86
|
+
spline: string | null;
|
|
87
|
+
/** Edge softness: metres for height, masks and splines; degrees for slope. */
|
|
88
|
+
blend: number;
|
|
89
|
+
}
|
|
90
|
+
/** A terrain source (`*.terrain.json`) after validation, with every default filled in. */
|
|
91
|
+
export interface TerrainSource {
|
|
92
|
+
size: [number, number];
|
|
93
|
+
spacing: number;
|
|
94
|
+
paintSpacing: number;
|
|
95
|
+
heightRange: [number, number];
|
|
96
|
+
seed: number;
|
|
97
|
+
splines: Record<string, SourceSpline>;
|
|
98
|
+
height: SourceHeightLayer[];
|
|
99
|
+
layers: SourceMaterialLayer[];
|
|
100
|
+
textures: {
|
|
101
|
+
albedo: SourceRef | null;
|
|
102
|
+
normal: SourceRef | null;
|
|
103
|
+
orm: SourceRef | null;
|
|
104
|
+
};
|
|
105
|
+
paint: SourcePaint[];
|
|
106
|
+
}
|
|
107
|
+
/** The terrain's grid (spec 0071): roots, depths, blocks, and paint cells. */
|
|
108
|
+
export interface TerrainLayout {
|
|
109
|
+
sizeX: number;
|
|
110
|
+
sizeZ: number;
|
|
111
|
+
spacing: number;
|
|
112
|
+
paintSpacing: number;
|
|
113
|
+
/** Leaf samples per paint sample. */
|
|
114
|
+
paintStep: number;
|
|
115
|
+
/** Paint cells per page side (control pages hold cells + 1 texels a side). */
|
|
116
|
+
cells: number;
|
|
117
|
+
/** Leaves are at this depth; roots at 0. */
|
|
118
|
+
depth: number;
|
|
119
|
+
rootsX: number;
|
|
120
|
+
rootsZ: number;
|
|
121
|
+
/** Metres a root covers. */
|
|
122
|
+
rootSize: number;
|
|
123
|
+
/** Metres a leaf covers. */
|
|
124
|
+
leafSize: number;
|
|
125
|
+
leavesX: number;
|
|
126
|
+
leavesZ: number;
|
|
127
|
+
/** Leaves per block side (BLOCK), and the levels each block bakes itself above its leaves. */
|
|
128
|
+
block: number;
|
|
129
|
+
blocksX: number;
|
|
130
|
+
blocksZ: number;
|
|
131
|
+
blockLevels: number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Validates a terrain file's JSON and fills in defaults. Throws a ShardError whose path is a JSON
|
|
135
|
+
* pointer into the file: `terrain/bad-size`, `terrain/bad-spacing`, `terrain/unknown-spline`,
|
|
136
|
+
* `terrain/unknown-layer`, `terrain/too-many-layers`, or `terrain/bad-source` for anything else.
|
|
137
|
+
*/
|
|
138
|
+
export declare function parseTerrainSource(json: unknown): TerrainSource;
|
|
139
|
+
/**
|
|
140
|
+
* The terrain's grid (spec 0071): leaves of PAGE segments at `spacing`, roots at the shallowest
|
|
141
|
+
* depth giving at most MAX_ROOTS of them, and blocks of `block` × `block` leaves (a power of two;
|
|
142
|
+
* BLOCK but in tests). Throws `terrain/bad-size` when a side is outside 256 m – 64 km or isn't a
|
|
143
|
+
* whole number of roots.
|
|
144
|
+
*/
|
|
145
|
+
export declare function terrainLayout(source: Pick<TerrainSource, 'size' | 'spacing' | 'paintSpacing'>, block?: number): TerrainLayout;
|
|
146
|
+
/** Every asset a source refers to, by path: noise graphs, heightmaps (images and masks), texture arrays. */
|
|
147
|
+
export declare function sourceDependencies(source: TerrainSource): {
|
|
148
|
+
noise: string[];
|
|
149
|
+
heightmaps: string[];
|
|
150
|
+
textures: string[];
|
|
151
|
+
};
|