incanto 0.25.4 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2d.js +1 -1
- package/dist/3d.d.ts +140 -2
- package/dist/3d.js +5 -5
- package/dist/{create-game-iYptFr-q.js → create-game-C0bPoURs.js} +1 -1
- package/dist/{create-game-DyAPwm0j.js → create-game-DUAtTqID.js} +3 -3
- package/dist/env.d.ts +6 -0
- package/dist/env.js +8 -5
- package/dist/{gameplay-BFzBVqhr.js → gameplay-Ddk13pQ6.js} +229 -2
- package/dist/gameplay.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{physics-3d-ob23XE0Y.js → physics-3d-4mGzOZ2J.js} +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-CmtGqGSS.js → register-DcHXS1MA.js} +426 -17
- package/dist/test.js +4 -4
- package/editor/assets/{agent8-DzaYTNiK.js → agent8-DElIPozC.js} +1 -1
- package/editor/assets/{index-BGtXxzJn.js → index-BrneVqN0.js} +145 -49
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +115 -0
- package/skills/incanto-environment.md +88 -9
- package/skills/incanto-node-reference.md +15 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
package/dist/2d.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-
|
|
2
|
+
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-C0bPoURs.js";
|
|
3
3
|
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-CvpSUU3O.js";
|
|
4
4
|
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-DiVFFlH3.js";
|
|
5
5
|
//#region src/2d/library-sprite.ts
|
package/dist/3d.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { $ as Node, C as RendererStats, S as GameStats, b as Scheduler, l as Pro
|
|
|
3
3
|
import { t as LoadSceneOptions } from "./loader-DILt9PGC.js";
|
|
4
4
|
import { t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
|
|
5
5
|
import { n as PathGrid, s as SpatialPose } from "./pathfinding-RWYkNKx9.js";
|
|
6
|
-
import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
|
|
6
|
+
import { AnimationClip, BufferGeometry, Color, DirectionalLight, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Vector3, WebGLRenderer } from "three";
|
|
7
7
|
import { VRM } from "@pixiv/three-vrm";
|
|
8
8
|
import { Sky } from "three/examples/jsm/objects/Sky.js";
|
|
9
9
|
import * as RapierNs from "@dimforge/rapier3d-compat";
|
|
@@ -1590,7 +1590,18 @@ interface PathSample {
|
|
|
1590
1590
|
}
|
|
1591
1591
|
/** A cross-section station: where the water is, how wide, how fast. */
|
|
1592
1592
|
interface RiverRing extends PathSample {
|
|
1593
|
+
/** The width the author ASKED for (half of it) — nominal, drives current. */
|
|
1593
1594
|
halfWidth: number;
|
|
1595
|
+
/** Wetted half-width to the left of the flow: where the ground rises to the
|
|
1596
|
+
* surface. This, not {@link halfWidth}, is where the water actually ends. */
|
|
1597
|
+
halfLeft: number;
|
|
1598
|
+
/** Wetted half-width to the right of the flow. */
|
|
1599
|
+
halfRight: number;
|
|
1600
|
+
/** No bank on that side: the ground never came up to the surface, so the
|
|
1601
|
+
* water is spilling out over it rather than being held. The ribbon thins to
|
|
1602
|
+
* a film there instead of ending in a wall of water standing on nothing. */
|
|
1603
|
+
spillLeft: boolean;
|
|
1604
|
+
spillRight: boolean;
|
|
1594
1605
|
/** Water surface height (local Y). */
|
|
1595
1606
|
surfaceY: number;
|
|
1596
1607
|
/** Downhill grade of the surface (dimensionless, ≥ 0). */
|
|
@@ -1706,6 +1717,18 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1706
1717
|
sunIntensity: number;
|
|
1707
1718
|
/** Drape target path; empty = auto-find the first Terrain3D in the tree. */
|
|
1708
1719
|
terrain: string;
|
|
1720
|
+
/**
|
|
1721
|
+
* Cut the bed. Real water arrives on a hillside and erodes itself a channel;
|
|
1722
|
+
* a ribbon laid on raw ground has nothing holding it and reads as a strip of
|
|
1723
|
+
* blue hovering over the grass. With this on, the river trenches its own
|
|
1724
|
+
* course into the terrain it drapes on — banks the player can walk into, a
|
|
1725
|
+
* collider and `heightAt` that agree with it, and splat that paints the cut.
|
|
1726
|
+
*
|
|
1727
|
+
* Off means you carve it yourself with `Terrain3D.channels` (or the reach is
|
|
1728
|
+
* genuinely a flat flood plain). Either way the surface is FITTED to
|
|
1729
|
+
* whatever ground it finds: it can never stand above its own banks.
|
|
1730
|
+
*/
|
|
1731
|
+
carve: boolean;
|
|
1709
1732
|
/** How hard the current sweeps bodies downstream (0 = visual only). */
|
|
1710
1733
|
flowForce: number;
|
|
1711
1734
|
/**
|
|
@@ -1721,13 +1744,22 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1721
1744
|
private sprayKey;
|
|
1722
1745
|
private courseKey;
|
|
1723
1746
|
private bedAt;
|
|
1747
|
+
/** Whether the last course was fitted to real ground (see buildRiverRings). */
|
|
1748
|
+
private fitted;
|
|
1749
|
+
/** Everything standing in the current, rescanned on a slow timer. */
|
|
1750
|
+
private rocks;
|
|
1751
|
+
private rockScanAt;
|
|
1724
1752
|
private world;
|
|
1725
1753
|
private readonly _bodies;
|
|
1726
1754
|
/** Loader hook: a malformed river fails at LOAD, not as an invisible ribbon. */
|
|
1727
1755
|
static validateJson(node: Node): void;
|
|
1728
1756
|
protected override _createObject3D(): Object3D;
|
|
1757
|
+
override onReady(): void;
|
|
1758
|
+
override onExitTree(): void;
|
|
1729
1759
|
override update(dt: number): void;
|
|
1730
1760
|
override _syncObject3D(): void;
|
|
1761
|
+
/** Rocks are picked per FRAME (nearest to the eye), scanned on a timer. */
|
|
1762
|
+
_onRender3D(ctx: RenderContext3D): void;
|
|
1731
1763
|
/**
|
|
1732
1764
|
* Where a WORLD point sits in the channel — the query gameplay scripts run
|
|
1733
1765
|
* (steer a raft, drown a torch, decide a ford is too fast to cross). `null`
|
|
@@ -1755,6 +1787,14 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1755
1787
|
_applySunDirection(dir: readonly [number, number, number]): void;
|
|
1756
1788
|
override free(): void;
|
|
1757
1789
|
private widthProfile;
|
|
1790
|
+
/**
|
|
1791
|
+
* Cut (or release) this river's trench in the terrain it drapes on.
|
|
1792
|
+
*
|
|
1793
|
+
* Runs at ready as well as on every course rebuild: the heightfield collider
|
|
1794
|
+
* is built before the first render sync, so a bed cut only at draw time
|
|
1795
|
+
* would leave the player walking on ground that is no longer there.
|
|
1796
|
+
*/
|
|
1797
|
+
private applyCarve;
|
|
1758
1798
|
/** Resolve the drape terrain; an explicit path that misses is a hard error. */
|
|
1759
1799
|
private resolveTerrain;
|
|
1760
1800
|
/**
|
|
@@ -1764,6 +1804,21 @@ declare class River3D extends Node3D implements SunConsumer3D {
|
|
|
1764
1804
|
private ensureCourse;
|
|
1765
1805
|
private rebuildIfNeeded;
|
|
1766
1806
|
private buildMaterial;
|
|
1807
|
+
/**
|
|
1808
|
+
* Find everything standing in the current.
|
|
1809
|
+
*
|
|
1810
|
+
* A boulder in a creek is not a physics problem — it is a thing the water
|
|
1811
|
+
* has to be TOLD about, or the stream runs through it as if it were painted
|
|
1812
|
+
* on. Any mesh whose footprint lands inside the wetted channel and whose top
|
|
1813
|
+
* reaches near the surface counts, `InstancedMesh3D` rows included (a rock
|
|
1814
|
+
* scatter is one node with fifty stones in it).
|
|
1815
|
+
*
|
|
1816
|
+
* Scanned on a slow timer rather than per frame: rocks do not move often,
|
|
1817
|
+
* and a river with a hundred of them still writes only the nearest few.
|
|
1818
|
+
*/
|
|
1819
|
+
private scanRocks;
|
|
1820
|
+
/** Write the nearest rocks into the shader, faded in by distance. */
|
|
1821
|
+
private syncRocks;
|
|
1767
1822
|
private syncUniforms;
|
|
1768
1823
|
/**
|
|
1769
1824
|
* Hang a mist plume on every fall. Runtime children (`__spray*`), rebuilt
|
|
@@ -1965,6 +2020,14 @@ declare class Terrain3D extends Node3D {
|
|
|
1965
2020
|
* bed to sit in, and the collider/heightAt/drape all see it. */
|
|
1966
2021
|
channels: ChannelSpec[];
|
|
1967
2022
|
/**
|
|
2023
|
+
* Trenches other NODES cut in this terrain — a `River3D` carving its own bed.
|
|
2024
|
+
* Keyed by the node that owns each, so a moving or re-pathed river replaces
|
|
2025
|
+
* its cut instead of stacking a new one. Runtime state, never serialized:
|
|
2026
|
+
* the scene file says "there is a river here", not "here is the ditch it
|
|
2027
|
+
* dug", and re-loading the scene digs it again.
|
|
2028
|
+
*/
|
|
2029
|
+
private readonly externalChannels;
|
|
2030
|
+
/**
|
|
1968
2031
|
* WET SAND band: `{ y, band? }` darkens + glosses the splat in a noisy,
|
|
1969
2032
|
* breathing band just above height `y` (a waterline) — the trace of the
|
|
1970
2033
|
* last swash runup. `band` is the max height above `y` that reads wet
|
|
@@ -1989,6 +2052,14 @@ declare class Terrain3D extends Node3D {
|
|
|
1989
2052
|
* physics bodies follow).
|
|
1990
2053
|
*/
|
|
1991
2054
|
heightAt(x: number, z: number): number;
|
|
2055
|
+
/**
|
|
2056
|
+
* @internal Cut (spec) or release (null) a trench another node owns. A
|
|
2057
|
+
* `River3D` calls this to dig its own bed; the heightmap, the collider,
|
|
2058
|
+
* `heightAt`, the drape and the mesh all pick it up on their next rebuild.
|
|
2059
|
+
*/
|
|
2060
|
+
_setExternalChannel(owner: Node, specs: ChannelSpec[] | null): void;
|
|
2061
|
+
/** Authored trenches plus the ones other nodes cut. */
|
|
2062
|
+
private allChannels;
|
|
1992
2063
|
/** @internal The pure heightfield (lazily built — physics pulls this). */
|
|
1993
2064
|
_heightmap(): Heightmap;
|
|
1994
2065
|
/** The splat layers in effect — theme preset, or `layers` for 'custom'. */
|
|
@@ -2387,6 +2458,10 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
|
2387
2458
|
private readonly _scratchA;
|
|
2388
2459
|
private readonly _scratchB;
|
|
2389
2460
|
private readonly _bodies;
|
|
2461
|
+
private readonly _cutouts;
|
|
2462
|
+
/** Scratch for the cutout mask pass (no per-frame allocation). */
|
|
2463
|
+
private readonly _maskMeshes;
|
|
2464
|
+
private readonly _maskSaved;
|
|
2390
2465
|
/** Scratch for the side-pass cull (no per-frame allocation). */
|
|
2391
2466
|
private readonly _passHidden;
|
|
2392
2467
|
private cubeTarget;
|
|
@@ -2428,6 +2503,16 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
|
2428
2503
|
/** The cheap lake ShaderMaterial — fresnel sky tint + ripple normals + sun
|
|
2429
2504
|
* glint + edge fade, NO scene re-renders (see lake.ts). */
|
|
2430
2505
|
private buildLakeMaterial;
|
|
2506
|
+
/**
|
|
2507
|
+
* Stamp every {@link WaterCutout3D} owner into the depth pre-pass.
|
|
2508
|
+
*
|
|
2509
|
+
* The cutout does not describe a hole in the water — it names a piece of
|
|
2510
|
+
* geometry that holds the water back, and this pass re-draws exactly that
|
|
2511
|
+
* geometry (clipped to the cutout box) at the near plane. The water's
|
|
2512
|
+
* occlusion discard does the rest. The tree is walked live, so a cutout on a
|
|
2513
|
+
* bobbing hull tracks it with no wiring.
|
|
2514
|
+
*/
|
|
2515
|
+
private renderCutoutMasks;
|
|
2431
2516
|
private syncFancy;
|
|
2432
2517
|
/**
|
|
2433
2518
|
* @internal Environment-sky sun hand-off (see {@link SunConsumer3D}): the
|
|
@@ -2454,6 +2539,59 @@ declare class Water3D extends Node3D implements RenderHook3D, SunConsumer3D {
|
|
|
2454
2539
|
private waveOffsetAt;
|
|
2455
2540
|
}
|
|
2456
2541
|
//#endregion
|
|
2542
|
+
//#region src/3d/nodes/water-cutout-3d.d.ts
|
|
2543
|
+
/**
|
|
2544
|
+
* A box the water does not enter.
|
|
2545
|
+
*
|
|
2546
|
+
* Water is ONE surface: it has no idea a boat has an inside. Float an open hull
|
|
2547
|
+
* and every wave crest that rises above its floor renders as sea sloshing
|
|
2548
|
+
* through the deck — correct for a plane cutting an open box, wrong for a boat.
|
|
2549
|
+
* Freeboard alone cannot fix it (a big enough swell always wins), so the fix is
|
|
2550
|
+
* to name the geometry that holds the water back. `Water3D` re-draws that
|
|
2551
|
+
* geometry — the part of it inside this box — into its depth pre-pass at the
|
|
2552
|
+
* near plane, and its ordinary occlusion discard then removes the water for
|
|
2553
|
+
* exactly the pixels the hull already covers.
|
|
2554
|
+
*
|
|
2555
|
+
* Park it as a CHILD of the thing it belongs to — a hull, a diving bell, a
|
|
2556
|
+
* dock's well, a cave mouth — and it follows along, rotation included.
|
|
2557
|
+
*
|
|
2558
|
+
* SAFE BY CONSTRUCTION: nothing is ever cut out of the water. The mask is the
|
|
2559
|
+
* owner's own silhouette, so water can only be hidden behind geometry the
|
|
2560
|
+
* player is already looking at. Size the box to the interior and err large —
|
|
2561
|
+
* an overlarge box just means more of this hull holds water back; it can never
|
|
2562
|
+
* punch a rectangle of bare ground into open water. (Needs the depth pre-pass:
|
|
2563
|
+
* `Water3D` in `fancy` quality. Skinned and instanced meshes are re-drawn at
|
|
2564
|
+
* their rest pose, so hang cutouts on ordinary geometry.)
|
|
2565
|
+
*
|
|
2566
|
+
* ```json
|
|
2567
|
+
* { "name": "Rowboat", "type": "RigidBody3D", "children": [
|
|
2568
|
+
* { "name": "DryInside", "type": "WaterCutout3D",
|
|
2569
|
+
* "props": { "size": [3.0, 1.2, 1.1], "position": [0, 0.4, 0] } } ] }
|
|
2570
|
+
* ```
|
|
2571
|
+
*
|
|
2572
|
+
* Cheap: a handful of these cost a few ALU per water pixel. `Water3D` reads the
|
|
2573
|
+
* nearest {@link WATER_CUTOUT_MAX} of them.
|
|
2574
|
+
*/
|
|
2575
|
+
declare class WaterCutout3D extends Node3D {
|
|
2576
|
+
static override readonly typeName: string;
|
|
2577
|
+
static override readonly props: PropSchema;
|
|
2578
|
+
/** Box extent in meters, centered on the node ([x, y, z]). */
|
|
2579
|
+
size: number[];
|
|
2580
|
+
/** Live toggle — a scuttled hull can start letting the sea in. */
|
|
2581
|
+
enabled: boolean;
|
|
2582
|
+
static validateJson(node: Node): void;
|
|
2583
|
+
/**
|
|
2584
|
+
* A cutout works by re-drawing its PARENT, so it has to have one. At the
|
|
2585
|
+
* scene root it would hand the whole world a licence to erase water — the
|
|
2586
|
+
* one failure this design exists to rule out — so that arrangement is a hard
|
|
2587
|
+
* load error, not a silent no-op. (Checked here rather than in
|
|
2588
|
+
* `validateJson`: the loader validates a node before attaching it.)
|
|
2589
|
+
*/
|
|
2590
|
+
override onReady(): void;
|
|
2591
|
+
}
|
|
2592
|
+
/** How many cutouts one Water3D reads (the nearest ones win). */
|
|
2593
|
+
declare const WATER_CUTOUT_MAX = 4;
|
|
2594
|
+
//#endregion
|
|
2457
2595
|
//#region src/3d/register.d.ts
|
|
2458
2596
|
/**
|
|
2459
2597
|
* Register the 3D node taxonomy (and the core nodes). Call once in your game
|
|
@@ -2761,4 +2899,4 @@ declare function createNavDebugNode(nav: TerrainNav, opts?: {
|
|
|
2761
2899
|
name?: string;
|
|
2762
2900
|
}): InstancedMesh3D;
|
|
2763
2901
|
//#endregion
|
|
2764
|
-
export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, River3D, type RiverHit, type RiverRing, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
2902
|
+
export { Area3D, AssetStore3D, type BedSampler, Billboard3D, type BillboardGroupMode, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, type CreateGame3DOptions, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, type DownhillTraceOptions, Environment3D, type Environment3DConfig, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, type FlowerVariety, Flowers3D, type FogEnvironment, Foliage3D, type FoliageKind, type FoliageStyle, type Game3D, type HeightSampler, type Heightmap, type HeightmapOptions, InstancedMesh3D, Joint3D, type JointType3D, LoftMesh3D, type LoftSection, MeshInstance3D, type MeshKind, type MeshMaterialProps, type ModelEntry, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, type Physics3DOptions, PhysicsBody3D, QUARTER_PITCH, type RenderContext3D, type RenderHook3D, Renderer3D, type Renderer3DOptions, type RigView, RigidBody3D, type Ripple, River3D, type RiverHit, type RiverRing, type ShadowsEnvironment, type SkyEnvironment, StaticBody3D, type SunConsumer3D, type SyncOptions, type SyncResult, TERRAIN_THEMES, Terrain3D, type TerrainLayer, type TerrainNav, type TerrainNavOptions, type TerrainTheme, Trail3D, Tree3D, type TreeTier, type TreeType, VOXEL_PALETTE, type VoxelBlock, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
package/dist/3d.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import { A as Water3D, F as
|
|
3
|
-
import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-
|
|
4
|
-
import { A as QUARTER_PITCH, C as BoneAttachment3D, D as TERRAIN_THEMES, E as DEFAULT_TERRAIN_TEXTURE_BASE, M as keyboardIntensity, N as movementState, O as terrainThemeLayers, P as rigPose, S as BoneLookAt3D, T as Terrain3D, _ as Flowers3D, a as Trail3D, b as CharacterController3D, c as Particles3D, d as DirectionalLight3D, f as OmniLight3D, g as DENSITY_PRESETS, h as Foliage3D, i as Tree3D, j as cameraRelative, k as Joint3D, l as ModelInstance3D, m as MeshInstance3D, n as VOXEL_PALETTE, o as River3D, p as InstancedMesh3D, r as VoxelGrid3D, s as traceDownhillPath, t as registerNodes3D, u as LoftMesh3D, v as resolveFlowerDensity, w as Billboard3D, x as Camera3D, y as FLOWER_VARIETIES } from "./register-
|
|
2
|
+
import { A as Water3D, F as PhysicsBody3D, I as RigidBody3D, L as StaticBody3D, M as WaterCutout3D, N as Area3D, P as CharacterBody3D, R as Node3D, V as WATER_MAX_RIPPLES, j as WATER_CUTOUT_MAX } from "./gameplay-Ddk13pQ6.js";
|
|
3
|
+
import { a as Environment3D, c as sunDirectionFromElevationAzimuth, i as syncTree, l as sunDirectionFromSky, o as horizonColorFromSky, r as Renderer3D, s as parseEnvironment3D, t as createGame3D, u as AssetStore3D } from "./create-game-DUAtTqID.js";
|
|
4
|
+
import { A as QUARTER_PITCH, C as BoneAttachment3D, D as TERRAIN_THEMES, E as DEFAULT_TERRAIN_TEXTURE_BASE, M as keyboardIntensity, N as movementState, O as terrainThemeLayers, P as rigPose, S as BoneLookAt3D, T as Terrain3D, _ as Flowers3D, a as Trail3D, b as CharacterController3D, c as Particles3D, d as DirectionalLight3D, f as OmniLight3D, g as DENSITY_PRESETS, h as Foliage3D, i as Tree3D, j as cameraRelative, k as Joint3D, l as ModelInstance3D, m as MeshInstance3D, n as VOXEL_PALETTE, o as River3D, p as InstancedMesh3D, r as VoxelGrid3D, s as traceDownhillPath, t as registerNodes3D, u as LoftMesh3D, v as resolveFlowerDensity, w as Billboard3D, x as Camera3D, y as FLOWER_VARIETIES } from "./register-DcHXS1MA.js";
|
|
5
5
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
6
|
-
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-
|
|
6
|
+
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-4mGzOZ2J.js";
|
|
7
7
|
//#region src/3d/environment-runtime.ts
|
|
8
8
|
/**
|
|
9
9
|
* Live environment editing — the renderer re-applies `scene.environment`
|
|
@@ -142,4 +142,4 @@ function createNavDebugNode(nav, opts) {
|
|
|
142
142
|
return node;
|
|
143
143
|
}
|
|
144
144
|
//#endregion
|
|
145
|
-
export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_MAX_RIPPLES, Water3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
145
|
+
export { Area3D, AssetStore3D, Billboard3D, BoneAttachment3D, BoneLookAt3D, Camera3D, CharacterBody3D, CharacterController3D, DEFAULT_TERRAIN_TEXTURE_BASE, DirectionalLight3D, Environment3D, DENSITY_PRESETS as FLOWER_DENSITY_PRESETS, FLOWER_VARIETIES, Flowers3D, Foliage3D, InstancedMesh3D, Joint3D, LoftMesh3D, MeshInstance3D, ModelInstance3D, Node3D, OmniLight3D, Particles3D, Physics3D, PhysicsBody3D, QUARTER_PITCH, Renderer3D, RigidBody3D, River3D, StaticBody3D, TERRAIN_THEMES, Terrain3D, Trail3D, Tree3D, VOXEL_PALETTE, VoxelGrid3D, WATER_CUTOUT_MAX, WATER_MAX_RIPPLES, Water3D, WaterCutout3D, buildHeightmap, buildTerrainNav, cameraRelative, createGame3D, createNavDebugNode, enablePhysics3D, horizonColorFromSky, keyboardIntensity, movementState, parseEnvironment3D, registerNodes3D, resolveFlowerDensity, rigPose, setEnvironment3D, splatWeights, sunDirectionFromElevationAzimuth, sunDirectionFromSky, syncTree, terrainThemeLayers, traceDownhillPath };
|
|
@@ -3,7 +3,7 @@ import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resol
|
|
|
3
3
|
import { l as AudioPlayer, u as Engine } from "./register-nObreUQR.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
|
|
6
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
6
|
+
import { n as registerGameplayBehaviors } from "./gameplay-Ddk13pQ6.js";
|
|
7
7
|
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-CvpSUU3O.js";
|
|
8
8
|
import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
|
|
9
9
|
import { n as enablePhysics2D } from "./physics-2d-DiVFFlH3.js";
|
|
@@ -3,10 +3,10 @@ import { _ as registerBehavior, n as loadScene } from "./loader-BZqOKfI2.js";
|
|
|
3
3
|
import { l as AudioPlayer, u as Engine } from "./register-nObreUQR.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
|
|
6
|
-
import {
|
|
6
|
+
import { B as createCausticsQuad, F as PhysicsBody3D, R as Node3D, n as registerGameplayBehaviors } from "./gameplay-Ddk13pQ6.js";
|
|
7
7
|
import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
|
|
8
|
-
import { d as DirectionalLight3D, l as ModelInstance3D, t as registerNodes3D, x as Camera3D } from "./register-
|
|
9
|
-
import { n as enablePhysics3D } from "./physics-3d-
|
|
8
|
+
import { d as DirectionalLight3D, l as ModelInstance3D, t as registerNodes3D, x as Camera3D } from "./register-DcHXS1MA.js";
|
|
9
|
+
import { n as enablePhysics3D } from "./physics-3d-4mGzOZ2J.js";
|
|
10
10
|
import { ACESFilmicToneMapping, AmbientLight, Box3, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
11
11
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
12
12
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
package/dist/env.d.ts
CHANGED
|
@@ -376,6 +376,12 @@ interface TerrainOptions {
|
|
|
376
376
|
* theme ALWAYS ships its sea — without it the edge-wrap skirt is bare cliff.
|
|
377
377
|
*/
|
|
378
378
|
water?: boolean;
|
|
379
|
+
/**
|
|
380
|
+
* Grid segments per side (default 128). Raise it when the map carries
|
|
381
|
+
* features finer than a cell — a 2 m creek on a 260 m terrain has to be cut
|
|
382
|
+
* into ~1 m cells (256) or the trench smooths away to nothing.
|
|
383
|
+
*/
|
|
384
|
+
resolution?: number;
|
|
379
385
|
}
|
|
380
386
|
/**
|
|
381
387
|
* The environment header that matches a generator theme — sky, horizon fog
|
package/dist/env.js
CHANGED
|
@@ -1419,7 +1419,7 @@ const TERRAIN_GENERATOR_THEMES = [
|
|
|
1419
1419
|
"volcanic"
|
|
1420
1420
|
];
|
|
1421
1421
|
/** Terrain3D's default grid segments — the probe MUST match the runtime grid. */
|
|
1422
|
-
const
|
|
1422
|
+
const DEFAULT_TERRAIN_RESOLUTION = 128;
|
|
1423
1423
|
/** Terrain3D edge wrap: the rim drops this many meters before the −50 m skirt. */
|
|
1424
1424
|
const EDGE_WRAP_DROP = 20;
|
|
1425
1425
|
/** Sea margin above the highest rim cliff (> the ~0.52 m wave-trough dip). */
|
|
@@ -1676,6 +1676,7 @@ function generateTerrain(opts) {
|
|
|
1676
1676
|
prop: "theme",
|
|
1677
1677
|
validOptions: [...TERRAIN_GENERATOR_THEMES]
|
|
1678
1678
|
});
|
|
1679
|
+
const TERRAIN_RESOLUTION = Math.max(2, Math.round(opts.resolution ?? DEFAULT_TERRAIN_RESOLUTION));
|
|
1679
1680
|
let maxHeight = opts.maxHeight || spec.maxHeight;
|
|
1680
1681
|
const rng = new Rng(seed);
|
|
1681
1682
|
const isIsland = theme === "island";
|
|
@@ -1714,7 +1715,8 @@ function generateTerrain(opts) {
|
|
|
1714
1715
|
seed,
|
|
1715
1716
|
theme: spec.splat,
|
|
1716
1717
|
...spec.roughness !== void 0 ? { roughness: spec.roughness } : {},
|
|
1717
|
-
...spec.detail !== void 0 ? { detail: spec.detail } : {}
|
|
1718
|
+
...spec.detail !== void 0 ? { detail: spec.detail } : {},
|
|
1719
|
+
...TERRAIN_RESOLUTION !== DEFAULT_TERRAIN_RESOLUTION ? { resolution: TERRAIN_RESOLUTION } : {}
|
|
1718
1720
|
}
|
|
1719
1721
|
}]
|
|
1720
1722
|
}];
|
|
@@ -1799,9 +1801,10 @@ function borderMaxHeight(hm) {
|
|
|
1799
1801
|
let borderMax = Number.NEGATIVE_INFINITY;
|
|
1800
1802
|
const hw = hm.width / 2;
|
|
1801
1803
|
const hd = hm.depth / 2;
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
const
|
|
1804
|
+
const steps = hm.segsX;
|
|
1805
|
+
for (let i = 0; i <= steps; i++) {
|
|
1806
|
+
const x = -hw + i / steps * hm.width;
|
|
1807
|
+
const z = -hd + i / steps * hm.depth;
|
|
1805
1808
|
borderMax = Math.max(borderMax, hm.baseHeight(x, -hd), hm.baseHeight(x, hd), hm.baseHeight(-hw, z), hm.baseHeight(hw, z));
|
|
1806
1809
|
}
|
|
1807
1810
|
return borderMax;
|
|
@@ -2,7 +2,7 @@ import { _ as registerBehavior, f as Node, m as Behavior, n as loadScene } from
|
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
3
|
import { t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
4
4
|
import { r as effectiveOrder, t as duplicateNode } from "./duplicate-BgtFrFo4.js";
|
|
5
|
-
import { Color, CubeCamera, DepthTexture, DoubleSide, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
|
|
5
|
+
import { Color, CubeCamera, DepthTexture, DoubleSide, Euler, FloatType, Frustum, HalfFloatType, LinearMipmapLinearFilter, MathUtils, Matrix4, Mesh, MeshDepthMaterial, Object3D, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, ShaderMaterial, Sphere, Vector2, Vector3, Vector4, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
|
|
6
6
|
//#region src/3d/frustum.ts
|
|
7
7
|
const scratchFrustum = new Frustum();
|
|
8
8
|
const scratchMatrix = new Matrix4();
|
|
@@ -1022,6 +1022,12 @@ uniform float uReflectivityMax;
|
|
|
1022
1022
|
// Both cures are the same: filter the depth over its own texel footprint and
|
|
1023
1023
|
// estimate gradients from those taps instead of from fwidth().
|
|
1024
1024
|
uniform vec2 uScenePassTexel;
|
|
1025
|
+
// incanto CUTOUTS (a boat's inside, a diving bell, a well under a dock) are
|
|
1026
|
+
// NOT a fragment test — nothing here knows about them. A cutout stamps its
|
|
1027
|
+
// OWNER'S OWN geometry into the depth pre-pass at the near plane, so the
|
|
1028
|
+
// occlusion discard below removes the water for exactly the pixels that owner
|
|
1029
|
+
// already covers. That is what makes a hole in open sea impossible: this
|
|
1030
|
+
// shader can only ever hide water behind something the player is looking at.
|
|
1025
1031
|
/** Shoreline dissolve width, in DEPTH TEXELS (≈2 screen px each). */
|
|
1026
1032
|
const float SHORE_FADE_TEXELS = 11.0;
|
|
1027
1033
|
/** Deepest water that can still be a shoreline (m) — past this the fade is off. */
|
|
@@ -1399,6 +1405,7 @@ void main() {
|
|
|
1399
1405
|
depthMask = 1.0 - smoothstep(softDiscardStart, softDiscardEnd, depthDiff);
|
|
1400
1406
|
// At border: 0 (fully transparent) ~ 1 (fully opaque)
|
|
1401
1407
|
}
|
|
1408
|
+
|
|
1402
1409
|
}
|
|
1403
1410
|
|
|
1404
1411
|
// Calculate vector from camera to the vertex
|
|
@@ -2388,6 +2395,75 @@ var CharacterBody3D = class extends PhysicsBody3D {
|
|
|
2388
2395
|
}
|
|
2389
2396
|
};
|
|
2390
2397
|
//#endregion
|
|
2398
|
+
//#region src/3d/nodes/water-cutout-3d.ts
|
|
2399
|
+
/**
|
|
2400
|
+
* A box the water does not enter.
|
|
2401
|
+
*
|
|
2402
|
+
* Water is ONE surface: it has no idea a boat has an inside. Float an open hull
|
|
2403
|
+
* and every wave crest that rises above its floor renders as sea sloshing
|
|
2404
|
+
* through the deck — correct for a plane cutting an open box, wrong for a boat.
|
|
2405
|
+
* Freeboard alone cannot fix it (a big enough swell always wins), so the fix is
|
|
2406
|
+
* to name the geometry that holds the water back. `Water3D` re-draws that
|
|
2407
|
+
* geometry — the part of it inside this box — into its depth pre-pass at the
|
|
2408
|
+
* near plane, and its ordinary occlusion discard then removes the water for
|
|
2409
|
+
* exactly the pixels the hull already covers.
|
|
2410
|
+
*
|
|
2411
|
+
* Park it as a CHILD of the thing it belongs to — a hull, a diving bell, a
|
|
2412
|
+
* dock's well, a cave mouth — and it follows along, rotation included.
|
|
2413
|
+
*
|
|
2414
|
+
* SAFE BY CONSTRUCTION: nothing is ever cut out of the water. The mask is the
|
|
2415
|
+
* owner's own silhouette, so water can only be hidden behind geometry the
|
|
2416
|
+
* player is already looking at. Size the box to the interior and err large —
|
|
2417
|
+
* an overlarge box just means more of this hull holds water back; it can never
|
|
2418
|
+
* punch a rectangle of bare ground into open water. (Needs the depth pre-pass:
|
|
2419
|
+
* `Water3D` in `fancy` quality. Skinned and instanced meshes are re-drawn at
|
|
2420
|
+
* their rest pose, so hang cutouts on ordinary geometry.)
|
|
2421
|
+
*
|
|
2422
|
+
* ```json
|
|
2423
|
+
* { "name": "Rowboat", "type": "RigidBody3D", "children": [
|
|
2424
|
+
* { "name": "DryInside", "type": "WaterCutout3D",
|
|
2425
|
+
* "props": { "size": [3.0, 1.2, 1.1], "position": [0, 0.4, 0] } } ] }
|
|
2426
|
+
* ```
|
|
2427
|
+
*
|
|
2428
|
+
* Cheap: a handful of these cost a few ALU per water pixel. `Water3D` reads the
|
|
2429
|
+
* nearest {@link WATER_CUTOUT_MAX} of them.
|
|
2430
|
+
*/
|
|
2431
|
+
var WaterCutout3D = class extends Node3D {
|
|
2432
|
+
static typeName = "WaterCutout3D";
|
|
2433
|
+
static props = {
|
|
2434
|
+
size: { default: [
|
|
2435
|
+
2,
|
|
2436
|
+
1,
|
|
2437
|
+
1
|
|
2438
|
+
] },
|
|
2439
|
+
enabled: { default: true }
|
|
2440
|
+
};
|
|
2441
|
+
/** Box extent in meters, centered on the node ([x, y, z]). */
|
|
2442
|
+
size = [
|
|
2443
|
+
2,
|
|
2444
|
+
1,
|
|
2445
|
+
1
|
|
2446
|
+
];
|
|
2447
|
+
/** Live toggle — a scuttled hull can start letting the sea in. */
|
|
2448
|
+
enabled = true;
|
|
2449
|
+
static validateJson(node) {
|
|
2450
|
+
const c = node;
|
|
2451
|
+
if (!Array.isArray(c.size) || c.size.length !== 3 || c.size.some((v) => !(v > 0))) throw new IncantoError("BAD_FORMAT", `WaterCutout3D '${node.name}' size must be three positive meters [x, y, z], got ${JSON.stringify(c.size)}.`, { prop: "size" });
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* A cutout works by re-drawing its PARENT, so it has to have one. At the
|
|
2455
|
+
* scene root it would hand the whole world a licence to erase water — the
|
|
2456
|
+
* one failure this design exists to rule out — so that arrangement is a hard
|
|
2457
|
+
* load error, not a silent no-op. (Checked here rather than in
|
|
2458
|
+
* `validateJson`: the loader validates a node before attaching it.)
|
|
2459
|
+
*/
|
|
2460
|
+
onReady() {
|
|
2461
|
+
if (!this.parent?.parent) throw new IncantoError("BAD_FORMAT", `WaterCutout3D '${this.name}' must be a CHILD of the object that holds the water back (a hull, a diving bell, a dock's well) — it re-draws that object to keep water out of it, so at the scene root it has nothing to stand in for.`, { prop: "parent" });
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
/** How many cutouts one Water3D reads (the nearest ones win). */
|
|
2465
|
+
const WATER_CUTOUT_MAX = 4;
|
|
2466
|
+
//#endregion
|
|
2391
2467
|
//#region src/3d/nodes/water-3d.ts
|
|
2392
2468
|
const WATER_QUALITIES = ["fancy", "simple"];
|
|
2393
2469
|
const COLOR_KEYS = [
|
|
@@ -2869,6 +2945,10 @@ var Water3D = class Water3D extends Node3D {
|
|
|
2869
2945
|
z: 0
|
|
2870
2946
|
};
|
|
2871
2947
|
_bodies = [];
|
|
2948
|
+
_cutouts = [];
|
|
2949
|
+
/** Scratch for the cutout mask pass (no per-frame allocation). */
|
|
2950
|
+
_maskMeshes = [];
|
|
2951
|
+
_maskSaved = [];
|
|
2872
2952
|
/** Scratch for the side-pass cull (no per-frame allocation). */
|
|
2873
2953
|
_passHidden = [];
|
|
2874
2954
|
cubeTarget = null;
|
|
@@ -3052,6 +3132,7 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3052
3132
|
restoreForWaterPass(this._passHidden);
|
|
3053
3133
|
ctx.scene.overrideMaterial = prevOverride;
|
|
3054
3134
|
ctx.scene.background = prevBackground;
|
|
3135
|
+
this.renderCutoutMasks(ctx);
|
|
3055
3136
|
ctx.gl.setRenderTarget(prevTarget);
|
|
3056
3137
|
ctx.gl.autoClear = prevAutoClear;
|
|
3057
3138
|
ctx.gl.clippingPlanes = prevClippingPlanes;
|
|
@@ -3232,6 +3313,61 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3232
3313
|
side: DoubleSide
|
|
3233
3314
|
});
|
|
3234
3315
|
}
|
|
3316
|
+
/**
|
|
3317
|
+
* Stamp every {@link WaterCutout3D} owner into the depth pre-pass.
|
|
3318
|
+
*
|
|
3319
|
+
* The cutout does not describe a hole in the water — it names a piece of
|
|
3320
|
+
* geometry that holds the water back, and this pass re-draws exactly that
|
|
3321
|
+
* geometry (clipped to the cutout box) at the near plane. The water's
|
|
3322
|
+
* occlusion discard does the rest. The tree is walked live, so a cutout on a
|
|
3323
|
+
* bobbing hull tracks it with no wiring.
|
|
3324
|
+
*/
|
|
3325
|
+
renderCutoutMasks(ctx) {
|
|
3326
|
+
const list = this._cutouts;
|
|
3327
|
+
list.length = 0;
|
|
3328
|
+
let top = this;
|
|
3329
|
+
while (top.parent) top = top.parent;
|
|
3330
|
+
collectCutouts(top, list);
|
|
3331
|
+
if (list.length === 0) return;
|
|
3332
|
+
const u = cutoutMaskMaterial.uniforms;
|
|
3333
|
+
const saved = this._maskSaved;
|
|
3334
|
+
const meshes = this._maskMeshes;
|
|
3335
|
+
const prevClip = ctx.gl.clippingPlanes;
|
|
3336
|
+
ctx.gl.clippingPlanes = noClipPlanes;
|
|
3337
|
+
let n = 0;
|
|
3338
|
+
for (const cut of list) {
|
|
3339
|
+
if (n >= 4) break;
|
|
3340
|
+
const ownerObj = cutoutOwnerObject(cut);
|
|
3341
|
+
if (!ownerObj) continue;
|
|
3342
|
+
cut._syncObject3D();
|
|
3343
|
+
const world = cut._ensureObject3D();
|
|
3344
|
+
world.updateWorldMatrix(true, false);
|
|
3345
|
+
world.getWorldPosition(cutoutPosScratch);
|
|
3346
|
+
world.getWorldQuaternion(cutoutQuatScratch);
|
|
3347
|
+
(u.uCenter?.value)?.copy(cutoutPosScratch);
|
|
3348
|
+
(u.uHalf?.value)?.set((cut.size[0] ?? 1) / 2, (cut.size[1] ?? 1) / 2, (cut.size[2] ?? 1) / 2);
|
|
3349
|
+
cutoutEulerScratch.setFromQuaternion(cutoutQuatScratch, "YXZ");
|
|
3350
|
+
(u.uYaw?.value)?.set(Math.cos(cutoutEulerScratch.y), Math.sin(cutoutEulerScratch.y));
|
|
3351
|
+
meshes.length = 0;
|
|
3352
|
+
saved.length = 0;
|
|
3353
|
+
collectMaskMeshes(ownerObj, meshes);
|
|
3354
|
+
if (meshes.length === 0) continue;
|
|
3355
|
+
for (const m of meshes) {
|
|
3356
|
+
saved.push(m.material);
|
|
3357
|
+
m.material = cutoutMaskMaterial;
|
|
3358
|
+
}
|
|
3359
|
+
ctx.gl.render(ownerObj, ctx.camera);
|
|
3360
|
+
for (let i = 0; i < meshes.length; i++) {
|
|
3361
|
+
const m = meshes[i];
|
|
3362
|
+
const mat = saved[i];
|
|
3363
|
+
if (m && mat) m.material = mat;
|
|
3364
|
+
}
|
|
3365
|
+
n++;
|
|
3366
|
+
}
|
|
3367
|
+
meshes.length = 0;
|
|
3368
|
+
saved.length = 0;
|
|
3369
|
+
ctx.gl.clippingPlanes = prevClip;
|
|
3370
|
+
}
|
|
3235
3371
|
syncFancy(mesh) {
|
|
3236
3372
|
const u = mesh.material.uniforms;
|
|
3237
3373
|
const w = this.size[0] ?? 1;
|
|
@@ -3444,6 +3580,45 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3444
3580
|
});
|
|
3445
3581
|
}
|
|
3446
3582
|
};
|
|
3583
|
+
const cutoutPosScratch = new Vector3();
|
|
3584
|
+
const cutoutQuatScratch = new Quaternion();
|
|
3585
|
+
const cutoutEulerScratch = new Euler();
|
|
3586
|
+
/** Every enabled water cutout in the tree, nearest-first is not needed: the
|
|
3587
|
+
* cap is small and scenes rarely exceed it. */
|
|
3588
|
+
function collectCutouts(node, out) {
|
|
3589
|
+
if (node instanceof WaterCutout3D && node.enabled && node.visible) out.push(node);
|
|
3590
|
+
for (const child of node.children) collectCutouts(child, out);
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* The three object of the thing a cutout hangs on — its PARENT, the hull that
|
|
3594
|
+
* holds the water back. A cutout parented to the scene root has no owner and
|
|
3595
|
+
* does nothing (the loader rejects that arrangement outright): masking a whole
|
|
3596
|
+
* scene's geometry is how you get a hole in open water, which is the one thing
|
|
3597
|
+
* this design must never allow.
|
|
3598
|
+
*/
|
|
3599
|
+
function cutoutOwnerObject(cut) {
|
|
3600
|
+
const owner = cut.parent;
|
|
3601
|
+
if (!(owner instanceof Node3D) || !owner.parent) return null;
|
|
3602
|
+
const obj = owner._ensureObject3D();
|
|
3603
|
+
return typeof obj?.traverse === "function" ? obj : null;
|
|
3604
|
+
}
|
|
3605
|
+
/** Node types that never stand in for water, however they are parented. */
|
|
3606
|
+
const NEVER_MASKS = new Set([
|
|
3607
|
+
"Terrain3D",
|
|
3608
|
+
"Water3D",
|
|
3609
|
+
"River3D"
|
|
3610
|
+
]);
|
|
3611
|
+
/** Visible meshes of a cutout owner, the ones the mask pass re-draws. */
|
|
3612
|
+
function collectMaskMeshes(root, out) {
|
|
3613
|
+
root.traverse((obj) => {
|
|
3614
|
+
const mesh = obj;
|
|
3615
|
+
if (!mesh.isMesh || !mesh.visible || !mesh.material) return;
|
|
3616
|
+
const type = (obj.userData?.incantoNode)?.constructor?.typeName;
|
|
3617
|
+
if (type && NEVER_MASKS.has(type)) return;
|
|
3618
|
+
out.push(mesh);
|
|
3619
|
+
});
|
|
3620
|
+
return out;
|
|
3621
|
+
}
|
|
3447
3622
|
/** Bodies that can splash: kinematic characters and dynamic rigid bodies. */
|
|
3448
3623
|
function collectBodies(node, out) {
|
|
3449
3624
|
if (node instanceof CharacterBody3D || node instanceof RigidBody3D) out.push(node);
|
|
@@ -3517,6 +3692,8 @@ const mirrorClipPlane = new Plane(new Vector3(0, 1, 0), 0);
|
|
|
3517
3692
|
const mirrorClipPlanes = [mirrorClipPlane];
|
|
3518
3693
|
const grabClipPlane = new Plane(new Vector3(0, -1, 0), 0);
|
|
3519
3694
|
const grabClipPlanes = [grabClipPlane];
|
|
3695
|
+
/** No clipping — the cutout mask pass wants the submerged geometry back. */
|
|
3696
|
+
const noClipPlanes = [];
|
|
3520
3697
|
/** Keep-above twin for the depth-completion pass: normal (0,1,0) keeps
|
|
3521
3698
|
* y > −constant — set per render to waterY − slack (overlapping the grab). */
|
|
3522
3699
|
const depthOnlyClipPlane = new Plane(new Vector3(0, 1, 0), 0);
|
|
@@ -3524,6 +3701,56 @@ const depthOnlyClipPlanes = [depthOnlyClipPlane];
|
|
|
3524
3701
|
/** Shared override for the depth-completion pass — writes depth, never color. */
|
|
3525
3702
|
const depthOnlyMaterial = new MeshDepthMaterial();
|
|
3526
3703
|
depthOnlyMaterial.colorWrite = false;
|
|
3704
|
+
/**
|
|
3705
|
+
* The cutout mask material — the whole of {@link WaterCutout3D}'s mechanism.
|
|
3706
|
+
*
|
|
3707
|
+
* It is drawn over the cutout OWNER'S OWN meshes into the water's depth
|
|
3708
|
+
* pre-pass, and does exactly two things: throw away everything outside the
|
|
3709
|
+
* cutout box, and stamp what is left at the NEAR PLANE. The water's existing
|
|
3710
|
+
* occlusion discard then reads "something solid is in front of me here" and
|
|
3711
|
+
* removes itself — for exactly the pixels the owner's geometry already covers.
|
|
3712
|
+
*
|
|
3713
|
+
* Screen position is untouched (only clip-space z is rewritten), so the mask
|
|
3714
|
+
* is pixel-identical to the owner's silhouette. THAT is the safety property:
|
|
3715
|
+
* water can only ever be hidden behind the hull the player is looking at, so
|
|
3716
|
+
* an oversized box degrades into "more of this boat holds water back" and can
|
|
3717
|
+
* never carve a rectangle of bare seabed out of open water.
|
|
3718
|
+
*/
|
|
3719
|
+
const cutoutMaskMaterial = new ShaderMaterial({
|
|
3720
|
+
uniforms: {
|
|
3721
|
+
uCenter: { value: new Vector3() },
|
|
3722
|
+
uHalf: { value: new Vector3(1, 1, 1) },
|
|
3723
|
+
uYaw: { value: new Vector2(1, 0) }
|
|
3724
|
+
},
|
|
3725
|
+
vertexShader: `
|
|
3726
|
+
uniform vec3 uCenter;
|
|
3727
|
+
uniform vec2 uYaw;
|
|
3728
|
+
varying vec3 vCutLocal;
|
|
3729
|
+
void main() {
|
|
3730
|
+
vec3 world = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
3731
|
+
vec3 rel = world - uCenter;
|
|
3732
|
+
vCutLocal = vec3(rel.x * uYaw.x + rel.z * uYaw.y, rel.y, -rel.x * uYaw.y + rel.z * uYaw.x);
|
|
3733
|
+
vec4 clip = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
3734
|
+
// pull the DEPTH to the near plane and nothing else — scaling the view
|
|
3735
|
+
// position would slide the silhouette under perspective, and a silhouette
|
|
3736
|
+
// that grows past its owner is exactly the hole this design forbids
|
|
3737
|
+
clip.z = -clip.w * 0.99;
|
|
3738
|
+
gl_Position = clip;
|
|
3739
|
+
}
|
|
3740
|
+
`,
|
|
3741
|
+
fragmentShader: `
|
|
3742
|
+
uniform vec3 uHalf;
|
|
3743
|
+
varying vec3 vCutLocal;
|
|
3744
|
+
void main() {
|
|
3745
|
+
if (any(greaterThan(abs(vCutLocal), uHalf))) discard;
|
|
3746
|
+
gl_FragColor = vec4(0.0);
|
|
3747
|
+
}
|
|
3748
|
+
`,
|
|
3749
|
+
colorWrite: false,
|
|
3750
|
+
depthWrite: true,
|
|
3751
|
+
depthTest: false,
|
|
3752
|
+
side: DoubleSide
|
|
3753
|
+
});
|
|
3527
3754
|
//#endregion
|
|
3528
3755
|
//#region src/gameplay/buoyancy.ts
|
|
3529
3756
|
/** Gravity the float is sized against (m/s²) — the cap is expressed in g. */
|
|
@@ -5640,4 +5867,4 @@ function registerGameplayBehaviors(opts) {
|
|
|
5640
5867
|
for (const [name, ctor] of Object.entries(GAMEPLAY_BEHAVIORS)) registerBehavior(name, ctor, { replace });
|
|
5641
5868
|
}
|
|
5642
5869
|
//#endregion
|
|
5643
|
-
export { Water3D as A,
|
|
5870
|
+
export { Water3D as A, createCausticsQuad as B, FollowCamera as C, Health as D, DamageOnContact as E, PhysicsBody3D as F, colliderFootDrop as H, RigidBody3D as I, StaticBody3D as L, WaterCutout3D as M, Area3D as N, Collector as O, CharacterBody3D as P, Node3D as R, restartScene as S, phaseOf as T, WATER_MAX_RIPPLES as V, hitStop as _, Wander as a, GameFlow as b, Projectile as c, PathFollow as d, Oscillate as f, Cooldown as g, CameraShake as h, WaveSpawner as i, WATER_CUTOUT_MAX as j, Chase as k, Pickup as l, Lifetime as m, registerGameplayBehaviors as n, Spawner as o, MoveTo as p, ZombieAI as r, ScoreKeeper as s, GAMEPLAY_BEHAVIORS as t, Patrol as u, screenFlash as v, DayNight as w, goToScene as x, Interactable as y, validateCollider3D as z };
|
package/dist/gameplay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-
|
|
1
|
+
import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-Ddk13pQ6.js";
|
|
2
2
|
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
|