@aethervtt/shard-physics 0.0.1

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.
@@ -0,0 +1,155 @@
1
+ import { type Entity } from '@aethervtt/shard-core';
2
+ export declare const BODY_KINDS: readonly ['dynamic', 'fixed', 'kinematic-position', 'kinematic-velocity'];
3
+ export type BodyKind = (typeof BODY_KINDS)[number];
4
+ export declare const RigidBody: import("@aethervtt/shard-core").ComponentDef<{
5
+ readonly kind: import("@aethervtt/shard-core").FieldType<"dynamic" | "fixed" | "kinematic-position" | "kinematic-velocity", "u8">;
6
+ readonly gravityScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
7
+ readonly linearDamping: import("@aethervtt/shard-core").FieldType<number, "f32">;
8
+ readonly angularDamping: import("@aethervtt/shard-core").FieldType<number, "f32">;
9
+ readonly ccd: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
10
+ readonly canSleep: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
11
+ readonly dominance: import("@aethervtt/shard-core").FieldType<number, "i8">;
12
+ readonly lockTranslation: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
13
+ readonly lockRotation: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
14
+ }>;
15
+ export declare const Velocity: import("@aethervtt/shard-core").ComponentDef<{
16
+ readonly linear: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
17
+ readonly angular: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
18
+ }>;
19
+ /**
20
+ * A body the physics world set aside because it is farther than `PhysicsRange.radius` from the
21
+ * floating origin (spec 0040). It isn't in Rapier: it doesn't move, collide, or show up in queries.
22
+ * It keeps its Transform and comes back, with this velocity, when the origin gets close again.
23
+ *
24
+ * Saved with the scene (the default): for a body without a Velocity component, this is the only
25
+ * record of how it was moving, so a ship parked mid-flight keeps flying after a load.
26
+ */
27
+ export declare const PhysicsParked: import("@aethervtt/shard-core").ComponentDef<{
28
+ readonly linear: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
29
+ readonly angular: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
30
+ }>;
31
+ export declare const SHAPES: readonly ['ball', 'cuboid', 'capsule', 'cylinder', 'cone', 'convex', 'trimesh', 'heightfield', 'segment', 'polyline'];
32
+ export type Shape = (typeof SHAPES)[number];
33
+ export declare const Collider: import("@aethervtt/shard-core").ComponentDef<{
34
+ readonly shape: import("@aethervtt/shard-core").FieldType<"ball" | "capsule" | "cone" | "convex" | "cuboid" | "cylinder" | "heightfield" | "polyline" | "segment" | "trimesh", "u8">;
35
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
36
+ readonly halfExtents: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
37
+ readonly halfHeight: import("@aethervtt/shard-core").FieldType<number, "f32">;
38
+ readonly mesh: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"Mesh"> | null, "object">;
39
+ readonly points: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3[], "object">;
40
+ readonly heightfield: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").InferFields<{
41
+ rows: import("@aethervtt/shard-core").FieldType<number, "u16">;
42
+ cols: import("@aethervtt/shard-core").FieldType<number, "u16">;
43
+ heights: import("@aethervtt/shard-core").FieldType<number[], "object">;
44
+ }>, "object">;
45
+ readonly friction: import("@aethervtt/shard-core").FieldType<number, "f32">;
46
+ readonly restitution: import("@aethervtt/shard-core").FieldType<number, "f32">;
47
+ readonly density: import("@aethervtt/shard-core").FieldType<number, "f32">;
48
+ readonly sensor: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
49
+ readonly layers: import("@aethervtt/shard-core").FieldType<number, "u16">;
50
+ readonly mask: import("@aethervtt/shard-core").FieldType<number, "u16">;
51
+ readonly events: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
52
+ readonly forceThreshold: import("@aethervtt/shard-core").FieldType<number, "f32">;
53
+ }>;
54
+ export declare const ExternalForce: import("@aethervtt/shard-core").ComponentDef<{
55
+ readonly force: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
56
+ readonly torque: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
57
+ }>;
58
+ export declare const ExternalImpulse: import("@aethervtt/shard-core").ComponentDef<{
59
+ readonly impulse: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
60
+ readonly torque: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
61
+ }>;
62
+ export declare const Mass: import("@aethervtt/shard-core").ComponentDef<{
63
+ readonly mass: import("@aethervtt/shard-core").FieldType<number, "f32">;
64
+ }>;
65
+ export declare const GravitySource: import("@aethervtt/shard-core").ComponentDef<{
66
+ readonly strength: import("@aethervtt/shard-core").FieldType<number, "f32">;
67
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
68
+ readonly range: import("@aethervtt/shard-core").FieldType<number, "f32">;
69
+ readonly falloff: import("@aethervtt/shard-core").FieldType<"constant" | "inverse-square", "u8">;
70
+ }>;
71
+ export declare const JOINT_KINDS: readonly ['fixed', 'revolute', 'prismatic', 'spherical', 'rope'];
72
+ export declare const Joint: import("@aethervtt/shard-core").ComponentDef<{
73
+ readonly kind: import("@aethervtt/shard-core").FieldType<"fixed" | "prismatic" | "revolute" | "rope" | "spherical", "u8">;
74
+ readonly other: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
75
+ readonly anchor: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
76
+ readonly otherAnchor: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
77
+ readonly axis: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
78
+ readonly limits: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
79
+ readonly motorVelocity: import("@aethervtt/shard-core").FieldType<number, "f32">;
80
+ readonly motorFactor: import("@aethervtt/shard-core").FieldType<number, "f32">;
81
+ }>;
82
+ export declare const CharacterIntent: import("@aethervtt/shard-core").ComponentDef<{
83
+ readonly move: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
84
+ readonly jump: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
85
+ }>;
86
+ export declare const CharacterState: import("@aethervtt/shard-core").ComponentDef<{
87
+ readonly grounded: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
88
+ readonly groundNormal: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
89
+ readonly groundEntity: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
90
+ readonly velocity: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
91
+ readonly up: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
92
+ readonly airTime: import("@aethervtt/shard-core").FieldType<number, "f32">;
93
+ }>;
94
+ export declare const CharacterController: import("@aethervtt/shard-core").ComponentDef<{
95
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
96
+ readonly height: import("@aethervtt/shard-core").FieldType<number, "f32">;
97
+ readonly stepHeight: import("@aethervtt/shard-core").FieldType<number, "f32">;
98
+ readonly maxSlope: import("@aethervtt/shard-core").FieldType<number, "f32">;
99
+ readonly snapDistance: import("@aethervtt/shard-core").FieldType<number, "f32">;
100
+ readonly up: import("@aethervtt/shard-core").FieldType<"fixed" | "gravity", "u8">;
101
+ readonly fixedUp: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
102
+ readonly gravity: import("@aethervtt/shard-core").FieldType<number, "f32">;
103
+ readonly jumpSpeed: import("@aethervtt/shard-core").FieldType<number, "f32">;
104
+ readonly airControl: import("@aethervtt/shard-core").FieldType<number, "f32">;
105
+ readonly pushForce: import("@aethervtt/shard-core").FieldType<number, "f32">;
106
+ readonly layers: import("@aethervtt/shard-core").FieldType<number, "u16">;
107
+ readonly mask: import("@aethervtt/shard-core").FieldType<number, "u16">;
108
+ readonly alignRotation: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
109
+ }>;
110
+ export interface CharacterGroundEventData {
111
+ entity: Entity;
112
+ /** true: landed. false: left the ground (jumped or walked off). */
113
+ grounded: boolean;
114
+ }
115
+ export declare const CharacterGroundEvent: import("@aethervtt/shard-core").EventDef<CharacterGroundEventData>;
116
+ /** App-wide physics settings. */
117
+ export declare const PhysicsConfig: import("@aethervtt/shard-core").ResourceDef<PhysicsConfigValue>;
118
+ /**
119
+ * With grids (spec 0040), bodies farther than `radius` from the floating origin are parked
120
+ * (PhysicsParked): out of Rapier, so f32 precision loss far away can't make them jitter or fall
121
+ * through the ground. They resume within 0.95 × radius, so an origin at the edge doesn't flip them.
122
+ */
123
+ export declare const PhysicsRange: import("@aethervtt/shard-core").ResourceDef<PhysicsRangeValue>;
124
+ export interface PhysicsRangeValue {
125
+ /** Bodies farther than this from the origin (m) are parked. */
126
+ radius: number;
127
+ }
128
+ export interface PhysicsConfigValue {
129
+ /** m/s². In 2D, x and y are used. */
130
+ gravity: [number, number, number];
131
+ /** Solver iterations per step (Rapier's default is 4). */
132
+ iterations: number;
133
+ /** Blend Transforms between steps by FixedTime.alpha, for smooth motion on fast displays. */
134
+ interpolate: boolean;
135
+ /** Skip stepping (bodies keep their state). */
136
+ paused: boolean;
137
+ }
138
+ export interface CollisionEventData {
139
+ kind: 'started' | 'stopped';
140
+ /** The collider entities. */
141
+ a: Entity;
142
+ b: Entity;
143
+ /** Their bodies (the collider's entity or ancestor with RigidBody; -1 for fixed colliders). */
144
+ bodyA: Entity;
145
+ bodyB: Entity;
146
+ sensor: boolean;
147
+ }
148
+ export declare const CollisionEvent: import("@aethervtt/shard-core").EventDef<CollisionEventData>;
149
+ export interface ContactForceEventData {
150
+ a: Entity;
151
+ b: Entity;
152
+ /** Total contact force magnitude, N. */
153
+ force: number;
154
+ }
155
+ export declare const ContactForceEvent: import("@aethervtt/shard-core").EventDef<ContactForceEventData>;
@@ -0,0 +1,340 @@
1
+ import { defineComponent, defineEvent, defineResource, t } from "@aethervtt/shard-core";
2
+ import { Transform } from "@aethervtt/shard-transform";
3
+ const BODY_KINDS = ["dynamic", "fixed", "kinematic-position", "kinematic-velocity"];
4
+ const RigidBody = defineComponent(
5
+ "physics/RigidBody",
6
+ {
7
+ kind: t.enum(BODY_KINDS, {
8
+ description: "dynamic: moved by forces and contacts. fixed: never moves. kinematic-position: follows its Transform. kinematic-velocity: moves by its Velocity."
9
+ }),
10
+ gravityScale: t.f32({ default: 1, description: "Multiplies gravity for this body." }),
11
+ linearDamping: t.f32({ min: 0, description: "Slows linear motion over time (1/s)." }),
12
+ angularDamping: t.f32({ min: 0, description: "Slows rotation over time (1/s)." }),
13
+ ccd: t.bool({
14
+ description: "Continuous collision detection: fast bodies (bullets) do not tunnel."
15
+ }),
16
+ canSleep: t.bool({
17
+ default: true,
18
+ description: "Stops simulating the body while it rests."
19
+ }),
20
+ dominance: t.i8({
21
+ min: -127,
22
+ max: 127,
23
+ description: "Higher dominance pushes lower without being pushed back."
24
+ }),
25
+ lockTranslation: t.vec3({
26
+ min: 0,
27
+ max: 1,
28
+ description: "Per axis: 1 locks movement along that world axis."
29
+ }),
30
+ lockRotation: t.vec3({
31
+ min: 0,
32
+ max: 1,
33
+ description: "Per axis: 1 locks rotation around that axis. In 2D, z locks rotation."
34
+ })
35
+ },
36
+ {
37
+ description: "A physics body. Colliders on this entity and on descendants without their own body make its shape. Moves the entity\u2019s Transform.",
38
+ requires: [Transform]
39
+ }
40
+ );
41
+ const Velocity = defineComponent(
42
+ "physics/Velocity",
43
+ {
44
+ linear: t.vec3({ unit: "m/s", description: "World-space linear velocity." }),
45
+ angular: t.vec3({ unit: "rad/s", description: "World-space angular velocity (2D: z)." })
46
+ },
47
+ {
48
+ description: "A body\u2019s velocity. Written by physics each step; writing it sets the body\u2019s velocity."
49
+ }
50
+ );
51
+ const PhysicsParked = defineComponent(
52
+ "physics/PhysicsParked",
53
+ {
54
+ linear: t.vec3({ unit: "m/s", description: "Linear velocity when it was parked." }),
55
+ angular: t.vec3({ unit: "rad/s", description: "Angular velocity when it was parked (2D: z)." })
56
+ },
57
+ {
58
+ description: "Out of physics range: removed from the simulation until the origin comes back within PhysicsRange.radius. Added and removed by physics."
59
+ }
60
+ );
61
+ const SHAPES = [
62
+ "ball",
63
+ "cuboid",
64
+ "capsule",
65
+ "cylinder",
66
+ "cone",
67
+ "convex",
68
+ "trimesh",
69
+ "heightfield",
70
+ "segment",
71
+ "polyline"
72
+ ];
73
+ const Collider = defineComponent(
74
+ "physics/Collider",
75
+ {
76
+ shape: t.enum(SHAPES, {
77
+ description: "ball: radius. cuboid: halfExtents. capsule, cylinder, cone: radius and halfHeight (along Y). convex and trimesh: mesh or points. heightfield: heightfield and halfExtents. segment and polyline (2D): points or mesh."
78
+ }),
79
+ radius: t.f32({ default: 0.5, min: 0, unit: "m" }),
80
+ halfExtents: t.vec3({
81
+ default: [0.5, 0.5, 0.5],
82
+ min: 0,
83
+ unit: "m",
84
+ description: "cuboid: half size per axis. heightfield: half width (x), height scale (y), half depth (z)."
85
+ }),
86
+ halfHeight: t.f32({
87
+ default: 0.5,
88
+ min: 0,
89
+ unit: "m",
90
+ description: "Half the length of a capsule\u2019s middle, or half a cylinder\u2019s or cone\u2019s height."
91
+ }),
92
+ mesh: t.handle("Mesh", {
93
+ description: "convex, trimesh, and polyline: the mesh whose vertices make the shape."
94
+ }),
95
+ points: t.list(t.vec3, {
96
+ description: "convex, polyline, and segment without a mesh: points in local space (2D reads x and y)."
97
+ }),
98
+ heightfield: t.struct(
99
+ {
100
+ rows: t.u16({ description: "Samples along Z." }),
101
+ cols: t.u16({ description: "Samples along X." }),
102
+ heights: t.list(t.f32, {
103
+ description: "rows \xD7 cols heights, row by row, scaled by halfExtents.y."
104
+ })
105
+ },
106
+ { description: "heightfield: the height samples." }
107
+ ),
108
+ friction: t.f32({ default: 0.5, min: 0 }),
109
+ restitution: t.f32({ min: 0, description: "Bounciness: 0 stops, 1 bounces back fully." }),
110
+ density: t.f32({
111
+ default: 1,
112
+ min: 0,
113
+ unit: "kg/m\xB3",
114
+ description: "Mass from volume. Ignored when the body has Mass."
115
+ }),
116
+ sensor: t.bool({ description: "Detects overlaps without colliding." }),
117
+ layers: t.u16({
118
+ default: 1,
119
+ description: "Bitmask: the collision layers this collider is in."
120
+ }),
121
+ mask: t.u16({
122
+ default: 65535,
123
+ description: "Bitmask: the layers it collides with. Both sides must accept each other."
124
+ }),
125
+ events: t.bool({ description: "Send CollisionEvent when contacts start and stop." }),
126
+ forceThreshold: t.f32({
127
+ min: 0,
128
+ unit: "N",
129
+ description: "Send ContactForceEvent when the contact force exceeds this (0: never)."
130
+ })
131
+ },
132
+ {
133
+ description: "A collision shape. Uses the entity\u2019s world transform (scale included). Without a RigidBody on it or an ancestor, it is fixed.",
134
+ requires: [Transform]
135
+ }
136
+ );
137
+ const ExternalForce = defineComponent(
138
+ "physics/ExternalForce",
139
+ {
140
+ force: t.vec3({ unit: "N", description: "World-space force applied every step." }),
141
+ torque: t.vec3({ unit: "N\xB7m" })
142
+ },
143
+ { description: "A continuous force on the body, e.g. thrust. Set to zero to stop." }
144
+ );
145
+ const ExternalImpulse = defineComponent(
146
+ "physics/ExternalImpulse",
147
+ {
148
+ impulse: t.vec3({ unit: "N\xB7s", description: "Applied once at the next step, then zeroed." }),
149
+ torque: t.vec3({ unit: "N\xB7m\xB7s" })
150
+ },
151
+ { description: "A one-off push: a jump, an explosion, a hit." }
152
+ );
153
+ const Mass = defineComponent(
154
+ "physics/Mass",
155
+ { mass: t.f32({ default: 1, min: 0, unit: "kg", description: "The body\u2019s total mass." }) },
156
+ { description: "Sets a body\u2019s mass directly instead of from collider density." }
157
+ );
158
+ const GravitySource = defineComponent(
159
+ "physics/GravitySource",
160
+ {
161
+ strength: t.f32({
162
+ default: 9.81,
163
+ unit: "m/s\xB2",
164
+ description: "Acceleration toward this entity at `radius` (at any distance if constant)."
165
+ }),
166
+ radius: t.f32({
167
+ default: 1,
168
+ min: 0,
169
+ unit: "m",
170
+ description: "Surface radius; inverse-square falloff is 1 here."
171
+ }),
172
+ range: t.f32({ min: 0, unit: "m", description: "No pull beyond this distance (0: no limit)." }),
173
+ falloff: t.enum(["inverse-square", "constant"])
174
+ },
175
+ {
176
+ description: "Point gravity toward the entity (a planet). Pulls dynamic bodies and sets character up (CharacterController up: gravity).",
177
+ requires: [Transform]
178
+ }
179
+ );
180
+ const JOINT_KINDS = ["fixed", "revolute", "prismatic", "spherical", "rope"];
181
+ const Joint = defineComponent(
182
+ "physics/Joint",
183
+ {
184
+ kind: t.enum(JOINT_KINDS),
185
+ other: t.entity({ description: "The body this one is jointed to." }),
186
+ anchor: t.vec3({ unit: "m", description: "Joint point in this body\u2019s local space." }),
187
+ otherAnchor: t.vec3({ unit: "m", description: "Joint point in the other body\u2019s local space." }),
188
+ axis: t.vec3({
189
+ default: [0, 1, 0],
190
+ description: "revolute: hinge axis. prismatic: slide axis (local to this body)."
191
+ }),
192
+ limits: t.vec2({
193
+ description: "revolute (rad), prismatic (m): [min, max], off when equal. rope: [_, max length]."
194
+ }),
195
+ motorVelocity: t.f32({ description: "revolute, prismatic: drive at this speed (0: off)." }),
196
+ motorFactor: t.f32({ default: 1, min: 0, description: "How hard the motor drives." })
197
+ },
198
+ { description: "Connects this entity\u2019s body to another body." }
199
+ );
200
+ const CharacterIntent = defineComponent(
201
+ "physics/CharacterIntent",
202
+ {
203
+ move: t.vec3({
204
+ unit: "m/s",
205
+ description: "Desired walking velocity in the character\u2019s frame: x right, -z forward (y is ignored; jump instead)."
206
+ }),
207
+ jump: t.bool({
208
+ description: "Jump at the next step on the ground. The controller clears it when the jump happens."
209
+ })
210
+ },
211
+ { description: "What a character wants to do, written by games, input, or AI each frame." }
212
+ );
213
+ const CharacterState = defineComponent(
214
+ "physics/CharacterState",
215
+ {
216
+ grounded: t.bool({ readonly: true, description: "Standing on walkable ground." }),
217
+ groundNormal: t.vec3({
218
+ default: [0, 1, 0],
219
+ readonly: true,
220
+ description: "Normal of the ground under the character (up while airborne)."
221
+ }),
222
+ groundEntity: t.entity({
223
+ readonly: true,
224
+ description: "The body (or fixed collider) the character stands on."
225
+ }),
226
+ velocity: t.vec3({ unit: "m/s", readonly: true, description: "World-space velocity." }),
227
+ up: t.vec3({ default: [0, 1, 0], readonly: true, description: "The character\u2019s up." }),
228
+ airTime: t.f32({ unit: "s", readonly: true, description: "Seconds since last grounded." })
229
+ },
230
+ { description: "What the character controller did at the last step. Read-only." }
231
+ );
232
+ const CharacterController = defineComponent(
233
+ "physics/CharacterController",
234
+ {
235
+ radius: t.f32({ default: 0.35, min: 0, unit: "m", description: "Capsule radius." }),
236
+ height: t.f32({
237
+ default: 1.8,
238
+ min: 0,
239
+ unit: "m",
240
+ description: "Total capsule height, centered on the entity."
241
+ }),
242
+ stepHeight: t.f32({
243
+ default: 0.3,
244
+ min: 0,
245
+ unit: "m",
246
+ description: "Tallest step it walks up (0: none)."
247
+ }),
248
+ maxSlope: t.f32({
249
+ default: 45,
250
+ min: 0,
251
+ max: 90,
252
+ unit: "deg",
253
+ description: "Steepest slope it walks up and stands on; steeper ones it slides down."
254
+ }),
255
+ snapDistance: t.f32({
256
+ default: 0.2,
257
+ min: 0,
258
+ unit: "m",
259
+ description: "Stays on the ground over drops this small, e.g. walking down steps (0: off)."
260
+ }),
261
+ up: t.enum(["fixed", "gravity"], {
262
+ description: "fixed: up is fixedUp and gravity pulls along -fixedUp. gravity: up points away from the strongest GravitySource (or against physics/Config gravity when none reaches)."
263
+ }),
264
+ fixedUp: t.vec3({ default: [0, 1, 0], description: "Up when up is fixed." }),
265
+ gravity: t.f32({
266
+ default: 9.81,
267
+ min: 0,
268
+ unit: "m/s\xB2",
269
+ description: "Fall acceleration when up is fixed."
270
+ }),
271
+ jumpSpeed: t.f32({ default: 5, min: 0, unit: "m/s", description: "Upward speed of a jump." }),
272
+ airControl: t.f32({
273
+ default: 0.3,
274
+ min: 0,
275
+ max: 1,
276
+ description: "Steering in the air: the share of the way to the intended velocity per 1/60 s (0: none, 1: as on ground)."
277
+ }),
278
+ pushForce: t.f32({
279
+ default: 200,
280
+ min: 0,
281
+ unit: "N",
282
+ description: "Push on dynamic bodies it walks into, never faster than it walks."
283
+ }),
284
+ layers: t.u16({ default: 1, description: "Bitmask: the collision layers the capsule is in." }),
285
+ mask: t.u16({ default: 65535, description: "Bitmask: the layers it collides with." }),
286
+ alignRotation: t.bool({
287
+ default: true,
288
+ description: "Turn the entity so its +Y matches up, keeping its heading."
289
+ })
290
+ },
291
+ {
292
+ description: "A kinematic capsule that walks by CharacterIntent: slides along walls, climbs steps and slopes, snaps to the ground, jumps, pushes bodies, rides platforms. No RigidBody or Collider on the same entity.",
293
+ requires: [Transform, CharacterIntent, CharacterState]
294
+ }
295
+ );
296
+ const CharacterGroundEvent = defineEvent(
297
+ "physics/CharacterGroundEvent",
298
+ { description: "A character landed or left the ground." }
299
+ );
300
+ const PhysicsConfig = defineResource("physics/Config", {
301
+ description: "Gravity, solver iterations, and render interpolation. Set before the first step or at any time.",
302
+ init: () => ({
303
+ gravity: [0, -9.81, 0],
304
+ iterations: 4,
305
+ interpolate: false,
306
+ paused: false
307
+ })
308
+ });
309
+ const PhysicsRange = defineResource("physics/Range", {
310
+ description: "How far from the floating origin bodies simulate (m). Farther ones are parked until the origin comes near. Only used when the world has a Grid.",
311
+ init: () => ({ radius: 2e4 })
312
+ });
313
+ const CollisionEvent = defineEvent("physics/CollisionEvent", {
314
+ description: "Contacts starting and stopping between colliders with events: true (either side). Sensors report overlaps."
315
+ });
316
+ const ContactForceEvent = defineEvent("physics/ContactForceEvent", {
317
+ description: "Contact force above a collider\u2019s forceThreshold (hits, crashes)."
318
+ });
319
+ export {
320
+ BODY_KINDS,
321
+ CharacterController,
322
+ CharacterGroundEvent,
323
+ CharacterIntent,
324
+ CharacterState,
325
+ Collider,
326
+ CollisionEvent,
327
+ ContactForceEvent,
328
+ ExternalForce,
329
+ ExternalImpulse,
330
+ GravitySource,
331
+ JOINT_KINDS,
332
+ Joint,
333
+ Mass,
334
+ PhysicsConfig,
335
+ PhysicsParked,
336
+ PhysicsRange,
337
+ RigidBody,
338
+ SHAPES,
339
+ Velocity
340
+ };
@@ -0,0 +1,17 @@
1
+ import type { Query } from '@aethervtt/shard-core';
2
+ /**
3
+ * Gravity sources packed for per-body sampling: 6 floats each (position, strength, radius, and
4
+ * range, stored as -range - 1 for constant falloff). Filled once per step.
5
+ */
6
+ export interface GravitySources {
7
+ data: Float64Array;
8
+ count: number;
9
+ }
10
+ export declare const createGravitySources: () => GravitySources;
11
+ /** Packs every source in the query (entities with GravitySource and GlobalTransform). */
12
+ export declare function gatherGravitySources(query: Query, out: GravitySources): void;
13
+ /**
14
+ * Gravity at a point into out[0..2]: the sum of every source's pull, or with `strongest` only the
15
+ * strongest one's (a character's down). out[3] is the returned vector's length.
16
+ */
17
+ export declare function sampleGravity(sources: GravitySources, dim: 2 | 3, px: number, py: number, pz: number, strongest: boolean, out: Float64Array): void;
@@ -0,0 +1,73 @@
1
+ import { GlobalTransform } from "@aethervtt/shard-transform";
2
+ import { GravitySource } from "./components.js";
3
+ const createGravitySources = () => ({
4
+ data: new Float64Array(6 * 8),
5
+ count: 0
6
+ });
7
+ function gatherGravitySources(query, out) {
8
+ let n = 0;
9
+ for (let t = 0; t < query.tables.length; t++) {
10
+ const table = query.tables[t];
11
+ const m = table.column(GlobalTransform, "matrix");
12
+ const strength = table.column(GravitySource, "strength");
13
+ const radius = table.column(GravitySource, "radius");
14
+ const range = table.column(GravitySource, "range");
15
+ const falloff = table.column(GravitySource, "falloff");
16
+ for (let row = 0; row < table.count; row++) {
17
+ if ((n + 1) * 6 > out.data.length) {
18
+ const grown = new Float64Array(out.data.length * 2);
19
+ grown.set(out.data);
20
+ out.data = grown;
21
+ }
22
+ const d = out.data;
23
+ d[n * 6] = m[row * 12 + 3];
24
+ d[n * 6 + 1] = m[row * 12 + 7];
25
+ d[n * 6 + 2] = m[row * 12 + 11];
26
+ d[n * 6 + 3] = strength[row];
27
+ d[n * 6 + 4] = radius[row];
28
+ d[n * 6 + 5] = falloff[row] === 1 ? -range[row] - 1 : range[row];
29
+ n++;
30
+ }
31
+ }
32
+ out.count = n;
33
+ }
34
+ function sampleGravity(sources, dim, px, py, pz, strongest, out) {
35
+ let fx = 0;
36
+ let fy = 0;
37
+ let fz = 0;
38
+ let best = 0;
39
+ const d = sources.data;
40
+ for (let i = 0; i < sources.count; i++) {
41
+ const dx = d[i * 6] - px;
42
+ const dy = d[i * 6 + 1] - py;
43
+ const dz = dim === 3 ? d[i * 6 + 2] - pz : 0;
44
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
45
+ if (dist < 1e-6) continue;
46
+ const packed = d[i * 6 + 5];
47
+ const constant = packed < 0;
48
+ const range = constant ? -packed - 1 : packed;
49
+ if (range > 0 && dist > range) continue;
50
+ const r = d[i * 6 + 4];
51
+ const accel = constant ? d[i * 6 + 3] : d[i * 6 + 3] * (r * r / (dist * dist));
52
+ if (strongest) {
53
+ if (accel <= best) continue;
54
+ best = accel;
55
+ fx = dx / dist * accel;
56
+ fy = dy / dist * accel;
57
+ fz = dz / dist * accel;
58
+ } else {
59
+ fx += dx / dist * accel;
60
+ fy += dy / dist * accel;
61
+ fz += dz / dist * accel;
62
+ }
63
+ }
64
+ out[0] = fx;
65
+ out[1] = fy;
66
+ out[2] = fz;
67
+ out[3] = Math.sqrt(fx * fx + fy * fy + fz * fz);
68
+ }
69
+ export {
70
+ createGravitySources,
71
+ gatherGravitySources,
72
+ sampleGravity
73
+ };
@@ -0,0 +1,5 @@
1
+ export { characterSystem } from './character.js';
2
+ export { BODY_KINDS, type BodyKind, CharacterController, CharacterGroundEvent, type CharacterGroundEventData, CharacterIntent, CharacterState, Collider, CollisionEvent, type CollisionEventData, ContactForceEvent, type ContactForceEventData, ExternalForce, ExternalImpulse, GravitySource, JOINT_KINDS, Joint, Mass, PhysicsConfig, type PhysicsConfigValue, PhysicsParked, PhysicsRange, type PhysicsRangeValue, RigidBody, SHAPES, type Shape, Velocity, } from './components.js';
3
+ export { physicsMethods } from './methods.js';
4
+ export { Physics, PhysicsSystems, physics, physics2dPlugin, physics3dPlugin } from './plugin.js';
5
+ export { type BodyRecord, CHARACTER_OFFSET, type CharacterRecord, createRayHit, loadRapier, type PhysicsStats, PhysicsWorld, type QueryOptions, type QueryShape, type RayHit, } from './world.js';
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ import { characterSystem } from "./character.js";
2
+ import {
3
+ BODY_KINDS,
4
+ CharacterController,
5
+ CharacterGroundEvent,
6
+ CharacterIntent,
7
+ CharacterState,
8
+ Collider,
9
+ CollisionEvent,
10
+ ContactForceEvent,
11
+ ExternalForce,
12
+ ExternalImpulse,
13
+ GravitySource,
14
+ JOINT_KINDS,
15
+ Joint,
16
+ Mass,
17
+ PhysicsConfig,
18
+ PhysicsParked,
19
+ PhysicsRange,
20
+ RigidBody,
21
+ SHAPES,
22
+ Velocity
23
+ } from "./components.js";
24
+ import { physicsMethods } from "./methods.js";
25
+ import { Physics, PhysicsSystems, physics, physics2dPlugin, physics3dPlugin } from "./plugin.js";
26
+ import {
27
+ CHARACTER_OFFSET,
28
+ createRayHit,
29
+ loadRapier,
30
+ PhysicsWorld
31
+ } from "./world.js";
32
+ export {
33
+ BODY_KINDS,
34
+ CHARACTER_OFFSET,
35
+ CharacterController,
36
+ CharacterGroundEvent,
37
+ CharacterIntent,
38
+ CharacterState,
39
+ Collider,
40
+ CollisionEvent,
41
+ ContactForceEvent,
42
+ ExternalForce,
43
+ ExternalImpulse,
44
+ GravitySource,
45
+ JOINT_KINDS,
46
+ Joint,
47
+ Mass,
48
+ Physics,
49
+ PhysicsConfig,
50
+ PhysicsParked,
51
+ PhysicsRange,
52
+ PhysicsSystems,
53
+ PhysicsWorld,
54
+ RigidBody,
55
+ SHAPES,
56
+ Velocity,
57
+ characterSystem,
58
+ createRayHit,
59
+ loadRapier,
60
+ physics,
61
+ physics2dPlugin,
62
+ physics3dPlugin,
63
+ physicsMethods
64
+ };
@@ -0,0 +1,2 @@
1
+ import type { AppMethod } from '@aethervtt/shard-runtime';
2
+ export declare const physicsMethods: AppMethod[];