@forgeax/engine-physics 0.1.2
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/LICENSE +202 -0
- package/README.md +233 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/physics.unit.test.d.ts +2 -0
- package/dist/__tests__/physics.unit.test.d.ts.map +1 -0
- package/dist/collision-event.d.ts +26 -0
- package/dist/collision-event.d.ts.map +1 -0
- package/dist/components.d.ts +162 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +137 -0
- package/dist/index.mjs.map +1 -0
- package/dist/physics-world.d.ts +138 -0
- package/dist/physics-world.d.ts.map +1 -0
- package/dist/plugin-factory.d.ts +22 -0
- package/dist/plugin-factory.d.ts.map +1 -0
- package/dist/system-set.d.ts +2 -0
- package/dist/system-set.d.ts.map +1 -0
- package/package.json +65 -0
- package/src/__tests__/physics.unit.test.ts +364 -0
- package/src/collision-event.ts +35 -0
- package/src/components.ts +225 -0
- package/src/errors.ts +10 -0
- package/src/index.ts +36 -0
- package/src/load-rapier-backend.d.mts +2 -0
- package/src/load-rapier-backend.mjs +8 -0
- package/src/physics-world.ts +165 -0
- package/src/plugin-factory.ts +90 -0
- package/src/system-set.ts +3 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { type Component, type World } from '@forgeax/engine-ecs';
|
|
2
|
+
/**
|
|
3
|
+
* RigidBody motion type — 3-state discriminant mirroring Rapier's
|
|
4
|
+
* Dynamic / Fixed / KinematicPositionBased triplet.
|
|
5
|
+
*
|
|
6
|
+
* `'static'`: infinite mass, never moves (Rapier Fixed).
|
|
7
|
+
* `'dynamic'`: driven by forces, gravity, collisions (Rapier Dynamic).
|
|
8
|
+
* `'kinematic'`: user-controlled position, velocity derived by engine
|
|
9
|
+
* (Rapier KinematicPositionBased).
|
|
10
|
+
*/
|
|
11
|
+
export type RigidBodyType = 'static' | 'dynamic' | 'kinematic';
|
|
12
|
+
/**
|
|
13
|
+
* Collider shape discriminant — 3 AI-friendly shape names.
|
|
14
|
+
*
|
|
15
|
+
* `'cuboid'`: box shape defined by half-extents (x, y, z).
|
|
16
|
+
* `'sphere'`: sphere defined by radius.
|
|
17
|
+
* `'capsule'`: capsule defined by half-height + radius.
|
|
18
|
+
*
|
|
19
|
+
* Named `'sphere'` not `'ball'` per plan-strategy D-5: AI users see
|
|
20
|
+
* the familiar geometric term; backend maps to Rapier `ColliderDesc.ball()`.
|
|
21
|
+
*/
|
|
22
|
+
export type ColliderShape = 'cuboid' | 'sphere' | 'capsule';
|
|
23
|
+
/** Numeric value for static rigid body (Rapier Fixed). */
|
|
24
|
+
export declare const RIGID_BODY_TYPE_STATIC = 0;
|
|
25
|
+
/** Numeric value for dynamic rigid body (Rapier Dynamic). */
|
|
26
|
+
export declare const RIGID_BODY_TYPE_DYNAMIC = 1;
|
|
27
|
+
/** Numeric value for kinematic rigid body (Rapier KinematicPositionBased). */
|
|
28
|
+
export declare const RIGID_BODY_TYPE_KINEMATIC = 2;
|
|
29
|
+
export declare const RigidBodyTypeValue: {
|
|
30
|
+
readonly static: 0;
|
|
31
|
+
readonly dynamic: 1;
|
|
32
|
+
readonly kinematic: 2;
|
|
33
|
+
};
|
|
34
|
+
export declare function rigidBodyTypeFromF32(n: number): RigidBodyType;
|
|
35
|
+
/** Numeric value for cuboid collider shape. */
|
|
36
|
+
export declare const COLLIDER_SHAPE_CUBOID = 0;
|
|
37
|
+
/** Numeric value for sphere collider shape. */
|
|
38
|
+
export declare const COLLIDER_SHAPE_SPHERE = 1;
|
|
39
|
+
/** Numeric value for capsule collider shape. */
|
|
40
|
+
export declare const COLLIDER_SHAPE_CAPSULE = 2;
|
|
41
|
+
export declare const ColliderShapeValue: {
|
|
42
|
+
readonly cuboid: 0;
|
|
43
|
+
readonly sphere: 1;
|
|
44
|
+
readonly capsule: 2;
|
|
45
|
+
};
|
|
46
|
+
export declare function colliderShapeFromF32(n: number): ColliderShape;
|
|
47
|
+
/**
|
|
48
|
+
* ECS Component: rigid body physics properties.
|
|
49
|
+
*
|
|
50
|
+
* AI user entry point — spawn with `world.spawn(RigidBody({ type: 'dynamic' }))`
|
|
51
|
+
* to opt an entity into physics simulation.
|
|
52
|
+
*
|
|
53
|
+
* | Field | Type | Default | Purpose |
|
|
54
|
+
* |:--|:--|:--|:--|
|
|
55
|
+
* | `type` | `RigidBodyType` | `'dynamic'` | Motion type discriminant |
|
|
56
|
+
* | `mass` | `number` | `1.0` | Linear mass (> 0 for dynamic) |
|
|
57
|
+
* | `linearDamping` | `number` | `0.0` | Velocity damping factor [0, 1] |
|
|
58
|
+
* | `angularDamping` | `number` | `0.0` | Angular velocity damping [0, 1] |
|
|
59
|
+
* | `gravityScale` | `number` | `1.0` | Per-body gravity multiplier |
|
|
60
|
+
* | `ccdEnabled` | `boolean` | `false` | Continuous collision detection |
|
|
61
|
+
*
|
|
62
|
+
* `type` declares `labels: RigidBodyTypeValue` so `describeComponent` projects
|
|
63
|
+
* the `static=0 / dynamic=1 / kinematic=2` map through the front door — an AI
|
|
64
|
+
* learns the legal variants from the schema, not from engine source.
|
|
65
|
+
*/
|
|
66
|
+
export declare const RigidBody: Component<"RigidBody", {
|
|
67
|
+
readonly type: "enum";
|
|
68
|
+
readonly mass: "f32";
|
|
69
|
+
readonly linearDamping: "f32";
|
|
70
|
+
readonly angularDamping: "f32";
|
|
71
|
+
readonly gravityScale: "f32";
|
|
72
|
+
readonly ccdEnabled: "bool";
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* ECS Component: collision geometry.
|
|
76
|
+
*
|
|
77
|
+
* Spawn alongside RigidBody to give an entity a collision shape. Entities
|
|
78
|
+
* with Collider but no RigidBody are treated as static colliders (Rapier
|
|
79
|
+
* native behavior — collider without parent body is fixed): `physicsSyncBackend`
|
|
80
|
+
* synthesizes an implicit static body for them, so a bare-Collider floor/wall is
|
|
81
|
+
* simulated as immovable level geometry — the natural way to author static
|
|
82
|
+
* scenery without a redundant `RigidBody{static}`.
|
|
83
|
+
*
|
|
84
|
+
* | Field | Type | Default | Purpose |
|
|
85
|
+
* |:--|:--|:--|:--|
|
|
86
|
+
* | `shape` | `ColliderShape` | — | Shape discriminant |
|
|
87
|
+
* | `halfExtents` | `[number, number, number]` | `[0.5, 0.5, 0.5]` | Cuboid half-width/height/depth |
|
|
88
|
+
* | `radius` | `number` | `0.5` | Sphere / capsule radius |
|
|
89
|
+
* | `halfHeight` | `number` | `0.5` | Capsule half-height |
|
|
90
|
+
* | `friction` | `number` | `0.5` | Coulomb friction coefficient |
|
|
91
|
+
* | `restitution` | `number` | `0.0` | Elasticity (1.0 = perfect bounce) |
|
|
92
|
+
* | `density` | `number` | `1.0` | Mass density (alternative to mass) |
|
|
93
|
+
* | `isSensor` | `bool` | `false` | Sensor mode (detect, no physical response) |
|
|
94
|
+
* | `collisionGroups` | `u32` | `0x0001_FFFF` | 32-bit packed membership/filter |
|
|
95
|
+
* | `solverGroups` | `u32` | `0xFFFF_FFFF` | 32-bit packed constraint groups |
|
|
96
|
+
*/
|
|
97
|
+
export declare const Collider: Component<"Collider", {
|
|
98
|
+
readonly shape: "enum";
|
|
99
|
+
readonly halfExtents: "array<f32, 3>";
|
|
100
|
+
readonly radius: "f32";
|
|
101
|
+
readonly halfHeight: "f32";
|
|
102
|
+
readonly friction: "f32";
|
|
103
|
+
readonly restitution: "f32";
|
|
104
|
+
readonly density: "f32";
|
|
105
|
+
readonly isSensor: "bool";
|
|
106
|
+
readonly collisionGroups: "u32";
|
|
107
|
+
readonly solverGroups: "u32";
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* ECS Component: kinematic character controller tuning + output state.
|
|
111
|
+
*
|
|
112
|
+
* Spawn alongside a `RigidBody({ type: 'kinematic' })` + `Collider` to opt an
|
|
113
|
+
* entity into collision-aware movement via `PhysicsWorld.moveAndSlide`. The
|
|
114
|
+
* tuning fields are stable character properties; `grounded` is written back by
|
|
115
|
+
* the engine after each `moveAndSlide` (game code reads it, never writes it).
|
|
116
|
+
*
|
|
117
|
+
* All fields are flat scalars in engine units (degrees, world-space distance) —
|
|
118
|
+
* no Rapier types leak through. Slope angles use the `Deg` suffix to make the
|
|
119
|
+
* unit explicit; the backend translates degrees to radians. A single field
|
|
120
|
+
* carries both the on/off switch and the value: `autoStepMaxHeight === 0`
|
|
121
|
+
* disables auto-step, `snapToGroundDist === 0` disables snap-to-ground.
|
|
122
|
+
*
|
|
123
|
+
* 2D and 3D reuse this same component (plan-strategy D-9): every field is a
|
|
124
|
+
* dimension-agnostic scalar, so no separate 2D component is needed.
|
|
125
|
+
*
|
|
126
|
+
* | Field | Type | Default | Purpose |
|
|
127
|
+
* |:--|:--|:--|:--|
|
|
128
|
+
* | `offset` | `f32` | `0.01` | Skin thickness, prevents penetration |
|
|
129
|
+
* | `maxSlopeClimbDeg` | `f32` | `45` | Max climbable slope angle (degrees) |
|
|
130
|
+
* | `minSlopeSlideDeg` | `f32` | `30` | Slope angle past which sliding starts (degrees) |
|
|
131
|
+
* | `autoStepMaxHeight` | `f32` | `0.3` | Max auto-step height (0 = off) |
|
|
132
|
+
* | `autoStepMinWidth` | `f32` | `0.2` | Min step width to be steppable |
|
|
133
|
+
* | `snapToGroundDist` | `f32` | `0.2` | Downhill ground-snap distance (0 = off) |
|
|
134
|
+
* | `grounded` | `bool` | `false` | Engine-written: grounded after last move |
|
|
135
|
+
*/
|
|
136
|
+
export declare const CharacterController: Component<"CharacterController", {
|
|
137
|
+
readonly offset: "f32";
|
|
138
|
+
readonly maxSlopeClimbDeg: "f32";
|
|
139
|
+
readonly minSlopeSlideDeg: "f32";
|
|
140
|
+
readonly autoStepMaxHeight: "f32";
|
|
141
|
+
readonly autoStepMinWidth: "f32";
|
|
142
|
+
readonly snapToGroundDist: "f32";
|
|
143
|
+
readonly grounded: "bool";
|
|
144
|
+
}>;
|
|
145
|
+
/**
|
|
146
|
+
* ECS Component: set of entities currently colliding with the holder entity.
|
|
147
|
+
*
|
|
148
|
+
* Maintained by the physics tick systems — entities are added on collision
|
|
149
|
+
* start (`CollisionEvent.started`) and removed on collision stop
|
|
150
|
+
* (`CollisionEvent.stopped`). AI users query this component to know whose
|
|
151
|
+
* colliders overlap right now without consuming per-frame events.
|
|
152
|
+
*
|
|
153
|
+
* This is the `'continued'` equivalent — no repeated per-frame events,
|
|
154
|
+
* one component query per frame exposes the full active contact set
|
|
155
|
+
* (plan-strategy D-3: CollidingEntities set-query mode).
|
|
156
|
+
*/
|
|
157
|
+
export declare const CollidingEntities: Component<"CollidingEntities", {
|
|
158
|
+
readonly entities: "array<entity>";
|
|
159
|
+
}>;
|
|
160
|
+
/** Install the physics component vocabulary in a World and release its leases on teardown. */
|
|
161
|
+
export declare function registerPhysicsComponents(world: World): () => void;
|
|
162
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,SAAS,EAAmB,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAElF;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAe5D,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAEX,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,CAI7D;AAED,+CAA+C;AAC/C,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,+CAA+C;AAC/C,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,gDAAgD;AAChD,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAEX,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,CAI7D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,SAAS;;;;;;;EAOpB,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;EAgBnB,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;EAQ9B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB;;EAM7B,CAAC;AASF,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,IAAI,CAOlE"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAQA,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { CollisionEventPayload } from './collision-event';
|
|
2
|
+
export { CollisionEvent } from './collision-event';
|
|
3
|
+
export type { ColliderShape, RigidBodyType } from './components';
|
|
4
|
+
export { CharacterController, COLLIDER_SHAPE_CAPSULE, COLLIDER_SHAPE_CUBOID, COLLIDER_SHAPE_SPHERE, Collider, ColliderShapeValue, CollidingEntities, colliderShapeFromF32, RIGID_BODY_TYPE_DYNAMIC, RIGID_BODY_TYPE_KINEMATIC, RIGID_BODY_TYPE_STATIC, RigidBody, RigidBodyTypeValue, registerPhysicsComponents, rigidBodyTypeFromF32, } from './components';
|
|
5
|
+
export type { PhysicsErrorCode, PhysicsErrorDetail } from './errors';
|
|
6
|
+
export { PHYSICS_ERROR_HINTS, PhysicsError } from './errors';
|
|
7
|
+
export type { PhysicsWorld, PhysicsWorld2D, RaycastHit, RaycastHit2D } from './physics-world';
|
|
8
|
+
export type { PhysicsBackend } from './plugin-factory';
|
|
9
|
+
export { physicsPlugin } from './plugin-factory';
|
|
10
|
+
export { PhysicsSet } from './system-set';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,QAAQ,EACR,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,sBAAsB,EACtB,SAAS,EACT,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC9F,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { defineComponent, defineSystemSet } from '@forgeax/engine-ecs';
|
|
2
|
+
export { PHYSICS_ERROR_HINTS, PhysicsError } from '@forgeax/engine-types';
|
|
3
|
+
|
|
4
|
+
// src/collision-event.ts
|
|
5
|
+
var CollisionEvent = "__CollisionEvent__";
|
|
6
|
+
var RIGID_BODY_TYPE_STATIC = 0;
|
|
7
|
+
var RIGID_BODY_TYPE_DYNAMIC = 1;
|
|
8
|
+
var RIGID_BODY_TYPE_KINEMATIC = 2;
|
|
9
|
+
var RigidBodyTypeValue = {
|
|
10
|
+
static: RIGID_BODY_TYPE_STATIC,
|
|
11
|
+
dynamic: RIGID_BODY_TYPE_DYNAMIC,
|
|
12
|
+
kinematic: RIGID_BODY_TYPE_KINEMATIC
|
|
13
|
+
};
|
|
14
|
+
function rigidBodyTypeFromF32(n) {
|
|
15
|
+
if (n === RIGID_BODY_TYPE_DYNAMIC) return "dynamic";
|
|
16
|
+
if (n === RIGID_BODY_TYPE_KINEMATIC) return "kinematic";
|
|
17
|
+
return "static";
|
|
18
|
+
}
|
|
19
|
+
var COLLIDER_SHAPE_CUBOID = 0;
|
|
20
|
+
var COLLIDER_SHAPE_SPHERE = 1;
|
|
21
|
+
var COLLIDER_SHAPE_CAPSULE = 2;
|
|
22
|
+
var ColliderShapeValue = {
|
|
23
|
+
cuboid: COLLIDER_SHAPE_CUBOID,
|
|
24
|
+
sphere: COLLIDER_SHAPE_SPHERE,
|
|
25
|
+
capsule: COLLIDER_SHAPE_CAPSULE
|
|
26
|
+
};
|
|
27
|
+
function colliderShapeFromF32(n) {
|
|
28
|
+
if (n === COLLIDER_SHAPE_SPHERE) return "sphere";
|
|
29
|
+
if (n === COLLIDER_SHAPE_CAPSULE) return "capsule";
|
|
30
|
+
return "cuboid";
|
|
31
|
+
}
|
|
32
|
+
var RigidBody = defineComponent("RigidBody", {
|
|
33
|
+
type: { type: "enum", default: RIGID_BODY_TYPE_DYNAMIC, labels: RigidBodyTypeValue },
|
|
34
|
+
mass: { type: "f32", default: 1 },
|
|
35
|
+
linearDamping: { type: "f32", default: 0 },
|
|
36
|
+
angularDamping: { type: "f32", default: 0 },
|
|
37
|
+
gravityScale: { type: "f32", default: 1 },
|
|
38
|
+
ccdEnabled: { type: "bool", default: false }
|
|
39
|
+
});
|
|
40
|
+
var Collider = defineComponent("Collider", {
|
|
41
|
+
shape: { type: "enum", default: COLLIDER_SHAPE_CUBOID, labels: ColliderShapeValue },
|
|
42
|
+
// feat-20260709 M4: cuboid half-extents collapsed from 3 per-axis scalar
|
|
43
|
+
// columns into one inline array<f32,3> column. Explicit layer-2 default
|
|
44
|
+
// (the array layer-3 fallback is all-zero, which would give a degenerate
|
|
45
|
+
// zero-size box). radius/halfHeight stay scalar (OOS-1: independent
|
|
46
|
+
// sphere/capsule params, not part of the cuboid vec).
|
|
47
|
+
halfExtents: { type: "array<f32, 3>", default: new Float32Array([0.5, 0.5, 0.5]) },
|
|
48
|
+
radius: { type: "f32", default: 0.5 },
|
|
49
|
+
halfHeight: { type: "f32", default: 0.5 },
|
|
50
|
+
friction: { type: "f32", default: 0.5 },
|
|
51
|
+
restitution: { type: "f32", default: 0 },
|
|
52
|
+
density: { type: "f32", default: 1 },
|
|
53
|
+
isSensor: { type: "bool", default: false },
|
|
54
|
+
collisionGroups: { type: "u32", default: 131071 },
|
|
55
|
+
solverGroups: { type: "u32", default: 4294967295 }
|
|
56
|
+
});
|
|
57
|
+
var CharacterController = defineComponent("CharacterController", {
|
|
58
|
+
offset: { type: "f32", default: 0.01 },
|
|
59
|
+
maxSlopeClimbDeg: { type: "f32", default: 45 },
|
|
60
|
+
minSlopeSlideDeg: { type: "f32", default: 30 },
|
|
61
|
+
autoStepMaxHeight: { type: "f32", default: 0.3 },
|
|
62
|
+
autoStepMinWidth: { type: "f32", default: 0.2 },
|
|
63
|
+
snapToGroundDist: { type: "f32", default: 0.2 },
|
|
64
|
+
grounded: { type: "bool", default: false, transient: true }
|
|
65
|
+
});
|
|
66
|
+
var CollidingEntities = defineComponent(
|
|
67
|
+
"CollidingEntities",
|
|
68
|
+
{
|
|
69
|
+
entities: { type: "array<entity>" }
|
|
70
|
+
},
|
|
71
|
+
{ transient: true }
|
|
72
|
+
);
|
|
73
|
+
var PHYSICS_COMPONENTS = [
|
|
74
|
+
CharacterController,
|
|
75
|
+
Collider,
|
|
76
|
+
CollidingEntities,
|
|
77
|
+
RigidBody
|
|
78
|
+
];
|
|
79
|
+
function registerPhysicsComponents(world) {
|
|
80
|
+
const leases = PHYSICS_COMPONENTS.map(
|
|
81
|
+
(component) => world.components.register(component).unwrap()
|
|
82
|
+
);
|
|
83
|
+
return () => {
|
|
84
|
+
for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/load-rapier-backend.mjs
|
|
89
|
+
function loadRapier3DBackend() {
|
|
90
|
+
return import('@forgeax/engine-physics-rapier3d');
|
|
91
|
+
}
|
|
92
|
+
function loadRapier2DBackend() {
|
|
93
|
+
return import('@forgeax/engine-physics-rapier2d');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/plugin-factory.ts
|
|
97
|
+
function physicsPlugin(backend) {
|
|
98
|
+
return {
|
|
99
|
+
name: "physics",
|
|
100
|
+
inject: ["world"],
|
|
101
|
+
provide: "physics",
|
|
102
|
+
async apply(ctx) {
|
|
103
|
+
const world = ctx.world;
|
|
104
|
+
let physics;
|
|
105
|
+
let registerSystems;
|
|
106
|
+
if (backend === "rapier-3d") {
|
|
107
|
+
const { loadRapier3D, createRapier3DPhysicsWorld, registerPhysicsSystems } = await loadRapier3DBackend();
|
|
108
|
+
const rapier = await loadRapier3D();
|
|
109
|
+
physics = createRapier3DPhysicsWorld(rapier);
|
|
110
|
+
registerSystems = () => registerPhysicsSystems(world);
|
|
111
|
+
} else {
|
|
112
|
+
const { loadRapier2D, createRapier2DPhysicsWorld, registerPhysicsSystems2D } = await loadRapier2DBackend();
|
|
113
|
+
const rapier = await loadRapier2D();
|
|
114
|
+
physics = createRapier2DPhysicsWorld(rapier);
|
|
115
|
+
registerSystems = () => registerPhysicsSystems2D(world);
|
|
116
|
+
}
|
|
117
|
+
ctx.effect(() => registerPhysicsComponents(world), "physics/components");
|
|
118
|
+
ctx.effect(() => {
|
|
119
|
+
world.insertResource("PhysicsWorld", physics);
|
|
120
|
+
return () => {
|
|
121
|
+
world.removeResource("PhysicsWorld");
|
|
122
|
+
physics.dispose();
|
|
123
|
+
};
|
|
124
|
+
}, "physics/resource");
|
|
125
|
+
ctx.effect(() => {
|
|
126
|
+
const unregister = registerSystems();
|
|
127
|
+
return () => unregister();
|
|
128
|
+
}, "physics/systems");
|
|
129
|
+
ctx.provide("physics", physics);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
var PhysicsSet = defineSystemSet({ name: "physics" });
|
|
134
|
+
|
|
135
|
+
export { COLLIDER_SHAPE_CAPSULE, COLLIDER_SHAPE_CUBOID, COLLIDER_SHAPE_SPHERE, CharacterController, Collider, ColliderShapeValue, CollidingEntities, CollisionEvent, PhysicsSet, RIGID_BODY_TYPE_DYNAMIC, RIGID_BODY_TYPE_KINEMATIC, RIGID_BODY_TYPE_STATIC, RigidBody, RigidBodyTypeValue, colliderShapeFromF32, physicsPlugin, registerPhysicsComponents, rigidBodyTypeFromF32 };
|
|
136
|
+
//# sourceMappingURL=index.mjs.map
|
|
137
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/collision-event.ts","../src/components.ts","../src/load-rapier-backend.mjs","../src/plugin-factory.ts","../src/system-set.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @forgeax/engine-physics — CollisionEvent ECS Event token placeholder.\n//\n// Emitted by physics tick systems during the Writeback phase.\n// Two states only: 'started' (new contact) and 'stopped' (separated).\n// No 'continued' event — use CollidingEntities component for ongoing contacts\n// (plan-strategy D-3).\n\nimport type { Vec3 } from '@forgeax/engine-math';\n\n/**\n * Collision event payload — per-contact-pair event emitted during Writeback.\n *\n * `type: 'started'` — two colliders just began touching.\n * `type: 'stopped'` — two colliders just separated.\n */\nexport interface CollisionEventPayload {\n type: 'started' | 'stopped';\n entityA: number;\n entityB: number;\n contactPoint: Vec3;\n contactNormal: Vec3;\n}\n\n/**\n * CollisionEvent constant — identifies the collision event type.\n * Backend systems push CollisionEventPayload instances into the event queue\n * during the Writeback phase; user systems drain via query.\n *\n * The backing ECS event infrastructure (Event<T> generic + world.drainEvent)\n * is deferred to a future feat. For M1, this is a type-only contract.\n */\nexport const CollisionEvent = '__CollisionEvent__' as const;\n\n/** Type-level identifier for the CollisionEvent event channel. */\nexport type CollisionEvent = typeof CollisionEvent;\n","// @forgeax/engine-physics — ECS Component schemas.\n//\n// RigidBody and Collider are the two user-facing entry points; AI users\n// spawn entities with these components to opt into physics simulation.\n// CollidingEntities is the runtime set-query component for continuous\n// collision status (started/stopped model, no 'continued' event).\n\nimport { type Component, defineComponent, type World } from '@forgeax/engine-ecs';\n\n/**\n * RigidBody motion type — 3-state discriminant mirroring Rapier's\n * Dynamic / Fixed / KinematicPositionBased triplet.\n *\n * `'static'`: infinite mass, never moves (Rapier Fixed).\n * `'dynamic'`: driven by forces, gravity, collisions (Rapier Dynamic).\n * `'kinematic'`: user-controlled position, velocity derived by engine\n * (Rapier KinematicPositionBased).\n */\nexport type RigidBodyType = 'static' | 'dynamic' | 'kinematic';\n\n/**\n * Collider shape discriminant — 3 AI-friendly shape names.\n *\n * `'cuboid'`: box shape defined by half-extents (x, y, z).\n * `'sphere'`: sphere defined by radius.\n * `'capsule'`: capsule defined by half-height + radius.\n *\n * Named `'sphere'` not `'ball'` per plan-strategy D-5: AI users see\n * the familiar geometric term; backend maps to Rapier `ColliderDesc.ball()`.\n */\nexport type ColliderShape = 'cuboid' | 'sphere' | 'capsule';\n\n// ─── D-3: numeric enum constants + narrowing helpers ─────────────────────\n//\n// Aligned with `packages/runtime/src/components/camera.ts:41-53`\n// `cameraProjectionFromF32` pattern. The ECS `enum` field maps to `number`\n// (Uint32Array column); these constants let AI users write\n// `{ type: RigidBodyTypeValue.dynamic }` instead of bare magic numbers,\n// and the narrowing helpers let backends switch cleanly on the string union.\n//\n// Declared BEFORE the RigidBody / Collider components so each component's enum\n// field descriptor can reference the SAME `*Value` map as its `labels`\n// (Derive, don't Duplicate — one object is both the AI-facing const AND the\n// schema-projected label map that `describeComponent` surfaces).\n\n/** Numeric value for static rigid body (Rapier Fixed). */\nexport const RIGID_BODY_TYPE_STATIC = 0;\n/** Numeric value for dynamic rigid body (Rapier Dynamic). */\nexport const RIGID_BODY_TYPE_DYNAMIC = 1;\n/** Numeric value for kinematic rigid body (Rapier KinematicPositionBased). */\nexport const RIGID_BODY_TYPE_KINEMATIC = 2;\n\nexport const RigidBodyTypeValue = {\n static: RIGID_BODY_TYPE_STATIC,\n dynamic: RIGID_BODY_TYPE_DYNAMIC,\n kinematic: RIGID_BODY_TYPE_KINEMATIC,\n} as const;\n\nexport function rigidBodyTypeFromF32(n: number): RigidBodyType {\n if (n === RIGID_BODY_TYPE_DYNAMIC) return 'dynamic';\n if (n === RIGID_BODY_TYPE_KINEMATIC) return 'kinematic';\n return 'static';\n}\n\n/** Numeric value for cuboid collider shape. */\nexport const COLLIDER_SHAPE_CUBOID = 0;\n/** Numeric value for sphere collider shape. */\nexport const COLLIDER_SHAPE_SPHERE = 1;\n/** Numeric value for capsule collider shape. */\nexport const COLLIDER_SHAPE_CAPSULE = 2;\n\nexport const ColliderShapeValue = {\n cuboid: COLLIDER_SHAPE_CUBOID,\n sphere: COLLIDER_SHAPE_SPHERE,\n capsule: COLLIDER_SHAPE_CAPSULE,\n} as const;\n\nexport function colliderShapeFromF32(n: number): ColliderShape {\n if (n === COLLIDER_SHAPE_SPHERE) return 'sphere';\n if (n === COLLIDER_SHAPE_CAPSULE) return 'capsule';\n return 'cuboid';\n}\n\n/**\n * ECS Component: rigid body physics properties.\n *\n * AI user entry point — spawn with `world.spawn(RigidBody({ type: 'dynamic' }))`\n * to opt an entity into physics simulation.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `type` | `RigidBodyType` | `'dynamic'` | Motion type discriminant |\n * | `mass` | `number` | `1.0` | Linear mass (> 0 for dynamic) |\n * | `linearDamping` | `number` | `0.0` | Velocity damping factor [0, 1] |\n * | `angularDamping` | `number` | `0.0` | Angular velocity damping [0, 1] |\n * | `gravityScale` | `number` | `1.0` | Per-body gravity multiplier |\n * | `ccdEnabled` | `boolean` | `false` | Continuous collision detection |\n *\n * `type` declares `labels: RigidBodyTypeValue` so `describeComponent` projects\n * the `static=0 / dynamic=1 / kinematic=2` map through the front door — an AI\n * learns the legal variants from the schema, not from engine source.\n */\nexport const RigidBody = defineComponent('RigidBody', {\n type: { type: 'enum', default: RIGID_BODY_TYPE_DYNAMIC, labels: RigidBodyTypeValue },\n mass: { type: 'f32', default: 1 },\n linearDamping: { type: 'f32', default: 0 },\n angularDamping: { type: 'f32', default: 0 },\n gravityScale: { type: 'f32', default: 1 },\n ccdEnabled: { type: 'bool', default: false },\n});\n\n/**\n * ECS Component: collision geometry.\n *\n * Spawn alongside RigidBody to give an entity a collision shape. Entities\n * with Collider but no RigidBody are treated as static colliders (Rapier\n * native behavior — collider without parent body is fixed): `physicsSyncBackend`\n * synthesizes an implicit static body for them, so a bare-Collider floor/wall is\n * simulated as immovable level geometry — the natural way to author static\n * scenery without a redundant `RigidBody{static}`.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `shape` | `ColliderShape` | — | Shape discriminant |\n * | `halfExtents` | `[number, number, number]` | `[0.5, 0.5, 0.5]` | Cuboid half-width/height/depth |\n * | `radius` | `number` | `0.5` | Sphere / capsule radius |\n * | `halfHeight` | `number` | `0.5` | Capsule half-height |\n * | `friction` | `number` | `0.5` | Coulomb friction coefficient |\n * | `restitution` | `number` | `0.0` | Elasticity (1.0 = perfect bounce) |\n * | `density` | `number` | `1.0` | Mass density (alternative to mass) |\n * | `isSensor` | `bool` | `false` | Sensor mode (detect, no physical response) |\n * | `collisionGroups` | `u32` | `0x0001_FFFF` | 32-bit packed membership/filter |\n * | `solverGroups` | `u32` | `0xFFFF_FFFF` | 32-bit packed constraint groups |\n */\nexport const Collider = defineComponent('Collider', {\n shape: { type: 'enum', default: COLLIDER_SHAPE_CUBOID, labels: ColliderShapeValue },\n // feat-20260709 M4: cuboid half-extents collapsed from 3 per-axis scalar\n // columns into one inline array<f32,3> column. Explicit layer-2 default\n // (the array layer-3 fallback is all-zero, which would give a degenerate\n // zero-size box). radius/halfHeight stay scalar (OOS-1: independent\n // sphere/capsule params, not part of the cuboid vec).\n halfExtents: { type: 'array<f32, 3>', default: new Float32Array([0.5, 0.5, 0.5]) },\n radius: { type: 'f32', default: 0.5 },\n halfHeight: { type: 'f32', default: 0.5 },\n friction: { type: 'f32', default: 0.5 },\n restitution: { type: 'f32', default: 0 },\n density: { type: 'f32', default: 1 },\n isSensor: { type: 'bool', default: false },\n collisionGroups: { type: 'u32', default: 0x0001_ffff },\n solverGroups: { type: 'u32', default: 0xffff_ffff },\n});\n\n/**\n * ECS Component: kinematic character controller tuning + output state.\n *\n * Spawn alongside a `RigidBody({ type: 'kinematic' })` + `Collider` to opt an\n * entity into collision-aware movement via `PhysicsWorld.moveAndSlide`. The\n * tuning fields are stable character properties; `grounded` is written back by\n * the engine after each `moveAndSlide` (game code reads it, never writes it).\n *\n * All fields are flat scalars in engine units (degrees, world-space distance) —\n * no Rapier types leak through. Slope angles use the `Deg` suffix to make the\n * unit explicit; the backend translates degrees to radians. A single field\n * carries both the on/off switch and the value: `autoStepMaxHeight === 0`\n * disables auto-step, `snapToGroundDist === 0` disables snap-to-ground.\n *\n * 2D and 3D reuse this same component (plan-strategy D-9): every field is a\n * dimension-agnostic scalar, so no separate 2D component is needed.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `offset` | `f32` | `0.01` | Skin thickness, prevents penetration |\n * | `maxSlopeClimbDeg` | `f32` | `45` | Max climbable slope angle (degrees) |\n * | `minSlopeSlideDeg` | `f32` | `30` | Slope angle past which sliding starts (degrees) |\n * | `autoStepMaxHeight` | `f32` | `0.3` | Max auto-step height (0 = off) |\n * | `autoStepMinWidth` | `f32` | `0.2` | Min step width to be steppable |\n * | `snapToGroundDist` | `f32` | `0.2` | Downhill ground-snap distance (0 = off) |\n * | `grounded` | `bool` | `false` | Engine-written: grounded after last move |\n */\nexport const CharacterController = defineComponent('CharacterController', {\n offset: { type: 'f32', default: 0.01 },\n maxSlopeClimbDeg: { type: 'f32', default: 45 },\n minSlopeSlideDeg: { type: 'f32', default: 30 },\n autoStepMaxHeight: { type: 'f32', default: 0.3 },\n autoStepMinWidth: { type: 'f32', default: 0.2 },\n snapToGroundDist: { type: 'f32', default: 0.2 },\n grounded: { type: 'bool', default: false, transient: true },\n});\n\n/**\n * ECS Component: set of entities currently colliding with the holder entity.\n *\n * Maintained by the physics tick systems — entities are added on collision\n * start (`CollisionEvent.started`) and removed on collision stop\n * (`CollisionEvent.stopped`). AI users query this component to know whose\n * colliders overlap right now without consuming per-frame events.\n *\n * This is the `'continued'` equivalent — no repeated per-frame events,\n * one component query per frame exposes the full active contact set\n * (plan-strategy D-3: CollidingEntities set-query mode).\n */\nexport const CollidingEntities = defineComponent(\n 'CollidingEntities',\n {\n entities: { type: 'array<entity>' },\n },\n { transient: true },\n);\n\nconst PHYSICS_COMPONENTS: readonly Component[] = [\n CharacterController,\n Collider,\n CollidingEntities,\n RigidBody,\n];\n\n/** Install the physics component vocabulary in a World and release its leases on teardown. */\nexport function registerPhysicsComponents(world: World): () => void {\n const leases = PHYSICS_COMPONENTS.map((component) =>\n world.components.register(component).unwrap(),\n );\n return () => {\n for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();\n };\n}\n","// Keep literal specifiers visible to bundlers without pulling backend declarations into the physics TypeScript graph.\nexport function loadRapier3DBackend() {\n return import('@forgeax/engine-physics-rapier3d');\n}\n\nexport function loadRapier2DBackend() {\n return import('@forgeax/engine-physics-rapier2d');\n}\n","// @forgeax/engine-physics -- physicsPlugin(backend) factory (M2 / w10, plan-strategy D-5 / D-7).\n//\n// physicsPlugin lives in @forgeax/engine-physics (the interface package, C-9)\n// and accepts an interface->backend dependency inversion: its async apply\n// dynamic-imports the rapier 2D / 3D backend on demand. The backends are\n// declared as devDependencies in this package's package.json (a regular\n// dependency would form a physics <-> rapier cycle since the backends depend on\n// the interface package); the consuming app declares the real runtime dep.\n//\n// charter awareness:\n// P3 explicit failure: WASM load failure rejects plugin activation and the\n// App boundary preserves the cause; it is never a silent skip.\n// P4 consistent abstraction: physicsPlugin shares the same Plugin shape as\n// transform / audio -- one mental model covers every wiring.\n\nimport type { Plugin } from '@forgeax/engine-plugin';\nimport { registerPhysicsComponents } from './components';\nimport { loadRapier2DBackend, loadRapier3DBackend } from './load-rapier-backend.mjs';\nimport type { PhysicsWorld, PhysicsWorld2D } from './physics-world';\n\ninterface Rapier3DBackendModule {\n loadRapier3D(): Promise<unknown>;\n createRapier3DPhysicsWorld(rapier: unknown): PhysicsWorld;\n registerPhysicsSystems(world: import('@forgeax/engine-ecs').World): () => void;\n}\n\ninterface Rapier2DBackendModule {\n loadRapier2D(): Promise<unknown>;\n createRapier2DPhysicsWorld(rapier: unknown): PhysicsWorld2D;\n registerPhysicsSystems2D(world: import('@forgeax/engine-ecs').World): () => void;\n}\n\n/** Rapier backend selector. */\nexport type PhysicsBackend = 'rapier-2d' | 'rapier-3d';\n\ndeclare module '@forgeax/engine-plugin' {\n interface EngineContextServices {\n physics?: PhysicsWorld | PhysicsWorld2D;\n }\n}\n\n/**\n * physicsPlugin(backend) dynamically imports the Rapier backend,\n * loads the WASM module, creates the PhysicsWorld, inserts it as the\n * 'PhysicsWorld' world resource, and registers the three-phase tick systems.\n *\n * The resource is inserted before registering systems so moveAndSlide resolves\n * `PhysicsWorld` on the first tick. Cordis owns rollback if any later effect\n * fails.\n *\n * @param backend 'rapier-2d' or 'rapier-3d'\n */\nexport function physicsPlugin(backend: PhysicsBackend): Plugin {\n return {\n name: 'physics',\n inject: ['world'],\n provide: 'physics',\n async apply(ctx) {\n const world = ctx.world;\n let physics: PhysicsWorld | PhysicsWorld2D;\n let registerSystems: () => () => void;\n if (backend === 'rapier-3d') {\n const { loadRapier3D, createRapier3DPhysicsWorld, registerPhysicsSystems } =\n (await loadRapier3DBackend()) as Rapier3DBackendModule;\n const rapier = await loadRapier3D();\n physics = createRapier3DPhysicsWorld(rapier);\n registerSystems = () => registerPhysicsSystems(world);\n } else {\n const { loadRapier2D, createRapier2DPhysicsWorld, registerPhysicsSystems2D } =\n (await loadRapier2DBackend()) as Rapier2DBackendModule;\n const rapier = await loadRapier2D();\n physics = createRapier2DPhysicsWorld(rapier);\n registerSystems = () => registerPhysicsSystems2D(world);\n }\n ctx.effect(() => registerPhysicsComponents(world), 'physics/components');\n ctx.effect(() => {\n world.insertResource('PhysicsWorld', physics);\n return () => {\n world.removeResource('PhysicsWorld');\n physics.dispose();\n };\n }, 'physics/resource');\n ctx.effect(() => {\n const unregister = registerSystems();\n return () => unregister();\n }, 'physics/systems');\n ctx.provide('physics', physics);\n },\n };\n}\n","import { defineSystemSet } from '@forgeax/engine-ecs';\n\nexport const PhysicsSet = defineSystemSet({ name: 'physics' });\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Vec2, Vec3 } from '@forgeax/engine-math';
|
|
2
|
+
/**
|
|
3
|
+
* Raycast hit result — returned by `PhysicsWorld.raycast()`.
|
|
4
|
+
*
|
|
5
|
+
* `entity`: the entity whose collider was hit.
|
|
6
|
+
* `point`: world-space hit point.
|
|
7
|
+
* `normal`: world-space surface normal at hit point.
|
|
8
|
+
* `timeOfImpact`: ray parameter t (origin + direction * toi = hit point).
|
|
9
|
+
*/
|
|
10
|
+
export interface RaycastHit {
|
|
11
|
+
entity: number;
|
|
12
|
+
point: Vec3;
|
|
13
|
+
normal: Vec3;
|
|
14
|
+
timeOfImpact: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* PhysicsWorld Resource interface — the engine-side API surface for physics
|
|
18
|
+
* operations. Backend implementations (RapierPhysicsWorld3D/2D) satisfy this
|
|
19
|
+
* contract.
|
|
20
|
+
*
|
|
21
|
+
* Inserted as `'PhysicsWorld'` resource by `createApp` when `opts.physics`
|
|
22
|
+
* is set. AI users retrieve via `world.getResource<PhysicsWorld>('PhysicsWorld')`.
|
|
23
|
+
*
|
|
24
|
+
* All mutation methods are synchronous; physics step is driven by the tick
|
|
25
|
+
* systems (syncBackend → stepSimulation → writeback), not by user calls.
|
|
26
|
+
*/
|
|
27
|
+
export interface PhysicsWorld {
|
|
28
|
+
/** Release backend-native resources. Called by the owning physics plugin. */
|
|
29
|
+
dispose(): void;
|
|
30
|
+
/** Set world gravity vector. */
|
|
31
|
+
setGravity(gravity: Vec3): void;
|
|
32
|
+
/** Get current world gravity vector. */
|
|
33
|
+
getGravity(): Vec3;
|
|
34
|
+
/**
|
|
35
|
+
* Cast a ray into the physics world and return the first hit.
|
|
36
|
+
*
|
|
37
|
+
* @param origin - world-space ray origin.
|
|
38
|
+
* @param direction - normalized world-space ray direction.
|
|
39
|
+
* @param maxDist - maximum ray distance (0 = infinite).
|
|
40
|
+
* @param filterMask - 32-bit packed collision filter mask (optional).
|
|
41
|
+
* @returns RaycastHit on hit, undefined on miss.
|
|
42
|
+
*/
|
|
43
|
+
raycast(origin: Vec3, direction: Vec3, maxDist: number, filterMask?: number): RaycastHit | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Teleport a dynamic body to a position instantly, zeroing velocity.
|
|
46
|
+
*
|
|
47
|
+
* Use for spawning entities at specific locations or resetting after
|
|
48
|
+
* out-of-bounds. Does not accumulate velocity from the displacement
|
|
49
|
+
* (unlike `world.set(entity, Transform, { translation: ... })` on
|
|
50
|
+
* dynamic bodies, which would cause a velocity spike).
|
|
51
|
+
*
|
|
52
|
+
* @param entity - the entity (must have RigidBody + Collider).
|
|
53
|
+
* @param position - new world-space position.
|
|
54
|
+
*/
|
|
55
|
+
teleport(entity: number, position: Vec3): void;
|
|
56
|
+
/**
|
|
57
|
+
* Move a kinematic character with collision response, slope handling,
|
|
58
|
+
* auto-step, and ground-snap, then write the resolved position back to the
|
|
59
|
+
* entity's `Transform` and `CharacterController.grounded`.
|
|
60
|
+
*
|
|
61
|
+
* This is the engine's unopinionated movement primitive (modeled on Unity
|
|
62
|
+
* `CharacterController.Move`): the game layer computes `desiredDelta` from
|
|
63
|
+
* input + gravity + jump, and `moveAndSlide` resolves it against the world
|
|
64
|
+
* geometry. The entity must carry a `RigidBody({ type: 'kinematic' })`, a
|
|
65
|
+
* `Collider`, and a `CharacterController` component.
|
|
66
|
+
*
|
|
67
|
+
* Tuning (offset / slope / auto-step / ground-snap) is read from the
|
|
68
|
+
* `CharacterController` component each call; there is no per-call options
|
|
69
|
+
* object and no `dt` parameter (the delta already encodes elapsed time).
|
|
70
|
+
*
|
|
71
|
+
* @param entity the character entity (kinematic RigidBody + Collider + CharacterController).
|
|
72
|
+
* @param desiredDelta the requested world-space displacement for this step.
|
|
73
|
+
* @returns the actual displacement applied after collision resolution.
|
|
74
|
+
* @throws PhysicsError `body-not-found` if the entity has no Rapier body,
|
|
75
|
+
* `collider-not-found` if the body has no collider,
|
|
76
|
+
* `controller-requires-kinematic` if the body is not kinematic.
|
|
77
|
+
*/
|
|
78
|
+
moveAndSlide(entity: number, desiredDelta: Vec3): Vec3;
|
|
79
|
+
/** Advance the physics simulation by one timestep. */
|
|
80
|
+
step(deltaTime: number): void;
|
|
81
|
+
/** Return the number of active rigid bodies in the physics world. */
|
|
82
|
+
getBodyCount(): number;
|
|
83
|
+
/**
|
|
84
|
+
* Check whether a Rapier body exists for `entity`.
|
|
85
|
+
*
|
|
86
|
+
* Returns `true` after `ensureBody` has created a Rapier body for the entity
|
|
87
|
+
* (which happens asynchronously via WASM fire-and-forget load + tick pipeline).
|
|
88
|
+
* Always returns `false` for entities that have no `RigidBody` + `Collider`.
|
|
89
|
+
*
|
|
90
|
+
* AI-user contract: before calling `moveAndSlide` inside a per-frame driver,
|
|
91
|
+
* guard with `if (!pw.hasBody(entity)) return;` to avoid `body-not-found`
|
|
92
|
+
* errors during the window between `app.start()` and the first
|
|
93
|
+
* `physicsSyncBackend` tick that builds the body.
|
|
94
|
+
*/
|
|
95
|
+
hasBody(entity: number): boolean;
|
|
96
|
+
}
|
|
97
|
+
/** 2D raycast hit result. */
|
|
98
|
+
export interface RaycastHit2D {
|
|
99
|
+
entity: number;
|
|
100
|
+
point: Vec2;
|
|
101
|
+
normal: Vec2;
|
|
102
|
+
timeOfImpact: number;
|
|
103
|
+
}
|
|
104
|
+
/** 2D PhysicsWorld Resource interface. */
|
|
105
|
+
export interface PhysicsWorld2D {
|
|
106
|
+
/** Release backend-native resources. Called by the owning physics plugin. */
|
|
107
|
+
dispose(): void;
|
|
108
|
+
setGravity(gravity: Vec2): void;
|
|
109
|
+
getGravity(): Vec2;
|
|
110
|
+
raycast(origin: Vec2, direction: Vec2, maxDist: number, filterMask?: number): RaycastHit2D | undefined;
|
|
111
|
+
teleport(entity: number, position: Vec2, rotation: number): void;
|
|
112
|
+
/**
|
|
113
|
+
* Move a kinematic character with collision response, slope handling,
|
|
114
|
+
* auto-step, and ground-snap (2D variant of {@link PhysicsWorld.moveAndSlide}).
|
|
115
|
+
*
|
|
116
|
+
* Resolves `desiredDelta` against the world geometry, writes the resolved
|
|
117
|
+
* position back to the entity's `Transform` and `CharacterController.grounded`,
|
|
118
|
+
* and returns the actual 2D displacement. The entity must carry a
|
|
119
|
+
* `RigidBody({ type: 'kinematic' })`, a `Collider`, and a `CharacterController`.
|
|
120
|
+
*
|
|
121
|
+
* @param entity the character entity (kinematic RigidBody + Collider + CharacterController).
|
|
122
|
+
* @param desiredDelta the requested world-space 2D displacement for this step.
|
|
123
|
+
* @returns the actual 2D displacement applied after collision resolution.
|
|
124
|
+
* @throws PhysicsError `body-not-found`, `collider-not-found`, or
|
|
125
|
+
* `controller-requires-kinematic` (same contract as the 3D primitive).
|
|
126
|
+
*/
|
|
127
|
+
moveAndSlide(entity: number, desiredDelta: Vec2): Vec2;
|
|
128
|
+
step(deltaTime: number): void;
|
|
129
|
+
getBodyCount(): number;
|
|
130
|
+
/**
|
|
131
|
+
* Check whether a Rapier 2D body exists for `entity`.
|
|
132
|
+
*
|
|
133
|
+
* See {@link PhysicsWorld.hasBody} for the full contract — the 2D variant
|
|
134
|
+
* follows the same semantics.
|
|
135
|
+
*/
|
|
136
|
+
hasBody(entity: number): boolean;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=physics-world.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics-world.d.ts","sourceRoot":"","sources":["../src/physics-world.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,CAAC;IACZ,MAAM,EAAE,IAAI,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,OAAO,IAAI,IAAI,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IAEhC,wCAAwC;IACxC,UAAU,IAAI,IAAI,CAAC;IAEnB;;;;;;;;OAQG;IACH,OAAO,CACL,MAAM,EAAE,IAAI,EACZ,SAAS,EAAE,IAAI,EACf,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,UAAU,GAAG,SAAS,CAAC;IAE1B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IAE/C;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAEvD,sDAAsD;IACtD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B,qEAAqE;IACrE,YAAY,IAAI,MAAM,CAAC;IAEvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC;AAED,6BAA6B;AAC7B,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,CAAC;IACZ,MAAM,EAAE,IAAI,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,OAAO,IAAI,IAAI,CAAC;IAChB,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,UAAU,IAAI,IAAI,CAAC;IACnB,OAAO,CACL,MAAM,EAAE,IAAI,EACZ,SAAS,EAAE,IAAI,EACf,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,YAAY,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjE;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IACvD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,IAAI,MAAM,CAAC;IAEvB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Plugin } from '@forgeax/engine-plugin';
|
|
2
|
+
import type { PhysicsWorld, PhysicsWorld2D } from './physics-world';
|
|
3
|
+
/** Rapier backend selector. */
|
|
4
|
+
export type PhysicsBackend = 'rapier-2d' | 'rapier-3d';
|
|
5
|
+
declare module '@forgeax/engine-plugin' {
|
|
6
|
+
interface EngineContextServices {
|
|
7
|
+
physics?: PhysicsWorld | PhysicsWorld2D;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* physicsPlugin(backend) dynamically imports the Rapier backend,
|
|
12
|
+
* loads the WASM module, creates the PhysicsWorld, inserts it as the
|
|
13
|
+
* 'PhysicsWorld' world resource, and registers the three-phase tick systems.
|
|
14
|
+
*
|
|
15
|
+
* The resource is inserted before registering systems so moveAndSlide resolves
|
|
16
|
+
* `PhysicsWorld` on the first tick. Cordis owns rollback if any later effect
|
|
17
|
+
* fails.
|
|
18
|
+
*
|
|
19
|
+
* @param backend 'rapier-2d' or 'rapier-3d'
|
|
20
|
+
*/
|
|
21
|
+
export declare function physicsPlugin(backend: PhysicsBackend): Plugin;
|
|
22
|
+
//# sourceMappingURL=plugin-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-factory.d.ts","sourceRoot":"","sources":["../src/plugin-factory.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAGrD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAcpE,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,WAAW,CAAC;AAEvD,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,qBAAqB;QAC7B,OAAO,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC;KACzC;CACF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAqC7D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-set.d.ts","sourceRoot":"","sources":["../src/system-set.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,yCAAuC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeax/engine-physics",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"description": "Physics interface package for forgeax-engine — ECS component schemas (RigidBody / Collider / CollisionEvent), PhysicsWorld Resource interface, PhysicsErrorCode union, and the physicsPlugin(backend) factory that dynamic-imports the rapier 2D / 3D WASM backend on build.",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.mjs",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@forgeax/engine-ecs": "0.1.2",
|
|
26
|
+
"@forgeax/engine-math": "0.1.2",
|
|
27
|
+
"@forgeax/engine-plugin": "0.1.2",
|
|
28
|
+
"@forgeax/engine-types": "0.1.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@forgeax/engine-assets-runtime": "0.1.2",
|
|
32
|
+
"@forgeax/engine-physics-rapier2d": "0.1.2",
|
|
33
|
+
"@forgeax/engine-physics-rapier3d": "0.1.2",
|
|
34
|
+
"@forgeax/engine-runtime": "0.1.2",
|
|
35
|
+
"@forgeax/engine-shader": "0.1.2"
|
|
36
|
+
},
|
|
37
|
+
"forgeax": {
|
|
38
|
+
"metrics": {
|
|
39
|
+
"bundle-size": {
|
|
40
|
+
"enabled": false,
|
|
41
|
+
"reason": "interface + plugin factory package — physicsPlugin dynamic-imports the rapier backend, so the bundled payload is the factory shell only; bundle-size tracked by the backend packages it lazy-loads"
|
|
42
|
+
},
|
|
43
|
+
"fps": {
|
|
44
|
+
"enabled": false,
|
|
45
|
+
"reason": "interface + plugin factory package — no per-frame runtime systems of its own; fps tracked by backend / demo"
|
|
46
|
+
},
|
|
47
|
+
"bench": {
|
|
48
|
+
"enabled": false,
|
|
49
|
+
"reason": "interface + plugin factory package — type definitions, component schemas, and a dynamic-import factory; no hot-path benchmark surface"
|
|
50
|
+
},
|
|
51
|
+
"gate": {
|
|
52
|
+
"enabled": false,
|
|
53
|
+
"reason": "no package-level binary gate; smoke gate covered by hello-physics demo (M4)"
|
|
54
|
+
},
|
|
55
|
+
"spike-report": {
|
|
56
|
+
"enabled": false,
|
|
57
|
+
"reason": "not a spike package"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"test": "vitest run"
|
|
64
|
+
}
|
|
65
|
+
}
|