@aura3d/physics 1.4.0 → 1.4.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/package.json +34 -2
- package/src/CharacterController.ts +0 -304
- package/src/ClothFixtures.ts +0 -359
- package/src/Collider.ts +0 -67
- package/src/CollisionEvents.ts +0 -70
- package/src/CollisionVolumes.ts +0 -303
- package/src/Constraint.ts +0 -120
- package/src/Constraints.ts +0 -1
- package/src/Crowd.ts +0 -283
- package/src/ECSPhysicsBridge.ts +0 -53
- package/src/FireSmokeFixtures.ts +0 -382
- package/src/FluidFixtures.ts +0 -301
- package/src/FractureFixtures.ts +0 -277
- package/src/HitboxWorld.ts +0 -1179
- package/src/KinematicBody.ts +0 -597
- package/src/KinematicWorld.ts +0 -316
- package/src/Navigation.ts +0 -321
- package/src/PhysicsDebugDraw.ts +0 -71
- package/src/PhysicsSandboxFixtures.ts +0 -394
- package/src/PhysicsStepper.ts +0 -47
- package/src/PhysicsWorld.ts +0 -986
- package/src/PlatformerFixtures.ts +0 -205
- package/src/Raycast.ts +0 -345
- package/src/RigidBody.ts +0 -311
- package/src/ScenePhysicsBridge.ts +0 -207
- package/src/Shape.ts +0 -195
- package/src/SoftBodyFixtures.ts +0 -363
- package/src/Steering.ts +0 -531
- package/src/VehicleDynamics.ts +0 -524
- package/src/index.ts +0 -28
package/src/KinematicBody.ts
DELETED
|
@@ -1,597 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeFacing,
|
|
3
|
-
pushbox,
|
|
4
|
-
resolveCollisionVolume,
|
|
5
|
-
withVolumeOwner,
|
|
6
|
-
type CombatFacing,
|
|
7
|
-
type CollisionVolume,
|
|
8
|
-
type CollisionOwnerId,
|
|
9
|
-
type ResolvedCollisionVolume
|
|
10
|
-
} from "./CollisionVolumes.js";
|
|
11
|
-
import { cloneVec3, validateFiniteVec3, type Vec3 } from "./Shape.js";
|
|
12
|
-
|
|
13
|
-
export type KinematicBodyId = CollisionOwnerId;
|
|
14
|
-
|
|
15
|
-
export type KinematicBounds = {
|
|
16
|
-
readonly minX?: number;
|
|
17
|
-
readonly maxX?: number;
|
|
18
|
-
readonly minY?: number;
|
|
19
|
-
readonly maxY?: number;
|
|
20
|
-
readonly minZ?: number;
|
|
21
|
-
readonly maxZ?: number;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type KinematicMoveInput = {
|
|
25
|
-
readonly x?: number;
|
|
26
|
-
readonly z?: number;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type KinematicBodyDescriptor = {
|
|
30
|
-
readonly id?: KinematicBodyId;
|
|
31
|
-
readonly position?: Vec3;
|
|
32
|
-
readonly velocity?: Vec3;
|
|
33
|
-
readonly facing?: CombatFacing;
|
|
34
|
-
readonly halfExtents?: Vec3;
|
|
35
|
-
readonly maxSpeed?: number;
|
|
36
|
-
readonly acceleration?: number;
|
|
37
|
-
readonly airAcceleration?: number;
|
|
38
|
-
readonly groundFriction?: number;
|
|
39
|
-
readonly airFriction?: number;
|
|
40
|
-
readonly gravity?: number;
|
|
41
|
-
readonly jumpSpeed?: number;
|
|
42
|
-
readonly maxFallSpeed?: number;
|
|
43
|
-
readonly dashSpeed?: number;
|
|
44
|
-
readonly dashDuration?: number;
|
|
45
|
-
readonly dashCooldown?: number;
|
|
46
|
-
readonly groundY?: number;
|
|
47
|
-
readonly groundSnapDistance?: number;
|
|
48
|
-
readonly bounds?: KinematicBounds;
|
|
49
|
-
readonly lockDepth?: boolean;
|
|
50
|
-
readonly laneZ?: number;
|
|
51
|
-
readonly laneHalfWidth?: number;
|
|
52
|
-
readonly pushbox?: CollisionVolume;
|
|
53
|
-
readonly tags?: readonly string[];
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export type KinematicStepOptions = {
|
|
57
|
-
readonly gravity?: number;
|
|
58
|
-
readonly groundY?: number;
|
|
59
|
-
readonly groundSnapDistance?: number;
|
|
60
|
-
readonly bounds?: KinematicBounds;
|
|
61
|
-
readonly lockDepth?: boolean;
|
|
62
|
-
readonly laneZ?: number;
|
|
63
|
-
readonly laneHalfWidth?: number;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export type KnockbackOptions = {
|
|
67
|
-
readonly mode?: "add" | "set";
|
|
68
|
-
readonly clearDash?: boolean;
|
|
69
|
-
readonly forceAirborne?: boolean;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export type KinematicBodySnapshot = {
|
|
73
|
-
readonly id: KinematicBodyId;
|
|
74
|
-
readonly position: Vec3;
|
|
75
|
-
readonly previousPosition: Vec3;
|
|
76
|
-
readonly velocity: Vec3;
|
|
77
|
-
readonly facing: CombatFacing;
|
|
78
|
-
readonly grounded: boolean;
|
|
79
|
-
readonly crouching: boolean;
|
|
80
|
-
readonly moveInput: Vec3;
|
|
81
|
-
readonly halfExtents: Vec3;
|
|
82
|
-
readonly dashFramesRemaining: number;
|
|
83
|
-
readonly dashCooldownRemaining: number;
|
|
84
|
-
readonly tags: readonly string[];
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
export type KinematicBodyEvent =
|
|
88
|
-
| {
|
|
89
|
-
readonly type: "jump";
|
|
90
|
-
readonly bodyId: KinematicBodyId;
|
|
91
|
-
readonly position: Vec3;
|
|
92
|
-
readonly velocity: Vec3;
|
|
93
|
-
}
|
|
94
|
-
| {
|
|
95
|
-
readonly type: "dash";
|
|
96
|
-
readonly bodyId: KinematicBodyId;
|
|
97
|
-
readonly direction: CombatFacing;
|
|
98
|
-
readonly position: Vec3;
|
|
99
|
-
readonly velocity: Vec3;
|
|
100
|
-
readonly duration: number;
|
|
101
|
-
}
|
|
102
|
-
| {
|
|
103
|
-
readonly type: "fast-fall";
|
|
104
|
-
readonly bodyId: KinematicBodyId;
|
|
105
|
-
readonly position: Vec3;
|
|
106
|
-
readonly velocity: Vec3;
|
|
107
|
-
readonly speed: number;
|
|
108
|
-
}
|
|
109
|
-
| {
|
|
110
|
-
readonly type: "crouch";
|
|
111
|
-
readonly bodyId: KinematicBodyId;
|
|
112
|
-
readonly active: boolean;
|
|
113
|
-
readonly position: Vec3;
|
|
114
|
-
}
|
|
115
|
-
| {
|
|
116
|
-
readonly type: "land";
|
|
117
|
-
readonly bodyId: KinematicBodyId;
|
|
118
|
-
readonly position: Vec3;
|
|
119
|
-
readonly impactSpeed: number;
|
|
120
|
-
}
|
|
121
|
-
| {
|
|
122
|
-
readonly type: "bounds";
|
|
123
|
-
readonly bodyId: KinematicBodyId;
|
|
124
|
-
readonly axes: readonly ("x" | "y" | "z")[];
|
|
125
|
-
readonly position: Vec3;
|
|
126
|
-
}
|
|
127
|
-
| {
|
|
128
|
-
readonly type: "ground-snap";
|
|
129
|
-
readonly bodyId: KinematicBodyId;
|
|
130
|
-
readonly position: Vec3;
|
|
131
|
-
}
|
|
132
|
-
| {
|
|
133
|
-
readonly type: "knockback";
|
|
134
|
-
readonly bodyId: KinematicBodyId;
|
|
135
|
-
readonly impulse: Vec3;
|
|
136
|
-
readonly velocity: Vec3;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
export class KinematicBody {
|
|
140
|
-
readonly id: KinematicBodyId;
|
|
141
|
-
readonly halfExtents: Vec3;
|
|
142
|
-
readonly maxSpeed: number;
|
|
143
|
-
readonly acceleration: number;
|
|
144
|
-
readonly airAcceleration: number;
|
|
145
|
-
readonly groundFriction: number;
|
|
146
|
-
readonly airFriction: number;
|
|
147
|
-
readonly gravity: number;
|
|
148
|
-
readonly jumpSpeed: number;
|
|
149
|
-
readonly maxFallSpeed: number;
|
|
150
|
-
readonly dashSpeed: number;
|
|
151
|
-
readonly dashDuration: number;
|
|
152
|
-
readonly dashCooldown: number;
|
|
153
|
-
readonly groundY: number;
|
|
154
|
-
readonly groundSnapDistance: number;
|
|
155
|
-
readonly bounds: KinematicBounds | undefined;
|
|
156
|
-
readonly lockDepth: boolean;
|
|
157
|
-
readonly laneZ: number;
|
|
158
|
-
readonly laneHalfWidth: number | undefined;
|
|
159
|
-
readonly tags: readonly string[];
|
|
160
|
-
position: [number, number, number];
|
|
161
|
-
previousPosition: [number, number, number];
|
|
162
|
-
velocity: [number, number, number];
|
|
163
|
-
facing: CombatFacing;
|
|
164
|
-
grounded: boolean;
|
|
165
|
-
private pushboxVolume: CollisionVolume;
|
|
166
|
-
private moveInput: [number, number] = [0, 0];
|
|
167
|
-
private jumpQueued = false;
|
|
168
|
-
private dashQueued = false;
|
|
169
|
-
private queuedDashDirection: CombatFacing | null = null;
|
|
170
|
-
private crouchActive = false;
|
|
171
|
-
private dashTimeRemaining = 0;
|
|
172
|
-
private dashCooldownRemaining = 0;
|
|
173
|
-
private readonly pendingEvents: KinematicBodyEvent[] = [];
|
|
174
|
-
|
|
175
|
-
constructor(id: KinematicBodyId, descriptor?: KinematicBodyDescriptor);
|
|
176
|
-
constructor(descriptor?: KinematicBodyDescriptor);
|
|
177
|
-
constructor(idOrDescriptor: KinematicBodyId | KinematicBodyDescriptor = 1, descriptor: KinematicBodyDescriptor = {}) {
|
|
178
|
-
const resolvedDescriptor = isDescriptor(idOrDescriptor) ? idOrDescriptor : descriptor;
|
|
179
|
-
const resolvedId = isDescriptor(idOrDescriptor) ? resolvedDescriptor.id ?? 1 : idOrDescriptor;
|
|
180
|
-
validateBodyId(resolvedId);
|
|
181
|
-
this.id = resolvedId;
|
|
182
|
-
this.halfExtents = cloneVec3(resolvedDescriptor.halfExtents ?? [0.35, 0.9, 0.3]);
|
|
183
|
-
validatePositiveVec3(this.halfExtents, "kinematic body halfExtents");
|
|
184
|
-
this.maxSpeed = finiteNonNegative(resolvedDescriptor.maxSpeed ?? 4.5, "kinematic maxSpeed");
|
|
185
|
-
this.acceleration = finiteNonNegative(resolvedDescriptor.acceleration ?? 48, "kinematic acceleration");
|
|
186
|
-
this.airAcceleration = finiteNonNegative(resolvedDescriptor.airAcceleration ?? 22, "kinematic airAcceleration");
|
|
187
|
-
this.groundFriction = finiteNonNegative(resolvedDescriptor.groundFriction ?? 52, "kinematic groundFriction");
|
|
188
|
-
this.airFriction = finiteNonNegative(resolvedDescriptor.airFriction ?? 4, "kinematic airFriction");
|
|
189
|
-
this.gravity = finiteNonNegative(Math.abs(resolvedDescriptor.gravity ?? 24), "kinematic gravity");
|
|
190
|
-
this.jumpSpeed = finiteNonNegative(resolvedDescriptor.jumpSpeed ?? 8.5, "kinematic jumpSpeed");
|
|
191
|
-
this.maxFallSpeed = finiteNonNegative(resolvedDescriptor.maxFallSpeed ?? 18, "kinematic maxFallSpeed");
|
|
192
|
-
this.dashSpeed = finiteNonNegative(resolvedDescriptor.dashSpeed ?? 8.25, "kinematic dashSpeed");
|
|
193
|
-
this.dashDuration = finiteNonNegative(resolvedDescriptor.dashDuration ?? 0.14, "kinematic dashDuration");
|
|
194
|
-
this.dashCooldown = finiteNonNegative(resolvedDescriptor.dashCooldown ?? 0.18, "kinematic dashCooldown");
|
|
195
|
-
this.groundY = finite(resolvedDescriptor.groundY ?? 0, "kinematic groundY");
|
|
196
|
-
this.groundSnapDistance = finiteNonNegative(resolvedDescriptor.groundSnapDistance ?? 0.08, "kinematic groundSnapDistance");
|
|
197
|
-
this.bounds = resolvedDescriptor.bounds;
|
|
198
|
-
this.lockDepth = resolvedDescriptor.lockDepth ?? true;
|
|
199
|
-
this.laneZ = finite(resolvedDescriptor.laneZ ?? 0, "kinematic laneZ");
|
|
200
|
-
this.laneHalfWidth =
|
|
201
|
-
resolvedDescriptor.laneHalfWidth === undefined ? undefined : finiteNonNegative(resolvedDescriptor.laneHalfWidth, "kinematic laneHalfWidth");
|
|
202
|
-
this.tags = [...(resolvedDescriptor.tags ?? [])];
|
|
203
|
-
this.position = cloneVec3(resolvedDescriptor.position ?? [0, this.groundY + this.halfExtents[1], this.laneZ]);
|
|
204
|
-
this.previousPosition = cloneVec3(this.position);
|
|
205
|
-
this.velocity = cloneVec3(resolvedDescriptor.velocity ?? [0, 0, 0]);
|
|
206
|
-
validateFiniteVec3(this.position, "kinematic position");
|
|
207
|
-
validateFiniteVec3(this.velocity, "kinematic velocity");
|
|
208
|
-
this.facing = normalizeFacing(resolvedDescriptor.facing ?? 1);
|
|
209
|
-
this.pushboxVolume =
|
|
210
|
-
resolvedDescriptor.pushbox ??
|
|
211
|
-
pushbox({
|
|
212
|
-
id: "push",
|
|
213
|
-
halfExtents: this.halfExtents
|
|
214
|
-
});
|
|
215
|
-
this.grounded = this.position[1] - this.halfExtents[1] <= this.groundY + this.groundSnapDistance;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
setMoveInput(input: KinematicMoveInput | number, z = 0): void {
|
|
219
|
-
const rawX = typeof input === "number" ? input : input.x ?? 0;
|
|
220
|
-
const rawZ = typeof input === "number" ? z : input.z ?? 0;
|
|
221
|
-
const x = finiteOrZero(rawX);
|
|
222
|
-
const depth = this.lockDepth ? 0 : finiteOrZero(rawZ);
|
|
223
|
-
const length = Math.hypot(x, depth);
|
|
224
|
-
this.moveInput = length > 1 ? [x / length, depth / length] : [x, depth];
|
|
225
|
-
if (Math.abs(this.moveInput[0]) > 1e-6) {
|
|
226
|
-
this.facing = normalizeFacing(this.moveInput[0]);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
move(input: KinematicMoveInput | number, z = 0): void {
|
|
231
|
-
this.setMoveInput(input, z);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
queueJump(): void {
|
|
235
|
-
this.jumpQueued = true;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
jump(): void {
|
|
239
|
-
this.queueJump();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
queueDash(direction?: number): void {
|
|
243
|
-
this.dashQueued = true;
|
|
244
|
-
this.queuedDashDirection = direction === undefined ? null : normalizeFacing(direction);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
dash(direction?: number): void {
|
|
248
|
-
this.queueDash(direction);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
fastFall(speed = this.maxFallSpeed): void {
|
|
252
|
-
const fallSpeed = finiteNonNegative(Math.abs(speed), "kinematic fastFall speed");
|
|
253
|
-
if (this.grounded || fallSpeed <= 0) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
this.velocity[1] = Math.min(this.velocity[1], -fallSpeed);
|
|
257
|
-
this.pendingEvents.push({
|
|
258
|
-
type: "fast-fall",
|
|
259
|
-
bodyId: this.id,
|
|
260
|
-
position: cloneVec3(this.position),
|
|
261
|
-
velocity: cloneVec3(this.velocity),
|
|
262
|
-
speed: fallSpeed
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
crouch(active = true): void {
|
|
267
|
-
if (this.crouchActive === active) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
this.crouchActive = active;
|
|
271
|
-
this.pendingEvents.push({
|
|
272
|
-
type: "crouch",
|
|
273
|
-
bodyId: this.id,
|
|
274
|
-
active,
|
|
275
|
-
position: cloneVec3(this.position)
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
applyKnockback(impulse: Vec3, options: KnockbackOptions = {}): void {
|
|
280
|
-
validateFiniteVec3(impulse, "knockback impulse");
|
|
281
|
-
if (options.mode === "set") {
|
|
282
|
-
this.velocity = cloneVec3(impulse);
|
|
283
|
-
} else {
|
|
284
|
-
this.velocity = [
|
|
285
|
-
this.velocity[0] + impulse[0],
|
|
286
|
-
this.velocity[1] + impulse[1],
|
|
287
|
-
this.velocity[2] + impulse[2]
|
|
288
|
-
];
|
|
289
|
-
}
|
|
290
|
-
if (options.clearDash ?? true) {
|
|
291
|
-
this.dashTimeRemaining = 0;
|
|
292
|
-
this.dashQueued = false;
|
|
293
|
-
this.queuedDashDirection = null;
|
|
294
|
-
}
|
|
295
|
-
if (options.forceAirborne ?? impulse[1] > 0) {
|
|
296
|
-
this.grounded = false;
|
|
297
|
-
}
|
|
298
|
-
this.pendingEvents.push({
|
|
299
|
-
type: "knockback",
|
|
300
|
-
bodyId: this.id,
|
|
301
|
-
impulse: cloneVec3(impulse),
|
|
302
|
-
velocity: cloneVec3(this.velocity)
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
setPosition(position: Vec3): void {
|
|
307
|
-
validateFiniteVec3(position, "kinematic position");
|
|
308
|
-
this.position = cloneVec3(position);
|
|
309
|
-
this.previousPosition = cloneVec3(position);
|
|
310
|
-
this.grounded = this.position[1] - this.halfExtents[1] <= this.groundY + this.groundSnapDistance;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
translate(delta: Vec3): void {
|
|
314
|
-
validateFiniteVec3(delta, "kinematic translation");
|
|
315
|
-
this.position = [
|
|
316
|
-
this.position[0] + delta[0],
|
|
317
|
-
this.position[1] + delta[1],
|
|
318
|
-
this.position[2] + delta[2]
|
|
319
|
-
];
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
setVelocity(velocity: Vec3): void {
|
|
323
|
-
validateFiniteVec3(velocity, "kinematic velocity");
|
|
324
|
-
this.velocity = cloneVec3(velocity);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
setFacing(facing: number): void {
|
|
328
|
-
this.facing = normalizeFacing(facing);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
setPushbox(volume: CollisionVolume): void {
|
|
332
|
-
if (volume.kind !== "pushbox") {
|
|
333
|
-
throw new Error("KinematicBody pushbox volume must have kind \"pushbox\".");
|
|
334
|
-
}
|
|
335
|
-
this.pushboxVolume = volume;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
resolvedPushbox(): ResolvedCollisionVolume {
|
|
339
|
-
return resolveCollisionVolume(withVolumeOwner(this.pushboxVolume, this.id), this.position, this.facing);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
step(dt: number, options: KinematicStepOptions = {}): readonly KinematicBodyEvent[] {
|
|
343
|
-
if (!Number.isFinite(dt) || dt <= 0) {
|
|
344
|
-
throw new Error("KinematicBody.step dt must be finite and positive.");
|
|
345
|
-
}
|
|
346
|
-
const events = this.drainPendingEvents();
|
|
347
|
-
this.previousPosition = cloneVec3(this.position);
|
|
348
|
-
const wasGrounded = this.grounded;
|
|
349
|
-
const impactSpeed = Math.max(0, -this.velocity[1]);
|
|
350
|
-
const gravity = finiteNonNegative(Math.abs(options.gravity ?? this.gravity), "kinematic step gravity");
|
|
351
|
-
const lockDepth = options.lockDepth ?? this.lockDepth;
|
|
352
|
-
const laneZ = finite(options.laneZ ?? this.laneZ, "kinematic step laneZ");
|
|
353
|
-
if (this.dashCooldownRemaining > 0) {
|
|
354
|
-
this.dashCooldownRemaining = Math.max(0, this.dashCooldownRemaining - dt);
|
|
355
|
-
}
|
|
356
|
-
if (this.dashQueued && this.dashCooldownRemaining <= 0 && this.dashDuration > 0 && this.dashSpeed > 0) {
|
|
357
|
-
const direction = this.queuedDashDirection ?? (Math.abs(this.moveInput[0]) > 1e-6 ? normalizeFacing(this.moveInput[0]) : this.facing);
|
|
358
|
-
this.facing = direction;
|
|
359
|
-
this.dashTimeRemaining = this.dashDuration;
|
|
360
|
-
this.dashCooldownRemaining = this.dashDuration + this.dashCooldown;
|
|
361
|
-
this.velocity[0] = direction * this.dashSpeed;
|
|
362
|
-
if (lockDepth) {
|
|
363
|
-
this.velocity[2] = 0;
|
|
364
|
-
}
|
|
365
|
-
events.push({
|
|
366
|
-
type: "dash",
|
|
367
|
-
bodyId: this.id,
|
|
368
|
-
direction,
|
|
369
|
-
position: cloneVec3(this.position),
|
|
370
|
-
velocity: cloneVec3(this.velocity),
|
|
371
|
-
duration: this.dashDuration
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
this.dashQueued = false;
|
|
375
|
-
this.queuedDashDirection = null;
|
|
376
|
-
if (this.jumpQueued && this.grounded && this.jumpSpeed > 0) {
|
|
377
|
-
this.velocity[1] = this.jumpSpeed;
|
|
378
|
-
this.grounded = false;
|
|
379
|
-
events.push({
|
|
380
|
-
type: "jump",
|
|
381
|
-
bodyId: this.id,
|
|
382
|
-
position: cloneVec3(this.position),
|
|
383
|
-
velocity: cloneVec3(this.velocity)
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
this.jumpQueued = false;
|
|
387
|
-
if (this.dashTimeRemaining > 0) {
|
|
388
|
-
this.dashTimeRemaining = Math.max(0, this.dashTimeRemaining - dt);
|
|
389
|
-
if (lockDepth) {
|
|
390
|
-
this.velocity[2] = 0;
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
const acceleration = this.grounded ? this.acceleration : this.airAcceleration;
|
|
394
|
-
const friction = this.grounded ? this.groundFriction : this.airFriction;
|
|
395
|
-
this.velocity[0] = approachAxis(this.velocity[0], this.moveInput[0], this.maxSpeed, acceleration, friction, dt);
|
|
396
|
-
this.velocity[2] = lockDepth ? 0 : approachAxis(this.velocity[2], this.moveInput[1], this.maxSpeed, acceleration, friction, dt);
|
|
397
|
-
}
|
|
398
|
-
if (this.grounded && this.velocity[1] < 0) {
|
|
399
|
-
this.velocity[1] = 0;
|
|
400
|
-
}
|
|
401
|
-
if (!this.grounded || this.velocity[1] > 0) {
|
|
402
|
-
this.velocity[1] = Math.max(this.velocity[1] - gravity * dt, -this.maxFallSpeed);
|
|
403
|
-
}
|
|
404
|
-
this.position = [
|
|
405
|
-
this.position[0] + this.velocity[0] * dt,
|
|
406
|
-
this.position[1] + this.velocity[1] * dt,
|
|
407
|
-
this.position[2] + this.velocity[2] * dt
|
|
408
|
-
];
|
|
409
|
-
events.push(...this.applyConstraints({ ...options, lockDepth, laneZ }, wasGrounded, impactSpeed));
|
|
410
|
-
return events;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
constrain(options: KinematicStepOptions = {}): readonly KinematicBodyEvent[] {
|
|
414
|
-
return this.applyConstraints(options, this.grounded, Math.max(0, -this.velocity[1]));
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
stopAxis(axis: "x" | "y" | "z"): void {
|
|
418
|
-
const index = axis === "x" ? 0 : axis === "y" ? 1 : 2;
|
|
419
|
-
this.velocity[index] = 0;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
snapshot(): KinematicBodySnapshot {
|
|
423
|
-
return {
|
|
424
|
-
id: this.id,
|
|
425
|
-
position: cloneVec3(this.position),
|
|
426
|
-
previousPosition: cloneVec3(this.previousPosition),
|
|
427
|
-
velocity: cloneVec3(this.velocity),
|
|
428
|
-
facing: this.facing,
|
|
429
|
-
grounded: this.grounded,
|
|
430
|
-
crouching: this.crouchActive,
|
|
431
|
-
moveInput: [this.moveInput[0], 0, this.moveInput[1]],
|
|
432
|
-
halfExtents: cloneVec3(this.halfExtents),
|
|
433
|
-
dashFramesRemaining: this.dashTimeRemaining,
|
|
434
|
-
dashCooldownRemaining: this.dashCooldownRemaining,
|
|
435
|
-
tags: [...this.tags]
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private applyConstraints(options: KinematicStepOptions, wasGrounded: boolean, impactSpeed: number): KinematicBodyEvent[] {
|
|
440
|
-
const events: KinematicBodyEvent[] = [];
|
|
441
|
-
const lockDepth = options.lockDepth ?? this.lockDepth;
|
|
442
|
-
const laneZ = finite(options.laneZ ?? this.laneZ, "kinematic constraint laneZ");
|
|
443
|
-
const laneHalfWidth = options.laneHalfWidth ?? this.laneHalfWidth;
|
|
444
|
-
const bounds = options.bounds ?? this.bounds;
|
|
445
|
-
const axes: ("x" | "y" | "z")[] = [];
|
|
446
|
-
if (lockDepth) {
|
|
447
|
-
if (Math.abs(this.position[2] - laneZ) > 1e-9) {
|
|
448
|
-
axes.push("z");
|
|
449
|
-
}
|
|
450
|
-
this.position[2] = laneZ;
|
|
451
|
-
this.velocity[2] = 0;
|
|
452
|
-
} else if (laneHalfWidth !== undefined) {
|
|
453
|
-
const clamped = clampAxisWithHalfExtent(this.position[2], laneZ - laneHalfWidth, laneZ + laneHalfWidth, this.halfExtents[2]);
|
|
454
|
-
if (clamped !== this.position[2]) {
|
|
455
|
-
this.position[2] = clamped;
|
|
456
|
-
this.velocity[2] = 0;
|
|
457
|
-
axes.push("z");
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
if (bounds) {
|
|
461
|
-
const clampedX = clampAxisWithHalfExtent(this.position[0], bounds.minX, bounds.maxX, this.halfExtents[0]);
|
|
462
|
-
const clampedY = clampAxisWithHalfExtent(this.position[1], bounds.minY, bounds.maxY, this.halfExtents[1]);
|
|
463
|
-
const clampedZ = clampAxisWithHalfExtent(this.position[2], bounds.minZ, bounds.maxZ, this.halfExtents[2]);
|
|
464
|
-
if (clampedX !== this.position[0]) {
|
|
465
|
-
this.position[0] = clampedX;
|
|
466
|
-
this.velocity[0] = 0;
|
|
467
|
-
axes.push("x");
|
|
468
|
-
}
|
|
469
|
-
if (clampedY !== this.position[1]) {
|
|
470
|
-
this.position[1] = clampedY;
|
|
471
|
-
this.velocity[1] = 0;
|
|
472
|
-
axes.push("y");
|
|
473
|
-
}
|
|
474
|
-
if (clampedZ !== this.position[2]) {
|
|
475
|
-
this.position[2] = clampedZ;
|
|
476
|
-
this.velocity[2] = 0;
|
|
477
|
-
axes.push("z");
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
if (axes.length > 0) {
|
|
481
|
-
events.push({
|
|
482
|
-
type: "bounds",
|
|
483
|
-
bodyId: this.id,
|
|
484
|
-
axes: uniqueAxes(axes),
|
|
485
|
-
position: cloneVec3(this.position)
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
const groundY = finite(options.groundY ?? this.groundY, "kinematic constraint groundY");
|
|
489
|
-
const snapDistance = finiteNonNegative(options.groundSnapDistance ?? this.groundSnapDistance, "kinematic constraint groundSnapDistance");
|
|
490
|
-
const bottom = this.position[1] - this.halfExtents[1];
|
|
491
|
-
if (this.velocity[1] <= 0 && bottom <= groundY + snapDistance) {
|
|
492
|
-
const correctedY = groundY + this.halfExtents[1];
|
|
493
|
-
const snapped = Math.abs(this.position[1] - correctedY) > 1e-9;
|
|
494
|
-
this.position[1] = correctedY;
|
|
495
|
-
this.velocity[1] = 0;
|
|
496
|
-
this.grounded = true;
|
|
497
|
-
if (!wasGrounded) {
|
|
498
|
-
events.push({
|
|
499
|
-
type: "land",
|
|
500
|
-
bodyId: this.id,
|
|
501
|
-
position: cloneVec3(this.position),
|
|
502
|
-
impactSpeed
|
|
503
|
-
});
|
|
504
|
-
} else if (snapped) {
|
|
505
|
-
events.push({
|
|
506
|
-
type: "ground-snap",
|
|
507
|
-
bodyId: this.id,
|
|
508
|
-
position: cloneVec3(this.position)
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
} else {
|
|
512
|
-
this.grounded = false;
|
|
513
|
-
}
|
|
514
|
-
return events;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
private drainPendingEvents(): KinematicBodyEvent[] {
|
|
518
|
-
const events = [...this.pendingEvents];
|
|
519
|
-
this.pendingEvents.length = 0;
|
|
520
|
-
return events;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function isDescriptor(value: KinematicBodyId | KinematicBodyDescriptor): value is KinematicBodyDescriptor {
|
|
525
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function validateBodyId(id: KinematicBodyId): void {
|
|
529
|
-
if (typeof id === "number") {
|
|
530
|
-
if (!Number.isFinite(id)) {
|
|
531
|
-
throw new Error("Kinematic body id number must be finite.");
|
|
532
|
-
}
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
if (id.length === 0) {
|
|
536
|
-
throw new Error("Kinematic body id string cannot be empty.");
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
function finite(value: number, name: string): number {
|
|
541
|
-
if (!Number.isFinite(value)) {
|
|
542
|
-
throw new Error(`${name} must be finite.`);
|
|
543
|
-
}
|
|
544
|
-
return value;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function finiteNonNegative(value: number, name: string): number {
|
|
548
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
549
|
-
throw new Error(`${name} must be finite and non-negative.`);
|
|
550
|
-
}
|
|
551
|
-
return value;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function finiteOrZero(value: number): number {
|
|
555
|
-
return Number.isFinite(value) ? value : 0;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function validatePositiveVec3(value: Vec3, name: string): void {
|
|
559
|
-
validateFiniteVec3(value, name);
|
|
560
|
-
if (value[0] <= 0 || value[1] <= 0 || value[2] <= 0) {
|
|
561
|
-
throw new Error(`${name} must contain finite positive values.`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function approachAxis(current: number, input: number, maxSpeed: number, acceleration: number, friction: number, dt: number): number {
|
|
566
|
-
if (Math.abs(input) > 1e-6) {
|
|
567
|
-
return moveToward(current, input * maxSpeed, acceleration * dt);
|
|
568
|
-
}
|
|
569
|
-
return moveToward(current, 0, friction * dt);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function moveToward(current: number, target: number, maxDelta: number): number {
|
|
573
|
-
const delta = target - current;
|
|
574
|
-
if (Math.abs(delta) <= maxDelta) {
|
|
575
|
-
return target;
|
|
576
|
-
}
|
|
577
|
-
return current + Math.sign(delta) * maxDelta;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function clampAxisWithHalfExtent(value: number, min: number | undefined, max: number | undefined, halfExtent: number): number {
|
|
581
|
-
const lower = min === undefined ? -Infinity : min + halfExtent;
|
|
582
|
-
const upper = max === undefined ? Infinity : max - halfExtent;
|
|
583
|
-
if (lower > upper) {
|
|
584
|
-
return (lower + upper) * 0.5;
|
|
585
|
-
}
|
|
586
|
-
return Math.min(upper, Math.max(lower, value));
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function uniqueAxes(axes: readonly ("x" | "y" | "z")[]): readonly ("x" | "y" | "z")[] {
|
|
590
|
-
const result: ("x" | "y" | "z")[] = [];
|
|
591
|
-
for (const axis of axes) {
|
|
592
|
-
if (!result.includes(axis)) {
|
|
593
|
-
result.push(axis);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return result;
|
|
597
|
-
}
|