@threenative/terrain 0.0.0-stage → 0.1.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/AGENT_GUIDE.md +194 -0
- package/LICENSE +21 -0
- package/README.md +71 -2
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/dist/editor/index.d.ts +231 -0
- package/dist/editor/index.js +5624 -0
- package/dist/editor/server.d.ts +3 -0
- package/dist/editor/server.js +3688 -0
- package/dist/editor/worker.d.ts +2 -0
- package/dist/editor/worker.js +2212 -0
- package/dist/export.d.ts +54 -0
- package/dist/export.js +297 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.js +2927 -0
- package/dist/server-ByM2hmYJ.d.ts +630 -0
- package/dist/terrain-gPnkCU6S.d.ts +141 -0
- package/dist/three.d.ts +14 -0
- package/dist/three.js +40 -0
- package/dist/types-Ant8-Axc.d.ts +486 -0
- package/package.json +86 -3
- package/starter/alpine/README.md +29 -0
- package/starter/alpine/assets.json +12 -0
- package/starter/alpine/bake.mjs +118 -0
- package/starter/alpine/recipe.json +66167 -0
- package/starter/alpine/sky.ts +23 -0
- package/starter/alpine/surface.json +53 -0
- package/starter/alpine/world.ts +335 -0
- package/starter/coastal/README.md +37 -0
- package/starter/coastal/assets.json +10 -0
- package/starter/coastal/bake.mjs +118 -0
- package/starter/coastal/recipe.json +66161 -0
- package/starter/coastal/sea.ts +327 -0
- package/starter/coastal/sky.ts +24 -0
- package/starter/coastal/surface.json +41 -0
- package/starter/coastal/world.ts +386 -0
- package/starter/desert/README.md +35 -0
- package/starter/desert/assets.json +12 -0
- package/starter/desert/bake.mjs +118 -0
- package/starter/desert/recipe.json +66156 -0
- package/starter/desert/sky.ts +23 -0
- package/starter/desert/surface.json +54 -0
- package/starter/desert/world.ts +335 -0
- package/starter/forest/README.md +46 -0
- package/starter/forest/assets.json +32 -0
- package/starter/forest/bake.mjs +118 -0
- package/starter/forest/recipe.json +4586 -0
- package/starter/forest/sky.ts +27 -0
- package/starter/forest/surface.json +79 -0
- package/starter/forest/water.ts +266 -0
- package/starter/forest/world.ts +413 -0
- package/starter/tundra/README.md +29 -0
- package/starter/tundra/assets.json +12 -0
- package/starter/tundra/bake.mjs +118 -0
- package/starter/tundra/recipe.json +66325 -0
- package/starter/tundra/sky.ts +27 -0
- package/starter/tundra/surface.json +54 -0
- package/starter/tundra/water.ts +313 -0
- package/starter/tundra/world.ts +366 -0
- package/starter-assets/bark_brown_02/bark_brown_02_diff_1k.jpg +0 -0
- package/starter-assets/bark_brown_02/bark_brown_02_nor_gl_1k.jpg +0 -0
- package/starter-assets/bark_brown_02/bark_brown_02_rough_1k.jpg +0 -0
- package/starter-assets/boulder_01/boulder-mid.glb +0 -0
- package/starter-assets/boulder_01/boulder-near.glb +0 -0
- package/starter-assets/cliff_side/cliff_side_ao_1k.jpg +0 -0
- package/starter-assets/cliff_side/cliff_side_diff_1k.jpg +0 -0
- package/starter-assets/cliff_side/cliff_side_nor_gl_1k.jpg +0 -0
- package/starter-assets/cliff_side/cliff_side_rough_1k.jpg +0 -0
- package/starter-assets/credits.json +1022 -0
- package/starter-assets/fern_02/fern_02_alpha_512.png +0 -0
- package/starter-assets/fern_02/fern_02_diff_512.jpg +0 -0
- package/starter-assets/fern_02/fern_02_nor_gl_512.jpg +0 -0
- package/starter-assets/fir_tree_01/fir-b-lod1.glb +0 -0
- package/starter-assets/fir_tree_01/fir-b-lod2.glb +0 -0
- package/starter-assets/fir_tree_01/fir-c-lod1.glb +0 -0
- package/starter-assets/fir_tree_01/fir-c-lod2.glb +0 -0
- package/starter-assets/fir_tree_01/fir_tree_01_twig_arm_1k.jpg +0 -0
- package/starter-assets/fir_tree_01/fir_tree_01_twig_diff_1k.jpg +0 -0
- package/starter-assets/fir_tree_01/fir_tree_01_twig_nor_gl_1k.jpg +0 -0
- package/starter-assets/forest_ground_04/forest_ground_04_ao_1k.jpg +0 -0
- package/starter-assets/forest_ground_04/forest_ground_04_diff_1k.jpg +0 -0
- package/starter-assets/forest_ground_04/forest_ground_04_nor_gl_1k.jpg +0 -0
- package/starter-assets/forest_ground_04/forest_ground_04_rough_1k.jpg +0 -0
- package/starter-assets/forest_leaves_02/forest_leaves_02_arm_512.jpg +0 -0
- package/starter-assets/forest_leaves_02/forest_leaves_02_diff_512.jpg +0 -0
- package/starter-assets/forest_leaves_02/forest_leaves_02_nor_gl_512.jpg +0 -0
- package/starter-assets/grass_medium_02/grass_medium_02_alpha_512.png +0 -0
- package/starter-assets/grass_medium_02/grass_medium_02_diff_512.jpg +0 -0
- package/starter-assets/grass_medium_02/grass_medium_02_nor_gl_512.jpg +0 -0
- package/starter-assets/hdri/kloofendal_48d_partly_cloudy_1k.hdr +0 -0
- package/starter-assets/leafy_grass/leafy_grass_ao_1k.jpg +0 -0
- package/starter-assets/leafy_grass/leafy_grass_diff_1k.jpg +0 -0
- package/starter-assets/leafy_grass/leafy_grass_nor_gl_1k.jpg +0 -0
- package/starter-assets/leafy_grass/leafy_grass_rough_1k.jpg +0 -0
- package/starter-assets/lichen_rock/lichen_rock_arm_512.jpg +0 -0
- package/starter-assets/lichen_rock/lichen_rock_diff_512.jpg +0 -0
- package/starter-assets/lichen_rock/lichen_rock_nor_gl_512.jpg +0 -0
- package/starter-assets/mossy_rock/mossy_rock_ao_1k.jpg +0 -0
- package/starter-assets/mossy_rock/mossy_rock_diff_1k.jpg +0 -0
- package/starter-assets/mossy_rock/mossy_rock_nor_gl_1k.jpg +0 -0
- package/starter-assets/mossy_rock/mossy_rock_rough_1k.jpg +0 -0
- package/starter-assets/needle-atlas.png +0 -0
- package/starter-assets/needle-surface.png +0 -0
- package/starter-assets/river_small_rocks/river_small_rocks_arm_512.jpg +0 -0
- package/starter-assets/river_small_rocks/river_small_rocks_diff_512.jpg +0 -0
- package/starter-assets/river_small_rocks/river_small_rocks_nor_gl_512.jpg +0 -0
- package/starter-assets/rock_boulder_dry/rock_boulder_dry_arm_512.jpg +0 -0
- package/starter-assets/rock_boulder_dry/rock_boulder_dry_diff_512.jpg +0 -0
- package/starter-assets/rock_boulder_dry/rock_boulder_dry_nor_gl_512.jpg +0 -0
- package/starter-assets/rocks/boulder-mid.glb +0 -0
- package/starter-assets/rocks/boulder-near.glb +0 -0
- package/starter-assets/rocks/boulder-prep.json +37 -0
- package/starter-assets/rocks/rock01-mid.glb +0 -0
- package/starter-assets/rocks/rock01-near.glb +0 -0
- package/starter-assets/rocks/rock01-prep.json +37 -0
- package/starter-assets/rocks/rock04-mid.glb +0 -0
- package/starter-assets/rocks/rock04-near.glb +0 -0
- package/starter-assets/rocks/rock04-prep.json +37 -0
- package/starter-assets/sand_01/sand_01_ao_1k.jpg +0 -0
- package/starter-assets/sand_01/sand_01_diff_1k.jpg +0 -0
- package/starter-assets/sand_01/sand_01_nor_gl_1k.jpg +0 -0
- package/starter-assets/sand_01/sand_01_rough_1k.jpg +0 -0
- package/starter-assets/snow_02/snow_02_ao_1k.jpg +0 -0
- package/starter-assets/snow_02/snow_02_diff_1k.jpg +0 -0
- package/starter-assets/snow_02/snow_02_nor_gl_1k.jpg +0 -0
- package/starter-assets/snow_02/snow_02_rough_1k.jpg +0 -0
package/AGENT_GUIDE.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Strata: instructions for a coding agent
|
|
2
|
+
|
|
3
|
+
## Read this first
|
|
4
|
+
|
|
5
|
+
Use the typed public `@threenative/terrain` imports to author terrain. The headless implementation is recovered from the owner-supplied Strata source. An operation records intent; `evaluate()` produces disposable data. Never modify an evaluated height array expecting to edit the recipe.
|
|
6
|
+
|
|
7
|
+
World units are metres. Y is up. X and Z range from `-size/2` to `+size/2`. Brush centres and masks use `[x,z]`; spline points use `[x,y,z]`. Rotation and slope thresholds use degrees. **Placement output yaw uses radians.** Heights are absolute world elevations, not normalized texture values.
|
|
8
|
+
|
|
9
|
+
Prefer stable semantic IDs: `base`, `north-peak`, `camp-pad`, `access-road`, `river-main`, `forest`. Repeating an operation with an existing ID replaces it **in place**. It does not append another layer. Use `label` for a human-readable name in fluent calls; the saved layer uses `name`. In `biome()` the `name` parameter is the biome key, not the display name.
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
1. Establish world size, seed, and a low preview resolution. Add base noise and large landforms.
|
|
14
|
+
2. Add erosion before fine roads/building pads. Roads and pads take their elevation from the ground they cross unless you pin one.
|
|
15
|
+
3. Add surface/biome rules and population. Put rules after the geometry changes whose slopes they should see. Road painting can override earlier surface rules; a later reset-material pass can also erase a road's paint.
|
|
16
|
+
4. Inspect the result and diagnostics. Modify known IDs, keeping the source stack small.
|
|
17
|
+
5. Export the recipe and derived runtime data at an explicit resolution. Verify the high-resolution result; resolution-dependent simulation is not guaranteed to match the preview exactly.
|
|
18
|
+
|
|
19
|
+
Use `transaction()` for a synchronous semantic edit that should undo as a unit. Validation failure rolls back the entire batch. `applyPatch()` accepts JSON-only atomic commands, useful for a tool-calling agent:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
terrain.applyPatch([
|
|
23
|
+
{ op: 'update', id: 'north-peak', patch: { params: { amplitude: 120 } } },
|
|
24
|
+
{ op: 'update', id: 'river-main', patch: { params: { width: 22 } } },
|
|
25
|
+
{ op: 'toggle', id: 'erosion', enabled: true },
|
|
26
|
+
]);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Allowed patch commands: `upsert` with `layer`, `update` with `id` and `patch`, `remove` with `id`, `move` with `id` and zero-based `index`, `toggle` with `id` and optional `enabled`. Do not invent methods such as `terrain.generate()`, `terrain.bake()` or `terrain.saveGLB()`. Use `evaluate()`, `bakeTerrain()` and `makeExport()`.
|
|
30
|
+
|
|
31
|
+
## API patterns
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { Terrain, Mask, bakeMesh, bakeTerrain, makeExport } from '@threenative/terrain';
|
|
35
|
+
const t = new Terrain({ size: 512, resolution: 129, seed: 73 });
|
|
36
|
+
|
|
37
|
+
t.noise({ id: 'base', base: 15, amplitude: 20, scale: 140, octaves: 5, warp: 30 });
|
|
38
|
+
t.sculpt({ id: 'hill', at: [0,0], radius: 45, strength: 12, falloff: .7 });
|
|
39
|
+
t.sculpt({ id: 'ditch', points: [[-40,10],[0,30],[40,35]], radius: 10, strength: -5 });
|
|
40
|
+
t.smooth({ id: 'soften', at: [0,0], radius: 65, strength: .6, iterations: 4 });
|
|
41
|
+
t.flatten({ id: 'pad', at: [-70,50], radius: 28, height: 22, strength: 1 });
|
|
42
|
+
t.ramp({ id: 'ramp', from: [-70,22,50], to: [-20,38,50], width: 15, shoulder: 10 });
|
|
43
|
+
t.stamp({ id: 'cliff-mass', shape: 'mesa', at: [90,-70], radius: [70,40], amplitude: 50, rotation: 30 });
|
|
44
|
+
t.erode({ id: 'thermal', method: 'thermal', iterations: 15, talus: 32, rate: .22 });
|
|
45
|
+
t.erode({ id: 'hydraulic', method: 'hydraulic', droplets: 5000, maxSteps: 40 });
|
|
46
|
+
t.terrace({ id: 'strata', step: 8, softness: .22, strength: .7 });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A `flatten` with no `height` levels the pad to the ground already under it and lets the blend reach as far as its deepest cut or fill, so it is a bench rather than a mesa.
|
|
50
|
+
|
|
51
|
+
Stroke tools accept `at` or a polyline `points`. Brush radius is a **radius**, not diameter. Strength is signed metres for sculpt; `0..1` for smoothing, flattening and paint. Stroke coverage uses maximum falloff coverage, not repeated accumulation at every mouse event. Holding a brush stationary does not keep adding height. Points are interpolated at `2 * radius * spacing`; jitter is seeded. Procedural stamps use `amplitude` in metres. `opacity` is available on every layer.
|
|
52
|
+
|
|
53
|
+
Masks are serializable expressions, never callbacks:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const forest = Mask.and(
|
|
57
|
+
Mask.height(8, 85, 10), // altitude band, feathered boundaries
|
|
58
|
+
Mask.slope(0, 34, 6), // slope degrees
|
|
59
|
+
Mask.not(Mask.material('road')),
|
|
60
|
+
Mask.not(Mask.circle([-70, 50], 38, .3)),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
t.biome({ id: 'forest-zone', label: 'Conifer biome', name: 'forest', mask: forest });
|
|
64
|
+
t.scatter({ id: 'trees', asset: 'pine', count: 900, minDistance: 5,
|
|
65
|
+
scale: [.75,1.25], maxSlope: 35, mask: Mask.biome('forest') });
|
|
66
|
+
t.clear({ id: 'clear-camp', target: 'scatter', mask: Mask.circle([-70,50],35) });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Additional masks: `Mask.rectangle(at, size, rotation, falloff)`, `Mask.noise(scale, threshold, seed, fade)`, `Mask.or(...)`, `Mask.all()`, `Mask.none()`. Slope/height masks are evaluated against the terrain at that operation's stage. Population is deferred: scatter rules sample the **final** heights/materials/biomes and re-evaluate their mask there, so later height edits do not leave trees floating. Clear-scatter masks affect preceding rules only. Minimum distance is guaranteed **within each rule**, not across different scatter rules. The bounded rejection sampler may place fewer than requested; inspect diagnostics rather than assuming an exact count.
|
|
70
|
+
|
|
71
|
+
Materials are exactly `grass`, `dirt`, `rock`, `snow`, `sand`, `mud`, `road`, `moss`. Splat arrays are sample-major: `splat[sampleIndex * 8 + channel]`. Weights sum to one. `paint()` blends and renormalizes; `materials()` applies ordered rules. A later rule can override an earlier one.
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
t.materials({ id: 'surfaces', base: 'grass', rules: [
|
|
75
|
+
{ material: 'sand', mask: Mask.height(-100, 5, 3) },
|
|
76
|
+
{ material: 'rock', mask: Mask.slope(42, 90, 8) },
|
|
77
|
+
{ material: 'snow', mask: Mask.height(95, 1000, 15) },
|
|
78
|
+
] });
|
|
79
|
+
t.paint({ id: 'camp-dirt', at: [-70,50], radius: 27, material: 'dirt', strength: .9 });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Roads, rivers and water
|
|
83
|
+
|
|
84
|
+
A road, river or ramp **grades itself to the ground it crosses** when a control point leaves its elevation out (`null`, or `NaN` in code) or when it sets `followTerrain: true`. The elevation is sampled along the centreline, low-passed along arc length, then held inside `maxGrade` (default 0.12) and `maxCut`/`maxFill` (default 2.5 m / 1.5 m); the shoulder widens into a batter with the cut or fill, so a corridor never stands on a retaining step. Leaving the ground is the hard limit: where the ground itself is steeper than `maxGrade`, the road is steep too. Absolute elevations are still honoured exactly, so existing documents keep their meaning. Horizontal spline interpolation is Catmull–Rom; vertical interpolation is linear between controls to avoid vertical overshoot. Use `smooth:false` for a polyline.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
t.road({ id: 'access-road', points: [[-220,null,100],[-150,null,70],[-70,null,50]], width: 12, shoulder: 8 });
|
|
88
|
+
t.river({ id: 'river-main', points: [[20,14,-240],[35,8,0],[80,3,240]], width: 18, depth: 4, enforceDownhill: true });
|
|
89
|
+
t.water({ id: 'lake', kind: 'lake', at: [80,210], radius: 75, level: 4 });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`enforceDownhill:true` validates non-increasing Y, rather than silently changing elevations in the API. River carving only lowers terrain. The water surface is a ribbon, not a flow simulation. Keep profiles sensible and avoid self-intersecting splines.
|
|
93
|
+
|
|
94
|
+
Lake flood fill starts at the supplied location, follows connected samples below `level`, and is limited by `radius`/mask. A seed above water level produces an empty mask and a diagnostic. Oceans flood from map boundaries. Water placement does not itself excavate a basin: lower the basin first. Scatter avoids flooded lake/ocean samples and submerged river ribbons by default.
|
|
95
|
+
|
|
96
|
+
## Copy/paste and external heightfields
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
const heights = t.copyRegion({ at: [90,-70], size: 110, resolution: 65 });
|
|
100
|
+
t.paste({ id: 'copied-mass', data: heights, at: [-120,-90], size: 110,
|
|
101
|
+
rotation: 90, mirrorX: true, blend: 'replace', falloff: .2 });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The copy contains heights only. Paste/stamp data is `{width,height,values}` in world metres. Fluent `heightmap()` / `paste()` / `stamp()` normalize typed arrays to plain arrays; raw recipe JSON must already contain plain arrays. RAW files have no self-describing dimensions/range. Preserve sidecars. PNG import accepts non-interlaced grayscale 8/16-bit images only, not RGB/palette images, and reports `hasEmbeddedRange`.
|
|
105
|
+
|
|
106
|
+
Stamp and paste footprints use `at: [x,z]`, `radius: [halfWidth,halfDepth]` or
|
|
107
|
+
`size: [width,depth]`, and `rotation` in degrees (positive turns +X towards +Z).
|
|
108
|
+
Existing `radius` takes precedence over `size`. All three landform operations use
|
|
109
|
+
positive `scale` for vertical gain and `offset` in metres after that gain;
|
|
110
|
+
additive blends add both the scaled height and offset. Heightmap accepts optional
|
|
111
|
+
`at`, `size`, `rotation` and `falloff` to place a bounded rectangular footprint.
|
|
112
|
+
Without these footprint fields it retains the supplied full-world sampling.
|
|
113
|
+
Outside a bounded footprint, existing terrain remains unchanged. Samples must
|
|
114
|
+
all be finite numbers. X/Z rotations that create overhangs have no heightfield
|
|
115
|
+
representation. These transforms change recipe parameters, not an independent
|
|
116
|
+
solid hidden inside the evaluated terrain.
|
|
117
|
+
|
|
118
|
+
## Inspect and export
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
console.log(t.inspect({ resolution: 129 }));
|
|
122
|
+
const recipe = t.toJSON();
|
|
123
|
+
const same = Terrain.fromJSON(recipe);
|
|
124
|
+
const result = same.evaluate({ resolution: 257 });
|
|
125
|
+
const runtime = bakeTerrain(result, { chunkCells: 64, lodSteps: [1,2,4], skirtDepth: 6 });
|
|
126
|
+
const archive = await makeExport(result, recipe, 'runtime');
|
|
127
|
+
// Node: await writeFile(archive.name, archive.bytes)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`makeExport` kinds are `project`, `png`, `raw`, `splat`, `glb`, `runtime`; all return `{name,type,bytes}`. Exporting does not mutate the authoring recipe. Collision height samples are row-major from Z=-size/2 to Z=+size/2, with X increasing along each row. Grid vertices are `(x/(N-1)-.5)*size, height[z*N+x], (z/(N-1)-.5)*size`.
|
|
131
|
+
|
|
132
|
+
Do not promise navmesh, physics registration, EXR, seamless mixed-LOD stitching, procedural caves, true water simulation, or production art from this version. GLB exports terrain only; load placement assets separately. Native-engine integration is via arrays/manifests and still needs an engine-specific adapter.
|
|
133
|
+
|
|
134
|
+
## Bounds and operational safety
|
|
135
|
+
|
|
136
|
+
Valid grid resolutions: 17, 33, 65, 129, 257, 513, 1025 **vertices per side**. Use 129 or 257 while iterating, and inspect before moving to 513/1025. Changing resolution can change erosion and population. No cross-resolution exactness guarantee exists.
|
|
137
|
+
|
|
138
|
+
Maximum recipe layers: 512; maximum requested instances per scatter rule: 20,000; hydraulic droplets: 200,000; steps per droplet: 128; thermal/smooth iterations: 200; noise octaves: 10; spline/brush input points: 8192; interpolated brush centres per stroke: 65,536. These are input safeguards, not real-time performance targets. Do not choose all maximums together.
|
|
139
|
+
|
|
140
|
+
The evaluator is synchronous. An AbortSignal is checked between operations, not inside a long erosion pass. For real cancellation of a headless job, isolate it in a Node worker/process and terminate the worker. A prefix cache stores private snapshots, up to 48 MiB of counted height/splat/biome arrays by default; JavaScript metadata and output arrays are additional memory.
|
|
141
|
+
|
|
142
|
+
Never execute untrusted JavaScript from a recipe. The editor parses data only. Surface validation errors and preserve the previous valid recipe. Save the recipe, not just the baked mesh, before destructive project housekeeping.
|
|
143
|
+
|
|
144
|
+
Splatmaps are linear data, not color images: use a non-sRGB/NoColorSpace texture setting and renormalize sampled weights in the consuming shader. Do not use an 8-bit canvas conversion to read a PNG16 heightfield; the supplied decoder preserves its 16-bit samples.
|
|
145
|
+
|
|
146
|
+
## Live editor: environment, assets and surfaces
|
|
147
|
+
|
|
148
|
+
The running editor exposes three revision-checked operations beside the document, each also behind a GUI
|
|
149
|
+
panel that calls the same code. A write names `baseRevision`; a stale one is a 409 and nothing changes.
|
|
150
|
+
|
|
151
|
+
- `controller.environment({ op: "get" | "patch" | "reset" }, revision)`: sun azimuth/elevation/intensity/colour,
|
|
152
|
+
sky fill, sky colour or image, `exp2` haze, exposure, sea colours, and an independent `lighting` image.
|
|
153
|
+
`null` returns a field to the project's own value; an unsupported fog mode is refused by name. The
|
|
154
|
+
project's `src/render/` decides what each value does. Nothing here evaluates the terrain.
|
|
155
|
+
- `controller.asset({ op: "register" | "upload" | "adjust" | "remove" | "list" | "map" | "unmap" }, revision)`:
|
|
156
|
+
`register` takes an absolute local path (an asset-MCP download result), `upload` takes base64 bytes. Files
|
|
157
|
+
are stored as `<kind>/<sha12>-<id>.<ext>` so a replacement is a new URL; GLB, PNG, JPEG, WebP, HDR and EXR are
|
|
158
|
+
recognised by their bytes, measured, and refused by name when damaged, over the project's limits, or when a
|
|
159
|
+
GLB points outside itself. Same id with other bytes needs `replace: true`. `remove` drops the entry, never
|
|
160
|
+
the file, and is refused while a surface or an environment still uses it.
|
|
161
|
+
- `map` binds an image to a `<surface>.<channel>` input the project exposes (`bark.albedo`, `stone.normal`);
|
|
162
|
+
albedo is read as sRGB, every other channel as linear data. Placing a model is a `scatter` layer whose
|
|
163
|
+
`asset` is its id; `adjust` sets the file's unit scale and pivot without editing it.
|
|
164
|
+
|
|
165
|
+
## Three.js ownership
|
|
166
|
+
|
|
167
|
+
`@threenative/terrain/three` exports `toGeometry(bakeMesh(state))`. It creates only ordinary indexed geometry using your installed Three.js. Create the mesh/material in your own `src/render/`, register collision through the existing engine API, and dispose the geometry yourself. Baked vertex colours are absent by default; pass an explicit eight-channel sRGB `palette` to `bakeMesh` or `bakeTerrain` when wanted. The addon supplies no palette or material. The legacy `makeExport(..., "glb")` contains terrain only; use the separate static-world encoder below for resolved models and baked surfaces. The default GUI full-world action, chosen-resolution material/deformation baking and five final starter exports remain pending.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
## Resolved static-world GLB
|
|
171
|
+
|
|
172
|
+
Import `exportWorldGLB` from `@threenative/terrain/export` in browser authoring source. Its import is DOM-free; encoding requires FileReader and canvas. Root and `/three` remain headless. This entry uses the installed Three.js GLTFExporter rather than a terrain-specific loader format.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { exportWorldGLB } from '@threenative/terrain/export';
|
|
176
|
+
const output = await exportWorldGLB({
|
|
177
|
+
revision, // accepted document's 64-character content hash
|
|
178
|
+
snapshotTime, // finite seconds, pinned across all baked deformations
|
|
179
|
+
state, // evaluated committed recipe with applied override records
|
|
180
|
+
terrain, // canonical metre-frame Mesh with UVs and prepared PBR maps
|
|
181
|
+
assets, // Map<assetId, Object3D>: actual static models
|
|
182
|
+
transforms, // Map<placementId, Matrix4>: actual final grounded root matrices
|
|
183
|
+
water, // [{ id, object, time: snapshotTime, staleFrames: 0 }]
|
|
184
|
+
});
|
|
185
|
+
// output.bytes is the self-contained world.glb; persist only after success.
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Game source owns all geometry, materials and images. The terrain needs albedo `map`, `normalMap`, `roughnessMap` and `aoMap`; vertex colours alone are insufficient. MeshStandardMaterial metallic/roughness surfaces are supported. Colour/emissive images are sRGB, data maps are NoColorSpace. Supply decoded byte images (at most 16 megapixels); DataTexture images need complete RGBA bytes. Bake alpha into the colour image. Bake displacement, bump/light maps, node shaders, skinning/morphing and wind into the supplied static geometry/material first. Use tangent-space normal maps with equal X/Y scale magnitudes; bake object-space/anisotropic maps first. MeshPhysicalMaterial extensions are outside this bounded encoder's contract.
|
|
189
|
+
|
|
190
|
+
All final matrices use metres, Y-up, positive scale and no shear. Keys are durable placement IDs, never instance indices; unmatched matrices fail. Ordinary glTF nodes share model mesh data without requiring EXT_mesh_gpu_instancing. Source geometry, images and material values are copied before asynchronous encoding, so later source edits cannot mix the export snapshot. Input objects remain owned by the caller.
|
|
191
|
+
|
|
192
|
+
Every evaluated water body/river needs its actual baked object at exactly `snapshotTime` with `staleFrames: 0`. A low-resolution or stale ocean CPU height sample does not satisfy this contract. Missing/unbaked content rejects before producing a download; preserve the previous valid export and document. Encoding does not perform the project's material bake, chosen-resolution re-evaluation or ocean readback for you.
|
|
193
|
+
|
|
194
|
+
`output.report` records revision, resolution, time and placement/water IDs. Model cameras/lights and source userData are excluded. The receiving game supplies its own lighting, sky/environment, fog, exposure, post and live water/wind; GLB does not carry those running systems. A vanilla Three.js GLTFLoader or a normal game model loader can consume the bytes without this addon.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ThreeNative contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @threenative/terrain
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Optional, headless terrain authoring recovered from Strata. One seeded recipe produces
|
|
4
|
+
height/splat arrays, resolved placements, baked LOD chunks and numerical exports.
|
|
5
|
+
Games consume baked data and own appearance, collision registration and disposal.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Terrain, Mask, bakeMesh } from '@threenative/terrain';
|
|
9
|
+
import { toGeometry } from '@threenative/terrain/three';
|
|
10
|
+
|
|
11
|
+
const terrain = new Terrain({ size: 512, resolution: 257, seed: 73 })
|
|
12
|
+
.noise({ id: 'hills', amplitude: 35 })
|
|
13
|
+
.flatten({ id: 'camp', height: 12, mask: Mask.circle([20, 30], 15) });
|
|
14
|
+
const geometry = toGeometry(bakeMesh(terrain.evaluate()));
|
|
15
|
+
// Your game creates the Mesh/material and disposes geometry.
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Repeating an operation ID replaces that layer in place. `applyPatch` batches edits
|
|
19
|
+
atomically; malformed writes preserve the previous recipe. Evaluation is synchronous
|
|
20
|
+
and belongs in authoring/build jobs. Browser/editor dependencies are absent from
|
|
21
|
+
root and geometry imports. Three.js is a peer, never a bundled second copy.
|
|
22
|
+
|
|
23
|
+
The recovered `makeExport` GLB/runtime archives contain terrain geometry. The separate
|
|
24
|
+
`@threenative/terrain/export` entry exports resolved static worlds through Three.js
|
|
25
|
+
GLTFExporter: the caller supplies final models/matrices, baked UV PBR maps and
|
|
26
|
+
coherent water snapshots. It requires browser FileReader/canvas for encoding,
|
|
27
|
+
embeds images/buffers, excludes model cameras/lights, and uses ordinary shared
|
|
28
|
+
mesh nodes. Missing models/maps, shader deformation or stale water fail by name.
|
|
29
|
+
The default GUI full-world action, chosen-resolution baking and five final starter
|
|
30
|
+
world exports remain pending. See the agent guide for the explicit contract.
|
|
31
|
+
No browser/native terrain-game acceptance result is claimed by these library tests.
|
|
32
|
+
|
|
33
|
+
See [agent guide](AGENT_GUIDE.md) and [source provenance](THIRD_PARTY_NOTICES.md).
|
|
34
|
+
|
|
35
|
+
Optional live authoring uses `@threenative/terrain/editor/server` in the project's
|
|
36
|
+
Vite configuration and `@threenative/terrain/editor` in its browser authoring entry.
|
|
37
|
+
`terrainEditor({ documentPath })` mounts revisioned JSON/SSE endpoints on the
|
|
38
|
+
existing loopback server. The configured file contains `{ version: 1, recipe }`;
|
|
39
|
+
agent patches and GUI changes share that authority. Invalid external saves retain
|
|
40
|
+
the last valid document with a diagnostic, and stale writes return conflict.
|
|
41
|
+
|
|
42
|
+
The project provides `/terrain-editor/index.html` and calls `mountTerrainEditor`
|
|
43
|
+
with its own `IEditorView` and material colours. The addon creates no renderer or
|
|
44
|
+
scene. `TerrainEditorController(editorUrl)` exposes `activate`, `snapshot` and
|
|
45
|
+
atomic `commit`; the activation result names the actually bound URL, project,
|
|
46
|
+
session and revision. Use a configured forwarded viewer URL for remote access;
|
|
47
|
+
the loopback API does not become a public write service.
|
|
48
|
+
|
|
49
|
+
Scatter placement `id` identifies the layer, unsigned seed and candidate attempt,
|
|
50
|
+
not the accepted array index. Mask/spacing changes can remove candidates without
|
|
51
|
+
retargeting surviving keys or their seeded scale/yaw. Changing a seed creates new
|
|
52
|
+
identities. Save keys rather than mesh instance indices for later authoring edits.
|
|
53
|
+
|
|
54
|
+
The authoring document may also contain `placementOverrides`, keyed by those IDs.
|
|
55
|
+
`validatePlacementOverrides()` validates finite position/quaternion/positive scale
|
|
56
|
+
and defaults omitted `grounding` to true. `applyPlacementOverrides(state, overrides)`
|
|
57
|
+
attaches each requested transform to its placement and reports retained unmatched
|
|
58
|
+
keys. `bakeTerrain()` preserves those records. The game applies grounding against
|
|
59
|
+
its actual mesh bounds and terrain triangles; disabling grounding retains measured
|
|
60
|
+
clearance. The example's Select tool uses ordinary Three.js TransformControls and
|
|
61
|
+
numeric fields, saves once per drag, and preserves newer edits on conflict.
|
|
62
|
+
|
|
63
|
+
`mountTerrainEditor` passes `(host, controller)` to `createView`. The view implements
|
|
64
|
+
`setDocument(document, revision)` for metadata changes without terrain evaluation;
|
|
65
|
+
its `update(state)` may return the resolved state for inspection and data bakes.
|
|
66
|
+
|
|
67
|
+
The example also selects stamp, paste and heightmap layers by stable ID. One proxy
|
|
68
|
+
edits recipe footprints and vertical gain/offset; its line follows the visible
|
|
69
|
+
terrain, with rebuilding on commit. X/Z rotation is disabled because a heightfield
|
|
70
|
+
cannot overhang. `stamp`/`paste` accept positive vertical `scale`; `heightmap` also
|
|
71
|
+
accepts optional metre `at`/`size`, degree `rotation` and `falloff` for a bounded
|
|
72
|
+
footprint. The original full-world heightmap path remains the default.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Strata source provenance
|
|
2
|
+
|
|
3
|
+
João supplied `strata-terrain.html` and `AGENT_GUIDE.md` and explicitly confirmed
|
|
4
|
+
on 2026-09-30: “I own it; use MIT.” The recovered modules and their integration
|
|
5
|
+
are distributed under the accompanying MIT license. The supplied files contained
|
|
6
|
+
no earlier copyright/license notice; no upstream license or attribution is invented.
|
|
7
|
+
|
|
8
|
+
Source SHA-256:
|
|
9
|
+
|
|
10
|
+
- `strata-terrain.html`: `c0230e6891db829a5196d93ff7c9d714a8f2d964cac4722eb8ce4b2f723d794b`.
|
|
11
|
+
- `AGENT_GUIDE.md`: `487453218f870c5e829d58aea3fe9323e74ea93e0aecef8d5c329ecf56972d06`.
|
|
12
|
+
|
|
13
|
+
Recovery used the HTML's named `sources` module map, rather than its duplicated
|
|
14
|
+
worker blob or minified viewer. The algorithms, validation and transactions are
|
|
15
|
+
maintained as TypeScript source. Viewer appearance/renderer code is excluded;
|
|
16
|
+
baking takes optional caller-owned colours and exports no chosen material.
|
|
17
|
+
|
|
18
|
+
The optional `src/editor/` tooling also recovers the supplied app, icons, starter
|
|
19
|
+
recipes, worker and static UI shell. Local-storage authority and the separate
|
|
20
|
+
Three.js viewer are replaced by a revisioned project document and a required
|
|
21
|
+
project-owned view. Formatting changes preserve the recovered controls; GUI
|
|
22
|
+
swatches receive the game's explicit colours. Root and `/three` imports do not
|
|
23
|
+
load this tooling.
|
|
24
|
+
|
|
25
|
+
Maintained behavior difference: scatter candidates use layer/unsigned-seed/attempt
|
|
26
|
+
keys and consume the same five seeded draws even when rejected. This fixes the
|
|
27
|
+
supplied accepted-index identity and conditional-RNG defect. Placement lists
|
|
28
|
+
intentionally differ from the supplied source; the supplied noise/erosion
|
|
29
|
+
regression remains unchanged. The adapter's pine, boulder and grass shapes are
|
|
30
|
+
adapted in the example's editable `src/render/props.ts`, using installed merge and
|
|
31
|
+
instance mechanisms; no addon material or renderer is introduced.
|
|
32
|
+
|
|
33
|
+
Landform extensions add positive vertical gain to stamp/paste and a bounded,
|
|
34
|
+
translated/rotated rectangular footprint to heightmap. Additive stamp/paste now
|
|
35
|
+
honor the supplied vertical offset, which the recovered additive branch ignored.
|
|
36
|
+
Height data validation rejects missing or nonnumeric samples. Default gain/offset and heightmaps without
|
|
37
|
+
footprint fields retain the recovered sampling; the numerical noise/erosion
|
|
38
|
+
golden remains unchanged.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { P as PatchCommand } from '../terrain-gPnkCU6S.js';
|
|
2
|
+
import { I as ITerrainState } from '../types-Ant8-Axc.js';
|
|
3
|
+
import { I as IAuthoringDocument, a as ISavedCamera, b as IFocusRequest, c as IFocusOutcome, d as IFocusBounds, e as IEnvironment, f as IEditorActivation, g as IEditorSnapshot, h as ISpatialQuery, i as ISpatialObservation, j as ICameraOperation, k as ICameraResult, l as IEnvironmentOperation, m as IEnvironmentResult, n as IAssetOperation, o as IAssetResult } from '../server-ByM2hmYJ.js';
|
|
4
|
+
export { p as IAssetAdjust, q as IAssetBounds, r as IAssetKind, s as IAssetLimits, t as ICameraPose, u as ICameraSet, v as IFocusFraming, w as IFocusResolver, x as IFocusTarget, y as IHexColour, z as IPointObservation, A as IProfileObservation, B as IProfileSample, C as IProjectAsset, D as IReferenceObservation, E as ISavedSpatialReference, F as ISpatialControlPoint, G as ISpatialReference, H as ISurfaceMapping, J as ISurfaceMappings, K as ISurfaceReading, L as checkEnvironmentAssets, M as focusCamera, N as inspectSpatial, O as probeTerrain, P as runCameraOperation, Q as runEnvironmentOperation, R as sniff, S as surfaceSpace, T as validateAsset, U as validateAssets, V as validateCamera, W as validateCameras, X as validateEnvironment, Y as validateSpatialReference, Z as validateSurfaces } from '../server-ByM2hmYJ.js';
|
|
5
|
+
import 'vite';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Image header inspection for registered files: the real format, the real pixel size and whether the
|
|
9
|
+
* container is whole. It reads headers, never pixels, and never decodes into a texture: the project's
|
|
10
|
+
* render source loads the image through `ctx.assets` with the colour space its channel needs.
|
|
11
|
+
*/
|
|
12
|
+
interface IImageReport {
|
|
13
|
+
readonly format: "png" | "jpeg" | "webp" | "hdr" | "exr";
|
|
14
|
+
readonly width: number;
|
|
15
|
+
readonly height: number;
|
|
16
|
+
readonly diagnostics: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What an image's pixels mean once a game binds them. Colour is stored in sRGB and decoded to linear
|
|
20
|
+
* when sampled; every other channel is a number and must never be decoded.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The colour space of each surface channel.
|
|
24
|
+
* @summary Map a PBR channel name to the colour space its pixels are read in
|
|
25
|
+
* @requires npm i -D @threenative/terrain
|
|
26
|
+
* @situation load an imported albedo as sRGB and a normal, roughness, ao, height, opacity or metalness map as linear data
|
|
27
|
+
* @constraint a table only; the project's render source loads and binds the texture
|
|
28
|
+
* @example const space = SURFACE_CHANNELS.normal;
|
|
29
|
+
* @override the project owns which surface inputs exist and what they draw
|
|
30
|
+
*/
|
|
31
|
+
declare const SURFACE_CHANNELS: {
|
|
32
|
+
readonly albedo: "srgb";
|
|
33
|
+
readonly ao: "linear";
|
|
34
|
+
readonly height: "linear";
|
|
35
|
+
readonly metalness: "linear";
|
|
36
|
+
readonly normal: "linear";
|
|
37
|
+
readonly opacity: "linear";
|
|
38
|
+
readonly roughness: "linear";
|
|
39
|
+
};
|
|
40
|
+
type ISurfaceChannel = keyof typeof SURFACE_CHANNELS;
|
|
41
|
+
/**
|
|
42
|
+
* Validate a PNG, JPEG, WebP, Radiance HDR or OpenEXR container and read its pixel size.
|
|
43
|
+
* @summary Inspect an image file's container and pixel size without decoding it
|
|
44
|
+
* @requires npm i -D @threenative/terrain
|
|
45
|
+
* @situation check an imported surface or environment image for damage and for the project's size limit
|
|
46
|
+
* @constraint headers and checksums only; throws by name for a damaged, truncated or oversize file
|
|
47
|
+
* @example const report = inspectImage(bytes, "png", { maxDimension: 8192 });
|
|
48
|
+
* @override the project sets the dimension limit
|
|
49
|
+
*/
|
|
50
|
+
declare function inspectImage(bytes: Uint8Array, format: string, limits: {
|
|
51
|
+
maxDimension: number;
|
|
52
|
+
}): IImageReport;
|
|
53
|
+
|
|
54
|
+
/** The live view's own camera surface, shared by the GUI list and a controller. */
|
|
55
|
+
interface IViewCamera {
|
|
56
|
+
/** The actual current viewer pose, not the last saved bookmark. */
|
|
57
|
+
read(): IViewerPose;
|
|
58
|
+
/** Apply a saved pose to the real camera, or the ordinary editor camera when given null. */
|
|
59
|
+
apply(camera: ISavedCamera | null): void;
|
|
60
|
+
/**
|
|
61
|
+
* Frame a target for the live viewport; an unknown target keeps the last valid camera and
|
|
62
|
+
* reports why. The view measures the aspect itself, so a caller cannot save a stale one.
|
|
63
|
+
*/
|
|
64
|
+
focus(request: Omit<IFocusRequest, "aspect">): IFocusOutcome;
|
|
65
|
+
/** Resolves a non-point target against the live world; the view owns the world matrices. */
|
|
66
|
+
resolve(target: {
|
|
67
|
+
kind: "prop" | "landmark" | "region";
|
|
68
|
+
id: string;
|
|
69
|
+
}): IFocusBounds | undefined;
|
|
70
|
+
/** Clip-square extent of bounds under the live camera, so a framing can be measured, not assumed. */
|
|
71
|
+
measure(bounds: IFocusBounds): {
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
z: boolean;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** The live view's environment surface: bind saved overrides to the project's own render source. */
|
|
78
|
+
interface IViewEnvironment {
|
|
79
|
+
/**
|
|
80
|
+
* Apply saved overrides to the real lights, haze, sky, exposure and sea. Absent fields restore
|
|
81
|
+
* the project's own value; a field this source cannot honour throws by name and the scene keeps
|
|
82
|
+
* its last valid look.
|
|
83
|
+
*/
|
|
84
|
+
apply(environment: IEnvironment | undefined): void;
|
|
85
|
+
/** The effective values now in use: the project's own, with the saved overrides on top. */
|
|
86
|
+
read(): IEnvironment;
|
|
87
|
+
}
|
|
88
|
+
interface IViewerPose {
|
|
89
|
+
readonly position: readonly [number, number, number];
|
|
90
|
+
readonly target: readonly [number, number, number];
|
|
91
|
+
readonly up: readonly [number, number, number];
|
|
92
|
+
readonly projection: "perspective" | "orthographic";
|
|
93
|
+
readonly fov: number | null;
|
|
94
|
+
readonly extent: number | null;
|
|
95
|
+
readonly zoom: number | null;
|
|
96
|
+
readonly near: number;
|
|
97
|
+
readonly far: number;
|
|
98
|
+
readonly aspect: number;
|
|
99
|
+
readonly activeCamera: string | null;
|
|
100
|
+
}
|
|
101
|
+
interface IEditorView {
|
|
102
|
+
readonly backend: string;
|
|
103
|
+
update(state: ITerrainState): ITerrainState | undefined;
|
|
104
|
+
setDocument(document: IAuthoringDocument, revision: string): void;
|
|
105
|
+
pick(clientX: number, clientY: number): [number, number, number] | null;
|
|
106
|
+
setMode(mode: string): void;
|
|
107
|
+
setBrush(brush: {
|
|
108
|
+
at: [number, number];
|
|
109
|
+
radius: number;
|
|
110
|
+
rotation?: number;
|
|
111
|
+
shape?: string;
|
|
112
|
+
} | null): void;
|
|
113
|
+
showSpline(points: readonly [number, number, number][]): void;
|
|
114
|
+
setNavigation(enabled: boolean): void;
|
|
115
|
+
setSelection?(enabled: boolean): void;
|
|
116
|
+
selectLayer?(id: string): void;
|
|
117
|
+
setView(view: string): void;
|
|
118
|
+
frame(): void;
|
|
119
|
+
registerAsset?(id: string, object: unknown): void;
|
|
120
|
+
/**
|
|
121
|
+
* The asset IDs this project can place and scatter. The recovered palette asks the view rather
|
|
122
|
+
* than shipping a starter list, so a game's own props and its registered imports are what the
|
|
123
|
+
* scatter and clear controls offer.
|
|
124
|
+
*/
|
|
125
|
+
propAssets?(): readonly string[];
|
|
126
|
+
/**
|
|
127
|
+
* Settles once every registered model the view started loading is placeable or has failed. The
|
|
128
|
+
* preview waits on it before drawing a revision, so a placement of a model still arriving is
|
|
129
|
+
* not reported as a missing asset.
|
|
130
|
+
*/
|
|
131
|
+
assetsReady?(): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* The texture inputs of this project's render source that an imported image can replace, named
|
|
134
|
+
* `<surface>.<channel>`. The project decides what exists; the GUI offers exactly this list.
|
|
135
|
+
*/
|
|
136
|
+
surfaceInputs?(): readonly {
|
|
137
|
+
input: string;
|
|
138
|
+
channel: string;
|
|
139
|
+
}[];
|
|
140
|
+
cameras?(): IViewCamera;
|
|
141
|
+
environment?(): IViewEnvironment;
|
|
142
|
+
/**
|
|
143
|
+
* The whole rendered revision as a portable GLB: terrain, resolved models with their final
|
|
144
|
+
* transforms, this game's baked water and its portable PBR surfaces. Optional because a headless
|
|
145
|
+
* or DOM-free view has nothing to encode from; the export card then says so rather than quietly
|
|
146
|
+
* falling back to the terrain-only worker export.
|
|
147
|
+
*/
|
|
148
|
+
exportCurrentWorld?(): Promise<{
|
|
149
|
+
name: string;
|
|
150
|
+
mime: string;
|
|
151
|
+
bytes: Uint8Array;
|
|
152
|
+
}>;
|
|
153
|
+
dispose(): void;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* HTTP controller for the project-local terrain editor.
|
|
157
|
+
* @requires npm i -D @threenative/terrain
|
|
158
|
+
* @situation inspect and atomically edit the terrain editor from an agent
|
|
159
|
+
* @constraint optional tooling; only subscribe/mount touches DOM; stale revisions fail explicitly
|
|
160
|
+
* @example const controller = new TerrainEditorController(editorUrl); const snapshot = await controller.snapshot();
|
|
161
|
+
* @override editorUrl is the activation-returned project URL
|
|
162
|
+
*/
|
|
163
|
+
declare class TerrainEditorController {
|
|
164
|
+
#private;
|
|
165
|
+
readonly baseUrl: string;
|
|
166
|
+
constructor(editorUrl: string);
|
|
167
|
+
activate(): Promise<IEditorActivation>;
|
|
168
|
+
snapshot(): Promise<IEditorSnapshot>;
|
|
169
|
+
/**
|
|
170
|
+
* Answer a read-only spatial query against one exact revision, GUI or headless.
|
|
171
|
+
* @param query a `point`, `profile` or `reference` query from the saved document revision
|
|
172
|
+
*/
|
|
173
|
+
inspect(query: ISpatialQuery, baseRevision: string): Promise<{
|
|
174
|
+
result: ISpatialObservation;
|
|
175
|
+
}>;
|
|
176
|
+
/**
|
|
177
|
+
* Run one create / get / list / update / delete / activate camera operation against one exact
|
|
178
|
+
* revision. Editor observation cameras only: gameplay cameras belong to the consuming game.
|
|
179
|
+
* @param operation a camera operation from {@link ICameraOperation}
|
|
180
|
+
*/
|
|
181
|
+
camera(operation: ICameraOperation, baseRevision: string): Promise<ICameraResult & {
|
|
182
|
+
revision: string;
|
|
183
|
+
}>;
|
|
184
|
+
/**
|
|
185
|
+
* Run one get / patch / reset against the saved preview environment at one exact revision.
|
|
186
|
+
* Appearance only: terrain, collision and placements are never re-evaluated.
|
|
187
|
+
* @param operation an environment operation from {@link IEnvironmentOperation}
|
|
188
|
+
*/
|
|
189
|
+
environment(operation: IEnvironmentOperation, baseRevision: string): Promise<IEnvironmentResult & {
|
|
190
|
+
revision: string;
|
|
191
|
+
}>;
|
|
192
|
+
/**
|
|
193
|
+
* Register, upload, adjust, remove or list project assets at one exact revision. `register`
|
|
194
|
+
* takes a local file path, the shape an asset-MCP download returns; `upload` takes bytes a
|
|
195
|
+
* browser read. Both store the file under a content-hashed name and measure it.
|
|
196
|
+
* @param operation an asset operation from {@link IAssetOperation}
|
|
197
|
+
*/
|
|
198
|
+
asset(operation: IAssetOperation, baseRevision: string): Promise<IAssetResult & {
|
|
199
|
+
revision: string;
|
|
200
|
+
}>;
|
|
201
|
+
commit(transaction: {
|
|
202
|
+
baseRevision: string;
|
|
203
|
+
document: IAuthoringDocument;
|
|
204
|
+
} | {
|
|
205
|
+
baseRevision: string;
|
|
206
|
+
commands: readonly PatchCommand[];
|
|
207
|
+
}): Promise<IEditorSnapshot>;
|
|
208
|
+
subscribe(listener: (snapshot: IEditorSnapshot) => void, onError: () => void): () => void;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Mount recovered terrain controls around the project-owned ThreeNative view.
|
|
212
|
+
* @requires npm i -D @threenative/terrain
|
|
213
|
+
* @situation open the terrain brush and layer GUI around game-owned rendering
|
|
214
|
+
* @constraint browser authoring only; createView and materialColours are required game choices
|
|
215
|
+
* @example await mountTerrainEditor({ createView: createEditorView, materialColours: terrainPalette });
|
|
216
|
+
* @override createView owns the renderer scene, materials, lighting and camera
|
|
217
|
+
*/
|
|
218
|
+
declare function mountTerrainEditor(options: {
|
|
219
|
+
createView(host: HTMLElement, controller: TerrainEditorController): Promise<IEditorView>;
|
|
220
|
+
materialColours: readonly (readonly [number, number, number])[];
|
|
221
|
+
onEvaluated?(event: {
|
|
222
|
+
revision: string;
|
|
223
|
+
state: ITerrainState;
|
|
224
|
+
ms: number;
|
|
225
|
+
}): void;
|
|
226
|
+
}): Promise<{
|
|
227
|
+
controller: TerrainEditorController;
|
|
228
|
+
dispose(): void;
|
|
229
|
+
}>;
|
|
230
|
+
|
|
231
|
+
export { IAssetOperation, IAssetResult, ICameraOperation, ICameraResult, type IEditorView, IEnvironment, IEnvironmentOperation, IEnvironmentResult, IFocusBounds, IFocusOutcome, IFocusRequest, type IImageReport, ISavedCamera, ISpatialObservation, ISpatialQuery, type ISurfaceChannel, type IViewCamera, type IViewEnvironment, type IViewerPose, SURFACE_CHANNELS, TerrainEditorController, inspectImage, mountTerrainEditor };
|