@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
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { steerYaw } from "../movement/steering.js";
|
|
2
2
|
import { DEFAULT_GRIP_CURVE, sampleGripCurve } from "./vehicleBody.js";
|
|
3
|
+
/**
|
|
4
|
+
* Arcade ground-vehicle sim (car, kart, bike) with throttle, brake, steer, drift and retunable handling.
|
|
5
|
+
*
|
|
6
|
+
* @capability kinematic-vehicle arcade car/kart driving physics with throttle, brake, steer and retunable handling
|
|
7
|
+
*/
|
|
3
8
|
export function createKinematicVehicle(initialTuning, options = {}) {
|
|
4
9
|
// Mutable so `retune` can swap the whole block without disturbing the integrator's state; every
|
|
5
10
|
// read below is per-tick rather than captured at construction for the same reason.
|
|
@@ -231,8 +236,8 @@ export function createKinematicVehicle(initialTuning, options = {}) {
|
|
|
231
236
|
const toZ = z + vz * dt;
|
|
232
237
|
if (clampMove !== undefined && dt > 0) {
|
|
233
238
|
const allowed = clampMove([x, z], [toX, toZ]);
|
|
234
|
-
vx = (allowed[0] - x) / dt;
|
|
235
|
-
vz = (allowed[1] - z) / dt;
|
|
239
|
+
vx = (allowed.motion?.[0] ?? allowed[0] - x) / dt;
|
|
240
|
+
vz = (allowed.motion?.[1] ?? allowed[1] - z) / dt;
|
|
236
241
|
x = allowed[0];
|
|
237
242
|
z = allowed[1];
|
|
238
243
|
}
|
|
@@ -309,6 +314,16 @@ export function createKinematicVehicle(initialTuning, options = {}) {
|
|
|
309
314
|
retune(next) {
|
|
310
315
|
tuning = next;
|
|
311
316
|
},
|
|
317
|
+
snapshot: () => ({
|
|
318
|
+
x, y, z, heading, vx, vz, gearIndex, rpm, shiftRemaining, steerAngle, yawRate,
|
|
319
|
+
previousForwardSpeed, bodyPitch, bodyRoll, airOffset, verticalVelocity,
|
|
320
|
+
}),
|
|
321
|
+
restore(state) {
|
|
322
|
+
({
|
|
323
|
+
x, y, z, heading, vx, vz, gearIndex, rpm, shiftRemaining, steerAngle, yawRate,
|
|
324
|
+
previousForwardSpeed, bodyPitch, bodyRoll, airOffset, verticalVelocity,
|
|
325
|
+
} = state);
|
|
326
|
+
},
|
|
312
327
|
resetTo(position, nextHeading) {
|
|
313
328
|
x = position[0];
|
|
314
329
|
y = position[1];
|
|
@@ -52,6 +52,10 @@ export interface BodyDesc {
|
|
|
52
52
|
mask?: number;
|
|
53
53
|
/** Continuous collision detection for fast small bodies (bullets, thrown props). */
|
|
54
54
|
ccd?: boolean;
|
|
55
|
+
/** Per-second linear velocity damping on this body (air drag, water); default `0`. */
|
|
56
|
+
linearDamping?: number;
|
|
57
|
+
/** Per-second angular velocity damping on this body; default `0`. Ignored by rotation-free backends. */
|
|
58
|
+
angularDamping?: number;
|
|
55
59
|
asleep?: boolean;
|
|
56
60
|
userData?: unknown;
|
|
57
61
|
}
|
|
@@ -170,6 +174,14 @@ export interface PhysicsBackend {
|
|
|
170
174
|
setVelocity(handle: BodyHandle, velocity: PhysicsVec3): void;
|
|
171
175
|
setAngularVelocity(handle: BodyHandle, angularVelocity: PhysicsVec3): void;
|
|
172
176
|
applyImpulse(handle: BodyHandle, impulse: PhysicsVec3, point?: PhysicsVec3): void;
|
|
177
|
+
/**
|
|
178
|
+
* Apply a world-space force, N, over the next {@link step} only (thrust, wind, buoyancy, a tow rope). Call it
|
|
179
|
+
* every tick the force should act. A `point` off the centre of mass also applies torque on rotation backends.
|
|
180
|
+
* Pending forces are not part of a snapshot.
|
|
181
|
+
*/
|
|
182
|
+
applyForce(handle: BodyHandle, force: PhysicsVec3, point?: PhysicsVec3): void;
|
|
183
|
+
/** Apply a world-space torque, N·m, over the next {@link step} only; a no-op on rotation-free backends. */
|
|
184
|
+
applyTorque(handle: BodyHandle, torque: PhysicsVec3): void;
|
|
173
185
|
/** Move a kinematic body so the solver sees its velocity over the next step (platforms, doors, hands). */
|
|
174
186
|
setKinematicTarget(handle: BodyHandle, position: PhysicsVec3, rotation?: PhysicsQuat): void;
|
|
175
187
|
wake(handle: BodyHandle): void;
|
|
@@ -109,6 +109,40 @@ export function runPhysicsBackendConformance(create, harness) {
|
|
|
109
109
|
const later = backend.addBody({ shape: { kind: "sphere", radius: 0.3 }, position: [2, 2, 2] });
|
|
110
110
|
expect(later).not.toBe(crate);
|
|
111
111
|
});
|
|
112
|
+
test("applyForce accelerates at F/m for one step and then stops acting", () => {
|
|
113
|
+
const backend = create();
|
|
114
|
+
const held = backend.addBody({ shape: { kind: "sphere", radius: 0.5 }, position: [0, 20, 0], mass: 2 });
|
|
115
|
+
const once = backend.addBody({ shape: { kind: "sphere", radius: 0.5 }, position: [10, 20, 0], mass: 2 });
|
|
116
|
+
backend.applyForce(once, [4, 0, 0]);
|
|
117
|
+
for (let i = 0; i < 60; i += 1) {
|
|
118
|
+
backend.applyForce(held, [4, 0, 0]);
|
|
119
|
+
backend.step(1 / 60);
|
|
120
|
+
}
|
|
121
|
+
expect(backend.body(held).velocity[0]).toBeGreaterThan(1.6);
|
|
122
|
+
expect(backend.body(held).velocity[0]).toBeLessThan(2.2);
|
|
123
|
+
expect(backend.body(once).velocity[0]).toBeGreaterThan(0.02);
|
|
124
|
+
expect(backend.body(once).velocity[0]).toBeLessThan(0.05);
|
|
125
|
+
});
|
|
126
|
+
test("linear damping bleeds a body's speed faster than an undamped twin", () => {
|
|
127
|
+
const backend = create();
|
|
128
|
+
const damped = backend.addBody({ shape: { kind: "sphere", radius: 0.5 }, position: [0, 30, 0], velocity: [10, 0, 0], linearDamping: 1.5 });
|
|
129
|
+
const free = backend.addBody({ shape: { kind: "sphere", radius: 0.5 }, position: [0, 30, 20], velocity: [10, 0, 0] });
|
|
130
|
+
settle(backend, 1);
|
|
131
|
+
expect(backend.body(damped).velocity[0]).toBeLessThan(backend.body(free).velocity[0] * 0.5);
|
|
132
|
+
});
|
|
133
|
+
test("applyTorque spins a body on rotation backends and is harmless elsewhere", () => {
|
|
134
|
+
const backend = create();
|
|
135
|
+
const wheel = backend.addBody({ shape: { kind: "box", halfExtents: [0.5, 0.5, 0.5] }, position: [0, 30, 0] });
|
|
136
|
+
for (let i = 0; i < 30; i += 1) {
|
|
137
|
+
backend.applyTorque(wheel, [0, 2, 0]);
|
|
138
|
+
backend.step(1 / 60);
|
|
139
|
+
}
|
|
140
|
+
const spin = backend.body(wheel).angularVelocity[1];
|
|
141
|
+
if (backend.capabilities.rotation)
|
|
142
|
+
expect(spin).toBeGreaterThan(0.1);
|
|
143
|
+
else
|
|
144
|
+
expect(spin).toBe(0);
|
|
145
|
+
});
|
|
112
146
|
test("capabilities describe the backend", () => {
|
|
113
147
|
const backend = create();
|
|
114
148
|
expect(backend.capabilities.shapes.length).toBeGreaterThan(1);
|
|
@@ -21,6 +21,7 @@ interface SavedBody {
|
|
|
21
21
|
position: [number, number, number];
|
|
22
22
|
velocity: [number, number, number];
|
|
23
23
|
sleeping: boolean;
|
|
24
|
+
linearDamping?: number;
|
|
24
25
|
}
|
|
25
26
|
/** Serializable backend state: every live body and joint, with handles preserved. */
|
|
26
27
|
export interface PhysicsWorldBackendState {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { IDENTITY_QUAT, isIdentityQuat, shapeHalfExtents, } from "./physicsBackend.js";
|
|
2
2
|
import { PhysicsWorld } from "./physicsWorld.js";
|
|
3
|
+
/** Exact touching is not overlap; account for the same few-ulp face rounding as shapecast. */
|
|
4
|
+
function axisOverlaps(center, bodyCenter, combinedHalf) {
|
|
5
|
+
const tolerance = Number.EPSILON * 8 * Math.max(1, Math.abs(center), Math.abs(bodyCenter - combinedHalf), Math.abs(bodyCenter + combinedHalf));
|
|
6
|
+
return combinedHalf - Math.abs(bodyCenter - center) > tolerance;
|
|
7
|
+
}
|
|
3
8
|
const ALL_LAYERS = 0xffffffff;
|
|
4
9
|
const CAPABILITIES = {
|
|
5
10
|
rotation: false,
|
|
@@ -132,6 +137,8 @@ export function createPhysicsWorldBackend(options) {
|
|
|
132
137
|
half,
|
|
133
138
|
kinematicVelocity: [0, 0, 0],
|
|
134
139
|
kinematicTarget: null,
|
|
140
|
+
linearDamping: desc.linearDamping ?? 0,
|
|
141
|
+
force: [0, 0, 0],
|
|
135
142
|
});
|
|
136
143
|
handleOfIndex.set(index, handle);
|
|
137
144
|
masses.set(handle, desc.mass ?? 1);
|
|
@@ -248,6 +255,17 @@ export function createPhysicsWorldBackend(options) {
|
|
|
248
255
|
const invMass = world.invMass[i];
|
|
249
256
|
world.setVelocity(i, world.velX[i] + impulse[0] * invMass, world.velY[i] + impulse[1] * invMass, world.velZ[i] + impulse[2] * invMass);
|
|
250
257
|
},
|
|
258
|
+
applyForce(handle, force) {
|
|
259
|
+
const rec = record(handle);
|
|
260
|
+
if (rec.kind !== "dynamic")
|
|
261
|
+
return;
|
|
262
|
+
rec.force[0] += force[0];
|
|
263
|
+
rec.force[1] += force[1];
|
|
264
|
+
rec.force[2] += force[2];
|
|
265
|
+
},
|
|
266
|
+
applyTorque(handle) {
|
|
267
|
+
record(handle);
|
|
268
|
+
},
|
|
251
269
|
setKinematicTarget(handle, position) {
|
|
252
270
|
const rec = record(handle);
|
|
253
271
|
rec.kinematicTarget = [position[0], position[1], position[2]];
|
|
@@ -288,6 +306,24 @@ export function createPhysicsWorldBackend(options) {
|
|
|
288
306
|
world.setPosition(i, target[0], target[1], target[2]);
|
|
289
307
|
rec.kinematicTarget = null;
|
|
290
308
|
}
|
|
309
|
+
for (const rec of records.values()) {
|
|
310
|
+
if (rec.kind !== "dynamic")
|
|
311
|
+
continue;
|
|
312
|
+
const f = rec.force;
|
|
313
|
+
const i = rec.index;
|
|
314
|
+
if (f[0] !== 0 || f[1] !== 0 || f[2] !== 0) {
|
|
315
|
+
const scale = world.invMass[i] * dt;
|
|
316
|
+
world.setVelocity(i, world.velX[i] + f[0] * scale, world.velY[i] + f[1] * scale, world.velZ[i] + f[2] * scale);
|
|
317
|
+
world.wake(i);
|
|
318
|
+
f[0] = 0;
|
|
319
|
+
f[1] = 0;
|
|
320
|
+
f[2] = 0;
|
|
321
|
+
}
|
|
322
|
+
if (rec.linearDamping > 0) {
|
|
323
|
+
const keep = Math.max(0, 1 - rec.linearDamping * dt);
|
|
324
|
+
world.setVelocity(i, world.velX[i] * keep, world.velY[i] * keep, world.velZ[i] * keep);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
291
327
|
world.step(dt);
|
|
292
328
|
},
|
|
293
329
|
raycast(desc) {
|
|
@@ -377,6 +413,17 @@ export function createPhysicsWorldBackend(options) {
|
|
|
377
413
|
least === 2 ? -1 : least === 3 ? 1 : 0,
|
|
378
414
|
least === 4 ? -1 : least === 5 ? 1 : 0,
|
|
379
415
|
];
|
|
416
|
+
// Expanding an AABB by a capsule's half-height can round exact floor contact inside
|
|
417
|
+
// by a few ulps. Keep entering contacts and real penetration, but allow departing or
|
|
418
|
+
// tangent motion through this numerical touch so it cannot mask a later wall/ceiling.
|
|
419
|
+
const faceScale = least < 2
|
|
420
|
+
? Math.max(1, Math.abs(ox), Math.abs(minX), Math.abs(maxX))
|
|
421
|
+
: least < 4
|
|
422
|
+
? Math.max(1, Math.abs(oy), Math.abs(minY), Math.abs(maxY))
|
|
423
|
+
: Math.max(1, Math.abs(oz), Math.abs(minZ), Math.abs(maxZ));
|
|
424
|
+
const touchTolerance = Number.EPSILON * 8 * faceScale;
|
|
425
|
+
if (motionLen > 0 && pens[least] <= touchTolerance && mx * normal[0] + my * normal[1] + mz * normal[2] >= 0)
|
|
426
|
+
continue;
|
|
380
427
|
}
|
|
381
428
|
else {
|
|
382
429
|
if (!(motionLen > 0))
|
|
@@ -406,11 +453,11 @@ export function createPhysicsWorldBackend(options) {
|
|
|
406
453
|
if (!bodyMatches(rec, desc.mask, desc.exclude, handle))
|
|
407
454
|
continue;
|
|
408
455
|
const i = rec.index;
|
|
409
|
-
if (
|
|
456
|
+
if (!axisOverlaps(desc.position[0], world.posX[i], rec.half[0] + half[0]))
|
|
410
457
|
continue;
|
|
411
|
-
if (
|
|
458
|
+
if (!axisOverlaps(desc.position[1], world.posY[i], rec.half[1] + half[1]))
|
|
412
459
|
continue;
|
|
413
|
-
if (
|
|
460
|
+
if (!axisOverlaps(desc.position[2], world.posZ[i], rec.half[2] + half[2]))
|
|
414
461
|
continue;
|
|
415
462
|
out.push(handle);
|
|
416
463
|
}
|
|
@@ -437,6 +484,7 @@ export function createPhysicsWorldBackend(options) {
|
|
|
437
484
|
position: [world.posX[i], world.posY[i], world.posZ[i]],
|
|
438
485
|
velocity: [world.velX[i], world.velY[i], world.velZ[i]],
|
|
439
486
|
sleeping: world.isSleeping(i),
|
|
487
|
+
...(rec.linearDamping > 0 ? { linearDamping: rec.linearDamping } : {}),
|
|
440
488
|
});
|
|
441
489
|
}
|
|
442
490
|
return {
|
|
@@ -465,6 +513,7 @@ export function createPhysicsWorldBackend(options) {
|
|
|
465
513
|
layers: body.layers,
|
|
466
514
|
mask: body.mask,
|
|
467
515
|
userData: body.userData,
|
|
516
|
+
...(body.linearDamping === undefined ? {} : { linearDamping: body.linearDamping }),
|
|
468
517
|
}, body.sleeping);
|
|
469
518
|
}
|
|
470
519
|
for (const joint of saved.joints) {
|
|
@@ -36,6 +36,6 @@ export declare class SpatialGrid {
|
|
|
36
36
|
rebuild(count: number, xs: Float32Array, zs: Float32Array): void;
|
|
37
37
|
/** Fill `out` with every entity within `radius` of (x,z); returns the hit count (capped at `out.length`). */
|
|
38
38
|
queryCircle(x: number, z: number, radius: number, out: Int32Array): number;
|
|
39
|
-
/** Invoke `cb(a, b)` once for each unordered pair within `maxDistance
|
|
39
|
+
/** Invoke `cb(a, b)` once for each unordered pair within finite, nonnegative `maxDistance`, including distances larger than `cellSize`. */
|
|
40
40
|
forEachPair(maxDistance: number, cb: (a: number, b: number) => void): void;
|
|
41
41
|
}
|
|
@@ -103,25 +103,24 @@ export class SpatialGrid {
|
|
|
103
103
|
}
|
|
104
104
|
return n;
|
|
105
105
|
}
|
|
106
|
-
/** Invoke `cb(a, b)` once for each unordered pair within `maxDistance
|
|
106
|
+
/** Invoke `cb(a, b)` once for each unordered pair within finite, nonnegative `maxDistance`, including distances larger than `cellSize`. */
|
|
107
107
|
forEachPair(maxDistance, cb) {
|
|
108
|
+
if (!Number.isFinite(maxDistance) || maxDistance < 0) {
|
|
109
|
+
throw new RangeError("SpatialGrid: maxDistance must be finite and nonnegative");
|
|
110
|
+
}
|
|
108
111
|
const xs = this.xs;
|
|
109
112
|
const zs = this.zs;
|
|
110
113
|
if (xs === null || zs === null)
|
|
111
114
|
return;
|
|
112
115
|
const d2 = maxDistance * maxDistance;
|
|
113
116
|
const nx = this.nx;
|
|
114
|
-
const nz = this.nz;
|
|
115
117
|
for (let i = 0; i < this.entityCount; i += 1) {
|
|
116
|
-
const c = this.cellOfBody[i];
|
|
117
|
-
const cz = (c / nx) | 0;
|
|
118
|
-
const cx = c - cz * nx;
|
|
119
118
|
const x = xs[i];
|
|
120
119
|
const z = zs[i];
|
|
121
|
-
const z0 =
|
|
122
|
-
const z1 =
|
|
123
|
-
const x0 =
|
|
124
|
-
const x1 =
|
|
120
|
+
const z0 = this.cellZ(z - maxDistance);
|
|
121
|
+
const z1 = this.cellZ(z + maxDistance);
|
|
122
|
+
const x0 = this.cellX(x - maxDistance);
|
|
123
|
+
const x1 = this.cellX(x + maxDistance);
|
|
125
124
|
for (let gz = z0; gz <= z1; gz += 1) {
|
|
126
125
|
const rowBase = gz * nx;
|
|
127
126
|
for (let gx = x0; gx <= x1; gx += 1) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BodyHandle, PhysicsBackend, PhysicsVec3 } from "./physicsBackend.js";
|
|
2
|
+
/** Options for {@link createVehicleBackendLink}. */
|
|
3
|
+
export interface VehicleBackendLinkOptions {
|
|
4
|
+
/** Chassis collision box half extents, m (width/2, height/2, length/2), heading along +z. */
|
|
5
|
+
halfExtents: PhysicsVec3;
|
|
6
|
+
/** Gap kept between the box bottom and the car's base height, so the cast never scrapes the ground (default `0.1`). */
|
|
7
|
+
clearance?: number;
|
|
8
|
+
/** Collision layer bitmask of the chassis body (default `1`). */
|
|
9
|
+
layers?: number;
|
|
10
|
+
/** Layers the chassis body collides with and shoves in the solver (default all). */
|
|
11
|
+
mask?: number;
|
|
12
|
+
/** Layers that stop the car in `clampMove` (default all). Leave props off it so the car shoves them instead of stopping. */
|
|
13
|
+
blockMask?: number;
|
|
14
|
+
/** Contact normals steeper than this `y` count as ground, not walls, and never block (default `0.7`). */
|
|
15
|
+
groundNormalY?: number;
|
|
16
|
+
}
|
|
17
|
+
/** The pose fields {@link VehicleBackendLink.sync} reads; any vehicle step (`VehicleDynamicsStep`, `KinematicVehicleStep`) fits. */
|
|
18
|
+
export interface VehicleLinkPose {
|
|
19
|
+
position: readonly [number, number, number];
|
|
20
|
+
heading: number;
|
|
21
|
+
bodyPitch?: number;
|
|
22
|
+
bodyRoll?: number;
|
|
23
|
+
}
|
|
24
|
+
/** A wall the last clamped move ran into. */
|
|
25
|
+
export interface VehicleLinkHit {
|
|
26
|
+
body: BodyHandle;
|
|
27
|
+
normal: PhysicsVec3;
|
|
28
|
+
/** Metres of this move the wall absorbed; divide by `dt` for the impact speed. */
|
|
29
|
+
blocked: number;
|
|
30
|
+
}
|
|
31
|
+
/** A vehicle sim's presence in a physics backend: a kinematic chassis that shoves props, and a wall-aware move clamp. */
|
|
32
|
+
export interface VehicleBackendLink {
|
|
33
|
+
readonly body: BodyHandle;
|
|
34
|
+
/** Pass as the sim's `clampMove` option: sweeps the chassis box and stops at walls, sliding along them. */
|
|
35
|
+
clampMove(from: readonly [number, number], to: readonly [number, number]): readonly [number, number];
|
|
36
|
+
/** Move the kinematic chassis to the sim's pose; call after each sim tick, before the backend steps. */
|
|
37
|
+
sync(pose: VehicleLinkPose): void;
|
|
38
|
+
/** The wall the most recent `clampMove` hit, or `null`. */
|
|
39
|
+
lastHit(): VehicleLinkHit | null;
|
|
40
|
+
dispose(): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Puts a vehicle sim (`createVehicleDynamics`, `createKinematicVehicle`) into a {@link PhysicsBackend} world without
|
|
44
|
+
* handing its handling to the rigid-body solver: the sim still owns grip and balance, while a kinematic chassis box
|
|
45
|
+
* follows it so Rapier or `PhysicsWorld` props get shoved, and `clampMove` sweeps that box so walls and parked cars
|
|
46
|
+
* stop it and it slides along them.
|
|
47
|
+
* @capability vehicle-backend-link collide a vehicle sim with a physics backend — shove props, stop at walls, slide along them
|
|
48
|
+
*/
|
|
49
|
+
export declare function createVehicleBackendLink(backend: PhysicsBackend, options: VehicleBackendLinkOptions): VehicleBackendLink;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Yaw about +y (heading), then pitch about the car's x, then roll about its z, as a quaternion. */
|
|
2
|
+
function poseQuat(heading, pitch, roll) {
|
|
3
|
+
const hy = heading / 2;
|
|
4
|
+
const hp = pitch / 2;
|
|
5
|
+
const hr = roll / 2;
|
|
6
|
+
const cy = Math.cos(hy);
|
|
7
|
+
const sy = Math.sin(hy);
|
|
8
|
+
const cp = Math.cos(hp);
|
|
9
|
+
const sp = Math.sin(hp);
|
|
10
|
+
const cr = Math.cos(hr);
|
|
11
|
+
const sr = Math.sin(hr);
|
|
12
|
+
return [
|
|
13
|
+
cy * sp * cr + sy * cp * sr,
|
|
14
|
+
sy * cp * cr - cy * sp * sr,
|
|
15
|
+
cy * cp * sr - sy * sp * cr,
|
|
16
|
+
cy * cp * cr + sy * sp * sr,
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Puts a vehicle sim (`createVehicleDynamics`, `createKinematicVehicle`) into a {@link PhysicsBackend} world without
|
|
21
|
+
* handing its handling to the rigid-body solver: the sim still owns grip and balance, while a kinematic chassis box
|
|
22
|
+
* follows it so Rapier or `PhysicsWorld` props get shoved, and `clampMove` sweeps that box so walls and parked cars
|
|
23
|
+
* stop it and it slides along them.
|
|
24
|
+
* @capability vehicle-backend-link collide a vehicle sim with a physics backend — shove props, stop at walls, slide along them
|
|
25
|
+
*/
|
|
26
|
+
export function createVehicleBackendLink(backend, options) {
|
|
27
|
+
const half = options.halfExtents;
|
|
28
|
+
const clearance = options.clearance ?? 0.1;
|
|
29
|
+
const groundNormalY = options.groundNormalY ?? 0.7;
|
|
30
|
+
const shape = { kind: "box", halfExtents: half };
|
|
31
|
+
const pose = { baseY: 0, heading: 0, pitch: 0, roll: 0 };
|
|
32
|
+
const hit = { active: false, value: { body: -1, normal: [0, 0, 0], blocked: 0 } };
|
|
33
|
+
const body = backend.addBody({
|
|
34
|
+
shape,
|
|
35
|
+
kind: "kinematic",
|
|
36
|
+
position: [0, half[1] + clearance, 0],
|
|
37
|
+
layers: options.layers ?? 1,
|
|
38
|
+
...(options.mask === undefined ? {} : { mask: options.mask }),
|
|
39
|
+
});
|
|
40
|
+
function sweep(x, z, dx, dz) {
|
|
41
|
+
return backend.shapecast({
|
|
42
|
+
shape,
|
|
43
|
+
position: [x, pose.baseY + half[1] + clearance, z],
|
|
44
|
+
rotation: poseQuat(pose.heading, 0, 0),
|
|
45
|
+
motion: [dx, 0, dz],
|
|
46
|
+
exclude: body,
|
|
47
|
+
...(options.blockMask === undefined ? {} : { mask: options.blockMask }),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
body,
|
|
52
|
+
clampMove(from, to) {
|
|
53
|
+
hit.active = false;
|
|
54
|
+
let x = from[0];
|
|
55
|
+
let z = from[1];
|
|
56
|
+
let dx = to[0] - x;
|
|
57
|
+
let dz = to[1] - z;
|
|
58
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
59
|
+
const length = Math.hypot(dx, dz);
|
|
60
|
+
if (length < 1e-9)
|
|
61
|
+
break;
|
|
62
|
+
const found = sweep(x, z, dx, dz);
|
|
63
|
+
if (found === null || found.normal[1] > groundNormalY) {
|
|
64
|
+
x += dx;
|
|
65
|
+
z += dz;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
const travel = Math.max(0, found.toi - 0.02 / length);
|
|
69
|
+
x += dx * travel;
|
|
70
|
+
z += dz * travel;
|
|
71
|
+
const nx = found.normal[0];
|
|
72
|
+
const nz = found.normal[2];
|
|
73
|
+
const flat = Math.hypot(nx, nz) || 1;
|
|
74
|
+
const restX = dx * (1 - travel);
|
|
75
|
+
const restZ = dz * (1 - travel);
|
|
76
|
+
const into = (restX * nx + restZ * nz) / flat;
|
|
77
|
+
hit.active = true;
|
|
78
|
+
hit.value = { body: found.body, normal: [nx / flat, 0, nz / flat], blocked: Math.abs(into) };
|
|
79
|
+
dx = restX - (into * nx) / flat;
|
|
80
|
+
dz = restZ - (into * nz) / flat;
|
|
81
|
+
}
|
|
82
|
+
return [x, z];
|
|
83
|
+
},
|
|
84
|
+
sync(next) {
|
|
85
|
+
pose.baseY = next.position[1];
|
|
86
|
+
pose.heading = next.heading;
|
|
87
|
+
pose.pitch = next.bodyPitch ?? 0;
|
|
88
|
+
pose.roll = next.bodyRoll ?? 0;
|
|
89
|
+
const center = [next.position[0], next.position[1] + half[1] + clearance, next.position[2]];
|
|
90
|
+
const rotation = poseQuat(pose.heading, pose.pitch, pose.roll);
|
|
91
|
+
backend.setKinematicTarget(body, center, rotation);
|
|
92
|
+
// A resting prop sleeps, and a kinematic body moved by target does not wake it on every backend.
|
|
93
|
+
for (const touched of backend.overlap({ shape, position: center, rotation, exclude: body }))
|
|
94
|
+
backend.wake(touched);
|
|
95
|
+
},
|
|
96
|
+
lastHit: () => (hit.active ? hit.value : null),
|
|
97
|
+
dispose() {
|
|
98
|
+
if (backend.hasBody(body))
|
|
99
|
+
backend.removeBody(body);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|