@jgengine/core 0.8.0 → 0.10.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 +36 -0
- package/dist/ai/flock.d.ts +36 -0
- package/dist/ai/flock.js +182 -0
- package/dist/ai/groupAssist.d.ts +19 -0
- package/dist/ai/groupAssist.js +59 -0
- package/dist/ai/laneSelect.d.ts +21 -0
- package/dist/ai/laneSelect.js +37 -0
- package/dist/ai/mobBrain.d.ts +68 -0
- package/dist/ai/mobBrain.js +115 -0
- package/dist/ai/spawnDirector.d.ts +13 -0
- package/dist/ai/spawnDirector.js +41 -17
- package/dist/ai/spawnPoint.d.ts +5 -0
- package/dist/ai/spawnPoint.js +5 -0
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- package/dist/audio/audioFalloff.d.ts +5 -2
- package/dist/audio/music.d.ts +53 -0
- package/dist/audio/music.js +34 -0
- package/dist/audio/synth.d.ts +60 -0
- package/dist/audio/synth.js +8 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +14 -0
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/combat/abilityKit.d.ts +17 -2
- package/dist/combat/abilityKit.js +62 -13
- package/dist/combat/castRunner.d.ts +40 -0
- package/dist/combat/castRunner.js +56 -0
- package/dist/combat/comboPoints.d.ts +14 -0
- package/dist/combat/comboPoints.js +52 -0
- package/dist/combat/defensiveWindow.d.ts +1 -1
- package/dist/combat/defensiveWindow.js +5 -3
- package/dist/combat/dotField.d.ts +43 -0
- package/dist/combat/dotField.js +48 -0
- package/dist/combat/projectiles.d.ts +33 -12
- package/dist/combat/projectiles.js +214 -129
- package/dist/combat/regenShield.d.ts +35 -0
- package/dist/combat/regenShield.js +45 -0
- package/dist/combat/resistance.d.ts +16 -4
- package/dist/combat/resistance.js +43 -7
- package/dist/combat/resourcePool.d.ts +20 -0
- package/dist/combat/resourcePool.js +55 -0
- package/dist/combat/shotOrigin.d.ts +52 -0
- package/dist/combat/shotOrigin.js +86 -0
- package/dist/combat/telegraph.d.ts +25 -0
- package/dist/combat/telegraph.js +47 -0
- package/dist/crafting/recipe.js +4 -1
- package/dist/devtools/devtools.d.ts +72 -24
- package/dist/devtools/devtools.js +449 -101
- package/dist/devtools/transformTunables.js +3 -3
- package/dist/devtools/tunableSchema.d.ts +140 -0
- package/dist/devtools/tunableSchema.js +476 -0
- package/dist/editor/commands.d.ts +65 -0
- package/dist/editor/commands.js +193 -0
- package/dist/editor/document.d.ts +40 -0
- package/dist/editor/document.js +153 -0
- package/dist/editor/index.d.ts +30 -0
- package/dist/editor/index.js +6 -0
- package/dist/editor/types.d.ts +69 -0
- package/dist/editor/types.js +29 -0
- package/dist/faction/factions.d.ts +33 -0
- package/dist/faction/factions.js +76 -0
- package/dist/faction/reputation.d.ts +32 -0
- package/dist/faction/reputation.js +82 -0
- package/dist/game/chat.d.ts +2 -0
- package/dist/game/chat.js +9 -1
- package/dist/game/chatFilter.d.ts +15 -0
- package/dist/game/chatFilter.js +55 -0
- package/dist/game/connectedPlayers.d.ts +28 -0
- package/dist/game/connectedPlayers.js +21 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +44 -1
- package/dist/game/defineGame.js +1 -0
- package/dist/game/events.d.ts +25 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/keyValueStore.d.ts +25 -0
- package/dist/game/keyValueStore.js +61 -0
- package/dist/game/levelSequence.d.ts +24 -2
- package/dist/game/levelSequence.js +51 -2
- package/dist/game/lootTable.d.ts +14 -1
- package/dist/game/lootTable.js +28 -7
- package/dist/game/modelAnimation.d.ts +8 -0
- package/dist/game/modelAnimation.js +18 -0
- package/dist/game/playableGame.d.ts +116 -4
- package/dist/game/playableGame.js +1 -1
- package/dist/game/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +39 -1
- package/dist/game/race.js +96 -11
- package/dist/game/recordBook.d.ts +27 -0
- package/dist/game/recordBook.js +86 -0
- package/dist/game/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/input/actionBindings.d.ts +2 -0
- package/dist/input/actionBindings.js +1 -0
- package/dist/input/axisInput.d.ts +33 -3
- package/dist/input/axisInput.js +60 -4
- package/dist/input/bindingOverrides.d.ts +25 -0
- package/dist/input/bindingOverrides.js +95 -0
- package/dist/input/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +6 -0
- package/dist/input/touchScheme.js +45 -7
- package/dist/interaction/contextMenu.d.ts +1 -0
- package/dist/interaction/contextMenu.js +1 -0
- package/dist/interaction/proximityPrompt.d.ts +3 -1
- package/dist/interaction/proximityPrompt.js +3 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +44 -1
- package/dist/movement/dash.d.ts +3 -0
- package/dist/movement/dash.js +19 -5
- package/dist/movement/glideModel.d.ts +44 -0
- package/dist/movement/glideModel.js +54 -0
- package/dist/movement/grappleSwing.d.ts +34 -0
- package/dist/movement/grappleSwing.js +56 -0
- package/dist/movement/leaderTrail.d.ts +29 -0
- package/dist/movement/leaderTrail.js +67 -0
- package/dist/movement/movementModel.d.ts +4 -0
- package/dist/movement/movementModel.js +13 -0
- package/dist/movement/playerMovement.d.ts +41 -0
- package/dist/movement/playerMovement.js +181 -0
- package/dist/movement/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- package/dist/multiplayer/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -0
- package/dist/multiplayer/presenceModel.d.ts +14 -2
- package/dist/multiplayer/presenceModel.js +15 -3
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -0
- package/dist/nav/navGrid.js +30 -4
- package/dist/nav/railGraph.d.ts +66 -0
- package/dist/nav/railGraph.js +129 -0
- package/dist/nav/timetable.d.ts +41 -0
- package/dist/nav/timetable.js +94 -0
- package/dist/physics/buoyancy.d.ts +6 -0
- package/dist/physics/buoyancy.js +14 -0
- package/dist/physics/flowTube.d.ts +32 -0
- package/dist/physics/flowTube.js +64 -0
- package/dist/physics/forceVolume.d.ts +27 -0
- package/dist/physics/forceVolume.js +38 -0
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +13 -6
- package/dist/physics/physicsWorld.js +169 -40
- package/dist/physics/ragdoll.js +1 -1
- package/dist/physics/vehicleBody.js +2 -1
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +2 -0
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/render/postProcessing.d.ts +56 -0
- package/dist/render/postProcessing.js +1 -0
- package/dist/runtime/adapter.d.ts +19 -0
- package/dist/runtime/adapter.js +11 -5
- package/dist/runtime/cameraDirector.d.ts +6 -1
- package/dist/runtime/cameraDirector.js +6 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +72 -8
- package/dist/runtime/gameContext.js +374 -61
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +9 -1
- package/dist/runtime/hostPersistence.js +23 -8
- package/dist/runtime/hostedGameRunner.d.ts +49 -0
- package/dist/runtime/hostedGameRunner.js +62 -0
- package/dist/runtime/hostedWorldSession.d.ts +65 -0
- package/dist/runtime/hostedWorldSession.js +57 -0
- package/dist/runtime/inputSnapshot.d.ts +18 -0
- package/dist/runtime/inputSnapshot.js +7 -0
- package/dist/runtime/motionIntents.d.ts +16 -6
- package/dist/runtime/motionIntents.js +31 -2
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- package/dist/runtime/visibility.d.ts +74 -0
- package/dist/runtime/visibility.js +127 -0
- package/dist/runtime/worldChannel.d.ts +58 -0
- package/dist/runtime/worldChannel.js +69 -0
- package/dist/runtime/worldMirror.d.ts +24 -0
- package/dist/runtime/worldMirror.js +31 -0
- package/dist/runtime/worldReplication.d.ts +46 -0
- package/dist/runtime/worldReplication.js +185 -0
- package/dist/runtime/worldSnapshot.d.ts +17 -0
- package/dist/runtime/worldSnapshot.js +15 -0
- package/dist/scene/assetCatalog.d.ts +1 -0
- package/dist/scene/colliders.d.ts +50 -0
- package/dist/scene/colliders.js +101 -0
- package/dist/scene/entityStats.d.ts +4 -0
- package/dist/scene/entityStats.js +21 -0
- package/dist/scene/entityStore.d.ts +51 -5
- package/dist/scene/entityStore.js +119 -12
- package/dist/scene/objectQuery.d.ts +13 -2
- package/dist/scene/objectQuery.js +34 -2
- package/dist/scene/objectStore.d.ts +2 -0
- package/dist/scene/objectStore.js +31 -12
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +11 -16
- package/dist/scene/spatial.js +99 -38
- package/dist/sensor/recordingBuffer.d.ts +6 -0
- package/dist/sensor/recordingBuffer.js +7 -0
- package/dist/sensor/replayLoop.d.ts +39 -0
- package/dist/sensor/replayLoop.js +64 -0
- package/dist/sensor/visionCone.d.ts +31 -0
- package/dist/sensor/visionCone.js +72 -0
- package/dist/session/extraction.d.ts +2 -2
- package/dist/session/extraction.js +13 -2
- package/dist/settings/settingsModel.d.ts +97 -0
- package/dist/settings/settingsModel.js +100 -0
- package/dist/stats/statModifiers.d.ts +5 -2
- package/dist/stats/statModifiers.js +32 -14
- package/dist/store/observableKeyedStore.d.ts +4 -0
- package/dist/store/observableKeyedStore.js +17 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- package/dist/tactics/fallingGrid.d.ts +2 -2
- package/dist/tactics/fallingGrid.js +2 -2
- package/dist/time/beatClock.d.ts +18 -0
- package/dist/time/beatClock.js +24 -0
- package/dist/time/idleProgress.d.ts +29 -0
- package/dist/time/idleProgress.js +31 -0
- package/dist/time/serverTick.d.ts +7 -1
- package/dist/time/serverTick.js +27 -4
- package/dist/time/stateSchedule.d.ts +57 -0
- package/dist/time/stateSchedule.js +121 -0
- package/dist/turn/commit.d.ts +2 -2
- package/dist/turn/commit.js +2 -2
- package/dist/ui/gameLayout.d.ts +125 -0
- package/dist/ui/gameLayout.js +129 -0
- package/dist/ui/hudLayout.d.ts +58 -0
- package/dist/ui/hudLayout.js +207 -0
- package/dist/ui/hudScale.d.ts +44 -0
- package/dist/ui/hudScale.js +55 -0
- package/dist/ui/orientation.d.ts +39 -0
- package/dist/ui/orientation.js +49 -0
- package/dist/ui/swingTimer.d.ts +42 -0
- package/dist/ui/swingTimer.js +38 -0
- package/dist/visibility/assetStreaming.d.ts +66 -0
- package/dist/visibility/assetStreaming.js +201 -0
- package/dist/visibility/bounds.d.ts +66 -0
- package/dist/visibility/bounds.js +113 -0
- package/dist/visibility/camera.d.ts +21 -0
- package/dist/visibility/camera.js +3 -0
- package/dist/visibility/config.d.ts +21 -0
- package/dist/visibility/config.js +1 -0
- package/dist/visibility/diagnostics.d.ts +26 -0
- package/dist/visibility/diagnostics.js +36 -0
- package/dist/visibility/distance.d.ts +11 -0
- package/dist/visibility/distance.js +30 -0
- package/dist/visibility/frustum.d.ts +47 -0
- package/dist/visibility/frustum.js +186 -0
- package/dist/visibility/occlusion.d.ts +36 -0
- package/dist/visibility/occlusion.js +45 -0
- package/dist/visibility/settings.d.ts +75 -0
- package/dist/visibility/settings.js +49 -0
- package/dist/visibility/simulationCulling.d.ts +34 -0
- package/dist/visibility/simulationCulling.js +36 -0
- package/dist/visibility/spatialIndex.d.ts +45 -0
- package/dist/visibility/spatialIndex.js +212 -0
- package/dist/visibility/visibilitySystem.d.ts +90 -0
- package/dist/visibility/visibilitySystem.js +257 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/buildings.js +152 -0
- package/dist/world/carve.d.ts +7 -2
- package/dist/world/carve.js +17 -7
- package/dist/world/cellStates.d.ts +45 -0
- package/dist/world/cellStates.js +103 -0
- package/dist/world/environmentSummary.d.ts +21 -4
- package/dist/world/environmentSummary.js +22 -3
- package/dist/world/features.d.ts +185 -8
- package/dist/world/features.js +64 -6
- package/dist/world/gridInstances.d.ts +7 -8
- package/dist/world/gridInstances.js +145 -7
- package/dist/world/lod.d.ts +17 -0
- package/dist/world/lod.js +66 -0
- package/dist/world/mapLayers.d.ts +60 -0
- package/dist/world/mapLayers.js +44 -0
- package/dist/world/massing.d.ts +45 -0
- package/dist/world/massing.js +581 -0
- package/dist/world/minimap.d.ts +13 -1
- package/dist/world/minimap.js +21 -2
- package/dist/world/roads.d.ts +47 -0
- package/dist/world/roads.js +146 -0
- package/dist/world/streets.d.ts +80 -0
- package/dist/world/streets.js +144 -0
- package/dist/world/terrain.d.ts +69 -3
- package/dist/world/terrain.js +309 -16
- package/dist/world/water.d.ts +7 -0
- package/dist/world/water.js +19 -4
- package/dist/world/weather.d.ts +3 -18
- package/dist/world/weather.js +4 -17
- package/dist/world/windZones.d.ts +47 -0
- package/dist/world/windZones.js +59 -0
- package/llms.txt +3013 -1726
- package/package.json +12 -2
|
@@ -63,6 +63,44 @@ export class ForceVolume {
|
|
|
63
63
|
this.members = next;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
/** One tick's force math for a body outside `PhysicsWorld` — apply the returned velocity in any custom integrator. */
|
|
67
|
+
export function applyVolumeForce(velocity, force, mode, dt) {
|
|
68
|
+
if (mode === "velocity")
|
|
69
|
+
return force;
|
|
70
|
+
if (mode === "accelerate") {
|
|
71
|
+
return [velocity[0] + force[0] * dt, velocity[1] + force[1] * dt, velocity[2] + force[2] * dt];
|
|
72
|
+
}
|
|
73
|
+
return [velocity[0] + force[0], velocity[1] + force[1], velocity[2] + force[2]];
|
|
74
|
+
}
|
|
75
|
+
export function createVolumeTrigger(config) {
|
|
76
|
+
let members = new Set();
|
|
77
|
+
return {
|
|
78
|
+
bounds: config.bounds,
|
|
79
|
+
step(bodies) {
|
|
80
|
+
const entered = [];
|
|
81
|
+
const insideNow = [];
|
|
82
|
+
const next = new Set();
|
|
83
|
+
for (const body of bodies) {
|
|
84
|
+
if (!inside(config.bounds, body.position[0], body.position[1], body.position[2]))
|
|
85
|
+
continue;
|
|
86
|
+
next.add(body.id);
|
|
87
|
+
insideNow.push(body.id);
|
|
88
|
+
if (!members.has(body.id))
|
|
89
|
+
entered.push(body.id);
|
|
90
|
+
}
|
|
91
|
+
const exited = [];
|
|
92
|
+
for (const id of members) {
|
|
93
|
+
if (!next.has(id))
|
|
94
|
+
exited.push(id);
|
|
95
|
+
}
|
|
96
|
+
members = next;
|
|
97
|
+
return { entered, inside: insideNow, exited };
|
|
98
|
+
},
|
|
99
|
+
reset() {
|
|
100
|
+
members = new Set();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
66
104
|
/**
|
|
67
105
|
* Carries bodies standing on a moving platform by composing their transform with the platform's
|
|
68
106
|
* per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AxisInput } from "../input/axisInput.js";
|
|
2
|
+
import { type GripCurve } from "./vehicleBody.js";
|
|
3
|
+
export interface KinematicVehicleTuning {
|
|
4
|
+
engineAccel: number;
|
|
5
|
+
brakeAccel: number;
|
|
6
|
+
topSpeed: number;
|
|
7
|
+
reverseSpeed: number;
|
|
8
|
+
turnRate: number;
|
|
9
|
+
turnSpeedRef: number;
|
|
10
|
+
grip?: GripCurve;
|
|
11
|
+
gripStrength: number;
|
|
12
|
+
handbrakeGrip: number;
|
|
13
|
+
/** Forward-speed decay per second when coasting (sleds, boats); default `0`. */
|
|
14
|
+
rollingResistance?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface KinematicVehicleOptions {
|
|
17
|
+
position?: readonly [number, number, number];
|
|
18
|
+
heading?: number;
|
|
19
|
+
/** Grip multiplier by world position — off-track gravel, ice patches; default `1` everywhere. */
|
|
20
|
+
surfaceFriction?: (x: number, z: number) => number;
|
|
21
|
+
/** Extra velocity damping per second by world position (rough ground drag); default `0`. */
|
|
22
|
+
dragAt?: (x: number, z: number) => number;
|
|
23
|
+
}
|
|
24
|
+
export interface KinematicVehicleStep {
|
|
25
|
+
position: readonly [number, number, number];
|
|
26
|
+
heading: number;
|
|
27
|
+
/** Signed speed along the heading, world units/s. */
|
|
28
|
+
forwardSpeed: number;
|
|
29
|
+
lateralSpeed: number;
|
|
30
|
+
/** Lateral/forward slip ratio driving the grip curve — drift indicators key off it. */
|
|
31
|
+
slip: number;
|
|
32
|
+
/** The `surfaceFriction` sample used this tick (`< 1` = off the ideal surface). */
|
|
33
|
+
surface: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The pure-kinematic arcade car every racing game hand-rolled (#282.1): steer-yaw scaled by speed,
|
|
37
|
+
* throttle/brake acceleration, and a grip-curve lateral-slip bleed — no `PhysicsWorld`, no wheels,
|
|
38
|
+
* just the drift-friendly integration the three shipped racers proved out. Games keep their flavor
|
|
39
|
+
* (drift meters, boost, off-track rules) via `surfaceFriction`/`dragAt` hooks and the returned slip.
|
|
40
|
+
*/
|
|
41
|
+
export interface KinematicVehicle {
|
|
42
|
+
tick(dt: number, axis: AxisInput): KinematicVehicleStep;
|
|
43
|
+
pose(): {
|
|
44
|
+
position: readonly [number, number, number];
|
|
45
|
+
heading: number;
|
|
46
|
+
};
|
|
47
|
+
velocity(): readonly [number, number];
|
|
48
|
+
/** Scale the current velocity (boost pads, hard collisions). */
|
|
49
|
+
scaleVelocity(factor: number): void;
|
|
50
|
+
resetTo(position: readonly [number, number, number], heading: number): void;
|
|
51
|
+
}
|
|
52
|
+
export declare function createKinematicVehicle(tuning: KinematicVehicleTuning, options?: KinematicVehicleOptions): KinematicVehicle;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { steerYaw } from "../movement/steering.js";
|
|
2
|
+
import { DEFAULT_GRIP_CURVE, sampleGripCurve } from "./vehicleBody.js";
|
|
3
|
+
export function createKinematicVehicle(tuning, options = {}) {
|
|
4
|
+
const grip = tuning.grip ?? DEFAULT_GRIP_CURVE;
|
|
5
|
+
const rollingResistance = tuning.rollingResistance ?? 0;
|
|
6
|
+
const surfaceFriction = options.surfaceFriction ?? (() => 1);
|
|
7
|
+
const dragAt = options.dragAt ?? (() => 0);
|
|
8
|
+
let x = options.position?.[0] ?? 0;
|
|
9
|
+
let y = options.position?.[1] ?? 0;
|
|
10
|
+
let z = options.position?.[2] ?? 0;
|
|
11
|
+
let heading = options.heading ?? 0;
|
|
12
|
+
let vx = 0;
|
|
13
|
+
let vz = 0;
|
|
14
|
+
function forward() {
|
|
15
|
+
return [Math.sin(heading), Math.cos(heading)];
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
tick(dt, axis) {
|
|
19
|
+
const [fx0, fz0] = forward();
|
|
20
|
+
const speed = vx * fx0 + vz * fz0;
|
|
21
|
+
const steerScale = Math.min(1, Math.abs(speed) / tuning.turnSpeedRef);
|
|
22
|
+
const direction = speed >= 0 ? 1 : -1;
|
|
23
|
+
heading = steerYaw(heading, axis.steer * steerScale * direction, tuning.turnRate, dt);
|
|
24
|
+
const [fx, fz] = forward();
|
|
25
|
+
let accel = 0;
|
|
26
|
+
if (axis.throttle > 0 && speed < tuning.topSpeed)
|
|
27
|
+
accel += axis.throttle * tuning.engineAccel;
|
|
28
|
+
if (axis.brake > 0) {
|
|
29
|
+
if (speed > 0.2)
|
|
30
|
+
accel -= axis.brake * tuning.brakeAccel;
|
|
31
|
+
else if (speed > -tuning.reverseSpeed)
|
|
32
|
+
accel -= axis.brake * tuning.engineAccel;
|
|
33
|
+
}
|
|
34
|
+
vx += fx * accel * dt;
|
|
35
|
+
vz += fz * accel * dt;
|
|
36
|
+
let forwardSpeed = vx * fx + vz * fz;
|
|
37
|
+
const lateralSpeed = -vx * fz + vz * fx;
|
|
38
|
+
const surface = surfaceFriction(x, z);
|
|
39
|
+
const slip = Math.abs(lateralSpeed) / (Math.abs(forwardSpeed) + 1);
|
|
40
|
+
const handbrakeFactor = 1 - axis.handbrake * (1 - tuning.handbrakeGrip);
|
|
41
|
+
const gripValue = sampleGripCurve(grip, slip) * surface * handbrakeFactor;
|
|
42
|
+
const keep = Math.max(0, 1 - gripValue * tuning.gripStrength * dt);
|
|
43
|
+
const keptLateral = lateralSpeed * keep;
|
|
44
|
+
if (rollingResistance > 0)
|
|
45
|
+
forwardSpeed *= Math.max(0, 1 - rollingResistance * dt);
|
|
46
|
+
vx = fx * forwardSpeed - fz * keptLateral;
|
|
47
|
+
vz = fz * forwardSpeed + fx * keptLateral;
|
|
48
|
+
const drag = dragAt(x, z);
|
|
49
|
+
let dragDamp = 1;
|
|
50
|
+
if (drag > 0) {
|
|
51
|
+
dragDamp = Math.max(0, 1 - drag * dt);
|
|
52
|
+
vx *= dragDamp;
|
|
53
|
+
vz *= dragDamp;
|
|
54
|
+
}
|
|
55
|
+
x += vx * dt;
|
|
56
|
+
z += vz * dt;
|
|
57
|
+
return {
|
|
58
|
+
position: [x, y, z],
|
|
59
|
+
heading,
|
|
60
|
+
forwardSpeed: vx * fx + vz * fz,
|
|
61
|
+
lateralSpeed: keptLateral * dragDamp,
|
|
62
|
+
slip,
|
|
63
|
+
surface,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
pose: () => ({ position: [x, y, z], heading }),
|
|
67
|
+
velocity: () => [vx, vz],
|
|
68
|
+
scaleVelocity(factor) {
|
|
69
|
+
vx *= factor;
|
|
70
|
+
vz *= factor;
|
|
71
|
+
},
|
|
72
|
+
resetTo(position, nextHeading) {
|
|
73
|
+
x = position[0];
|
|
74
|
+
y = position[1];
|
|
75
|
+
z = position[2];
|
|
76
|
+
heading = nextHeading;
|
|
77
|
+
vx = 0;
|
|
78
|
+
vz = 0;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -72,11 +72,15 @@ export type AddBodyOptions = BoxBodyOptions | SphereBodyOptions;
|
|
|
72
72
|
export declare const SHAPE_BOX = 0;
|
|
73
73
|
export declare const SHAPE_SPHERE = 1;
|
|
74
74
|
/**
|
|
75
|
-
* `
|
|
76
|
-
*
|
|
77
|
-
* holds a fixed separation
|
|
75
|
+
* Hard bilateral: `fixed` pins the shared anchor (3-DOF), `hinge` pins only the plane
|
|
76
|
+
* perpendicular to `axis` (free along the axis — translational cylindrical), `distance`
|
|
77
|
+
* holds a fixed separation. `spring` is a soft PD drive toward `restLength`.
|
|
78
78
|
*/
|
|
79
79
|
export type JointKind = "hinge" | "distance" | "spring" | "fixed";
|
|
80
|
+
export declare const JOINT_FIXED = 0;
|
|
81
|
+
export declare const JOINT_HINGE = 1;
|
|
82
|
+
export declare const JOINT_DISTANCE = 2;
|
|
83
|
+
export declare const JOINT_SPRING = 3;
|
|
80
84
|
export interface JointOptions {
|
|
81
85
|
/** First body index. */
|
|
82
86
|
bodyA: number;
|
|
@@ -94,7 +98,7 @@ export interface JointOptions {
|
|
|
94
98
|
damping?: number;
|
|
95
99
|
/** Clamp on the per-substep spring impulse (a follow-point that never yanks). Default unbounded. */
|
|
96
100
|
maxImpulse?: number;
|
|
97
|
-
/**
|
|
101
|
+
/** Unit hinge axis (world). Required semantics for `hingeJoint`; free motion along this axis. Default [0,1,0]. */
|
|
98
102
|
axis?: readonly [number, number, number];
|
|
99
103
|
}
|
|
100
104
|
/** A contact reported to `onCollision`. The object is reused each call — read/copy, never retain. */
|
|
@@ -157,12 +161,12 @@ export declare class PhysicsWorld {
|
|
|
157
161
|
private readonly numCells;
|
|
158
162
|
private readonly cellStart;
|
|
159
163
|
private readonly cursor;
|
|
160
|
-
private
|
|
164
|
+
private sorted;
|
|
161
165
|
private readonly bodyCell;
|
|
162
166
|
private readonly awakeList;
|
|
163
167
|
private readonly awakeSlot;
|
|
164
168
|
private awakeCount;
|
|
165
|
-
private readonly
|
|
169
|
+
private readonly jKind;
|
|
166
170
|
private readonly jActive;
|
|
167
171
|
private readonly jBodyA;
|
|
168
172
|
private readonly jBodyB;
|
|
@@ -172,6 +176,9 @@ export declare class PhysicsWorld {
|
|
|
172
176
|
private readonly jAnchorBX;
|
|
173
177
|
private readonly jAnchorBY;
|
|
174
178
|
private readonly jAnchorBZ;
|
|
179
|
+
private readonly jAxisX;
|
|
180
|
+
private readonly jAxisY;
|
|
181
|
+
private readonly jAxisZ;
|
|
175
182
|
private readonly jRest;
|
|
176
183
|
private readonly jStiffness;
|
|
177
184
|
private readonly jDamping;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export const SHAPE_BOX = 0;
|
|
2
2
|
export const SHAPE_SPHERE = 1;
|
|
3
|
+
export const JOINT_FIXED = 0;
|
|
4
|
+
export const JOINT_HINGE = 1;
|
|
5
|
+
export const JOINT_DISTANCE = 2;
|
|
6
|
+
export const JOINT_SPRING = 3;
|
|
3
7
|
const FLAG_SLEEPING = 1;
|
|
4
8
|
const FLAG_STATIC = 2;
|
|
5
9
|
const FLAG_DEAD = 4;
|
|
@@ -65,7 +69,7 @@ export class PhysicsWorld {
|
|
|
65
69
|
awakeList;
|
|
66
70
|
awakeSlot;
|
|
67
71
|
awakeCount = 0;
|
|
68
|
-
|
|
72
|
+
jKind;
|
|
69
73
|
jActive;
|
|
70
74
|
jBodyA;
|
|
71
75
|
jBodyB;
|
|
@@ -75,6 +79,9 @@ export class PhysicsWorld {
|
|
|
75
79
|
jAnchorBX;
|
|
76
80
|
jAnchorBY;
|
|
77
81
|
jAnchorBZ;
|
|
82
|
+
jAxisX;
|
|
83
|
+
jAxisY;
|
|
84
|
+
jAxisZ;
|
|
78
85
|
jRest;
|
|
79
86
|
jStiffness;
|
|
80
87
|
jDamping;
|
|
@@ -168,7 +175,7 @@ export class PhysicsWorld {
|
|
|
168
175
|
this.awakeSlot = new Int32Array(cap).fill(-1);
|
|
169
176
|
this.bodyFree = new Int32Array(cap);
|
|
170
177
|
const jc = this.jointCapacity;
|
|
171
|
-
this.
|
|
178
|
+
this.jKind = new Uint8Array(jc);
|
|
172
179
|
this.jActive = new Uint8Array(jc);
|
|
173
180
|
this.jBodyA = new Int32Array(jc);
|
|
174
181
|
this.jBodyB = new Int32Array(jc);
|
|
@@ -178,6 +185,9 @@ export class PhysicsWorld {
|
|
|
178
185
|
this.jAnchorBX = new Float32Array(jc);
|
|
179
186
|
this.jAnchorBY = new Float32Array(jc);
|
|
180
187
|
this.jAnchorBZ = new Float32Array(jc);
|
|
188
|
+
this.jAxisX = new Float32Array(jc);
|
|
189
|
+
this.jAxisY = new Float32Array(jc);
|
|
190
|
+
this.jAxisZ = new Float32Array(jc);
|
|
181
191
|
this.jRest = new Float32Array(jc);
|
|
182
192
|
this.jStiffness = new Float32Array(jc);
|
|
183
193
|
this.jDamping = new Float32Array(jc);
|
|
@@ -224,18 +234,25 @@ export class PhysicsWorld {
|
|
|
224
234
|
this.collisionMinSpeed = minApproachSpeed;
|
|
225
235
|
}
|
|
226
236
|
hingeJoint(options) {
|
|
227
|
-
|
|
237
|
+
const raw = options.axis ?? [0, 1, 0];
|
|
238
|
+
const len = Math.hypot(raw[0], raw[1], raw[2]);
|
|
239
|
+
if (!(len > 1e-9))
|
|
240
|
+
throw new Error("PhysicsWorld: hingeJoint axis must be non-zero");
|
|
241
|
+
return this.addJoint(options, JOINT_HINGE, 0, [raw[0] / len, raw[1] / len, raw[2] / len]);
|
|
228
242
|
}
|
|
229
243
|
fixedJoint(options) {
|
|
230
|
-
return this.addJoint(options,
|
|
244
|
+
return this.addJoint(options, JOINT_FIXED, 0, null);
|
|
231
245
|
}
|
|
232
246
|
distanceJoint(options) {
|
|
233
|
-
return this.addJoint(options,
|
|
247
|
+
return this.addJoint(options, JOINT_DISTANCE, null, null);
|
|
234
248
|
}
|
|
235
249
|
springJoint(options) {
|
|
236
|
-
return this.addJoint(options,
|
|
250
|
+
return this.addJoint(options, JOINT_SPRING, null, null);
|
|
237
251
|
}
|
|
238
|
-
addJoint(options,
|
|
252
|
+
addJoint(options, kind, restOverride, axis) {
|
|
253
|
+
if (kind !== JOINT_FIXED && kind !== JOINT_HINGE && kind !== JOINT_DISTANCE && kind !== JOINT_SPRING) {
|
|
254
|
+
throw new Error(`PhysicsWorld: joint kind ${kind} is not implemented`);
|
|
255
|
+
}
|
|
239
256
|
let id;
|
|
240
257
|
if (this.jFreeCount > 0) {
|
|
241
258
|
id = this.jFree[--this.jFreeCount];
|
|
@@ -256,7 +273,10 @@ export class PhysicsWorld {
|
|
|
256
273
|
this.jAnchorBX[id] = aB?.[0] ?? 0;
|
|
257
274
|
this.jAnchorBY[id] = aB?.[1] ?? 0;
|
|
258
275
|
this.jAnchorBZ[id] = aB?.[2] ?? 0;
|
|
259
|
-
this.
|
|
276
|
+
this.jKind[id] = kind;
|
|
277
|
+
this.jAxisX[id] = axis?.[0] ?? 0;
|
|
278
|
+
this.jAxisY[id] = axis?.[1] ?? 0;
|
|
279
|
+
this.jAxisZ[id] = axis?.[2] ?? 0;
|
|
260
280
|
this.jStiffness[id] = options.stiffness ?? 40;
|
|
261
281
|
this.jDamping[id] = options.damping ?? 6;
|
|
262
282
|
this.jMaxImpulse[id] = options.maxImpulse ?? Number.POSITIVE_INFINITY;
|
|
@@ -587,28 +607,56 @@ export class PhysicsWorld {
|
|
|
587
607
|
const n = this.bodyHigh;
|
|
588
608
|
const start = this.cellStart;
|
|
589
609
|
start.fill(0);
|
|
590
|
-
// Sleeping bodies never move, so their cell index is cached from addBody / their last awake
|
|
591
|
-
// substep — only awake bodies pay the cell recomputation each substep.
|
|
592
610
|
const awakeCount = this.awakeCount;
|
|
593
611
|
for (let a = 0; a < awakeCount; a += 1) {
|
|
594
612
|
const i = this.awakeList[a];
|
|
595
613
|
this.bodyCell[i] = this.cellOf(i);
|
|
596
614
|
}
|
|
597
|
-
|
|
615
|
+
const nx = this.nx;
|
|
616
|
+
const ny = this.ny;
|
|
598
617
|
for (let i = 0; i < n; i += 1) {
|
|
599
618
|
if ((this.flags[i] & FLAG_DEAD) !== 0)
|
|
600
619
|
continue;
|
|
601
|
-
|
|
620
|
+
const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
621
|
+
const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
622
|
+
const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
623
|
+
const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
624
|
+
const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
625
|
+
const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
626
|
+
for (let z = z0; z <= z1; z += 1) {
|
|
627
|
+
for (let y = y0; y <= y1; y += 1) {
|
|
628
|
+
const rowBase = (z * ny + y) * nx;
|
|
629
|
+
for (let x = x0; x <= x1; x += 1)
|
|
630
|
+
start[rowBase + x + 1] += 1;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
602
633
|
}
|
|
603
634
|
for (let c = 0; c < this.numCells; c += 1) {
|
|
604
635
|
start[c + 1] += start[c];
|
|
605
636
|
this.cursor[c] = start[c];
|
|
606
637
|
}
|
|
638
|
+
const total = start[this.numCells];
|
|
639
|
+
if (total > this.sorted.length) {
|
|
640
|
+
this.sorted = new Int32Array(Math.max(total, this.sorted.length * 2));
|
|
641
|
+
}
|
|
607
642
|
for (let i = 0; i < n; i += 1) {
|
|
608
643
|
if ((this.flags[i] & FLAG_DEAD) !== 0)
|
|
609
644
|
continue;
|
|
610
|
-
const
|
|
611
|
-
this.
|
|
645
|
+
const x0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
646
|
+
const x1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
647
|
+
const y0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
648
|
+
const y1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
649
|
+
const z0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
650
|
+
const z1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
651
|
+
for (let z = z0; z <= z1; z += 1) {
|
|
652
|
+
for (let y = y0; y <= y1; y += 1) {
|
|
653
|
+
const rowBase = (z * ny + y) * nx;
|
|
654
|
+
for (let x = x0; x <= x1; x += 1) {
|
|
655
|
+
const c = rowBase + x;
|
|
656
|
+
this.sorted[this.cursor[c]++] = i;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
612
660
|
}
|
|
613
661
|
}
|
|
614
662
|
cellOf(i) {
|
|
@@ -620,26 +668,18 @@ export class PhysicsWorld {
|
|
|
620
668
|
solve(restitutionPass, countPass) {
|
|
621
669
|
const nx = this.nx;
|
|
622
670
|
const ny = this.ny;
|
|
623
|
-
const nz = this.nz;
|
|
624
|
-
const nxny = nx * ny;
|
|
625
|
-
// A contact may wake a sleeping body mid-pass, appending it to the awake list; re-read the
|
|
626
|
-
// count each iteration so it is solved this substep too.
|
|
627
671
|
for (let a = 0; a < this.awakeCount; a += 1) {
|
|
628
672
|
const i = this.awakeList[a];
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const x0 = cx > 0 ? cx - 1 : cx;
|
|
638
|
-
const x1 = cx < nx - 1 ? cx + 1 : cx;
|
|
639
|
-
for (let z = z0; z <= z1; z += 1) {
|
|
640
|
-
for (let y = y0; y <= y1; y += 1) {
|
|
673
|
+
const ix0 = cellCoord(this.posX[i] - this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
674
|
+
const ix1 = cellCoord(this.posX[i] + this.halfX[i], this.bounds.min[0], this.cellSize, this.nx);
|
|
675
|
+
const iy0 = cellCoord(this.posY[i] - this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
676
|
+
const iy1 = cellCoord(this.posY[i] + this.halfY[i], this.bounds.min[1], this.cellSize, this.ny);
|
|
677
|
+
const iz0 = cellCoord(this.posZ[i] - this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
678
|
+
const iz1 = cellCoord(this.posZ[i] + this.halfZ[i], this.bounds.min[2], this.cellSize, this.nz);
|
|
679
|
+
for (let z = iz0; z <= iz1; z += 1) {
|
|
680
|
+
for (let y = iy0; y <= iy1; y += 1) {
|
|
641
681
|
const rowBase = (z * ny + y) * nx;
|
|
642
|
-
for (let x =
|
|
682
|
+
for (let x = ix0; x <= ix1; x += 1) {
|
|
643
683
|
const cell = rowBase + x;
|
|
644
684
|
const end = this.cellStart[cell + 1];
|
|
645
685
|
for (let s = this.cellStart[cell]; s < end; s += 1) {
|
|
@@ -649,6 +689,14 @@ export class PhysicsWorld {
|
|
|
649
689
|
const jSleeping = (this.flags[j] & FLAG_SLEEPING) !== 0;
|
|
650
690
|
if (!jSleeping && j < i)
|
|
651
691
|
continue;
|
|
692
|
+
const jx0 = cellCoord(this.posX[j] - this.halfX[j], this.bounds.min[0], this.cellSize, this.nx);
|
|
693
|
+
const jy0 = cellCoord(this.posY[j] - this.halfY[j], this.bounds.min[1], this.cellSize, this.ny);
|
|
694
|
+
const jz0 = cellCoord(this.posZ[j] - this.halfZ[j], this.bounds.min[2], this.cellSize, this.nz);
|
|
695
|
+
const mx0 = ix0 > jx0 ? ix0 : jx0;
|
|
696
|
+
const my0 = iy0 > jy0 ? iy0 : jy0;
|
|
697
|
+
const mz0 = iz0 > jz0 ? iz0 : jz0;
|
|
698
|
+
if (x !== mx0 || y !== my0 || z !== mz0)
|
|
699
|
+
continue;
|
|
652
700
|
this.resolve(i, j, restitutionPass, countPass);
|
|
653
701
|
}
|
|
654
702
|
}
|
|
@@ -819,6 +867,7 @@ export class PhysicsWorld {
|
|
|
819
867
|
for (let id = 0; id < this.jointHigh; id += 1) {
|
|
820
868
|
if (this.jActive[id] !== 1)
|
|
821
869
|
continue;
|
|
870
|
+
const kind = this.jKind[id];
|
|
822
871
|
const bA = this.jBodyA[id];
|
|
823
872
|
const bB = this.jBodyB[id];
|
|
824
873
|
const ax = (bA >= 0 ? this.posX[bA] : 0) + this.jAnchorAX[id];
|
|
@@ -827,9 +876,91 @@ export class PhysicsWorld {
|
|
|
827
876
|
const bx = bB >= 0 ? this.posX[bB] + this.jAnchorBX[id] : this.jAnchorBX[id];
|
|
828
877
|
const by = bB >= 0 ? this.posY[bB] + this.jAnchorBY[id] : this.jAnchorBY[id];
|
|
829
878
|
const bz = bB >= 0 ? this.posZ[bB] + this.jAnchorBZ[id] : this.jAnchorBZ[id];
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
879
|
+
let dx = bx - ax;
|
|
880
|
+
let dy = by - ay;
|
|
881
|
+
let dz = bz - az;
|
|
882
|
+
const imA = bA >= 0 ? this.invMass[bA] : 0;
|
|
883
|
+
const imB = bB >= 0 ? this.invMass[bB] : 0;
|
|
884
|
+
const imSum = imA + imB;
|
|
885
|
+
if (imSum === 0)
|
|
886
|
+
continue;
|
|
887
|
+
if (kind === JOINT_HINGE) {
|
|
888
|
+
const ux = this.jAxisX[id];
|
|
889
|
+
const uy = this.jAxisY[id];
|
|
890
|
+
const uz = this.jAxisZ[id];
|
|
891
|
+
const along = dx * ux + dy * uy + dz * uz;
|
|
892
|
+
dx -= along * ux;
|
|
893
|
+
dy -= along * uy;
|
|
894
|
+
dz -= along * uz;
|
|
895
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
896
|
+
const vax = bA >= 0 ? this.velX[bA] : 0;
|
|
897
|
+
const vay = bA >= 0 ? this.velY[bA] : 0;
|
|
898
|
+
const vaz = bA >= 0 ? this.velZ[bA] : 0;
|
|
899
|
+
const vbx = bB >= 0 ? this.velX[bB] : 0;
|
|
900
|
+
const vby = bB >= 0 ? this.velY[bB] : 0;
|
|
901
|
+
const vbz = bB >= 0 ? this.velZ[bB] : 0;
|
|
902
|
+
let rvx = vbx - vax;
|
|
903
|
+
let rvy = vby - vay;
|
|
904
|
+
let rvz = vbz - vaz;
|
|
905
|
+
const rvAlong = rvx * ux + rvy * uy + rvz * uz;
|
|
906
|
+
rvx -= rvAlong * ux;
|
|
907
|
+
rvy -= rvAlong * uy;
|
|
908
|
+
rvz -= rvAlong * uz;
|
|
909
|
+
if (dist > 1e-9) {
|
|
910
|
+
const nX = dx / dist;
|
|
911
|
+
const nY = dy / dist;
|
|
912
|
+
const nZ = dz / dist;
|
|
913
|
+
if (dist > slop) {
|
|
914
|
+
if (bA >= 0)
|
|
915
|
+
this.wake(bA);
|
|
916
|
+
if (bB >= 0)
|
|
917
|
+
this.wake(bB);
|
|
918
|
+
}
|
|
919
|
+
const rvn = rvx * nX + rvy * nY + rvz * nZ;
|
|
920
|
+
const jn = -rvn / imSum;
|
|
921
|
+
if (bA >= 0) {
|
|
922
|
+
this.velX[bA] -= jn * imA * nX;
|
|
923
|
+
this.velY[bA] -= jn * imA * nY;
|
|
924
|
+
this.velZ[bA] -= jn * imA * nZ;
|
|
925
|
+
}
|
|
926
|
+
if (bB >= 0) {
|
|
927
|
+
this.velX[bB] += jn * imB * nX;
|
|
928
|
+
this.velY[bB] += jn * imB * nY;
|
|
929
|
+
this.velZ[bB] += jn * imB * nZ;
|
|
930
|
+
}
|
|
931
|
+
const corr = (dist * beta) / imSum;
|
|
932
|
+
if (bA >= 0) {
|
|
933
|
+
this.posX[bA] += corr * imA * nX;
|
|
934
|
+
this.posY[bA] += corr * imA * nY;
|
|
935
|
+
this.posZ[bA] += corr * imA * nZ;
|
|
936
|
+
}
|
|
937
|
+
if (bB >= 0) {
|
|
938
|
+
this.posX[bB] -= corr * imB * nX;
|
|
939
|
+
this.posY[bB] -= corr * imB * nY;
|
|
940
|
+
this.posZ[bB] -= corr * imB * nZ;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
const rvl = Math.sqrt(rvx * rvx + rvy * rvy + rvz * rvz);
|
|
945
|
+
if (rvl > 1e-9) {
|
|
946
|
+
const nX = rvx / rvl;
|
|
947
|
+
const nY = rvy / rvl;
|
|
948
|
+
const nZ = rvz / rvl;
|
|
949
|
+
const jn = -rvl / imSum;
|
|
950
|
+
if (bA >= 0) {
|
|
951
|
+
this.velX[bA] -= jn * imA * nX;
|
|
952
|
+
this.velY[bA] -= jn * imA * nY;
|
|
953
|
+
this.velZ[bA] -= jn * imA * nZ;
|
|
954
|
+
}
|
|
955
|
+
if (bB >= 0) {
|
|
956
|
+
this.velX[bB] += jn * imB * nX;
|
|
957
|
+
this.velY[bB] += jn * imB * nY;
|
|
958
|
+
this.velZ[bB] += jn * imB * nZ;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
833
964
|
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
834
965
|
if (dist < 1e-9)
|
|
835
966
|
continue;
|
|
@@ -837,11 +968,6 @@ export class PhysicsWorld {
|
|
|
837
968
|
const nY = dy / dist;
|
|
838
969
|
const nZ = dz / dist;
|
|
839
970
|
const c = dist - this.jRest[id];
|
|
840
|
-
const imA = bA >= 0 ? this.invMass[bA] : 0;
|
|
841
|
-
const imB = bB >= 0 ? this.invMass[bB] : 0;
|
|
842
|
-
const imSum = imA + imB;
|
|
843
|
-
if (imSum === 0)
|
|
844
|
-
continue;
|
|
845
971
|
const vax = bA >= 0 ? this.velX[bA] : 0;
|
|
846
972
|
const vay = bA >= 0 ? this.velY[bA] : 0;
|
|
847
973
|
const vaz = bA >= 0 ? this.velZ[bA] : 0;
|
|
@@ -849,7 +975,7 @@ export class PhysicsWorld {
|
|
|
849
975
|
const vby = bB >= 0 ? this.velY[bB] : 0;
|
|
850
976
|
const vbz = bB >= 0 ? this.velZ[bB] : 0;
|
|
851
977
|
const rvn = (vbx - vax) * nX + (vby - vay) * nY + (vbz - vaz) * nZ;
|
|
852
|
-
if (
|
|
978
|
+
if (kind === JOINT_SPRING) {
|
|
853
979
|
if (Math.abs(c) > slop) {
|
|
854
980
|
if (bA >= 0)
|
|
855
981
|
this.wake(bA);
|
|
@@ -874,6 +1000,9 @@ export class PhysicsWorld {
|
|
|
874
1000
|
}
|
|
875
1001
|
continue;
|
|
876
1002
|
}
|
|
1003
|
+
if (kind !== JOINT_FIXED && kind !== JOINT_DISTANCE) {
|
|
1004
|
+
throw new Error(`PhysicsWorld: joint kind ${kind} is not implemented`);
|
|
1005
|
+
}
|
|
877
1006
|
if (Math.abs(c) > slop) {
|
|
878
1007
|
if (bA >= 0)
|
|
879
1008
|
this.wake(bA);
|
package/dist/physics/ragdoll.js
CHANGED
|
@@ -88,7 +88,7 @@ export function createRagdoll(world, config) {
|
|
|
88
88
|
(world.posY[a] + world.posY[b]) / 2,
|
|
89
89
|
(world.posZ[a] + world.posZ[b]) / 2,
|
|
90
90
|
];
|
|
91
|
-
links.push(world.
|
|
91
|
+
links.push(world.fixedJoint({
|
|
92
92
|
bodyA: a,
|
|
93
93
|
bodyB: b,
|
|
94
94
|
anchorA: [mid[0] - world.posX[a], mid[1] - world.posY[a], mid[2] - world.posZ[a]],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { steerYaw } from "../movement/steering.js";
|
|
1
2
|
/**
|
|
2
3
|
* Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near
|
|
3
4
|
* the breakaway slip then falls off as the tire slides — the shape that separates a planted corner
|
|
@@ -166,7 +167,7 @@ export class VehicleBody {
|
|
|
166
167
|
if (grounded) {
|
|
167
168
|
const steerScale = Math.min(1, Math.abs(speed) / this.turnSpeedRef);
|
|
168
169
|
const dir = speed >= 0 ? 1 : -1;
|
|
169
|
-
this.heading
|
|
170
|
+
this.heading = steerYaw(this.heading, input.steer * steerScale * dir, this.turnRate, dt);
|
|
170
171
|
}
|
|
171
172
|
const [fx, fz] = this.forward;
|
|
172
173
|
if (grounded) {
|
package/dist/random/rng.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence. */
|
|
1
2
|
export declare function seededRng(seed: string | number): () => number;
|
|
3
|
+
/** Derives independent, deterministic {@link seededRng} streams from one base seed, keyed by stream name. */
|
|
2
4
|
export declare function seededStreams(seed: string | number): (stream: string) => () => number;
|
package/dist/random/rng.js
CHANGED
|
@@ -7,6 +7,7 @@ function hashSeed(seed) {
|
|
|
7
7
|
}
|
|
8
8
|
return h >>> 0;
|
|
9
9
|
}
|
|
10
|
+
/** Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence. */
|
|
10
11
|
export function seededRng(seed) {
|
|
11
12
|
let a = hashSeed(seed) >>> 0;
|
|
12
13
|
return () => {
|
|
@@ -17,6 +18,7 @@ export function seededRng(seed) {
|
|
|
17
18
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
18
19
|
};
|
|
19
20
|
}
|
|
21
|
+
/** Derives independent, deterministic {@link seededRng} streams from one base seed, keyed by stream name. */
|
|
20
22
|
export function seededStreams(seed) {
|
|
21
23
|
const base = typeof seed === "number" ? seed.toString() : seed;
|
|
22
24
|
return (stream) => seededRng(`${base}:${stream}`);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const DEFAULT_SEED_PARAM = "seed";
|
|
2
|
+
export declare function withSeedParam(url: string, seed: string | number, param?: string): string;
|
|
3
|
+
export declare function seedFromSearch(search: string, param?: string): string | null;
|
|
4
|
+
export declare function seedFromUrl(url: string, param?: string): string | null;
|
|
5
|
+
export declare function dailySeed(nowMs: number, salt?: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const DEFAULT_SEED_PARAM = "seed";
|
|
2
|
+
function splitUrl(url) {
|
|
3
|
+
const hashIndex = url.indexOf("#");
|
|
4
|
+
const hash = hashIndex === -1 ? "" : url.slice(hashIndex + 1);
|
|
5
|
+
const rest = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
6
|
+
const queryIndex = rest.indexOf("?");
|
|
7
|
+
const base = queryIndex === -1 ? rest : rest.slice(0, queryIndex);
|
|
8
|
+
const query = queryIndex === -1 ? "" : rest.slice(queryIndex + 1);
|
|
9
|
+
return { base, query, hash };
|
|
10
|
+
}
|
|
11
|
+
export function withSeedParam(url, seed, param = DEFAULT_SEED_PARAM) {
|
|
12
|
+
const { base, query, hash } = splitUrl(url);
|
|
13
|
+
const params = new URLSearchParams(query);
|
|
14
|
+
params.set(param, typeof seed === "number" ? seed.toString() : seed);
|
|
15
|
+
const queryString = params.toString();
|
|
16
|
+
return `${base}${queryString ? `?${queryString}` : ""}${hash ? `#${hash}` : ""}`;
|
|
17
|
+
}
|
|
18
|
+
export function seedFromSearch(search, param = DEFAULT_SEED_PARAM) {
|
|
19
|
+
const normalized = search.startsWith("?") ? search.slice(1) : search;
|
|
20
|
+
const params = new URLSearchParams(normalized);
|
|
21
|
+
const value = params.get(param);
|
|
22
|
+
return value === null || value === "" ? null : value;
|
|
23
|
+
}
|
|
24
|
+
export function seedFromUrl(url, param = DEFAULT_SEED_PARAM) {
|
|
25
|
+
const { query } = splitUrl(url);
|
|
26
|
+
return seedFromSearch(query, param);
|
|
27
|
+
}
|
|
28
|
+
export function dailySeed(nowMs, salt) {
|
|
29
|
+
const date = new Date(nowMs);
|
|
30
|
+
const year = date.getUTCFullYear().toString().padStart(4, "0");
|
|
31
|
+
const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
32
|
+
const day = date.getUTCDate().toString().padStart(2, "0");
|
|
33
|
+
const base = `${year}-${month}-${day}`;
|
|
34
|
+
return salt !== undefined && salt.length > 0 ? `${base}:${salt}` : base;
|
|
35
|
+
}
|