@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,83 @@
|
|
|
1
|
+
import type { PhysicsWorld } from "./physicsWorld.js";
|
|
2
|
+
export interface StructureMaterial {
|
|
3
|
+
id: string;
|
|
4
|
+
/** Node integrity and edge strength default to this — brittle glass vs load-bearing concrete. */
|
|
5
|
+
strength: number;
|
|
6
|
+
/** Debris body mass when a node of this material collapses. Default 1. */
|
|
7
|
+
mass?: number;
|
|
8
|
+
}
|
|
9
|
+
export type StructureMaterialTable = Readonly<Record<string, StructureMaterial>>;
|
|
10
|
+
export interface StructureNodeSpec {
|
|
11
|
+
id: string;
|
|
12
|
+
/** Body centre in world coordinates — also where its debris spawns. */
|
|
13
|
+
position: readonly [number, number, number];
|
|
14
|
+
halfExtents: readonly [number, number, number];
|
|
15
|
+
/** Overrides material mass for the collapsed debris body. */
|
|
16
|
+
mass?: number;
|
|
17
|
+
/** Hit points before the piece shatters. Default from `material.strength`, else 100. */
|
|
18
|
+
integrity?: number;
|
|
19
|
+
material?: string;
|
|
20
|
+
/** Foundation piece — reachability to any anchor is what keeps the rest standing. */
|
|
21
|
+
anchor?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface StructureEdgeSpec {
|
|
24
|
+
a: string;
|
|
25
|
+
b: string;
|
|
26
|
+
/** Accumulated edge damage past this severs the connection. Default from `material.strength`, else 100. */
|
|
27
|
+
strength?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface CollapseEvent {
|
|
30
|
+
/**
|
|
31
|
+
* Node ids that left the structure this operation — the shattered node plus every piece the loss
|
|
32
|
+
* disconnected from an anchor. This is the replicated collapse: broadcast the id list, not each
|
|
33
|
+
* fragment's physics (games re-derive the debris locally).
|
|
34
|
+
*/
|
|
35
|
+
fell: readonly string[];
|
|
36
|
+
collapsed: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface DebrisConfig {
|
|
39
|
+
/** Outward kick magnitude applied to each debris body from `origin`. Default 3. */
|
|
40
|
+
impulse?: number;
|
|
41
|
+
/** Blast origin the kick radiates from. Defaults to each node's own position (straight drop). */
|
|
42
|
+
origin?: readonly [number, number, number];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A structural-integrity graph over a building — nodes are pieces (walls, beams, floors), edges are
|
|
46
|
+
* load-bearing connections, some nodes are anchored foundations. `damage`/`damageEdge` wear pieces and
|
|
47
|
+
* connections down; when a piece shatters or an edge severs, the graph recomputes which pieces still
|
|
48
|
+
* reach an anchor and hands back every newly-disconnected piece as one `CollapseEvent`. Feed that to
|
|
49
|
+
* `toDebris` to sink the fallen pieces into a `PhysicsWorld` as rigid bodies ("The Finals" smooth
|
|
50
|
+
* destruction, Rainbow Six walls). Coarse by design: it replicates the collapse event, not per fragment.
|
|
51
|
+
*/
|
|
52
|
+
export declare class StructureGraph {
|
|
53
|
+
private readonly nodes;
|
|
54
|
+
private readonly integrity;
|
|
55
|
+
private readonly adj;
|
|
56
|
+
private readonly edgeStrength;
|
|
57
|
+
private readonly edgeDamage;
|
|
58
|
+
private readonly fallenSpecs;
|
|
59
|
+
private readonly materials;
|
|
60
|
+
constructor(config: {
|
|
61
|
+
nodes: readonly StructureNodeSpec[];
|
|
62
|
+
edges: readonly StructureEdgeSpec[];
|
|
63
|
+
materials?: StructureMaterialTable;
|
|
64
|
+
});
|
|
65
|
+
private materialStrength;
|
|
66
|
+
has(id: string): boolean;
|
|
67
|
+
integrityOf(id: string): number;
|
|
68
|
+
nodeSpec(id: string): StructureNodeSpec | undefined;
|
|
69
|
+
neighbors(id: string): string[];
|
|
70
|
+
standing(): string[];
|
|
71
|
+
isSupported(id: string): boolean;
|
|
72
|
+
private supportedSet;
|
|
73
|
+
private removeNode;
|
|
74
|
+
private settle;
|
|
75
|
+
/** Wear a piece down; at ≤0 integrity it shatters and the loss cascades through unsupported pieces. */
|
|
76
|
+
damage(id: string, amount: number): CollapseEvent;
|
|
77
|
+
/** Wear a connection down; past its strength it severs and unsupported pieces cascade off. */
|
|
78
|
+
damageEdge(a: string, b: string, amount: number): CollapseEvent;
|
|
79
|
+
/** Cut a connection outright (a beam sheared by a blast), then cascade. */
|
|
80
|
+
severEdge(a: string, b: string): CollapseEvent;
|
|
81
|
+
/** Spawn one rigid debris body per fallen piece into `world`; returns the new body indices. */
|
|
82
|
+
toDebris(world: PhysicsWorld, event: CollapseEvent, config?: DebrisConfig): number[];
|
|
83
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { PhysicsWorld } from "./physicsWorld.js";
|
|
2
|
+
export interface GrappleConfig {
|
|
3
|
+
/** Metres per second the rope shortens under `reel` / lengthens under `payOut`. Default 8. */
|
|
4
|
+
reelSpeed?: number;
|
|
5
|
+
/** Rope refuses to shorten past this — the traveller stops on top of the anchor, not inside it. Default 1.5. */
|
|
6
|
+
minLength?: number;
|
|
7
|
+
/** Firing range; `fire` beyond this misses. Default 40. */
|
|
8
|
+
maxLength?: number;
|
|
9
|
+
/** Elastic rope (spring) that yanks and bounces, vs a rigid `distance` cable that holds a hard length. Default false. */
|
|
10
|
+
elastic?: boolean;
|
|
11
|
+
/** Spring restoring rate when `elastic`. Default 40. */
|
|
12
|
+
stiffness?: number;
|
|
13
|
+
/** Spring damping when `elastic`. Default 6. */
|
|
14
|
+
damping?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A fired-anchor rope on the joint API — grapple (reel toward a hit point), zipline (rigid cable to a
|
|
18
|
+
* far anchor you then slide/reel along), swing (rigid rope + gravity = a pendulum). `fire` attaches a
|
|
19
|
+
* `distance`/`spring` joint from the traveller body to a fixed world point; `reel` shrinks its rest
|
|
20
|
+
* length so the constraint drags the body in; `moveAnchor` re-points it (zipline glide, grapple-to-
|
|
21
|
+
* moving-target). The pick — a raycast to find the anchor — is the caller's; core owns the constraint.
|
|
22
|
+
*/
|
|
23
|
+
export declare class Grapple {
|
|
24
|
+
readonly body: number;
|
|
25
|
+
private readonly world;
|
|
26
|
+
private readonly reelSpeed;
|
|
27
|
+
private readonly minLength;
|
|
28
|
+
private readonly maxLength;
|
|
29
|
+
private readonly elastic;
|
|
30
|
+
private readonly stiffness;
|
|
31
|
+
private readonly damping;
|
|
32
|
+
private joint;
|
|
33
|
+
private length;
|
|
34
|
+
private ax;
|
|
35
|
+
private ay;
|
|
36
|
+
private az;
|
|
37
|
+
constructor(world: PhysicsWorld, body: number, config?: GrappleConfig);
|
|
38
|
+
get attached(): boolean;
|
|
39
|
+
get ropeLength(): number;
|
|
40
|
+
anchor(): readonly [number, number, number] | null;
|
|
41
|
+
distanceToAnchor(): number;
|
|
42
|
+
/** Attach to a world anchor point. Returns false (a miss) when the point is beyond `maxLength`. */
|
|
43
|
+
fire(x: number, y: number, z: number): boolean;
|
|
44
|
+
/** Shorten the rope by `reelSpeed * dt`, pulling the traveller toward the anchor. Clamps at `minLength`. */
|
|
45
|
+
reel(dt: number): void;
|
|
46
|
+
/** Pay out slack, lengthening the rope up to `maxLength`. */
|
|
47
|
+
payOut(dt: number): void;
|
|
48
|
+
/** Re-point the anchor (slide a zipline attachment along the cable, or track a moving target). */
|
|
49
|
+
moveAnchor(x: number, y: number, z: number): void;
|
|
50
|
+
release(): void;
|
|
51
|
+
}
|
|
52
|
+
export interface GlideConfig {
|
|
53
|
+
/** Fraction of world gravity the glider still feels — 1 is normal fall, 0 hangs. Default 0.25. */
|
|
54
|
+
gravityScale?: number;
|
|
55
|
+
/** Forward acceleration applied along the steer vector each `apply`. Default 6. */
|
|
56
|
+
thrust?: number;
|
|
57
|
+
/** Terminal descent clamp so a glide never plummets. Default 8. */
|
|
58
|
+
maxFallSpeed?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A reduced-gravity, forward-thrust glide over a physics body — wingsuit / glider / paraglider
|
|
62
|
+
* (Enshrouded, Grounded). Call `apply(dt, steerX, steerZ)` each frame *before* `world.step`: it feeds
|
|
63
|
+
* back most of the gravity the sim is about to apply (leaving `gravityScale` of it), pushes the body
|
|
64
|
+
* along the steer vector by `thrust`, and clamps descent to `maxFallSpeed`. Stop calling it to fall
|
|
65
|
+
* normally again — no attach/detach state to leak.
|
|
66
|
+
*/
|
|
67
|
+
export declare class Glide {
|
|
68
|
+
readonly body: number;
|
|
69
|
+
private readonly world;
|
|
70
|
+
private readonly gravityScale;
|
|
71
|
+
private readonly thrust;
|
|
72
|
+
private readonly maxFallSpeed;
|
|
73
|
+
constructor(world: PhysicsWorld, body: number, config?: GlideConfig);
|
|
74
|
+
apply(dt: number, steerX?: number, steerZ?: number): void;
|
|
75
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fired-anchor rope on the joint API — grapple (reel toward a hit point), zipline (rigid cable to a
|
|
3
|
+
* far anchor you then slide/reel along), swing (rigid rope + gravity = a pendulum). `fire` attaches a
|
|
4
|
+
* `distance`/`spring` joint from the traveller body to a fixed world point; `reel` shrinks its rest
|
|
5
|
+
* length so the constraint drags the body in; `moveAnchor` re-points it (zipline glide, grapple-to-
|
|
6
|
+
* moving-target). The pick — a raycast to find the anchor — is the caller's; core owns the constraint.
|
|
7
|
+
*/
|
|
8
|
+
export class Grapple {
|
|
9
|
+
body;
|
|
10
|
+
world;
|
|
11
|
+
reelSpeed;
|
|
12
|
+
minLength;
|
|
13
|
+
maxLength;
|
|
14
|
+
elastic;
|
|
15
|
+
stiffness;
|
|
16
|
+
damping;
|
|
17
|
+
joint = -1;
|
|
18
|
+
length = 0;
|
|
19
|
+
ax = 0;
|
|
20
|
+
ay = 0;
|
|
21
|
+
az = 0;
|
|
22
|
+
constructor(world, body, config = {}) {
|
|
23
|
+
this.world = world;
|
|
24
|
+
this.body = body;
|
|
25
|
+
this.reelSpeed = config.reelSpeed ?? 8;
|
|
26
|
+
this.minLength = config.minLength ?? 1.5;
|
|
27
|
+
this.maxLength = config.maxLength ?? 40;
|
|
28
|
+
this.elastic = config.elastic ?? false;
|
|
29
|
+
this.stiffness = config.stiffness ?? 40;
|
|
30
|
+
this.damping = config.damping ?? 6;
|
|
31
|
+
}
|
|
32
|
+
get attached() {
|
|
33
|
+
return this.joint >= 0;
|
|
34
|
+
}
|
|
35
|
+
get ropeLength() {
|
|
36
|
+
return this.length;
|
|
37
|
+
}
|
|
38
|
+
anchor() {
|
|
39
|
+
return this.joint < 0 ? null : [this.ax, this.ay, this.az];
|
|
40
|
+
}
|
|
41
|
+
distanceToAnchor() {
|
|
42
|
+
const w = this.world;
|
|
43
|
+
const dx = this.ax - w.posX[this.body];
|
|
44
|
+
const dy = this.ay - w.posY[this.body];
|
|
45
|
+
const dz = this.az - w.posZ[this.body];
|
|
46
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
47
|
+
}
|
|
48
|
+
/** Attach to a world anchor point. Returns false (a miss) when the point is beyond `maxLength`. */
|
|
49
|
+
fire(x, y, z) {
|
|
50
|
+
if (this.joint >= 0)
|
|
51
|
+
this.release();
|
|
52
|
+
const w = this.world;
|
|
53
|
+
const dx = x - w.posX[this.body];
|
|
54
|
+
const dy = y - w.posY[this.body];
|
|
55
|
+
const dz = z - w.posZ[this.body];
|
|
56
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
57
|
+
if (dist > this.maxLength)
|
|
58
|
+
return false;
|
|
59
|
+
this.ax = x;
|
|
60
|
+
this.ay = y;
|
|
61
|
+
this.az = z;
|
|
62
|
+
this.length = dist;
|
|
63
|
+
const opts = {
|
|
64
|
+
bodyA: this.body,
|
|
65
|
+
anchorB: [x, y, z],
|
|
66
|
+
restLength: dist,
|
|
67
|
+
stiffness: this.stiffness,
|
|
68
|
+
damping: this.damping,
|
|
69
|
+
};
|
|
70
|
+
this.joint = this.elastic ? w.springJoint(opts) : w.distanceJoint(opts);
|
|
71
|
+
w.wake(this.body);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/** Shorten the rope by `reelSpeed * dt`, pulling the traveller toward the anchor. Clamps at `minLength`. */
|
|
75
|
+
reel(dt) {
|
|
76
|
+
if (this.joint < 0)
|
|
77
|
+
return;
|
|
78
|
+
this.length = Math.max(this.minLength, this.length - this.reelSpeed * dt);
|
|
79
|
+
this.world.setJointRest(this.joint, this.length);
|
|
80
|
+
this.world.wake(this.body);
|
|
81
|
+
}
|
|
82
|
+
/** Pay out slack, lengthening the rope up to `maxLength`. */
|
|
83
|
+
payOut(dt) {
|
|
84
|
+
if (this.joint < 0)
|
|
85
|
+
return;
|
|
86
|
+
this.length = Math.min(this.maxLength, this.length + this.reelSpeed * dt);
|
|
87
|
+
this.world.setJointRest(this.joint, this.length);
|
|
88
|
+
this.world.wake(this.body);
|
|
89
|
+
}
|
|
90
|
+
/** Re-point the anchor (slide a zipline attachment along the cable, or track a moving target). */
|
|
91
|
+
moveAnchor(x, y, z) {
|
|
92
|
+
if (this.joint < 0)
|
|
93
|
+
return;
|
|
94
|
+
this.ax = x;
|
|
95
|
+
this.ay = y;
|
|
96
|
+
this.az = z;
|
|
97
|
+
this.world.setJointAnchor(this.joint, x, y, z);
|
|
98
|
+
this.world.wake(this.body);
|
|
99
|
+
}
|
|
100
|
+
release() {
|
|
101
|
+
if (this.joint < 0)
|
|
102
|
+
return;
|
|
103
|
+
this.world.removeJoint(this.joint);
|
|
104
|
+
this.joint = -1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A reduced-gravity, forward-thrust glide over a physics body — wingsuit / glider / paraglider
|
|
109
|
+
* (Enshrouded, Grounded). Call `apply(dt, steerX, steerZ)` each frame *before* `world.step`: it feeds
|
|
110
|
+
* back most of the gravity the sim is about to apply (leaving `gravityScale` of it), pushes the body
|
|
111
|
+
* along the steer vector by `thrust`, and clamps descent to `maxFallSpeed`. Stop calling it to fall
|
|
112
|
+
* normally again — no attach/detach state to leak.
|
|
113
|
+
*/
|
|
114
|
+
export class Glide {
|
|
115
|
+
body;
|
|
116
|
+
world;
|
|
117
|
+
gravityScale;
|
|
118
|
+
thrust;
|
|
119
|
+
maxFallSpeed;
|
|
120
|
+
constructor(world, body, config = {}) {
|
|
121
|
+
this.world = world;
|
|
122
|
+
this.body = body;
|
|
123
|
+
this.gravityScale = config.gravityScale ?? 0.25;
|
|
124
|
+
this.thrust = config.thrust ?? 6;
|
|
125
|
+
this.maxFallSpeed = config.maxFallSpeed ?? 8;
|
|
126
|
+
}
|
|
127
|
+
apply(dt, steerX = 0, steerZ = 0) {
|
|
128
|
+
const w = this.world;
|
|
129
|
+
const b = this.body;
|
|
130
|
+
const counter = -w.gravity * (1 - this.gravityScale);
|
|
131
|
+
w.velY[b] += counter * dt;
|
|
132
|
+
w.velX[b] += steerX * this.thrust * dt;
|
|
133
|
+
w.velZ[b] += steerZ * this.thrust * dt;
|
|
134
|
+
if (w.velY[b] < -this.maxFallSpeed)
|
|
135
|
+
w.velY[b] = -this.maxFallSpeed;
|
|
136
|
+
w.wake(b);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { PhysicsWorld } from "./physicsWorld.js";
|
|
2
|
+
import type { AxisInput } from "../input/axisInput.js";
|
|
3
|
+
export interface GripCurve {
|
|
4
|
+
points: readonly (readonly [number, number])[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near
|
|
8
|
+
* the breakaway slip then falls off as the tire slides — the shape that separates a planted corner
|
|
9
|
+
* from a drift. Points are read in ascending slip order; ends clamp.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sampleGripCurve(curve: GripCurve, slip: number): number;
|
|
12
|
+
export declare const DEFAULT_GRIP_CURVE: GripCurve;
|
|
13
|
+
export interface WheelSpec {
|
|
14
|
+
offset: readonly [number, number, number];
|
|
15
|
+
radius?: number;
|
|
16
|
+
restLength?: number;
|
|
17
|
+
steered?: boolean;
|
|
18
|
+
powered?: boolean;
|
|
19
|
+
handbrakeLocks?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface WheelState {
|
|
22
|
+
worldX: number;
|
|
23
|
+
worldY: number;
|
|
24
|
+
worldZ: number;
|
|
25
|
+
grounded: boolean;
|
|
26
|
+
compression: number;
|
|
27
|
+
steerAngle: number;
|
|
28
|
+
}
|
|
29
|
+
export interface VehicleBodyConfig {
|
|
30
|
+
position: readonly [number, number, number];
|
|
31
|
+
heading?: number;
|
|
32
|
+
chassisHalfExtents?: readonly [number, number, number];
|
|
33
|
+
mass?: number;
|
|
34
|
+
wheels?: readonly WheelSpec[];
|
|
35
|
+
engineAccel?: number;
|
|
36
|
+
brakeAccel?: number;
|
|
37
|
+
topSpeed?: number;
|
|
38
|
+
reverseSpeed?: number;
|
|
39
|
+
turnRate?: number;
|
|
40
|
+
turnSpeedRef?: number;
|
|
41
|
+
grip?: GripCurve;
|
|
42
|
+
gripStrength?: number;
|
|
43
|
+
handbrakeGrip?: number;
|
|
44
|
+
rollingResistance?: number;
|
|
45
|
+
suspensionRest?: number;
|
|
46
|
+
suspensionStiffness?: number;
|
|
47
|
+
suspensionDamping?: number;
|
|
48
|
+
groundHeight?: (x: number, z: number) => number;
|
|
49
|
+
surfaceFriction?: (x: number, z: number) => number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Arcade vehicle over the {@link PhysicsWorld} rigid-body sim. The chassis is a single box body driven
|
|
53
|
+
* by an {@link AxisInput}: per-wheel suspension is a spring-damper held with G3's `springJoint` against
|
|
54
|
+
* the sampled ground, drive/brake push along the heading, and a tire-grip curve bleeds lateral velocity
|
|
55
|
+
* (cornering, and drift under handbrake). Because the chassis is a physics body it still collides — that
|
|
56
|
+
* contact feeds crash damage. Call `update(dt, input)` before the shared `world.step(dt)`.
|
|
57
|
+
*/
|
|
58
|
+
export declare class VehicleBody {
|
|
59
|
+
heading: number;
|
|
60
|
+
readonly chassis: number;
|
|
61
|
+
private readonly world;
|
|
62
|
+
private readonly wheels;
|
|
63
|
+
private readonly springs;
|
|
64
|
+
private readonly wheelStates;
|
|
65
|
+
private readonly engineAccel;
|
|
66
|
+
private readonly brakeAccel;
|
|
67
|
+
private readonly topSpeed;
|
|
68
|
+
private readonly reverseSpeed;
|
|
69
|
+
private readonly turnRate;
|
|
70
|
+
private readonly turnSpeedRef;
|
|
71
|
+
private readonly grip;
|
|
72
|
+
private readonly gripStrength;
|
|
73
|
+
private readonly handbrakeGrip;
|
|
74
|
+
private readonly rollingResistance;
|
|
75
|
+
private readonly suspensionRest;
|
|
76
|
+
private readonly groundHeight;
|
|
77
|
+
private readonly surfaceFriction;
|
|
78
|
+
private groundedCount;
|
|
79
|
+
constructor(world: PhysicsWorld, config: VehicleBodyConfig);
|
|
80
|
+
private wheelMount;
|
|
81
|
+
get position(): [number, number, number];
|
|
82
|
+
get forward(): [number, number];
|
|
83
|
+
/** Signed forward speed along the heading (negative = reversing). */
|
|
84
|
+
get speed(): number;
|
|
85
|
+
get grounded(): boolean;
|
|
86
|
+
wheelState(index: number): WheelState | null;
|
|
87
|
+
get wheelCount(): number;
|
|
88
|
+
resetTo(position: readonly [number, number, number], heading: number): void;
|
|
89
|
+
update(dt: number, input: AxisInput): void;
|
|
90
|
+
private updateSuspension;
|
|
91
|
+
remove(): void;
|
|
92
|
+
}
|
|
93
|
+
export declare function createVehicleBody(world: PhysicsWorld, config: VehicleBodyConfig): VehicleBody;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Piecewise-linear tire-grip curve: normalized lateral slip → available grip (0..1). Grip peaks near
|
|
3
|
+
* the breakaway slip then falls off as the tire slides — the shape that separates a planted corner
|
|
4
|
+
* from a drift. Points are read in ascending slip order; ends clamp.
|
|
5
|
+
*/
|
|
6
|
+
export function sampleGripCurve(curve, slip) {
|
|
7
|
+
const pts = curve.points;
|
|
8
|
+
if (pts.length === 0)
|
|
9
|
+
return 1;
|
|
10
|
+
const s = Math.abs(slip);
|
|
11
|
+
if (s <= pts[0][0])
|
|
12
|
+
return pts[0][1];
|
|
13
|
+
for (let i = 1; i < pts.length; i += 1) {
|
|
14
|
+
const [x1, y1] = pts[i];
|
|
15
|
+
if (s <= x1) {
|
|
16
|
+
const [x0, y0] = pts[i - 1];
|
|
17
|
+
const span = x1 - x0;
|
|
18
|
+
const t = span <= 0 ? 0 : (s - x0) / span;
|
|
19
|
+
return y0 + (y1 - y0) * t;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return pts[pts.length - 1][1];
|
|
23
|
+
}
|
|
24
|
+
export const DEFAULT_GRIP_CURVE = {
|
|
25
|
+
points: [
|
|
26
|
+
[0, 1],
|
|
27
|
+
[0.2, 1],
|
|
28
|
+
[0.45, 0.78],
|
|
29
|
+
[1, 0.55],
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
function cornerWheels(half, rest, radius) {
|
|
33
|
+
const x = half[0];
|
|
34
|
+
const z = half[2];
|
|
35
|
+
const y = -half[1];
|
|
36
|
+
return [
|
|
37
|
+
{ offset: [-x, y, z], steered: true, restLength: rest, radius },
|
|
38
|
+
{ offset: [x, y, z], steered: true, restLength: rest, radius },
|
|
39
|
+
{ offset: [-x, y, -z], powered: true, handbrakeLocks: true, restLength: rest, radius },
|
|
40
|
+
{ offset: [x, y, -z], powered: true, handbrakeLocks: true, restLength: rest, radius },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Arcade vehicle over the {@link PhysicsWorld} rigid-body sim. The chassis is a single box body driven
|
|
45
|
+
* by an {@link AxisInput}: per-wheel suspension is a spring-damper held with G3's `springJoint` against
|
|
46
|
+
* the sampled ground, drive/brake push along the heading, and a tire-grip curve bleeds lateral velocity
|
|
47
|
+
* (cornering, and drift under handbrake). Because the chassis is a physics body it still collides — that
|
|
48
|
+
* contact feeds crash damage. Call `update(dt, input)` before the shared `world.step(dt)`.
|
|
49
|
+
*/
|
|
50
|
+
export class VehicleBody {
|
|
51
|
+
heading;
|
|
52
|
+
chassis;
|
|
53
|
+
world;
|
|
54
|
+
wheels;
|
|
55
|
+
springs;
|
|
56
|
+
wheelStates;
|
|
57
|
+
engineAccel;
|
|
58
|
+
brakeAccel;
|
|
59
|
+
topSpeed;
|
|
60
|
+
reverseSpeed;
|
|
61
|
+
turnRate;
|
|
62
|
+
turnSpeedRef;
|
|
63
|
+
grip;
|
|
64
|
+
gripStrength;
|
|
65
|
+
handbrakeGrip;
|
|
66
|
+
rollingResistance;
|
|
67
|
+
suspensionRest;
|
|
68
|
+
groundHeight;
|
|
69
|
+
surfaceFriction;
|
|
70
|
+
groundedCount = 0;
|
|
71
|
+
constructor(world, config) {
|
|
72
|
+
this.world = world;
|
|
73
|
+
this.heading = config.heading ?? 0;
|
|
74
|
+
const half = config.chassisHalfExtents ?? [0.9, 0.35, 1.8];
|
|
75
|
+
this.suspensionRest = config.suspensionRest ?? 0.55;
|
|
76
|
+
const radius = 0.35;
|
|
77
|
+
this.wheels = config.wheels ?? cornerWheels(half, this.suspensionRest, radius);
|
|
78
|
+
this.engineAccel = config.engineAccel ?? 18;
|
|
79
|
+
this.brakeAccel = config.brakeAccel ?? 26;
|
|
80
|
+
this.topSpeed = config.topSpeed ?? 28;
|
|
81
|
+
this.reverseSpeed = config.reverseSpeed ?? 10;
|
|
82
|
+
this.turnRate = config.turnRate ?? 1.6;
|
|
83
|
+
this.turnSpeedRef = config.turnSpeedRef ?? 8;
|
|
84
|
+
this.grip = config.grip ?? DEFAULT_GRIP_CURVE;
|
|
85
|
+
this.gripStrength = config.gripStrength ?? 8;
|
|
86
|
+
this.handbrakeGrip = config.handbrakeGrip ?? 0.35;
|
|
87
|
+
this.rollingResistance = config.rollingResistance ?? 0.6;
|
|
88
|
+
const stiffness = config.suspensionStiffness ?? 60;
|
|
89
|
+
const damping = config.suspensionDamping ?? 8;
|
|
90
|
+
this.groundHeight = config.groundHeight ?? (() => 0);
|
|
91
|
+
this.surfaceFriction = config.surfaceFriction ?? (() => 1);
|
|
92
|
+
this.chassis = world.addBody({
|
|
93
|
+
position: config.position,
|
|
94
|
+
halfExtents: half,
|
|
95
|
+
mass: config.mass ?? 4,
|
|
96
|
+
});
|
|
97
|
+
this.springs = [];
|
|
98
|
+
this.wheelStates = [];
|
|
99
|
+
for (const wheel of this.wheels) {
|
|
100
|
+
const worldPos = this.wheelMount(wheel);
|
|
101
|
+
const rest = wheel.restLength ?? this.suspensionRest;
|
|
102
|
+
const id = world.springJoint({
|
|
103
|
+
bodyA: this.chassis,
|
|
104
|
+
anchorA: [wheel.offset[0], wheel.offset[1], wheel.offset[2]],
|
|
105
|
+
anchorB: [worldPos[0], worldPos[1] - rest, worldPos[2]],
|
|
106
|
+
restLength: rest,
|
|
107
|
+
stiffness,
|
|
108
|
+
damping,
|
|
109
|
+
});
|
|
110
|
+
this.springs.push(id);
|
|
111
|
+
this.wheelStates.push({
|
|
112
|
+
worldX: worldPos[0],
|
|
113
|
+
worldY: worldPos[1],
|
|
114
|
+
worldZ: worldPos[2],
|
|
115
|
+
grounded: false,
|
|
116
|
+
compression: 0,
|
|
117
|
+
steerAngle: 0,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
wheelMount(wheel) {
|
|
122
|
+
const sin = Math.sin(this.heading);
|
|
123
|
+
const cos = Math.cos(this.heading);
|
|
124
|
+
const [ox, oy, oz] = wheel.offset;
|
|
125
|
+
return [
|
|
126
|
+
this.world.posX[this.chassis] + ox * cos + oz * sin,
|
|
127
|
+
this.world.posY[this.chassis] + oy,
|
|
128
|
+
this.world.posZ[this.chassis] - ox * sin + oz * cos,
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
get position() {
|
|
132
|
+
return [this.world.posX[this.chassis], this.world.posY[this.chassis], this.world.posZ[this.chassis]];
|
|
133
|
+
}
|
|
134
|
+
get forward() {
|
|
135
|
+
return [Math.sin(this.heading), Math.cos(this.heading)];
|
|
136
|
+
}
|
|
137
|
+
/** Signed forward speed along the heading (negative = reversing). */
|
|
138
|
+
get speed() {
|
|
139
|
+
const [fx, fz] = this.forward;
|
|
140
|
+
return this.world.velX[this.chassis] * fx + this.world.velZ[this.chassis] * fz;
|
|
141
|
+
}
|
|
142
|
+
get grounded() {
|
|
143
|
+
return this.groundedCount > 0;
|
|
144
|
+
}
|
|
145
|
+
wheelState(index) {
|
|
146
|
+
return this.wheelStates[index] ?? null;
|
|
147
|
+
}
|
|
148
|
+
get wheelCount() {
|
|
149
|
+
return this.wheels.length;
|
|
150
|
+
}
|
|
151
|
+
resetTo(position, heading) {
|
|
152
|
+
const w = this.world;
|
|
153
|
+
const c = this.chassis;
|
|
154
|
+
w.posX[c] = position[0];
|
|
155
|
+
w.posY[c] = position[1];
|
|
156
|
+
w.posZ[c] = position[2];
|
|
157
|
+
w.velX[c] = 0;
|
|
158
|
+
w.velY[c] = 0;
|
|
159
|
+
w.velZ[c] = 0;
|
|
160
|
+
this.heading = heading;
|
|
161
|
+
w.wake(c);
|
|
162
|
+
}
|
|
163
|
+
update(dt, input) {
|
|
164
|
+
const w = this.world;
|
|
165
|
+
const c = this.chassis;
|
|
166
|
+
this.updateSuspension(input.steer);
|
|
167
|
+
const speed = this.speed;
|
|
168
|
+
const grounded = this.groundedCount > 0;
|
|
169
|
+
if (grounded) {
|
|
170
|
+
const steerScale = Math.min(1, Math.abs(speed) / this.turnSpeedRef);
|
|
171
|
+
const dir = speed >= 0 ? 1 : -1;
|
|
172
|
+
this.heading += input.steer * this.turnRate * steerScale * dir * dt;
|
|
173
|
+
}
|
|
174
|
+
const [fx, fz] = this.forward;
|
|
175
|
+
if (grounded) {
|
|
176
|
+
let accel = 0;
|
|
177
|
+
if (input.throttle > 0 && speed < this.topSpeed)
|
|
178
|
+
accel += input.throttle * this.engineAccel;
|
|
179
|
+
if (input.brake > 0) {
|
|
180
|
+
if (speed > 0.2)
|
|
181
|
+
accel -= input.brake * this.brakeAccel;
|
|
182
|
+
else if (speed > -this.reverseSpeed)
|
|
183
|
+
accel -= input.brake * this.engineAccel;
|
|
184
|
+
}
|
|
185
|
+
w.velX[c] += fx * accel * dt;
|
|
186
|
+
w.velZ[c] += fz * accel * dt;
|
|
187
|
+
const roll = Math.max(0, 1 - this.rollingResistance * dt * (input.throttle > 0 ? 0 : 1));
|
|
188
|
+
const forwardSpeed = w.velX[c] * fx + w.velZ[c] * fz;
|
|
189
|
+
const rolled = forwardSpeed * roll;
|
|
190
|
+
const lateralSpeed = -w.velX[c] * fz + w.velZ[c] * fx;
|
|
191
|
+
const surface = this.surfaceFriction(w.posX[c], w.posZ[c]);
|
|
192
|
+
const slip = Math.abs(lateralSpeed) / (Math.abs(forwardSpeed) + 1);
|
|
193
|
+
const handbrake = 1 - input.handbrake * (1 - this.handbrakeGrip);
|
|
194
|
+
const grip = sampleGripCurve(this.grip, slip) * surface * handbrake;
|
|
195
|
+
const keep = Math.max(0, 1 - grip * this.gripStrength * dt);
|
|
196
|
+
const newLateral = lateralSpeed * keep;
|
|
197
|
+
w.velX[c] = fx * rolled - fz * newLateral;
|
|
198
|
+
w.velZ[c] = fz * rolled + fx * newLateral;
|
|
199
|
+
w.wake(c);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
updateSuspension(steer) {
|
|
203
|
+
this.groundedCount = 0;
|
|
204
|
+
const w = this.world;
|
|
205
|
+
for (let i = 0; i < this.wheels.length; i += 1) {
|
|
206
|
+
const wheel = this.wheels[i];
|
|
207
|
+
const state = this.wheelStates[i];
|
|
208
|
+
const mount = this.wheelMount(wheel);
|
|
209
|
+
const radius = wheel.radius ?? 0.35;
|
|
210
|
+
const rest = wheel.restLength ?? this.suspensionRest;
|
|
211
|
+
const groundY = this.groundHeight(mount[0], mount[2]);
|
|
212
|
+
const gap = mount[1] - (groundY + radius);
|
|
213
|
+
const maxTravel = rest + radius;
|
|
214
|
+
const grounded = gap <= maxTravel;
|
|
215
|
+
w.setJointAnchor(this.springs[i], mount[0], groundY + radius, mount[2]);
|
|
216
|
+
state.worldX = mount[0];
|
|
217
|
+
state.worldY = groundY + radius;
|
|
218
|
+
state.worldZ = mount[2];
|
|
219
|
+
state.grounded = grounded;
|
|
220
|
+
state.compression = Math.max(0, rest - gap);
|
|
221
|
+
state.steerAngle = wheel.steered === true ? steer : 0;
|
|
222
|
+
if (grounded)
|
|
223
|
+
this.groundedCount += 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
remove() {
|
|
227
|
+
for (const id of this.springs)
|
|
228
|
+
this.world.removeJoint(id);
|
|
229
|
+
this.springs.length = 0;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export function createVehicleBody(world, config) {
|
|
233
|
+
return new VehicleBody(world, config);
|
|
234
|
+
}
|