@jgengine/core 0.11.0 → 0.12.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/CHANGELOG.md +26 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat.d.ts +1 -0
- package/dist/combat.js +1 -0
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/playableGame.d.ts +34 -435
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +19 -0
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +24 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +130 -0
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +59 -0
- package/dist/runtime/context/scene.js +291 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +88 -501
- package/dist/runtime/gameContextTypes.d.ts +467 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +4 -2
- package/dist/runtime/headlessRunner.js +1 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +5 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +58 -0
- package/dist/scene/colliders.js +108 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +10 -14
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +59 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/features.d.ts +18 -3
- package/dist/world/features.js +10 -1
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/pathNetwork.d.ts +159 -0
- package/dist/world/pathNetwork.js +684 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/scatter.js +2 -20
- package/dist/world/streets.d.ts +29 -0
- package/dist/world/streets.js +55 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +45 -0
- package/dist/world/terrain.js +78 -0
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +7 -4
- package/skills/jgengine/api.md +172 -79
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +5 -1
- package/skills/jgengine-combat/api.md +36 -0
- package/skills/jgengine-combat/capabilities.md +44 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/reference.md +12 -2
- package/skills/jgengine-gameplay/api.md +152 -26
- package/skills/jgengine-gameplay/capabilities.md +78 -2
- package/skills/jgengine-gameplay/reference-systems.md +1 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +419 -15
- package/skills/jgengine-world/capabilities.md +108 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CommandResult } from "../commands/commandRegistry.js";
|
|
2
2
|
import type { ModelAssetRef } from "../scene/assetCatalog.js";
|
|
3
|
-
import type { GameContext, GameContextContent } from "./gameContext.js";
|
|
3
|
+
import type { GameContext, GameContextContent, GameContextModels } from "./gameContext.js";
|
|
4
4
|
import type { GameDefinition, LoopPlayer } from "../game/defineGame.js";
|
|
5
5
|
import { type HostedGameRunner, type InputFrame } from "./hostedGameRunner.js";
|
|
6
6
|
import type { WorldDiff } from "./worldReplication.js";
|
|
@@ -42,6 +42,8 @@ export interface HostedWorldSessionOptions<TAssetRef extends ModelAssetRef, TMul
|
|
|
42
42
|
store?: HostedWorldStore;
|
|
43
43
|
/** Minimum elapsed ms between auto-saves on tick, measured against `now` (defaults to `Date.now` when omitted). Default `0` — persist on every revision-changing tick. */
|
|
44
44
|
saveIntervalMs?: number;
|
|
45
|
+
/** Render-model lookup for collider auto-fit, forwarded to the runner — see {@link HostedGameRunnerOptions.models}. */
|
|
46
|
+
models?: GameContextModels;
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* The stateful host substrate a long-lived backend binds (ws server, browser P2P host): a live
|
|
@@ -25,6 +25,7 @@ export function createHostedWorldSession(options) {
|
|
|
25
25
|
...(host === undefined ? {} : { host }),
|
|
26
26
|
...(now === undefined ? {} : { now }),
|
|
27
27
|
...(loaded === null ? {} : { restore: loaded.snapshot }),
|
|
28
|
+
...(options.models === undefined ? {} : { models: options.models }),
|
|
28
29
|
});
|
|
29
30
|
let savedRevision = loaded?.revision ?? 0;
|
|
30
31
|
let lastSaveAt = clock();
|
|
@@ -13,11 +13,20 @@ export interface SnapshotViewer {
|
|
|
13
13
|
* - {@link project} narrows this module's snapshot to what one viewer may see (private state, area of
|
|
14
14
|
* interest). Absent → the module is world-public and every client receives it verbatim (the default).
|
|
15
15
|
* - {@link version} is a monotone counter the host reads to skip re-serializing an unchanged module.
|
|
16
|
+
* - {@link decode} validates/coerces wire `unknown` into `T` before hydrate; returning `null` skips
|
|
17
|
+
* that module (fail-soft) instead of poisoning live state with a bare cast.
|
|
16
18
|
*/
|
|
17
19
|
export interface SnapshotModule<T = unknown> {
|
|
18
20
|
readonly key: string;
|
|
19
21
|
snapshot(): T;
|
|
20
22
|
hydrate(data: T): void;
|
|
23
|
+
/**
|
|
24
|
+
* Optional wire codec: turn the raw snapshot value into a typed `T`, or `null` to skip this module.
|
|
25
|
+
* When present, {@link applyWorldSnapshot} runs it before {@link hydrate} and never calls hydrate on
|
|
26
|
+
* a failed decode — a bad host/client payload leaves the prior module state intact rather than
|
|
27
|
+
* throwing or applying garbage.
|
|
28
|
+
*/
|
|
29
|
+
decode?(raw: unknown): T | null;
|
|
21
30
|
/**
|
|
22
31
|
* Narrow this module's full snapshot to what `viewer` is allowed to see — return the same shape
|
|
23
32
|
* carrying only the visible subset (drop other players' private state, cull entities outside the
|
|
@@ -38,7 +47,10 @@ export type WorldSnapshot = Record<string, unknown>;
|
|
|
38
47
|
* @internal
|
|
39
48
|
*/
|
|
40
49
|
export declare function composeWorldSnapshot(modules: readonly SnapshotModule[], viewer?: SnapshotViewer): WorldSnapshot;
|
|
41
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Hydrate every registered module whose key is present in `snapshot`; keys absent from it are left
|
|
52
|
+
* untouched. When a module provides {@link SnapshotModule.decode}, a `null` decode skips that module
|
|
53
|
+
* (fail-soft) instead of calling hydrate with unvalidated wire data.
|
|
42
54
|
* @internal
|
|
43
55
|
*/
|
|
44
56
|
export declare function applyWorldSnapshot(modules: readonly SnapshotModule[], snapshot: WorldSnapshot): void;
|
|
@@ -18,13 +18,25 @@ export function composeWorldSnapshot(modules, viewer) {
|
|
|
18
18
|
}
|
|
19
19
|
return snapshot;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Hydrate every registered module whose key is present in `snapshot`; keys absent from it are left
|
|
23
|
+
* untouched. When a module provides {@link SnapshotModule.decode}, a `null` decode skips that module
|
|
24
|
+
* (fail-soft) instead of calling hydrate with unvalidated wire data.
|
|
22
25
|
* @internal
|
|
23
26
|
*/
|
|
24
27
|
export function applyWorldSnapshot(modules, snapshot) {
|
|
25
28
|
for (const module of modules) {
|
|
26
|
-
if (Object.prototype.hasOwnProperty.call(snapshot, module.key))
|
|
27
|
-
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(snapshot, module.key))
|
|
30
|
+
continue;
|
|
31
|
+
const raw = snapshot[module.key];
|
|
32
|
+
if (module.decode !== undefined) {
|
|
33
|
+
const decoded = module.decode(raw);
|
|
34
|
+
if (decoded === null)
|
|
35
|
+
continue;
|
|
36
|
+
module.hydrate(decoded);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
module.hydrate(raw);
|
|
28
40
|
}
|
|
29
41
|
}
|
|
30
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssetSpace } from "./assetSpace.js";
|
|
2
|
+
import type { CollisionMeshData } from "./collisionMesh.js";
|
|
2
3
|
/** Measured horizontal footprint, footprint center, and lowest Y of a model in model space. */
|
|
3
4
|
export interface ModelDims {
|
|
4
5
|
footprint: {
|
|
@@ -10,11 +11,15 @@ export interface ModelDims {
|
|
|
10
11
|
z: number;
|
|
11
12
|
};
|
|
12
13
|
minY: number;
|
|
14
|
+
/** Highest Y in model space (pre-scale). `maxY - minY` is the measured model height; absent in indexes generated before it was measured. */
|
|
15
|
+
maxY?: number;
|
|
13
16
|
}
|
|
14
17
|
export interface ModelAssetRef {
|
|
15
18
|
url: string;
|
|
16
19
|
/** Measured at asset reindex: horizontal footprint, footprint center, and lowest Y in model space (pre-scale). Lets the shell auto-center and ground-snap corner-pivot kit models. */
|
|
17
20
|
dims?: ModelDims;
|
|
21
|
+
/** Opt-in compact triangle mesh extracted at asset reindex (see {@link "scene/collisionMesh".CollisionMeshData}) — feeds mesh-accurate hitboxes for concave models. */
|
|
22
|
+
collisionMesh?: CollisionMeshData;
|
|
18
23
|
/** Authored asset-space metadata — canonical facing (degrees), source-unit scale, footprint, anchor, bounds, and rotation policy. The upstream owner of placement corrections; see {@link "scene/assetSpace".AssetSpace}. */
|
|
19
24
|
space?: AssetSpace;
|
|
20
25
|
}
|
package/dist/scene/assetSpace.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clamp } from "../math/scalar.js";
|
|
1
2
|
/**
|
|
2
3
|
* Asset-space metadata + placement rotation policy: the catalog-owned description of how a model sits
|
|
3
4
|
* in its source space and how a placement tool may rotate it. Games author scenes against engine north
|
|
@@ -11,9 +12,6 @@
|
|
|
11
12
|
const DEG2RAD = Math.PI / 180;
|
|
12
13
|
const RAD2DEG = 180 / Math.PI;
|
|
13
14
|
const FULL_TURN = 360;
|
|
14
|
-
function clamp(value, min, max) {
|
|
15
|
-
return value < min ? min : value > max ? max : value;
|
|
16
|
-
}
|
|
17
15
|
/**
|
|
18
16
|
* Wrap `degrees` into `[0, 360)` — the canonical heading range every policy and conversion here works
|
|
19
17
|
* in. Non-finite input collapses to `0` so bad metadata never leaks a `NaN` heading downstream.
|
|
@@ -71,6 +71,23 @@ export declare function getTriggerAction(id: string): TriggerActionDefinition |
|
|
|
71
71
|
export declare function listTriggerActions(target?: TriggerSourceKind): TriggerActionDefinition[];
|
|
72
72
|
/** Clears the action registry — tests only. @internal */
|
|
73
73
|
export declare function clearTriggerActions(): void;
|
|
74
|
+
/**
|
|
75
|
+
* The engine's built-in trigger actions — the shared `announce` / `win` / `advance` vocabulary every
|
|
76
|
+
* game gets for free, so "enter this zone → win" is authored entirely in the editor with no bespoke
|
|
77
|
+
* action code. Their params land flat on the object's `meta` like any registered action; drive them
|
|
78
|
+
* at runtime with the handlers from {@link createTriggerOutcome}.
|
|
79
|
+
*
|
|
80
|
+
* @capability authored-triggers built-in announce/win/advance actions authorable on any marker or volume
|
|
81
|
+
*/
|
|
82
|
+
export declare const BUILTIN_TRIGGER_ACTIONS: readonly TriggerActionDefinition[];
|
|
83
|
+
/**
|
|
84
|
+
* Register the engine's built-in {@link BUILTIN_TRIGGER_ACTIONS} so they appear in the editor's
|
|
85
|
+
* trigger inspector. Idempotent; call at module load (a game may still register its own actions or
|
|
86
|
+
* re-register a built-in id to override it).
|
|
87
|
+
*
|
|
88
|
+
* @capability authored-triggers register the shared announce/win/advance actions
|
|
89
|
+
*/
|
|
90
|
+
export declare function registerBuiltinTriggerActions(): void;
|
|
74
91
|
/** Resolve the proximity radius for a marker trigger from meta. @internal */
|
|
75
92
|
export declare function triggerRadiusOf(meta: Record<string, unknown> | undefined, fallback?: number): number;
|
|
76
93
|
/**
|
|
@@ -156,6 +173,39 @@ export declare function createAuthoredTriggerRuntime(options: {
|
|
|
156
173
|
/** Override the collected trigger list (tests / hot-reload). Default: {@link collectAuthoredTriggers}. */
|
|
157
174
|
triggers?: readonly AuthoredTrigger[];
|
|
158
175
|
}): AuthoredTriggerRuntime;
|
|
176
|
+
/** Serializable read-model the built-in trigger actions write — the HUD/game reads it each frame. */
|
|
177
|
+
export interface TriggerOutcome {
|
|
178
|
+
/** True once a `win` action fired. */
|
|
179
|
+
won: boolean;
|
|
180
|
+
/** Latest announce/win/advance message to surface, or null before any fires. */
|
|
181
|
+
message: string | null;
|
|
182
|
+
/** Tone for the latest message: `"info" | "warn" | "good"`. */
|
|
183
|
+
tone: string;
|
|
184
|
+
/** Current objective id from the latest `advance`, or null. */
|
|
185
|
+
objective: string | null;
|
|
186
|
+
/** Bumped on every change — a cheap key for `useSyncExternalStore`. */
|
|
187
|
+
revision: number;
|
|
188
|
+
}
|
|
189
|
+
/** Outcome store: the built-in action handlers plus a subscribable snapshot of what they produced. */
|
|
190
|
+
export interface TriggerOutcomeStore {
|
|
191
|
+
/** Current outcome snapshot (stable reference until the next change). */
|
|
192
|
+
get(): TriggerOutcome;
|
|
193
|
+
/** Subscribe to outcome changes; returns an unsubscribe fn. */
|
|
194
|
+
subscribe(listener: () => void): () => void;
|
|
195
|
+
/** Handlers for `createAuthoredTriggerRuntime({ handlers })` — wires announce/win/advance. */
|
|
196
|
+
handlers: TriggerHandlers;
|
|
197
|
+
/** Reset to the initial (un-won) outcome — new run / retry. */
|
|
198
|
+
reset(): void;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Default runtime for the built-in {@link BUILTIN_TRIGGER_ACTIONS}: a small serializable outcome the
|
|
202
|
+
* HUD renders, with no engine-side UI. Pass {@link TriggerOutcomeStore.handlers} to
|
|
203
|
+
* {@link createAuthoredTriggerRuntime} and read {@link TriggerOutcomeStore.get} from the HUD via
|
|
204
|
+
* `useSyncExternalStore` — that is the whole zero-code "reach the zone to win" wiring.
|
|
205
|
+
*
|
|
206
|
+
* @capability authored-triggers headless win/announce/objective outcome for the built-in actions
|
|
207
|
+
*/
|
|
208
|
+
export declare function createTriggerOutcome(): TriggerOutcomeStore;
|
|
159
209
|
/**
|
|
160
210
|
* Build a meta patch that sets a single flat trigger (`on` + `action` + optional param fields) and
|
|
161
211
|
* clears a multi-trigger list if present. Used by the editor inspector.
|
|
@@ -41,6 +41,64 @@ export function listTriggerActions(target) {
|
|
|
41
41
|
export function clearTriggerActions() {
|
|
42
42
|
actionRegistry.clear();
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The engine's built-in trigger actions — the shared `announce` / `win` / `advance` vocabulary every
|
|
46
|
+
* game gets for free, so "enter this zone → win" is authored entirely in the editor with no bespoke
|
|
47
|
+
* action code. Their params land flat on the object's `meta` like any registered action; drive them
|
|
48
|
+
* at runtime with the handlers from {@link createTriggerOutcome}.
|
|
49
|
+
*
|
|
50
|
+
* @capability authored-triggers built-in announce/win/advance actions authorable on any marker or volume
|
|
51
|
+
*/
|
|
52
|
+
export const BUILTIN_TRIGGER_ACTIONS = [
|
|
53
|
+
{
|
|
54
|
+
id: "announce",
|
|
55
|
+
label: "Announce",
|
|
56
|
+
schema: {
|
|
57
|
+
fields: [
|
|
58
|
+
{ type: "text", key: "message", label: "Message", default: "Entered zone" },
|
|
59
|
+
{
|
|
60
|
+
type: "select",
|
|
61
|
+
key: "tone",
|
|
62
|
+
label: "Tone",
|
|
63
|
+
options: [
|
|
64
|
+
{ value: "info", label: "Info" },
|
|
65
|
+
{ value: "warn", label: "Warn" },
|
|
66
|
+
{ value: "good", label: "Good" },
|
|
67
|
+
],
|
|
68
|
+
default: "info",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "win",
|
|
75
|
+
label: "Win the game",
|
|
76
|
+
schema: {
|
|
77
|
+
fields: [{ type: "text", key: "message", label: "Message", default: "You win!" }],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "advance",
|
|
82
|
+
label: "Advance objective",
|
|
83
|
+
schema: {
|
|
84
|
+
fields: [
|
|
85
|
+
{ type: "text", key: "objective", label: "Next objective", default: "" },
|
|
86
|
+
{ type: "text", key: "message", label: "Message", default: "" },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
/**
|
|
92
|
+
* Register the engine's built-in {@link BUILTIN_TRIGGER_ACTIONS} so they appear in the editor's
|
|
93
|
+
* trigger inspector. Idempotent; call at module load (a game may still register its own actions or
|
|
94
|
+
* re-register a built-in id to override it).
|
|
95
|
+
*
|
|
96
|
+
* @capability authored-triggers register the shared announce/win/advance actions
|
|
97
|
+
*/
|
|
98
|
+
export function registerBuiltinTriggerActions() {
|
|
99
|
+
for (const definition of BUILTIN_TRIGGER_ACTIONS)
|
|
100
|
+
registerTriggerAction(definition);
|
|
101
|
+
}
|
|
44
102
|
function isTriggerEvent(value) {
|
|
45
103
|
return typeof value === "string" && TRIGGER_EVENTS.includes(value);
|
|
46
104
|
}
|
|
@@ -311,6 +369,62 @@ export function createAuthoredTriggerRuntime(options) {
|
|
|
311
369
|
},
|
|
312
370
|
};
|
|
313
371
|
}
|
|
372
|
+
const INITIAL_OUTCOME = { won: false, message: null, tone: "info", objective: null, revision: 0 };
|
|
373
|
+
/**
|
|
374
|
+
* Default runtime for the built-in {@link BUILTIN_TRIGGER_ACTIONS}: a small serializable outcome the
|
|
375
|
+
* HUD renders, with no engine-side UI. Pass {@link TriggerOutcomeStore.handlers} to
|
|
376
|
+
* {@link createAuthoredTriggerRuntime} and read {@link TriggerOutcomeStore.get} from the HUD via
|
|
377
|
+
* `useSyncExternalStore` — that is the whole zero-code "reach the zone to win" wiring.
|
|
378
|
+
*
|
|
379
|
+
* @capability authored-triggers headless win/announce/objective outcome for the built-in actions
|
|
380
|
+
*/
|
|
381
|
+
export function createTriggerOutcome() {
|
|
382
|
+
let outcome = INITIAL_OUTCOME;
|
|
383
|
+
const listeners = new Set();
|
|
384
|
+
const commit = (next) => {
|
|
385
|
+
outcome = { ...next, revision: outcome.revision + 1 };
|
|
386
|
+
for (const listener of listeners)
|
|
387
|
+
listener();
|
|
388
|
+
};
|
|
389
|
+
const messageOf = (event, fallback) => {
|
|
390
|
+
const value = event.params.message;
|
|
391
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
392
|
+
};
|
|
393
|
+
const stringParam = (event, key) => {
|
|
394
|
+
const value = event.params[key];
|
|
395
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
396
|
+
};
|
|
397
|
+
return {
|
|
398
|
+
get: () => outcome,
|
|
399
|
+
subscribe(listener) {
|
|
400
|
+
listeners.add(listener);
|
|
401
|
+
return () => {
|
|
402
|
+
listeners.delete(listener);
|
|
403
|
+
};
|
|
404
|
+
},
|
|
405
|
+
handlers: {
|
|
406
|
+
announce: (event) => {
|
|
407
|
+
commit({ won: outcome.won, message: messageOf(event, "Entered zone"), tone: stringParam(event, "tone") ?? "info", objective: outcome.objective });
|
|
408
|
+
},
|
|
409
|
+
win: (event) => {
|
|
410
|
+
commit({ won: true, message: messageOf(event, "You win!"), tone: "good", objective: outcome.objective });
|
|
411
|
+
},
|
|
412
|
+
advance: (event) => {
|
|
413
|
+
commit({
|
|
414
|
+
won: outcome.won,
|
|
415
|
+
message: stringParam(event, "message") ?? outcome.message,
|
|
416
|
+
tone: outcome.tone,
|
|
417
|
+
objective: stringParam(event, "objective") ?? outcome.objective,
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
reset() {
|
|
422
|
+
outcome = INITIAL_OUTCOME;
|
|
423
|
+
for (const listener of listeners)
|
|
424
|
+
listener();
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
314
428
|
/**
|
|
315
429
|
* Build a meta patch that sets a single flat trigger (`on` + `action` + optional param fields) and
|
|
316
430
|
* clears a multi-trigger list if present. Used by the editor inspector.
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
* Registers the engine's built-in *environment* scene kinds against the {@link registerSceneKind}
|
|
3
3
|
* seam: `scatter` (the proof adopter — its inspector, `+ Add` entry, and parse now come from the
|
|
4
4
|
* registry), `water` (a reusable parametric water surface), `grass_field` (GPU vertex-wind grass), and
|
|
5
|
-
* `soil` (crack/moss ground-variation patch)
|
|
6
|
-
* Domain studios (
|
|
7
|
-
* code via the same public seam, needing zero
|
|
5
|
+
* `soil` (crack/moss ground-variation patch), and `pole_line` (poles + sagging cables along a path).
|
|
6
|
+
* These are genre-agnostic engine primitives, like terrain. Domain studios (bookcases, …) are NOT
|
|
7
|
+
* built in — they register themselves from example/game code via the same public seam, needing zero
|
|
8
|
+
* engine edits (see `examples/studios`). Call
|
|
8
9
|
* {@link registerBuiltinSceneKinds} once at startup (idempotent).
|
|
9
10
|
*/
|
|
10
11
|
import { SCATTER_PATH_KIND } from "../world/scatterRegion.js";
|
|
11
12
|
import { registerWaterKind } from "../world/waterKind.js";
|
|
12
13
|
import { registerGrassKind } from "../world/grassKind.js";
|
|
13
14
|
import { registerSoilKind } from "../world/soilKind.js";
|
|
15
|
+
import { registerPoleLineKind } from "../world/poleLineKind.js";
|
|
14
16
|
import { registerBuildingGenerator } from "../world/buildingGenerator.js";
|
|
17
|
+
import { registerCityKind } from "../world/cityKind.js";
|
|
15
18
|
import { registerSceneKind } from "./sceneKinds.js";
|
|
16
19
|
/** The scatter/foliage region schema — the fields the inspector exposed as hand-written `ScatterFields`. */
|
|
17
20
|
export const SCATTER_SCHEMA = {
|
|
@@ -58,5 +61,7 @@ export function registerBuiltinSceneKinds() {
|
|
|
58
61
|
registerWaterKind();
|
|
59
62
|
registerGrassKind();
|
|
60
63
|
registerSoilKind();
|
|
64
|
+
registerCityKind();
|
|
65
|
+
registerPoleLineKind();
|
|
61
66
|
registerBuildingGenerator();
|
|
62
67
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import type { ModelDims } from "./assetCatalog.js";
|
|
2
|
+
import { type CollisionMeshData, type PreparedCollisionMesh } from "./collisionMesh.js";
|
|
1
3
|
import type { EntityPosition } from "./entityStore.js";
|
|
2
4
|
export type ColliderPurpose = "physical" | "damage";
|
|
5
|
+
/**
|
|
6
|
+
* Collision geometry in entity-local space. `sphere` and `aabb` are analytic; `mesh` carries a
|
|
7
|
+
* prepared triangle mesh so opted-in concave models raycast their real surface while bounds and
|
|
8
|
+
* broadphase keep reading the conservative `halfExtents`.
|
|
9
|
+
* @capability mesh-hitboxes shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box.
|
|
10
|
+
* @capability mesh-movement the walking player slides against a mesh collider's compound `boxes` — a capsule walks THROUGH an opted-in archway opening and is stopped by its pillars/lintel instead of one outer box.
|
|
11
|
+
*/
|
|
3
12
|
export type ColliderShape = {
|
|
4
13
|
kind: "sphere";
|
|
5
14
|
radius: number;
|
|
@@ -8,6 +17,27 @@ export type ColliderShape = {
|
|
|
8
17
|
kind: "aabb";
|
|
9
18
|
halfExtents: EntityPosition;
|
|
10
19
|
offset?: EntityPosition;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "mesh";
|
|
22
|
+
/** Prepared triangle mesh in model space — engine-derived from an opted-in catalog asset, never hand-authored. */
|
|
23
|
+
mesh: PreparedCollisionMesh;
|
|
24
|
+
/** Uniform model→entity-local scale (same composition as the fitted box). */
|
|
25
|
+
meshScale: number;
|
|
26
|
+
/** Entity-local translation applied after `meshScale`. */
|
|
27
|
+
meshTranslate: EntityPosition;
|
|
28
|
+
/** Conservative entity-local AABB of the placed mesh — bounds/broadphase read this exactly like an `aabb`. */
|
|
29
|
+
halfExtents: EntityPosition;
|
|
30
|
+
/**
|
|
31
|
+
* Optional compact entity-local solid decomposition (from the model's `collisionMesh.boxes`, scaled
|
|
32
|
+
* and translated onto the placement exactly like `mesh`). Movement obstruction slides against these
|
|
33
|
+
* sub-boxes so a capsule walks THROUGH a concave opening; raycasts still use `mesh`. Absent when the
|
|
34
|
+
* model shipped no `boxes`.
|
|
35
|
+
*/
|
|
36
|
+
boxes?: readonly {
|
|
37
|
+
min: EntityPosition;
|
|
38
|
+
max: EntityPosition;
|
|
39
|
+
}[];
|
|
40
|
+
offset?: EntityPosition;
|
|
11
41
|
};
|
|
12
42
|
export interface ColliderDef {
|
|
13
43
|
name: string;
|
|
@@ -49,6 +79,34 @@ export declare function defaultObjectColliders(halfExtents?: EntityPosition): En
|
|
|
49
79
|
* @internal
|
|
50
80
|
*/
|
|
51
81
|
export declare function scaledObjectColliders(scale: readonly [number, number, number]): EntityColliderSet;
|
|
82
|
+
/**
|
|
83
|
+
* The render-config subset collider fitting reads — structurally satisfied by a resolved `ModelConfig`,
|
|
84
|
+
* so the shell can hand its render config straight to the fitting math without a conversion step.
|
|
85
|
+
*/
|
|
86
|
+
export interface ModelBodySource {
|
|
87
|
+
/** Measured model-space bounds from the asset index; fitting requires `dims` with `maxY`. */
|
|
88
|
+
dims?: ModelDims;
|
|
89
|
+
/** Uniform render scale. Default `1`. */
|
|
90
|
+
scale?: number;
|
|
91
|
+
/** Normalize-to-height in world units — fitting composes it exactly like the renderer (`targetHeight / measured height`, multiplied by `scale`). */
|
|
92
|
+
targetHeight?: number;
|
|
93
|
+
/** Vertical render offset added to the placement Y. Default `0`. */
|
|
94
|
+
y?: number;
|
|
95
|
+
/** Placement registration. `"center"` (default) centers the footprint and grounds `minY` on the placement point; `"origin"` renders at the raw model origin. */
|
|
96
|
+
anchor?: "center" | "origin";
|
|
97
|
+
/** Opt-in triangle collision mesh extracted at asset reindex; when present, fitting emits a mesh-accurate shape (rays pass through holes in concave models) instead of the fitted box. */
|
|
98
|
+
collisionMesh?: CollisionMeshData;
|
|
99
|
+
}
|
|
100
|
+
/** Damage hitbox fitted to the rendered model's measured bounds — the model-aware replacement for the
|
|
101
|
+
* fixed humanoid default. `null` when the model is unmeasured, keeping the caller's existing fallback.
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
export declare function fittedEntityColliders(model: ModelBodySource): EntityColliderSet | null;
|
|
105
|
+
/** Blocking physical body fitted to the rendered model's measured bounds — the model-aware replacement
|
|
106
|
+
* for the unit-cube-times-scale guess. `null` when the model is unmeasured.
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
export declare function fittedObjectColliders(model: ModelBodySource): EntityColliderSet | null;
|
|
52
110
|
/** @internal */
|
|
53
111
|
export declare function resolveColliders(set: EntityColliderSet | null | undefined): ResolvedCollider[];
|
|
54
112
|
/** @internal */
|
package/dist/scene/colliders.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareCollisionMesh } from "./collisionMesh.js";
|
|
1
2
|
export const DEFAULT_ENTITY_HIT_RADIUS = 0.5;
|
|
2
3
|
export const DEFAULT_OBJECT_HALF_EXTENTS = [0.5, 0.5, 0.5];
|
|
3
4
|
/** Matches the shell's fallback actor capsule (~0.7m wide, feet at y=0 to head at ~1.8m). */
|
|
@@ -80,6 +81,113 @@ export function scaledObjectColliders(scale) {
|
|
|
80
81
|
},
|
|
81
82
|
};
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The entity-local AABB the rendered model actually occupies, from measured dims + render config — the
|
|
86
|
+
* same scale/normalize/anchor math the shell uses to place the mesh, so box equals visual by
|
|
87
|
+
* construction. `null` when the model is unmeasured (`dims`/`maxY` absent) or degenerate.
|
|
88
|
+
*/
|
|
89
|
+
function fittedModelBox(model) {
|
|
90
|
+
const dims = model.dims;
|
|
91
|
+
if (dims === undefined || dims.maxY === undefined)
|
|
92
|
+
return null;
|
|
93
|
+
const { w, d } = dims.footprint;
|
|
94
|
+
const height = dims.maxY - dims.minY;
|
|
95
|
+
if (!(w > 0) || !(d > 0) || !(height > 0))
|
|
96
|
+
return null;
|
|
97
|
+
if (!Number.isFinite(w) || !Number.isFinite(d) || !Number.isFinite(height))
|
|
98
|
+
return null;
|
|
99
|
+
const normalize = model.targetHeight !== undefined && model.targetHeight > 0 ? model.targetHeight / height : 1;
|
|
100
|
+
const scale = (model.scale ?? 1) * normalize;
|
|
101
|
+
if (!(scale > 0) || !Number.isFinite(scale))
|
|
102
|
+
return null;
|
|
103
|
+
const baseY = model.y ?? 0;
|
|
104
|
+
// The renderer centers/grounds whenever it normalizes to targetHeight or anchors "center" (the
|
|
105
|
+
// default); only a raw-origin model without targetHeight keeps its authored pivot.
|
|
106
|
+
const centered = model.targetHeight !== undefined || (model.anchor ?? "center") === "center";
|
|
107
|
+
const halfExtents = [(w / 2) * scale, (height / 2) * scale, (d / 2) * scale];
|
|
108
|
+
const offset = centered
|
|
109
|
+
? [0, baseY + (height / 2) * scale, 0]
|
|
110
|
+
: [dims.center.x * scale, baseY + (dims.minY + height / 2) * scale, dims.center.z * scale];
|
|
111
|
+
// Maps the model point (center.x, minY, center.z) to the entity-local (0, baseY, 0) the box grounds
|
|
112
|
+
// on when centered; a raw-origin model keeps its authored pivot and only takes the render offset.
|
|
113
|
+
const meshTranslate = centered
|
|
114
|
+
? [-dims.center.x * scale, baseY - dims.minY * scale, -dims.center.z * scale]
|
|
115
|
+
: [0, baseY, 0];
|
|
116
|
+
return { halfExtents, offset, scale, meshTranslate };
|
|
117
|
+
}
|
|
118
|
+
/** Map a model-space point onto entity-local space with the mesh shape's uniform `scale` and per-axis
|
|
119
|
+
* `translate` (`coord * scale + translate`) — the same composition applied to `meshScale`/`meshTranslate`,
|
|
120
|
+
* so a model-space AABB corner lands exactly where the placed mesh puts it. `scale > 0` keeps min<max. */
|
|
121
|
+
function scaleTranslatePoint(point, scale, translate) {
|
|
122
|
+
return [point[0] * scale + translate[0], point[1] * scale + translate[1], point[2] * scale + translate[2]];
|
|
123
|
+
}
|
|
124
|
+
/** The fitted collider shape for a rendered model: a mesh-accurate triangle shape when the model
|
|
125
|
+
* opts in with a decodable `collisionMesh`, otherwise the conservative fitted box. `null` when the
|
|
126
|
+
* model is unmeasured or degenerate.
|
|
127
|
+
*/
|
|
128
|
+
function fittedBodyShape(model) {
|
|
129
|
+
const box = fittedModelBox(model);
|
|
130
|
+
if (box === null)
|
|
131
|
+
return null;
|
|
132
|
+
const mesh = model.collisionMesh !== undefined ? prepareCollisionMesh(model.collisionMesh) : null;
|
|
133
|
+
if (mesh !== null) {
|
|
134
|
+
const sourceBoxes = model.collisionMesh?.boxes;
|
|
135
|
+
const boxes = sourceBoxes !== undefined && sourceBoxes.length > 0
|
|
136
|
+
? sourceBoxes.map((b) => ({
|
|
137
|
+
min: scaleTranslatePoint(b.min, box.scale, box.meshTranslate),
|
|
138
|
+
max: scaleTranslatePoint(b.max, box.scale, box.meshTranslate),
|
|
139
|
+
}))
|
|
140
|
+
: undefined;
|
|
141
|
+
return {
|
|
142
|
+
kind: "mesh",
|
|
143
|
+
mesh,
|
|
144
|
+
meshScale: box.scale,
|
|
145
|
+
meshTranslate: box.meshTranslate,
|
|
146
|
+
halfExtents: box.halfExtents,
|
|
147
|
+
...(boxes !== undefined ? { boxes } : {}),
|
|
148
|
+
offset: box.offset,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return { kind: "aabb", halfExtents: box.halfExtents, offset: box.offset };
|
|
152
|
+
}
|
|
153
|
+
/** Damage hitbox fitted to the rendered model's measured bounds — the model-aware replacement for the
|
|
154
|
+
* fixed humanoid default. `null` when the model is unmeasured, keeping the caller's existing fallback.
|
|
155
|
+
* @internal
|
|
156
|
+
*/
|
|
157
|
+
export function fittedEntityColliders(model) {
|
|
158
|
+
const shape = fittedBodyShape(model);
|
|
159
|
+
if (shape === null)
|
|
160
|
+
return null;
|
|
161
|
+
return {
|
|
162
|
+
hitboxes: [
|
|
163
|
+
{
|
|
164
|
+
name: "body",
|
|
165
|
+
purpose: "damage",
|
|
166
|
+
shape,
|
|
167
|
+
damageEligible: true,
|
|
168
|
+
blocks: false,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Blocking physical body fitted to the rendered model's measured bounds — the model-aware replacement
|
|
174
|
+
* for the unit-cube-times-scale guess. `null` when the model is unmeasured.
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
export function fittedObjectColliders(model) {
|
|
178
|
+
const shape = fittedBodyShape(model);
|
|
179
|
+
if (shape === null)
|
|
180
|
+
return null;
|
|
181
|
+
return {
|
|
182
|
+
body: {
|
|
183
|
+
name: "body",
|
|
184
|
+
purpose: "physical",
|
|
185
|
+
shape,
|
|
186
|
+
damageEligible: false,
|
|
187
|
+
blocks: true,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
83
191
|
/** @internal */
|
|
84
192
|
export function resolveColliders(set) {
|
|
85
193
|
if (set === null || set === undefined)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { EntityPosition } from "./entityStore.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compact, serializable triangle collision mesh measured at asset reindex — the renderer-free source
|
|
4
|
+
* of mesh-accurate hitboxes. Positions are welded onto a 16-bit grid spanning `min`..`max` (model
|
|
5
|
+
* space) and stored base64-encoded, so an opted-in catalog asset ships its collision triangles inside
|
|
6
|
+
* the generated index without the runtime ever touching the rendered scene graph.
|
|
7
|
+
*/
|
|
8
|
+
export interface CollisionMeshData {
|
|
9
|
+
/** Model-space bounds of the encoded vertices — the quantization frame. */
|
|
10
|
+
min: readonly [number, number, number];
|
|
11
|
+
max: readonly [number, number, number];
|
|
12
|
+
/** Welded vertex count; `positions` holds exactly `vertexCount * 3` u16 values. */
|
|
13
|
+
vertexCount: number;
|
|
14
|
+
/** Triangle count; `indices` holds exactly `triangleCount * 3` values. */
|
|
15
|
+
triangleCount: number;
|
|
16
|
+
/** Base64 little-endian u16 xyz triples, each axis normalized 0..65535 across `min`..`max`. */
|
|
17
|
+
positions: string;
|
|
18
|
+
/** Base64 little-endian triangle vertex indices — u16, or u32 when `vertexCount` exceeds 65536. */
|
|
19
|
+
indices: string;
|
|
20
|
+
/**
|
|
21
|
+
* Optional compact model-space solid decomposition from {@link voxelizeToBoxes} — a deterministic set
|
|
22
|
+
* of AABBs approximating the model's filled volume so movement obstruction can slide a capsule against
|
|
23
|
+
* the real pillars/lintel of a concave opening (walk THROUGH an arch) instead of one outer box. Absent
|
|
24
|
+
* for models whose voxelization is empty, keeping box-less assets byte-identical.
|
|
25
|
+
*/
|
|
26
|
+
boxes?: readonly {
|
|
27
|
+
min: readonly [number, number, number];
|
|
28
|
+
max: readonly [number, number, number];
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
/** Raw triangle soup for {@link encodeCollisionMesh}: model-space xyz triples plus triangle indices. */
|
|
32
|
+
export interface CollisionMeshSource {
|
|
33
|
+
positions: ArrayLike<number>;
|
|
34
|
+
indices: ArrayLike<number>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Decoded, BVH-indexed triangles ready for per-ray queries — build once per asset via
|
|
38
|
+
* {@link prepareCollisionMesh}. Treat the fields as opaque; they exist so the raycast hot path can
|
|
39
|
+
* run allocation-free over flat arrays.
|
|
40
|
+
*/
|
|
41
|
+
export interface PreparedCollisionMesh {
|
|
42
|
+
/** Dequantized model-space vertex xyz triples. */
|
|
43
|
+
readonly positions: Float32Array;
|
|
44
|
+
/** Triangle vertex indices, 3 per triangle. */
|
|
45
|
+
readonly indices: Uint32Array;
|
|
46
|
+
/** Per-node AABBs, 6 floats (minXYZ, maxXYZ) per node. */
|
|
47
|
+
readonly nodeBounds: Float32Array;
|
|
48
|
+
/** Leaf: first slot in `triOrder`. Internal: right-child node index (left child is `node + 1`). */
|
|
49
|
+
readonly nodeStart: Int32Array;
|
|
50
|
+
/** Leaf: triangle count (> 0). Internal: 0. */
|
|
51
|
+
readonly nodeCount: Int32Array;
|
|
52
|
+
/** Triangle indices grouped by leaf ranges. */
|
|
53
|
+
readonly triOrder: Uint32Array;
|
|
54
|
+
}
|
|
55
|
+
/** Nearest triangle impact from {@link raycastCollisionMesh}: world distance along the ray and the world-space geometric normal facing the ray. */
|
|
56
|
+
export interface CollisionMeshHit {
|
|
57
|
+
distance: number;
|
|
58
|
+
normal: EntityPosition;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Quantize a triangle soup onto the 16-bit grid, weld coincident vertices, and drop triangles that
|
|
62
|
+
* collapse — the reindex-time compressor producing {@link CollisionMeshData}. Returns `null` when no
|
|
63
|
+
* finite, non-degenerate triangle survives.
|
|
64
|
+
*/
|
|
65
|
+
export declare function encodeCollisionMesh(source: CollisionMeshSource): CollisionMeshData | null;
|
|
66
|
+
/**
|
|
67
|
+
* Decompose a triangle soup into a compact, deterministic set of model-space AABBs approximating its
|
|
68
|
+
* solid volume — the movement-obstruction counterpart to the raycast BVH. Rasterizes triangles onto a
|
|
69
|
+
* `cellSize` voxel grid, fills each vertical column between its lowest and highest occupied voxel (so a
|
|
70
|
+
* solid becomes solid and a concave opening — an archway — keeps its central gap empty while its pillars
|
|
71
|
+
* and lintel fill in), then greedy-merges occupied voxels into AABBs in a fixed z→y→x order. Pure and
|
|
72
|
+
* allocation-tolerant (runs once per asset at reindex). Caps the output at `maxBoxes`, folding any
|
|
73
|
+
* remainder into one enclosing AABB so obstruction stays conservative. Returns `[]` for degenerate input.
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export declare function voxelizeToBoxes(source: CollisionMeshSource, cellSize?: number, maxBoxes?: number): {
|
|
77
|
+
min: [number, number, number];
|
|
78
|
+
max: [number, number, number];
|
|
79
|
+
}[];
|
|
80
|
+
/**
|
|
81
|
+
* Decode a stored collision mesh and index it with a BVH. Memoized per data object, so the shell and
|
|
82
|
+
* a headless host preparing the same catalog entry share one build. Returns `null` (also memoized)
|
|
83
|
+
* when the payload is malformed.
|
|
84
|
+
*/
|
|
85
|
+
export declare function prepareCollisionMesh(data: CollisionMeshData): PreparedCollisionMesh | null;
|
|
86
|
+
/**
|
|
87
|
+
* Nearest triangle hit for a world-space ray against a prepared mesh placed like its rendered model:
|
|
88
|
+
* model space scaled uniformly by `scale`, translated by entity-local `translate`, yaw-rotated by
|
|
89
|
+
* `rotationY`, and moved to `position` — the same composition {@link "scene/colliders".ModelBodySource}
|
|
90
|
+
* fitting applies to the AABB. Allocation-free until the returned hit. `distance` is world distance
|
|
91
|
+
* along `direction` (callers pass `direction` normalized); `normal` is the world-space geometric
|
|
92
|
+
* triangle normal facing the ray.
|
|
93
|
+
*/
|
|
94
|
+
export declare function raycastCollisionMesh(mesh: PreparedCollisionMesh, origin: EntityPosition, direction: EntityPosition, maxDistance: number, position: EntityPosition, rotationY: number, scale: number, translate: EntityPosition): CollisionMeshHit | null;
|