@jgengine/core 0.18.1 → 0.21.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 +504 -7
- package/dist/ai/decisionGraph.d.ts +50 -4
- package/dist/ai/decisionGraph.js +159 -23
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +43 -1
- package/dist/anim/animGraph.js +326 -10
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/locomotionGraph.d.ts +18 -1
- package/dist/anim/locomotionGraph.js +42 -3
- package/dist/audio/audioFalloff.d.ts +6 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +20 -0
- package/dist/combat/abilityKit.js +41 -4
- package/dist/combat/death.d.ts +11 -3
- package/dist/combat/death.js +3 -3
- package/dist/combat/magazine.d.ts +9 -0
- package/dist/combat/magazine.js +24 -2
- package/dist/combat/projectiles.d.ts +94 -2
- package/dist/combat/projectiles.js +282 -61
- package/dist/combat/regenShield.d.ts +14 -0
- package/dist/combat/regenShield.js +23 -7
- package/dist/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/economy/auctionBook.d.ts +1 -1
- package/dist/economy/auctionBook.js +11 -1
- package/dist/economy/listingBook.d.ts +1 -1
- package/dist/economy/listingBook.js +11 -1
- package/dist/economy/resourceLedger.js +3 -3
- package/dist/economy/shopStock.js +7 -6
- package/dist/economy/techTree.js +7 -6
- package/dist/economy/unlockPoints.js +3 -6
- package/dist/editor/commandApply.js +89 -16
- package/dist/editor/commands.d.ts +38 -1
- package/dist/editor/commands.js +320 -3
- package/dist/editor/creatorStorage.d.ts +59 -0
- package/dist/editor/creatorStorage.js +164 -0
- package/dist/editor/document.d.ts +4 -0
- package/dist/editor/document.js +119 -18
- package/dist/editor/environment.js +8 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/liveSync.d.ts +4 -1
- package/dist/editor/liveSync.js +26 -10
- package/dist/editor/materialAuthoring.d.ts +14 -0
- package/dist/editor/materialAuthoring.js +38 -0
- package/dist/editor/modelNodeVisibility.d.ts +11 -0
- package/dist/editor/modelNodeVisibility.js +29 -0
- package/dist/editor/movementCatalog.d.ts +90 -0
- package/dist/editor/movementCatalog.js +144 -0
- package/dist/editor/simulation.d.ts +60 -0
- package/dist/editor/simulation.js +202 -0
- package/dist/editor/staticPrefab.d.ts +17 -0
- package/dist/editor/staticPrefab.js +51 -0
- package/dist/editor/types.d.ts +15 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +54 -2
- package/dist/game/clipRoles.d.ts +2 -1
- package/dist/game/clipRoles.js +7 -3
- package/dist/game/contentValidation.d.ts +69 -0
- package/dist/game/contentValidation.js +118 -0
- package/dist/game/controlGate.d.ts +8 -0
- package/dist/game/controlGate.js +43 -6
- package/dist/game/defineGame.d.ts +32 -3
- package/dist/game/dialogueGraph.d.ts +20 -0
- package/dist/game/dialogueGraph.js +107 -0
- package/dist/game/events.d.ts +11 -0
- package/dist/game/keyValueStore.d.ts +17 -4
- package/dist/game/keyValueStore.js +35 -23
- package/dist/game/lootModifiers.d.ts +17 -0
- package/dist/game/lootModifiers.js +26 -0
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +134 -35
- package/dist/game/quest.d.ts +15 -0
- package/dist/game/quest.js +209 -63
- package/dist/game/questCatalog.d.ts +50 -0
- package/dist/game/questCatalog.js +205 -0
- package/dist/game/saveStore.d.ts +6 -5
- package/dist/game/saveStore.js +57 -57
- package/dist/game/systemRuntime.d.ts +2 -2
- package/dist/game/systemRuntime.js +84 -33
- package/dist/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/input/actionBindings.d.ts +15 -1
- package/dist/input/actionBindings.js +14 -3
- package/dist/input/actionContexts.d.ts +25 -1
- package/dist/input/actionContexts.js +82 -25
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +36 -2
- package/dist/input/gamepadModel.js +69 -30
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -0
- package/dist/interaction/promptRegistry.d.ts +5 -4
- package/dist/interaction/promptRegistry.js +8 -2
- package/dist/inventory/inventoryModel.js +9 -6
- package/dist/inventory/shapedGrid.d.ts +48 -9
- package/dist/inventory/shapedGrid.js +69 -10
- package/dist/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/item/durability.js +4 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -1
- package/dist/item/itemInstanceRegistry.js +111 -7
- package/dist/item/itemgen.d.ts +1 -1
- package/dist/item/itemgen.js +1 -1
- package/dist/item/modularItem.js +7 -1
- package/dist/material/appearanceSignals.d.ts +53 -0
- package/dist/material/appearanceSignals.js +47 -0
- package/dist/material/hairCards.d.ts +47 -0
- package/dist/material/hairCards.js +136 -0
- package/dist/material/materialAsset.d.ts +162 -0
- package/dist/material/materialAsset.js +382 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +444 -1
- package/dist/movement/characterController.d.ts +5 -2
- package/dist/movement/characterController.js +50 -23
- package/dist/movement/freeFlight.d.ts +2 -2
- package/dist/movement/freeFlight.js +3 -3
- package/dist/movement/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +63 -1
- package/dist/movement/movementModel.js +136 -26
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +48 -2
- package/dist/movement/playerMovement.js +493 -87
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +11 -1
- package/dist/movement/solidObstacles.js +48 -3
- package/dist/movement/terrainGrade.d.ts +13 -0
- package/dist/movement/terrainGrade.js +33 -0
- package/dist/movement/voxelController.d.ts +2 -2
- package/dist/movement/voxelController.js +10 -10
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/navMesh.d.ts +43 -2
- package/dist/nav/navMesh.js +534 -128
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +21 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +40 -5
- package/dist/physics/kinematicVehicle.js +17 -2
- package/dist/physics/physicsBackend.d.ts +12 -0
- package/dist/physics/physicsBackendConformance.js +34 -0
- package/dist/physics/physicsWorldBackend.d.ts +1 -0
- package/dist/physics/physicsWorldBackend.js +52 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +375 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/physics/worldColliders.d.ts +1 -1
- package/dist/physics/worldColliders.js +35 -3
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/render/lookPreset.d.ts +9 -2
- package/dist/render/lookPreset.js +29 -2
- package/dist/render/postProcessing.d.ts +30 -0
- package/dist/render/postProcessing.js +1 -1
- package/dist/runtime/cameraDirector.d.ts +54 -3
- package/dist/runtime/cameraDirector.js +187 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/commandInput.d.ts +37 -0
- package/dist/runtime/commandInput.js +58 -0
- package/dist/runtime/commandRunner.d.ts +27 -0
- package/dist/runtime/commandRunner.js +31 -3
- package/dist/runtime/context/combat.d.ts +12 -4
- package/dist/runtime/context/combat.js +179 -11
- package/dist/runtime/context/deathLoot.d.ts +11 -8
- package/dist/runtime/context/deathLoot.js +17 -16
- package/dist/runtime/context/scene.d.ts +2 -0
- package/dist/runtime/context/scene.js +13 -21
- package/dist/runtime/descriptors/baseline.js +34 -3
- package/dist/runtime/descriptors/features.d.ts +2 -1
- package/dist/runtime/descriptors/features.js +18 -0
- package/dist/runtime/gameContext.js +86 -13
- package/dist/runtime/gameContextTypes.d.ts +33 -3
- package/dist/runtime/gameRuntime.d.ts +3 -1
- package/dist/runtime/gameRuntime.js +4 -0
- package/dist/runtime/headlessRunner.d.ts +3 -1
- package/dist/runtime/headlessRunner.js +11 -11
- package/dist/runtime/hostedGameRunner.d.ts +24 -1
- package/dist/runtime/hostedGameRunner.js +156 -10
- package/dist/runtime/hostedWorldSession.d.ts +17 -5
- package/dist/runtime/hostedWorldSession.js +154 -16
- package/dist/runtime/hostedWorldStore.d.ts +2 -2
- package/dist/runtime/inputRecorder.d.ts +1 -1
- package/dist/runtime/inputRecorder.js +21 -1
- package/dist/runtime/inputSnapshot.d.ts +22 -4
- package/dist/runtime/inputSnapshot.js +41 -3
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/runtimeSave.d.ts +10 -6
- package/dist/runtime/runtimeSave.js +11 -5
- package/dist/runtime/simContext.d.ts +8 -3
- package/dist/runtime/simContext.js +7 -3
- package/dist/runtime/simSnapshot.js +2 -0
- package/dist/runtime/transport.d.ts +9 -0
- package/dist/runtime/transport.js +7 -1
- package/dist/runtime/worldReplication.d.ts +2 -0
- package/dist/runtime/worldReplication.js +1 -1
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/behaviorRuntime.d.ts +38 -7
- package/dist/scene/behaviorRuntime.js +293 -30
- package/dist/scene/behaviors.d.ts +35 -2
- package/dist/scene/behaviors.js +29 -0
- package/dist/scene/entityStore.d.ts +14 -3
- package/dist/scene/entityStore.js +45 -9
- package/dist/scene/mount.d.ts +9 -0
- package/dist/scene/mount.js +35 -0
- package/dist/scene/possession.d.ts +2 -0
- package/dist/scene/possession.js +21 -1
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/sceneRaycast.d.ts +7 -0
- package/dist/scene/sceneRaycast.js +55 -0
- package/dist/scene/spatial.d.ts +20 -1
- package/dist/scene/spatial.js +94 -2
- package/dist/scene/vehicleSeat.d.ts +41 -3
- package/dist/scene/vehicleSeat.js +94 -3
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +11 -1
- package/dist/sensor/perception.js +57 -10
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +2 -0
- package/dist/settings/graphicsProfile.js +12 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +10 -3
- package/dist/stats/eventMeter.js +6 -3
- package/dist/store/defineStore.d.ts +3 -1
- package/dist/store/defineStore.js +3 -1
- package/dist/store/observableKeyedStore.d.ts +1 -1
- package/dist/store/observableKeyedStore.js +8 -6
- package/dist/time/simClock.d.ts +6 -2
- package/dist/time/simClock.js +5 -0
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -0
- package/dist/vfx/particleDirector.d.ts +58 -6
- package/dist/vfx/particleDirector.js +113 -22
- package/dist/vfx/particles.d.ts +68 -5
- package/dist/vfx/particles.js +284 -32
- package/dist/visibility/assetStreaming.d.ts +21 -3
- package/dist/visibility/assetStreaming.js +108 -24
- package/dist/visibility/settings.d.ts +8 -2
- package/dist/visibility/settings.js +3 -0
- package/dist/visibility/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -0
- package/dist/visibility/visibilitySystem.js +1 -0
- package/dist/work/expedition.d.ts +110 -0
- package/dist/work/expedition.js +195 -0
- package/dist/work/staffedStation.d.ts +29 -0
- package/dist/work/staffedStation.js +27 -0
- package/dist/world/authoredAnimation.d.ts +38 -0
- package/dist/world/authoredAnimation.js +99 -0
- package/dist/world/authoredObjects.d.ts +24 -4
- package/dist/world/authoredObjects.js +43 -16
- package/dist/world/authoredSimulation.d.ts +74 -0
- package/dist/world/authoredSimulation.js +302 -0
- package/dist/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +11 -4
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/buildings.d.ts +34 -2
- package/dist/world/buildings.js +11 -1
- package/dist/world/cityKind.d.ts +7 -0
- package/dist/world/cityKind.js +52 -1
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +32 -5
- package/dist/world/features.js +6 -0
- package/dist/world/footprintGrid.d.ts +62 -0
- package/dist/world/footprintGrid.js +217 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +9 -1
- package/dist/world/terraform.js +53 -18
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +95 -0
- package/dist/world/worldSolids.js +351 -0
- package/dist/world.d.ts +6 -6
- package/dist/world.js +5 -5
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +28 -6
- package/skills/jgengine/api.md +139 -99
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +10 -2
- package/skills/jgengine-combat/SKILL.md +13 -2
- package/skills/jgengine-combat/api.md +150 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +583 -463
- package/skills/jgengine-gameplay/capabilities.md +92 -0
- package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
- package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
- package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
- package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
- package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
- package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
- package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
- package/skills/jgengine-gameplay/reference-systems.md +123 -0
- package/skills/jgengine-world/SKILL.md +25 -4
- package/skills/jgengine-world/api.md +1045 -655
- package/skills/jgengine-world/capabilities.md +246 -9
- package/skills/jgengine-world/recipes/character-animation.md +62 -0
- package/skills/jgengine-world/recipes/controls.md +48 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +55 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
- package/skills/jgengine-world/reference.md +180 -4
- package/skills/jgengine-world/visibility.md +139 -0
|
@@ -28,6 +28,8 @@ export interface PoseState {
|
|
|
28
28
|
/** Restore every entity's pose and aim from a {@link snapshotAll} payload. */
|
|
29
29
|
hydrateAll(state: PoseSnapshot): void;
|
|
30
30
|
}
|
|
31
|
+
/** Restore one owned pose on synchronous failure; external implementations retain their semantic setter policy. @internal */
|
|
32
|
+
export declare function withPoseRollback<T>(state: PoseState, instanceId: string, operation: () => T): T;
|
|
31
33
|
/**
|
|
32
34
|
* Stance/pose transitions — stand, crouch, prone — that change the hitbox and movement.
|
|
33
35
|
*
|
|
@@ -23,6 +23,32 @@ export const POSE_HITBOX = {
|
|
|
23
23
|
speedMultiplier: MOVEMENT_TUNING.runSpeedMultiplier,
|
|
24
24
|
},
|
|
25
25
|
};
|
|
26
|
+
const ownedPoses = new WeakMap();
|
|
27
|
+
/** Restore one owned pose on synchronous failure; external implementations retain their semantic setter policy. @internal */
|
|
28
|
+
export function withPoseRollback(state, instanceId, operation) {
|
|
29
|
+
const poses = ownedPoses.get(state.getPose);
|
|
30
|
+
const previous = poses === undefined ? state.getPose(instanceId) : poses.get(instanceId);
|
|
31
|
+
try {
|
|
32
|
+
return operation();
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (poses !== undefined) {
|
|
36
|
+
if (previous === undefined)
|
|
37
|
+
poses.delete(instanceId);
|
|
38
|
+
else
|
|
39
|
+
poses.set(instanceId, previous);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
try {
|
|
43
|
+
state.setPose(instanceId, previous);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
26
52
|
/**
|
|
27
53
|
* Stance/pose transitions — stand, crouch, prone — that change the hitbox and movement.
|
|
28
54
|
*
|
|
@@ -37,7 +63,7 @@ export function createPoseState(resolveAllowed) {
|
|
|
37
63
|
function allowedAims(instanceId) {
|
|
38
64
|
return resolveAllowed(instanceId)?.aim ?? ["hip"];
|
|
39
65
|
}
|
|
40
|
-
|
|
66
|
+
const state = {
|
|
41
67
|
getPose(instanceId) {
|
|
42
68
|
return poses.get(instanceId) ?? "standing";
|
|
43
69
|
},
|
|
@@ -74,4 +100,6 @@ export function createPoseState(resolveAllowed) {
|
|
|
74
100
|
aims.set(id, aim);
|
|
75
101
|
},
|
|
76
102
|
};
|
|
103
|
+
ownedPoses.set(state.getPose, poses);
|
|
104
|
+
return state;
|
|
77
105
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GameContext } from "../runtime/gameContext.js";
|
|
2
2
|
import { resolveColliders, type ResolvedCollider } from "../scene/colliders.js";
|
|
3
3
|
import type { EntityPosition } from "../scene/entityStore.js";
|
|
4
|
+
import type { WorldSolid, WorldSolids } from "../world/worldSolids.js";
|
|
4
5
|
import { type CollisionObstacle } from "./movementModel.js";
|
|
5
6
|
/** Largest blocking-collider extent in the scene, split so tower height cannot inflate the XZ query. */
|
|
6
7
|
export interface ObstacleReach {
|
|
@@ -15,6 +16,8 @@ export interface ObstacleReach {
|
|
|
15
16
|
* pure rules package with no `GameContext` to import can satisfy it from its own object list.
|
|
16
17
|
*/
|
|
17
18
|
export interface SolidObstacleSource {
|
|
19
|
+
/** Optional indexed world geometry that is not represented by scene objects. */
|
|
20
|
+
solids?: Pick<WorldSolids, "count" | "inBox">;
|
|
18
21
|
list(): readonly {
|
|
19
22
|
instanceId: string;
|
|
20
23
|
position: EntityPosition;
|
|
@@ -49,9 +52,16 @@ export declare function sourceObstacleReach(source: SolidObstacleSource, cache:
|
|
|
49
52
|
* never under-covers.
|
|
50
53
|
* @internal */
|
|
51
54
|
export declare function obstacleFromCollider(collider: ResolvedCollider, position: EntityPosition, rotationY: number): CollisionObstacle;
|
|
55
|
+
/**
|
|
56
|
+
* A {@link WorldSolid} as a {@link CollisionObstacle}. Axis-aligned solids are one box; a yawed solid is
|
|
57
|
+
* cut into strips along its long axis, each fitted with an AABB, so a turned building does not block
|
|
58
|
+
* the street beside it the way a single enclosing box would.
|
|
59
|
+
* @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share
|
|
60
|
+
*/
|
|
61
|
+
export declare function obstacleFromSolid(solid: WorldSolid): CollisionObstacle;
|
|
52
62
|
/**
|
|
53
63
|
* Every blocking physical obstacle overlapping the box `position` ± `reachX`/`reachZ`, read from the
|
|
54
|
-
* scene's resolved colliders.
|
|
64
|
+
* scene's resolved colliders and `ctx.world.solids`.
|
|
55
65
|
*
|
|
56
66
|
* This is the engine's single answer to "what is solid here". The player resolver, a walking NPC, and
|
|
57
67
|
* anything else that has to stop at a wall read it rather than deriving their own set — three
|
|
@@ -15,7 +15,7 @@ const OBSTACLE_MAX_HALF_EXTENT = 4;
|
|
|
15
15
|
const OBSTACLE_HORIZONTAL_CAP = 22;
|
|
16
16
|
/**
|
|
17
17
|
* Vertical broadphase floor/cap. A walker only needs nearby ground props / step-ups — never full tower
|
|
18
|
-
* height. Using building height for Y was the
|
|
18
|
+
* height. Using building height for Y was the Harbor Heat pose killer: a 50 m tower turned `inBox` into
|
|
19
19
|
* a 100^3 empty cell walk every frame.
|
|
20
20
|
*/
|
|
21
21
|
const OBSTACLE_VERTICAL_FLOOR = 3;
|
|
@@ -26,6 +26,10 @@ const OBSTACLE_VERTICAL_CAP = 8;
|
|
|
26
26
|
* multiply narrowphase cost without changing the blocked footprint.
|
|
27
27
|
*/
|
|
28
28
|
const MOVEMENT_MESH_BOX_BUDGET = 12;
|
|
29
|
+
/** Most AABB strips an oriented world solid is sliced into, bounding the corner overhang a yawed box leaves. */
|
|
30
|
+
const SOLID_MAX_STRIPS = 8;
|
|
31
|
+
/** Yaw within this of a quarter turn is treated as axis-aligned (radians). */
|
|
32
|
+
const AXIS_ALIGNED_EPSILON = 1e-4;
|
|
29
33
|
/** Feet-to-head span a walker is tested over — matches the player's, so both agree on what a wall is. */
|
|
30
34
|
const WALKER_HEIGHT = 1.8;
|
|
31
35
|
/** A fresh {@link ObstacleReachCache} for a caller that owns its own source. */
|
|
@@ -141,9 +145,40 @@ export function obstacleFromCollider(collider, position, rotationY) {
|
|
|
141
145
|
halfExtents: [Math.abs(hx * cos) + Math.abs(hz * sin), hy, Math.abs(hx * sin) + Math.abs(hz * cos)],
|
|
142
146
|
};
|
|
143
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* A {@link WorldSolid} as a {@link CollisionObstacle}. Axis-aligned solids are one box; a yawed solid is
|
|
150
|
+
* cut into strips along its long axis, each fitted with an AABB, so a turned building does not block
|
|
151
|
+
* the street beside it the way a single enclosing box would.
|
|
152
|
+
* @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share
|
|
153
|
+
*/
|
|
154
|
+
export function obstacleFromSolid(solid) {
|
|
155
|
+
const [hx, hy, hz] = solid.halfExtents;
|
|
156
|
+
const yaw = solid.rotationY ?? 0;
|
|
157
|
+
const quarter = yaw / (Math.PI / 2);
|
|
158
|
+
const nearest = Math.round(quarter);
|
|
159
|
+
if (Math.abs(quarter - nearest) < AXIS_ALIGNED_EPSILON) {
|
|
160
|
+
const swapped = Math.abs(nearest) % 2 === 1;
|
|
161
|
+
return { position: solid.center, halfExtents: swapped ? [hz, hy, hx] : [hx, hy, hz] };
|
|
162
|
+
}
|
|
163
|
+
const alongX = hx >= hz;
|
|
164
|
+
const long = alongX ? hx : hz;
|
|
165
|
+
const short = Math.max(alongX ? hz : hx, 1e-6);
|
|
166
|
+
const strips = Math.min(SOLID_MAX_STRIPS, Math.max(1, Math.ceil(long / short)));
|
|
167
|
+
const cos = Math.cos(yaw);
|
|
168
|
+
const sin = Math.sin(yaw);
|
|
169
|
+
const boxes = [];
|
|
170
|
+
for (let i = 0; i < strips; i += 1) {
|
|
171
|
+
const from = -long + (2 * long * i) / strips;
|
|
172
|
+
const to = from + (2 * long) / strips;
|
|
173
|
+
const boxMin = alongX ? [from, -hy, -hz] : [-hx, -hy, from];
|
|
174
|
+
const boxMax = alongX ? [to, hy, hz] : [hx, hy, to];
|
|
175
|
+
boxes.push(yawExpandLocalBox(boxMin, boxMax, cos, sin));
|
|
176
|
+
}
|
|
177
|
+
return { position: solid.center, boxes };
|
|
178
|
+
}
|
|
144
179
|
/**
|
|
145
180
|
* Every blocking physical obstacle overlapping the box `position` ± `reachX`/`reachZ`, read from the
|
|
146
|
-
* scene's resolved colliders.
|
|
181
|
+
* scene's resolved colliders and `ctx.world.solids`.
|
|
147
182
|
*
|
|
148
183
|
* This is the engine's single answer to "what is solid here". The player resolver, a walking NPC, and
|
|
149
184
|
* anything else that has to stop at a wall read it rather than deriving their own set — three
|
|
@@ -153,7 +188,16 @@ export function obstacleFromCollider(collider, position, rotationY) {
|
|
|
153
188
|
* @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share
|
|
154
189
|
*/
|
|
155
190
|
export function solidObstaclesNear(ctx, position, reachX, reachZ, height = WALKER_HEIGHT) {
|
|
156
|
-
|
|
191
|
+
const obstacles = sourceObstaclesNear(ctx.scene.object, solidObstacleReach(ctx), position, reachX, reachZ, height);
|
|
192
|
+
appendWorldSolidObstacles(obstacles, ctx.world.solids, position, reachX, reachZ, height);
|
|
193
|
+
return obstacles;
|
|
194
|
+
}
|
|
195
|
+
function appendWorldSolidObstacles(obstacles, solids, position, reachX, reachZ, height) {
|
|
196
|
+
if (solids === undefined || solids.count() === 0)
|
|
197
|
+
return;
|
|
198
|
+
for (const solid of solids.inBox([position[0] - reachX, position[1] - height, position[2] - reachZ], [position[0] + reachX, position[1] + 2 * height, position[2] + reachZ])) {
|
|
199
|
+
obstacles.push(obstacleFromSolid(solid));
|
|
200
|
+
}
|
|
157
201
|
}
|
|
158
202
|
/** {@link solidObstaclesNear} against a bare source and an already-resolved reach. */
|
|
159
203
|
export function sourceObstaclesNear(source, reach, position, reachX, reachZ, height = WALKER_HEIGHT) {
|
|
@@ -181,6 +225,7 @@ export function sourceObstaclesNear(source, reach, position, reachX, reachZ, hei
|
|
|
181
225
|
obstacles.push(obstacleFromCollider(collider, object.position, object.rotationY));
|
|
182
226
|
}
|
|
183
227
|
}
|
|
228
|
+
appendWorldSolidObstacles(obstacles, source.solids, position, reachX, reachZ, height);
|
|
184
229
|
return obstacles;
|
|
185
230
|
}
|
|
186
231
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrain an uphill heightfield step by its sampled rise/run grade. Try the full step, then
|
|
3
|
+
* X-only, then Z-only, preserving downhill travel and sliding along a traversable axis.
|
|
4
|
+
* The sampler can represent a game's terrain policy independently of the ground used for feet.
|
|
5
|
+
* This endpoint test also applies while jumping; it is not continuous terrain collision.
|
|
6
|
+
* @capability uphill-grade-step constrain sampled heightfield ascent with axis sliding and a configurable terrain-policy sampler
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveTerrainGradeStep(sampler: {
|
|
9
|
+
sampleHeight(x: number, z: number): number;
|
|
10
|
+
} | ((x: number, z: number) => number), position: readonly [number, number, number], stepX: number, stepZ: number, maxClimbGrade: number): {
|
|
11
|
+
stepX: number;
|
|
12
|
+
stepZ: number;
|
|
13
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrain an uphill heightfield step by its sampled rise/run grade. Try the full step, then
|
|
3
|
+
* X-only, then Z-only, preserving downhill travel and sliding along a traversable axis.
|
|
4
|
+
* The sampler can represent a game's terrain policy independently of the ground used for feet.
|
|
5
|
+
* This endpoint test also applies while jumping; it is not continuous terrain collision.
|
|
6
|
+
* @capability uphill-grade-step constrain sampled heightfield ascent with axis sliding and a configurable terrain-policy sampler
|
|
7
|
+
*/
|
|
8
|
+
export function resolveTerrainGradeStep(sampler, position, stepX, stepZ, maxClimbGrade) {
|
|
9
|
+
if (!Number.isFinite(maxClimbGrade) || maxClimbGrade < 0) {
|
|
10
|
+
throw new RangeError("maxClimbGrade must be finite and nonnegative.");
|
|
11
|
+
}
|
|
12
|
+
const startX = position[0];
|
|
13
|
+
const startZ = position[2];
|
|
14
|
+
const sampleHeight = (x, z) => {
|
|
15
|
+
const height = typeof sampler === "function" ? sampler(x, z) : sampler.sampleHeight(x, z);
|
|
16
|
+
if (!Number.isFinite(height)) {
|
|
17
|
+
throw new RangeError(`climbGradeHeight/sampleHeight must return a finite height at (${x}, ${z}); received ${height}.`);
|
|
18
|
+
}
|
|
19
|
+
return height;
|
|
20
|
+
};
|
|
21
|
+
const height = sampleHeight(startX, startZ);
|
|
22
|
+
const allowed = (dx, dz) => {
|
|
23
|
+
const distance = Math.hypot(dx, dz);
|
|
24
|
+
return distance < 0.0001 || (sampleHeight(startX + dx, startZ + dz) - height) / distance <= maxClimbGrade;
|
|
25
|
+
};
|
|
26
|
+
if (allowed(stepX, stepZ))
|
|
27
|
+
return { stepX, stepZ };
|
|
28
|
+
if (allowed(stepX, 0))
|
|
29
|
+
return { stepX, stepZ: 0 };
|
|
30
|
+
if (allowed(0, stepZ))
|
|
31
|
+
return { stepX: 0, stepZ };
|
|
32
|
+
return { stepX: 0, stepZ: 0 };
|
|
33
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* block at cell y therefore has its top face at world y+1, so a player standing
|
|
20
20
|
* on the surface layer (cell y=-1) rests with their feet at y=0.
|
|
21
21
|
*/
|
|
22
|
-
import { type MovementIntent, type MovementTuningOverrides } from "./movementModel.js";
|
|
22
|
+
import { type MovementIntent, type MovementTuningOverrides, type MotionFrameOptions } from "./movementModel.js";
|
|
23
23
|
export type SolidQuery = (x: number, y: number, z: number) => boolean;
|
|
24
24
|
/** Footprint + height of the player box, in world units. */
|
|
25
25
|
export interface VoxelPlayerDims {
|
|
@@ -58,4 +58,4 @@ export type GroundHeightQuery = (x: number, z: number) => number;
|
|
|
58
58
|
* supported (resets jump state like landing on a solid cell). Where terrain and
|
|
59
59
|
* a solid cell's top disagree, the higher of the two wins.
|
|
60
60
|
*/
|
|
61
|
-
export declare function advanceVoxelPlayer(body: VoxelPlayerBody, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, isSolid: SolidQuery, dims?: VoxelPlayerDims, tuning?: MovementTuningOverrides, groundHeight?: GroundHeightQuery): void;
|
|
61
|
+
export declare function advanceVoxelPlayer(body: VoxelPlayerBody, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, isSolid: SolidQuery, dims?: VoxelPlayerDims, tuning?: MovementTuningOverrides, groundHeight?: GroundHeightQuery, options?: Pick<MotionFrameOptions, "authoritativeStep">): void;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* block at cell y therefore has its top face at world y+1, so a player standing
|
|
20
20
|
* on the surface layer (cell y=-1) rests with their feet at y=0.
|
|
21
21
|
*/
|
|
22
|
-
import { MOVEMENT_TUNING } from "./movementModel.js";
|
|
22
|
+
import { MOVEMENT_TUNING, motionStepSeconds } from "./movementModel.js";
|
|
23
23
|
export const DEFAULT_VOXEL_DIMS = {
|
|
24
24
|
halfWidth: 0.3,
|
|
25
25
|
height: 1.8,
|
|
@@ -97,7 +97,7 @@ function supportTopWithin(x, z, fromY, toY, isSolid, dims) {
|
|
|
97
97
|
}
|
|
98
98
|
return best;
|
|
99
99
|
}
|
|
100
|
-
function targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed) {
|
|
100
|
+
function targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed, tuning) {
|
|
101
101
|
if (!intent.moving)
|
|
102
102
|
return { x: 0, z: 0 };
|
|
103
103
|
let fx = forwardX;
|
|
@@ -120,9 +120,9 @@ function targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed) {
|
|
|
120
120
|
if (magSq < 1e-6)
|
|
121
121
|
return { x: 0, z: 0 };
|
|
122
122
|
const speedMultiplier = intent.crouching
|
|
123
|
-
? MOVEMENT_TUNING.crouchSpeedMultiplier
|
|
123
|
+
? (tuning?.crouchSpeedMultiplier ?? MOVEMENT_TUNING.crouchSpeedMultiplier)
|
|
124
124
|
: intent.running
|
|
125
|
-
? MOVEMENT_TUNING.runSpeedMultiplier
|
|
125
|
+
? (tuning?.runSpeedMultiplier ?? MOVEMENT_TUNING.runSpeedMultiplier)
|
|
126
126
|
: 1;
|
|
127
127
|
const targetSpeed = baseSpeed * MOVEMENT_TUNING.walkSpeedMultiplier * speedMultiplier;
|
|
128
128
|
const scale = targetSpeed / Math.sqrt(magSq);
|
|
@@ -172,19 +172,19 @@ function moveAxis(body, axis, delta, isSolid, dims) {
|
|
|
172
172
|
* supported (resets jump state like landing on a solid cell). Where terrain and
|
|
173
173
|
* a solid cell's top disagree, the higher of the two wins.
|
|
174
174
|
*/
|
|
175
|
-
export function advanceVoxelPlayer(body, intent, forwardX, forwardZ, baseSpeed, rawDeltaSeconds, isSolid, dims = DEFAULT_VOXEL_DIMS, tuning, groundHeight) {
|
|
176
|
-
const dt =
|
|
175
|
+
export function advanceVoxelPlayer(body, intent, forwardX, forwardZ, baseSpeed, rawDeltaSeconds, isSolid, dims = DEFAULT_VOXEL_DIMS, tuning, groundHeight, options) {
|
|
176
|
+
const dt = motionStepSeconds(rawDeltaSeconds, options?.authoritativeStep);
|
|
177
177
|
const gravityAcceleration = tuning?.gravityAcceleration ?? MOVEMENT_TUNING.gravityAcceleration;
|
|
178
178
|
const jumpVelocity = tuning?.jumpVelocity ?? MOVEMENT_TUNING.jumpVelocity;
|
|
179
|
-
const target = targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed);
|
|
179
|
+
const target = targetHorizontalVelocity(intent, forwardX, forwardZ, baseSpeed, tuning);
|
|
180
180
|
const acceleration = body.grounded
|
|
181
|
-
? MOVEMENT_TUNING.groundAcceleration
|
|
182
|
-
: MOVEMENT_TUNING.airAcceleration;
|
|
181
|
+
? (tuning?.groundAcceleration ?? MOVEMENT_TUNING.groundAcceleration)
|
|
182
|
+
: (tuning?.airAcceleration ?? MOVEMENT_TUNING.airAcceleration);
|
|
183
183
|
const accelerationBlend = 1 - Math.exp(-acceleration * dt);
|
|
184
184
|
body.velocityX += (target.x - body.velocityX) * accelerationBlend;
|
|
185
185
|
body.velocityZ += (target.z - body.velocityZ) * accelerationBlend;
|
|
186
186
|
if (!intent.moving && body.grounded) {
|
|
187
|
-
const friction = Math.exp(-MOVEMENT_TUNING.groundFriction * dt);
|
|
187
|
+
const friction = Math.exp(-(tuning?.groundFriction ?? MOVEMENT_TUNING.groundFriction) * dt);
|
|
188
188
|
body.velocityX *= friction;
|
|
189
189
|
body.velocityZ *= friction;
|
|
190
190
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { EnvironmentWorldFeature } from "../world/features.js";
|
|
2
2
|
import type { Aabb } from "../world/geometry.js";
|
|
3
|
+
import { type WorldSolids } from "../world/worldSolids.js";
|
|
3
4
|
export interface NavObstacleGrid {
|
|
4
5
|
blockAabb(aabb: Aabb): void;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* number of buildings blocked.
|
|
8
|
+
* Blocks the footprint of every solid building an environment feature's `structures` generate — the
|
|
9
|
+
* same set `ctx.world.solids` holds for it. Returns the number of buildings blocked.
|
|
10
10
|
*/
|
|
11
11
|
export declare function populateNavGridFromEnvironment(grid: NavObstacleGrid, world: EnvironmentWorldFeature): number;
|
|
12
|
+
/**
|
|
13
|
+
* Blocks the XZ footprint of every solid in `solids` (usually `ctx.world.solids`) on `grid`, so NPC
|
|
14
|
+
* paths avoid exactly what movement and physics collide with. Returns the number of solids blocked.
|
|
15
|
+
* @capability nav-from-solids block world solids on a nav grid
|
|
16
|
+
*/
|
|
17
|
+
export declare function populateNavGridFromSolids(grid: NavObstacleGrid, solids: Pick<WorldSolids, "all">): number;
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { structureSolids, worldSolidFootprint } from "../world/worldSolids.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* number of buildings blocked.
|
|
3
|
+
* Blocks the footprint of every solid building an environment feature's `structures` generate — the
|
|
4
|
+
* same set `ctx.world.solids` holds for it. Returns the number of buildings blocked.
|
|
6
5
|
*/
|
|
7
6
|
export function populateNavGridFromEnvironment(grid, world) {
|
|
8
|
-
|
|
9
|
-
for (const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const solids = structureSolids(world);
|
|
8
|
+
for (const solid of solids)
|
|
9
|
+
grid.blockAabb(worldSolidFootprint(solid));
|
|
10
|
+
return solids.length;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Blocks the XZ footprint of every solid in `solids` (usually `ctx.world.solids`) on `grid`, so NPC
|
|
14
|
+
* paths avoid exactly what movement and physics collide with. Returns the number of solids blocked.
|
|
15
|
+
* @capability nav-from-solids block world solids on a nav grid
|
|
16
|
+
*/
|
|
17
|
+
export function populateNavGridFromSolids(grid, solids) {
|
|
18
|
+
const all = solids.all();
|
|
19
|
+
for (const solid of all)
|
|
20
|
+
grid.blockAabb(worldSolidFootprint(solid));
|
|
21
|
+
return all.length;
|
|
16
22
|
}
|
package/dist/nav/navGrid.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface FindPathOptions {
|
|
|
17
17
|
clearance?: number;
|
|
18
18
|
/** Multiplier (>=1) applied to the base cost of stepping from one cell center to a neighbour's. Use to penalize slopes, terrain, etc. */
|
|
19
19
|
stepCost?: (from: NavPoint, to: NavPoint) => number;
|
|
20
|
+
/** Additional edge/shortcut policy. Called after grid occupancy checks. */
|
|
21
|
+
canTraverse?: (from: NavPoint, to: NavPoint) => boolean;
|
|
22
|
+
/** Maximum expanded cells. Omit for the legacy whole-grid search. */
|
|
23
|
+
maxNodes?: number;
|
|
20
24
|
}
|
|
21
25
|
export interface NavGrid {
|
|
22
26
|
readonly cols: number;
|
|
@@ -44,6 +48,20 @@ export declare function createNavGrid(config: NavGridConfig): NavGrid;
|
|
|
44
48
|
* the nearest walkable cell so a click on an obstacle still routes to its edge.
|
|
45
49
|
*/
|
|
46
50
|
export declare function findPath(grid: NavGrid, from: NavPoint, to: NavPoint, options?: FindPathOptions): NavPoint[] | null;
|
|
51
|
+
/** Bounded grid search distinguishes exhausted work from a proven unreachable goal. */
|
|
52
|
+
export type FindPathResult = {
|
|
53
|
+
status: "path";
|
|
54
|
+
points: NavPoint[];
|
|
55
|
+
visited: number;
|
|
56
|
+
} | {
|
|
57
|
+
status: "no-path" | "budget";
|
|
58
|
+
visited: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Same endpoint snapping as {@link findPath}, with explicit search-budget exhaustion.
|
|
62
|
+
* @capability bounded-grid-route distinguish no path from exhausted grid search work
|
|
63
|
+
*/
|
|
64
|
+
export declare function findPathResult(grid: NavGrid, from: NavPoint, to: NavPoint, options?: FindPathOptions): FindPathResult;
|
|
47
65
|
/**
|
|
48
66
|
* `FindPathOptions.stepCost` factory that penalizes steep terrain: cost is
|
|
49
67
|
* `1 + weight * |Δheight| / horizontalDistance`, so with the default weight a
|
|
@@ -53,4 +71,4 @@ export declare function slopeStepCost(field: {
|
|
|
53
71
|
sampleHeight(x: number, z: number): number;
|
|
54
72
|
}, weight?: number): (from: NavPoint, to: NavPoint) => number;
|
|
55
73
|
/** Remove waypoints the mover can skip because it has clear line-of-sight past them. */
|
|
56
|
-
export declare function smoothPath(grid: NavGrid, points: readonly NavPoint[]): NavPoint[];
|
|
74
|
+
export declare function smoothPath(grid: NavGrid, points: readonly NavPoint[], canTraverse?: (from: NavPoint, to: NavPoint) => boolean): NavPoint[];
|
package/dist/nav/navGrid.js
CHANGED
|
@@ -174,14 +174,27 @@ function nearestPassable(grid, point, passable) {
|
|
|
174
174
|
* the nearest walkable cell so a click on an obstacle still routes to its edge.
|
|
175
175
|
*/
|
|
176
176
|
export function findPath(grid, from, to, options = {}) {
|
|
177
|
+
const result = findPathResult(grid, from, to, options);
|
|
178
|
+
return result.status === "path" ? result.points : null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Same endpoint snapping as {@link findPath}, with explicit search-budget exhaustion.
|
|
182
|
+
* @capability bounded-grid-route distinguish no path from exhausted grid search work
|
|
183
|
+
*/
|
|
184
|
+
export function findPathResult(grid, from, to, options = {}) {
|
|
185
|
+
const maxNodes = options.maxNodes ?? Number.POSITIVE_INFINITY;
|
|
186
|
+
if (!(maxNodes >= 0))
|
|
187
|
+
throw new Error("navGrid maxNodes must be nonnegative.");
|
|
177
188
|
const passable = withClearance(grid, options.clearance ?? 0);
|
|
178
189
|
const stepCost = options.stepCost;
|
|
179
190
|
const startCell = grid.cellAt(from);
|
|
180
191
|
const goalCell = grid.cellAt(to);
|
|
181
192
|
const start = passable(startCell.col, startCell.row) ? startCell : nearestPassable(grid, from, passable);
|
|
182
|
-
const
|
|
193
|
+
const exactGoal = passable(goalCell.col, goalCell.row);
|
|
194
|
+
const goal = exactGoal ? goalCell : nearestPassable(grid, to, passable);
|
|
183
195
|
if (start === null || goal === null)
|
|
184
|
-
return
|
|
196
|
+
return { status: "no-path", visited: 0 };
|
|
197
|
+
const pointAt = (col, row) => exactGoal && col === goal.col && row === goal.row ? to : grid.center(col, row);
|
|
185
198
|
const cols = grid.cols;
|
|
186
199
|
const keyOf = (col, row) => row * cols + col;
|
|
187
200
|
const startKey = keyOf(start.col, start.row);
|
|
@@ -222,6 +235,8 @@ export function findPath(grid, from, to, options = {}) {
|
|
|
222
235
|
found = true;
|
|
223
236
|
break;
|
|
224
237
|
}
|
|
238
|
+
if (closed.size >= maxNodes)
|
|
239
|
+
return { status: "budget", visited: closed.size };
|
|
225
240
|
closed.add(current.key);
|
|
226
241
|
const col = current.key % cols;
|
|
227
242
|
const row = (current.key - col) / cols;
|
|
@@ -236,7 +251,9 @@ export function findPath(grid, from, to, options = {}) {
|
|
|
236
251
|
const nKey = keyOf(nc, nr);
|
|
237
252
|
if (closed.has(nKey))
|
|
238
253
|
continue;
|
|
239
|
-
|
|
254
|
+
if (options.canTraverse !== undefined && !options.canTraverse(pointAt(col, row), pointAt(nc, nr)))
|
|
255
|
+
continue;
|
|
256
|
+
const multiplier = stepCost === undefined ? 1 : Math.max(1, stepCost(pointAt(col, row), pointAt(nc, nr)));
|
|
240
257
|
const tentative = g + step * multiplier;
|
|
241
258
|
if (tentative < (gScore.get(nKey) ?? Number.POSITIVE_INFINITY)) {
|
|
242
259
|
gScore.set(nKey, tentative);
|
|
@@ -246,7 +263,7 @@ export function findPath(grid, from, to, options = {}) {
|
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
if (!found)
|
|
249
|
-
return
|
|
266
|
+
return { status: "no-path", visited: closed.size };
|
|
250
267
|
const cells = [];
|
|
251
268
|
let cursor = goalKey;
|
|
252
269
|
while (cursor !== undefined) {
|
|
@@ -254,10 +271,9 @@ export function findPath(grid, from, to, options = {}) {
|
|
|
254
271
|
cells.unshift({ col, row: (cursor - col) / cols });
|
|
255
272
|
cursor = cameFrom.get(cursor);
|
|
256
273
|
}
|
|
257
|
-
const raw = cells.map((cell) =>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return options.smooth === false ? raw : smoothPath(grid, raw);
|
|
274
|
+
const raw = cells.map((cell) => [...pointAt(cell.col, cell.row)]);
|
|
275
|
+
const points = options.smooth === false ? raw : smoothPath(grid, raw, options.canTraverse);
|
|
276
|
+
return { status: "path", points, visited: closed.size };
|
|
261
277
|
}
|
|
262
278
|
const DEFAULT_SLOPE_STEP_WEIGHT = 1;
|
|
263
279
|
/**
|
|
@@ -275,13 +291,13 @@ export function slopeStepCost(field, weight = DEFAULT_SLOPE_STEP_WEIGHT) {
|
|
|
275
291
|
};
|
|
276
292
|
}
|
|
277
293
|
/** Remove waypoints the mover can skip because it has clear line-of-sight past them. */
|
|
278
|
-
export function smoothPath(grid, points) {
|
|
294
|
+
export function smoothPath(grid, points, canTraverse) {
|
|
279
295
|
if (points.length <= 2)
|
|
280
296
|
return points.slice();
|
|
281
297
|
const result = [points[0]];
|
|
282
298
|
let anchor = 0;
|
|
283
299
|
for (let i = 2; i < points.length; i += 1) {
|
|
284
|
-
if (!grid.lineOfSight(points[anchor], points[i])) {
|
|
300
|
+
if (!grid.lineOfSight(points[anchor], points[i]) || (canTraverse !== undefined && !canTraverse(points[anchor], points[i]))) {
|
|
285
301
|
result.push(points[i - 1]);
|
|
286
302
|
anchor = i - 1;
|
|
287
303
|
}
|
package/dist/nav/navMesh.d.ts
CHANGED
|
@@ -4,13 +4,19 @@ export interface NavMeshLink {
|
|
|
4
4
|
from: number;
|
|
5
5
|
to: number;
|
|
6
6
|
cost?: number;
|
|
7
|
+
/** Where the link leaves `from`; defaults to that polygon's center. */
|
|
8
|
+
start?: Vec3;
|
|
9
|
+
/** Where the link lands in `to`; defaults to that polygon's center. */
|
|
10
|
+
end?: Vec3;
|
|
7
11
|
}
|
|
8
|
-
/** Serializable polygon navigation mesh data. */
|
|
12
|
+
/** Serializable polygon navigation mesh data. Treat it as immutable once queried; queries cache derived geometry per object. */
|
|
9
13
|
export interface NavMeshData {
|
|
10
14
|
/** Flat xyz vertex coordinates. Polygon indices refer to triples in this array. */
|
|
11
15
|
verts: number[];
|
|
16
|
+
/** Convex polygons as vertex index lists. */
|
|
12
17
|
polys: number[][];
|
|
13
18
|
links: NavMeshLink[];
|
|
19
|
+
/** Area id per polygon, priced by {@link NavMeshQueryOptions.areaCosts}. */
|
|
14
20
|
areas?: number[];
|
|
15
21
|
}
|
|
16
22
|
/** Neighbor relationship for one navigation polygon. */
|
|
@@ -22,12 +28,47 @@ export interface NavMeshAdjacency {
|
|
|
22
28
|
export interface NavMeshPath {
|
|
23
29
|
points: Vec3[];
|
|
24
30
|
polys: number[];
|
|
31
|
+
/** True when the search budget ran out and the route ends at the polygon nearest the goal. */
|
|
32
|
+
partial?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Tunable pricing and bounds for a {@link NavMeshQuery}. */
|
|
35
|
+
export interface NavMeshQueryOptions {
|
|
36
|
+
/** Cost multiplier per area id; unlisted areas cost 1, and a negative or non-finite cost blocks the area. */
|
|
37
|
+
areaCosts?: Readonly<Record<number, number>>;
|
|
38
|
+
/** Most polygons one path search may expand before returning a partial path. Default 2048. */
|
|
39
|
+
maxNodes?: number;
|
|
40
|
+
}
|
|
41
|
+
/** Serializable tuning of a {@link NavMeshQuery}. */
|
|
42
|
+
export interface NavMeshQuerySnapshot {
|
|
43
|
+
areaCosts: Record<number, number>;
|
|
44
|
+
maxNodes: number;
|
|
45
|
+
}
|
|
46
|
+
/** Cached, allocation-light queries over one {@link NavMeshData}. */
|
|
47
|
+
export interface NavMeshQuery {
|
|
48
|
+
readonly mesh: NavMeshData;
|
|
49
|
+
/** Polygon nearest the point in 3D, so stacked floors resolve by height; null for an empty mesh. */
|
|
50
|
+
findPolygon(point: Vec3): number | null;
|
|
51
|
+
/** Closest point on the mesh surface, or null for an empty mesh. */
|
|
52
|
+
closestPoint(point: Vec3): Vec3 | null;
|
|
53
|
+
/** Cheapest polygon corridor by A*, straightened with a portal funnel. */
|
|
54
|
+
findPath(from: Vec3, to: Vec3): NavMeshPath | null;
|
|
55
|
+
/** True when the straight segment stays on walkable, unblocked polygons. */
|
|
56
|
+
raycast(from: Vec3, to: Vec3): boolean;
|
|
57
|
+
retune(patch: NavMeshQueryOptions): void;
|
|
58
|
+
snapshot(): NavMeshQuerySnapshot;
|
|
59
|
+
restore(next: NavMeshQuerySnapshot): void;
|
|
25
60
|
}
|
|
26
61
|
/** Build polygon adjacency from shared edges and explicit off-mesh links. */
|
|
27
62
|
export declare function buildNavAdjacency(mesh: NavMeshData): NavMeshAdjacency[];
|
|
63
|
+
/**
|
|
64
|
+
* Create a cached query over a nav mesh: height-aware polygon lookup through a uniform grid, binary-heap A*
|
|
65
|
+
* priced by area costs and bounded by `maxNodes`, portal-funnel path straightening, and edge-walking raycasts.
|
|
66
|
+
* @capability navmesh-query route, snap, and raycast on a polygon navmesh with retunable area costs and a bounded search
|
|
67
|
+
*/
|
|
68
|
+
export declare function createNavMeshQuery(mesh: NavMeshData, options?: NavMeshQueryOptions): NavMeshQuery;
|
|
28
69
|
/** Return the closest point on the mesh surface, or null for an empty mesh. */
|
|
29
70
|
export declare function closestPoint(mesh: NavMeshData, point: Vec3): Vec3 | null;
|
|
30
|
-
/** A* over polygon
|
|
71
|
+
/** A* over polygon portals with default area costs, straightened by a portal funnel. Use {@link createNavMeshQuery} to price areas. */
|
|
31
72
|
export declare function findPath(mesh: NavMeshData, from: Vec3, to: Vec3): NavMeshPath | null;
|
|
32
73
|
/** True when the segment remains over walkable polygons. */
|
|
33
74
|
export declare function raycastNav(mesh: NavMeshData, from: Vec3, to: Vec3): boolean;
|