incanto 0.1.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/LICENSE +30 -0
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.md +88 -0
- package/assets/audio/attacked.mp3 +0 -0
- package/assets/audio/explosion.mp3 +0 -0
- package/assets/audio/gold_loot.mp3 +0 -0
- package/assets/audio/heal.mp3 +0 -0
- package/assets/audio/hit_metal_bang.mp3 +0 -0
- package/assets/audio/ice_spear.mp3 +0 -0
- package/assets/audio/monster_died.mp3 +0 -0
- package/assets/audio/slash.mp3 +0 -0
- package/assets/audio/smite.mp3 +0 -0
- package/assets/audio/spells_cast.mp3 +0 -0
- package/assets/audio/ui_click.wav +0 -0
- package/assets/audio/walk.mp3 +0 -0
- package/assets/catalog.json +390 -0
- package/assets/characters/2dbasic.json +41 -0
- package/assets/characters/2dbasic.png +0 -0
- package/assets/characters/ghost.json +46 -0
- package/assets/characters/ghost.png +0 -0
- package/assets/characters/goblin.json +40 -0
- package/assets/characters/goblin.png +0 -0
- package/assets/characters/medieval-knight.json +41 -0
- package/assets/characters/medieval-knight.png +0 -0
- package/assets/effects/swoosh.png +0 -0
- package/assets/items/box.png +0 -0
- package/assets/items/buff_potion.png +0 -0
- package/assets/items/coin.png +0 -0
- package/assets/items/gem.png +0 -0
- package/assets/items/gold.png +0 -0
- package/assets/items/hp_potion.png +0 -0
- package/assets/items/locked_item_box.png +0 -0
- package/assets/items/map.png +0 -0
- package/assets/items/resurrection_potion.png +0 -0
- package/assets/items/super_box.png +0 -0
- package/assets/items/trap.png +0 -0
- package/assets/tiles/floor00.jpg +0 -0
- package/assets/tiles/minecraft-tiles.png +0 -0
- package/assets/tiles/wall00.jpg +0 -0
- package/assets/vegetation/ash_color.png +0 -0
- package/assets/vegetation/aspen_color.png +0 -0
- package/assets/vegetation/bark/birch_color_1k.jpg +0 -0
- package/assets/vegetation/bark/birch_normal_1k.jpg +0 -0
- package/assets/vegetation/bark/birch_roughness_1k.jpg +0 -0
- package/assets/vegetation/bark/oak_color_1k.jpg +0 -0
- package/assets/vegetation/bark/oak_normal_1k.jpg +0 -0
- package/assets/vegetation/bark/oak_roughness_1k.jpg +0 -0
- package/assets/vegetation/bark/pine_color_1k.jpg +0 -0
- package/assets/vegetation/bark/pine_normal_1k.jpg +0 -0
- package/assets/vegetation/bark/pine_roughness_1k.jpg +0 -0
- package/assets/vegetation/ground/dirt_color.jpg +0 -0
- package/assets/vegetation/ground/dirt_normal.jpg +0 -0
- package/assets/vegetation/ground/grass.jpg +0 -0
- package/assets/vegetation/oak_color.png +0 -0
- package/assets/vegetation/pine_color.png +0 -0
- package/bin/incanto-assets.mjs +107 -0
- package/bin/incanto-check.mjs +107 -0
- package/bin/incanto-editor.mjs +343 -0
- package/bin/incanto-env.mjs +144 -0
- package/bin/incanto-model.mjs +296 -0
- package/bin/incanto-play.mjs +219 -0
- package/bin/incanto-skills.mjs +71 -0
- package/dist/2d.d.ts +642 -0
- package/dist/2d.js +44 -0
- package/dist/3d.d.ts +1860 -0
- package/dist/3d.js +5 -0
- package/dist/agent8-DzU2fFyH.js +129 -0
- package/dist/audio-player-DqUR3XFs.d.ts +110 -0
- package/dist/behavior-BAQq7HGM.d.ts +851 -0
- package/dist/create-game-BdjpTHrW.js +1725 -0
- package/dist/create-game-CZHROKcT.js +527 -0
- package/dist/debug-draw-CZmOYjL2.js +13 -0
- package/dist/debug.d.ts +66 -0
- package/dist/debug.js +658 -0
- package/dist/duplicate-DP2WPYom.js +22 -0
- package/dist/env.d.ts +430 -0
- package/dist/env.js +3152 -0
- package/dist/errors-BMFaY68Q.d.ts +33 -0
- package/dist/errors-BpWbnbb_.js +13 -0
- package/dist/gameplay-Ccruc3Wd.js +1501 -0
- package/dist/gameplay.d.ts +543 -0
- package/dist/gameplay.js +2 -0
- package/dist/heightmap-CroQPEER.js +185 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +62 -0
- package/dist/json-BLk7H2Qa.js +30 -0
- package/dist/loader-CGs_G-r0.js +919 -0
- package/dist/loader-Mo0KghCv.d.ts +41 -0
- package/dist/net.d.ts +427 -0
- package/dist/net.js +772 -0
- package/dist/noise-CGUMx44x.js +82 -0
- package/dist/particle-sim-CbN4YUuH.d.ts +63 -0
- package/dist/particle-sim-DYuSUxvK.js +1319 -0
- package/dist/physics-2d-KuMWPTf6.js +288 -0
- package/dist/physics-3d-Dl67vOLT.js +434 -0
- package/dist/react.d.ts +65 -0
- package/dist/react.js +209 -0
- package/dist/register-BuUV1_KB.js +561 -0
- package/dist/register-CNlYAS1_.js +10634 -0
- package/dist/register-DPEV9_9t.js +851 -0
- package/dist/register-Dasmnurl.js +374 -0
- package/dist/registry-BVJ2HbCn.js +132 -0
- package/dist/rng-DP-SR7eg.js +38 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/schema-CcoWb32N.d.ts +104 -0
- package/dist/test.d.ts +158 -0
- package/dist/test.js +275 -0
- package/dist/touch-031PxtCR.js +208 -0
- package/dist/vite.d.ts +26 -0
- package/dist/vite.js +57 -0
- package/editor/assets/GameServer-C56iOUgF.js +1 -0
- package/editor/assets/agent8-Bp7QFI7v.js +1 -0
- package/editor/assets/index-DF3tMeKJ.css +1 -0
- package/editor/assets/index-Dl2pjA8e.js +7365 -0
- package/editor/assets/rapier-CEuLKeCu.js +1 -0
- package/editor/assets/rapier-DE6a0vmv.js +1 -0
- package/editor/index.html +169 -0
- package/package.json +97 -0
- package/schemas/scene.schema.json +4254 -0
- package/skills/README.md +9 -0
- package/skills/incanto-3d-character.md +229 -0
- package/skills/incanto-3d-models.md +151 -0
- package/skills/incanto-assets.md +118 -0
- package/skills/incanto-audio.md +309 -0
- package/skills/incanto-behaviors-and-scripts.md +169 -0
- package/skills/incanto-building-2d-games.md +242 -0
- package/skills/incanto-building-3d-games.md +245 -0
- package/skills/incanto-editor.md +163 -0
- package/skills/incanto-environment.md +743 -0
- package/skills/incanto-gameplay-behaviors.md +707 -0
- package/skills/incanto-multiplayer.md +264 -0
- package/skills/incanto-node-reference.md +797 -0
- package/skills/incanto-physics-and-input.md +164 -0
- package/skills/incanto-scene-json-authoring.md +325 -0
- package/skills/incanto-verifying-your-game.md +191 -0
- package/skills/incanto-web-integration.md +96 -0
- package/templates/agent8-server.js +84 -0
- package/templates/agent8-server.ts +138 -0
package/dist/3d.d.ts
ADDED
|
@@ -0,0 +1,1860 @@
|
|
|
1
|
+
import { s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
+
import { C as RendererStats, S as GameStats, T as Node, b as Scheduler, l as PropSchema, n as BehaviorCtor, v as Engine, w as Scene$1 } from "./behavior-BAQq7HGM.js";
|
|
3
|
+
import { t as LoadSceneOptions } from "./loader-Mo0KghCv.js";
|
|
4
|
+
import { t as ParticleSim } from "./particle-sim-CbN4YUuH.js";
|
|
5
|
+
import { r as SpatialPose } from "./audio-player-DqUR3XFs.js";
|
|
6
|
+
import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
|
|
7
|
+
import { VRM } from "@pixiv/three-vrm";
|
|
8
|
+
import { Sky } from "three/examples/jsm/objects/Sky.js";
|
|
9
|
+
import * as RapierNs from "@dimforge/rapier3d-compat";
|
|
10
|
+
|
|
11
|
+
//#region src/3d/assets.d.ts
|
|
12
|
+
interface ModelEntry {
|
|
13
|
+
status: "loading" | "ready" | "error";
|
|
14
|
+
/** Source scene — GLB instances are CLONED from this; VRM mounts it directly. */
|
|
15
|
+
scene: Object3D | null;
|
|
16
|
+
animations: AnimationClip[];
|
|
17
|
+
isVrm: boolean;
|
|
18
|
+
/** The live three-vrm runtime (normalized rig, springbones) — VRM only. */
|
|
19
|
+
vrm: VRM | null;
|
|
20
|
+
/** VRM scenes mount directly: the node currently displaying this avatar. */
|
|
21
|
+
claimedBy: object | null;
|
|
22
|
+
/** Retargeted clips cached per animation-asset reference. */
|
|
23
|
+
retargeted: Map<string, AnimationClip>;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
interface AnimationEntry {
|
|
27
|
+
status: "loading" | "ready" | "error";
|
|
28
|
+
clips: AnimationClip[];
|
|
29
|
+
/** The animation file's own scene — its rest pose drives VRM retargeting. */
|
|
30
|
+
scene: Object3D | null;
|
|
31
|
+
/** Optional clip selector from the asset declaration. */
|
|
32
|
+
clip?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* GLB/glTF/VRM store for the 3D adapter. Scene `assets` entries:
|
|
37
|
+
*
|
|
38
|
+
* {type:"model", url} → ModelInstance3D `model: "$key"`
|
|
39
|
+
* {type:"animation", url, clip?} → ModelInstance3D `animation: "$key"`
|
|
40
|
+
* (clips live in memory, never drawn)
|
|
41
|
+
*
|
|
42
|
+
* Raw URLs also work anywhere a `$key` does. VRM files load through
|
|
43
|
+
* @pixiv/three-vrm (VRM 0.x facing fixed via rotateVRM0).
|
|
44
|
+
*/
|
|
45
|
+
declare class AssetStore3D {
|
|
46
|
+
private readonly models;
|
|
47
|
+
private readonly animations;
|
|
48
|
+
private readonly modelKeys;
|
|
49
|
+
private readonly animationKeys;
|
|
50
|
+
private readonly loader;
|
|
51
|
+
constructor();
|
|
52
|
+
/** Map a scene's `assets` header. */
|
|
53
|
+
load(assets: JsonObject): void;
|
|
54
|
+
/** Animation `$key`s declared by the scene (editor dropdowns). */
|
|
55
|
+
animationRefs(): string[];
|
|
56
|
+
modelRefs(): string[];
|
|
57
|
+
/** Resolve `$key` or URL; kicks off the load on first sight. */
|
|
58
|
+
getModel(ref: string): ModelEntry;
|
|
59
|
+
/** Resolve an animation `$key` or URL; clips load into memory only. */
|
|
60
|
+
getAnimation(ref: string): AnimationEntry;
|
|
61
|
+
dispose(): void;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/3d/camera-rig.d.ts
|
|
65
|
+
/**
|
|
66
|
+
* Camera rig math for the four classic character views — pure functions so
|
|
67
|
+
* every magic number stays testable. All values mirror vibe-starter-3d's
|
|
68
|
+
* controllers exactly (the agent8 template stack):
|
|
69
|
+
*
|
|
70
|
+
* free third-person orbit: pivot = player + eye, cam at -distance
|
|
71
|
+
* along the yaw/pitch direction, lookAt pivot
|
|
72
|
+
* firstPerson cam AT the eye pivot, looking along yaw/pitch
|
|
73
|
+
* quarter fixed isometric pitch atan(1/√2) ≈ 35.264°, offset
|
|
74
|
+
* (0, d·sinX, d·cosX), rotation hard-set (no lookAt)
|
|
75
|
+
* side cam at (player.x, eye.y, player.z + distance), facing -z
|
|
76
|
+
*/
|
|
77
|
+
type RigView = "free" | "firstPerson" | "quarter" | "side";
|
|
78
|
+
interface RigPose {
|
|
79
|
+
position: [number, number, number];
|
|
80
|
+
/** Euler XYZ in RADIANS (Camera3D's rotation prop is degrees — convert). */
|
|
81
|
+
rotation: [number, number, number];
|
|
82
|
+
}
|
|
83
|
+
declare const QUARTER_PITCH: number;
|
|
84
|
+
declare function rigPose(view: RigView, player: [number, number, number], eyeHeight: number, yaw: number, pitch: number, distance: number): RigPose;
|
|
85
|
+
/** Camera-relative movement: input (x right, y forward) rotated by camera yaw. */
|
|
86
|
+
declare function cameraRelative(x: number, y: number, yaw: number): [number, number, number];
|
|
87
|
+
/** Keyboard intensity model: any direction = 0.6, sprint adds 0.4 (cap 1). */
|
|
88
|
+
declare function keyboardIntensity(moving: boolean, sprinting: boolean): number;
|
|
89
|
+
/** intensity → movement state (the animation driver). */
|
|
90
|
+
declare function movementState(grounded: boolean, intensity: number): "idle" | "walk" | "run" | "fastRun" | "airborne";
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/3d/nodes/node-3d.d.ts
|
|
93
|
+
/**
|
|
94
|
+
* Per-frame hand-off from `Renderer3D` to nodes that need the LIVE WebGL
|
|
95
|
+
* context (cube-camera reflections, depth pre-passes…). `syncTree` collects
|
|
96
|
+
* `_onRender3D` hooks while it walks the tree; the renderer invokes them just
|
|
97
|
+
* before the main render. Headless runs (no Renderer3D) never invoke them —
|
|
98
|
+
* GPU-only features must stay dormant, not crash.
|
|
99
|
+
*/
|
|
100
|
+
interface RenderContext3D {
|
|
101
|
+
gl: WebGLRenderer;
|
|
102
|
+
scene: Scene;
|
|
103
|
+
camera: PerspectiveCamera;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Implement on a Node3D subclass to receive {@link RenderContext3D} once per
|
|
107
|
+
* rendered frame — `syncTree` detects `_onRender3D` structurally, so plain
|
|
108
|
+
* headless trees pay nothing and never invoke it.
|
|
109
|
+
*/
|
|
110
|
+
interface RenderHook3D {
|
|
111
|
+
/** @internal Called by Renderer3D right before the main render. */
|
|
112
|
+
_onRender3D(ctx: RenderContext3D): void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Implement on a Node3D subclass to receive the environment sky's UNIT sun
|
|
116
|
+
* direction once per sync (after the node's own `_syncObject3D`). Contract:
|
|
117
|
+
* apply it ONLY while the node's own sun prop is at its schema default — an
|
|
118
|
+
* explicitly authored prop always wins over the sky.
|
|
119
|
+
*/
|
|
120
|
+
interface SunConsumer3D {
|
|
121
|
+
/** @internal Called by syncTree when `environment.sky` declares a sun. */
|
|
122
|
+
_applySunDirection(dir: readonly [number, number, number]): void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Base of every 3D node: a JSON-prop transform backed by a `THREE.Object3D`.
|
|
126
|
+
*
|
|
127
|
+
* Props stay plain JSON (registry/serializer compatible); the renderer layer
|
|
128
|
+
* pushes them onto the backing object once per frame (`_syncObject3D`).
|
|
129
|
+
* Rotation is in DEGREES in JSON — friendlier for agents and humans.
|
|
130
|
+
*/
|
|
131
|
+
declare class Node3D extends Node {
|
|
132
|
+
static override readonly typeName: string;
|
|
133
|
+
static readonly props: PropSchema;
|
|
134
|
+
position: number[];
|
|
135
|
+
/** Euler XYZ in degrees. */
|
|
136
|
+
rotation: number[];
|
|
137
|
+
scale: number[];
|
|
138
|
+
visible: boolean;
|
|
139
|
+
/** Draw-order priority (higher = drawn later / on top). Only affects TRANSPARENT
|
|
140
|
+
* materials — three sorts the transparent pass by renderOrder, then depth.
|
|
141
|
+
* Pairs with named scene constants (e.g. `{"@const": "UI"}`) for sorting tiers. */
|
|
142
|
+
renderOrder: number;
|
|
143
|
+
private _object3D;
|
|
144
|
+
/**
|
|
145
|
+
* @internal Fixed-step render interpolation. A physics body fills these with
|
|
146
|
+
* its previous and current fixed-step LOCAL positions; `_syncObject3D(alpha)`
|
|
147
|
+
* lerps the rendered transform between them so motion is smooth between fixed
|
|
148
|
+
* steps. `null` means "not physics-driven" — render `position` directly.
|
|
149
|
+
*/
|
|
150
|
+
_interpPrev: number[] | null;
|
|
151
|
+
_interpCurr: number[] | null;
|
|
152
|
+
/** @internal The backing three object (lazily created). */
|
|
153
|
+
_ensureObject3D(): Object3D;
|
|
154
|
+
/** @internal Override point for subclasses (mesh, camera, light…). */
|
|
155
|
+
protected _createObject3D(): Object3D;
|
|
156
|
+
/** @internal Push JSON props onto the backing object. Called every frame.
|
|
157
|
+
* `alpha` (0..1) interpolates a physics body between its last two fixed-step
|
|
158
|
+
* positions; non-physics nodes ignore it and render `position` directly. */
|
|
159
|
+
_syncObject3D(alpha?: number): void;
|
|
160
|
+
override free(): void;
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/3d/nodes/bodies-3d.d.ts
|
|
164
|
+
/**
|
|
165
|
+
* Shared base for physics-backed 3D nodes. Colliders are NODE PROPS:
|
|
166
|
+
* `{shape:'box', size:[x,y,z]}` · `{shape:'sphere', radius}` ·
|
|
167
|
+
* `{shape:'capsule', radius, height}` (meters, y-up).
|
|
168
|
+
*/
|
|
169
|
+
declare class PhysicsBody3D extends Node3D {
|
|
170
|
+
static override readonly props: PropSchema;
|
|
171
|
+
/** The unified collision model: every collider participant can emit these. */
|
|
172
|
+
static override readonly signals: readonly string[];
|
|
173
|
+
collider: JsonObject;
|
|
174
|
+
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
175
|
+
static validateJson(node: Node): void;
|
|
176
|
+
/** @internal Set by Physics3D when the body is created. */
|
|
177
|
+
_physics: Physics3D | null;
|
|
178
|
+
}
|
|
179
|
+
/** Immovable collider (ground, walls, platforms). */
|
|
180
|
+
declare class StaticBody3D extends PhysicsBody3D {
|
|
181
|
+
static override readonly typeName: string;
|
|
182
|
+
}
|
|
183
|
+
/** Sensor volume emitting `triggerEnter(other)` / `triggerExit(other)`. */
|
|
184
|
+
declare class Area3D extends PhysicsBody3D {
|
|
185
|
+
static override readonly typeName: string;
|
|
186
|
+
}
|
|
187
|
+
/** Dynamic simulated body. */
|
|
188
|
+
declare class RigidBody3D extends PhysicsBody3D {
|
|
189
|
+
static override readonly typeName: string;
|
|
190
|
+
static override readonly props: PropSchema;
|
|
191
|
+
mass: number;
|
|
192
|
+
gravityScale: number;
|
|
193
|
+
fixedRotation: boolean;
|
|
194
|
+
friction: number;
|
|
195
|
+
restitution: number;
|
|
196
|
+
/** m/s, y-up. Read back every step; write to launch. */
|
|
197
|
+
linearVelocity: number[];
|
|
198
|
+
/** @internal set by Physics3D */
|
|
199
|
+
_physics3d: Physics3D | null;
|
|
200
|
+
/** World-space impulse (kg·m/s) — the floating-capsule controller's verb. */
|
|
201
|
+
applyImpulse(impulse: [number, number, number]): void;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Kinematic capsule on Rapier's character controller. Gravity is NOT applied
|
|
205
|
+
* automatically — integrate `velocity` yourself, then `moveAndSlide()` from
|
|
206
|
+
* `fixedUpdate`.
|
|
207
|
+
*/
|
|
208
|
+
declare class CharacterBody3D extends PhysicsBody3D {
|
|
209
|
+
static override readonly typeName: string;
|
|
210
|
+
static override readonly props: PropSchema;
|
|
211
|
+
/** m/s, y-up (up = +y). */
|
|
212
|
+
velocity: number[];
|
|
213
|
+
snapToGround: boolean;
|
|
214
|
+
/** Max climbable slope angle. */
|
|
215
|
+
slopeLimitDeg: number;
|
|
216
|
+
/** @internal Updated by Physics3D.moveAndSlide. */
|
|
217
|
+
_grounded: boolean;
|
|
218
|
+
moveAndSlide(): void;
|
|
219
|
+
isOnFloor(): boolean;
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/3d/physics/physics-3d.d.ts
|
|
223
|
+
type Rapier = typeof RapierNs;
|
|
224
|
+
interface Physics3DOptions {
|
|
225
|
+
/** m/s², y-up. Default: scene `physics.gravity`, else [0, -9.81, 0]. */
|
|
226
|
+
gravity?: [number, number, number];
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Enable 3D physics for an engine. Dynamically imports Rapier (compat build)
|
|
230
|
+
* so games without physics never pay its bundle cost. 1 unit = 1 meter, y-up —
|
|
231
|
+
* three's native space, no conversion.
|
|
232
|
+
*/
|
|
233
|
+
declare function enablePhysics3D(engine: Engine, opts?: Physics3DOptions): Promise<Physics3D>;
|
|
234
|
+
/** Per-engine 3D physics world. Same contract as Physics2D. */
|
|
235
|
+
declare class Physics3D {
|
|
236
|
+
private readonly R;
|
|
237
|
+
private readonly engine;
|
|
238
|
+
/** Render collider outlines in the GAME view (renderers pick this up). */
|
|
239
|
+
debugDraw: boolean;
|
|
240
|
+
readonly dimension = "3d";
|
|
241
|
+
private readonly unregisterDebug;
|
|
242
|
+
private readonly warnedNoCollider;
|
|
243
|
+
private readonly entries;
|
|
244
|
+
private readonly byColliderHandle;
|
|
245
|
+
private readonly world;
|
|
246
|
+
private readonly events;
|
|
247
|
+
private readonly kcc;
|
|
248
|
+
private readonly disconnect;
|
|
249
|
+
private lastDt;
|
|
250
|
+
private readonly optsGravity;
|
|
251
|
+
private lastScene;
|
|
252
|
+
constructor(R: Rapier, engine: Engine, opts?: Physics3DOptions);
|
|
253
|
+
/** @internal Driven by engine.fixedUpdated. */
|
|
254
|
+
step(dt: number): void;
|
|
255
|
+
/** @internal Called by CharacterBody3D.moveAndSlide (during tree fixedUpdate). */
|
|
256
|
+
moveAndSlide(node: CharacterBody3D): void;
|
|
257
|
+
/** Rapier debug segments (meters). Null while debugDraw is off. */
|
|
258
|
+
debugLines(): Float32Array | null;
|
|
259
|
+
dispose(): void;
|
|
260
|
+
private syncBodies;
|
|
261
|
+
private ensureEntry;
|
|
262
|
+
/** Apply a world-space impulse to a dynamic body (character controllers). */
|
|
263
|
+
applyImpulse(node: PhysicsBody3D, impulse: [number, number, number]): void;
|
|
264
|
+
/** Mass the solver actually uses (collider-derived unless overridden). */
|
|
265
|
+
massOf(node: PhysicsBody3D): number;
|
|
266
|
+
/** Current solver velocity (fresher than the node prop mid-step). */
|
|
267
|
+
velocityOf(node: PhysicsBody3D): [number, number, number];
|
|
268
|
+
/**
|
|
269
|
+
* World-space raycast (meters). `exclude` skips that body's collider —
|
|
270
|
+
* ground probes from inside a capsule need it. `opts.staticOnly` hits ONLY the
|
|
271
|
+
* fixed/static world (excludes both dynamic AND kinematic bodies): the camera
|
|
272
|
+
* spring arm uses it so neither a dynamic projectile nor a KINEMATIC enemy
|
|
273
|
+
* (CharacterBody3D) passing behind the player yanks the camera in.
|
|
274
|
+
*/
|
|
275
|
+
castRay(origin: [number, number, number], dir: [number, number, number], maxLen: number, exclude?: PhysicsBody3D, opts?: {
|
|
276
|
+
staticOnly?: boolean;
|
|
277
|
+
}): {
|
|
278
|
+
distance: number;
|
|
279
|
+
normal: [number, number, number];
|
|
280
|
+
node: PhysicsBody3D | null;
|
|
281
|
+
} | null;
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/3d/create-game.d.ts
|
|
285
|
+
interface KeyboardTarget {
|
|
286
|
+
addEventListener: (t: string, cb: (e: KeyboardEvent) => void) => void;
|
|
287
|
+
removeEventListener: (t: string, cb: (e: KeyboardEvent) => void) => void;
|
|
288
|
+
}
|
|
289
|
+
interface CreateGame3DOptions {
|
|
290
|
+
canvas: HTMLCanvasElement;
|
|
291
|
+
/** Scene JSON (cloned internally — the same object can boot many games). */
|
|
292
|
+
scene: unknown;
|
|
293
|
+
behaviors?: Record<string, BehaviorCtor>;
|
|
294
|
+
/**
|
|
295
|
+
* Auto-register the built-in `incanto/gameplay` behaviors (Health, Pickup,
|
|
296
|
+
* ScoreKeeper, …) — default true. They register BEFORE `behaviors`, so a
|
|
297
|
+
* same-named user behavior always wins. Set false to ship none of them.
|
|
298
|
+
*/
|
|
299
|
+
gameplay?: boolean;
|
|
300
|
+
/** Keyboard source (default: window when available). `false` disables. */
|
|
301
|
+
keyboard?: KeyboardTarget | false;
|
|
302
|
+
/** Pointer-look on the canvas (FPS pattern). Default false. */
|
|
303
|
+
pointer?: boolean | {
|
|
304
|
+
lockOnClick?: boolean;
|
|
305
|
+
};
|
|
306
|
+
/** 'auto' (default): enable Rapier iff the tree has physics bodies. */
|
|
307
|
+
physics?: "auto" | boolean;
|
|
308
|
+
/**
|
|
309
|
+
* On-screen touch controls for the scene's `"touch"` input declarations:
|
|
310
|
+
* 'auto' (default) shows them on coarse-pointer devices, true forces them,
|
|
311
|
+
* false disables. They overlay `touchContainer` (default: the canvas's
|
|
312
|
+
* parent element — give it position: relative).
|
|
313
|
+
*/
|
|
314
|
+
touch?: "auto" | boolean;
|
|
315
|
+
touchContainer?: HTMLElement;
|
|
316
|
+
/** @internal Test seam — replaces `document` for the touch overlay. */
|
|
317
|
+
_touchDoc?: {
|
|
318
|
+
createElement(tag: string): HTMLElement;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Mount the runtime debug overlay (`incanto/debug` — explorer/inspector/
|
|
322
|
+
* logs). Default: false (off), and there is NO URL/query toggle — a deployed
|
|
323
|
+
* build must never be switchable on by end users. Opt in via `debug`, gated
|
|
324
|
+
* to development, e.g. `debug: import.meta.env.VITE_INCANTO_DEBUG === '1'`.
|
|
325
|
+
*/
|
|
326
|
+
debug?: boolean;
|
|
327
|
+
/** @internal Test seam — replaces `document` for the debug overlay. */
|
|
328
|
+
_debugDoc?: {
|
|
329
|
+
createElement(tag: string): HTMLElement;
|
|
330
|
+
};
|
|
331
|
+
seed?: number;
|
|
332
|
+
fixedHz?: number;
|
|
333
|
+
pixelRatio?: number;
|
|
334
|
+
resolveScene?: LoadSceneOptions["resolveScene"];
|
|
335
|
+
/** @internal Test seam — replaces the Renderer3D construction. */
|
|
336
|
+
_rendererFactory?: (engine: Engine, canvas: HTMLCanvasElement) => GameRenderer;
|
|
337
|
+
/** @internal Test seam — replaces the rAF scheduler. */
|
|
338
|
+
_scheduler?: Scheduler;
|
|
339
|
+
}
|
|
340
|
+
/** What createGame needs from a renderer (stats is optional for test stubs). */
|
|
341
|
+
interface GameRenderer {
|
|
342
|
+
dispose(): void;
|
|
343
|
+
stats?(): RendererStats;
|
|
344
|
+
}
|
|
345
|
+
interface Game3D {
|
|
346
|
+
engine: Engine;
|
|
347
|
+
scene: Scene$1;
|
|
348
|
+
renderer: GameRenderer;
|
|
349
|
+
physics: Physics3D | null;
|
|
350
|
+
/** Engine + renderer perf counters in one read (fps/nodes/triangles/…). */
|
|
351
|
+
stats(): GameStats;
|
|
352
|
+
dispose(): void;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* One-call 3D boot/teardown — the 3D twin of createGame2D: register → load
|
|
356
|
+
* (engine attached for onReady) → physics (auto) → input → renderer → start.
|
|
357
|
+
*/
|
|
358
|
+
declare function createGame3D(opts: CreateGame3DOptions): Promise<Game3D>;
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/3d/environment.d.ts
|
|
361
|
+
/**
|
|
362
|
+
* The 3D `environment` header, parsed and validated — the renderer's
|
|
363
|
+
* "rendering stage". Pure data in, plain data out (NO three imports), so the
|
|
364
|
+
* whole contract tests headlessly:
|
|
365
|
+
*
|
|
366
|
+
* - `sky`: a physical atmosphere (three Sky shader) that also feeds
|
|
367
|
+
* image-based lighting and the unified sun direction
|
|
368
|
+
* - `fog`: linear distance fog (color defaults to the sky's horizon haze)
|
|
369
|
+
* - `shadows`: shadow-map master switch + quality knobs
|
|
370
|
+
* - `exposure`: ACES tone-mapping exposure dial
|
|
371
|
+
*
|
|
372
|
+
* Everything is renderer-interpreted: scenes without these keys parse to the
|
|
373
|
+
* exact pre-atmosphere defaults (no sky, no fog, exposure 1).
|
|
374
|
+
*/
|
|
375
|
+
interface SkyEnvironment {
|
|
376
|
+
/** Sun direction (not necessarily unit length — see {@link sunDirectionFromSky}). */
|
|
377
|
+
sunPosition: [number, number, number];
|
|
378
|
+
/** Atmospheric haze 0.1–20 (three Sky semantics; 2 ≈ clear day). */
|
|
379
|
+
turbidity: number;
|
|
380
|
+
/** Rayleigh scattering ≥ 0 (1 ≈ earth-like blue). */
|
|
381
|
+
rayleigh: number;
|
|
382
|
+
}
|
|
383
|
+
interface FogEnvironment {
|
|
384
|
+
/** Hex fog color. */
|
|
385
|
+
color: string;
|
|
386
|
+
/** Distance where fog starts, meters. */
|
|
387
|
+
near: number;
|
|
388
|
+
/** Distance of full fog, meters. */
|
|
389
|
+
far: number;
|
|
390
|
+
}
|
|
391
|
+
interface ShadowsEnvironment {
|
|
392
|
+
/** Shadow map resolution per side. */
|
|
393
|
+
mapSize: 1024 | 2048;
|
|
394
|
+
/** Shadow edge softening radius (PCF blur, default 1). */
|
|
395
|
+
radius: number;
|
|
396
|
+
}
|
|
397
|
+
interface CloudsEnvironment {
|
|
398
|
+
/** How much of the sky is cloudy, 0..1. */
|
|
399
|
+
coverage: number;
|
|
400
|
+
/** Optical thickness multiplier (higher = more opaque/dramatic). */
|
|
401
|
+
density: number;
|
|
402
|
+
/** Cloud-base altitude, world Y. */
|
|
403
|
+
base: number;
|
|
404
|
+
/** Cloud-top altitude, world Y (> base). */
|
|
405
|
+
top: number;
|
|
406
|
+
/** Sunlit cloud color (hex). */
|
|
407
|
+
color: string;
|
|
408
|
+
/** Shaded underside color (hex). */
|
|
409
|
+
shadeColor: string;
|
|
410
|
+
/** Wind drift speed. */
|
|
411
|
+
speed: number;
|
|
412
|
+
/** Feature size in world units (bigger = larger, softer cloud masses). */
|
|
413
|
+
scale: number;
|
|
414
|
+
}
|
|
415
|
+
interface Environment3DConfig {
|
|
416
|
+
/** Tone-mapping exposure (ACES), default 1. */
|
|
417
|
+
exposure: number;
|
|
418
|
+
/** Physical sky, or null when the scene keeps a flat `background`. */
|
|
419
|
+
sky: SkyEnvironment | null;
|
|
420
|
+
/** Linear fog, or null for none. */
|
|
421
|
+
fog: FogEnvironment | null;
|
|
422
|
+
/** Volumetric cloud layer (renderer raymarch composite), or null for none. */
|
|
423
|
+
clouds: CloudsEnvironment | null;
|
|
424
|
+
/**
|
|
425
|
+
* `null` = not declared (shadow maps stay available, free until a light
|
|
426
|
+
* casts), `false` = force-disabled, object = declared ON with quality knobs
|
|
427
|
+
* (the renderer then makes the scene's main DirectionalLight cast).
|
|
428
|
+
*/
|
|
429
|
+
shadows: ShadowsEnvironment | false | null;
|
|
430
|
+
}
|
|
431
|
+
/** Parse + hard-validate the 3D slice of a scene's `environment` header. */
|
|
432
|
+
declare function parseEnvironment3D(env: JsonObject | undefined): Environment3DConfig;
|
|
433
|
+
/**
|
|
434
|
+
* elevation/azimuth (degrees) → unit direction. Matches three's Sky example
|
|
435
|
+
* (`Vector3.setFromSphericalCoords`): azimuth 0 = +Z, 90 = +X; elevation 90 =
|
|
436
|
+
* straight up.
|
|
437
|
+
*/
|
|
438
|
+
declare function sunDirectionFromElevationAzimuth(elevationDeg: number, azimuthDeg: number): [number, number, number];
|
|
439
|
+
/** The sky's sun as a UNIT vector — what Water3D/Foliage3D uniforms consume. */
|
|
440
|
+
declare function sunDirectionFromSky(sky: SkyEnvironment): [number, number, number];
|
|
441
|
+
/**
|
|
442
|
+
* A horizon-ish haze color derived from the sky config — the default fog
|
|
443
|
+
* color. Cheap model, judged by eye: clear skies haze blue-grey, turbid skies
|
|
444
|
+
* whiten, and a low sun warms the band toward amber.
|
|
445
|
+
*/
|
|
446
|
+
declare function horizonColorFromSky(sky: SkyEnvironment): string;
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/3d/water/underwater.d.ts
|
|
449
|
+
/** Animated caustics — the dancing refracted-light pattern on submerged surfaces. */
|
|
450
|
+
interface CausticsConfig {
|
|
451
|
+
enabled: boolean;
|
|
452
|
+
/** Hex tint of the caustic highlights (a bright pale aqua by default). */
|
|
453
|
+
color: string;
|
|
454
|
+
/** Brightness of the additive caustic light. */
|
|
455
|
+
intensity: number;
|
|
456
|
+
/** World-space frequency: bigger = smaller, busier cells. */
|
|
457
|
+
scale: number;
|
|
458
|
+
/** Animation speed multiplier. */
|
|
459
|
+
speed: number;
|
|
460
|
+
}
|
|
461
|
+
/** Resolved underwater look: fog color, view distance, and the caustics layer. */
|
|
462
|
+
interface UnderwaterConfig {
|
|
463
|
+
enabled: boolean;
|
|
464
|
+
/** Hex color of the underwater fog + background (the murk you're submerged in). */
|
|
465
|
+
color: string;
|
|
466
|
+
/** Visibility in meters — the fog's far plane (near is fixed tiny). */
|
|
467
|
+
visibility: number;
|
|
468
|
+
/** Caustics projected onto submerged surfaces (the floor especially). */
|
|
469
|
+
caustics: CausticsConfig;
|
|
470
|
+
}
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/3d/environment-3d.d.ts
|
|
473
|
+
/**
|
|
474
|
+
* Applies the scene `environment` header to a three scene — the rendering
|
|
475
|
+
* stage Renderer3D delegates to. One instance per renderer; `apply` runs once
|
|
476
|
+
* per frame but re-parses/rebuilds only when the env JSON actually changed.
|
|
477
|
+
*
|
|
478
|
+
* Headless-safe: `gl` is nullable, and everything GPU-bound (PMREM
|
|
479
|
+
* image-based lighting, exposure, shadow-map switches) is skipped without it
|
|
480
|
+
* while the scene-graph side (Sky object, fog, background, ambient) still
|
|
481
|
+
* applies — that's what the node-environment tests assert against.
|
|
482
|
+
*/
|
|
483
|
+
declare class Environment3D {
|
|
484
|
+
private readonly scene;
|
|
485
|
+
private readonly ambient;
|
|
486
|
+
private envKey;
|
|
487
|
+
private config;
|
|
488
|
+
private hdriUrl;
|
|
489
|
+
private hdriTexture;
|
|
490
|
+
/** @internal The Sky backdrop object (tests reach in). */
|
|
491
|
+
_sky: Sky | null;
|
|
492
|
+
private skyKey;
|
|
493
|
+
private skyEnvKey;
|
|
494
|
+
private skyEnvTarget;
|
|
495
|
+
private readonly fog;
|
|
496
|
+
/** Underwater fog/tint, created lazily the first time the camera submerges. */
|
|
497
|
+
private underwaterFog;
|
|
498
|
+
private readonly underwaterBg;
|
|
499
|
+
constructor(scene: Scene);
|
|
500
|
+
/** The sky's unit sun direction, or null when no sky is declared. */
|
|
501
|
+
get sunDirection(): [number, number, number] | null;
|
|
502
|
+
/** Parsed volumetric-cloud config, or null when no cloud layer is declared. */
|
|
503
|
+
get clouds(): CloudsEnvironment | null;
|
|
504
|
+
/** The live scene fog (color/near/far) the cloud composite fades into, or null. */
|
|
505
|
+
get sceneFog(): {
|
|
506
|
+
color: Color;
|
|
507
|
+
near: number;
|
|
508
|
+
far: number;
|
|
509
|
+
} | null;
|
|
510
|
+
/** Apply the env header. Cheap when unchanged; hard-validates on change. */
|
|
511
|
+
apply(env: JsonObject | undefined, gl: WebGLRenderer | null): void;
|
|
512
|
+
/**
|
|
513
|
+
* Per-frame hand-off for the scene's MAIN DirectionalLight (the renderer
|
|
514
|
+
* passes the brightest one after the sync pass — never auto-created):
|
|
515
|
+
*
|
|
516
|
+
* - sky declared → the light's backing object is re-aimed along the sun
|
|
517
|
+
* direction (its distance from origin is kept, so the node's `position`
|
|
518
|
+
* keeps meaning "how far out the sun sits")
|
|
519
|
+
* - `shadows` declared truthy → the light casts; env mapSize/radius win,
|
|
520
|
+
* and lights that configured nothing themselves get the default ortho box
|
|
521
|
+
*/
|
|
522
|
+
applySunLight(light: DirectionalLight | null, focus?: {
|
|
523
|
+
x: number;
|
|
524
|
+
y: number;
|
|
525
|
+
z: number;
|
|
526
|
+
} | null): void;
|
|
527
|
+
/**
|
|
528
|
+
* Underwater override (the renderer passes the resolved config from whichever
|
|
529
|
+
* water the camera is submerged in, or null). When submerged, the scene
|
|
530
|
+
* switches to a short-range underwater fog + matching background AND the sky
|
|
531
|
+
* dome is hidden — without that you'd see the bright sky through the surface
|
|
532
|
+
* and the murk would never read. `null` is a no-op: the per-frame `apply`
|
|
533
|
+
* already (re)established the normal fog/background/sky, so surfacing restores
|
|
534
|
+
* the look for free.
|
|
535
|
+
*/
|
|
536
|
+
applyUnderwater(config: UnderwaterConfig | null): void;
|
|
537
|
+
private applyHdri;
|
|
538
|
+
private applySky;
|
|
539
|
+
private applyFog;
|
|
540
|
+
dispose(): void;
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/3d/nodes/camera-3d.d.ts
|
|
544
|
+
/**
|
|
545
|
+
* A perspective camera. Mark exactly one camera `current: true`; with none
|
|
546
|
+
* marked, the renderer falls back to the first camera in tree order.
|
|
547
|
+
* Aspect ratio is managed by the renderer on resize.
|
|
548
|
+
*/
|
|
549
|
+
declare class Camera3D extends Node3D {
|
|
550
|
+
static override readonly typeName: string;
|
|
551
|
+
static override readonly props: PropSchema;
|
|
552
|
+
fov: number;
|
|
553
|
+
near: number;
|
|
554
|
+
far: number;
|
|
555
|
+
current: boolean;
|
|
556
|
+
protected override _createObject3D(): Object3D;
|
|
557
|
+
override _syncObject3D(): void;
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/3d/nodes/character-controller-3d.d.ts
|
|
561
|
+
/**
|
|
562
|
+
* The floating-capsule character controller behind every agent8 3D template
|
|
563
|
+
* (vibe-starter-3d parity, same numbers): put it UNDER a dynamic RigidBody3D
|
|
564
|
+
* with a capsule collider and it gives you impulse-based movement with
|
|
565
|
+
* sprint, jump, snappy falls, hover-float ground suspension, an
|
|
566
|
+
* intensity-driven movement state (drives animations), and a camera rig for
|
|
567
|
+
* the four classic views — free orbit, first person, quarter, side.
|
|
568
|
+
*/
|
|
569
|
+
declare class CharacterController3D extends Node3D {
|
|
570
|
+
static override readonly typeName: string;
|
|
571
|
+
static override readonly signals: readonly string[];
|
|
572
|
+
static override readonly props: PropSchema;
|
|
573
|
+
view: RigView;
|
|
574
|
+
camDistance: number;
|
|
575
|
+
eyeHeight: number;
|
|
576
|
+
maxSpeed: number;
|
|
577
|
+
sprintMultiplier: number;
|
|
578
|
+
jumpVelocity: number;
|
|
579
|
+
sprintJumpMultiplier: number;
|
|
580
|
+
floatHeight: number;
|
|
581
|
+
mouseLook: boolean;
|
|
582
|
+
zoomMin: number;
|
|
583
|
+
zoomMax: number;
|
|
584
|
+
pitchMin: number;
|
|
585
|
+
pitchMax: number;
|
|
586
|
+
/** Spring-arm: pull the orbit camera in front of walls/ground instead of clipping. */
|
|
587
|
+
cameraCollision: boolean;
|
|
588
|
+
turnSpeed: number;
|
|
589
|
+
/** Camera smoothing rate (1-exp(-rate·dt)); side view uses 5. */
|
|
590
|
+
camLerp: number;
|
|
591
|
+
moveAction: string;
|
|
592
|
+
jumpAction: string;
|
|
593
|
+
sprintAction: string;
|
|
594
|
+
skinPath: string;
|
|
595
|
+
/**
|
|
596
|
+
* Extra yaw added to the move-facing target (degrees) for models whose
|
|
597
|
+
* native forward is not +z. agent8's base-model IS +z-forward, so the
|
|
598
|
+
* default is 0; the skin merely STARTS at 180° (vibe-starter mounts the
|
|
599
|
+
* character facing away from the camera until it first moves).
|
|
600
|
+
*/
|
|
601
|
+
skinYawOffset: number;
|
|
602
|
+
/** Camera yaw/pitch in radians (mouse look mutates these). */
|
|
603
|
+
yaw: number;
|
|
604
|
+
pitch: number;
|
|
605
|
+
/** idle | walk | run | fastRun | airborne — drive animations from this. */
|
|
606
|
+
state: "idle" | "walk" | "run" | "fastRun" | "airborne";
|
|
607
|
+
grounded: boolean;
|
|
608
|
+
private get body();
|
|
609
|
+
override onEnterTree(): void;
|
|
610
|
+
override fixedUpdate(dt: number): void;
|
|
611
|
+
override update(dt: number): void;
|
|
612
|
+
private pivot;
|
|
613
|
+
private cameraCache;
|
|
614
|
+
private findCamera;
|
|
615
|
+
}
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region src/3d/vegetation/flower-geometry.d.ts
|
|
618
|
+
/**
|
|
619
|
+
* Procedural flower PLANTS for Flowers3D — real plants like the reference
|
|
620
|
+
* meadow flowers, not confetti quads: a curved stem strip, 2-3 leaf blades
|
|
621
|
+
* low on the stem, and a multi-petal head (5-8 rounded-diamond petals in a
|
|
622
|
+
* shallow cup around a contrasting center disc), with 1-3 blooms clustered
|
|
623
|
+
* per plant at staggered heights.
|
|
624
|
+
*
|
|
625
|
+
* Per VARIETY the geometry is built ONCE (deterministic — no rng; topology
|
|
626
|
+
* and positions are pure functions of the spec) and split into TWO buffers:
|
|
627
|
+
*
|
|
628
|
+
* - `structure`: stems + leaves + center discs, vertex-colored (greens + the
|
|
629
|
+
* variety's center color) — instanced with a light per-plant jitter tint
|
|
630
|
+
* - `petals`: the petal fans only, white — the per-plant `instanceColor`
|
|
631
|
+
* carries the palette pick, so one variety renders patches of different
|
|
632
|
+
* head colors without splitting draw calls
|
|
633
|
+
*
|
|
634
|
+
* The template is ~unit height: the instance matrix's uniform scale supplies
|
|
635
|
+
* the plant height in meters (petal/leaf sizes stay proportionate).
|
|
636
|
+
*/
|
|
637
|
+
declare const FLOWER_VARIETIES: readonly ["daisy", "cosmos", "bellflower"];
|
|
638
|
+
type FlowerVariety = (typeof FLOWER_VARIETIES)[number];
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/3d/nodes/flowers-3d.d.ts
|
|
641
|
+
/** Named density presets, plants per m² — the vibe-coding dial (풍성하게 /
|
|
642
|
+
* 듬성듬성 / 없게). Numbers are accepted too. */
|
|
643
|
+
declare const DENSITY_PRESETS: Record<string, number>;
|
|
644
|
+
/** Map a density prop value to plants/m² — presets by name, numbers as-is. */
|
|
645
|
+
declare function resolveFlowerDensity(density: string | number): number;
|
|
646
|
+
/**
|
|
647
|
+
* A first-class flower field: instanced procedural flower PLANTS — curved
|
|
648
|
+
* stems, leaf blades, multi-petal heads with contrasting centers, 1-3 blooms
|
|
649
|
+
* per plant (see vegetation/flower-geometry.ts) — scattered over an XZ
|
|
650
|
+
* `area`, deterministic from the `seed` PROP. Placement is cluster-gathered:
|
|
651
|
+
* whole Voronoi patches bloom together (`clustering`), each patch coherent
|
|
652
|
+
* in variety AND head color, like real meadow flowers.
|
|
653
|
+
*
|
|
654
|
+
* `density` is the vibe dial: `'lush'` (풍성하게), `'sparse'` (듬성듬성,
|
|
655
|
+
* default), `'none'` (없게) or a number in plants/m². Up to 3 `varieties`
|
|
656
|
+
* render as TWO InstancedMeshes each (green structure + palette-tinted
|
|
657
|
+
* petals) — ≤6 draw calls per node. `sway` bobs the heads in a gentle wind
|
|
658
|
+
* via a vertex-shader time uniform; roots never move.
|
|
659
|
+
*
|
|
660
|
+
* Like Foliage3D, the bed lies flat on the node's local XZ plane by default —
|
|
661
|
+
* on rolling terrain either set `drape: true` (each plant roots on a Terrain3D's
|
|
662
|
+
* surface per-instance) or place small patches at flat spots (the meadow theme
|
|
663
|
+
* does the latter for you).
|
|
664
|
+
*/
|
|
665
|
+
declare class Flowers3D extends Node3D {
|
|
666
|
+
static override readonly typeName: string;
|
|
667
|
+
static override readonly props: PropSchema;
|
|
668
|
+
/** 'lush' | 'sparse' | 'none' | plants per m² (load-time check + budget). */
|
|
669
|
+
density: string | number;
|
|
670
|
+
/** [x, z] extent in meters, centered on the node. */
|
|
671
|
+
area: number[];
|
|
672
|
+
/** Placement seed — the field is identical across runs and machines. */
|
|
673
|
+
seed: number;
|
|
674
|
+
/** Unique subset of ['daisy', 'cosmos', 'bellflower'] — ≤6 draw calls
|
|
675
|
+
* total. `[]` (default) = all three varieties. */
|
|
676
|
+
varieties: string[];
|
|
677
|
+
/** Head colors (hex) — patches pick one each. `[]` (default) = the
|
|
678
|
+
* reference white/yellow/violet trio. */
|
|
679
|
+
palette: string[];
|
|
680
|
+
/** Plant height in meters (±25% per-plant jitter; petals scale along). */
|
|
681
|
+
height: number;
|
|
682
|
+
/** 0 = uniform scatter … 1 = tight Voronoi patches (default 0.6). */
|
|
683
|
+
clustering: number;
|
|
684
|
+
/** Gentle head-bob wind strength — 0 holds still. */
|
|
685
|
+
sway: number;
|
|
686
|
+
/** Drape each plant onto a Terrain3D's surface so flowers root on the ground
|
|
687
|
+
* instead of a flat plane (lets ONE bed cover rolling terrain). */
|
|
688
|
+
drape: boolean;
|
|
689
|
+
/** Drape target: a node path to the Terrain3D. Empty = auto-find the first
|
|
690
|
+
* Terrain3D in the tree (the zero-config default when `drape` is on). */
|
|
691
|
+
terrain: string;
|
|
692
|
+
/** Resolved drape target + the bed's world position, set per rebuild. */
|
|
693
|
+
private drapeTerrain;
|
|
694
|
+
private drapeWorld;
|
|
695
|
+
/** Loader hook: bad densities/varieties/palettes fail at LOAD, not render. */
|
|
696
|
+
static validateJson(node: Node): void;
|
|
697
|
+
private time;
|
|
698
|
+
private field;
|
|
699
|
+
private varietyMeshes;
|
|
700
|
+
private fieldKey;
|
|
701
|
+
/** Shared by every material — update(dt) writes, the vertex sway reads. */
|
|
702
|
+
private readonly timeUniform;
|
|
703
|
+
private readonly swayUniform;
|
|
704
|
+
protected override _createObject3D(): Object3D;
|
|
705
|
+
/** @internal Every variety's mesh pair (tests and tools reach in here). */
|
|
706
|
+
_varietyMeshes(): {
|
|
707
|
+
variety: FlowerVariety;
|
|
708
|
+
structure: InstancedMesh;
|
|
709
|
+
petals: InstancedMesh;
|
|
710
|
+
}[];
|
|
711
|
+
override update(dt: number): void;
|
|
712
|
+
override _syncObject3D(): void;
|
|
713
|
+
/** Re-scatter only when a field-shaping prop changed (the JSON IS the field). */
|
|
714
|
+
private rebuildIfNeeded;
|
|
715
|
+
/** Resolve the drape target + cache the bed's world position for `bedY`. */
|
|
716
|
+
private resolveDrape;
|
|
717
|
+
/** Bed-local Y that roots a plant on the draped terrain (0 = flat). */
|
|
718
|
+
private bedY;
|
|
719
|
+
private buildField;
|
|
720
|
+
private disposeMeshes;
|
|
721
|
+
override free(): void;
|
|
722
|
+
}
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/3d/nodes/foliage-3d.d.ts
|
|
725
|
+
type FoliageKind = "grass" | "flowers" | "reeds";
|
|
726
|
+
type FoliageStyle = "mesh" | "tufts" | "blades" | "simple";
|
|
727
|
+
/**
|
|
728
|
+
* An instanced vegetation field — grass (잔디밭), flowers or reeds — scattered
|
|
729
|
+
* over an XZ `area`, deterministic from the `seed` PROP (never the engine
|
|
730
|
+
* rng) so the same scene JSON grows the identical field on every machine.
|
|
731
|
+
*
|
|
732
|
+
* Four looks, picked by `style`:
|
|
733
|
+
*
|
|
734
|
+
* - `'tufts'` (kind 'grass' only): the ez-tree-demo mechanism — ONE
|
|
735
|
+
* InstancedMesh of 3-crossed-quad cards (12 vertices each) carrying a
|
|
736
|
+
* baked multi-blade alpha-cutout texture, per-instance green tints
|
|
737
|
+
* (`colorA`→`colorB`, clump-coherent, dry patches) and the ported sin·cos
|
|
738
|
+
* tuft sway. Dozens of overlapping textured fans read as a DENSE meadow
|
|
739
|
+
* (잔디밭) at a fraction of the instances — the look that per-blade
|
|
740
|
+
* geometry never reaches at sane budgets.
|
|
741
|
+
*
|
|
742
|
+
* - `'mesh'` (default, kind 'grass' only): the per-blade path — ONE
|
|
743
|
+
* InstancedMesh where every instance is a REAL tapered blade (7-vertex
|
|
744
|
+
* strip) curved along a vertex-shader quadratic bezier. 8× the instances
|
|
745
|
+
* of the other styles (one blade each, still capped at `maxInstances`),
|
|
746
|
+
* Voronoi-clump shared lean/hue, soil-`groundColor` roots, Blinn-Phong tip
|
|
747
|
+
* sheen vs `sunDirection`, 2-octave rolling wind gusts, and a
|
|
748
|
+
* camera-distance LOD (`fadeStart`/`fadeEnd`). Fully opaque — no alpha
|
|
749
|
+
* sorting. Best up close; pairs with tufts (sparse mesh over a tuft body).
|
|
750
|
+
*
|
|
751
|
+
* - `'blades'` (kind 'grass' only): the ported shader look — TWO
|
|
752
|
+
* InstancedMeshes of crossed bottom-anchored quads (same instances, second
|
|
753
|
+
* rotated 90°) whose fragment shader draws 8 procedural SDF blades per
|
|
754
|
+
* quad with per-blade wind phases, plus a vertex wind ride.
|
|
755
|
+
*
|
|
756
|
+
* - `'simple'`: the legacy look (and the ONLY look for flowers/reeds) — one
|
|
757
|
+
* InstancedMesh of crossed quads with per-instance colorA→colorB lerp and a
|
|
758
|
+
* whole-field shear sway (one matrix write per frame).
|
|
759
|
+
*
|
|
760
|
+
* mesh + blades share placement (cluster-noise passes) and the character
|
|
761
|
+
* bend: with `interaction: true` the nearest ≤4 moving bodies
|
|
762
|
+
* (CharacterBody3D / RigidBody3D) push blade tops radially aside. Tufts
|
|
763
|
+
* skip the bend (a card fan has no per-blade tops to push).
|
|
764
|
+
*/
|
|
765
|
+
declare class Foliage3D extends Node3D implements SunConsumer3D {
|
|
766
|
+
static override readonly typeName: string;
|
|
767
|
+
static override readonly props: PropSchema;
|
|
768
|
+
/** Transparent blades draw after water (1) + opaque ground (overrides Node3D's 0). */
|
|
769
|
+
override renderOrder: number;
|
|
770
|
+
/** 'grass' | 'flowers' | 'reeds' — picks the blade silhouette. */
|
|
771
|
+
kind: string;
|
|
772
|
+
/** 'mesh' (real curved blades) | 'tufts' (ez-tree textured cards) |
|
|
773
|
+
* 'blades' (SDF quads) | 'simple' — mesh/tufts/blades are grass only. */
|
|
774
|
+
style: string;
|
|
775
|
+
/** Tufts style only: which silhouette the card texture bakes — 'grass'
|
|
776
|
+
* (the dense blade fan, default) or 'fern' (fewer, wider, arching fronds
|
|
777
|
+
* with leaflet notches — forest undergrowth). */
|
|
778
|
+
tuftStyle: string;
|
|
779
|
+
/** [x, z] extent in meters, centered on the node. */
|
|
780
|
+
area: number[];
|
|
781
|
+
/** Instances per square meter (mesh style plants 8× — capped at `maxInstances`). */
|
|
782
|
+
density: number;
|
|
783
|
+
/** Instance cap (hard ceiling 200_000). */
|
|
784
|
+
maxInstances: number;
|
|
785
|
+
/** Base blade height in meters — mesh style additionally scales it 0.55–1.7×
|
|
786
|
+
* regionally (low-frequency noise: tall patches and short worn patches). */
|
|
787
|
+
height: number;
|
|
788
|
+
/** colorA = bottom/lerp-start, colorB = top/lerp-end. Refinement pass 3:
|
|
789
|
+
* lifted toward the reference's warm sunlit green (the old olives read
|
|
790
|
+
* dusky under the brighter meadow stage). */
|
|
791
|
+
colorA: string;
|
|
792
|
+
colorB: string;
|
|
793
|
+
/** Mesh style: soil tint at the blade ROOT — pair with the terrain/ground color. */
|
|
794
|
+
groundColor: string;
|
|
795
|
+
/** Mesh style: fixed sun for the specular sheen — match the scene's key light. */
|
|
796
|
+
sunDirection: number[];
|
|
797
|
+
/** Mesh style LOD: blades start shrinking at this camera distance (meters)… */
|
|
798
|
+
fadeStart: number;
|
|
799
|
+
/** …and are gone by this one. */
|
|
800
|
+
fadeEnd: number;
|
|
801
|
+
/** Wind strength — 0 disables wind entirely. */
|
|
802
|
+
sway: number;
|
|
803
|
+
/** mesh/blades styles: let nearby moving bodies bend the grass. */
|
|
804
|
+
interaction: boolean;
|
|
805
|
+
/** Mesh style: carpet fill in (0, 1] — below 1, low-frequency noise carves
|
|
806
|
+
* dirt patches into the field (default 0.85 ≈ a real meadow's break-up;
|
|
807
|
+
* 1 = the old wall-to-wall carpet). */
|
|
808
|
+
coverage: number;
|
|
809
|
+
/** Mesh style: fraction of instances rendered as small flower heads
|
|
810
|
+
* (white/yellow/violet), 0–0.2. Default 0.
|
|
811
|
+
* @deprecated The 5-vertex heads read as confetti — use a `Flowers3D`
|
|
812
|
+
* node for real flower plants (stems, leaves, multi-petal heads, density
|
|
813
|
+
* presets). Kept working for existing scenes; generators emit Flowers3D. */
|
|
814
|
+
flowers: number;
|
|
815
|
+
/** Placement seed — the field is identical across runs and machines. */
|
|
816
|
+
seed: number;
|
|
817
|
+
/** Drape each instance onto a Terrain3D's surface so blades root on the
|
|
818
|
+
* ground instead of a flat plane (lets ONE field cover rolling terrain). */
|
|
819
|
+
drape: boolean;
|
|
820
|
+
/** Drape target: a node path to the Terrain3D. Empty = auto-find the first
|
|
821
|
+
* Terrain3D in the tree (the zero-config default when `drape` is on). */
|
|
822
|
+
terrain: string;
|
|
823
|
+
/** Resolved drape target + the field's world position, set per rebuild. */
|
|
824
|
+
private drapeTerrain;
|
|
825
|
+
private drapeWorld;
|
|
826
|
+
/** Loader hook: bad kinds/styles and degenerate areas fail at LOAD, not at render. */
|
|
827
|
+
static validateJson(node: Node): void;
|
|
828
|
+
private time;
|
|
829
|
+
private pivot;
|
|
830
|
+
private meshes;
|
|
831
|
+
private bladesMaterial;
|
|
832
|
+
private benderSlots;
|
|
833
|
+
private pickedBenders;
|
|
834
|
+
private fieldKey;
|
|
835
|
+
/** Tufts style: the wind uniforms + the baked card texture (disposed here). */
|
|
836
|
+
private tuftTime;
|
|
837
|
+
private tuftStrength;
|
|
838
|
+
private tuftTexture;
|
|
839
|
+
protected override _createObject3D(): Object3D;
|
|
840
|
+
/** Whether this field renders the ported procedural-blades shader. */
|
|
841
|
+
private get isBlades();
|
|
842
|
+
/** Whether this field renders real bezier-curved blade meshes (the default). */
|
|
843
|
+
private get isMesh();
|
|
844
|
+
/** Whether this field renders the ez-tree-demo textured tuft cards. */
|
|
845
|
+
private get isTufts();
|
|
846
|
+
/** Mesh/blades share the shader-driven path (uniform wind + benders). */
|
|
847
|
+
private get isShaderField();
|
|
848
|
+
/** @internal The primary InstancedMesh (lazily built — tests reach in here). */
|
|
849
|
+
_field(): InstancedMesh;
|
|
850
|
+
/** @internal Every InstancedMesh of the field: [quads] or [quads, quads90°]. */
|
|
851
|
+
_fields(): InstancedMesh[];
|
|
852
|
+
override update(dt: number): void;
|
|
853
|
+
override _syncObject3D(): void;
|
|
854
|
+
/**
|
|
855
|
+
* @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
|
|
856
|
+
* sky drives the blade sheen ONLY while the `sunDirection` prop sits at its
|
|
857
|
+
* schema default — an explicit prop wins. Mesh style only (the other
|
|
858
|
+
* styles have no sun uniform).
|
|
859
|
+
*/
|
|
860
|
+
_applySunDirection(dir: readonly [number, number, number]): void;
|
|
861
|
+
/**
|
|
862
|
+
* Character bend: collect moving bodies in the tree, keep the nearest ≤4
|
|
863
|
+
* whose reach touches this field. `_syncObject3D` writes them into the
|
|
864
|
+
* shader's vec4 slots (xyz = world position, w = radius; w 0 = inactive).
|
|
865
|
+
*/
|
|
866
|
+
private computeBenders;
|
|
867
|
+
/** Re-scatter only when a field-shaping prop changed (the JSON IS the field). */
|
|
868
|
+
private rebuildIfNeeded;
|
|
869
|
+
/** Resolve the drape target + cache the field's world position for `bedY`. */
|
|
870
|
+
private resolveDrape;
|
|
871
|
+
/** Field-local Y that roots an instance on the draped terrain (0 = flat). */
|
|
872
|
+
private bedY;
|
|
873
|
+
/**
|
|
874
|
+
* The mesh-style field: ONE InstancedMesh of real tapered blade strips,
|
|
875
|
+
* cluster-noise placement, clump-coherent per-instance attributes —
|
|
876
|
+
* `aBlade` (leanX, leanZ, curve, rand), `aClumpHue` and `aDry` — feeding
|
|
877
|
+
* the bezier vertex shader. Fully opaque: no sorting, no alpha test.
|
|
878
|
+
*
|
|
879
|
+
* Refinement pass: `coverage` < 1 drops blades in noise-carved patches
|
|
880
|
+
* (dirt breaks the carpet), a low-frequency height field scales blades
|
|
881
|
+
* 0.55–1.7× regionally (tall blades lean/curve harder — droop), `aDry`
|
|
882
|
+
* mixes dry yellow-green hues in noise patches, and `flowers` > 0 splits a
|
|
883
|
+
* fraction of instances into a SECOND InstancedMesh of 5-vertex flower
|
|
884
|
+
* heads (white/yellow/violet per instance).
|
|
885
|
+
*/
|
|
886
|
+
private buildMeshField;
|
|
887
|
+
/**
|
|
888
|
+
* The meadow's flower sprinkle: ONE extra InstancedMesh of 5-vertex flower
|
|
889
|
+
* heads riding the grass canopy (the matrix y-scale is the stem height; the
|
|
890
|
+
* head's local y ≈ 1). A standard material keeps fog/shadows/tonemapping
|
|
891
|
+
* for free, instance colors pick white/yellow/violet deterministically.
|
|
892
|
+
*/
|
|
893
|
+
private buildFlowerMesh;
|
|
894
|
+
/**
|
|
895
|
+
* The tufts field — the ez-tree demo's grass verbatim in spirit: ONE
|
|
896
|
+
* InstancedMesh of 3-crossed-quad cards, a baked multi-blade alpha-cutout
|
|
897
|
+
* texture (see tuft-texture.ts for the recon), per-instance green tints
|
|
898
|
+
* (clump-coherent + dry patches) and the ported sin·cos tuft sway.
|
|
899
|
+
* MeshStandardMaterial keeps fog/shadows/tonemapping for free.
|
|
900
|
+
*/
|
|
901
|
+
private buildTuftField;
|
|
902
|
+
/**
|
|
903
|
+
* The ported procedural grass: cluster-noise placement, then the SAME
|
|
904
|
+
* placements into two InstancedMeshes — the second yawed 90° so every slot
|
|
905
|
+
* renders as a crossed pair — sharing ONE ShaderMaterial (one uniform set).
|
|
906
|
+
*/
|
|
907
|
+
private buildBladesField;
|
|
908
|
+
/** The legacy field: uniform scatter, instance colors, one draw call. */
|
|
909
|
+
private buildSimpleField;
|
|
910
|
+
private disposeMeshes;
|
|
911
|
+
override free(): void;
|
|
912
|
+
}
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/3d/nodes/lights-3d.d.ts
|
|
915
|
+
/** Sun-style light. Direction comes from the node's rotation. */
|
|
916
|
+
declare class DirectionalLight3D extends Node3D {
|
|
917
|
+
static override readonly typeName: string;
|
|
918
|
+
static override readonly props: PropSchema;
|
|
919
|
+
color: string;
|
|
920
|
+
intensity: number;
|
|
921
|
+
castShadow: boolean;
|
|
922
|
+
/** Half-extent of the directional shadow frustum (template default ±75). */
|
|
923
|
+
shadowArea: number;
|
|
924
|
+
shadowMapSize: number;
|
|
925
|
+
/**
|
|
926
|
+
* Re-center the shadow frustum on the camera every frame. A directional sun's
|
|
927
|
+
* shadow map covers a fixed ortho box; in a big roaming world that box must be
|
|
928
|
+
* huge (and so low-resolution that the player's own shadow nearly vanishes).
|
|
929
|
+
* With this on, a SMALL `shadowArea` tracks the camera, so contact shadows stay
|
|
930
|
+
* crisp wherever you go. The renderer supplies the camera position.
|
|
931
|
+
*/
|
|
932
|
+
shadowFollowsCamera: boolean;
|
|
933
|
+
protected override _createObject3D(): Object3D;
|
|
934
|
+
override _syncObject3D(): void;
|
|
935
|
+
}
|
|
936
|
+
/** Point light with a range (Godot's OmniLight3D). */
|
|
937
|
+
declare class OmniLight3D extends Node3D {
|
|
938
|
+
static override readonly typeName: string;
|
|
939
|
+
static override readonly props: PropSchema;
|
|
940
|
+
color: string;
|
|
941
|
+
intensity: number;
|
|
942
|
+
/** 0 = unlimited (three `distance` semantics). */
|
|
943
|
+
range: number;
|
|
944
|
+
protected override _createObject3D(): Object3D;
|
|
945
|
+
override _syncObject3D(): void;
|
|
946
|
+
}
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/3d/nodes/mesh-instance-3d.d.ts
|
|
949
|
+
type MeshKind = "box" | "sphere" | "capsule" | "plane" | "cylinder" | "gem";
|
|
950
|
+
interface MeshMaterialProps {
|
|
951
|
+
color?: string;
|
|
952
|
+
metalness?: number;
|
|
953
|
+
roughness?: number;
|
|
954
|
+
emissive?: string;
|
|
955
|
+
emissiveIntensity?: number;
|
|
956
|
+
wireframe?: boolean;
|
|
957
|
+
/** Flat per-face shading — gives faceted meshes (gem!) crisp glints that
|
|
958
|
+
* sparkle as the mesh turns, instead of a smoothed surface. */
|
|
959
|
+
flatShading?: boolean;
|
|
960
|
+
/** 0..1 surface opacity; below 1 turns on transparency (glassy gems, etc.). */
|
|
961
|
+
opacity?: number;
|
|
962
|
+
/** Color texture URL (sampled sRGB, tinted by `color`). */
|
|
963
|
+
map?: string;
|
|
964
|
+
/** Tangent-space normal map URL (sampled linear). */
|
|
965
|
+
normalMap?: string;
|
|
966
|
+
/** Texture tiling [u, v] across the mesh's UVs (both maps; default [1,1]).
|
|
967
|
+
* Box/plane UVs span 0–1 per face — scale repeat by the face's world size
|
|
968
|
+
* for worldspace-ish texel density (e.g. a 6×2.5 m wall at one tile per
|
|
969
|
+
* meter wants `repeat: [6, 2.5]`). */
|
|
970
|
+
repeat?: [number, number];
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* A primitive mesh. Intent-level props only:
|
|
974
|
+
* - `mesh`: box | sphere | capsule | plane | cylinder | gem
|
|
975
|
+
* - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
|
|
976
|
+
* height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
|
|
977
|
+
* - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
|
|
978
|
+
* emissive, emissiveIntensity, map, normalMap, repeat}` — `map`/`normalMap` are
|
|
979
|
+
* texture URLs loaded lazily (headless-safe: geometry and physics never
|
|
980
|
+
* wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading: true` + a low
|
|
981
|
+
* `roughness` reads as a sparkling faceted jewel (spin it for the sparkle).
|
|
982
|
+
*/
|
|
983
|
+
declare class MeshInstance3D extends Node3D {
|
|
984
|
+
static override readonly typeName: string;
|
|
985
|
+
static override readonly props: PropSchema;
|
|
986
|
+
mesh: string;
|
|
987
|
+
size: number[];
|
|
988
|
+
material: MeshMaterialProps;
|
|
989
|
+
castShadow: boolean;
|
|
990
|
+
receiveShadow: boolean;
|
|
991
|
+
/** Loader hook: malformed materials fail at LOAD time, not at render. */
|
|
992
|
+
static validateJson(node: Node): void;
|
|
993
|
+
private geometryKey;
|
|
994
|
+
private textureKey;
|
|
995
|
+
private loadedTextures;
|
|
996
|
+
protected override _createObject3D(): Object3D;
|
|
997
|
+
override _syncObject3D(): void;
|
|
998
|
+
/** Lazy texture dressing — same headless guard as Tree3D bark: without a
|
|
999
|
+
* DOM there is no Image decode, so geometry/colors apply and the maps are
|
|
1000
|
+
* skipped. Re-loads only when a texture-shaping key changes. */
|
|
1001
|
+
private syncTextures;
|
|
1002
|
+
private loadTexture;
|
|
1003
|
+
override free(): void;
|
|
1004
|
+
}
|
|
1005
|
+
//#endregion
|
|
1006
|
+
//#region src/3d/nodes/model-instance-3d.d.ts
|
|
1007
|
+
/**
|
|
1008
|
+
* A GLB/glTF/VRM model. `model` is a `$key` into the scene's assets header
|
|
1009
|
+
* ({type:"model", url}) or a direct URL. `targetHeight` (>0) uniformly scales
|
|
1010
|
+
* the model to stand that many units tall (`npx incanto-model` reports the raw
|
|
1011
|
+
* size). `animation` plays either an EMBEDDED clip name or an animation-asset
|
|
1012
|
+
* reference (`"$run"` → {type:"animation"} entry); Mixamo-rigged assets are
|
|
1013
|
+
* retargeted automatically onto VRM humanoids (vibe-starter rigmap).
|
|
1014
|
+
*
|
|
1015
|
+
* GLB instances are skeleton-aware clones (one asset → many nodes). A VRM
|
|
1016
|
+
* mounts its SOURCE scene so the humanoid rig and springbones stay live —
|
|
1017
|
+
* one node per VRM asset (additional nodes warn and stay empty).
|
|
1018
|
+
*/
|
|
1019
|
+
declare class ModelInstance3D extends Node3D {
|
|
1020
|
+
static override readonly typeName: string;
|
|
1021
|
+
static override readonly signals: readonly string[];
|
|
1022
|
+
static override props: PropSchema;
|
|
1023
|
+
model: string;
|
|
1024
|
+
animation: string;
|
|
1025
|
+
targetHeight: number;
|
|
1026
|
+
castShadow: boolean;
|
|
1027
|
+
/** false = play the clip once, then emit `animationFinished`. */
|
|
1028
|
+
animationLoop: boolean;
|
|
1029
|
+
receiveShadow: boolean;
|
|
1030
|
+
/** Hex colour multiplied into every material — RESKIN one shared GLB into many
|
|
1031
|
+
* variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
|
|
1032
|
+
tint: string;
|
|
1033
|
+
private store;
|
|
1034
|
+
private entry;
|
|
1035
|
+
private mountedRef;
|
|
1036
|
+
private fittedHeight;
|
|
1037
|
+
private fitGroup;
|
|
1038
|
+
private mixer;
|
|
1039
|
+
private playing;
|
|
1040
|
+
private currentAction;
|
|
1041
|
+
/** action → the `animation` key that played it, so `animationFinished` reports
|
|
1042
|
+
* the clip that ACTUALLY finished (a crossfaded-away one-shot can finish a few
|
|
1043
|
+
* frames after `this.animation` already moved on). */
|
|
1044
|
+
private actionKeys;
|
|
1045
|
+
private warnedClaim;
|
|
1046
|
+
/** Per-mesh original (shared) materials, captured at mount so `tint` can always
|
|
1047
|
+
* re-derive from the source. Our clones are tracked for disposal. */
|
|
1048
|
+
private tintTargets;
|
|
1049
|
+
private tintedClones;
|
|
1050
|
+
private appliedTint;
|
|
1051
|
+
protected override _createObject3D(): Object3D;
|
|
1052
|
+
/** Embedded clip names + the scene's animation `$key`s (editor dropdown). */
|
|
1053
|
+
availableAnimations(): string[];
|
|
1054
|
+
/** Called by the 3D sync pass with the renderer's asset store. */
|
|
1055
|
+
_syncModel(assets: AssetStore3D): void;
|
|
1056
|
+
/** Multiply `tint` into every material — a per-instance reskin of one shared
|
|
1057
|
+
* GLB (e.g. base human → green zombie). Clones the captured originals (never
|
|
1058
|
+
* mutates the shared source), disposing prior clones on change. */
|
|
1059
|
+
private applyTint;
|
|
1060
|
+
private syncAnimation;
|
|
1061
|
+
override update(dt: number): void;
|
|
1062
|
+
override onExitTree(): void;
|
|
1063
|
+
private unmount;
|
|
1064
|
+
}
|
|
1065
|
+
//#endregion
|
|
1066
|
+
//#region src/3d/nodes/particles-3d.d.ts
|
|
1067
|
+
/**
|
|
1068
|
+
* Point-sprite particle emitter — same presets and deterministic sim as
|
|
1069
|
+
* Particles2D, in meters (preset distances auto-scale ÷100). `rate: 0` +
|
|
1070
|
+
* `burst` one-shots emit `finished`. Spread covers a sphere (`spreadDeg`
|
|
1071
|
+
* tilts emission out of the plane too).
|
|
1072
|
+
*/
|
|
1073
|
+
declare class Particles3D extends Node3D {
|
|
1074
|
+
static override readonly typeName: string;
|
|
1075
|
+
static override readonly signals: readonly string[];
|
|
1076
|
+
static override readonly props: PropSchema;
|
|
1077
|
+
preset: string;
|
|
1078
|
+
emitting: boolean;
|
|
1079
|
+
rate: number;
|
|
1080
|
+
burst: number;
|
|
1081
|
+
lifetime: number[];
|
|
1082
|
+
speed: number[];
|
|
1083
|
+
directionDeg: number;
|
|
1084
|
+
spreadDeg: number;
|
|
1085
|
+
gravity: number[];
|
|
1086
|
+
drag: number;
|
|
1087
|
+
sizeStart: number;
|
|
1088
|
+
sizeEnd: number;
|
|
1089
|
+
colorStart: string;
|
|
1090
|
+
colorEnd: string;
|
|
1091
|
+
/** Per-particle base colours sampled by the particle's stable seed (multi-
|
|
1092
|
+
* colour confetti). Empty → the colorStart→colorEnd ramp is used unchanged. */
|
|
1093
|
+
paletteColors: string[];
|
|
1094
|
+
alphaStart: number;
|
|
1095
|
+
alphaEnd: number;
|
|
1096
|
+
/** Per-particle alpha twinkle frequency in Hz (0 = off) — a seed-phased sine
|
|
1097
|
+
* over the particle's age, so each sparkle glitters on its own rhythm. */
|
|
1098
|
+
shimmer: number;
|
|
1099
|
+
blend: string;
|
|
1100
|
+
/** Depth-test against scene geometry (default true). Set false so the effect
|
|
1101
|
+
* isn't depth-occluded by the scene — pair with a high `renderOrder` to also
|
|
1102
|
+
* win the TRANSPARENT draw-order against alpha scenery (e.g. grass blades,
|
|
1103
|
+
* which write depth AND draw after a default-order particle, hiding it). */
|
|
1104
|
+
depthTest: boolean;
|
|
1105
|
+
maxParticles: number;
|
|
1106
|
+
static validateJson(node: Node): void;
|
|
1107
|
+
private sim;
|
|
1108
|
+
private bursted;
|
|
1109
|
+
private finishedEmitted;
|
|
1110
|
+
/** @internal */
|
|
1111
|
+
_ensureSim(): ParticleSim;
|
|
1112
|
+
override update(dt: number): void;
|
|
1113
|
+
/** Restart a one-shot (re-burst + allow finished to fire again). */
|
|
1114
|
+
replay(): void;
|
|
1115
|
+
private points;
|
|
1116
|
+
private positions;
|
|
1117
|
+
private colors;
|
|
1118
|
+
/** Parsed `paletteColors`, reused; rebuilt only when the hex list changes. */
|
|
1119
|
+
private paletteCache;
|
|
1120
|
+
private paletteCacheKey;
|
|
1121
|
+
override _syncObject3D(): void;
|
|
1122
|
+
/** Parse `paletteColors` into a reused `Color[]`, rebuilt only when the hex
|
|
1123
|
+
* list changes (zero per-frame allocation); null when empty so the caller
|
|
1124
|
+
* uses the colorStart→colorEnd ramp. */
|
|
1125
|
+
private refreshPalette;
|
|
1126
|
+
private syncParticles;
|
|
1127
|
+
}
|
|
1128
|
+
//#endregion
|
|
1129
|
+
//#region src/3d/terrain/heightmap.d.ts
|
|
1130
|
+
/**
|
|
1131
|
+
* A smooth radial depression carved into the terrain — a lake/pond bowl. x/z
|
|
1132
|
+
* are terrain-LOCAL centered meters (the same space `heightAt`/`slopeAt` use);
|
|
1133
|
+
* `radius`/`depth` are meters. The carve is a smoothstep bowl (0 slope at the
|
|
1134
|
+
* rim), so banks blend into the surrounding ground with no cliff.
|
|
1135
|
+
*/
|
|
1136
|
+
interface BasinSpec {
|
|
1137
|
+
x: number;
|
|
1138
|
+
z: number;
|
|
1139
|
+
radius: number;
|
|
1140
|
+
depth: number;
|
|
1141
|
+
}
|
|
1142
|
+
interface HeightmapOptions {
|
|
1143
|
+
/** Terrain extent in meters along x. */
|
|
1144
|
+
width: number;
|
|
1145
|
+
/** Terrain extent in meters along z. */
|
|
1146
|
+
depth: number;
|
|
1147
|
+
/** Grid segments along x (vertices = segsX+1). */
|
|
1148
|
+
segsX: number;
|
|
1149
|
+
/** Grid segments along z (vertices = segsZ+1). */
|
|
1150
|
+
segsZ: number;
|
|
1151
|
+
/** Height scale — realized heights land at roughly 3.5–8× this value. */
|
|
1152
|
+
maxHeight: number;
|
|
1153
|
+
/** Integer seed — same seed, same terrain, every machine. */
|
|
1154
|
+
seed: number;
|
|
1155
|
+
/** Detail persistence (amplitude falloff per detail octave). */
|
|
1156
|
+
roughness?: number;
|
|
1157
|
+
/** Detail octave count. */
|
|
1158
|
+
detail?: number;
|
|
1159
|
+
/** Snap-flatten heights within ±0.6 m of maxHeight·flatThreshold. */
|
|
1160
|
+
flatThreshold?: number;
|
|
1161
|
+
/** Curl the border down a quarter circle and floor it at edgeBottom. */
|
|
1162
|
+
islandEdge?: boolean;
|
|
1163
|
+
/** Quarter-circle radius in meters (clamped to [0, 45]). */
|
|
1164
|
+
edgeWrapRadius?: number;
|
|
1165
|
+
/** Floor y at the outermost ring once the wrap completes. */
|
|
1166
|
+
edgeBottom?: number;
|
|
1167
|
+
/** 0..1 — 1 rounds island corners fully, 0 keeps them square-ish. */
|
|
1168
|
+
edgeCornerSmoothness?: number;
|
|
1169
|
+
/** Smooth radial lake/pond bowls carved into the surface (interior basins). */
|
|
1170
|
+
basins?: BasinSpec[];
|
|
1171
|
+
}
|
|
1172
|
+
interface Heightmap {
|
|
1173
|
+
readonly width: number;
|
|
1174
|
+
readonly depth: number;
|
|
1175
|
+
readonly segsX: number;
|
|
1176
|
+
readonly segsZ: number;
|
|
1177
|
+
/**
|
|
1178
|
+
* Vertex heights INCLUDING edge wrap, `(segsX+1)·(segsZ+1)` long, indexed
|
|
1179
|
+
* `iz·(segsX+1)+ix` with x = -width/2 + ix·cell and z = -depth/2 + iz·cell —
|
|
1180
|
+
* exactly a rotateX(-π/2) PlaneGeometry's vertex order.
|
|
1181
|
+
*/
|
|
1182
|
+
readonly heights: Float32Array;
|
|
1183
|
+
/** Realized height range EXCLUDING edge wrap — normalizes splat heights. */
|
|
1184
|
+
readonly minHeight: number;
|
|
1185
|
+
readonly maxHeight: number;
|
|
1186
|
+
/** Bilinear height lookup in terrain-local centered meters (clamps outside). */
|
|
1187
|
+
heightAt(x: number, z: number): number;
|
|
1188
|
+
/** Analytic pipeline height WITHOUT edge wrap (splat domain), same coords. */
|
|
1189
|
+
baseHeight(x: number, z: number): number;
|
|
1190
|
+
/** Splat slope at a point: atan(|∇ baseHeight|) via central differences. */
|
|
1191
|
+
slopeAt(x: number, z: number): number;
|
|
1192
|
+
}
|
|
1193
|
+
/** One splat layer: a texture plus the trapezoid ranges that place it. */
|
|
1194
|
+
interface TerrainLayer {
|
|
1195
|
+
/** Base color texture URL. */
|
|
1196
|
+
texture: string;
|
|
1197
|
+
/** Optional tangent-space normal map URL. */
|
|
1198
|
+
normalMap?: string;
|
|
1199
|
+
/** World-UV tiling factor (UV = worldXZ · repeat · 0.1). Default 2. */
|
|
1200
|
+
repeat?: number;
|
|
1201
|
+
/** Normalized [min, max] over the realized height span. Default [0, 1]. */
|
|
1202
|
+
heightRange?: [number, number];
|
|
1203
|
+
/** [min, max] slope in radians (atan of gradient). Default [0, π/2]. */
|
|
1204
|
+
slopeRange?: [number, number];
|
|
1205
|
+
/** Linear falloff width OUTSIDE heightRange (fraction of span). Default 0.1. */
|
|
1206
|
+
heightBlendRange?: number;
|
|
1207
|
+
/** Linear falloff width outside slopeRange (radians). Default 0.1. */
|
|
1208
|
+
slopeBlendRange?: number;
|
|
1209
|
+
/** PBR roughness scalar for this layer. Default 1. */
|
|
1210
|
+
roughness?: number;
|
|
1211
|
+
/** PBR metalness scalar for this layer. Default 0. */
|
|
1212
|
+
metalness?: number;
|
|
1213
|
+
/** Anti-tiling UV jitter strength. Default 0.15. */
|
|
1214
|
+
uvNoiseIntensity?: number;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Build the deterministic heightfield. Heavy (one noise eval per vertex) —
|
|
1218
|
+
* call once and keep the result.
|
|
1219
|
+
*/
|
|
1220
|
+
declare function buildHeightmap(opts: HeightmapOptions): Heightmap;
|
|
1221
|
+
/**
|
|
1222
|
+
* Per-vertex splat weights for up to 4 layers. `height01` is the vertex
|
|
1223
|
+
* height normalized over the heightmap's realized (no-wrap) span, `slopeRad`
|
|
1224
|
+
* is `atan(|∇h|)`. Weight = heightTrapezoid · slopeTrapezoid, sharpened by
|
|
1225
|
+
* `pow(w, 1/blendingStrength)`, then normalized to sum 1 — when nothing
|
|
1226
|
+
* matches, layer 0 takes everything. Always returns exactly 4 entries
|
|
1227
|
+
* (missing layers weigh 0).
|
|
1228
|
+
*/
|
|
1229
|
+
declare function splatWeights(height01: number, slopeRad: number, layers: readonly TerrainLayer[], blendingStrength?: number): [number, number, number, number];
|
|
1230
|
+
//#endregion
|
|
1231
|
+
//#region src/3d/terrain/themes.d.ts
|
|
1232
|
+
/** Valid `Terrain3D.theme` values — drives the prop options AND validateJson. */
|
|
1233
|
+
declare const TERRAIN_THEMES: readonly ["island", "alpine", "plains", "desert", "grassland", "forest", "savanna", "snow", "wetland", "volcanic", "custom"];
|
|
1234
|
+
type TerrainTheme = (typeof TERRAIN_THEMES)[number];
|
|
1235
|
+
/** Live CDN the agent8 starter terrains splat from — works without any setup.
|
|
1236
|
+
* (Lives here so themes can detect it; re-exported by Terrain3D.) */
|
|
1237
|
+
declare const DEFAULT_TERRAIN_TEXTURE_BASE = "https://agent8-games.verse8.io/assets/3D/default/textures/terrain";
|
|
1238
|
+
/**
|
|
1239
|
+
* Resolve a theme to its 3–4 splat layers against a texture CDN base
|
|
1240
|
+
* (`<base>/<name>.png` + `<base>/<name>_normal.png`). Height ranges are
|
|
1241
|
+
* NORMALIZED 0..1 over the terrain's realized (no-edge-wrap) height span,
|
|
1242
|
+
* slope ranges are radians of `atan(|∇h|)`. 'custom' has no preset — the
|
|
1243
|
+
* node hard-fails before ever calling this with it.
|
|
1244
|
+
*/
|
|
1245
|
+
declare function terrainThemeLayers(theme: TerrainTheme, textureBase: string): TerrainLayer[];
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region src/3d/nodes/terrain-3d.d.ts
|
|
1248
|
+
/**
|
|
1249
|
+
* Procedural heightfield terrain with biome texture splatting — seeded
|
|
1250
|
+
* simplex octaves displace a PlaneGeometry once on the CPU, per-vertex
|
|
1251
|
+
* height/slope trapezoids pick between 4 biome textures (vertex-color
|
|
1252
|
+
* weights, world-UV anti-tiled sampling in a patched MeshStandardMaterial).
|
|
1253
|
+
* Ported from the agent8 `vibe-starter-3d-environment` terrain stack.
|
|
1254
|
+
*
|
|
1255
|
+
* - `theme` presets the splat layers (and the island edge wrap); 'custom'
|
|
1256
|
+
* takes your own `layers`.
|
|
1257
|
+
* - `heightAt(x, z)` answers the surface height at WORLD coords — placement,
|
|
1258
|
+
* spawning, AI all read it, headless included (no renderer needed).
|
|
1259
|
+
* - Physics: put the terrain UNDER a `StaticBody3D` with
|
|
1260
|
+
* `collider: { shape: 'heightfield' }` — the body pulls this node's grid.
|
|
1261
|
+
*
|
|
1262
|
+
* Heights realize at roughly 3.5–8× `maxHeight` (the ported pipeline sums
|
|
1263
|
+
* noise octaves above zero) — treat `maxHeight` as a scale knob, read the
|
|
1264
|
+
* real numbers off `heightAt`.
|
|
1265
|
+
*/
|
|
1266
|
+
declare class Terrain3D extends Node3D {
|
|
1267
|
+
static override readonly typeName: string;
|
|
1268
|
+
static override readonly props: PropSchema;
|
|
1269
|
+
/** [width, depth] extent in meters, centered on the node. */
|
|
1270
|
+
size: number[];
|
|
1271
|
+
/** Height scale (realized heights land at ~3.5–8× this — see class docs). */
|
|
1272
|
+
maxHeight: number;
|
|
1273
|
+
/** Integer seed — the same seed grows the identical terrain everywhere. */
|
|
1274
|
+
seed: number;
|
|
1275
|
+
/** Grid segments per side (2–128). 128 ≈ 16k vertices, built once. */
|
|
1276
|
+
resolution: number;
|
|
1277
|
+
/** 'island' | 'alpine' | 'plains' | 'desert' | 'custom'. */
|
|
1278
|
+
theme: string;
|
|
1279
|
+
/** Detail-octave persistence — higher = more rugged. */
|
|
1280
|
+
roughness: number;
|
|
1281
|
+
/** Detail octave count. */
|
|
1282
|
+
detail: number;
|
|
1283
|
+
/** Snap-flatten a plateau within ±0.6 m of maxHeight·flatThreshold. */
|
|
1284
|
+
flatThreshold: number;
|
|
1285
|
+
/** Wrap edges down into an island rim. null = theme decides (island: on). */
|
|
1286
|
+
islandEdge: boolean | null;
|
|
1287
|
+
/** Custom splat layers (theme 'custom' only, 1–4 entries). */
|
|
1288
|
+
layers: TerrainLayer[];
|
|
1289
|
+
/** CDN base for theme textures: `<base>/<name>.png` (+`_normal`). */
|
|
1290
|
+
textureBase: string;
|
|
1291
|
+
/** Lake/pond bowls carved into the surface — smooth radial depressions the
|
|
1292
|
+
* collider + heightAt + dressing all see (place a Water3D in each). */
|
|
1293
|
+
basins: BasinSpec[];
|
|
1294
|
+
/** Loader hook: bad themes/layers/grids fail at LOAD, not at render. */
|
|
1295
|
+
static validateJson(node: Node): void;
|
|
1296
|
+
private heightmap;
|
|
1297
|
+
private heightmapKey;
|
|
1298
|
+
private builtKey;
|
|
1299
|
+
private loadedTextures;
|
|
1300
|
+
protected override _createObject3D(): Object3D;
|
|
1301
|
+
/**
|
|
1302
|
+
* Terrain surface height (world y) at WORLD x/z — works headless, before
|
|
1303
|
+
* any render. Ancestor transforms are translation-only (the same contract
|
|
1304
|
+
* physics bodies follow).
|
|
1305
|
+
*/
|
|
1306
|
+
heightAt(x: number, z: number): number;
|
|
1307
|
+
/** @internal The pure heightfield (lazily built — physics pulls this). */
|
|
1308
|
+
_heightmap(): Heightmap;
|
|
1309
|
+
/** The splat layers in effect — theme preset, or `layers` for 'custom'. */
|
|
1310
|
+
resolvedLayers(): TerrainLayer[];
|
|
1311
|
+
override _syncObject3D(): void;
|
|
1312
|
+
private effectiveIslandEdge;
|
|
1313
|
+
private segments;
|
|
1314
|
+
/** Rebuild geometry+material only when a terrain-shaping prop changed. */
|
|
1315
|
+
private rebuildIfNeeded;
|
|
1316
|
+
private buildGeometry;
|
|
1317
|
+
private buildMaterial;
|
|
1318
|
+
private disposeTextures;
|
|
1319
|
+
override free(): void;
|
|
1320
|
+
}
|
|
1321
|
+
//#endregion
|
|
1322
|
+
//#region src/3d/vegetation/tree-blueprint.d.ts
|
|
1323
|
+
/**
|
|
1324
|
+
* Tree3D construction recipes — the pure half of the procedural trees.
|
|
1325
|
+
* A (tier, type) pair maps to exact segment/layer/branch counts; the
|
|
1326
|
+
* geometry builder consumes these so tests can pin the numbers without
|
|
1327
|
+
* touching three.
|
|
1328
|
+
*/
|
|
1329
|
+
type TreeTier = "simple" | "medium" | "high";
|
|
1330
|
+
type TreeType = "conifer" | "broadleaf" | "dead" | "bush";
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region src/3d/nodes/tree-3d.d.ts
|
|
1333
|
+
interface TreeVariant {
|
|
1334
|
+
branches: InstancedMesh;
|
|
1335
|
+
/** null for dead trees (leafless recipes). */
|
|
1336
|
+
leaves: InstancedMesh | null;
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Procedural trees grown by the ported ez-tree generator (recursive gnarled
|
|
1340
|
+
* branches + textured leaf billboards) — one node is a whole grove in ≤6
|
|
1341
|
+
* draw calls: up to 3 seed VARIANTS, each one branches-InstancedMesh plus
|
|
1342
|
+
* one leaves-InstancedMesh, with `count` instances scattered over an XZ
|
|
1343
|
+
* `area` and distributed among the variants.
|
|
1344
|
+
*
|
|
1345
|
+
* `type` picks the recipe family: 'conifer' → pine whorls, 'broadleaf' →
|
|
1346
|
+
* oak/ash/aspen rotation, 'dead' → bare branches, no leaves, 'bush' → the
|
|
1347
|
+
* ported bush_1 shrub (undergrowth — pair with height 2–4). `tier` picks
|
|
1348
|
+
* the cost: 'simple' keeps the original primitive low-poly trees (cheapest,
|
|
1349
|
+
* unchanged), 'medium' grows the light `*-forest` presets (≤3k tris/tree —
|
|
1350
|
+
* forests), 'high' the full presets (8–20k tris/tree — hero trees).
|
|
1351
|
+
* validateJson enforces count × tris/tree ≤ 1.5M at LOAD time.
|
|
1352
|
+
*
|
|
1353
|
+
* Everything is deterministic from the `seed` PROP (never the engine rng):
|
|
1354
|
+
* the same scene JSON grows the identical grove on every machine. Leaves are
|
|
1355
|
+
* alpha-cutout quads (MeshStandardMaterial) sampling the packaged ez-tree
|
|
1356
|
+
* textures, riding a ported simplex-wind vertex sway on medium+.
|
|
1357
|
+
*/
|
|
1358
|
+
declare class Tree3D extends Node3D {
|
|
1359
|
+
static override readonly typeName: string;
|
|
1360
|
+
static override readonly props: PropSchema;
|
|
1361
|
+
/** 'simple' (primitive low-poly) | 'medium' (light ez-trees) | 'high' (full). */
|
|
1362
|
+
tier: string;
|
|
1363
|
+
/** 'conifer' | 'broadleaf' | 'dead' | 'bush' — the recipe family. */
|
|
1364
|
+
type: string;
|
|
1365
|
+
/** Construction seed — identical grove across runs and machines. */
|
|
1366
|
+
seed: number;
|
|
1367
|
+
/** Tree height in meters (each instance jitters ±20%). */
|
|
1368
|
+
height: number;
|
|
1369
|
+
/** Instances — count > 1 turns the node into a forest patch. */
|
|
1370
|
+
count: number;
|
|
1371
|
+
/** [x, z] scatter extent in meters when count > 1, centered on the node. */
|
|
1372
|
+
area: number[];
|
|
1373
|
+
trunkColor: string;
|
|
1374
|
+
/** Leaf/canopy tint — hue is kept, value is normalized over leaf textures. */
|
|
1375
|
+
canopyColor: string;
|
|
1376
|
+
/** Override the leaf texture URL ('' = packaged per-type ez-tree texture). */
|
|
1377
|
+
leafTexture: string;
|
|
1378
|
+
/** Leaf LOD: leaf cards begin collapsing toward their branch at this camera
|
|
1379
|
+
* distance (m). 0 (with leafFadeEnd 0) disables LOD — full leaves at any range. */
|
|
1380
|
+
leafFadeStart: number;
|
|
1381
|
+
/** Leaf LOD: cards are fully collapsed (zero overdraw) by here — tune near the
|
|
1382
|
+
* scene fog far so the thinning hides in fog. leafFadeEnd > leafFadeStart to enable. */
|
|
1383
|
+
leafFadeEnd: number;
|
|
1384
|
+
/** Let leaves cast shadows. false keeps trunk/branch shadows but drops the
|
|
1385
|
+
* expensive alpha-cutout leaf shadow pass (the dappled floor) for big FPS. */
|
|
1386
|
+
leafShadows: boolean;
|
|
1387
|
+
/** Drape the grove over a Terrain3D: each scattered instance roots at the
|
|
1388
|
+
* ground height under it (not the node's single Y), so a patch on rolling or
|
|
1389
|
+
* carved terrain never floats/buries. No effect on a count:1 tree placed by
|
|
1390
|
+
* hand unless it sits off the ground. */
|
|
1391
|
+
drape: boolean;
|
|
1392
|
+
/** Node path to the Terrain3D to drape onto ('' = auto-find the first one). */
|
|
1393
|
+
terrain: string;
|
|
1394
|
+
/** Resolved drape target + the grove's world position, set per rebuild. */
|
|
1395
|
+
private drapeTerrain;
|
|
1396
|
+
private drapeWorld;
|
|
1397
|
+
/** Loader hook: bad tiers/types/counts/budgets fail at LOAD, not at render. */
|
|
1398
|
+
static validateJson(node: Node): void;
|
|
1399
|
+
private time;
|
|
1400
|
+
private grove;
|
|
1401
|
+
private variants;
|
|
1402
|
+
private simpleTrunk;
|
|
1403
|
+
private simpleCanopy;
|
|
1404
|
+
private swayUniform;
|
|
1405
|
+
private leafFadeUniform;
|
|
1406
|
+
private loadedTextures;
|
|
1407
|
+
private groveKey;
|
|
1408
|
+
protected override _createObject3D(): Object3D;
|
|
1409
|
+
/** @internal First branches/trunk InstancedMesh (tests reach in here). */
|
|
1410
|
+
_trunk(): InstancedMesh;
|
|
1411
|
+
/** @internal First canopy/leaves InstancedMesh — null for dead trees. */
|
|
1412
|
+
_canopy(): InstancedMesh | null;
|
|
1413
|
+
/** @internal Every grown variant (medium/high tiers; empty for 'simple'). */
|
|
1414
|
+
_variantMeshes(): TreeVariant[];
|
|
1415
|
+
override update(dt: number): void;
|
|
1416
|
+
override _syncObject3D(): void;
|
|
1417
|
+
/** Re-grow only when a grove-shaping prop changed (the JSON IS the grove). */
|
|
1418
|
+
private rebuildIfNeeded;
|
|
1419
|
+
/** Resolve the drape target + cache the grove's world position for `bedY`. */
|
|
1420
|
+
private resolveDrape;
|
|
1421
|
+
/** Grove-local Y that roots an instance on the draped terrain (0 = flat). */
|
|
1422
|
+
private bedY;
|
|
1423
|
+
/** The original primitive trees — the cheap path, byte-for-byte unchanged. */
|
|
1424
|
+
private buildSimpleGrove;
|
|
1425
|
+
/** The ported ez-tree grove: K seed variants, instances dealt round-robin. */
|
|
1426
|
+
private buildEzGrove;
|
|
1427
|
+
/**
|
|
1428
|
+
* Trunk/branch dressing. Dead trees stay barkless weathered gray (flat
|
|
1429
|
+
* `trunkColor` — bark long gone from a snag); live medium/high trees
|
|
1430
|
+
* sample the packaged ez-tree bark set (color + normal + roughness, 1k)
|
|
1431
|
+
* tiled by the upstream per-preset repeat over the generator's ring UVs —
|
|
1432
|
+
* ridged bark up close instead of smooth plastic. The default `trunkColor`
|
|
1433
|
+
* hands the tint to the upstream preset (the maps carry the color); a
|
|
1434
|
+
* customized prop keeps its hue max-channel-normalized so it TINTS the map
|
|
1435
|
+
* instead of multiplying brown×brown into mud.
|
|
1436
|
+
*/
|
|
1437
|
+
private buildBarkMaterial;
|
|
1438
|
+
private loadBarkTexture;
|
|
1439
|
+
/** Per-instance placement + jitter — ONE rng draw order for both paths. */
|
|
1440
|
+
private scatter;
|
|
1441
|
+
private disposeMeshes;
|
|
1442
|
+
override free(): void;
|
|
1443
|
+
}
|
|
1444
|
+
//#endregion
|
|
1445
|
+
//#region src/3d/nodes/voxel-grid-3d.d.ts
|
|
1446
|
+
/**
|
|
1447
|
+
* Face colors per tile (front,right,back,left,top,bottom) — the agent8
|
|
1448
|
+
* minecraft template's exact 26-entry palette (tile 1 = grass, 2 = dirt,
|
|
1449
|
+
* 5 = bedrock, …). Override per-scene via the `palette` prop.
|
|
1450
|
+
*/
|
|
1451
|
+
declare const VOXEL_PALETTE: number[][][];
|
|
1452
|
+
interface VoxelBlock {
|
|
1453
|
+
x: number;
|
|
1454
|
+
y: number;
|
|
1455
|
+
z: number;
|
|
1456
|
+
tile: number;
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* A whole voxel terrain in ONE draw call: an InstancedMesh of unit cubes
|
|
1460
|
+
* with per-face instanced colors, simple directional shading and darkened
|
|
1461
|
+
* edges — the minecraft-template look. Blocks are runtime data
|
|
1462
|
+
* (`setBlocks`/`addBlock`) or baked scene JSON via the `voxels` prop
|
|
1463
|
+
* (`[[x,y,z,tile], …]` — what `incanto-env terrain` emits), applied on the
|
|
1464
|
+
* next update; runtime edits take over from there. Colliders are the
|
|
1465
|
+
* GAME's job (chunked trimeshes near the player — see the minecraft
|
|
1466
|
+
* template's Terrain behavior).
|
|
1467
|
+
*/
|
|
1468
|
+
declare class VoxelGrid3D extends Node3D {
|
|
1469
|
+
static override readonly typeName: string;
|
|
1470
|
+
static override readonly signals: readonly string[];
|
|
1471
|
+
static override readonly props: PropSchema;
|
|
1472
|
+
/** JSON-authored blocks as [x, y, z, tile] tuples (generated terrains bake these). */
|
|
1473
|
+
voxels: number[][];
|
|
1474
|
+
private appliedVoxels;
|
|
1475
|
+
private readonly map;
|
|
1476
|
+
private mesh;
|
|
1477
|
+
private dirty;
|
|
1478
|
+
blockCount(): number;
|
|
1479
|
+
tileAt(x: number, y: number, z: number): number | undefined;
|
|
1480
|
+
setBlocks(blocks: VoxelBlock[]): void;
|
|
1481
|
+
addBlock(b: VoxelBlock): void;
|
|
1482
|
+
removeBlockAt(x: number, y: number, z: number): boolean;
|
|
1483
|
+
/** All blocks (for chunk collider builders). */
|
|
1484
|
+
blocks(): VoxelBlock[];
|
|
1485
|
+
protected override _createObject3D(): Object3D;
|
|
1486
|
+
override update(): void;
|
|
1487
|
+
}
|
|
1488
|
+
//#endregion
|
|
1489
|
+
//#region src/3d/water/interaction.d.ts
|
|
1490
|
+
/**
|
|
1491
|
+
* Water3D character interaction — pure functions, no three, node-env testable.
|
|
1492
|
+
*
|
|
1493
|
+
* The node samples physics bodies each update, detects waterline crossings
|
|
1494
|
+
* (`detectCrossings`), and converts them into ripple impulses in a FIXED-SIZE
|
|
1495
|
+
* ring buffer that mirrors the shader's `uRipples[8]` uniform array
|
|
1496
|
+
* (`pushRipple` / `ageRipples`). `rippleHeight` is the exact ring-wave shape
|
|
1497
|
+
* the vertex shader evaluates — `quality: 'simple'` runs it on the CPU.
|
|
1498
|
+
*/
|
|
1499
|
+
/** Ring-buffer capacity — MUST match `uRipples[8]` in the water shaders. */
|
|
1500
|
+
declare const WATER_MAX_RIPPLES = 8;
|
|
1501
|
+
/** One expanding ring wave, in world XZ. */
|
|
1502
|
+
interface Ripple {
|
|
1503
|
+
x: number;
|
|
1504
|
+
z: number;
|
|
1505
|
+
/** Seconds since impact (real time — waveSpeed does not stretch ripples). */
|
|
1506
|
+
age: number;
|
|
1507
|
+
/** Impulse amplitude in meters. */
|
|
1508
|
+
amp: number;
|
|
1509
|
+
/** Splash-foam strength 0..1 the shaders paint as surface froth (omitted = 0). */
|
|
1510
|
+
foam?: number;
|
|
1511
|
+
}
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/3d/nodes/water-3d.d.ts
|
|
1514
|
+
/**
|
|
1515
|
+
* A water surface on XZ. `quality: 'fancy'` (default) is a commercial-grade
|
|
1516
|
+
* water shader — 9-iteration simplex FBM displacement with analytic normals,
|
|
1517
|
+
* two animated procedural detail-normal layers (`detailStrength`), a sun
|
|
1518
|
+
* specular glint+sheen (`sunDirection`/`sunColor`/`sunIntensity`), Beer's-law
|
|
1519
|
+
* depth `absorption` (clear shallows → dark depths), screen-space
|
|
1520
|
+
* `refraction` of the submerged scene, depth-texture shoreline `foam` (on by
|
|
1521
|
+
* default), a trough/surface/peak color ramp and fresnel-mixed CubeCamera
|
|
1522
|
+
* reflections (re-rendered every `reflectionInterval` ms). `quality:
|
|
1523
|
+
* 'simple'` is the cheap CPU sine-wave material (low-end fallback; also the
|
|
1524
|
+
* deterministic headless path).
|
|
1525
|
+
*
|
|
1526
|
+
* Per-frame GPU budget for fancy (beyond drawing the surface): ONE half-res
|
|
1527
|
+
* scene pre-pass that feeds depth (absorption/foam/soft intersections) AND
|
|
1528
|
+
* the refraction color grab; the cube reflection stays on its `1000 ms`
|
|
1529
|
+
* throttle.
|
|
1530
|
+
*
|
|
1531
|
+
* Character interaction (`interaction: true`): each update the node scans the
|
|
1532
|
+
* tree for CharacterBody3D/RigidBody3D, compares their world y against the
|
|
1533
|
+
* waterline at their xz (wave offset included, CPU twin of the shader), emits
|
|
1534
|
+
* `entered`/`exited` with the body node, and pushes ripple impulses into a
|
|
1535
|
+
* fixed 8-slot ring buffer both shaders turn into expanding ring waves. The
|
|
1536
|
+
* surface has no bottom — anything below the waterline inside the XZ
|
|
1537
|
+
* footprint counts as in-water (pair with `Area3D` for volumetric gameplay).
|
|
1538
|
+
*
|
|
1539
|
+
* GPU-only features (reflection/foam) run through the renderer's
|
|
1540
|
+
* `_onRender3D` hand-off — headless runs simply never invoke it.
|
|
1541
|
+
*/
|
|
1542
|
+
declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
1543
|
+
static override readonly typeName: string;
|
|
1544
|
+
static override readonly signals: readonly string[];
|
|
1545
|
+
static override readonly props: PropSchema;
|
|
1546
|
+
/** [width, depth] in meters (the surface lies on XZ). */
|
|
1547
|
+
size: number[];
|
|
1548
|
+
/** Legacy single hue: simple-quality color; tints the fancy ramp when set. */
|
|
1549
|
+
color: string;
|
|
1550
|
+
opacity: number;
|
|
1551
|
+
/** Draw after opaque geometry (overrides Node3D's 0 default). */
|
|
1552
|
+
override renderOrder: number;
|
|
1553
|
+
/** Wave intensity dial, meters-ish. 0 = mirror-flat; 0.04 (default) reads
|
|
1554
|
+
* as a calm sea; 0.08 = the noisier ported-source look. */
|
|
1555
|
+
waveHeight: number;
|
|
1556
|
+
waveSpeed: number;
|
|
1557
|
+
/** 'fancy' shader water (default) | 'simple' cheap CPU sine material. */
|
|
1558
|
+
quality: string;
|
|
1559
|
+
/** Fancy ramp overrides: `{trough?, surface?, peak?}` hex strings. */
|
|
1560
|
+
colors: JsonObject;
|
|
1561
|
+
/** CubeCamera reflections (fancy only; needs a live renderer). */
|
|
1562
|
+
reflection: boolean;
|
|
1563
|
+
/** ms between reflection (and foam depth) re-renders. */
|
|
1564
|
+
reflectionInterval: number;
|
|
1565
|
+
/** Depth-texture shoreline foam — rides the always-on fancy scene pass. */
|
|
1566
|
+
foam: boolean;
|
|
1567
|
+
/** Bodies crossing the surface emit signals and raise ripples. */
|
|
1568
|
+
interaction: boolean;
|
|
1569
|
+
/** Splash WHITEWATER: the shaders paint expanding surface foam where bodies
|
|
1570
|
+
* interact — a "풍덩" froth bloom on entry (scaled by fall speed) and a foam
|
|
1571
|
+
* trail ("물살") behind a body wading/running through. Needs `interaction`. */
|
|
1572
|
+
splash: boolean;
|
|
1573
|
+
/** Fixed sun for the specular glint — match the scene's key light. */
|
|
1574
|
+
sunDirection: number[];
|
|
1575
|
+
/** Sun glint/sheen tint. */
|
|
1576
|
+
sunColor: string;
|
|
1577
|
+
/** Sun glint/sheen strength — 0 disables the specular entirely. */
|
|
1578
|
+
sunIntensity: number;
|
|
1579
|
+
/** Animated detail-normal strength — the close-up liquid feel (0 = off). */
|
|
1580
|
+
detailStrength: number;
|
|
1581
|
+
/** Beer's-law absorption per meter of water depth (0 = no depth tinting).
|
|
1582
|
+
* Per-channel under the hood: red dies ~3× faster than blue, so shallows
|
|
1583
|
+
* over a bright bottom read turquoise before fading to deep blue. */
|
|
1584
|
+
absorption: number;
|
|
1585
|
+
/** Screen-space refraction of the submerged scene (fancy only). */
|
|
1586
|
+
refraction: boolean;
|
|
1587
|
+
/**
|
|
1588
|
+
* Submerged-camera look: when the camera dips below this surface (inside its
|
|
1589
|
+
* XZ footprint) the scene switches to an underwater fog + tint for that frame
|
|
1590
|
+
* — the murky, limited-visibility feel of being underwater. `true` (default)
|
|
1591
|
+
* derives the murk from `color`; `false` disables it; an object
|
|
1592
|
+
* `{ color?, visibility? }` overrides the fog hue / view distance (meters).
|
|
1593
|
+
*/
|
|
1594
|
+
underwater: boolean | JsonObject;
|
|
1595
|
+
/** Loader hook: degenerate planes and bad enums fail at LOAD, not at render. */
|
|
1596
|
+
static validateJson(node: Node): void;
|
|
1597
|
+
private time;
|
|
1598
|
+
private surfaceKey;
|
|
1599
|
+
/** @internal Live ripple ring buffer (≤8) — tests and tools read this. */
|
|
1600
|
+
readonly _ripples: Ripple[];
|
|
1601
|
+
private inWater;
|
|
1602
|
+
private readonly bobTimers;
|
|
1603
|
+
/** Last position a wake ripple was dropped, per in-water body (for the trail). */
|
|
1604
|
+
private readonly wadeAnchors;
|
|
1605
|
+
private readonly _scratchA;
|
|
1606
|
+
private readonly _scratchB;
|
|
1607
|
+
private readonly _bodies;
|
|
1608
|
+
private cubeTarget;
|
|
1609
|
+
private cubeCamera;
|
|
1610
|
+
/** Reflection near plane (max half extent) — recreate the camera on resize. */
|
|
1611
|
+
private reflectionNear;
|
|
1612
|
+
private lastReflectionAt;
|
|
1613
|
+
/** Per-frame scene pre-pass: depth (absorption/foam) + color grab (refraction). */
|
|
1614
|
+
private scenePassTarget;
|
|
1615
|
+
protected override _createObject3D(): Object3D;
|
|
1616
|
+
override update(dt: number): void;
|
|
1617
|
+
override _syncObject3D(): void;
|
|
1618
|
+
/**
|
|
1619
|
+
* @internal Renderer hand-off (see {@link RenderContext3D}): re-render the
|
|
1620
|
+
* CubeCamera reflection every `reflectionInterval` ms, plus ONE per-frame
|
|
1621
|
+
* half-res scene pre-pass whose depth texture drives Beer's-law absorption,
|
|
1622
|
+
* shoreline foam and soft intersections, and whose color texture is the
|
|
1623
|
+
* refraction grab — one extra render feeds all three.
|
|
1624
|
+
*/
|
|
1625
|
+
_onRender3D(ctx: RenderContext3D): void;
|
|
1626
|
+
/**
|
|
1627
|
+
* Camera-submersion query (the renderer asks every water node each frame): the
|
|
1628
|
+
* resolved underwater look + this surface's Y when the camera EYE is below
|
|
1629
|
+
* THIS surface inside its XZ footprint, else null. Deliberately gated to the
|
|
1630
|
+
* eye being underwater (not merely near the surface) so wading with the camera
|
|
1631
|
+
* above the water never tints the view. Cheap above water (geometric test first).
|
|
1632
|
+
*/
|
|
1633
|
+
underwaterAt(camX: number, camY: number, camZ: number): (UnderwaterConfig & {
|
|
1634
|
+
surfaceY: number;
|
|
1635
|
+
}) | null;
|
|
1636
|
+
override free(): void;
|
|
1637
|
+
/** Swap geometry+material only when a surface-shaping prop changed. */
|
|
1638
|
+
private rebuildSurfaceIfNeeded;
|
|
1639
|
+
private buildFancyMaterial;
|
|
1640
|
+
/** The cheap lake ShaderMaterial — fresnel sky tint + ripple normals + sun
|
|
1641
|
+
* glint + edge fade, NO scene re-renders (see lake.ts). */
|
|
1642
|
+
private buildLakeMaterial;
|
|
1643
|
+
private syncFancy;
|
|
1644
|
+
/**
|
|
1645
|
+
* @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
|
|
1646
|
+
* sky's sun drives the glint ONLY while the `sunDirection` prop sits at its
|
|
1647
|
+
* schema default — an explicitly authored prop always wins. Runs right
|
|
1648
|
+
* after `_syncObject3D` (which wrote the prop value), overwriting the
|
|
1649
|
+
* uniform for this frame.
|
|
1650
|
+
*/
|
|
1651
|
+
_applySunDirection(dir: readonly [number, number, number]): void;
|
|
1652
|
+
/** The fancy three-color ramp; a customized legacy `color` tints it. */
|
|
1653
|
+
private resolvePalette;
|
|
1654
|
+
private syncSimple;
|
|
1655
|
+
private stepInteraction;
|
|
1656
|
+
/** Base wave height at world (x, z) — the CPU twin of the active shader.
|
|
1657
|
+
* Ripples are excluded on purpose: a splash must not re-trigger itself. */
|
|
1658
|
+
private waveOffsetAt;
|
|
1659
|
+
}
|
|
1660
|
+
//#endregion
|
|
1661
|
+
//#region src/3d/register.d.ts
|
|
1662
|
+
/**
|
|
1663
|
+
* Register the 3D node taxonomy (and the core nodes). Call once in your game
|
|
1664
|
+
* entry before loading a 3D scene. Explicit — never an import side effect.
|
|
1665
|
+
*/
|
|
1666
|
+
declare function registerNodes3D(): void;
|
|
1667
|
+
//#endregion
|
|
1668
|
+
//#region src/3d/renderer.d.ts
|
|
1669
|
+
interface Renderer3DOptions {
|
|
1670
|
+
canvas: HTMLCanvasElement;
|
|
1671
|
+
engine: Engine;
|
|
1672
|
+
/** Bring your own model store (custom loader) — defaults to a GLTF+VRM one. */
|
|
1673
|
+
assets?: AssetStore3D;
|
|
1674
|
+
/** Defaults to `window.devicePixelRatio` capped at 2. */
|
|
1675
|
+
pixelRatio?: number;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* WebGL presentation layer: subscribes to `engine.updated`, mirrors the active
|
|
1679
|
+
* scene's node tree onto a three.js scene, applies the scene `environment`
|
|
1680
|
+
* header (ambient/background/sky/fog/shadows/exposure — see Environment3D),
|
|
1681
|
+
* and renders with the current Camera3D under ACES filmic tone mapping.
|
|
1682
|
+
*
|
|
1683
|
+
* Keep this class thin — everything testable lives in `syncTree`, the node
|
|
1684
|
+
* classes and `Environment3D`; this file is the only place that touches WebGL.
|
|
1685
|
+
*/
|
|
1686
|
+
declare class Renderer3D {
|
|
1687
|
+
/**
|
|
1688
|
+
* When set, the render uses THIS free camera instead of the scene's active
|
|
1689
|
+
* Camera3D — tooling (the scene editor) orbits/pans without touching scene
|
|
1690
|
+
* data. `null` restores normal camera behavior.
|
|
1691
|
+
*/
|
|
1692
|
+
viewOverride: {
|
|
1693
|
+
position: [number, number, number];
|
|
1694
|
+
target: [number, number, number];
|
|
1695
|
+
} | null;
|
|
1696
|
+
private readonly overrideCam;
|
|
1697
|
+
private lastCamera;
|
|
1698
|
+
private lastSize;
|
|
1699
|
+
private readonly webgl;
|
|
1700
|
+
private readonly threeScene;
|
|
1701
|
+
private readonly environment;
|
|
1702
|
+
private readonly engine;
|
|
1703
|
+
private readonly disconnect;
|
|
1704
|
+
private readonly canvas;
|
|
1705
|
+
private readonly assets;
|
|
1706
|
+
private readonly ownsAssets;
|
|
1707
|
+
private readonly loadedAssetScenes;
|
|
1708
|
+
/** The scene whose GPU programs have been pre-compiled (warm-up, see render). */
|
|
1709
|
+
private compiledScene;
|
|
1710
|
+
/** Reused per-frame walk scratch (instance-scoped — never a module global). */
|
|
1711
|
+
private readonly syncScratch;
|
|
1712
|
+
/** Reused render-hook context — gl/scene are stable, camera reassigned/frame. */
|
|
1713
|
+
private renderCtx;
|
|
1714
|
+
/** Underwater caustics pass (lazy — only created the first time submerged). */
|
|
1715
|
+
private causticsTarget;
|
|
1716
|
+
private causticsScene;
|
|
1717
|
+
private causticsUniforms;
|
|
1718
|
+
/** Volumetric cloud pass (lazy — only created when environment.clouds is set).
|
|
1719
|
+
* The raymarch runs at HALF resolution into `cloudsHalfTarget`, then a cheap
|
|
1720
|
+
* full-res composite blends it over the scene — keeps the heavy fullscreen
|
|
1721
|
+
* raymarch off the critical path so it holds 60fps at retina. */
|
|
1722
|
+
private cloudsTarget;
|
|
1723
|
+
private cloudsHalfTarget;
|
|
1724
|
+
private cloudsBlurTarget;
|
|
1725
|
+
private cloudsScene;
|
|
1726
|
+
private cloudsUniforms;
|
|
1727
|
+
private cloudsBlurScene;
|
|
1728
|
+
private cloudsBlurUniforms;
|
|
1729
|
+
private cloudsCompositeScene;
|
|
1730
|
+
private cloudsCompositeUniforms;
|
|
1731
|
+
constructor(opts: Renderer3DOptions);
|
|
1732
|
+
private readonly debugLines;
|
|
1733
|
+
private syncDebugLines;
|
|
1734
|
+
private render;
|
|
1735
|
+
/**
|
|
1736
|
+
* Volumetric cloud pass (only when `environment.clouds` is declared — scenes
|
|
1737
|
+
* without it render exactly as before, zero cost). Three steps:
|
|
1738
|
+
* 1. scene → full-res offscreen target (color + depth)
|
|
1739
|
+
* 2. raymarch the cloud slab → HALF-res target (the heavy fullscreen work,
|
|
1740
|
+
* run at ¼ the pixels so it holds 60fps even at retina)
|
|
1741
|
+
* 3. composite the half-res cloud buffer over the full-res scene → screen
|
|
1742
|
+
*/
|
|
1743
|
+
private renderWithClouds;
|
|
1744
|
+
/**
|
|
1745
|
+
* Underwater caustics pass (only while submerged — normal rendering never
|
|
1746
|
+
* touches this). Render the scene to an offscreen color+depth target, then a
|
|
1747
|
+
* fullscreen composite that reconstructs each pixel's WORLD position from depth
|
|
1748
|
+
* and adds an animated caustic highlight below the water line. World-keyed, so
|
|
1749
|
+
* the pattern sticks to the floor/props instead of being a flat screen overlay.
|
|
1750
|
+
*/
|
|
1751
|
+
private renderWithCaustics;
|
|
1752
|
+
/**
|
|
1753
|
+
* Project a world point through the LAST rendered camera to canvas CSS px.
|
|
1754
|
+
* `behind` is true when the point sits behind the camera (don't draw it).
|
|
1755
|
+
*/
|
|
1756
|
+
screenFromWorld(wx: number, wy: number, wz: number): {
|
|
1757
|
+
x: number;
|
|
1758
|
+
y: number;
|
|
1759
|
+
behind: boolean;
|
|
1760
|
+
};
|
|
1761
|
+
/**
|
|
1762
|
+
* The node rendered under canvas pixel (sx, sy), or null — raycasts the last
|
|
1763
|
+
* rendered camera into the scene and resolves the hit up to its `incantoNode`
|
|
1764
|
+
* (every Node3D stamps that in `_ensureObject3D`). 3D depth-sorts, so the
|
|
1765
|
+
* nearest hit wins. Mirrors `Renderer2D.pick` for click-to-select in the editor.
|
|
1766
|
+
*/
|
|
1767
|
+
pick(sx: number, sy: number): Node | null;
|
|
1768
|
+
/**
|
|
1769
|
+
* GPU counters for the LAST rendered frame, straight from three's
|
|
1770
|
+
* per-frame-maintained `renderer.info` (render pass + GPU memory).
|
|
1771
|
+
*/
|
|
1772
|
+
stats(): RendererStats;
|
|
1773
|
+
/** The last rendered camera's world-space basis (for screen-plane dragging). */
|
|
1774
|
+
cameraBasis(): {
|
|
1775
|
+
right: Vector3;
|
|
1776
|
+
up: Vector3;
|
|
1777
|
+
forward: Vector3;
|
|
1778
|
+
};
|
|
1779
|
+
dispose(): void;
|
|
1780
|
+
}
|
|
1781
|
+
//#endregion
|
|
1782
|
+
//#region src/3d/sync.d.ts
|
|
1783
|
+
/** A node that accepts a per-frame spatial pose (AudioPlayer with spatial on). */
|
|
1784
|
+
interface SpatialConsumer {
|
|
1785
|
+
spatial: boolean;
|
|
1786
|
+
_setSpatialPose(pose: SpatialPose): void;
|
|
1787
|
+
}
|
|
1788
|
+
interface SyncResult {
|
|
1789
|
+
activeCamera: PerspectiveCamera | null;
|
|
1790
|
+
/**
|
|
1791
|
+
* Nodes that declared `_onRender3D`, in tree order — Renderer3D invokes
|
|
1792
|
+
* `node._onRender3D(ctx)` with the live WebGL context right before the main
|
|
1793
|
+
* render. Node refs (not bound closures) so the per-frame walk allocates
|
|
1794
|
+
* nothing; the renderer owns and reuses the context object.
|
|
1795
|
+
*/
|
|
1796
|
+
renderHooks: RenderHook3D[];
|
|
1797
|
+
/**
|
|
1798
|
+
* The scene's MAIN directional light (highest intensity, tree order breaks
|
|
1799
|
+
* ties) — the environment sky aims it and `environment.shadows` makes it
|
|
1800
|
+
* cast. null when the scene declares none (never auto-created).
|
|
1801
|
+
*/
|
|
1802
|
+
sunLight: DirectionalLight3D | null;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Per-frame walk scratch — the caller (Renderer3D) owns ONE and reuses it every
|
|
1806
|
+
* frame so `syncTree` allocates nothing for its containers. Instance-scoped (a
|
|
1807
|
+
* field on the renderer), never a module global. Omit it and `syncTree` makes a
|
|
1808
|
+
* throwaway one (the test path).
|
|
1809
|
+
*/
|
|
1810
|
+
interface SyncScratch {
|
|
1811
|
+
visited: Set<Object3D>;
|
|
1812
|
+
cameras: Camera3D[];
|
|
1813
|
+
renderHooks: RenderHook3D[];
|
|
1814
|
+
emitters: Array<{
|
|
1815
|
+
node: Node & SpatialConsumer;
|
|
1816
|
+
parent: Object3D;
|
|
1817
|
+
}>;
|
|
1818
|
+
state: WalkState;
|
|
1819
|
+
}
|
|
1820
|
+
interface SyncOptions {
|
|
1821
|
+
assets?: AssetStore3D;
|
|
1822
|
+
/**
|
|
1823
|
+
* The environment sky's unit sun direction. Pushed to every
|
|
1824
|
+
* `_applySunDirection` node (Water3D, Foliage3D) right after its own sync —
|
|
1825
|
+
* a per-frame cheap setter, chosen over sceneChanged-time wiring because the
|
|
1826
|
+
* walk already touches every node and uniforms are overwritten by node sync
|
|
1827
|
+
* each frame anyway. Nodes ignore it when their own prop was customized.
|
|
1828
|
+
*/
|
|
1829
|
+
sunDirection?: readonly [number, number, number] | null;
|
|
1830
|
+
/** Render interpolation factor (0..1) for fixed-step physics bodies; see
|
|
1831
|
+
* Engine.interpolationAlpha. Defaults to 1 (render the current transform). */
|
|
1832
|
+
alpha?: number;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Mirror an Incanto node tree onto a three.js scene (dirty-push, once per frame):
|
|
1836
|
+
*
|
|
1837
|
+
* - every Node3D's backing Object3D is parented under its nearest Node3D
|
|
1838
|
+
* ancestor's object (plain Nodes are transparent), falling back to the scene
|
|
1839
|
+
* - transforms/props are pushed via `_syncObject3D`
|
|
1840
|
+
* - backing objects whose nodes left the tree are pruned
|
|
1841
|
+
* - returns the active camera (`current: true` wins, else first in tree order)
|
|
1842
|
+
*
|
|
1843
|
+
* Pure scene-graph math — no WebGL — so it tests headlessly with real three.
|
|
1844
|
+
*/
|
|
1845
|
+
declare function syncTree(root: Node, threeScene: Scene, assets?: AssetStore3D, opts?: SyncOptions, scratch?: SyncScratch): SyncResult;
|
|
1846
|
+
interface WalkState {
|
|
1847
|
+
visited: Set<Object3D>;
|
|
1848
|
+
cameras: Camera3D[];
|
|
1849
|
+
renderHooks: RenderHook3D[];
|
|
1850
|
+
emitters: Array<{
|
|
1851
|
+
node: Node & SpatialConsumer;
|
|
1852
|
+
parent: Object3D;
|
|
1853
|
+
}>;
|
|
1854
|
+
assets: AssetStore3D | undefined;
|
|
1855
|
+
sunDirection: readonly [number, number, number] | null;
|
|
1856
|
+
sunLight: DirectionalLight3D | null;
|
|
1857
|
+
alpha: number;
|
|
1858
|
+
}
|
|
1859
|
+
//#endregion
|
|
1860
|
+
export { Area3D, AssetStore3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type Heightmap, type HeightmapOptions, 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, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainTheme, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, cameraRelative, createGame3D, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers };
|