@aura3d/physics 1.5.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ArcadeCharacterController.d.ts +53 -0
- package/dist/ArcadeCharacterController.d.ts.map +1 -0
- package/dist/ArcadeCharacterController.js +98 -0
- package/dist/ArcadeCharacterController.js.map +1 -0
- package/dist/ArcadeVehicleTelemetry.d.ts +162 -0
- package/dist/ArcadeVehicleTelemetry.d.ts.map +1 -0
- package/dist/ArcadeVehicleTelemetry.js +363 -0
- package/dist/ArcadeVehicleTelemetry.js.map +1 -0
- package/dist/CharacterController.d.ts +52 -0
- package/dist/CharacterController.d.ts.map +1 -1
- package/dist/CharacterController.js +380 -7
- package/dist/CharacterController.js.map +1 -1
- package/dist/Constraint.d.ts +20 -4
- package/dist/Constraint.d.ts.map +1 -1
- package/dist/Constraint.js +34 -60
- package/dist/Constraint.js.map +1 -1
- package/dist/FightingCharacterController.d.ts +32 -0
- package/dist/FightingCharacterController.d.ts.map +1 -0
- package/dist/FightingCharacterController.js +76 -0
- package/dist/FightingCharacterController.js.map +1 -0
- package/dist/MeshBVH.d.ts +72 -0
- package/dist/MeshBVH.d.ts.map +1 -0
- package/dist/MeshBVH.js +244 -0
- package/dist/MeshBVH.js.map +1 -0
- package/dist/PathFollowDriver.d.ts +162 -0
- package/dist/PathFollowDriver.d.ts.map +1 -0
- package/dist/PathFollowDriver.js +163 -0
- package/dist/PathFollowDriver.js.map +1 -0
- package/dist/PhysicsDebugDraw.d.ts +40 -1
- package/dist/PhysicsDebugDraw.d.ts.map +1 -1
- package/dist/PhysicsDebugDraw.js +87 -17
- package/dist/PhysicsDebugDraw.js.map +1 -1
- package/dist/PhysicsWorld.d.ts +37 -18
- package/dist/PhysicsWorld.d.ts.map +1 -1
- package/dist/PhysicsWorld.js +231 -630
- package/dist/PhysicsWorld.js.map +1 -1
- package/dist/RacingLineProfile.d.ts +116 -0
- package/dist/RacingLineProfile.d.ts.map +1 -0
- package/dist/RacingLineProfile.js +181 -0
- package/dist/RacingLineProfile.js.map +1 -0
- package/dist/Raycast.d.ts +11 -0
- package/dist/Raycast.d.ts.map +1 -1
- package/dist/Raycast.js +62 -8
- package/dist/Raycast.js.map +1 -1
- package/dist/RigidBody.d.ts +16 -9
- package/dist/RigidBody.d.ts.map +1 -1
- package/dist/RigidBody.js +23 -75
- package/dist/RigidBody.js.map +1 -1
- package/dist/SurfaceQuery.d.ts +72 -0
- package/dist/SurfaceQuery.d.ts.map +1 -0
- package/dist/SurfaceQuery.js +89 -0
- package/dist/SurfaceQuery.js.map +1 -0
- package/dist/VehicleDynamics.d.ts +10 -0
- package/dist/VehicleDynamics.d.ts.map +1 -1
- package/dist/VehicleDynamics.js +31 -3
- package/dist/VehicleDynamics.js.map +1 -1
- package/dist/VehicleMotion.d.ts +125 -0
- package/dist/VehicleMotion.d.ts.map +1 -0
- package/dist/VehicleMotion.js +373 -0
- package/dist/VehicleMotion.js.map +1 -0
- package/dist/index.d.ts +62 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -17
- package/dist/index.js.map +1 -1
- package/dist/solverless-entry.d.ts +41 -0
- package/dist/solverless-entry.d.ts.map +1 -0
- package/dist/solverless-entry.js +40 -0
- package/dist/solverless-entry.js.map +1 -0
- package/dist/solverless.d.ts +37 -0
- package/dist/solverless.d.ts.map +1 -0
- package/dist/solverless.js +36 -0
- package/dist/solverless.js.map +1 -0
- package/dist/world-entry.d.ts +31 -0
- package/dist/world-entry.d.ts.map +1 -0
- package/dist/world-entry.js +30 -0
- package/dist/world-entry.js.map +1 -0
- package/dist/world.d.ts +18 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +17 -0
- package/dist/world.js.map +1 -0
- package/package.json +10 -2
package/dist/PhysicsWorld.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRapierPhysicsSync } from "@aura3d/physics-rapier";
|
|
2
2
|
import { Collider } from "./Collider.js";
|
|
3
3
|
import { CollisionEventQueue } from "./CollisionEvents.js";
|
|
4
4
|
import { Constraint } from "./Constraint.js";
|
|
5
|
-
import { buildNativeNarrowPhaseContact } from "./NarrowPhase.js";
|
|
6
5
|
import { raycastCollider, sphereCastCollider } from "./Raycast.js";
|
|
7
6
|
import { RigidBody } from "./RigidBody.js";
|
|
8
|
-
import { cloneVec3, dotVec3, lengthVec3, normalizeVec3, scaleVec3, subVec3 } from "./Shape.js";
|
|
7
|
+
import { cloneVec3, dotVec3, lengthVec3, normalizeVec3, scaleVec3, subVec3, validateFiniteVec3 } from "./Shape.js";
|
|
9
8
|
import { timeOfImpact } from "./TimeOfImpact.js";
|
|
10
9
|
export class PhysicsWorld {
|
|
11
10
|
gravity;
|
|
12
11
|
fixedDelta;
|
|
13
12
|
solverIterations;
|
|
14
13
|
enableSleeping;
|
|
15
|
-
sleepVelocityThreshold;
|
|
16
|
-
sleepDelay;
|
|
17
14
|
continuousCollision;
|
|
18
15
|
bodiesById = new Map();
|
|
19
16
|
collidersById = new Map();
|
|
@@ -22,8 +19,10 @@ export class PhysicsWorld {
|
|
|
22
19
|
eventQueue = new CollisionEventQueue();
|
|
23
20
|
requestedBackend;
|
|
24
21
|
backendSelection;
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
rapierWorld;
|
|
23
|
+
rapierBodiesByAuraId = new Map();
|
|
24
|
+
rapierCollidersByAuraId = new Map();
|
|
25
|
+
rapierJointsByConstraint = new Map();
|
|
27
26
|
nextBodyId = 1;
|
|
28
27
|
nextColliderId = 1;
|
|
29
28
|
lastEvents = [];
|
|
@@ -35,10 +34,9 @@ export class PhysicsWorld {
|
|
|
35
34
|
this.requestedBackend = descriptor.backend ?? "auto";
|
|
36
35
|
this.gravity = cloneVec3(descriptor.gravity ?? [0, -9.81, 0]);
|
|
37
36
|
this.fixedDelta = descriptor.fixedDelta ?? 1 / 60;
|
|
38
|
-
|
|
37
|
+
// Keep the established public default while configuring Rapier's native solver.
|
|
38
|
+
this.solverIterations = descriptor.solverIterations ?? 10;
|
|
39
39
|
this.enableSleeping = descriptor.enableSleeping ?? true;
|
|
40
|
-
this.sleepVelocityThreshold = descriptor.sleepVelocityThreshold ?? 0.02;
|
|
41
|
-
this.sleepDelay = descriptor.sleepDelay ?? 0.5;
|
|
42
40
|
this.continuousCollision = normalizeContinuousCollisionDescriptor(descriptor.continuousCollision);
|
|
43
41
|
if (!Number.isFinite(this.fixedDelta) || this.fixedDelta <= 0) {
|
|
44
42
|
throw new Error("fixedDelta must be a finite positive number.");
|
|
@@ -46,39 +44,51 @@ export class PhysicsWorld {
|
|
|
46
44
|
if (!Number.isInteger(this.solverIterations) || this.solverIterations <= 0) {
|
|
47
45
|
throw new Error("solverIterations must be a positive integer.");
|
|
48
46
|
}
|
|
49
|
-
if (!Number.isFinite(
|
|
47
|
+
if (descriptor.sleepVelocityThreshold !== undefined && (!Number.isFinite(descriptor.sleepVelocityThreshold) || descriptor.sleepVelocityThreshold < 0)) {
|
|
50
48
|
throw new Error("sleepVelocityThreshold must be finite and non-negative.");
|
|
51
49
|
}
|
|
52
|
-
if (!Number.isFinite(
|
|
50
|
+
if (descriptor.sleepDelay !== undefined && (!Number.isFinite(descriptor.sleepDelay) || descriptor.sleepDelay < 0)) {
|
|
53
51
|
throw new Error("sleepDelay must be finite and non-negative.");
|
|
54
52
|
}
|
|
53
|
+
if (this.requestedBackend !== "auto" && this.requestedBackend !== "rapier") {
|
|
54
|
+
// Reached from JavaScript callers and from code compiled against 1.5.x, where
|
|
55
|
+
// `"aura-js"` was a selectable backend. Failing loudly is the point: the previous
|
|
56
|
+
// behaviour of that string was "quietly run a different solver".
|
|
57
|
+
throw new Error(`Unknown physics backend '${String(this.requestedBackend)}'. Aura3D 2.0 has one ` +
|
|
58
|
+
"production solver, 'rapier'; the legacy 'aura-js' and 'cannon-es' backends were removed " +
|
|
59
|
+
"(see docs/architecture/physics-backend-decision.md). Omit `backend` or pass " +
|
|
60
|
+
"'rapier'.");
|
|
61
|
+
}
|
|
55
62
|
this.backendSelection = {
|
|
56
63
|
requested: this.requestedBackend,
|
|
57
|
-
active:
|
|
64
|
+
active: "rapier",
|
|
58
65
|
deterministic: true,
|
|
59
|
-
jsFallbackAvailable: true,
|
|
60
66
|
continuousCollision: this.continuousCollisionSelection(true)
|
|
61
67
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
gravity: toCannonVec3(this.gravity),
|
|
65
|
-
allowSleep: this.enableSleeping
|
|
66
|
-
});
|
|
67
|
-
this.cannonWorld.solver.iterations = this.solverIterations;
|
|
68
|
-
this.cannonWorld.defaultContactMaterial.friction = 0.5;
|
|
69
|
-
this.cannonWorld.defaultContactMaterial.restitution = 0;
|
|
70
|
-
}
|
|
68
|
+
this.rapierWorld = createRapierPhysicsSync({ gravity: this.gravity });
|
|
69
|
+
this.rapierWorld.unsafeRapierWorld().integrationParameters.numSolverIterations = this.solverIterations;
|
|
71
70
|
}
|
|
72
71
|
createRigidBody(descriptor = {}) {
|
|
73
72
|
const body = new RigidBody(this.nextBodyId, descriptor);
|
|
74
73
|
this.nextBodyId += 1;
|
|
75
74
|
this.bodiesById.set(body.id, body);
|
|
76
75
|
this.bodyColliders.set(body.id, new Set());
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
const rapierBody = this.rapierWorld.createRigidBody({
|
|
77
|
+
type: body.type === "static" ? "fixed" : body.type === "kinematic" ? "kinematic-position" : "dynamic",
|
|
78
|
+
position: body.position,
|
|
79
|
+
rotation: body.rotation,
|
|
80
|
+
linearVelocity: body.velocity,
|
|
81
|
+
angularVelocity: body.angularVelocity,
|
|
82
|
+
linearDamping: body.linearDamping,
|
|
83
|
+
angularDamping: body.angularDamping,
|
|
84
|
+
ccd: body.type === "dynamic",
|
|
85
|
+
canSleep: this.enableSleeping,
|
|
86
|
+
...(body.type === "dynamic" ? { mass: body.mass } : {}),
|
|
87
|
+
...(body.declaredInertia ? { principalAngularInertia: body.declaredInertia } : {})
|
|
88
|
+
});
|
|
89
|
+
if (body.sleeping)
|
|
90
|
+
rapierBody.sleep();
|
|
91
|
+
this.rapierBodiesByAuraId.set(body.id, rapierBody);
|
|
82
92
|
return body;
|
|
83
93
|
}
|
|
84
94
|
createCollider(body, descriptor) {
|
|
@@ -90,7 +100,18 @@ export class PhysicsWorld {
|
|
|
90
100
|
this.nextColliderId += 1;
|
|
91
101
|
this.collidersById.set(collider.id, collider);
|
|
92
102
|
this.bodyColliders.get(bodyId)?.add(collider.id);
|
|
93
|
-
this.
|
|
103
|
+
const rapierBody = this.rapierBodiesByAuraId.get(bodyId);
|
|
104
|
+
if (!rapierBody)
|
|
105
|
+
throw new Error(`Missing Rapier body for Aura body ${bodyId}.`);
|
|
106
|
+
const rapierCollider = this.rapierWorld.createCollider(rapierBody, {
|
|
107
|
+
shape: collider.shape,
|
|
108
|
+
density: 0,
|
|
109
|
+
friction: collider.material.friction,
|
|
110
|
+
restitution: collider.material.restitution,
|
|
111
|
+
sensor: collider.sensor,
|
|
112
|
+
collisionGroups: encodeRapierCollisionGroups(collider.filter.layer, collider.filter.mask)
|
|
113
|
+
});
|
|
114
|
+
this.rapierCollidersByAuraId.set(collider.id, rapierCollider);
|
|
94
115
|
return collider;
|
|
95
116
|
}
|
|
96
117
|
createConstraint(descriptor) {
|
|
@@ -99,8 +120,23 @@ export class PhysicsWorld {
|
|
|
99
120
|
}
|
|
100
121
|
const constraint = new Constraint(descriptor);
|
|
101
122
|
this.constraintsList.push(constraint);
|
|
123
|
+
this.rapierJointsByConstraint.set(constraint, this.createRapierJoint(constraint));
|
|
102
124
|
return constraint;
|
|
103
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Change world gravity after construction.
|
|
128
|
+
*
|
|
129
|
+
* Needed for anything that flips or scales gravity at runtime — a low-gravity level, a
|
|
130
|
+
* zero-g section, a game that inverts it as a mechanic. Without a setter the only way to
|
|
131
|
+
* change it was to rebuild the world and lose every body.
|
|
132
|
+
*/
|
|
133
|
+
setGravity(gravity) {
|
|
134
|
+
validateFiniteVec3(gravity, "world gravity");
|
|
135
|
+
this.gravity[0] = gravity[0];
|
|
136
|
+
this.gravity[1] = gravity[1];
|
|
137
|
+
this.gravity[2] = gravity[2];
|
|
138
|
+
this.rapierWorld.setGravity(this.gravity);
|
|
139
|
+
}
|
|
104
140
|
getBody(id) {
|
|
105
141
|
return this.bodiesById.get(id);
|
|
106
142
|
}
|
|
@@ -129,14 +165,18 @@ export class PhysicsWorld {
|
|
|
129
165
|
}
|
|
130
166
|
this.bodyColliders.delete(id);
|
|
131
167
|
this.bodiesById.delete(id);
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
this.
|
|
135
|
-
this.
|
|
168
|
+
const rapierBody = this.rapierBodiesByAuraId.get(id);
|
|
169
|
+
if (rapierBody) {
|
|
170
|
+
this.rapierWorld.removeBody(rapierBody);
|
|
171
|
+
this.rapierBodiesByAuraId.delete(id);
|
|
136
172
|
}
|
|
137
173
|
for (let index = this.constraintsList.length - 1; index >= 0; index -= 1) {
|
|
138
174
|
const constraint = this.constraintsList[index];
|
|
139
175
|
if (constraint.bodyA.id === id || constraint.bodyB.id === id) {
|
|
176
|
+
const joint = this.rapierJointsByConstraint.get(constraint);
|
|
177
|
+
if (joint)
|
|
178
|
+
joint.remove();
|
|
179
|
+
this.rapierJointsByConstraint.delete(constraint);
|
|
140
180
|
this.constraintsList.splice(index, 1);
|
|
141
181
|
}
|
|
142
182
|
}
|
|
@@ -148,6 +188,10 @@ export class PhysicsWorld {
|
|
|
148
188
|
}
|
|
149
189
|
this.bodyColliders.get(collider.bodyId)?.delete(id);
|
|
150
190
|
this.collidersById.delete(id);
|
|
191
|
+
const rapierCollider = this.rapierCollidersByAuraId.get(id);
|
|
192
|
+
if (rapierCollider)
|
|
193
|
+
rapierCollider.remove();
|
|
194
|
+
this.rapierCollidersByAuraId.delete(id);
|
|
151
195
|
const removalEvents = this.eventQueue.removeCollider(id);
|
|
152
196
|
if (removalEvents.length > 0) {
|
|
153
197
|
this.lastEvents = [...this.lastEvents, ...removalEvents].sort((a, b) => a.pairKey.localeCompare(b.pairKey) || a.type.localeCompare(b.type));
|
|
@@ -157,51 +201,7 @@ export class PhysicsWorld {
|
|
|
157
201
|
if (!Number.isFinite(dt) || dt <= 0) {
|
|
158
202
|
throw new Error("PhysicsWorld.step dt must be finite and positive.");
|
|
159
203
|
}
|
|
160
|
-
|
|
161
|
-
return this.stepCannon(dt);
|
|
162
|
-
}
|
|
163
|
-
const continuousStep = this.planContinuousCollisionStep(dt);
|
|
164
|
-
this.lastContinuousCollisionStep = continuousStep;
|
|
165
|
-
this.assertContinuousCollisionPlan(continuousStep);
|
|
166
|
-
const subDelta = dt / continuousStep.subSteps;
|
|
167
|
-
const outerStepTransforms = new Map(this.bodies().map((body) => [
|
|
168
|
-
body.id,
|
|
169
|
-
{
|
|
170
|
-
position: cloneVec3(body.position),
|
|
171
|
-
rotation: [...body.rotation]
|
|
172
|
-
}
|
|
173
|
-
]));
|
|
174
|
-
let contacts = [];
|
|
175
|
-
for (let subStep = 0; subStep < continuousStep.subSteps; subStep += 1) {
|
|
176
|
-
for (const body of this.bodyValues()) {
|
|
177
|
-
body.integrate(subDelta, this.gravity, false);
|
|
178
|
-
}
|
|
179
|
-
for (const constraint of this.constraintsList) {
|
|
180
|
-
constraint.solve();
|
|
181
|
-
}
|
|
182
|
-
const contactImpulses = new Map();
|
|
183
|
-
for (let i = 0; i < this.solverIterations; i += 1) {
|
|
184
|
-
contacts = this.detectContacts();
|
|
185
|
-
for (const contact of contacts) {
|
|
186
|
-
this.resolveContact(contact, contactImpulses);
|
|
187
|
-
}
|
|
188
|
-
for (const constraint of this.constraintsList) {
|
|
189
|
-
constraint.solve();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
for (const body of this.bodyValues()) {
|
|
194
|
-
body.clearForces();
|
|
195
|
-
const outerTransform = outerStepTransforms.get(body.id);
|
|
196
|
-
if (outerTransform) {
|
|
197
|
-
body.previousPosition = outerTransform.position;
|
|
198
|
-
body.previousRotation = outerTransform.rotation;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
this.lastEvents = this.eventQueue.update(contacts);
|
|
202
|
-
this.updateSleeping(dt, contacts);
|
|
203
|
-
this.steps += 1;
|
|
204
|
-
return this.lastEvents;
|
|
204
|
+
return this.stepProduction(dt);
|
|
205
205
|
}
|
|
206
206
|
drainEvents() {
|
|
207
207
|
const events = this.lastEvents;
|
|
@@ -220,6 +220,9 @@ export class PhysicsWorld {
|
|
|
220
220
|
if (!body) {
|
|
221
221
|
continue;
|
|
222
222
|
}
|
|
223
|
+
if (options.ignoreColliders?.includes(collider.id) || options.ignoreBodies?.includes(collider.bodyId)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
223
226
|
const hit = raycastCollider(origin, normalized, collider, body, options);
|
|
224
227
|
if (hit) {
|
|
225
228
|
hits.push(hit);
|
|
@@ -239,6 +242,9 @@ export class PhysicsWorld {
|
|
|
239
242
|
if (!body) {
|
|
240
243
|
continue;
|
|
241
244
|
}
|
|
245
|
+
if (options.ignoreColliders?.includes(collider.id) || options.ignoreBodies?.includes(collider.bodyId)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
242
248
|
const hit = sphereCastCollider(origin, radius, normalized, collider, body, options);
|
|
243
249
|
if (hit) {
|
|
244
250
|
hits.push(hit);
|
|
@@ -276,15 +282,61 @@ export class PhysicsWorld {
|
|
|
276
282
|
}
|
|
277
283
|
};
|
|
278
284
|
}
|
|
285
|
+
/** Release the selected native solver and every world-owned handle. Idempotent. */
|
|
286
|
+
dispose() {
|
|
287
|
+
this.rapierWorld.dispose();
|
|
288
|
+
this.rapierJointsByConstraint.clear();
|
|
289
|
+
this.rapierCollidersByAuraId.clear();
|
|
290
|
+
this.rapierBodiesByAuraId.clear();
|
|
291
|
+
this.constraintsList.length = 0;
|
|
292
|
+
this.collidersById.clear();
|
|
293
|
+
this.bodiesById.clear();
|
|
294
|
+
this.bodyColliders.clear();
|
|
295
|
+
this.lastEvents = [];
|
|
296
|
+
}
|
|
279
297
|
detectContacts() {
|
|
280
298
|
const potentialPairs = this.collectPotentialPairs();
|
|
281
299
|
this.lastBroadphasePairs = potentialPairs.length;
|
|
282
300
|
const contacts = [];
|
|
283
301
|
for (const [a, bodyA, b, bodyB] of potentialPairs) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
302
|
+
const colliderA = this.rapierCollidersByAuraId.get(a.id)?.unsafeRapierCollider();
|
|
303
|
+
const colliderB = this.rapierCollidersByAuraId.get(b.id)?.unsafeRapierCollider();
|
|
304
|
+
if (!colliderA || !colliderB)
|
|
305
|
+
continue;
|
|
306
|
+
if (a.sensor || b.sensor) {
|
|
307
|
+
if (!this.rapierWorld.unsafeRapierWorld().intersectionPair(colliderA, colliderB))
|
|
308
|
+
continue;
|
|
309
|
+
const separation = subVec3(bodyB.position, bodyA.position);
|
|
310
|
+
const normal = lengthVec3(separation) > 1e-9 ? normalizeVec3(separation) : [0, 1, 0];
|
|
311
|
+
contacts.push({ colliderA: a.id, colliderB: b.id, bodyA: bodyA.id, bodyB: bodyB.id, normal, penetration: 0, sensor: true });
|
|
312
|
+
continue;
|
|
287
313
|
}
|
|
314
|
+
let best;
|
|
315
|
+
this.rapierWorld.unsafeRapierWorld().contactPair(colliderA, colliderB, (manifold, flipped) => {
|
|
316
|
+
const normalValue = manifold.normal();
|
|
317
|
+
const direction = flipped ? -1 : 1;
|
|
318
|
+
const normal = [normalValue.x * direction, normalValue.y * direction, normalValue.z * direction];
|
|
319
|
+
const count = Math.max(manifold.numSolverContacts(), manifold.numContacts());
|
|
320
|
+
for (let index = 0; index < count; index += 1) {
|
|
321
|
+
const distance = index < manifold.numSolverContacts() ? manifold.solverContactDist(index) : manifold.contactDist(index);
|
|
322
|
+
const penetration = Math.max(0, -distance);
|
|
323
|
+
const pointValue = index < manifold.numSolverContacts() ? manifold.solverContactPoint(index) : undefined;
|
|
324
|
+
const candidate = {
|
|
325
|
+
colliderA: a.id,
|
|
326
|
+
colliderB: b.id,
|
|
327
|
+
bodyA: bodyA.id,
|
|
328
|
+
bodyB: bodyB.id,
|
|
329
|
+
normal,
|
|
330
|
+
penetration,
|
|
331
|
+
...(pointValue ? { point: [pointValue.x, pointValue.y, pointValue.z] } : {}),
|
|
332
|
+
sensor: false
|
|
333
|
+
};
|
|
334
|
+
if (!best || candidate.penetration > best.penetration)
|
|
335
|
+
best = candidate;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
if (best)
|
|
339
|
+
contacts.push(best);
|
|
288
340
|
}
|
|
289
341
|
contacts.sort((a, b) => a.colliderA - b.colliderA || a.colliderB - b.colliderB);
|
|
290
342
|
return contacts;
|
|
@@ -345,203 +397,88 @@ export class PhysicsWorld {
|
|
|
345
397
|
this.lastBroadphaseProfile = profile;
|
|
346
398
|
return pairs;
|
|
347
399
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
normalImpulseMagnitude = normalDenominator > 0 ? -(1 + restitution) * velocityAlongNormal / normalDenominator : 0;
|
|
379
|
-
impulseState.normal += normalImpulseMagnitude;
|
|
380
|
-
this.applyImpulsePair(bodyA, bodyB, scaleVec3(contact.normal, normalImpulseMagnitude), contact.point);
|
|
381
|
-
}
|
|
382
|
-
const updatedRelativeVelocity = relativeContactVelocity(bodyA, bodyB, contact.point);
|
|
383
|
-
const updatedNormalVelocity = dotVec3(updatedRelativeVelocity, contact.normal);
|
|
384
|
-
const tangentVelocity = subVec3(updatedRelativeVelocity, scaleVec3(contact.normal, updatedNormalVelocity));
|
|
385
|
-
const tangentSpeed = Math.hypot(tangentVelocity[0], tangentVelocity[1], tangentVelocity[2]);
|
|
386
|
-
if (tangentSpeed > 1e-9) {
|
|
387
|
-
const friction = Math.sqrt(Math.max(0, materialA.friction) * Math.max(0, materialB.friction));
|
|
388
|
-
const tangent = scaleVec3(tangentVelocity, 1 / tangentSpeed);
|
|
389
|
-
const tangentDenominator = contactImpulseDenominator(bodyA, bodyB, tangent, contact.point);
|
|
390
|
-
const targetImpulse = tangentDenominator > 0 ? scaleVec3(tangentVelocity, -1 / tangentDenominator) : [0, 0, 0];
|
|
391
|
-
const proposedTangent = [
|
|
392
|
-
impulseState.tangent[0] + targetImpulse[0],
|
|
393
|
-
impulseState.tangent[1] + targetImpulse[1],
|
|
394
|
-
impulseState.tangent[2] + targetImpulse[2]
|
|
395
|
-
];
|
|
396
|
-
const proposedMagnitude = lengthVec3(proposedTangent);
|
|
397
|
-
const maxFriction = friction * impulseState.normal;
|
|
398
|
-
const accumulatedTangent = proposedMagnitude > maxFriction && proposedMagnitude > 0
|
|
399
|
-
? scaleVec3(proposedTangent, maxFriction / proposedMagnitude)
|
|
400
|
-
: proposedTangent;
|
|
401
|
-
const frictionImpulse = subVec3(accumulatedTangent, impulseState.tangent);
|
|
402
|
-
impulseState.tangent = accumulatedTangent;
|
|
403
|
-
if (lengthVec3(frictionImpulse) > 0) {
|
|
404
|
-
this.applyImpulsePair(bodyA, bodyB, frictionImpulse, contact.point);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
contactImpulses.set(pairKey, impulseState);
|
|
408
|
-
}
|
|
409
|
-
effectiveMaterial(body, colliderId) {
|
|
410
|
-
const collider = this.collidersById.get(colliderId);
|
|
411
|
-
return {
|
|
412
|
-
restitution: Math.max(body.restitution, collider?.material.restitution ?? 0),
|
|
413
|
-
friction: Math.max(body.friction, collider?.material.friction ?? 0)
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
applyImpulsePair(bodyA, bodyB, impulse, point) {
|
|
417
|
-
if (bodyA.inverseMass > 0) {
|
|
418
|
-
if (point)
|
|
419
|
-
bodyA.applyContactImpulseAtPoint(scaleVec3(impulse, -1), point);
|
|
420
|
-
else
|
|
421
|
-
bodyA.velocity = [bodyA.velocity[0] - impulse[0] * bodyA.inverseMass, bodyA.velocity[1] - impulse[1] * bodyA.inverseMass, bodyA.velocity[2] - impulse[2] * bodyA.inverseMass];
|
|
422
|
-
if (bodyA.sleeping && bodyA.speedSquared() > this.sleepVelocityThreshold * this.sleepVelocityThreshold) {
|
|
423
|
-
bodyA.wake();
|
|
400
|
+
createRapierJoint(constraint) {
|
|
401
|
+
const bodyA = this.rapierBodiesByAuraId.get(constraint.bodyA.id);
|
|
402
|
+
const bodyB = this.rapierBodiesByAuraId.get(constraint.bodyB.id);
|
|
403
|
+
if (!bodyA || !bodyB)
|
|
404
|
+
throw new Error("Cannot create a Rapier joint for missing bodies.");
|
|
405
|
+
return this.rapierWorld.createJoint({
|
|
406
|
+
type: constraint.type,
|
|
407
|
+
localAnchorA: constraint.type === "fixed"
|
|
408
|
+
&& constraint.localAnchorA.every((value) => value === 0)
|
|
409
|
+
&& constraint.localAnchorB.every((value) => value === 0)
|
|
410
|
+
? constraint.restOffset
|
|
411
|
+
: constraint.localAnchorA,
|
|
412
|
+
localAnchorB: constraint.localAnchorB,
|
|
413
|
+
axis: constraint.axis,
|
|
414
|
+
restLength: constraint.restLength,
|
|
415
|
+
stiffness: constraint.stiffness,
|
|
416
|
+
damping: constraint.damping,
|
|
417
|
+
motorSpeed: constraint.motorSpeed(),
|
|
418
|
+
maxMotorTorque: Number.isFinite(constraint.maxMotorTorque) ? constraint.maxMotorTorque : Number.MAX_VALUE,
|
|
419
|
+
...(constraint.limits ? { limits: constraint.limits } : {})
|
|
420
|
+
}, bodyA, bodyB);
|
|
421
|
+
}
|
|
422
|
+
synchronizeRapierJoints() {
|
|
423
|
+
for (const constraint of this.constraintsList) {
|
|
424
|
+
const existing = this.rapierJointsByConstraint.get(constraint);
|
|
425
|
+
if (!constraint.isEnabled()) {
|
|
426
|
+
if (existing)
|
|
427
|
+
existing.remove();
|
|
428
|
+
this.rapierJointsByConstraint.delete(constraint);
|
|
429
|
+
continue;
|
|
424
430
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
else
|
|
430
|
-
bodyB.velocity = [bodyB.velocity[0] + impulse[0] * bodyB.inverseMass, bodyB.velocity[1] + impulse[1] * bodyB.inverseMass, bodyB.velocity[2] + impulse[2] * bodyB.inverseMass];
|
|
431
|
-
if (bodyB.sleeping && bodyB.speedSquared() > this.sleepVelocityThreshold * this.sleepVelocityThreshold) {
|
|
432
|
-
bodyB.wake();
|
|
431
|
+
const joint = existing ?? this.createRapierJoint(constraint);
|
|
432
|
+
this.rapierJointsByConstraint.set(constraint, joint);
|
|
433
|
+
if (constraint.type === "motorised-hinge") {
|
|
434
|
+
joint.configureMotor(constraint.motorSpeed(), Number.isFinite(constraint.maxMotorTorque) ? constraint.maxMotorTorque : Number.MAX_VALUE);
|
|
433
435
|
}
|
|
434
436
|
}
|
|
435
437
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
const contactBodyIds = new Set();
|
|
441
|
-
for (const contact of contacts) {
|
|
442
|
-
if (!contact.sensor) {
|
|
443
|
-
contactBodyIds.add(contact.bodyA);
|
|
444
|
-
contactBodyIds.add(contact.bodyB);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
const gravityMagnitudeSquared = dotVec3(this.gravity, this.gravity);
|
|
448
|
-
const thresholdSquared = this.sleepVelocityThreshold * this.sleepVelocityThreshold;
|
|
438
|
+
stepProduction(dt) {
|
|
439
|
+
this.rapierWorld.setGravity(this.gravity);
|
|
440
|
+
const stepForces = new Map();
|
|
449
441
|
for (const body of this.bodyValues()) {
|
|
450
|
-
|
|
442
|
+
const rapierBody = this.rapierBodiesByAuraId.get(body.id);
|
|
443
|
+
if (!rapierBody)
|
|
451
444
|
continue;
|
|
445
|
+
const force = body.pendingForce();
|
|
446
|
+
const torque = body.pendingTorque();
|
|
447
|
+
if (force[0] !== 0 || force[1] !== 0 || force[2] !== 0 || torque[0] !== 0 || torque[1] !== 0 || torque[2] !== 0) {
|
|
448
|
+
stepForces.set(body.id, { force, torque });
|
|
449
|
+
body.clearForces();
|
|
452
450
|
}
|
|
453
|
-
|
|
454
|
-
body.resetSleepTimer();
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
const supportedOrUnaccelerated = contactBodyIds.has(body.id) || gravityMagnitudeSquared <= thresholdSquared;
|
|
458
|
-
if (!supportedOrUnaccelerated) {
|
|
459
|
-
body.resetSleepTimer();
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
if (body.accumulateSleepTime(dt) >= this.sleepDelay) {
|
|
463
|
-
body.sleep();
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
createCannonBody(body) {
|
|
468
|
-
if (!this.cannonWorld)
|
|
469
|
-
return undefined;
|
|
470
|
-
const cannonBody = new CannonBody({
|
|
471
|
-
type: body.type === "static" ? CannonBody.STATIC : body.type === "kinematic" ? CannonBody.KINEMATIC : CannonBody.DYNAMIC,
|
|
472
|
-
mass: body.type === "dynamic" ? body.mass : 0,
|
|
473
|
-
position: toCannonVec3(body.position),
|
|
474
|
-
velocity: toCannonVec3(body.velocity),
|
|
475
|
-
quaternion: new CannonQuaternion(body.rotation[0], body.rotation[1], body.rotation[2], body.rotation[3]),
|
|
476
|
-
angularVelocity: toCannonVec3(body.angularVelocity),
|
|
477
|
-
linearDamping: body.linearDamping,
|
|
478
|
-
angularDamping: body.angularDamping,
|
|
479
|
-
allowSleep: this.enableSleeping,
|
|
480
|
-
sleepSpeedLimit: this.sleepVelocityThreshold,
|
|
481
|
-
sleepTimeLimit: this.sleepDelay
|
|
482
|
-
});
|
|
483
|
-
if (body.sleeping)
|
|
484
|
-
cannonBody.sleep();
|
|
485
|
-
return cannonBody;
|
|
486
|
-
}
|
|
487
|
-
addCannonCollider(collider) {
|
|
488
|
-
if (!this.cannonWorld)
|
|
489
|
-
return;
|
|
490
|
-
const body = this.bodiesById.get(collider.bodyId);
|
|
491
|
-
const cannonBody = this.cannonBodiesByAuraId.get(collider.bodyId);
|
|
492
|
-
if (!body || !cannonBody)
|
|
493
|
-
return;
|
|
494
|
-
const resolved = toCannonShape(collider.shape);
|
|
495
|
-
if (!resolved) {
|
|
496
|
-
this.disableCannonBackend(`unsupported shape '${collider.shape.kind}'`);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
cannonBody.collisionFilterGroup = collider.filter.layer;
|
|
500
|
-
cannonBody.collisionFilterMask = collider.filter.mask;
|
|
501
|
-
cannonBody.isTrigger = cannonBody.isTrigger || collider.sensor;
|
|
502
|
-
cannonBody.addShape(resolved.shape, resolved.offset, resolved.orientation);
|
|
503
|
-
cannonBody.updateMassProperties();
|
|
504
|
-
syncCannonFromAura(body, cannonBody);
|
|
505
|
-
}
|
|
506
|
-
disableCannonBackend(reason) {
|
|
507
|
-
if (!this.cannonWorld)
|
|
508
|
-
return;
|
|
509
|
-
this.cannonWorld = undefined;
|
|
510
|
-
this.cannonBodiesByAuraId.clear();
|
|
511
|
-
this.backendSelection = {
|
|
512
|
-
requested: this.requestedBackend,
|
|
513
|
-
active: "aura-js",
|
|
514
|
-
fallback: reason,
|
|
515
|
-
deterministic: true,
|
|
516
|
-
jsFallbackAvailable: true,
|
|
517
|
-
continuousCollision: this.continuousCollisionSelection(true)
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
stepCannon(dt) {
|
|
521
|
-
if (!this.cannonWorld)
|
|
522
|
-
return [];
|
|
523
|
-
this.cannonWorld.gravity.copy(toCannonVec3(this.gravity));
|
|
524
|
-
for (const body of this.bodyValues()) {
|
|
525
|
-
const cannonBody = this.cannonBodiesByAuraId.get(body.id);
|
|
526
|
-
if (cannonBody)
|
|
527
|
-
syncCannonFromAura(body, cannonBody);
|
|
451
|
+
syncRapierFromAura(body, rapierBody);
|
|
528
452
|
}
|
|
453
|
+
this.synchronizeRapierJoints();
|
|
529
454
|
const continuousStep = this.planContinuousCollisionStep(dt);
|
|
530
455
|
this.lastContinuousCollisionStep = continuousStep;
|
|
531
456
|
this.assertContinuousCollisionPlan(continuousStep);
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
457
|
+
const subDelta = dt / continuousStep.subSteps;
|
|
458
|
+
for (let index = 0; index < continuousStep.subSteps; index += 1) {
|
|
459
|
+
for (const [bodyId, pending] of stepForces) {
|
|
460
|
+
const raw = this.rapierBodiesByAuraId.get(bodyId)?.unsafeRapierBody();
|
|
461
|
+
if (!raw)
|
|
462
|
+
continue;
|
|
463
|
+
raw.resetForces(false);
|
|
464
|
+
raw.resetTorques(false);
|
|
465
|
+
raw.addForce(toRapierVector(pending.force), true);
|
|
466
|
+
raw.addTorque(toRapierVector(pending.torque), true);
|
|
467
|
+
}
|
|
468
|
+
this.rapierWorld.step(subDelta);
|
|
469
|
+
}
|
|
470
|
+
for (const bodyId of stepForces.keys()) {
|
|
471
|
+
const raw = this.rapierBodiesByAuraId.get(bodyId)?.unsafeRapierBody();
|
|
472
|
+
raw?.resetForces(false);
|
|
473
|
+
raw?.resetTorques(false);
|
|
536
474
|
}
|
|
537
475
|
for (const body of this.bodyValues()) {
|
|
538
|
-
const
|
|
539
|
-
if (
|
|
540
|
-
|
|
476
|
+
const rapierBody = this.rapierBodiesByAuraId.get(body.id);
|
|
477
|
+
if (rapierBody)
|
|
478
|
+
syncAuraFromRapier(rapierBody, body);
|
|
541
479
|
}
|
|
542
480
|
const contacts = this.detectContacts();
|
|
543
481
|
this.lastEvents = this.eventQueue.update(contacts);
|
|
544
|
-
this.updateSleeping(dt, contacts);
|
|
545
482
|
this.steps += 1;
|
|
546
483
|
return this.lastEvents;
|
|
547
484
|
}
|
|
@@ -638,7 +575,7 @@ export class PhysicsWorld {
|
|
|
638
575
|
return {
|
|
639
576
|
active,
|
|
640
577
|
mode: active ? "adaptive-substeps" : "disabled",
|
|
641
|
-
provider: active ? "
|
|
578
|
+
provider: active ? "rapier-native-ccd+adaptive-substeps" : "none",
|
|
642
579
|
maxSubSteps: this.continuousCollision.maxSubSteps,
|
|
643
580
|
motionThreshold: this.continuousCollision.motionThreshold,
|
|
644
581
|
onSubstepLimit: this.continuousCollision.onSubstepLimit,
|
|
@@ -707,48 +644,40 @@ function colliderBoundingRadius(shape) {
|
|
|
707
644
|
}
|
|
708
645
|
return 0;
|
|
709
646
|
}
|
|
710
|
-
function
|
|
711
|
-
return
|
|
712
|
-
}
|
|
713
|
-
function
|
|
714
|
-
return
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
647
|
+
function toRapierVector(value) {
|
|
648
|
+
return { x: value[0], y: value[1], z: value[2] };
|
|
649
|
+
}
|
|
650
|
+
function encodeRapierCollisionGroups(layer, mask) {
|
|
651
|
+
return (((layer & 0xffff) << 16) | (mask & 0xffff)) >>> 0;
|
|
652
|
+
}
|
|
653
|
+
/** Mirror the public Aura body state onto its sole physical-simulation owner. */
|
|
654
|
+
function syncRapierFromAura(body, rapierBody) {
|
|
655
|
+
const raw = rapierBody.unsafeRapierBody();
|
|
656
|
+
raw.setTranslation(toRapierVector(body.position), false);
|
|
657
|
+
raw.setRotation({ x: body.rotation[0], y: body.rotation[1], z: body.rotation[2], w: body.rotation[3] }, false);
|
|
658
|
+
raw.setLinvel(toRapierVector(body.velocity), false);
|
|
659
|
+
raw.setAngvel(toRapierVector(body.angularVelocity), false);
|
|
660
|
+
raw.setLinearDamping(body.linearDamping);
|
|
661
|
+
raw.setAngularDamping(body.angularDamping);
|
|
721
662
|
if (body.sleeping)
|
|
722
|
-
|
|
723
|
-
else
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
|
|
663
|
+
raw.sleep();
|
|
664
|
+
else if (raw.isSleeping())
|
|
665
|
+
raw.wakeUp();
|
|
666
|
+
}
|
|
667
|
+
/** Publish Rapier's solved transform and velocity through the stable Aura wrapper. */
|
|
668
|
+
function syncAuraFromRapier(rapierBody, body) {
|
|
669
|
+
const raw = rapierBody.unsafeRapierBody();
|
|
670
|
+
const position = raw.translation();
|
|
671
|
+
const rotation = raw.rotation();
|
|
672
|
+
const velocity = raw.linvel();
|
|
673
|
+
const angularVelocity = raw.angvel();
|
|
727
674
|
body.previousPosition = cloneVec3(body.position);
|
|
728
675
|
body.previousRotation = [body.rotation[0], body.rotation[1], body.rotation[2], body.rotation[3]];
|
|
729
|
-
body.position =
|
|
730
|
-
body.
|
|
731
|
-
body.
|
|
732
|
-
body.
|
|
733
|
-
body.sleeping =
|
|
734
|
-
}
|
|
735
|
-
function toCannonShape(shape) {
|
|
736
|
-
if (shape.kind === "box")
|
|
737
|
-
return { shape: new CannonBox(toCannonVec3(shape.halfExtents)) };
|
|
738
|
-
if (shape.kind === "sphere")
|
|
739
|
-
return { shape: new CannonSphere(shape.radius) };
|
|
740
|
-
if (shape.kind === "capsule")
|
|
741
|
-
return { shape: new CannonCylinder(shape.radius, shape.radius, shape.halfHeight * 2 + shape.radius * 2, 12) };
|
|
742
|
-
if (shape.kind === "plane") {
|
|
743
|
-
const orientation = new CannonQuaternion();
|
|
744
|
-
orientation.setFromVectors(new CannonVec3(0, 0, 1), toCannonVec3(shape.normal));
|
|
745
|
-
return {
|
|
746
|
-
shape: new CannonPlane(),
|
|
747
|
-
offset: toCannonVec3(scaleVec3(shape.normal, shape.constant)),
|
|
748
|
-
orientation
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
return undefined;
|
|
676
|
+
body.position = [position.x, position.y, position.z];
|
|
677
|
+
body.rotation = [rotation.x, rotation.y, rotation.z, rotation.w];
|
|
678
|
+
body.velocity = [velocity.x, velocity.y, velocity.z];
|
|
679
|
+
body.angularVelocity = [angularVelocity.x, angularVelocity.y, angularVelocity.z];
|
|
680
|
+
body.sleeping = raw.isSleeping();
|
|
752
681
|
}
|
|
753
682
|
function totalKineticEnergy(bodies) {
|
|
754
683
|
let total = 0;
|
|
@@ -765,31 +694,6 @@ function totalKineticEnergy(bodies) {
|
|
|
765
694
|
}
|
|
766
695
|
return total;
|
|
767
696
|
}
|
|
768
|
-
function relativeContactVelocity(bodyA, bodyB, point) {
|
|
769
|
-
if (!point)
|
|
770
|
-
return subVec3(bodyB.velocity, bodyA.velocity);
|
|
771
|
-
const radiusA = subVec3(point, bodyA.position);
|
|
772
|
-
const radiusB = subVec3(point, bodyB.position);
|
|
773
|
-
const velocityA = addVectors(bodyA.velocity, crossVectors(bodyA.angularVelocity, radiusA));
|
|
774
|
-
const velocityB = addVectors(bodyB.velocity, crossVectors(bodyB.angularVelocity, radiusB));
|
|
775
|
-
return subVec3(velocityB, velocityA);
|
|
776
|
-
}
|
|
777
|
-
function contactImpulseDenominator(bodyA, bodyB, direction, point) {
|
|
778
|
-
let denominator = bodyA.inverseMass + bodyB.inverseMass;
|
|
779
|
-
if (!point)
|
|
780
|
-
return denominator;
|
|
781
|
-
if (bodyA.inverseMass > 0) {
|
|
782
|
-
const radius = subVec3(point, bodyA.position);
|
|
783
|
-
const angular = bodyA.multiplyInverseInertiaWorld(crossVectors(radius, direction));
|
|
784
|
-
denominator += dotVec3(direction, crossVectors(angular, radius));
|
|
785
|
-
}
|
|
786
|
-
if (bodyB.inverseMass > 0) {
|
|
787
|
-
const radius = subVec3(point, bodyB.position);
|
|
788
|
-
const angular = bodyB.multiplyInverseInertiaWorld(crossVectors(radius, direction));
|
|
789
|
-
denominator += dotVec3(direction, crossVectors(angular, radius));
|
|
790
|
-
}
|
|
791
|
-
return denominator;
|
|
792
|
-
}
|
|
793
697
|
function addVectors(a, b) {
|
|
794
698
|
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
795
699
|
}
|
|
@@ -837,307 +741,4 @@ function pushPotentialPair(pairs, seen, a, b) {
|
|
|
837
741
|
seen.add(key);
|
|
838
742
|
pairs.push([first.collider, first.body, second.collider, second.body]);
|
|
839
743
|
}
|
|
840
|
-
function buildContact(a, bodyA, b, bodyB) {
|
|
841
|
-
const ab = subVec3(bodyB.position, bodyA.position);
|
|
842
|
-
if (a.shape.kind === "plane" || b.shape.kind === "plane") {
|
|
843
|
-
const planeCollider = a.shape.kind === "plane" ? a : b;
|
|
844
|
-
const planeBody = a.shape.kind === "plane" ? bodyA : bodyB;
|
|
845
|
-
const otherCollider = a.shape.kind === "plane" ? b : a;
|
|
846
|
-
const otherBody = a.shape.kind === "plane" ? bodyB : bodyA;
|
|
847
|
-
if (planeCollider.shape.kind !== "plane") {
|
|
848
|
-
return undefined;
|
|
849
|
-
}
|
|
850
|
-
const radius = supportRadius(otherCollider);
|
|
851
|
-
const signedDistance = dotVec3(planeCollider.shape.normal, otherBody.position) + planeCollider.shape.constant + dotVec3(planeCollider.shape.normal, planeBody.position);
|
|
852
|
-
if (signedDistance > radius) {
|
|
853
|
-
return undefined;
|
|
854
|
-
}
|
|
855
|
-
const normal = a.shape.kind === "plane" ? planeCollider.shape.normal : scaleVec3(planeCollider.shape.normal, -1);
|
|
856
|
-
return {
|
|
857
|
-
colliderA: a.id,
|
|
858
|
-
colliderB: b.id,
|
|
859
|
-
bodyA: bodyA.id,
|
|
860
|
-
bodyB: bodyB.id,
|
|
861
|
-
normal,
|
|
862
|
-
penetration: radius - signedDistance,
|
|
863
|
-
sensor: a.sensor || b.sensor
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
if (a.shape.kind === "sphere" && b.shape.kind === "sphere") {
|
|
867
|
-
const distance = Math.hypot(ab[0], ab[1], ab[2]);
|
|
868
|
-
const radiusSum = a.shape.radius + b.shape.radius;
|
|
869
|
-
if (distance >= radiusSum) {
|
|
870
|
-
return undefined;
|
|
871
|
-
}
|
|
872
|
-
return {
|
|
873
|
-
colliderA: a.id,
|
|
874
|
-
colliderB: b.id,
|
|
875
|
-
bodyA: bodyA.id,
|
|
876
|
-
bodyB: bodyB.id,
|
|
877
|
-
normal: distance > 1e-9 ? [ab[0] / distance, ab[1] / distance, ab[2] / distance] : [1, 0, 0],
|
|
878
|
-
penetration: radiusSum - distance,
|
|
879
|
-
sensor: a.sensor || b.sensor
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
if ((a.shape.kind === "sphere" && b.shape.kind === "box") || (a.shape.kind === "box" && b.shape.kind === "sphere")) {
|
|
883
|
-
return buildSphereBoxContact(a, bodyA, b, bodyB);
|
|
884
|
-
}
|
|
885
|
-
if ((a.shape.kind === "capsule" && b.shape.kind === "sphere") || (a.shape.kind === "sphere" && b.shape.kind === "capsule")) {
|
|
886
|
-
return buildCapsuleSphereContact(a, bodyA, b, bodyB);
|
|
887
|
-
}
|
|
888
|
-
if ((a.shape.kind === "capsule" && b.shape.kind === "box") || (a.shape.kind === "box" && b.shape.kind === "capsule")) {
|
|
889
|
-
return buildCapsuleBoxContact(a, bodyA, b, bodyB);
|
|
890
|
-
}
|
|
891
|
-
if (a.shape.kind === "capsule" && b.shape.kind === "capsule") {
|
|
892
|
-
return buildCapsuleCapsuleContact(a, bodyA, b, bodyB);
|
|
893
|
-
}
|
|
894
|
-
const nativeContact = buildNativeNarrowPhaseContact(a, bodyA, b, bodyB);
|
|
895
|
-
if (nativeContact) {
|
|
896
|
-
return {
|
|
897
|
-
colliderA: a.id,
|
|
898
|
-
colliderB: b.id,
|
|
899
|
-
bodyA: bodyA.id,
|
|
900
|
-
bodyB: bodyB.id,
|
|
901
|
-
normal: nativeContact.normal,
|
|
902
|
-
penetration: nativeContact.penetration,
|
|
903
|
-
point: nativeContact.point,
|
|
904
|
-
sensor: a.sensor || b.sensor
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
const boundsA = a.bounds(bodyA.position);
|
|
908
|
-
const boundsB = b.bounds(bodyB.position);
|
|
909
|
-
const overlapX = Math.min(boundsA.max[0] - boundsB.min[0], boundsB.max[0] - boundsA.min[0]);
|
|
910
|
-
const overlapY = Math.min(boundsA.max[1] - boundsB.min[1], boundsB.max[1] - boundsA.min[1]);
|
|
911
|
-
const overlapZ = Math.min(boundsA.max[2] - boundsB.min[2], boundsB.max[2] - boundsA.min[2]);
|
|
912
|
-
if (overlapX <= 0 || overlapY <= 0 || overlapZ <= 0) {
|
|
913
|
-
return undefined;
|
|
914
|
-
}
|
|
915
|
-
let normal = [Math.sign(ab[0]) || 1, 0, 0];
|
|
916
|
-
let penetration = overlapX;
|
|
917
|
-
if (overlapY < penetration) {
|
|
918
|
-
normal = [0, Math.sign(ab[1]) || 1, 0];
|
|
919
|
-
penetration = overlapY;
|
|
920
|
-
}
|
|
921
|
-
if (overlapZ < penetration) {
|
|
922
|
-
normal = [0, 0, Math.sign(ab[2]) || 1];
|
|
923
|
-
penetration = overlapZ;
|
|
924
|
-
}
|
|
925
|
-
return {
|
|
926
|
-
colliderA: a.id,
|
|
927
|
-
colliderB: b.id,
|
|
928
|
-
bodyA: bodyA.id,
|
|
929
|
-
bodyB: bodyB.id,
|
|
930
|
-
normal,
|
|
931
|
-
penetration,
|
|
932
|
-
sensor: a.sensor || b.sensor
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
function buildSphereBoxContact(a, bodyA, b, bodyB) {
|
|
936
|
-
const sphereCollider = a.shape.kind === "sphere" ? a : b;
|
|
937
|
-
const sphereBody = a.shape.kind === "sphere" ? bodyA : bodyB;
|
|
938
|
-
const boxCollider = a.shape.kind === "box" ? a : b;
|
|
939
|
-
const boxBody = a.shape.kind === "box" ? bodyA : bodyB;
|
|
940
|
-
if (sphereCollider.shape.kind !== "sphere" || boxCollider.shape.kind !== "box") {
|
|
941
|
-
return undefined;
|
|
942
|
-
}
|
|
943
|
-
const boxBounds = boxCollider.bounds(boxBody.position);
|
|
944
|
-
const closest = [
|
|
945
|
-
clamp(sphereBody.position[0], boxBounds.min[0], boxBounds.max[0]),
|
|
946
|
-
clamp(sphereBody.position[1], boxBounds.min[1], boxBounds.max[1]),
|
|
947
|
-
clamp(sphereBody.position[2], boxBounds.min[2], boxBounds.max[2])
|
|
948
|
-
];
|
|
949
|
-
const fromBoxToSphere = subVec3(sphereBody.position, closest);
|
|
950
|
-
const distance = Math.hypot(fromBoxToSphere[0], fromBoxToSphere[1], fromBoxToSphere[2]);
|
|
951
|
-
if (distance >= sphereCollider.shape.radius) {
|
|
952
|
-
return undefined;
|
|
953
|
-
}
|
|
954
|
-
const normalFromBoxToSphere = distance > 1e-9 ? scaleVec3(fromBoxToSphere, 1 / distance) : axisFromBoxCenter(boxBody.position, sphereBody.position, boxCollider.shape.halfExtents);
|
|
955
|
-
const normal = a.shape.kind === "box" ? normalFromBoxToSphere : scaleVec3(normalFromBoxToSphere, -1);
|
|
956
|
-
return {
|
|
957
|
-
colliderA: a.id,
|
|
958
|
-
colliderB: b.id,
|
|
959
|
-
bodyA: bodyA.id,
|
|
960
|
-
bodyB: bodyB.id,
|
|
961
|
-
normal,
|
|
962
|
-
penetration: sphereCollider.shape.radius - distance,
|
|
963
|
-
sensor: a.sensor || b.sensor
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
function buildCapsuleSphereContact(a, bodyA, b, bodyB) {
|
|
967
|
-
const capsuleCollider = a.shape.kind === "capsule" ? a : b;
|
|
968
|
-
const capsuleBody = a.shape.kind === "capsule" ? bodyA : bodyB;
|
|
969
|
-
const sphereCollider = a.shape.kind === "sphere" ? a : b;
|
|
970
|
-
const sphereBody = a.shape.kind === "sphere" ? bodyA : bodyB;
|
|
971
|
-
if (capsuleCollider.shape.kind !== "capsule" || sphereCollider.shape.kind !== "sphere") {
|
|
972
|
-
return undefined;
|
|
973
|
-
}
|
|
974
|
-
const closest = closestPointOnSegment(sphereBody.position, capsuleSegment(capsuleBody.position, capsuleCollider.shape.halfHeight));
|
|
975
|
-
const delta = subVec3(sphereBody.position, closest);
|
|
976
|
-
const distance = Math.hypot(delta[0], delta[1], delta[2]);
|
|
977
|
-
const radiusSum = capsuleCollider.shape.radius + sphereCollider.shape.radius;
|
|
978
|
-
if (distance >= radiusSum) {
|
|
979
|
-
return undefined;
|
|
980
|
-
}
|
|
981
|
-
const normalFromCapsuleToSphere = distance > 1e-9 ? scaleVec3(delta, 1 / distance) : [1, 0, 0];
|
|
982
|
-
const normal = a.shape.kind === "capsule" ? normalFromCapsuleToSphere : scaleVec3(normalFromCapsuleToSphere, -1);
|
|
983
|
-
return {
|
|
984
|
-
colliderA: a.id,
|
|
985
|
-
colliderB: b.id,
|
|
986
|
-
bodyA: bodyA.id,
|
|
987
|
-
bodyB: bodyB.id,
|
|
988
|
-
normal,
|
|
989
|
-
penetration: radiusSum - distance,
|
|
990
|
-
sensor: a.sensor || b.sensor
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
function buildCapsuleCapsuleContact(a, bodyA, b, bodyB) {
|
|
994
|
-
if (a.shape.kind !== "capsule" || b.shape.kind !== "capsule") {
|
|
995
|
-
return undefined;
|
|
996
|
-
}
|
|
997
|
-
const closest = closestPointsBetweenSegments(capsuleSegment(bodyA.position, a.shape.halfHeight), capsuleSegment(bodyB.position, b.shape.halfHeight));
|
|
998
|
-
const delta = subVec3(closest.b, closest.a);
|
|
999
|
-
const distance = Math.hypot(delta[0], delta[1], delta[2]);
|
|
1000
|
-
const radiusSum = a.shape.radius + b.shape.radius;
|
|
1001
|
-
if (distance >= radiusSum) {
|
|
1002
|
-
return undefined;
|
|
1003
|
-
}
|
|
1004
|
-
return {
|
|
1005
|
-
colliderA: a.id,
|
|
1006
|
-
colliderB: b.id,
|
|
1007
|
-
bodyA: bodyA.id,
|
|
1008
|
-
bodyB: bodyB.id,
|
|
1009
|
-
normal: distance > 1e-9 ? scaleVec3(delta, 1 / distance) : [1, 0, 0],
|
|
1010
|
-
penetration: radiusSum - distance,
|
|
1011
|
-
sensor: a.sensor || b.sensor
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
function buildCapsuleBoxContact(a, bodyA, b, bodyB) {
|
|
1015
|
-
const capsuleCollider = a.shape.kind === "capsule" ? a : b;
|
|
1016
|
-
const capsuleBody = a.shape.kind === "capsule" ? bodyA : bodyB;
|
|
1017
|
-
const boxCollider = a.shape.kind === "box" ? a : b;
|
|
1018
|
-
const boxBody = a.shape.kind === "box" ? bodyA : bodyB;
|
|
1019
|
-
if (capsuleCollider.shape.kind !== "capsule" || boxCollider.shape.kind !== "box") {
|
|
1020
|
-
return undefined;
|
|
1021
|
-
}
|
|
1022
|
-
const bounds = boxCollider.bounds(boxBody.position);
|
|
1023
|
-
const closest = closestVerticalSegmentPointToAabb(capsuleBody.position, capsuleCollider.shape.halfHeight, bounds);
|
|
1024
|
-
const fromBoxToCapsule = subVec3(closest.segmentPoint, closest.boxPoint);
|
|
1025
|
-
const distance = Math.hypot(fromBoxToCapsule[0], fromBoxToCapsule[1], fromBoxToCapsule[2]);
|
|
1026
|
-
if (distance >= capsuleCollider.shape.radius) {
|
|
1027
|
-
return undefined;
|
|
1028
|
-
}
|
|
1029
|
-
const normalFromBoxToCapsule = distance > 1e-9 ? scaleVec3(fromBoxToCapsule, 1 / distance) : axisFromBoxCenter(boxBody.position, closest.segmentPoint, boxCollider.shape.halfExtents);
|
|
1030
|
-
const normal = a.shape.kind === "box" ? normalFromBoxToCapsule : scaleVec3(normalFromBoxToCapsule, -1);
|
|
1031
|
-
return {
|
|
1032
|
-
colliderA: a.id,
|
|
1033
|
-
colliderB: b.id,
|
|
1034
|
-
bodyA: bodyA.id,
|
|
1035
|
-
bodyB: bodyB.id,
|
|
1036
|
-
normal,
|
|
1037
|
-
penetration: capsuleCollider.shape.radius - distance,
|
|
1038
|
-
sensor: a.sensor || b.sensor
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
function capsuleSegment(position, halfHeight) {
|
|
1042
|
-
return {
|
|
1043
|
-
start: [position[0], position[1] - halfHeight, position[2]],
|
|
1044
|
-
end: [position[0], position[1] + halfHeight, position[2]]
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
function closestPointOnSegment(point, segment) {
|
|
1048
|
-
const ab = subVec3(segment.end, segment.start);
|
|
1049
|
-
const lengthSquared = dotVec3(ab, ab);
|
|
1050
|
-
if (lengthSquared <= 1e-9) {
|
|
1051
|
-
return segment.start;
|
|
1052
|
-
}
|
|
1053
|
-
const t = clamp(dotVec3(subVec3(point, segment.start), ab) / lengthSquared, 0, 1);
|
|
1054
|
-
return [
|
|
1055
|
-
segment.start[0] + ab[0] * t,
|
|
1056
|
-
segment.start[1] + ab[1] * t,
|
|
1057
|
-
segment.start[2] + ab[2] * t
|
|
1058
|
-
];
|
|
1059
|
-
}
|
|
1060
|
-
function closestPointsBetweenSegments(a, b) {
|
|
1061
|
-
const d1 = subVec3(a.end, a.start);
|
|
1062
|
-
const d2 = subVec3(b.end, b.start);
|
|
1063
|
-
const r = subVec3(a.start, b.start);
|
|
1064
|
-
const aLength = dotVec3(d1, d1);
|
|
1065
|
-
const bLength = dotVec3(d2, d2);
|
|
1066
|
-
const d12 = dotVec3(d1, d2);
|
|
1067
|
-
const d1r = dotVec3(d1, r);
|
|
1068
|
-
const d2r = dotVec3(d2, r);
|
|
1069
|
-
const denominator = aLength * bLength - d12 * d12;
|
|
1070
|
-
let s = denominator <= 1e-9 ? 0 : clamp((d12 * d2r - bLength * d1r) / denominator, 0, 1);
|
|
1071
|
-
let t = bLength <= 1e-9 ? 0 : (d12 * s + d2r) / bLength;
|
|
1072
|
-
if (t < 0) {
|
|
1073
|
-
t = 0;
|
|
1074
|
-
s = aLength <= 1e-9 ? 0 : clamp(-d1r / aLength, 0, 1);
|
|
1075
|
-
}
|
|
1076
|
-
else if (t > 1) {
|
|
1077
|
-
t = 1;
|
|
1078
|
-
s = aLength <= 1e-9 ? 0 : clamp((d12 - d1r) / aLength, 0, 1);
|
|
1079
|
-
}
|
|
1080
|
-
return {
|
|
1081
|
-
a: [a.start[0] + d1[0] * s, a.start[1] + d1[1] * s, a.start[2] + d1[2] * s],
|
|
1082
|
-
b: [b.start[0] + d2[0] * t, b.start[1] + d2[1] * t, b.start[2] + d2[2] * t]
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
function closestVerticalSegmentPointToAabb(position, halfHeight, bounds) {
|
|
1086
|
-
const segmentMinY = position[1] - halfHeight;
|
|
1087
|
-
const segmentMaxY = position[1] + halfHeight;
|
|
1088
|
-
let segmentY;
|
|
1089
|
-
let boxY;
|
|
1090
|
-
if (segmentMaxY < bounds.min[1]) {
|
|
1091
|
-
segmentY = segmentMaxY;
|
|
1092
|
-
boxY = bounds.min[1];
|
|
1093
|
-
}
|
|
1094
|
-
else if (segmentMinY > bounds.max[1]) {
|
|
1095
|
-
segmentY = segmentMinY;
|
|
1096
|
-
boxY = bounds.max[1];
|
|
1097
|
-
}
|
|
1098
|
-
else {
|
|
1099
|
-
segmentY = clamp(position[1], Math.max(segmentMinY, bounds.min[1]), Math.min(segmentMaxY, bounds.max[1]));
|
|
1100
|
-
boxY = segmentY;
|
|
1101
|
-
}
|
|
1102
|
-
const segmentPoint = [position[0], segmentY, position[2]];
|
|
1103
|
-
const boxPoint = [
|
|
1104
|
-
clamp(position[0], bounds.min[0], bounds.max[0]),
|
|
1105
|
-
boxY,
|
|
1106
|
-
clamp(position[2], bounds.min[2], bounds.max[2])
|
|
1107
|
-
];
|
|
1108
|
-
return { segmentPoint, boxPoint };
|
|
1109
|
-
}
|
|
1110
|
-
function axisFromBoxCenter(boxPosition, spherePosition, halfExtents) {
|
|
1111
|
-
const local = subVec3(spherePosition, boxPosition);
|
|
1112
|
-
const distances = [
|
|
1113
|
-
[halfExtents[0] - Math.abs(local[0]), [Math.sign(local[0]) || 1, 0, 0]],
|
|
1114
|
-
[halfExtents[1] - Math.abs(local[1]), [0, Math.sign(local[1]) || 1, 0]],
|
|
1115
|
-
[halfExtents[2] - Math.abs(local[2]), [0, 0, Math.sign(local[2]) || 1]]
|
|
1116
|
-
];
|
|
1117
|
-
return distances.reduce((best, candidate) => candidate[0] < best[0] ? candidate : best)[1];
|
|
1118
|
-
}
|
|
1119
|
-
function clamp(value, min, max) {
|
|
1120
|
-
return Math.max(min, Math.min(max, value));
|
|
1121
|
-
}
|
|
1122
|
-
function supportRadius(collider) {
|
|
1123
|
-
switch (collider.shape.kind) {
|
|
1124
|
-
case "box":
|
|
1125
|
-
return collider.shape.halfExtents[1];
|
|
1126
|
-
case "sphere":
|
|
1127
|
-
return collider.shape.radius;
|
|
1128
|
-
case "capsule":
|
|
1129
|
-
return collider.shape.radius + collider.shape.halfHeight;
|
|
1130
|
-
case "plane":
|
|
1131
|
-
return 0;
|
|
1132
|
-
case "mesh": {
|
|
1133
|
-
const bounds = collider.bounds([0, 0, 0]);
|
|
1134
|
-
return Math.max(Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
|
|
1135
|
-
}
|
|
1136
|
-
case "convex-hull":
|
|
1137
|
-
case "heightfield": {
|
|
1138
|
-
const bounds = collider.bounds([0, 0, 0]);
|
|
1139
|
-
return Math.max(Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
744
|
//# sourceMappingURL=PhysicsWorld.js.map
|