@aura3d/physics 1.4.0 → 1.4.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.
- 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/RigidBody.ts
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { addVec3, cloneVec3, scaleVec3, validateFiniteVec3, type Vec3, vec3 } from "./Shape.js";
|
|
2
|
-
|
|
3
|
-
export type RigidBodyType = "dynamic" | "static" | "kinematic";
|
|
4
|
-
export type Quat = readonly [number, number, number, number];
|
|
5
|
-
|
|
6
|
-
export type RigidBodyDescriptor = {
|
|
7
|
-
readonly type?: RigidBodyType;
|
|
8
|
-
readonly position?: Vec3;
|
|
9
|
-
readonly rotation?: Quat;
|
|
10
|
-
readonly velocity?: Vec3;
|
|
11
|
-
readonly angularVelocity?: Vec3;
|
|
12
|
-
readonly mass?: number;
|
|
13
|
-
readonly inertia?: Vec3;
|
|
14
|
-
readonly linearDamping?: number;
|
|
15
|
-
readonly angularDamping?: number;
|
|
16
|
-
readonly restitution?: number;
|
|
17
|
-
readonly friction?: number;
|
|
18
|
-
readonly sleeping?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type RigidBodySnapshot = {
|
|
22
|
-
readonly id: number;
|
|
23
|
-
readonly type: RigidBodyType;
|
|
24
|
-
readonly position: Vec3;
|
|
25
|
-
readonly rotation: Quat;
|
|
26
|
-
readonly velocity: Vec3;
|
|
27
|
-
readonly angularVelocity: Vec3;
|
|
28
|
-
readonly mass: number;
|
|
29
|
-
readonly inverseMass: number;
|
|
30
|
-
readonly inverseInertia: Vec3;
|
|
31
|
-
readonly sleeping: boolean;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export class RigidBody {
|
|
35
|
-
readonly id: number;
|
|
36
|
-
readonly type: RigidBodyType;
|
|
37
|
-
readonly mass: number;
|
|
38
|
-
readonly inverseMass: number;
|
|
39
|
-
readonly restitution: number;
|
|
40
|
-
readonly friction: number;
|
|
41
|
-
position: [number, number, number];
|
|
42
|
-
previousPosition: [number, number, number];
|
|
43
|
-
rotation: [number, number, number, number];
|
|
44
|
-
previousRotation: [number, number, number, number];
|
|
45
|
-
velocity: [number, number, number];
|
|
46
|
-
angularVelocity: [number, number, number];
|
|
47
|
-
linearDamping: number;
|
|
48
|
-
angularDamping: number;
|
|
49
|
-
inverseInertia: [number, number, number];
|
|
50
|
-
sleeping: boolean;
|
|
51
|
-
private accumulatedForce: [number, number, number];
|
|
52
|
-
private accumulatedTorque: [number, number, number];
|
|
53
|
-
private sleepTimer = 0;
|
|
54
|
-
|
|
55
|
-
constructor(id: number, descriptor: RigidBodyDescriptor = {}) {
|
|
56
|
-
if (!Number.isInteger(id) || id <= 0) {
|
|
57
|
-
throw new Error("RigidBody id must be a positive integer.");
|
|
58
|
-
}
|
|
59
|
-
this.id = id;
|
|
60
|
-
this.type = descriptor.type ?? "dynamic";
|
|
61
|
-
this.position = cloneVec3(descriptor.position ?? vec3());
|
|
62
|
-
this.previousPosition = cloneVec3(this.position);
|
|
63
|
-
this.rotation = normalizeQuat(descriptor.rotation ?? [0, 0, 0, 1]);
|
|
64
|
-
this.previousRotation = cloneQuat(this.rotation);
|
|
65
|
-
this.velocity = cloneVec3(descriptor.velocity ?? vec3());
|
|
66
|
-
this.angularVelocity = cloneVec3(descriptor.angularVelocity ?? vec3());
|
|
67
|
-
this.linearDamping = descriptor.linearDamping ?? 0;
|
|
68
|
-
this.angularDamping = descriptor.angularDamping ?? 0;
|
|
69
|
-
this.restitution = descriptor.restitution ?? 0;
|
|
70
|
-
this.friction = descriptor.friction ?? 0.5;
|
|
71
|
-
this.sleeping = descriptor.sleeping ?? false;
|
|
72
|
-
validateFiniteVec3(this.position, "body position");
|
|
73
|
-
validateFiniteVec3(this.velocity, "body velocity");
|
|
74
|
-
validateFiniteVec3(this.angularVelocity, "body angularVelocity");
|
|
75
|
-
if (!Number.isFinite(this.linearDamping) || this.linearDamping < 0) {
|
|
76
|
-
throw new Error("linearDamping must be finite and non-negative.");
|
|
77
|
-
}
|
|
78
|
-
if (!Number.isFinite(this.angularDamping) || this.angularDamping < 0) {
|
|
79
|
-
throw new Error("angularDamping must be finite and non-negative.");
|
|
80
|
-
}
|
|
81
|
-
const mass = descriptor.mass ?? 1;
|
|
82
|
-
if (this.type === "dynamic") {
|
|
83
|
-
if (!Number.isFinite(mass) || mass <= 0) {
|
|
84
|
-
throw new Error("dynamic body mass must be a finite positive number.");
|
|
85
|
-
}
|
|
86
|
-
this.mass = mass;
|
|
87
|
-
this.inverseMass = 1 / mass;
|
|
88
|
-
this.inverseInertia = inverseInertia(descriptor.inertia ?? [mass, mass, mass]);
|
|
89
|
-
} else {
|
|
90
|
-
this.mass = Number.POSITIVE_INFINITY;
|
|
91
|
-
this.inverseMass = 0;
|
|
92
|
-
this.inverseInertia = vec3();
|
|
93
|
-
}
|
|
94
|
-
this.accumulatedForce = vec3();
|
|
95
|
-
this.accumulatedTorque = vec3();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
setPosition(position: Vec3): void {
|
|
99
|
-
validateFiniteVec3(position, "body position");
|
|
100
|
-
this.previousPosition = cloneVec3(this.position);
|
|
101
|
-
this.position = cloneVec3(position);
|
|
102
|
-
this.wake();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
setVelocity(velocity: Vec3): void {
|
|
106
|
-
validateFiniteVec3(velocity, "body velocity");
|
|
107
|
-
this.velocity = cloneVec3(velocity);
|
|
108
|
-
this.wake();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
setRotation(rotation: Quat): void {
|
|
112
|
-
this.previousRotation = cloneQuat(this.rotation);
|
|
113
|
-
this.rotation = normalizeQuat(rotation);
|
|
114
|
-
this.wake();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
setAngularVelocity(angularVelocity: Vec3): void {
|
|
118
|
-
validateFiniteVec3(angularVelocity, "body angularVelocity");
|
|
119
|
-
this.angularVelocity = cloneVec3(angularVelocity);
|
|
120
|
-
this.wake();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
applyForce(force: Vec3): void {
|
|
124
|
-
validateFiniteVec3(force, "force");
|
|
125
|
-
if (this.type !== "dynamic") {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
this.accumulatedForce = addVec3(this.accumulatedForce, force);
|
|
129
|
-
this.wake();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
applyTorque(torque: Vec3): void {
|
|
133
|
-
validateFiniteVec3(torque, "torque");
|
|
134
|
-
if (this.type !== "dynamic") {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
this.accumulatedTorque = addVec3(this.accumulatedTorque, torque);
|
|
138
|
-
this.wake();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
applyImpulse(impulse: Vec3): void {
|
|
142
|
-
validateFiniteVec3(impulse, "impulse");
|
|
143
|
-
if (this.type !== "dynamic") {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
this.velocity = addVec3(this.velocity, scaleVec3(impulse, this.inverseMass));
|
|
147
|
-
this.wake();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
applyAngularImpulse(impulse: Vec3): void {
|
|
151
|
-
validateFiniteVec3(impulse, "angular impulse");
|
|
152
|
-
if (this.type !== "dynamic") {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
this.angularVelocity = addVec3(this.angularVelocity, multiplyVec3(impulse, this.inverseInertia));
|
|
156
|
-
this.wake();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
applyImpulseAtPoint(impulse: Vec3, worldPoint: Vec3): void {
|
|
160
|
-
validateFiniteVec3(impulse, "impulse");
|
|
161
|
-
validateFiniteVec3(worldPoint, "impulse point");
|
|
162
|
-
if (this.type !== "dynamic") {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
this.applyImpulse(impulse);
|
|
166
|
-
this.applyAngularImpulse(crossVec3(subVec3(worldPoint, this.position), impulse));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
integrate(dt: number, gravity: Vec3): void {
|
|
170
|
-
if (!Number.isFinite(dt) || dt <= 0) {
|
|
171
|
-
throw new Error("dt must be a finite positive number.");
|
|
172
|
-
}
|
|
173
|
-
if (this.type !== "dynamic" || this.sleeping) {
|
|
174
|
-
this.clearForces();
|
|
175
|
-
this.previousPosition = cloneVec3(this.position);
|
|
176
|
-
this.previousRotation = cloneQuat(this.rotation);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
this.previousPosition = cloneVec3(this.position);
|
|
180
|
-
this.previousRotation = cloneQuat(this.rotation);
|
|
181
|
-
const acceleration = addVec3(gravity, scaleVec3(this.accumulatedForce, this.inverseMass));
|
|
182
|
-
this.velocity = addVec3(this.velocity, scaleVec3(acceleration, dt));
|
|
183
|
-
this.angularVelocity = addVec3(this.angularVelocity, scaleVec3(multiplyVec3(this.accumulatedTorque, this.inverseInertia), dt));
|
|
184
|
-
const dampingFactor = Math.max(0, 1 - this.linearDamping * dt);
|
|
185
|
-
const angularDampingFactor = Math.max(0, 1 - this.angularDamping * dt);
|
|
186
|
-
this.velocity = scaleVec3(this.velocity, dampingFactor);
|
|
187
|
-
this.angularVelocity = scaleVec3(this.angularVelocity, angularDampingFactor);
|
|
188
|
-
this.position = addVec3(this.position, scaleVec3(this.velocity, dt));
|
|
189
|
-
this.rotation = integrateRotation(this.rotation, this.angularVelocity, dt);
|
|
190
|
-
this.clearForces();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
clearForces(): void {
|
|
194
|
-
this.accumulatedForce = vec3();
|
|
195
|
-
this.accumulatedTorque = vec3();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
speedSquared(): number {
|
|
199
|
-
return this.velocity[0] * this.velocity[0] + this.velocity[1] * this.velocity[1] + this.velocity[2] * this.velocity[2] +
|
|
200
|
-
this.angularVelocity[0] * this.angularVelocity[0] + this.angularVelocity[1] * this.angularVelocity[1] + this.angularVelocity[2] * this.angularVelocity[2];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
wake(): void {
|
|
204
|
-
if (this.type !== "dynamic") {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
this.sleeping = false;
|
|
208
|
-
this.sleepTimer = 0;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
sleep(): void {
|
|
212
|
-
if (this.type !== "dynamic") {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
this.velocity = vec3();
|
|
216
|
-
this.angularVelocity = vec3();
|
|
217
|
-
this.clearForces();
|
|
218
|
-
this.sleeping = true;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
resetSleepTimer(): void {
|
|
222
|
-
this.sleepTimer = 0;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
accumulateSleepTime(dt: number): number {
|
|
226
|
-
if (!Number.isFinite(dt) || dt < 0) {
|
|
227
|
-
throw new Error("sleep dt must be finite and non-negative.");
|
|
228
|
-
}
|
|
229
|
-
this.sleepTimer += dt;
|
|
230
|
-
return this.sleepTimer;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
snapshot(): RigidBodySnapshot {
|
|
234
|
-
return {
|
|
235
|
-
id: this.id,
|
|
236
|
-
type: this.type,
|
|
237
|
-
position: cloneVec3(this.position),
|
|
238
|
-
rotation: cloneQuat(this.rotation),
|
|
239
|
-
velocity: cloneVec3(this.velocity),
|
|
240
|
-
angularVelocity: cloneVec3(this.angularVelocity),
|
|
241
|
-
mass: this.mass,
|
|
242
|
-
inverseMass: this.inverseMass,
|
|
243
|
-
inverseInertia: cloneVec3(this.inverseInertia),
|
|
244
|
-
sleeping: this.sleeping
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function cloneQuat(value: Quat): [number, number, number, number] {
|
|
250
|
-
return [value[0], value[1], value[2], value[3]];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function normalizeQuat(value: Quat): [number, number, number, number] {
|
|
254
|
-
if (value.length !== 4 || !Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2]) || !Number.isFinite(value[3])) {
|
|
255
|
-
throw new Error("body rotation must be a finite quaternion.");
|
|
256
|
-
}
|
|
257
|
-
const length = Math.hypot(value[0], value[1], value[2], value[3]);
|
|
258
|
-
if (length <= 1e-9) {
|
|
259
|
-
throw new Error("body rotation quaternion cannot be zero.");
|
|
260
|
-
}
|
|
261
|
-
return [value[0] / length, value[1] / length, value[2] / length, value[3] / length];
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function inverseInertia(inertia: Vec3): [number, number, number] {
|
|
265
|
-
validateFiniteVec3(inertia, "body inertia");
|
|
266
|
-
if (inertia[0] <= 0 || inertia[1] <= 0 || inertia[2] <= 0) {
|
|
267
|
-
throw new Error("dynamic body inertia must contain finite positive principal moments.");
|
|
268
|
-
}
|
|
269
|
-
return [1 / inertia[0], 1 / inertia[1], 1 / inertia[2]];
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function multiplyVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
273
|
-
return [a[0] * b[0], a[1] * b[1], a[2] * b[2]];
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function subVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
277
|
-
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function crossVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
281
|
-
return [
|
|
282
|
-
a[1] * b[2] - a[2] * b[1],
|
|
283
|
-
a[2] * b[0] - a[0] * b[2],
|
|
284
|
-
a[0] * b[1] - a[1] * b[0]
|
|
285
|
-
];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function integrateRotation(rotation: Quat, angularVelocity: Vec3, dt: number): [number, number, number, number] {
|
|
289
|
-
const speed = Math.hypot(angularVelocity[0], angularVelocity[1], angularVelocity[2]);
|
|
290
|
-
if (speed <= 1e-12) {
|
|
291
|
-
return cloneQuat(rotation);
|
|
292
|
-
}
|
|
293
|
-
const halfAngle = speed * dt * 0.5;
|
|
294
|
-
const sinHalf = Math.sin(halfAngle);
|
|
295
|
-
const delta: Quat = [
|
|
296
|
-
angularVelocity[0] / speed * sinHalf,
|
|
297
|
-
angularVelocity[1] / speed * sinHalf,
|
|
298
|
-
angularVelocity[2] / speed * sinHalf,
|
|
299
|
-
Math.cos(halfAngle)
|
|
300
|
-
];
|
|
301
|
-
return normalizeQuat(multiplyQuat(delta, rotation));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function multiplyQuat(a: Quat, b: Quat): [number, number, number, number] {
|
|
305
|
-
return [
|
|
306
|
-
a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
|
|
307
|
-
a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
|
|
308
|
-
a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
|
|
309
|
-
a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2]
|
|
310
|
-
];
|
|
311
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import type { PhysicsWorld } from "./PhysicsWorld.js";
|
|
2
|
-
import type { Quat } from "./RigidBody.js";
|
|
3
|
-
import type { Vec3 } from "./Shape.js";
|
|
4
|
-
|
|
5
|
-
type MutableQuat = [number, number, number, number];
|
|
6
|
-
type QuaternionObject = {
|
|
7
|
-
x: number;
|
|
8
|
-
y: number;
|
|
9
|
-
z: number;
|
|
10
|
-
w: number;
|
|
11
|
-
set?: (x: number, y: number, z: number, w: number) => void;
|
|
12
|
-
toArray?: () => readonly number[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type ScenePhysicsNode = {
|
|
16
|
-
position?: [number, number, number];
|
|
17
|
-
rotation?: MutableQuat;
|
|
18
|
-
quaternion?: MutableQuat | QuaternionObject;
|
|
19
|
-
getWorldPosition?: () => Vec3;
|
|
20
|
-
getWorldQuaternion?: () => Quat;
|
|
21
|
-
getQuaternion?: () => Quat;
|
|
22
|
-
setWorldPosition?: (position: Vec3) => void;
|
|
23
|
-
setWorldQuaternion?: (rotation: Quat) => void;
|
|
24
|
-
setPosition?: (position: Vec3) => void;
|
|
25
|
-
setRotation?: (rotation: Quat) => void;
|
|
26
|
-
setQuaternion?: (rotation: Quat) => void;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type ScenePhysicsBinding = {
|
|
30
|
-
readonly bodyId: number;
|
|
31
|
-
readonly node: ScenePhysicsNode;
|
|
32
|
-
readonly mode?: "dynamic" | "kinematic";
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export class ScenePhysicsBridge {
|
|
36
|
-
private readonly bindings: ScenePhysicsBinding[] = [];
|
|
37
|
-
|
|
38
|
-
bind(binding: ScenePhysicsBinding): void {
|
|
39
|
-
if (this.bindings.some((existing) => existing.bodyId === binding.bodyId)) {
|
|
40
|
-
throw new Error(`Body ${binding.bodyId} is already bound to a scene node.`);
|
|
41
|
-
}
|
|
42
|
-
this.bindings.push(binding);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
unbind(bodyId: number): void {
|
|
46
|
-
const index = this.bindings.findIndex((binding) => binding.bodyId === bodyId);
|
|
47
|
-
if (index >= 0) {
|
|
48
|
-
this.bindings.splice(index, 1);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
pushKinematic(world: Pick<PhysicsWorld, "getBody">): void {
|
|
53
|
-
for (const binding of this.bindings) {
|
|
54
|
-
if (binding.mode !== "kinematic") {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const body = world.getBody(binding.bodyId);
|
|
58
|
-
const position = readNodePosition(binding.node);
|
|
59
|
-
if (body && position) {
|
|
60
|
-
body.setPosition(position);
|
|
61
|
-
}
|
|
62
|
-
const rotation = readNodeRotation(binding.node);
|
|
63
|
-
if (body && rotation) {
|
|
64
|
-
body.setRotation(rotation);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
pullDynamic(world: Pick<PhysicsWorld, "getBody">, alpha = 1): void {
|
|
70
|
-
const interpolationAlpha = clamp01(alpha);
|
|
71
|
-
for (const binding of this.bindings) {
|
|
72
|
-
if (binding.mode === "kinematic") {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const body = world.getBody(binding.bodyId);
|
|
76
|
-
if (body) {
|
|
77
|
-
writeNodePosition(binding.node, interpolatePosition(body.previousPosition, body.position, interpolationAlpha));
|
|
78
|
-
writeNodeRotation(binding.node, interpolateRotation(body.previousRotation, body.rotation, interpolationAlpha));
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
update(world: Pick<PhysicsWorld, "getBody">, alpha = 1): void {
|
|
84
|
-
this.pushKinematic(world);
|
|
85
|
-
this.pullDynamic(world, alpha);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function interpolatePosition(previous: Vec3, current: Vec3, alpha: number): [number, number, number] {
|
|
90
|
-
const t = clamp01(alpha);
|
|
91
|
-
return [
|
|
92
|
-
previous[0] + (current[0] - previous[0]) * t,
|
|
93
|
-
previous[1] + (current[1] - previous[1]) * t,
|
|
94
|
-
previous[2] + (current[2] - previous[2]) * t
|
|
95
|
-
];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function interpolateRotation(previous: Quat, current: Quat, alpha: number): MutableQuat {
|
|
99
|
-
const t = clamp01(alpha);
|
|
100
|
-
const currentSign = dotQuat(previous, current) < 0 ? -1 : 1;
|
|
101
|
-
const x = previous[0] + (current[0] * currentSign - previous[0]) * t;
|
|
102
|
-
const y = previous[1] + (current[1] * currentSign - previous[1]) * t;
|
|
103
|
-
const z = previous[2] + (current[2] * currentSign - previous[2]) * t;
|
|
104
|
-
const w = previous[3] + (current[3] * currentSign - previous[3]) * t;
|
|
105
|
-
return normalizeRotation([x, y, z, w]);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function clamp01(value: number): number {
|
|
109
|
-
if (!Number.isFinite(value)) {
|
|
110
|
-
throw new Error("Physics interpolation alpha must be finite.");
|
|
111
|
-
}
|
|
112
|
-
return Math.min(1, Math.max(0, value));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function dotQuat(a: Quat, b: Quat): number {
|
|
116
|
-
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function readNodePosition(node: ScenePhysicsNode): Vec3 | undefined {
|
|
120
|
-
if (node.getWorldPosition) {
|
|
121
|
-
return node.getWorldPosition();
|
|
122
|
-
}
|
|
123
|
-
return node.position;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function readNodeRotation(node: ScenePhysicsNode): Quat | undefined {
|
|
127
|
-
if (node.getWorldQuaternion) {
|
|
128
|
-
return normalizeRotation(node.getWorldQuaternion());
|
|
129
|
-
}
|
|
130
|
-
if (node.getQuaternion) {
|
|
131
|
-
return normalizeRotation(node.getQuaternion());
|
|
132
|
-
}
|
|
133
|
-
if (Array.isArray(node.rotation)) {
|
|
134
|
-
return normalizeRotation(node.rotation);
|
|
135
|
-
}
|
|
136
|
-
if (Array.isArray(node.quaternion)) {
|
|
137
|
-
return normalizeRotation(node.quaternion);
|
|
138
|
-
}
|
|
139
|
-
if (node.quaternion?.toArray) {
|
|
140
|
-
return normalizeRotation(node.quaternion.toArray());
|
|
141
|
-
}
|
|
142
|
-
if (node.quaternion) {
|
|
143
|
-
return normalizeRotation([node.quaternion.x, node.quaternion.y, node.quaternion.z, node.quaternion.w]);
|
|
144
|
-
}
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function writeNodePosition(node: ScenePhysicsNode, position: Vec3): void {
|
|
149
|
-
if (node.setWorldPosition) {
|
|
150
|
-
node.setWorldPosition(position);
|
|
151
|
-
} else if (node.setPosition) {
|
|
152
|
-
node.setPosition(position);
|
|
153
|
-
} else if (node.position) {
|
|
154
|
-
node.position[0] = position[0];
|
|
155
|
-
node.position[1] = position[1];
|
|
156
|
-
node.position[2] = position[2];
|
|
157
|
-
} else {
|
|
158
|
-
node.position = [position[0], position[1], position[2]];
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function writeNodeRotation(node: ScenePhysicsNode, rotation: Quat): void {
|
|
163
|
-
const normalized = normalizeRotation(rotation);
|
|
164
|
-
if (node.setWorldQuaternion) {
|
|
165
|
-
node.setWorldQuaternion(normalized);
|
|
166
|
-
} else if (node.setQuaternion) {
|
|
167
|
-
node.setQuaternion(normalized);
|
|
168
|
-
} else if (node.setRotation) {
|
|
169
|
-
node.setRotation(normalized);
|
|
170
|
-
} else if (Array.isArray(node.rotation)) {
|
|
171
|
-
node.rotation[0] = normalized[0];
|
|
172
|
-
node.rotation[1] = normalized[1];
|
|
173
|
-
node.rotation[2] = normalized[2];
|
|
174
|
-
node.rotation[3] = normalized[3];
|
|
175
|
-
} else if (Array.isArray(node.quaternion)) {
|
|
176
|
-
node.quaternion[0] = normalized[0];
|
|
177
|
-
node.quaternion[1] = normalized[1];
|
|
178
|
-
node.quaternion[2] = normalized[2];
|
|
179
|
-
node.quaternion[3] = normalized[3];
|
|
180
|
-
} else if (node.quaternion?.set) {
|
|
181
|
-
node.quaternion.set(normalized[0], normalized[1], normalized[2], normalized[3]);
|
|
182
|
-
} else if (node.quaternion) {
|
|
183
|
-
node.quaternion.x = normalized[0];
|
|
184
|
-
node.quaternion.y = normalized[1];
|
|
185
|
-
node.quaternion.z = normalized[2];
|
|
186
|
-
node.quaternion.w = normalized[3];
|
|
187
|
-
} else {
|
|
188
|
-
node.rotation = [normalized[0], normalized[1], normalized[2], normalized[3]];
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function normalizeRotation(rotation: readonly number[]): MutableQuat {
|
|
193
|
-
if (
|
|
194
|
-
rotation.length !== 4 ||
|
|
195
|
-
!Number.isFinite(rotation[0]) ||
|
|
196
|
-
!Number.isFinite(rotation[1]) ||
|
|
197
|
-
!Number.isFinite(rotation[2]) ||
|
|
198
|
-
!Number.isFinite(rotation[3])
|
|
199
|
-
) {
|
|
200
|
-
throw new Error("Scene physics node rotation must be a finite quaternion.");
|
|
201
|
-
}
|
|
202
|
-
const length = Math.hypot(rotation[0], rotation[1], rotation[2], rotation[3]);
|
|
203
|
-
if (length <= 1e-9) {
|
|
204
|
-
throw new Error("Scene physics node rotation quaternion cannot be zero.");
|
|
205
|
-
}
|
|
206
|
-
return [rotation[0] / length, rotation[1] / length, rotation[2] / length, rotation[3] / length];
|
|
207
|
-
}
|
package/src/Shape.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
export type Vec3 = readonly [number, number, number];
|
|
2
|
-
|
|
3
|
-
export type BoxShape = {
|
|
4
|
-
readonly kind: "box";
|
|
5
|
-
readonly halfExtents: Vec3;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type SphereShape = {
|
|
9
|
-
readonly kind: "sphere";
|
|
10
|
-
readonly radius: number;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type CapsuleShape = {
|
|
14
|
-
readonly kind: "capsule";
|
|
15
|
-
readonly radius: number;
|
|
16
|
-
readonly halfHeight: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type PlaneShape = {
|
|
20
|
-
readonly kind: "plane";
|
|
21
|
-
readonly normal: Vec3;
|
|
22
|
-
readonly constant: number;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type MeshShape = {
|
|
26
|
-
readonly kind: "mesh";
|
|
27
|
-
readonly vertices: readonly Vec3[];
|
|
28
|
-
readonly indices: readonly number[];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type PhysicsShape = BoxShape | SphereShape | CapsuleShape | PlaneShape | MeshShape;
|
|
32
|
-
|
|
33
|
-
export type Bounds = {
|
|
34
|
-
readonly min: Vec3;
|
|
35
|
-
readonly max: Vec3;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const EPSILON = 1e-9;
|
|
39
|
-
|
|
40
|
-
export function vec3(x = 0, y = 0, z = 0): [number, number, number] {
|
|
41
|
-
return [x, y, z];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function cloneVec3(value: Vec3): [number, number, number] {
|
|
45
|
-
return [value[0], value[1], value[2]];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function addVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
49
|
-
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function subVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
53
|
-
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function scaleVec3(value: Vec3, scalar: number): [number, number, number] {
|
|
57
|
-
return [value[0] * scalar, value[1] * scalar, value[2] * scalar];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function dotVec3(a: Vec3, b: Vec3): number {
|
|
61
|
-
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function lengthVec3(value: Vec3): number {
|
|
65
|
-
return Math.hypot(value[0], value[1], value[2]);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function normalizeVec3(value: Vec3): [number, number, number] {
|
|
69
|
-
const length = lengthVec3(value);
|
|
70
|
-
if (length <= EPSILON) {
|
|
71
|
-
throw new Error("Cannot normalize a zero-length vector.");
|
|
72
|
-
}
|
|
73
|
-
return [value[0] / length, value[1] / length, value[2] / length];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function minVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
77
|
-
return [Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.min(a[2], b[2])];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function maxVec3(a: Vec3, b: Vec3): [number, number, number] {
|
|
81
|
-
return [Math.max(a[0], b[0]), Math.max(a[1], b[1]), Math.max(a[2], b[2])];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function validateFiniteVec3(value: Vec3, name: string): void {
|
|
85
|
-
if (value.length !== 3 || !Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) {
|
|
86
|
-
throw new Error(`${name} must be a finite 3D vector.`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function validatePositive(value: number, name: string): void {
|
|
91
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
92
|
-
throw new Error(`${name} must be a finite positive number.`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export class Shape {
|
|
97
|
-
static box(x: number, y: number, z: number): BoxShape {
|
|
98
|
-
validatePositive(x, "box x half extent");
|
|
99
|
-
validatePositive(y, "box y half extent");
|
|
100
|
-
validatePositive(z, "box z half extent");
|
|
101
|
-
return { kind: "box", halfExtents: [x, y, z] };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
static sphere(radius: number): SphereShape {
|
|
105
|
-
validatePositive(radius, "sphere radius");
|
|
106
|
-
return { kind: "sphere", radius };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
static capsule(radius: number, halfHeight: number): CapsuleShape {
|
|
110
|
-
validatePositive(radius, "capsule radius");
|
|
111
|
-
validatePositive(halfHeight, "capsule half height");
|
|
112
|
-
return { kind: "capsule", radius, halfHeight };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
static plane(normal: Vec3 = [0, 1, 0], constant = 0): PlaneShape {
|
|
116
|
-
validateFiniteVec3(normal, "plane normal");
|
|
117
|
-
if (!Number.isFinite(constant)) {
|
|
118
|
-
throw new Error("plane constant must be finite.");
|
|
119
|
-
}
|
|
120
|
-
return { kind: "plane", normal: normalizeVec3(normal), constant };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
static mesh(vertices: readonly Vec3[], indices: readonly number[]): MeshShape {
|
|
124
|
-
if (vertices.length < 3) {
|
|
125
|
-
throw new Error("mesh shape requires at least three vertices.");
|
|
126
|
-
}
|
|
127
|
-
for (const [index, vertex] of vertices.entries()) {
|
|
128
|
-
validateFiniteVec3(vertex, `mesh vertex ${index}`);
|
|
129
|
-
}
|
|
130
|
-
if (indices.length === 0 || indices.length % 3 !== 0) {
|
|
131
|
-
throw new Error("mesh shape indices must contain one or more complete triangles.");
|
|
132
|
-
}
|
|
133
|
-
for (const index of indices) {
|
|
134
|
-
if (!Number.isInteger(index) || index < 0 || index >= vertices.length) {
|
|
135
|
-
throw new Error(`mesh shape index ${index} is out of range.`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
kind: "mesh",
|
|
140
|
-
vertices: vertices.map(cloneVec3),
|
|
141
|
-
indices: [...indices]
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
static bounds(shape: PhysicsShape, position: Vec3): Bounds {
|
|
146
|
-
validateFiniteVec3(position, "shape position");
|
|
147
|
-
switch (shape.kind) {
|
|
148
|
-
case "box":
|
|
149
|
-
return {
|
|
150
|
-
min: subVec3(position, shape.halfExtents),
|
|
151
|
-
max: addVec3(position, shape.halfExtents)
|
|
152
|
-
};
|
|
153
|
-
case "sphere":
|
|
154
|
-
return {
|
|
155
|
-
min: [position[0] - shape.radius, position[1] - shape.radius, position[2] - shape.radius],
|
|
156
|
-
max: [position[0] + shape.radius, position[1] + shape.radius, position[2] + shape.radius]
|
|
157
|
-
};
|
|
158
|
-
case "capsule": {
|
|
159
|
-
const yRadius = shape.radius + shape.halfHeight;
|
|
160
|
-
return {
|
|
161
|
-
min: [position[0] - shape.radius, position[1] - yRadius, position[2] - shape.radius],
|
|
162
|
-
max: [position[0] + shape.radius, position[1] + yRadius, position[2] + shape.radius]
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
case "plane":
|
|
166
|
-
return {
|
|
167
|
-
min: [-Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER],
|
|
168
|
-
max: [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]
|
|
169
|
-
};
|
|
170
|
-
case "mesh": {
|
|
171
|
-
let min = addVec3(position, shape.vertices[0]!);
|
|
172
|
-
let max = cloneVec3(min);
|
|
173
|
-
for (let index = 1; index < shape.vertices.length; index += 1) {
|
|
174
|
-
const vertex = addVec3(position, shape.vertices[index]!);
|
|
175
|
-
min = minVec3(min, vertex);
|
|
176
|
-
max = maxVec3(max, vertex);
|
|
177
|
-
}
|
|
178
|
-
return { min, max };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function rotateVec3ByQuat(v: Vec3, q: readonly [number, number, number, number]): [number, number, number] {
|
|
185
|
-
// q * v * q^-1 for rotating a vector by a unit quaternion
|
|
186
|
-
const x = q[0], y = q[1], z = q[2], w = q[3];
|
|
187
|
-
const tx = 2 * (y * v[2] - z * v[1]);
|
|
188
|
-
const ty = 2 * (z * v[0] - x * v[2]);
|
|
189
|
-
const tz = 2 * (x * v[1] - y * v[0]);
|
|
190
|
-
return [
|
|
191
|
-
v[0] + w * tx + (y * tz - z * ty),
|
|
192
|
-
v[1] + w * ty + (z * tx - x * tz),
|
|
193
|
-
v[2] + w * tz + (x * ty - y * tx)
|
|
194
|
-
];
|
|
195
|
-
}
|