@jgengine/core 0.6.0 → 0.7.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 +167 -2
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +56 -0
- package/dist/ai/spawnDirector.js +150 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +63 -0
- package/dist/cards/cardPile.js +166 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +42 -0
- package/dist/combat/abilityKit.js +129 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +11 -0
- package/dist/combat/effects.js +22 -13
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +27 -0
- package/dist/combat/telegraph.js +37 -0
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +2 -0
- package/dist/game/events.d.ts +62 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +281 -4
- package/dist/game/race.d.ts +95 -0
- package/dist/game/race.js +216 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +27 -0
- package/dist/game/social.js +16 -0
- package/dist/game/worldItem.d.ts +92 -0
- package/dist/game/worldItem.js +132 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/pointer.d.ts +24 -0
- package/dist/input/pointer.js +17 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +133 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +35 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/nav/navGrid.d.ts +46 -0
- package/dist/nav/navGrid.js +248 -0
- package/dist/nav/pathFollow.d.ts +29 -0
- package/dist/nav/pathFollow.js +67 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +83 -0
- package/dist/physics/physicsWorld.js +261 -1
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +138 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +93 -0
- package/dist/physics/vehicleBody.js +234 -0
- package/dist/runtime/gameContext.d.ts +64 -2
- package/dist/runtime/gameContext.js +154 -4
- package/dist/runtime/hostPersistence.d.ts +18 -0
- package/dist/runtime/hostPersistence.js +4 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +1 -1
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roundState.d.ts +55 -0
- package/dist/session/roundState.js +129 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +57 -0
- package/dist/tactics/tacticalGrid.js +244 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/turnLoop.d.ts +58 -0
- package/dist/turn/turnLoop.js +216 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +48 -0
- package/dist/world/placementController.js +97 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CollisionEvent } from "./physicsWorld.js";
|
|
2
|
+
export interface DamageZoneDef {
|
|
3
|
+
id: string;
|
|
4
|
+
thresholds: readonly number[];
|
|
5
|
+
detachStage?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DamageZoneState {
|
|
8
|
+
id: string;
|
|
9
|
+
accumulated: number;
|
|
10
|
+
stage: number;
|
|
11
|
+
detached: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface DamageTransition {
|
|
14
|
+
zone: string;
|
|
15
|
+
stage: number;
|
|
16
|
+
previousStage: number;
|
|
17
|
+
detached: boolean;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
accumulated: number;
|
|
20
|
+
total: number;
|
|
21
|
+
}
|
|
22
|
+
export interface DamageModelConfig {
|
|
23
|
+
zones: readonly DamageZoneDef[];
|
|
24
|
+
disableAt?: number;
|
|
25
|
+
onStage?: (transition: DamageTransition) => void;
|
|
26
|
+
onDetach?: (zoneId: string, accumulated: number) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Coarse crash-damage model (issue #86 — stages, not soft-body). Each zone accumulates contact impulse
|
|
30
|
+
* fed from G3's `onCollision` `CollisionEvent`; crossing a threshold bumps the zone to the next discrete
|
|
31
|
+
* stage (the caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once,
|
|
32
|
+
* and a total-impulse `disableAt` flips the whole body to a disabled state. `absorb` returns a transition
|
|
33
|
+
* only when something actually changed, so it doubles as the event source for HUD/feed.
|
|
34
|
+
*/
|
|
35
|
+
export declare class DamageModel {
|
|
36
|
+
private readonly zones;
|
|
37
|
+
private readonly order;
|
|
38
|
+
private readonly disableAt;
|
|
39
|
+
private readonly onStage?;
|
|
40
|
+
private readonly onDetach?;
|
|
41
|
+
private totalImpulse;
|
|
42
|
+
private disabledFlag;
|
|
43
|
+
constructor(config: DamageModelConfig);
|
|
44
|
+
get total(): number;
|
|
45
|
+
get disabled(): boolean;
|
|
46
|
+
stageOf(zoneId: string): number;
|
|
47
|
+
states(): readonly DamageZoneState[];
|
|
48
|
+
absorb(zoneId: string, impulse: number): DamageTransition | null;
|
|
49
|
+
/**
|
|
50
|
+
* Route a raw {@link CollisionEvent} into a zone by a caller-supplied selector (front/rear/side from
|
|
51
|
+
* `nx`/`nz`, or which body index took the hit). The event's `impulse` feeds `absorb`.
|
|
52
|
+
*/
|
|
53
|
+
routeCollision(event: CollisionEvent, resolveZone: (event: CollisionEvent) => string | null): DamageTransition | null;
|
|
54
|
+
reset(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function createDamageModel(config: DamageModelConfig): DamageModel;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
function stageFor(thresholds, accumulated) {
|
|
2
|
+
let stage = 0;
|
|
3
|
+
for (const t of thresholds) {
|
|
4
|
+
if (accumulated >= t)
|
|
5
|
+
stage += 1;
|
|
6
|
+
else
|
|
7
|
+
break;
|
|
8
|
+
}
|
|
9
|
+
return stage;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Coarse crash-damage model (issue #86 — stages, not soft-body). Each zone accumulates contact impulse
|
|
13
|
+
* fed from G3's `onCollision` `CollisionEvent`; crossing a threshold bumps the zone to the next discrete
|
|
14
|
+
* stage (the caller swaps the visual/collider), an optional `detachStage` ejects a part as debris once,
|
|
15
|
+
* and a total-impulse `disableAt` flips the whole body to a disabled state. `absorb` returns a transition
|
|
16
|
+
* only when something actually changed, so it doubles as the event source for HUD/feed.
|
|
17
|
+
*/
|
|
18
|
+
export class DamageModel {
|
|
19
|
+
zones = new Map();
|
|
20
|
+
order = [];
|
|
21
|
+
disableAt;
|
|
22
|
+
onStage;
|
|
23
|
+
onDetach;
|
|
24
|
+
totalImpulse = 0;
|
|
25
|
+
disabledFlag = false;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
for (const def of config.zones) {
|
|
28
|
+
this.zones.set(def.id, { def, state: { id: def.id, accumulated: 0, stage: 0, detached: false } });
|
|
29
|
+
this.order.push(def.id);
|
|
30
|
+
}
|
|
31
|
+
this.disableAt = config.disableAt ?? null;
|
|
32
|
+
this.onStage = config.onStage;
|
|
33
|
+
this.onDetach = config.onDetach;
|
|
34
|
+
}
|
|
35
|
+
get total() {
|
|
36
|
+
return this.totalImpulse;
|
|
37
|
+
}
|
|
38
|
+
get disabled() {
|
|
39
|
+
return this.disabledFlag;
|
|
40
|
+
}
|
|
41
|
+
stageOf(zoneId) {
|
|
42
|
+
return this.zones.get(zoneId)?.state.stage ?? 0;
|
|
43
|
+
}
|
|
44
|
+
states() {
|
|
45
|
+
return this.order.map((id) => {
|
|
46
|
+
const s = this.zones.get(id).state;
|
|
47
|
+
return { id: s.id, accumulated: s.accumulated, stage: s.stage, detached: s.detached };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
absorb(zoneId, impulse) {
|
|
51
|
+
const entry = this.zones.get(zoneId);
|
|
52
|
+
if (entry === undefined || impulse <= 0)
|
|
53
|
+
return null;
|
|
54
|
+
const { def, state } = entry;
|
|
55
|
+
const previousStage = state.stage;
|
|
56
|
+
const wasDisabled = this.disabledFlag;
|
|
57
|
+
state.accumulated += impulse;
|
|
58
|
+
this.totalImpulse += impulse;
|
|
59
|
+
state.stage = stageFor(def.thresholds, state.accumulated);
|
|
60
|
+
let detachedNow = false;
|
|
61
|
+
if (def.detachStage !== undefined &&
|
|
62
|
+
!state.detached &&
|
|
63
|
+
state.stage >= def.detachStage) {
|
|
64
|
+
state.detached = true;
|
|
65
|
+
detachedNow = true;
|
|
66
|
+
this.onDetach?.(zoneId, state.accumulated);
|
|
67
|
+
}
|
|
68
|
+
if (this.disableAt !== null && this.totalImpulse >= this.disableAt)
|
|
69
|
+
this.disabledFlag = true;
|
|
70
|
+
const changed = state.stage !== previousStage || detachedNow || this.disabledFlag !== wasDisabled;
|
|
71
|
+
if (!changed)
|
|
72
|
+
return null;
|
|
73
|
+
const transition = {
|
|
74
|
+
zone: zoneId,
|
|
75
|
+
stage: state.stage,
|
|
76
|
+
previousStage,
|
|
77
|
+
detached: state.detached,
|
|
78
|
+
disabled: this.disabledFlag,
|
|
79
|
+
accumulated: state.accumulated,
|
|
80
|
+
total: this.totalImpulse,
|
|
81
|
+
};
|
|
82
|
+
this.onStage?.(transition);
|
|
83
|
+
return transition;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Route a raw {@link CollisionEvent} into a zone by a caller-supplied selector (front/rear/side from
|
|
87
|
+
* `nx`/`nz`, or which body index took the hit). The event's `impulse` feeds `absorb`.
|
|
88
|
+
*/
|
|
89
|
+
routeCollision(event, resolveZone) {
|
|
90
|
+
const zone = resolveZone(event);
|
|
91
|
+
if (zone === null)
|
|
92
|
+
return null;
|
|
93
|
+
return this.absorb(zone, Math.abs(event.impulse));
|
|
94
|
+
}
|
|
95
|
+
reset() {
|
|
96
|
+
for (const { state } of this.zones.values()) {
|
|
97
|
+
state.accumulated = 0;
|
|
98
|
+
state.stage = 0;
|
|
99
|
+
state.detached = false;
|
|
100
|
+
}
|
|
101
|
+
this.totalImpulse = 0;
|
|
102
|
+
this.disabledFlag = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function createDamageModel(config) {
|
|
106
|
+
return new DamageModel(config);
|
|
107
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PhysicsBounds, PhysicsWorld } from "./physicsWorld.js";
|
|
2
|
+
export type ForceMode = "impulse" | "velocity" | "accelerate";
|
|
3
|
+
export interface ForceVolumeConfig {
|
|
4
|
+
/** Region a body's center must be inside to be affected. */
|
|
5
|
+
bounds: PhysicsBounds;
|
|
6
|
+
/** `impulse` adds to velocity, `velocity` sets it, `accelerate` adds force·dt each tick. */
|
|
7
|
+
force: readonly [number, number, number];
|
|
8
|
+
/** Default `impulse`. */
|
|
9
|
+
mode?: ForceMode;
|
|
10
|
+
/** Fire only when a body first enters (boost pad) rather than every tick inside (fan/wind). Default false. */
|
|
11
|
+
once?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A trigger region that pushes bodies passing through it — boost pads (`impulse` + `once`),
|
|
15
|
+
* conveyors (`velocity`), fans/wind (`accelerate`). Call `apply` each tick; `once` mode fires only
|
|
16
|
+
* on entry by tracking membership between ticks.
|
|
17
|
+
*/
|
|
18
|
+
export declare class ForceVolume {
|
|
19
|
+
readonly bounds: PhysicsBounds;
|
|
20
|
+
readonly mode: ForceMode;
|
|
21
|
+
readonly once: boolean;
|
|
22
|
+
private readonly fx;
|
|
23
|
+
private readonly fy;
|
|
24
|
+
private readonly fz;
|
|
25
|
+
private members;
|
|
26
|
+
private nextMembers;
|
|
27
|
+
constructor(config: ForceVolumeConfig);
|
|
28
|
+
apply(world: PhysicsWorld, dt: number): void;
|
|
29
|
+
}
|
|
30
|
+
export interface PlatformCarryConfig {
|
|
31
|
+
/** Vertical gap between a rider's base and the platform top counted as "standing on". Default 0.12. */
|
|
32
|
+
contactTolerance?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Carries bodies standing on a moving platform by composing their transform with the platform's
|
|
36
|
+
* per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The
|
|
37
|
+
* platform is a body the game repositions each frame; riders are detected by overlap on its top face.
|
|
38
|
+
*/
|
|
39
|
+
export declare class PlatformCarry {
|
|
40
|
+
private readonly world;
|
|
41
|
+
private readonly platform;
|
|
42
|
+
private readonly tolerance;
|
|
43
|
+
private prevX;
|
|
44
|
+
private prevY;
|
|
45
|
+
private prevZ;
|
|
46
|
+
constructor(world: PhysicsWorld, platform: number, config?: PlatformCarryConfig);
|
|
47
|
+
step(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
function inside(bounds, x, y, z) {
|
|
2
|
+
return (x >= bounds.min[0] &&
|
|
3
|
+
x <= bounds.max[0] &&
|
|
4
|
+
y >= bounds.min[1] &&
|
|
5
|
+
y <= bounds.max[1] &&
|
|
6
|
+
z >= bounds.min[2] &&
|
|
7
|
+
z <= bounds.max[2]);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A trigger region that pushes bodies passing through it — boost pads (`impulse` + `once`),
|
|
11
|
+
* conveyors (`velocity`), fans/wind (`accelerate`). Call `apply` each tick; `once` mode fires only
|
|
12
|
+
* on entry by tracking membership between ticks.
|
|
13
|
+
*/
|
|
14
|
+
export class ForceVolume {
|
|
15
|
+
bounds;
|
|
16
|
+
mode;
|
|
17
|
+
once;
|
|
18
|
+
fx;
|
|
19
|
+
fy;
|
|
20
|
+
fz;
|
|
21
|
+
members = new Set();
|
|
22
|
+
nextMembers = new Set();
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.bounds = config.bounds;
|
|
25
|
+
this.mode = config.mode ?? "impulse";
|
|
26
|
+
this.once = config.once ?? false;
|
|
27
|
+
this.fx = config.force[0];
|
|
28
|
+
this.fy = config.force[1];
|
|
29
|
+
this.fz = config.force[2];
|
|
30
|
+
}
|
|
31
|
+
apply(world, dt) {
|
|
32
|
+
const next = this.nextMembers;
|
|
33
|
+
next.clear();
|
|
34
|
+
const count = world.count;
|
|
35
|
+
for (let i = 0; i < count; i += 1) {
|
|
36
|
+
if (world.invMass[i] === 0)
|
|
37
|
+
continue;
|
|
38
|
+
if (!inside(this.bounds, world.posX[i], world.posY[i], world.posZ[i]))
|
|
39
|
+
continue;
|
|
40
|
+
const wasInside = this.members.has(i);
|
|
41
|
+
next.add(i);
|
|
42
|
+
if (this.once && wasInside)
|
|
43
|
+
continue;
|
|
44
|
+
switch (this.mode) {
|
|
45
|
+
case "velocity":
|
|
46
|
+
world.velX[i] = this.fx;
|
|
47
|
+
world.velY[i] = this.fy;
|
|
48
|
+
world.velZ[i] = this.fz;
|
|
49
|
+
break;
|
|
50
|
+
case "accelerate":
|
|
51
|
+
world.velX[i] += this.fx * dt;
|
|
52
|
+
world.velY[i] += this.fy * dt;
|
|
53
|
+
world.velZ[i] += this.fz * dt;
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
world.velX[i] += this.fx;
|
|
57
|
+
world.velY[i] += this.fy;
|
|
58
|
+
world.velZ[i] += this.fz;
|
|
59
|
+
}
|
|
60
|
+
world.wake(i);
|
|
61
|
+
}
|
|
62
|
+
this.nextMembers = this.members;
|
|
63
|
+
this.members = next;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Carries bodies standing on a moving platform by composing their transform with the platform's
|
|
68
|
+
* per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The
|
|
69
|
+
* platform is a body the game repositions each frame; riders are detected by overlap on its top face.
|
|
70
|
+
*/
|
|
71
|
+
export class PlatformCarry {
|
|
72
|
+
world;
|
|
73
|
+
platform;
|
|
74
|
+
tolerance;
|
|
75
|
+
prevX;
|
|
76
|
+
prevY;
|
|
77
|
+
prevZ;
|
|
78
|
+
constructor(world, platform, config = {}) {
|
|
79
|
+
this.world = world;
|
|
80
|
+
this.platform = platform;
|
|
81
|
+
this.tolerance = config.contactTolerance ?? 0.12;
|
|
82
|
+
this.prevX = world.posX[platform];
|
|
83
|
+
this.prevY = world.posY[platform];
|
|
84
|
+
this.prevZ = world.posZ[platform];
|
|
85
|
+
}
|
|
86
|
+
step() {
|
|
87
|
+
const w = this.world;
|
|
88
|
+
const p = this.platform;
|
|
89
|
+
const dx = w.posX[p] - this.prevX;
|
|
90
|
+
const dy = w.posY[p] - this.prevY;
|
|
91
|
+
const dz = w.posZ[p] - this.prevZ;
|
|
92
|
+
const topY = this.prevY + w.halfY[p];
|
|
93
|
+
const px = this.prevX;
|
|
94
|
+
const pz = this.prevZ;
|
|
95
|
+
this.prevX = w.posX[p];
|
|
96
|
+
this.prevY = w.posY[p];
|
|
97
|
+
this.prevZ = w.posZ[p];
|
|
98
|
+
if (dx === 0 && dy === 0 && dz === 0)
|
|
99
|
+
return;
|
|
100
|
+
const hx = w.halfX[p];
|
|
101
|
+
const hz = w.halfZ[p];
|
|
102
|
+
const count = w.count;
|
|
103
|
+
for (let i = 0; i < count; i += 1) {
|
|
104
|
+
if (i === p)
|
|
105
|
+
continue;
|
|
106
|
+
const base = w.posY[i] - w.halfY[i];
|
|
107
|
+
if (base < topY - this.tolerance || base > topY + this.tolerance)
|
|
108
|
+
continue;
|
|
109
|
+
if (Math.abs(w.posX[i] - px) > hx + w.halfX[i])
|
|
110
|
+
continue;
|
|
111
|
+
if (Math.abs(w.posZ[i] - pz) > hz + w.halfZ[i])
|
|
112
|
+
continue;
|
|
113
|
+
w.posX[i] += dx;
|
|
114
|
+
w.posY[i] += dy;
|
|
115
|
+
w.posZ[i] += dz;
|
|
116
|
+
w.wake(i);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -35,6 +35,10 @@ export interface PhysicsWorldConfig {
|
|
|
35
35
|
sleepThresholdSteps?: number;
|
|
36
36
|
/** Approach speed a contact must exceed to wake a sleeping body (resting contacts don't). Default 0.5. */
|
|
37
37
|
wakeThreshold?: number;
|
|
38
|
+
/** Maximum simultaneous joints the constraint buffers can hold. Default 256. */
|
|
39
|
+
jointCapacity?: number;
|
|
40
|
+
/** Baumgarte fraction applied to hard-joint positional error per iteration, 0..1. Default 0.5. */
|
|
41
|
+
jointCorrection?: number;
|
|
38
42
|
}
|
|
39
43
|
export interface PhysicsStats {
|
|
40
44
|
count: number;
|
|
@@ -56,6 +60,42 @@ export interface AddBodyOptions {
|
|
|
56
60
|
/** Seed the body asleep (a settled bed pays no integration cost until woken). */
|
|
57
61
|
asleep?: boolean;
|
|
58
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* `hinge`/`fixed`/`distance` are hard bilateral constraints; `spring` is a soft PD constraint.
|
|
65
|
+
* No angular state → `hinge`/`fixed` both pin the shared anchor (`axis` is metadata), `distance`
|
|
66
|
+
* holds a fixed separation, `spring` drives toward `restLength` (suspension, follow-point carry).
|
|
67
|
+
*/
|
|
68
|
+
export type JointKind = "hinge" | "distance" | "spring" | "fixed";
|
|
69
|
+
export interface JointOptions {
|
|
70
|
+
/** First body index. */
|
|
71
|
+
bodyA: number;
|
|
72
|
+
/** Second body index, or omit / < 0 to anchor A to a fixed world point (`anchorB`). */
|
|
73
|
+
bodyB?: number;
|
|
74
|
+
/** Local offset of the anchor on A, in world axes (no rotation). Default [0,0,0]. */
|
|
75
|
+
anchorA?: readonly [number, number, number];
|
|
76
|
+
/** Local offset on B, or — when B is a world anchor — the world point itself. Default [0,0,0]. */
|
|
77
|
+
anchorB?: readonly [number, number, number];
|
|
78
|
+
/** Target separation for `distance`/`spring`. Defaults to the anchors' current distance. */
|
|
79
|
+
restLength?: number;
|
|
80
|
+
/** Spring restoring rate. Default 40. */
|
|
81
|
+
stiffness?: number;
|
|
82
|
+
/** Spring damping (relative-velocity bleed). Default 6. */
|
|
83
|
+
damping?: number;
|
|
84
|
+
/** Clamp on the per-substep spring impulse (a follow-point that never yanks). Default unbounded. */
|
|
85
|
+
maxImpulse?: number;
|
|
86
|
+
/** Hinge axis, retained as metadata (this sim has no angular DOF). */
|
|
87
|
+
axis?: readonly [number, number, number];
|
|
88
|
+
}
|
|
89
|
+
/** A contact reported to `onCollision`. The object is reused each call — read/copy, never retain. */
|
|
90
|
+
export interface CollisionEvent {
|
|
91
|
+
a: number;
|
|
92
|
+
b: number;
|
|
93
|
+
nx: number;
|
|
94
|
+
ny: number;
|
|
95
|
+
nz: number;
|
|
96
|
+
approachSpeed: number;
|
|
97
|
+
impulse: number;
|
|
98
|
+
}
|
|
59
99
|
/** Grid cell containing a point, clamped into the grid. Pure — exported for tests. */
|
|
60
100
|
export declare function cellCoord(value: number, min: number, cellSize: number, cells: number): number;
|
|
61
101
|
/** Linear cell index from clamped 3D coords. Pure — exported for tests. */
|
|
@@ -77,6 +117,8 @@ export declare class PhysicsWorld {
|
|
|
77
117
|
readonly sleepMove2: number;
|
|
78
118
|
readonly sleepSteps: number;
|
|
79
119
|
readonly wakeThreshold: number;
|
|
120
|
+
readonly jointCapacity: number;
|
|
121
|
+
readonly jointCorrection: number;
|
|
80
122
|
readonly posX: Float32Array;
|
|
81
123
|
readonly posY: Float32Array;
|
|
82
124
|
readonly posZ: Float32Array;
|
|
@@ -105,6 +147,27 @@ export declare class PhysicsWorld {
|
|
|
105
147
|
private readonly awakeList;
|
|
106
148
|
private readonly awakeSlot;
|
|
107
149
|
private awakeCount;
|
|
150
|
+
private readonly jSpring;
|
|
151
|
+
private readonly jActive;
|
|
152
|
+
private readonly jBodyA;
|
|
153
|
+
private readonly jBodyB;
|
|
154
|
+
private readonly jAnchorAX;
|
|
155
|
+
private readonly jAnchorAY;
|
|
156
|
+
private readonly jAnchorAZ;
|
|
157
|
+
private readonly jAnchorBX;
|
|
158
|
+
private readonly jAnchorBY;
|
|
159
|
+
private readonly jAnchorBZ;
|
|
160
|
+
private readonly jRest;
|
|
161
|
+
private readonly jStiffness;
|
|
162
|
+
private readonly jDamping;
|
|
163
|
+
private readonly jMaxImpulse;
|
|
164
|
+
private readonly jFree;
|
|
165
|
+
private jFreeCount;
|
|
166
|
+
private jointHigh;
|
|
167
|
+
private jointActiveCount;
|
|
168
|
+
private collisionListener;
|
|
169
|
+
private collisionMinSpeed;
|
|
170
|
+
private readonly collisionEvent;
|
|
108
171
|
private bodyCount;
|
|
109
172
|
private accumulator;
|
|
110
173
|
private readonly stats;
|
|
@@ -118,6 +181,25 @@ export declare class PhysicsWorld {
|
|
|
118
181
|
nz: number;
|
|
119
182
|
total: number;
|
|
120
183
|
};
|
|
184
|
+
get jointCount(): number;
|
|
185
|
+
/**
|
|
186
|
+
* Deliver every impacting contact (approach speed ≥ `minApproachSpeed`) to `listener` during
|
|
187
|
+
* `step`. The event object is reused — read/copy it, never retain. Pass `null` to detach. This is
|
|
188
|
+
* the seam crash-damage and destruction read; contacts otherwise stay inside the sim.
|
|
189
|
+
*/
|
|
190
|
+
onCollision(listener: ((e: CollisionEvent) => void) | null, minApproachSpeed?: number): void;
|
|
191
|
+
hingeJoint(options: JointOptions): number;
|
|
192
|
+
fixedJoint(options: JointOptions): number;
|
|
193
|
+
distanceJoint(options: JointOptions): number;
|
|
194
|
+
springJoint(options: JointOptions): number;
|
|
195
|
+
private addJoint;
|
|
196
|
+
removeJoint(id: number): void;
|
|
197
|
+
/** Move a joint's world anchor point (only meaningful when `bodyB` is a world anchor). */
|
|
198
|
+
setJointAnchor(id: number, x: number, y: number, z: number): void;
|
|
199
|
+
setJointRest(id: number, restLength: number): void;
|
|
200
|
+
private jointDistance;
|
|
201
|
+
/** Write [ax,ay,az,bx,by,bz] world endpoints per active joint into `out`; returns the joint count. */
|
|
202
|
+
readJointSegments(out: Float32Array): number;
|
|
121
203
|
addBody(options: AddBodyOptions): number;
|
|
122
204
|
isSleeping(i: number): boolean;
|
|
123
205
|
wake(i: number): void;
|
|
@@ -132,5 +214,6 @@ export declare class PhysicsWorld {
|
|
|
132
214
|
private cellOf;
|
|
133
215
|
private solve;
|
|
134
216
|
private resolve;
|
|
217
|
+
private solveJoints;
|
|
135
218
|
private updateSleep;
|
|
136
219
|
}
|