incanto 0.27.0 → 0.29.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/README.md +44 -0
- package/bin/incanto-editor.mjs +118 -1
- package/dist/2d.d.ts +38 -5
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +496 -150
- package/dist/3d.js +5 -5
- package/dist/{behavior-CPibUfnH.d.ts → behavior-BAc0erXF.d.ts} +21 -0
- package/dist/{create-game-DUAtTqID.js → create-game-CNKXGfpr.js} +270 -47
- package/dist/{create-game-C0bPoURs.js → create-game-CbuLWorm.js} +53 -14
- package/dist/debug.d.ts +51 -6
- package/dist/debug.js +242 -19
- package/dist/editor-switch-B0wB_DSr.d.ts +59 -0
- package/dist/editor-switch-CAKlJMIY.js +161 -0
- package/dist/editor.d.ts +49 -0
- package/dist/editor.js +8439 -0
- package/dist/{gameplay-Ddk13pQ6.js → gameplay-L05WgWd1.js} +220 -37
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.js +2 -2
- package/dist/{loader-DILt9PGC.d.ts → loader-B242FF6N.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +2 -2
- package/dist/{pathfinding-RWYkNKx9.d.ts → pathfinding-BwD974Ss.d.ts} +1 -1
- package/dist/{physics-2d-DiVFFlH3.js → physics-2d-CCVTrKOd.js} +62 -2
- package/dist/{physics-3d-4mGzOZ2J.js → physics-3d-BZZLtwJu.js} +439 -8
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-nObreUQR.js → register-BTg0EM7s.js} +35 -3
- package/dist/{register-DcHXS1MA.js → register-C44aSduO.js} +5694 -5108
- package/dist/{register-CvpSUU3O.js → register-DWcWq4QG.js} +22 -2
- package/dist/{register-BFFE1Mh1.js → register-MelqEdza.js} +1 -1
- package/dist/test.d.ts +35 -3
- package/dist/test.js +104 -10
- package/dist/vite.d.ts +53 -1
- package/dist/vite.js +94 -2
- package/editor/assets/{agent8-DElIPozC.js → agent8-DEVkEa3d.js} +1 -1
- package/editor/assets/debug-zGAtpDF0.js +2 -0
- package/editor/assets/index-Bx4UtWYY.js +10586 -0
- package/editor/index.html +3 -157
- package/package.json +3 -2
- package/schemas/scene.schema.json +110 -4
- package/skills/incanto-3d-models.md +38 -0
- package/skills/incanto-assets.md +13 -0
- package/skills/incanto-building-3d-games.md +83 -6
- package/skills/incanto-editor.md +151 -0
- package/skills/incanto-environment.md +88 -1
- package/skills/incanto-node-reference.md +35 -1
- package/skills/incanto-physics-and-input.md +45 -6
- package/skills/incanto-verifying-your-game.md +51 -2
- package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +5 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/docs/project-3d-rules.md +5 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/docs/project-3d-rules.md +5 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/editor/assets/index-BrneVqN0.js +0 -8330
- package/editor/assets/index-D8QvwvOm.css +0 -1
package/dist/3d.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-
|
|
3
|
-
import { t as
|
|
1
|
+
import { i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
+
import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-BAc0erXF.js";
|
|
3
|
+
import { t as EditorSwitchOptions } from "./editor-switch-B0wB_DSr.js";
|
|
4
|
+
import { t as LoadSceneOptions } from "./loader-B242FF6N.js";
|
|
4
5
|
import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
|
|
5
|
-
import { n as PathGrid, s as SpatialPose } from "./pathfinding-
|
|
6
|
-
import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Vector3, WebGLRenderer } from "three";
|
|
6
|
+
import { n as PathGrid, s as SpatialPose } from "./pathfinding-BwD974Ss.js";
|
|
7
|
+
import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Vector3, WebGLRenderer } from "three";
|
|
7
8
|
import { VRM } from "@pixiv/three-vrm";
|
|
8
9
|
import { Sky } from "three/examples/jsm/objects/Sky.js";
|
|
9
10
|
import * as RapierNs from "@dimforge/rapier3d-compat";
|
|
@@ -124,13 +125,6 @@ interface SunConsumer3D {
|
|
|
124
125
|
/** @internal Called by syncTree when `environment.sky` declares a sun. */
|
|
125
126
|
_applySunDirection(dir: readonly [number, number, number]): void;
|
|
126
127
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Base of every 3D node: a JSON-prop transform backed by a `THREE.Object3D`.
|
|
129
|
-
*
|
|
130
|
-
* Props stay plain JSON (registry/serializer compatible); the renderer layer
|
|
131
|
-
* pushes them onto the backing object once per frame (`_syncObject3D`).
|
|
132
|
-
* Rotation is in DEGREES in JSON — friendlier for agents and humans.
|
|
133
|
-
*/
|
|
134
128
|
declare class Node3D extends Node {
|
|
135
129
|
static override readonly typeName: string;
|
|
136
130
|
static readonly props: PropSchema;
|
|
@@ -154,6 +148,23 @@ declare class Node3D extends Node {
|
|
|
154
148
|
renderOrder: number;
|
|
155
149
|
/** Named draw-order band; `renderOrder` is the fine offset inside it. */
|
|
156
150
|
orderGroup: string;
|
|
151
|
+
/**
|
|
152
|
+
* PUT THIS ON THE GROUND. `true` sets the node's Y from the terrain under it
|
|
153
|
+
* at load; a number does the same and lifts it by that many metres (a post
|
|
154
|
+
* whose origin sits at its middle wants half its height).
|
|
155
|
+
*
|
|
156
|
+
* Computing a Y by hand is the single most repeated mistake in scene
|
|
157
|
+
* authoring, and it is not a careless one: the number is right when it is
|
|
158
|
+
* written and wrong the moment anything reshapes the ground — a river carving
|
|
159
|
+
* its bed, a basin, a different seed, a generator that mirrors the carve
|
|
160
|
+
* slightly differently. Every "floating boulder", "buried player" and
|
|
161
|
+
* "bridge posts in mid-air" in this engine's history is that drift.
|
|
162
|
+
*
|
|
163
|
+
* With this the ground is asked at load, in the same frame the terrain is
|
|
164
|
+
* built, and there is nothing to drift from. `null` (default) leaves `y`
|
|
165
|
+
* exactly as authored — for anything that genuinely hangs in the air.
|
|
166
|
+
*/
|
|
167
|
+
snapToGround: number | boolean | null;
|
|
157
168
|
/** What three actually sorts by: band base + renderOrder. */
|
|
158
169
|
get effectiveRenderOrder(): number;
|
|
159
170
|
private _object3D;
|
|
@@ -165,6 +176,15 @@ declare class Node3D extends Node {
|
|
|
165
176
|
*/
|
|
166
177
|
_interpPrev: number[] | null;
|
|
167
178
|
_interpCurr: number[] | null;
|
|
179
|
+
override onReady(): void;
|
|
180
|
+
/**
|
|
181
|
+
* Resolve {@link snapToGround} against the terrain under this node.
|
|
182
|
+
*
|
|
183
|
+
* Ancestor transforms are translation-only here, the same contract heightAt
|
|
184
|
+
* and the physics heightfield already follow, so the world height converts
|
|
185
|
+
* back to a local Y by subtracting the parents' offset.
|
|
186
|
+
*/
|
|
187
|
+
applyGroundSnap(): void;
|
|
168
188
|
/** @internal The backing three object (lazily created). */
|
|
169
189
|
_ensureObject3D(): Object3D;
|
|
170
190
|
/** @internal Override point for subclasses (mesh, camera, light…). */
|
|
@@ -262,6 +282,8 @@ declare class Physics3D {
|
|
|
262
282
|
private readonly engine;
|
|
263
283
|
/** Render collider outlines in the GAME view (renderers pick this up). */
|
|
264
284
|
debugDraw: boolean;
|
|
285
|
+
/** Narrow those outlines to one node's subtree (see DebugLineSource). */
|
|
286
|
+
debugScope: object | null;
|
|
265
287
|
readonly dimension = "3d";
|
|
266
288
|
private readonly unregisterDebug;
|
|
267
289
|
private readonly warnedNoCollider;
|
|
@@ -281,13 +303,36 @@ declare class Physics3D {
|
|
|
281
303
|
moveAndSlide(node: CharacterBody3D): void;
|
|
282
304
|
/** Rapier debug segments (meters). Null while debugDraw is off. */
|
|
283
305
|
debugLines(): Float32Array | null;
|
|
306
|
+
/**
|
|
307
|
+
* The Rapier bodies a selected node stands for: its own, its descendants',
|
|
308
|
+
* and — when it owns none — the nearest body ABOVE it, so clicking the visual
|
|
309
|
+
* mesh child of a StaticBody3D shows that body's collider rather than nothing.
|
|
310
|
+
*/
|
|
311
|
+
private scopedBodies;
|
|
284
312
|
dispose(): void;
|
|
285
313
|
private lastStructure;
|
|
286
314
|
private lastRoot;
|
|
287
315
|
private readonly jointSet;
|
|
288
316
|
private readonly joints;
|
|
317
|
+
/** One fixed body per solid scatter field, carrying a collider per row. */
|
|
318
|
+
private readonly scatterBodies;
|
|
289
319
|
private readonly bodySet;
|
|
320
|
+
/**
|
|
321
|
+
* Give every row of a scatter field its own static collider.
|
|
322
|
+
*
|
|
323
|
+
* A stone field is one node with fifty transforms in it, and a player walking
|
|
324
|
+
* through all fifty is what gives a world away as scenery. All of them hang
|
|
325
|
+
* off ONE fixed body — they never move, so there is nothing to step — and the
|
|
326
|
+
* whole set is rebuilt only when the tree's shape changes.
|
|
327
|
+
*/
|
|
328
|
+
private syncScatters;
|
|
290
329
|
private syncBodies;
|
|
330
|
+
/**
|
|
331
|
+
* Null while a `{"shape":"auto"}` body is waiting on its model — the shape
|
|
332
|
+
* lives in the model's triangles, and there is nothing honest to guess in the
|
|
333
|
+
* meantime. syncBodies runs every step, so the body appears the moment the
|
|
334
|
+
* download lands.
|
|
335
|
+
*/
|
|
291
336
|
private ensureEntry;
|
|
292
337
|
/** Apply a world-space impulse to a dynamic body (character controllers). */
|
|
293
338
|
applyImpulse(node: PhysicsBody3D, impulse: [number, number, number]): void;
|
|
@@ -374,6 +419,20 @@ interface CreateGame3DOptions {
|
|
|
374
419
|
_debugDoc?: {
|
|
375
420
|
createElement(tag: string): HTMLElement;
|
|
376
421
|
};
|
|
422
|
+
/**
|
|
423
|
+
* LIVE EDITING: the ☰ debug menu gains "edit this scene", which turns this
|
|
424
|
+
* page into the scene editor — same window, no reload — and the editor's play
|
|
425
|
+
* button turns it back into the game. The scene and the camera survive both
|
|
426
|
+
* crossings.
|
|
427
|
+
*
|
|
428
|
+
* DEFAULTS TO `debug`: a build with the debug overlay is a build someone is
|
|
429
|
+
* working on, and "the scene I am looking at, open in the editor" is what
|
|
430
|
+
* working on it means. It costs nothing until clicked — the editor is a lazy
|
|
431
|
+
* chunk. `false` opts out; `{ open }` loads it yourself (apps that alias
|
|
432
|
+
* incanto to source); `{ save }` makes the editor's save button write your
|
|
433
|
+
* file. Never reachable without `debug` — the ☰ menu is its only entrance.
|
|
434
|
+
*/
|
|
435
|
+
editor?: boolean | EditorSwitchOptions;
|
|
377
436
|
seed?: number;
|
|
378
437
|
fixedHz?: number;
|
|
379
438
|
pixelRatio?: number;
|
|
@@ -381,6 +440,19 @@ interface CreateGame3DOptions {
|
|
|
381
440
|
* (default ON). `false` pins the resolution — screenshots, capture, tests. */
|
|
382
441
|
adaptiveResolution?: boolean;
|
|
383
442
|
resolveScene?: LoadSceneOptions["resolveScene"];
|
|
443
|
+
/**
|
|
444
|
+
* Boot progress, for the loading screen an app already has.
|
|
445
|
+
*
|
|
446
|
+
* Assets are only the FIRST half of a boot: building a dressed scene and
|
|
447
|
+
* compiling its GPU programs takes as long again, and a bar that hits 100%
|
|
448
|
+
* on the last downloaded byte then sits there while the world is built is
|
|
449
|
+
* worse than no bar — the player reads "done" and waits anyway.
|
|
450
|
+
*
|
|
451
|
+
* `fraction` runs 0 → 1 across everything this call does, and `label` names
|
|
452
|
+
* the phase ('scene', 'physics', 'renderer', 'shaders', 'ready'). Feed the
|
|
453
|
+
* asset preload into the first half and this into the second.
|
|
454
|
+
*/
|
|
455
|
+
onProgress?: (fraction: number, label: string) => void;
|
|
384
456
|
/** @internal Test seam — replaces the Renderer3D construction. */
|
|
385
457
|
_rendererFactory?: (engine: Engine, canvas: HTMLCanvasElement) => GameRenderer;
|
|
386
458
|
/** @internal Test seam — replaces the rAF scheduler. */
|
|
@@ -394,10 +466,32 @@ interface GameRenderer {
|
|
|
394
466
|
interface Game3D {
|
|
395
467
|
engine: Engine;
|
|
396
468
|
scene: Scene$1;
|
|
469
|
+
/**
|
|
470
|
+
* The AUTHORED scene this game booted from — the JSON, not the live tree.
|
|
471
|
+
*
|
|
472
|
+
* A running tree has moved: physics has stepped it, behaviors have spawned
|
|
473
|
+
* into it, the player is somewhere else. Handing that to an editor would
|
|
474
|
+
* make "edit the scene" mean "edit wherever the simulation happens to be",
|
|
475
|
+
* so the source is kept beside the game and handed over unchanged.
|
|
476
|
+
*/
|
|
477
|
+
sourceJson: SceneJson;
|
|
397
478
|
renderer: GameRenderer;
|
|
398
479
|
physics: Physics3D | null;
|
|
399
480
|
/** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
|
|
400
481
|
stats(): GameStats;
|
|
482
|
+
/**
|
|
483
|
+
* Turn this page into the scene editor, right where the player is standing —
|
|
484
|
+
* what the ☰ debug menu's "edit this scene" calls. Bind it to your own key or
|
|
485
|
+
* button if you want the switch without the overlay.
|
|
486
|
+
*
|
|
487
|
+
* Resolves once the editor has taken the page. The game is disposed on the
|
|
488
|
+
* way out and rebooted when the editor hands it back, so the `Game3D` this
|
|
489
|
+
* was called on is DEAD afterwards — take the new one from the editor's
|
|
490
|
+
* `play` path if you need a handle.
|
|
491
|
+
*
|
|
492
|
+
* `null` when the game was booted with `editor: false` — nothing to open.
|
|
493
|
+
*/
|
|
494
|
+
openEditor: (() => Promise<void>) | null;
|
|
401
495
|
dispose(): void;
|
|
402
496
|
}
|
|
403
497
|
/**
|
|
@@ -909,6 +1003,12 @@ declare class Flowers3D extends Node3D {
|
|
|
909
1003
|
* draping entirely.
|
|
910
1004
|
*/
|
|
911
1005
|
drape: boolean | null;
|
|
1006
|
+
/**
|
|
1007
|
+
* Keep plants out of standing and running water (default true) — a meadow a
|
|
1008
|
+
* stream runs through has a bank of flowers, not flowers in the current.
|
|
1009
|
+
* Set false for water lilies and marsh planting.
|
|
1010
|
+
*/
|
|
1011
|
+
avoidWater: boolean;
|
|
912
1012
|
/** Drape target: a node path to the Terrain3D. Empty = auto-find the first
|
|
913
1013
|
* Terrain3D in the tree (the zero-config default when `drape` is on). */
|
|
914
1014
|
terrain: string;
|
|
@@ -1051,6 +1151,14 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
|
|
|
1051
1151
|
* a terrain appears), false disables draping entirely.
|
|
1052
1152
|
*/
|
|
1053
1153
|
drape: boolean | null;
|
|
1154
|
+
/**
|
|
1155
|
+
* Keep blades out of standing and running water (default true). Grass does
|
|
1156
|
+
* not grow in a stream: any instance whose ground sits under a `Water3D` or
|
|
1157
|
+
* `River3D` surface is dropped, which is what stops a creek that cut its bed
|
|
1158
|
+
* through a meadow from filling with waving blades. Set false for reeds and
|
|
1159
|
+
* paddy fields, where the plant IS in the water.
|
|
1160
|
+
*/
|
|
1161
|
+
avoidWater: boolean;
|
|
1054
1162
|
/** Drape target: a node path to the Terrain3D. Empty = auto-find the first
|
|
1055
1163
|
* Terrain3D in the tree (the zero-config default when `drape` is on). */
|
|
1056
1164
|
terrain: string;
|
|
@@ -1119,6 +1227,14 @@ declare class Foliage3D extends Node3D implements SunConsumer3D {
|
|
|
1119
1227
|
* fraction of instances into a SECOND InstancedMesh of 5-vertex flower
|
|
1120
1228
|
* heads (white/yellow/violet per instance).
|
|
1121
1229
|
*/
|
|
1230
|
+
/**
|
|
1231
|
+
* Drop the instances standing in water. The ground each blade drapes onto is
|
|
1232
|
+
* already known here, so the test is one question per candidate — and only
|
|
1233
|
+
* the ones a water surface actually covers pay for it.
|
|
1234
|
+
*/
|
|
1235
|
+
private dryOnly;
|
|
1236
|
+
/** This field's own world height (the bed sampler works in local Y). */
|
|
1237
|
+
private worldY;
|
|
1122
1238
|
private buildMeshField;
|
|
1123
1239
|
/**
|
|
1124
1240
|
* The meadow's flower sprinkle: ONE extra InstancedMesh of 5-vertex flower
|
|
@@ -1247,6 +1363,23 @@ declare class InstancedMesh3D extends Node3D {
|
|
|
1247
1363
|
material: MeshMaterialProps;
|
|
1248
1364
|
castShadow: boolean;
|
|
1249
1365
|
receiveShadow: boolean;
|
|
1366
|
+
/**
|
|
1367
|
+
* Make the scatter SOLID: `{}` gives every row a collider shaped like the
|
|
1368
|
+
* mesh itself — a faceted `gem` stone collides as that stone's own hull, not
|
|
1369
|
+
* as a ball tucked inside it — sized from `size` × the row's scale.
|
|
1370
|
+
*
|
|
1371
|
+
* A field of fifty boulders a player walks straight through is the tell that
|
|
1372
|
+
* a world is a picture. One fixed body carries all the colliders, so the cost
|
|
1373
|
+
* is a handful of shapes and no per-frame work — these never move.
|
|
1374
|
+
*
|
|
1375
|
+
* `{ shape: 'sphere' | 'box' }` (with `radius`/`size`) opts out into a
|
|
1376
|
+
* cheaper approximation when a rough blocker is genuinely all you want.
|
|
1377
|
+
*/
|
|
1378
|
+
collider: {
|
|
1379
|
+
shape?: string;
|
|
1380
|
+
radius?: number;
|
|
1381
|
+
size?: number[];
|
|
1382
|
+
} | null;
|
|
1250
1383
|
private _transforms;
|
|
1251
1384
|
private transformsDirty;
|
|
1252
1385
|
get transforms(): number[][];
|
|
@@ -1401,6 +1534,23 @@ declare class ModelInstance3D extends Node3D {
|
|
|
1401
1534
|
/** false = play the clip once, then emit `animationFinished`. */
|
|
1402
1535
|
animationLoop: boolean;
|
|
1403
1536
|
/**
|
|
1537
|
+
* Playback rate of BOTH animation layers. 1 is the speed the clip was
|
|
1538
|
+
* authored at; 0.5 halves it, 2 doubles it, 0 freezes on the current pose.
|
|
1539
|
+
* Negative plays backwards (a closing door is an opening one reversed).
|
|
1540
|
+
*
|
|
1541
|
+
* Live: changing it retimes the clip already playing, no restart.
|
|
1542
|
+
*/
|
|
1543
|
+
animationSpeed: number;
|
|
1544
|
+
/**
|
|
1545
|
+
* Seconds into the clip to start at.
|
|
1546
|
+
*
|
|
1547
|
+
* A row of identical torches all starting at 0 flickers in lockstep and the
|
|
1548
|
+
* eye reads it instantly as "copies". Give each a different offset and the
|
|
1549
|
+
* same clip becomes a crowd. Applied when a clip STARTS (a restart re-seeks),
|
|
1550
|
+
* wrapped into the clip's duration, so any number is legal.
|
|
1551
|
+
*/
|
|
1552
|
+
animationOffset: number;
|
|
1553
|
+
/**
|
|
1404
1554
|
* Two-layer animation: play this clip on the UPPER BODY ONLY (the
|
|
1405
1555
|
* `upperBodyRoot` bone subtree) while `animation` keeps driving the rest —
|
|
1406
1556
|
* attack while running, wave while walking. One-shot by default
|
|
@@ -1425,7 +1575,21 @@ declare class ModelInstance3D extends Node3D {
|
|
|
1425
1575
|
private mountedRef;
|
|
1426
1576
|
private fittedHeight;
|
|
1427
1577
|
private fitGroup;
|
|
1578
|
+
/**
|
|
1579
|
+
* @internal The mounted model, or null while the asset is still loading.
|
|
1580
|
+
* Physics reads this to shape a `{"shape":"auto"}` collider on the real
|
|
1581
|
+
* triangles — nothing else may reach into the loaded three graph.
|
|
1582
|
+
*/
|
|
1583
|
+
_mountedObject3D(): Group | null;
|
|
1428
1584
|
private mixer;
|
|
1585
|
+
/** The offset the playing clip was seeked to — re-seek when the prop moves. */
|
|
1586
|
+
private appliedOffset;
|
|
1587
|
+
/** @internal Test seam — the mixer driving this model's clips. */
|
|
1588
|
+
get _mixer(): AnimationMixer | null;
|
|
1589
|
+
/** @internal Test seam — which `animation` key is actually playing. */
|
|
1590
|
+
get _playingClip(): string;
|
|
1591
|
+
/** @internal Test seam — where the base clip currently is, in seconds. */
|
|
1592
|
+
get _currentActionTime(): number;
|
|
1429
1593
|
private playing;
|
|
1430
1594
|
private currentAction;
|
|
1431
1595
|
private playingUpper;
|
|
@@ -1555,29 +1719,152 @@ declare class Particles3D extends Node3D {
|
|
|
1555
1719
|
private syncParticles;
|
|
1556
1720
|
}
|
|
1557
1721
|
//#endregion
|
|
1558
|
-
//#region src/3d/
|
|
1722
|
+
//#region src/3d/terrain/heightmap.d.ts
|
|
1559
1723
|
/**
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
* imports nothing from three and stays testable in plain node.
|
|
1565
|
-
*
|
|
1566
|
-
* The shape of a river is NOT authored: it is derived. A path of control
|
|
1567
|
-
* points and a width profile is all a scene declares; the ground underneath
|
|
1568
|
-
* decides the rest —
|
|
1569
|
-
*
|
|
1570
|
-
* - the **surface profile** follows the bed's descending envelope, so the water
|
|
1571
|
-
* runs downhill and never climbs (a rising bed dams it into a still pool);
|
|
1572
|
-
* - the **visible width** is whatever the terrain lets the water occupy: every
|
|
1573
|
-
* vertex carries its own water column (`surfaceY − bed`), the shader fades to
|
|
1574
|
-
* nothing where that reaches zero, so banks, gravel bars and half-drowned
|
|
1575
|
-
* boulders carve the ribbon instead of a hand-authored outline;
|
|
1576
|
-
* - the **current** obeys continuity (narrow = fast) and grade (steep = fast),
|
|
1577
|
-
* which is what makes rapids sit where a channel pinches or tips.
|
|
1724
|
+
* A smooth radial depression carved into the terrain — a lake/pond bowl. x/z
|
|
1725
|
+
* are terrain-LOCAL centered meters (the same space `heightAt`/`slopeAt` use);
|
|
1726
|
+
* `radius`/`depth` are meters. The carve is a smoothstep bowl (0 slope at the
|
|
1727
|
+
* rim), so banks blend into the surrounding ground with no cliff.
|
|
1578
1728
|
*/
|
|
1729
|
+
interface BasinSpec {
|
|
1730
|
+
x: number;
|
|
1731
|
+
z: number;
|
|
1732
|
+
radius: number;
|
|
1733
|
+
/** > 0 carves a bowl; < 0 RAISES a smooth dome mound of that height. */
|
|
1734
|
+
depth: number;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* A river/road trench carved along a polyline: the line version of a basin.
|
|
1738
|
+
* `width` is the flat bed, `taper` the bank blend beyond it (default: the bed
|
|
1739
|
+
* width again, which is what makes a walkable V instead of a slot).
|
|
1740
|
+
*/
|
|
1741
|
+
interface ChannelSpec {
|
|
1742
|
+
path: readonly (readonly number[])[];
|
|
1743
|
+
width: number;
|
|
1744
|
+
depth: number;
|
|
1745
|
+
taper?: number;
|
|
1746
|
+
}
|
|
1747
|
+
interface HeightmapOptions {
|
|
1748
|
+
/** Terrain extent in meters along x. */
|
|
1749
|
+
width: number;
|
|
1750
|
+
/** Terrain extent in meters along z. */
|
|
1751
|
+
depth: number;
|
|
1752
|
+
/** Grid segments along x (vertices = segsX+1). */
|
|
1753
|
+
segsX: number;
|
|
1754
|
+
/** Grid segments along z (vertices = segsZ+1). */
|
|
1755
|
+
segsZ: number;
|
|
1756
|
+
/** Height scale — realized heights land at roughly 3.5–8× this value. */
|
|
1757
|
+
maxHeight: number;
|
|
1758
|
+
/** Integer seed — same seed, same terrain, every machine. */
|
|
1759
|
+
seed: number;
|
|
1760
|
+
/** Detail persistence (amplitude falloff per detail octave). */
|
|
1761
|
+
roughness?: number;
|
|
1762
|
+
/** Detail octave count. */
|
|
1763
|
+
detail?: number;
|
|
1764
|
+
/** Snap-flatten heights within ±0.6 m of maxHeight·flatThreshold. */
|
|
1765
|
+
flatThreshold?: number;
|
|
1766
|
+
/** Curl the border down a quarter circle and floor it at edgeBottom. */
|
|
1767
|
+
islandEdge?: boolean;
|
|
1768
|
+
/** Quarter-circle radius in meters (clamped to [0, 45]). */
|
|
1769
|
+
edgeWrapRadius?: number;
|
|
1770
|
+
/** Floor y at the outermost ring once the wrap completes. */
|
|
1771
|
+
edgeBottom?: number;
|
|
1772
|
+
/** 0..1 — 1 rounds island corners fully, 0 keeps them square-ish. */
|
|
1773
|
+
edgeCornerSmoothness?: number;
|
|
1774
|
+
/** Smooth radial lake/pond bowls carved into the surface (interior basins). */
|
|
1775
|
+
basins?: BasinSpec[];
|
|
1776
|
+
/** River/road trenches carved along polylines (see {@link ChannelSpec}). */
|
|
1777
|
+
channels?: ChannelSpec[];
|
|
1778
|
+
}
|
|
1779
|
+
interface Heightmap {
|
|
1780
|
+
readonly width: number;
|
|
1781
|
+
readonly depth: number;
|
|
1782
|
+
readonly segsX: number;
|
|
1783
|
+
readonly segsZ: number;
|
|
1784
|
+
/**
|
|
1785
|
+
* Vertex heights INCLUDING edge wrap, `(segsX+1)·(segsZ+1)` long, indexed
|
|
1786
|
+
* `iz·(segsX+1)+ix` with x = -width/2 + ix·cell and z = -depth/2 + iz·cell —
|
|
1787
|
+
* exactly a rotateX(-π/2) PlaneGeometry's vertex order.
|
|
1788
|
+
*/
|
|
1789
|
+
readonly heights: Float32Array;
|
|
1790
|
+
/** Realized height range EXCLUDING edge wrap — normalizes splat heights. */
|
|
1791
|
+
readonly minHeight: number;
|
|
1792
|
+
readonly maxHeight: number;
|
|
1793
|
+
/** Bilinear height lookup in terrain-local centered meters (clamps outside). */
|
|
1794
|
+
heightAt(x: number, z: number): number;
|
|
1795
|
+
/** Analytic pipeline height WITHOUT edge wrap (splat domain), same coords. */
|
|
1796
|
+
baseHeight(x: number, z: number): number;
|
|
1797
|
+
/** Splat slope at a point: atan(|∇ baseHeight|) via central differences. */
|
|
1798
|
+
slopeAt(x: number, z: number): number;
|
|
1799
|
+
}
|
|
1800
|
+
/** One splat layer: a texture plus the trapezoid ranges that place it. */
|
|
1801
|
+
interface TerrainLayer {
|
|
1802
|
+
/** Base color texture URL. */
|
|
1803
|
+
texture: string;
|
|
1804
|
+
/** Optional tangent-space normal map URL. */
|
|
1805
|
+
normalMap?: string;
|
|
1806
|
+
/** World-UV tiling factor (UV = worldXZ · repeat · 0.1). Default 2. */
|
|
1807
|
+
repeat?: number;
|
|
1808
|
+
/** Normalized [min, max] over the realized height span. Default [0, 1]. */
|
|
1809
|
+
heightRange?: [number, number];
|
|
1810
|
+
/** [min, max] slope in radians (atan of gradient). Default [0, π/2]. */
|
|
1811
|
+
slopeRange?: [number, number];
|
|
1812
|
+
/** Linear falloff width OUTSIDE heightRange (fraction of span). Default 0.1. */
|
|
1813
|
+
heightBlendRange?: number;
|
|
1814
|
+
/** Linear falloff width outside slopeRange (radians). Default 0.1. */
|
|
1815
|
+
slopeBlendRange?: number;
|
|
1816
|
+
/** PBR roughness scalar for this layer. Default 1. */
|
|
1817
|
+
roughness?: number;
|
|
1818
|
+
/** PBR metalness scalar for this layer. Default 0. */
|
|
1819
|
+
metalness?: number;
|
|
1820
|
+
/** Anti-tiling UV jitter strength. Default 0.15. */
|
|
1821
|
+
uvNoiseIntensity?: number;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Build the deterministic heightfield. Heavy (one noise eval per vertex) —
|
|
1825
|
+
* call once and keep the result.
|
|
1826
|
+
*/
|
|
1827
|
+
declare function buildHeightmap(opts: HeightmapOptions): Heightmap;
|
|
1828
|
+
/**
|
|
1829
|
+
* Per-vertex splat weights for up to 4 layers. `height01` is the vertex
|
|
1830
|
+
* height normalized over the heightmap's realized (no-wrap) span, `slopeRad`
|
|
1831
|
+
* is `atan(|∇h|)`. Weight = heightTrapezoid · slopeTrapezoid, sharpened by
|
|
1832
|
+
* `pow(w, 1/blendingStrength)`, then normalized to sum 1 — when nothing
|
|
1833
|
+
* matches, layer 0 takes everything. Always returns exactly 4 entries
|
|
1834
|
+
* (missing layers weigh 0).
|
|
1835
|
+
*/
|
|
1836
|
+
declare function splatWeights(height01: number, slopeRad: number, layers: readonly TerrainLayer[], blendingStrength?: number): [number, number, number, number];
|
|
1837
|
+
//#endregion
|
|
1838
|
+
//#region src/3d/water/river.d.ts
|
|
1579
1839
|
/** Terrain height under a LOCAL-frame point, in the same frame's meters. */
|
|
1580
1840
|
type BedSampler = (x: number, z: number) => number;
|
|
1841
|
+
interface RiverCarveOptions {
|
|
1842
|
+
/** Centreline in the TERRAIN'S local frame (that is where channels live). */
|
|
1843
|
+
path: readonly (readonly number[])[];
|
|
1844
|
+
/** Width profile source→mouth (one entry = constant). */
|
|
1845
|
+
widths: readonly number[];
|
|
1846
|
+
/** Water column at the centreline, meters. */
|
|
1847
|
+
depth: number;
|
|
1848
|
+
/** Terrain cell size in meters — a cut finer than this does not exist. */
|
|
1849
|
+
cell: number;
|
|
1850
|
+
/** Ground height BEFORE this river cut anything. */
|
|
1851
|
+
groundAt: BedSampler;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* The trench a river digs for itself, as {@link ChannelSpec}s for Terrain3D.
|
|
1855
|
+
*
|
|
1856
|
+
* Shared on purpose: `River3D` cuts this at load, and anything that has to
|
|
1857
|
+
* PLACE things against the result — a scene generator putting a player, a
|
|
1858
|
+
* bridge and a boulder field on the ground — computes the identical cut
|
|
1859
|
+
* instead of approximating it. A generator that guesses differently spawns the
|
|
1860
|
+
* player inside a hill.
|
|
1861
|
+
*
|
|
1862
|
+
* The floor only ever FALLS. Water does not climb, so a bed that climbs is a
|
|
1863
|
+
* river that stops and buries itself; carrying the running minimum downstream
|
|
1864
|
+
* digs through a rise instead, and the cap keeps that from turning a hill into
|
|
1865
|
+
* a canyon.
|
|
1866
|
+
*/
|
|
1867
|
+
declare function riverCarveChannels(opts: RiverCarveOptions): ChannelSpec[];
|
|
1581
1868
|
/** One arc-length sample of the smoothed centerline. */
|
|
1582
1869
|
interface PathSample {
|
|
1583
1870
|
x: number;
|
|
@@ -1588,6 +1875,30 @@ interface PathSample {
|
|
|
1588
1875
|
/** Meters from the source. */
|
|
1589
1876
|
along: number;
|
|
1590
1877
|
}
|
|
1878
|
+
/** One reach where the ribbon could not cover the ground its water stands on. */
|
|
1879
|
+
interface RiverCoverageGap {
|
|
1880
|
+
/** Metres from the source. */
|
|
1881
|
+
along: number;
|
|
1882
|
+
/** What the terrain says is under water at this station. */
|
|
1883
|
+
wettedHalfWidth: number;
|
|
1884
|
+
/** What a ribbon can carry through the bend here. */
|
|
1885
|
+
carriedHalfWidth: number;
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Where a river's ribbon falls short of the water it is standing in.
|
|
1889
|
+
*
|
|
1890
|
+
* A swept ribbon has a hard geometric ceiling — it cannot be wider than the
|
|
1891
|
+
* bend it is taking, or its inner edge folds through itself. When the ground
|
|
1892
|
+
* under the surface is wetter than that ceiling allows, the ribbon leaves BARE
|
|
1893
|
+
* CHANNEL FLOOR beside the water, and to a player the creek simply stops in
|
|
1894
|
+
* the middle of its own bed. No amount of shader work fixes it: the surface
|
|
1895
|
+
* really is missing.
|
|
1896
|
+
*
|
|
1897
|
+
* This reports those reaches so a caller can act on them instead of shipping
|
|
1898
|
+
* them. River3D calls it at build time and warns; `incanto/test` exposes it so
|
|
1899
|
+
* a game can gate on it headlessly.
|
|
1900
|
+
*/
|
|
1901
|
+
declare function findRiverCoverageGaps(rings: readonly RiverRing[], tolerance?: number): RiverCoverageGap[];
|
|
1591
1902
|
/** A cross-section station: where the water is, how wide, how fast. */
|
|
1592
1903
|
interface RiverRing extends PathSample {
|
|
1593
1904
|
/** The width the author ASKED for (half of it) — nominal, drives current. */
|
|
@@ -1595,6 +1906,9 @@ interface RiverRing extends PathSample {
|
|
|
1595
1906
|
/** Wetted half-width to the left of the flow: where the ground rises to the
|
|
1596
1907
|
* surface. This, not {@link halfWidth}, is where the water actually ends. */
|
|
1597
1908
|
halfLeft: number;
|
|
1909
|
+
/** What the terrain scan asked for before the bend capped it (fit only). */
|
|
1910
|
+
wettedLeft?: number;
|
|
1911
|
+
wettedRight?: number;
|
|
1598
1912
|
/** Wetted half-width to the right of the flow. */
|
|
1599
1913
|
halfRight: number;
|
|
1600
1914
|
/** No bank on that side: the ground never came up to the surface, so the
|
|
@@ -1609,6 +1923,26 @@ interface RiverRing extends PathSample {
|
|
|
1609
1923
|
/** Surface current in m/s. */
|
|
1610
1924
|
speed: number;
|
|
1611
1925
|
}
|
|
1926
|
+
interface RiverRingOptions {
|
|
1927
|
+
path: readonly (readonly number[])[];
|
|
1928
|
+
/** One entry = constant width; several = a profile lerped source→mouth. */
|
|
1929
|
+
widths: readonly number[];
|
|
1930
|
+
/** Water column at the centerline, in meters. */
|
|
1931
|
+
depth: number;
|
|
1932
|
+
/** Reference current in m/s (the mean over the river). */
|
|
1933
|
+
flowSpeed: number;
|
|
1934
|
+
/** Centerline sampling step in meters. */
|
|
1935
|
+
step: number;
|
|
1936
|
+
bedAt: BedSampler;
|
|
1937
|
+
/**
|
|
1938
|
+
* Fit the surface to the ground across the channel — the surface may not
|
|
1939
|
+
* stand above the rim that holds it, and the ribbon ends where the ground
|
|
1940
|
+
* comes up to meet it. Default OFF, because it is meaningful only when
|
|
1941
|
+
* `bedAt` is REAL ground: a river with no terrain under it invents a bed one
|
|
1942
|
+
* `depth` below itself, and there is nothing there to contain or spill it.
|
|
1943
|
+
*/
|
|
1944
|
+
fit?: boolean;
|
|
1945
|
+
}
|
|
1612
1946
|
interface RiverHit {
|
|
1613
1947
|
/** In the channel: inside the banks AND between source and mouth. */
|
|
1614
1948
|
inside: boolean;
|
|
@@ -1623,11 +1957,57 @@ interface RiverHit {
|
|
|
1623
1957
|
speed: number;
|
|
1624
1958
|
halfWidth: number;
|
|
1625
1959
|
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Make a traced course fit to carry a river of a given width.
|
|
1962
|
+
*
|
|
1963
|
+
* `traceDownhillPath` follows the steepest descent, which on real terrain
|
|
1964
|
+
* zigzags, kinks and sometimes hooks back within a few metres of a reach it
|
|
1965
|
+
* already ran. A ribbon on such a course cannot be well formed at any width
|
|
1966
|
+
* worth looking at: tight corners fold its inner edge, and a hook stacks two
|
|
1967
|
+
* sheets of water on the same ground. Every symptom of that is a rendering
|
|
1968
|
+
* artifact with no rendering fix.
|
|
1969
|
+
*
|
|
1970
|
+
* So the course is conditioned first, in three steps that are all about the
|
|
1971
|
+
* WIDTH the river will be:
|
|
1972
|
+
* 1. Laplacian passes relax the trace toward a curve whose bends a ribbon of
|
|
1973
|
+
* `halfWidth` can take.
|
|
1974
|
+
* 2. Points closer together than the width are dropped — they only encode
|
|
1975
|
+
* noise at that scale.
|
|
1976
|
+
* 3. The course is CUT where it comes back within two widths of itself: a
|
|
1977
|
+
* creek does not flow over its own reach, and ending is a better answer
|
|
1978
|
+
* than two stacked sheets or a strangled trickle.
|
|
1979
|
+
*
|
|
1980
|
+
* Use it on anything `traceDownhillPath` produces before handing it to River3D.
|
|
1981
|
+
*/
|
|
1982
|
+
declare function smoothCourse(path: readonly (readonly number[])[], halfWidth: number, passes?: number): number[][];
|
|
1983
|
+
/**
|
|
1984
|
+
* The centerline station spacing River3D itself uses — EXPORTED so a generator
|
|
1985
|
+
* placing things against the water calls the same derivation. A hardcoded step
|
|
1986
|
+
* puts the generator's stations in different places than the node's; on a
|
|
1987
|
+
* smooth reach nobody notices, on a tight tail the two snap to different rings
|
|
1988
|
+
* and rocks grown for one water stand beside the other.
|
|
1989
|
+
*/
|
|
1990
|
+
declare function riverStepFor(widths: readonly number[], depth: number): number;
|
|
1991
|
+
declare function buildRiverRings(opts: RiverRingOptions): RiverRing[];
|
|
1992
|
+
declare function projectToRiver(rings: readonly RiverRing[], x: number, z: number): RiverHit | null;
|
|
1626
1993
|
/** A place where the river stops flowing and starts falling. */
|
|
1627
1994
|
interface RiverFall {
|
|
1628
1995
|
/** Where the water LANDS (local frame) — the plunge point. */
|
|
1629
1996
|
x: number;
|
|
1630
1997
|
z: number;
|
|
1998
|
+
/** Where it LEAVES: the lip. A falling sheet hangs from here, not from the
|
|
1999
|
+
* plunge — hanging it downstream is how a curtain ends up floating in the
|
|
2000
|
+
* air past the cliff it should be pouring over. */
|
|
2001
|
+
lipX: number;
|
|
2002
|
+
lipZ: number;
|
|
2003
|
+
/** Water half-width AT THE LIP — a fall is as wide as the water going over
|
|
2004
|
+
* the edge, not as wide as the pool it lands in. */
|
|
2005
|
+
lipHalfWidth: number;
|
|
2006
|
+
/** Downstream direction at the lip. */
|
|
2007
|
+
lipDirX: number;
|
|
2008
|
+
lipDirZ: number;
|
|
2009
|
+
/** Current at the lip — what throws the sheet forward. */
|
|
2010
|
+
lipSpeed: number;
|
|
1631
2011
|
/** Water surface at the lip and at the base. */
|
|
1632
2012
|
topY: number;
|
|
1633
2013
|
baseY: number;
|
|
@@ -1746,6 +2126,8 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1746
2126
|
private bedAt;
|
|
1747
2127
|
/** Whether the last course was fitted to real ground (see buildRiverRings). */
|
|
1748
2128
|
private fitted;
|
|
2129
|
+
private bounds;
|
|
2130
|
+
private boundsKey;
|
|
1749
2131
|
/** Everything standing in the current, rescanned on a slow timer. */
|
|
1750
2132
|
private rocks;
|
|
1751
2133
|
private rockScanAt;
|
|
@@ -1761,6 +2143,15 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1761
2143
|
/** Rocks are picked per FRAME (nearest to the eye), scanned on a timer. */
|
|
1762
2144
|
_onRender3D(ctx: RenderContext3D): void;
|
|
1763
2145
|
/**
|
|
2146
|
+
* @internal Water surface height over a world point, or null where this
|
|
2147
|
+
* river does not run — the question scattered vegetation asks before it
|
|
2148
|
+
* plants a blade (see {@link WetSurface}).
|
|
2149
|
+
*/
|
|
2150
|
+
_waterYAt(x: number, z: number): number | null;
|
|
2151
|
+
/** XZ bounds of the wetted channel, padded by its widest reach. Rebuilt with
|
|
2152
|
+
* the course (the key is the same one the course is derived under). */
|
|
2153
|
+
private ringBounds;
|
|
2154
|
+
/**
|
|
1764
2155
|
* Where a WORLD point sits in the channel — the query gameplay scripts run
|
|
1765
2156
|
* (steer a raft, drown a torch, decide a ford is too fast to cross). `null`
|
|
1766
2157
|
* until the river has been built.
|
|
@@ -1802,6 +2193,18 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1802
2193
|
* Pure CPU work — no three objects — so gameplay can call it any time.
|
|
1803
2194
|
*/
|
|
1804
2195
|
private ensureCourse;
|
|
2196
|
+
/**
|
|
2197
|
+
* Say so when the course cannot carry the water standing in it.
|
|
2198
|
+
*
|
|
2199
|
+
* A swept ribbon may not be wider than the bend it takes (its inner edge
|
|
2200
|
+
* would fold through itself), so where the ground is wetter than that ceiling
|
|
2201
|
+
* the surface leaves BARE CHANNEL FLOOR — the creek visibly stops in the
|
|
2202
|
+
* middle of its own bed. That is a course/width mismatch the author has to
|
|
2203
|
+
* decide about, and it is invisible from the JSON, so it is said out loud
|
|
2204
|
+
* with the three things that fix it.
|
|
2205
|
+
*/
|
|
2206
|
+
private warnUncoveredWater;
|
|
2207
|
+
private warnedCoverage;
|
|
1805
2208
|
private rebuildIfNeeded;
|
|
1806
2209
|
private buildMaterial;
|
|
1807
2210
|
/**
|
|
@@ -1834,122 +2237,6 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1834
2237
|
private sweepBodies;
|
|
1835
2238
|
}
|
|
1836
2239
|
//#endregion
|
|
1837
|
-
//#region src/3d/terrain/heightmap.d.ts
|
|
1838
|
-
/**
|
|
1839
|
-
* A smooth radial depression carved into the terrain — a lake/pond bowl. x/z
|
|
1840
|
-
* are terrain-LOCAL centered meters (the same space `heightAt`/`slopeAt` use);
|
|
1841
|
-
* `radius`/`depth` are meters. The carve is a smoothstep bowl (0 slope at the
|
|
1842
|
-
* rim), so banks blend into the surrounding ground with no cliff.
|
|
1843
|
-
*/
|
|
1844
|
-
interface BasinSpec {
|
|
1845
|
-
x: number;
|
|
1846
|
-
z: number;
|
|
1847
|
-
radius: number;
|
|
1848
|
-
/** > 0 carves a bowl; < 0 RAISES a smooth dome mound of that height. */
|
|
1849
|
-
depth: number;
|
|
1850
|
-
}
|
|
1851
|
-
/**
|
|
1852
|
-
* A river/road trench carved along a polyline: the line version of a basin.
|
|
1853
|
-
* `width` is the flat bed, `taper` the bank blend beyond it (default: the bed
|
|
1854
|
-
* width again, which is what makes a walkable V instead of a slot).
|
|
1855
|
-
*/
|
|
1856
|
-
interface ChannelSpec {
|
|
1857
|
-
path: readonly (readonly number[])[];
|
|
1858
|
-
width: number;
|
|
1859
|
-
depth: number;
|
|
1860
|
-
taper?: number;
|
|
1861
|
-
}
|
|
1862
|
-
interface HeightmapOptions {
|
|
1863
|
-
/** Terrain extent in meters along x. */
|
|
1864
|
-
width: number;
|
|
1865
|
-
/** Terrain extent in meters along z. */
|
|
1866
|
-
depth: number;
|
|
1867
|
-
/** Grid segments along x (vertices = segsX+1). */
|
|
1868
|
-
segsX: number;
|
|
1869
|
-
/** Grid segments along z (vertices = segsZ+1). */
|
|
1870
|
-
segsZ: number;
|
|
1871
|
-
/** Height scale — realized heights land at roughly 3.5–8× this value. */
|
|
1872
|
-
maxHeight: number;
|
|
1873
|
-
/** Integer seed — same seed, same terrain, every machine. */
|
|
1874
|
-
seed: number;
|
|
1875
|
-
/** Detail persistence (amplitude falloff per detail octave). */
|
|
1876
|
-
roughness?: number;
|
|
1877
|
-
/** Detail octave count. */
|
|
1878
|
-
detail?: number;
|
|
1879
|
-
/** Snap-flatten heights within ±0.6 m of maxHeight·flatThreshold. */
|
|
1880
|
-
flatThreshold?: number;
|
|
1881
|
-
/** Curl the border down a quarter circle and floor it at edgeBottom. */
|
|
1882
|
-
islandEdge?: boolean;
|
|
1883
|
-
/** Quarter-circle radius in meters (clamped to [0, 45]). */
|
|
1884
|
-
edgeWrapRadius?: number;
|
|
1885
|
-
/** Floor y at the outermost ring once the wrap completes. */
|
|
1886
|
-
edgeBottom?: number;
|
|
1887
|
-
/** 0..1 — 1 rounds island corners fully, 0 keeps them square-ish. */
|
|
1888
|
-
edgeCornerSmoothness?: number;
|
|
1889
|
-
/** Smooth radial lake/pond bowls carved into the surface (interior basins). */
|
|
1890
|
-
basins?: BasinSpec[];
|
|
1891
|
-
/** River/road trenches carved along polylines (see {@link ChannelSpec}). */
|
|
1892
|
-
channels?: ChannelSpec[];
|
|
1893
|
-
}
|
|
1894
|
-
interface Heightmap {
|
|
1895
|
-
readonly width: number;
|
|
1896
|
-
readonly depth: number;
|
|
1897
|
-
readonly segsX: number;
|
|
1898
|
-
readonly segsZ: number;
|
|
1899
|
-
/**
|
|
1900
|
-
* Vertex heights INCLUDING edge wrap, `(segsX+1)·(segsZ+1)` long, indexed
|
|
1901
|
-
* `iz·(segsX+1)+ix` with x = -width/2 + ix·cell and z = -depth/2 + iz·cell —
|
|
1902
|
-
* exactly a rotateX(-π/2) PlaneGeometry's vertex order.
|
|
1903
|
-
*/
|
|
1904
|
-
readonly heights: Float32Array;
|
|
1905
|
-
/** Realized height range EXCLUDING edge wrap — normalizes splat heights. */
|
|
1906
|
-
readonly minHeight: number;
|
|
1907
|
-
readonly maxHeight: number;
|
|
1908
|
-
/** Bilinear height lookup in terrain-local centered meters (clamps outside). */
|
|
1909
|
-
heightAt(x: number, z: number): number;
|
|
1910
|
-
/** Analytic pipeline height WITHOUT edge wrap (splat domain), same coords. */
|
|
1911
|
-
baseHeight(x: number, z: number): number;
|
|
1912
|
-
/** Splat slope at a point: atan(|∇ baseHeight|) via central differences. */
|
|
1913
|
-
slopeAt(x: number, z: number): number;
|
|
1914
|
-
}
|
|
1915
|
-
/** One splat layer: a texture plus the trapezoid ranges that place it. */
|
|
1916
|
-
interface TerrainLayer {
|
|
1917
|
-
/** Base color texture URL. */
|
|
1918
|
-
texture: string;
|
|
1919
|
-
/** Optional tangent-space normal map URL. */
|
|
1920
|
-
normalMap?: string;
|
|
1921
|
-
/** World-UV tiling factor (UV = worldXZ · repeat · 0.1). Default 2. */
|
|
1922
|
-
repeat?: number;
|
|
1923
|
-
/** Normalized [min, max] over the realized height span. Default [0, 1]. */
|
|
1924
|
-
heightRange?: [number, number];
|
|
1925
|
-
/** [min, max] slope in radians (atan of gradient). Default [0, π/2]. */
|
|
1926
|
-
slopeRange?: [number, number];
|
|
1927
|
-
/** Linear falloff width OUTSIDE heightRange (fraction of span). Default 0.1. */
|
|
1928
|
-
heightBlendRange?: number;
|
|
1929
|
-
/** Linear falloff width outside slopeRange (radians). Default 0.1. */
|
|
1930
|
-
slopeBlendRange?: number;
|
|
1931
|
-
/** PBR roughness scalar for this layer. Default 1. */
|
|
1932
|
-
roughness?: number;
|
|
1933
|
-
/** PBR metalness scalar for this layer. Default 0. */
|
|
1934
|
-
metalness?: number;
|
|
1935
|
-
/** Anti-tiling UV jitter strength. Default 0.15. */
|
|
1936
|
-
uvNoiseIntensity?: number;
|
|
1937
|
-
}
|
|
1938
|
-
/**
|
|
1939
|
-
* Build the deterministic heightfield. Heavy (one noise eval per vertex) —
|
|
1940
|
-
* call once and keep the result.
|
|
1941
|
-
*/
|
|
1942
|
-
declare function buildHeightmap(opts: HeightmapOptions): Heightmap;
|
|
1943
|
-
/**
|
|
1944
|
-
* Per-vertex splat weights for up to 4 layers. `height01` is the vertex
|
|
1945
|
-
* height normalized over the heightmap's realized (no-wrap) span, `slopeRad`
|
|
1946
|
-
* is `atan(|∇h|)`. Weight = heightTrapezoid · slopeTrapezoid, sharpened by
|
|
1947
|
-
* `pow(w, 1/blendingStrength)`, then normalized to sum 1 — when nothing
|
|
1948
|
-
* matches, layer 0 takes everything. Always returns exactly 4 entries
|
|
1949
|
-
* (missing layers weigh 0).
|
|
1950
|
-
*/
|
|
1951
|
-
declare function splatWeights(height01: number, slopeRad: number, layers: readonly TerrainLayer[], blendingStrength?: number): [number, number, number, number];
|
|
1952
|
-
//#endregion
|
|
1953
2240
|
//#region src/3d/terrain/themes.d.ts
|
|
1954
2241
|
/** Valid `Terrain3D.theme` values — drives the prop options AND validateJson. */
|
|
1955
2242
|
declare const TERRAIN_THEMES: readonly ["island", "alpine", "plains", "desert", "grassland", "forest", "savanna", "snow", "wetland", "volcanic", "custom"];
|
|
@@ -2027,6 +2314,10 @@ declare class Terrain3D extends Node3D {
|
|
|
2027
2314
|
* dug", and re-loading the scene digs it again.
|
|
2028
2315
|
*/
|
|
2029
2316
|
private readonly externalChannels;
|
|
2317
|
+
/** Bumped whenever an external channel is cut or released. */
|
|
2318
|
+
private externalRev;
|
|
2319
|
+
/** What the cached heightmap was built from (see shapeUnchanged). */
|
|
2320
|
+
private shapeStamp;
|
|
2030
2321
|
/**
|
|
2031
2322
|
* WET SAND band: `{ y, band? }` darkens + glosses the splat in a noisy,
|
|
2032
2323
|
* breathing band just above height `y` (a waterline) — the trace of the
|
|
@@ -2062,6 +2353,9 @@ declare class Terrain3D extends Node3D {
|
|
|
2062
2353
|
private allChannels;
|
|
2063
2354
|
/** @internal The pure heightfield (lazily built — physics pulls this). */
|
|
2064
2355
|
_heightmap(): Heightmap;
|
|
2356
|
+
/** Cheap guard for the heightmap cache: scalars by value, arrays by identity. */
|
|
2357
|
+
private shapeUnchanged;
|
|
2358
|
+
private rememberShape;
|
|
2065
2359
|
/** The splat layers in effect — theme preset, or `layers` for 'custom'. */
|
|
2066
2360
|
resolvedLayers(): TerrainLayer[];
|
|
2067
2361
|
override _syncObject3D(): void;
|
|
@@ -2369,6 +2663,16 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
|
2369
2663
|
reflectionInterval: number;
|
|
2370
2664
|
/** Depth-texture shoreline foam — rides the always-on fancy scene pass. */
|
|
2371
2665
|
foam: boolean;
|
|
2666
|
+
/**
|
|
2667
|
+
* How much sky the surface throws back at grazing angles, 0..1 — `null`
|
|
2668
|
+
* keeps the preset's own ceiling.
|
|
2669
|
+
*
|
|
2670
|
+
* Turn it DOWN when the sky is blown out: a bright overcast reflected at 0.78
|
|
2671
|
+
* is a sheet of white, and a pool doing that next to a River3D (which paints
|
|
2672
|
+
* an AUTHORED sky palette and so never blows out) reads as two unrelated
|
|
2673
|
+
* materials meeting at a line. Turn it up for a mirror-calm lake.
|
|
2674
|
+
*/
|
|
2675
|
+
reflectivity: number | null;
|
|
2372
2676
|
/** Bodies crossing the surface emit signals and raise ripples. */
|
|
2373
2677
|
interaction: boolean;
|
|
2374
2678
|
/** Splash WHITEWATER: the shaders paint expanding surface foam where bodies
|
|
@@ -2475,6 +2779,9 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
|
2475
2779
|
private readonly mirrorCamera;
|
|
2476
2780
|
private readonly mirrorMatrix;
|
|
2477
2781
|
private lastMirrorAt;
|
|
2782
|
+
/** Camera pose the current mirror image was rendered from (see redraw). */
|
|
2783
|
+
private readonly lastMirrorEye;
|
|
2784
|
+
private readonly lastMirrorQuat;
|
|
2478
2785
|
protected override _createObject3D(): Object3D;
|
|
2479
2786
|
override update(dt: number): void;
|
|
2480
2787
|
override _syncObject3D(): void;
|
|
@@ -2534,6 +2841,13 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
|
2534
2841
|
* the still-water level, which is what a caller comparing heights wants.
|
|
2535
2842
|
*/
|
|
2536
2843
|
heightAt(x: number, z: number): number;
|
|
2844
|
+
/**
|
|
2845
|
+
* @internal Water surface height over a world point, or null outside this
|
|
2846
|
+
* surface's footprint — what scattered vegetation asks before it plants a
|
|
2847
|
+
* blade (see {@link WetSurface}). The STILL level, not the wave: grass does
|
|
2848
|
+
* not come and go with the swash.
|
|
2849
|
+
*/
|
|
2850
|
+
_waterYAt(x: number, z: number): number | null;
|
|
2537
2851
|
/** Base wave height at world (x, z) — the CPU twin of the active shader.
|
|
2538
2852
|
* Ripples are excluded on purpose: a splash must not re-trigger itself. */
|
|
2539
2853
|
private waveOffsetAt;
|
|
@@ -2649,6 +2963,11 @@ declare class Renderer3D {
|
|
|
2649
2963
|
private readonly loadedAssetScenes;
|
|
2650
2964
|
/** The scene whose GPU programs have been pre-compiled (warm-up, see render). */
|
|
2651
2965
|
private compiledScene;
|
|
2966
|
+
/** The camera the last frame drew with — what warmUp compiles against. */
|
|
2967
|
+
private warmCamera;
|
|
2968
|
+
/** Set while a caller intends to run {@link warmUp} instead: the first frame
|
|
2969
|
+
* then compiles only what it actually draws, and the rest goes async. */
|
|
2970
|
+
private deferCompile;
|
|
2652
2971
|
/** Reused per-frame walk scratch (instance-scoped — never a module global). */
|
|
2653
2972
|
private readonly syncScratch;
|
|
2654
2973
|
/** Editors set this: keep syncing `static: true` subtrees every frame. */
|
|
@@ -2698,6 +3017,20 @@ declare class Renderer3D {
|
|
|
2698
3017
|
* grab, the mirror and the bloom chain follow automatically.
|
|
2699
3018
|
*/
|
|
2700
3019
|
private governResolution;
|
|
3020
|
+
/**
|
|
3021
|
+
* Compile the scene's GPU programs off the critical path.
|
|
3022
|
+
*
|
|
3023
|
+
* The compile itself is unavoidable — a dressed outdoor scene is 40+ programs
|
|
3024
|
+
* and a driver takes hundreds of milliseconds each (measured: SEVEN SECONDS
|
|
3025
|
+
* for this engine's creek template on macOS/Metal). What is avoidable is
|
|
3026
|
+
* doing it in ONE synchronous block, which freezes the tab and leaves a
|
|
3027
|
+
* loading bar with nothing to say.
|
|
3028
|
+
*
|
|
3029
|
+
* Called by `createGame3D` behind the app's loading screen.
|
|
3030
|
+
*/
|
|
3031
|
+
/** @internal Tell the next frame to leave the bulk compile to {@link warmUp}. */
|
|
3032
|
+
deferFirstCompile(): void;
|
|
3033
|
+
warmUp(onProgress?: (fraction: number) => void): Promise<void>;
|
|
2701
3034
|
private render;
|
|
2702
3035
|
/**
|
|
2703
3036
|
* Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
|
|
@@ -2743,10 +3076,23 @@ declare class Renderer3D {
|
|
|
2743
3076
|
*/
|
|
2744
3077
|
pick(sx: number, sy: number): Node | null;
|
|
2745
3078
|
/**
|
|
2746
|
-
* GPU counters for the
|
|
2747
|
-
*
|
|
3079
|
+
* GPU counters for the last rendered frame — EVERY pass of it, summed (see
|
|
3080
|
+
* the `info.reset()` in `render`), plus GPU memory.
|
|
2748
3081
|
*/
|
|
2749
3082
|
stats(): RendererStats;
|
|
3083
|
+
/**
|
|
3084
|
+
* Where the last rendered frame was seen FROM.
|
|
3085
|
+
*
|
|
3086
|
+
* Paired with {@link cameraBasis} this is the view on screen — a third-person
|
|
3087
|
+
* rig, a cutscene camera and the editor's free-fly all answer here, which is
|
|
3088
|
+
* what a mode switch needs to carry across so the cut does not read as a
|
|
3089
|
+
* page load.
|
|
3090
|
+
*/
|
|
3091
|
+
lastCameraPosition(): {
|
|
3092
|
+
x: number;
|
|
3093
|
+
y: number;
|
|
3094
|
+
z: number;
|
|
3095
|
+
} | null;
|
|
2750
3096
|
/** The last rendered camera's world-space basis (for screen-plane dragging). */
|
|
2751
3097
|
cameraBasis(): {
|
|
2752
3098
|
right: Vector3;
|
|
@@ -2899,4 +3245,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
|
|
|
2899
3245
|
name?: string;
|
|
2900
3246
|
}): InstancedMesh3D;
|
|
2901
3247
|
//#endregion
|
|
2902
|
-
export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, River3D, type RiverHit, type RiverRing, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
3248
|
+
export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, River3D, type RiverCarveOptions, type RiverCoverageGap, type RiverHit, type RiverRing, type RiverRingOptions, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, buildHeightmap, buildRiverRings, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, findRiverCoverageGaps, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, projectToRiver, registerNodes3D, resolveFlowerDensity, rigPose, riverCarveChannels, riverStepFor, setEnvironment3D, smoothCourse, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|