@aura3d/physics 1.4.4 → 1.5.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/Collider.d.ts +1 -1
- package/dist/Collider.d.ts.map +1 -1
- package/dist/Collider.js +2 -2
- package/dist/Collider.js.map +1 -1
- package/dist/CollisionEvents.d.ts +1 -0
- package/dist/CollisionEvents.d.ts.map +1 -1
- package/dist/CollisionEvents.js.map +1 -1
- package/dist/NarrowPhase.d.ts +11 -0
- package/dist/NarrowPhase.d.ts.map +1 -0
- package/dist/NarrowPhase.js +565 -0
- package/dist/NarrowPhase.js.map +1 -0
- package/dist/PhysicsDebugDraw.js +19 -1
- package/dist/PhysicsDebugDraw.js.map +1 -1
- package/dist/PhysicsWorld.d.ts +43 -0
- package/dist/PhysicsWorld.d.ts.map +1 -1
- package/dist/PhysicsWorld.js +305 -30
- package/dist/PhysicsWorld.js.map +1 -1
- package/dist/RigidBody.d.ts +8 -1
- package/dist/RigidBody.d.ts.map +1 -1
- package/dist/RigidBody.js +32 -5
- package/dist/RigidBody.js.map +1 -1
- package/dist/Shape.d.ts +16 -2
- package/dist/Shape.d.ts.map +1 -1
- package/dist/Shape.js +93 -31
- package/dist/Shape.js.map +1 -1
- package/dist/TimeOfImpact.d.ts +15 -0
- package/dist/TimeOfImpact.d.ts.map +1 -0
- package/dist/TimeOfImpact.js +81 -0
- package/dist/TimeOfImpact.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/PhysicsWorld.js
CHANGED
|
@@ -2,9 +2,11 @@ import { Body as CannonBody, Box as CannonBox, Cylinder as CannonCylinder, Plane
|
|
|
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";
|
|
5
6
|
import { raycastCollider, sphereCastCollider } from "./Raycast.js";
|
|
6
7
|
import { RigidBody } from "./RigidBody.js";
|
|
7
|
-
import { cloneVec3, dotVec3, normalizeVec3, scaleVec3, subVec3 } from "./Shape.js";
|
|
8
|
+
import { cloneVec3, dotVec3, lengthVec3, normalizeVec3, scaleVec3, subVec3 } from "./Shape.js";
|
|
9
|
+
import { timeOfImpact } from "./TimeOfImpact.js";
|
|
8
10
|
export class PhysicsWorld {
|
|
9
11
|
gravity;
|
|
10
12
|
fixedDelta;
|
|
@@ -12,6 +14,7 @@ export class PhysicsWorld {
|
|
|
12
14
|
enableSleeping;
|
|
13
15
|
sleepVelocityThreshold;
|
|
14
16
|
sleepDelay;
|
|
17
|
+
continuousCollision;
|
|
15
18
|
bodiesById = new Map();
|
|
16
19
|
collidersById = new Map();
|
|
17
20
|
constraintsList = [];
|
|
@@ -26,6 +29,7 @@ export class PhysicsWorld {
|
|
|
26
29
|
lastEvents = [];
|
|
27
30
|
lastBroadphasePairs = 0;
|
|
28
31
|
lastBroadphaseProfile = emptyBroadphaseProfile();
|
|
32
|
+
lastContinuousCollisionStep = emptyContinuousCollisionStepPlan();
|
|
29
33
|
steps = 0;
|
|
30
34
|
constructor(descriptor = {}) {
|
|
31
35
|
this.requestedBackend = descriptor.backend ?? "auto";
|
|
@@ -35,6 +39,7 @@ export class PhysicsWorld {
|
|
|
35
39
|
this.enableSleeping = descriptor.enableSleeping ?? true;
|
|
36
40
|
this.sleepVelocityThreshold = descriptor.sleepVelocityThreshold ?? 0.02;
|
|
37
41
|
this.sleepDelay = descriptor.sleepDelay ?? 0.5;
|
|
42
|
+
this.continuousCollision = normalizeContinuousCollisionDescriptor(descriptor.continuousCollision);
|
|
38
43
|
if (!Number.isFinite(this.fixedDelta) || this.fixedDelta <= 0) {
|
|
39
44
|
throw new Error("fixedDelta must be a finite positive number.");
|
|
40
45
|
}
|
|
@@ -51,7 +56,8 @@ export class PhysicsWorld {
|
|
|
51
56
|
requested: this.requestedBackend,
|
|
52
57
|
active: this.requestedBackend === "aura-js" ? "aura-js" : "cannon-es",
|
|
53
58
|
deterministic: true,
|
|
54
|
-
jsFallbackAvailable: true
|
|
59
|
+
jsFallbackAvailable: true,
|
|
60
|
+
continuousCollision: this.continuousCollisionSelection(true)
|
|
55
61
|
};
|
|
56
62
|
if (this.backendSelection.active === "cannon-es") {
|
|
57
63
|
this.cannonWorld = new CannonWorld({
|
|
@@ -154,21 +160,43 @@ export class PhysicsWorld {
|
|
|
154
160
|
if (this.cannonWorld) {
|
|
155
161
|
return this.stepCannon(dt);
|
|
156
162
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
]));
|
|
163
174
|
let contacts = [];
|
|
164
|
-
for (let
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.resolveContact(contact);
|
|
175
|
+
for (let subStep = 0; subStep < continuousStep.subSteps; subStep += 1) {
|
|
176
|
+
for (const body of this.bodyValues()) {
|
|
177
|
+
body.integrate(subDelta, this.gravity, false);
|
|
168
178
|
}
|
|
169
179
|
for (const constraint of this.constraintsList) {
|
|
170
180
|
constraint.solve();
|
|
171
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
|
+
}
|
|
172
200
|
}
|
|
173
201
|
this.lastEvents = this.eventQueue.update(contacts);
|
|
174
202
|
this.updateSleeping(dt, contacts);
|
|
@@ -223,7 +251,10 @@ export class PhysicsWorld {
|
|
|
223
251
|
const contacts = this.eventQueue.snapshotContacts();
|
|
224
252
|
const bodies = this.bodies();
|
|
225
253
|
return {
|
|
226
|
-
backend:
|
|
254
|
+
backend: {
|
|
255
|
+
...this.backendSelection,
|
|
256
|
+
continuousCollision: this.continuousCollisionSelection(true)
|
|
257
|
+
},
|
|
227
258
|
bodies: bodies.map((body) => body.snapshot()),
|
|
228
259
|
contacts,
|
|
229
260
|
stats: {
|
|
@@ -267,7 +298,7 @@ export class PhysicsWorld {
|
|
|
267
298
|
if (!body) {
|
|
268
299
|
continue;
|
|
269
300
|
}
|
|
270
|
-
const bounds = collider.bounds(body.position);
|
|
301
|
+
const bounds = collider.bounds(body.position, body.rotation);
|
|
271
302
|
const entry = { collider, body, bounds };
|
|
272
303
|
if (isFiniteBounds(bounds)) {
|
|
273
304
|
finiteEntries.push(entry);
|
|
@@ -314,7 +345,7 @@ export class PhysicsWorld {
|
|
|
314
345
|
this.lastBroadphaseProfile = profile;
|
|
315
346
|
return pairs;
|
|
316
347
|
}
|
|
317
|
-
resolveContact(contact) {
|
|
348
|
+
resolveContact(contact, contactImpulses) {
|
|
318
349
|
if (contact.sensor) {
|
|
319
350
|
return;
|
|
320
351
|
}
|
|
@@ -334,30 +365,46 @@ export class PhysicsWorld {
|
|
|
334
365
|
if (bodyB.inverseMass > 0) {
|
|
335
366
|
bodyB.position = [bodyB.position[0] + correction[0] * bodyB.inverseMass, bodyB.position[1] + correction[1] * bodyB.inverseMass, bodyB.position[2] + correction[2] * bodyB.inverseMass];
|
|
336
367
|
}
|
|
337
|
-
const relativeVelocity =
|
|
368
|
+
const relativeVelocity = relativeContactVelocity(bodyA, bodyB, contact.point);
|
|
338
369
|
const velocityAlongNormal = dotVec3(relativeVelocity, contact.normal);
|
|
339
370
|
const materialA = this.effectiveMaterial(bodyA, contact.colliderA);
|
|
340
371
|
const materialB = this.effectiveMaterial(bodyB, contact.colliderB);
|
|
341
372
|
const restitution = Math.max(materialA.restitution, materialB.restitution);
|
|
373
|
+
const pairKey = `${contact.colliderA}:${contact.colliderB}`;
|
|
374
|
+
const impulseState = contactImpulses.get(pairKey) ?? { normal: 0, tangent: [0, 0, 0] };
|
|
342
375
|
let normalImpulseMagnitude = 0;
|
|
343
376
|
if (velocityAlongNormal <= 0) {
|
|
344
|
-
|
|
345
|
-
|
|
377
|
+
const normalDenominator = contactImpulseDenominator(bodyA, bodyB, contact.normal, contact.point);
|
|
378
|
+
normalImpulseMagnitude = normalDenominator > 0 ? -(1 + restitution) * velocityAlongNormal / normalDenominator : 0;
|
|
379
|
+
impulseState.normal += normalImpulseMagnitude;
|
|
380
|
+
this.applyImpulsePair(bodyA, bodyB, scaleVec3(contact.normal, normalImpulseMagnitude), contact.point);
|
|
346
381
|
}
|
|
347
|
-
const updatedRelativeVelocity =
|
|
382
|
+
const updatedRelativeVelocity = relativeContactVelocity(bodyA, bodyB, contact.point);
|
|
348
383
|
const updatedNormalVelocity = dotVec3(updatedRelativeVelocity, contact.normal);
|
|
349
384
|
const tangentVelocity = subVec3(updatedRelativeVelocity, scaleVec3(contact.normal, updatedNormalVelocity));
|
|
350
385
|
const tangentSpeed = Math.hypot(tangentVelocity[0], tangentVelocity[1], tangentVelocity[2]);
|
|
351
386
|
if (tangentSpeed > 1e-9) {
|
|
352
|
-
const tangent = scaleVec3(tangentVelocity, 1 / tangentSpeed);
|
|
353
387
|
const friction = Math.sqrt(Math.max(0, materialA.friction) * Math.max(0, materialB.friction));
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
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);
|
|
359
405
|
}
|
|
360
406
|
}
|
|
407
|
+
contactImpulses.set(pairKey, impulseState);
|
|
361
408
|
}
|
|
362
409
|
effectiveMaterial(body, colliderId) {
|
|
363
410
|
const collider = this.collidersById.get(colliderId);
|
|
@@ -366,15 +413,21 @@ export class PhysicsWorld {
|
|
|
366
413
|
friction: Math.max(body.friction, collider?.material.friction ?? 0)
|
|
367
414
|
};
|
|
368
415
|
}
|
|
369
|
-
applyImpulsePair(bodyA, bodyB, impulse) {
|
|
416
|
+
applyImpulsePair(bodyA, bodyB, impulse, point) {
|
|
370
417
|
if (bodyA.inverseMass > 0) {
|
|
371
|
-
|
|
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];
|
|
372
422
|
if (bodyA.sleeping && bodyA.speedSquared() > this.sleepVelocityThreshold * this.sleepVelocityThreshold) {
|
|
373
423
|
bodyA.wake();
|
|
374
424
|
}
|
|
375
425
|
}
|
|
376
426
|
if (bodyB.inverseMass > 0) {
|
|
377
|
-
|
|
427
|
+
if (point)
|
|
428
|
+
bodyB.applyContactImpulseAtPoint(impulse, point);
|
|
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];
|
|
378
431
|
if (bodyB.sleeping && bodyB.speedSquared() > this.sleepVelocityThreshold * this.sleepVelocityThreshold) {
|
|
379
432
|
bodyB.wake();
|
|
380
433
|
}
|
|
@@ -460,7 +513,8 @@ export class PhysicsWorld {
|
|
|
460
513
|
active: "aura-js",
|
|
461
514
|
fallback: reason,
|
|
462
515
|
deterministic: true,
|
|
463
|
-
jsFallbackAvailable: true
|
|
516
|
+
jsFallbackAvailable: true,
|
|
517
|
+
continuousCollision: this.continuousCollisionSelection(true)
|
|
464
518
|
};
|
|
465
519
|
}
|
|
466
520
|
stepCannon(dt) {
|
|
@@ -472,7 +526,14 @@ export class PhysicsWorld {
|
|
|
472
526
|
if (cannonBody)
|
|
473
527
|
syncCannonFromAura(body, cannonBody);
|
|
474
528
|
}
|
|
475
|
-
this.
|
|
529
|
+
const continuousStep = this.planContinuousCollisionStep(dt);
|
|
530
|
+
this.lastContinuousCollisionStep = continuousStep;
|
|
531
|
+
this.assertContinuousCollisionPlan(continuousStep);
|
|
532
|
+
const subSteps = continuousStep.subSteps;
|
|
533
|
+
const subDelta = dt / subSteps;
|
|
534
|
+
for (let index = 0; index < subSteps; index += 1) {
|
|
535
|
+
this.cannonWorld.step(subDelta);
|
|
536
|
+
}
|
|
476
537
|
for (const body of this.bodyValues()) {
|
|
477
538
|
const cannonBody = this.cannonBodiesByAuraId.get(body.id);
|
|
478
539
|
if (cannonBody)
|
|
@@ -484,6 +545,167 @@ export class PhysicsWorld {
|
|
|
484
545
|
this.steps += 1;
|
|
485
546
|
return this.lastEvents;
|
|
486
547
|
}
|
|
548
|
+
planContinuousCollisionStep(dt) {
|
|
549
|
+
if (!this.continuousCollision.active) {
|
|
550
|
+
return emptyContinuousCollisionStepPlan();
|
|
551
|
+
}
|
|
552
|
+
let maxMotion = 0;
|
|
553
|
+
for (const body of this.bodyValues()) {
|
|
554
|
+
if (body.type === "static" || body.sleeping)
|
|
555
|
+
continue;
|
|
556
|
+
const bodyRadius = this.bodyBoundingRadius(body.id);
|
|
557
|
+
const linearMotion = lengthVec3(body.velocity) * dt;
|
|
558
|
+
const angularMotion = lengthVec3(body.angularVelocity) * bodyRadius * dt;
|
|
559
|
+
maxMotion = Math.max(maxMotion, linearMotion + angularMotion);
|
|
560
|
+
}
|
|
561
|
+
const minColliderFeature = this.smallestFiniteColliderFeature();
|
|
562
|
+
const earliestImpact = this.earliestTimeOfImpact(dt);
|
|
563
|
+
if (maxMotion <= 0 || minColliderFeature === undefined) {
|
|
564
|
+
return {
|
|
565
|
+
subSteps: 1,
|
|
566
|
+
requiredSubSteps: 1,
|
|
567
|
+
maxMotion,
|
|
568
|
+
minColliderFeature,
|
|
569
|
+
timeOfImpact: earliestImpact,
|
|
570
|
+
limitExceeded: false
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
const maxMotionPerSubstep = minColliderFeature * this.continuousCollision.motionThreshold;
|
|
574
|
+
const requiredSubSteps = Math.max(1, Math.ceil(maxMotion / maxMotionPerSubstep));
|
|
575
|
+
const limitExceeded = requiredSubSteps > this.continuousCollision.maxSubSteps;
|
|
576
|
+
return {
|
|
577
|
+
subSteps: Math.min(requiredSubSteps, this.continuousCollision.maxSubSteps),
|
|
578
|
+
requiredSubSteps,
|
|
579
|
+
maxMotion,
|
|
580
|
+
minColliderFeature,
|
|
581
|
+
timeOfImpact: earliestImpact,
|
|
582
|
+
limitExceeded
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
earliestTimeOfImpact(dt) {
|
|
586
|
+
let earliest;
|
|
587
|
+
const colliders = this.colliders();
|
|
588
|
+
for (let firstIndex = 0; firstIndex < colliders.length; firstIndex += 1) {
|
|
589
|
+
const colliderA = colliders[firstIndex];
|
|
590
|
+
const bodyA = this.bodiesById.get(colliderA.bodyId);
|
|
591
|
+
if (!bodyA)
|
|
592
|
+
continue;
|
|
593
|
+
for (let secondIndex = firstIndex + 1; secondIndex < colliders.length; secondIndex += 1) {
|
|
594
|
+
const colliderB = colliders[secondIndex];
|
|
595
|
+
const bodyB = this.bodiesById.get(colliderB.bodyId);
|
|
596
|
+
if (!bodyB ||
|
|
597
|
+
bodyA.id === bodyB.id ||
|
|
598
|
+
(bodyA.type !== "dynamic" && bodyB.type !== "dynamic") ||
|
|
599
|
+
!colliderA.canCollideWith(colliderB)) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
const impact = timeOfImpact(colliderA.shape, bodyA.position, bodyA.velocity, colliderB.shape, bodyB.position, bodyB.velocity, dt);
|
|
603
|
+
if (impact && (earliest === undefined || impact.time < earliest)) {
|
|
604
|
+
earliest = impact.time;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return earliest;
|
|
609
|
+
}
|
|
610
|
+
assertContinuousCollisionPlan(plan) {
|
|
611
|
+
if (plan.limitExceeded && this.continuousCollision.onSubstepLimit === "error") {
|
|
612
|
+
throw new Error(`PhysicsWorld adaptive-substep CCD requires ${plan.requiredSubSteps} substeps, ` +
|
|
613
|
+
`above maxSubSteps ${this.continuousCollision.maxSubSteps}. Increase maxSubSteps, reduce dt, ` +
|
|
614
|
+
"or use a swept movement query; the step was rejected so tunneling is not silently accepted.");
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
smallestFiniteColliderFeature() {
|
|
618
|
+
let smallest;
|
|
619
|
+
for (const collider of this.colliderValues()) {
|
|
620
|
+
const feature = colliderFeatureSize(collider.shape);
|
|
621
|
+
if (feature === undefined)
|
|
622
|
+
continue;
|
|
623
|
+
smallest = smallest === undefined ? feature : Math.min(smallest, feature);
|
|
624
|
+
}
|
|
625
|
+
return smallest;
|
|
626
|
+
}
|
|
627
|
+
bodyBoundingRadius(bodyId) {
|
|
628
|
+
let radius = 0;
|
|
629
|
+
for (const colliderId of this.bodyColliders.get(bodyId) ?? []) {
|
|
630
|
+
const collider = this.collidersById.get(colliderId);
|
|
631
|
+
if (collider)
|
|
632
|
+
radius = Math.max(radius, colliderBoundingRadius(collider.shape));
|
|
633
|
+
}
|
|
634
|
+
return radius;
|
|
635
|
+
}
|
|
636
|
+
continuousCollisionSelection(backendSupportsWrapper) {
|
|
637
|
+
const active = this.continuousCollision.active && backendSupportsWrapper;
|
|
638
|
+
return {
|
|
639
|
+
active,
|
|
640
|
+
mode: active ? "adaptive-substeps" : "disabled",
|
|
641
|
+
provider: active ? "aura3d-adaptive-substep-wrapper" : "none",
|
|
642
|
+
maxSubSteps: this.continuousCollision.maxSubSteps,
|
|
643
|
+
motionThreshold: this.continuousCollision.motionThreshold,
|
|
644
|
+
onSubstepLimit: this.continuousCollision.onSubstepLimit,
|
|
645
|
+
lastSubSteps: active ? this.lastContinuousCollisionStep.subSteps : 1,
|
|
646
|
+
lastRequiredSubSteps: active ? this.lastContinuousCollisionStep.requiredSubSteps : 1,
|
|
647
|
+
lastMaxMotion: active ? this.lastContinuousCollisionStep.maxMotion : 0,
|
|
648
|
+
...(active && this.lastContinuousCollisionStep.minColliderFeature !== undefined
|
|
649
|
+
? { lastMinColliderFeature: this.lastContinuousCollisionStep.minColliderFeature }
|
|
650
|
+
: {}),
|
|
651
|
+
...(active && this.lastContinuousCollisionStep.timeOfImpact !== undefined
|
|
652
|
+
? { lastTimeOfImpact: this.lastContinuousCollisionStep.timeOfImpact }
|
|
653
|
+
: {}),
|
|
654
|
+
limitExceeded: active && this.lastContinuousCollisionStep.limitExceeded
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function normalizeContinuousCollisionDescriptor(descriptor) {
|
|
659
|
+
if (!descriptor) {
|
|
660
|
+
return {
|
|
661
|
+
active: false,
|
|
662
|
+
maxSubSteps: 1,
|
|
663
|
+
motionThreshold: 0.5,
|
|
664
|
+
onSubstepLimit: "error"
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
const maxSubSteps = descriptor.maxSubSteps ?? 256;
|
|
668
|
+
const motionThreshold = descriptor.motionThreshold ?? 0.5;
|
|
669
|
+
const onSubstepLimit = descriptor.onSubstepLimit ?? "error";
|
|
670
|
+
if (!Number.isInteger(maxSubSteps) || maxSubSteps < 1) {
|
|
671
|
+
throw new Error("continuousCollision.maxSubSteps must be a positive integer.");
|
|
672
|
+
}
|
|
673
|
+
if (!Number.isFinite(motionThreshold) || motionThreshold <= 0 || motionThreshold > 1) {
|
|
674
|
+
throw new Error("continuousCollision.motionThreshold must be finite and in the range (0, 1].");
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
active: true,
|
|
678
|
+
maxSubSteps,
|
|
679
|
+
motionThreshold,
|
|
680
|
+
onSubstepLimit
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function emptyContinuousCollisionStepPlan() {
|
|
684
|
+
return {
|
|
685
|
+
subSteps: 1,
|
|
686
|
+
requiredSubSteps: 1,
|
|
687
|
+
maxMotion: 0,
|
|
688
|
+
limitExceeded: false
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function colliderFeatureSize(shape) {
|
|
692
|
+
if (shape.kind === "box")
|
|
693
|
+
return Math.min(shape.halfExtents[0], shape.halfExtents[1], shape.halfExtents[2]);
|
|
694
|
+
if (shape.kind === "sphere" || shape.kind === "capsule")
|
|
695
|
+
return shape.radius;
|
|
696
|
+
return undefined;
|
|
697
|
+
}
|
|
698
|
+
function colliderBoundingRadius(shape) {
|
|
699
|
+
if (shape.kind === "box")
|
|
700
|
+
return Math.hypot(shape.halfExtents[0], shape.halfExtents[1], shape.halfExtents[2]);
|
|
701
|
+
if (shape.kind === "sphere")
|
|
702
|
+
return shape.radius;
|
|
703
|
+
if (shape.kind === "capsule")
|
|
704
|
+
return shape.radius + shape.halfHeight;
|
|
705
|
+
if (shape.kind === "mesh") {
|
|
706
|
+
return shape.vertices.reduce((radius, vertex) => Math.max(radius, lengthVec3(vertex)), 0);
|
|
707
|
+
}
|
|
708
|
+
return 0;
|
|
487
709
|
}
|
|
488
710
|
function toCannonVec3(value) {
|
|
489
711
|
return new CannonVec3(value[0], value[1], value[2]);
|
|
@@ -543,6 +765,41 @@ function totalKineticEnergy(bodies) {
|
|
|
543
765
|
}
|
|
544
766
|
return total;
|
|
545
767
|
}
|
|
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
|
+
function addVectors(a, b) {
|
|
794
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
795
|
+
}
|
|
796
|
+
function crossVectors(a, b) {
|
|
797
|
+
return [
|
|
798
|
+
a[1] * b[2] - a[2] * b[1],
|
|
799
|
+
a[2] * b[0] - a[0] * b[2],
|
|
800
|
+
a[0] * b[1] - a[1] * b[0]
|
|
801
|
+
];
|
|
802
|
+
}
|
|
546
803
|
function emptyBroadphaseProfile() {
|
|
547
804
|
return {
|
|
548
805
|
finiteColliders: 0,
|
|
@@ -634,6 +891,19 @@ function buildContact(a, bodyA, b, bodyB) {
|
|
|
634
891
|
if (a.shape.kind === "capsule" && b.shape.kind === "capsule") {
|
|
635
892
|
return buildCapsuleCapsuleContact(a, bodyA, b, bodyB);
|
|
636
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
|
+
}
|
|
637
907
|
const boundsA = a.bounds(bodyA.position);
|
|
638
908
|
const boundsB = b.bounds(bodyB.position);
|
|
639
909
|
const overlapX = Math.min(boundsA.max[0] - boundsB.min[0], boundsB.max[0] - boundsA.min[0]);
|
|
@@ -863,6 +1133,11 @@ function supportRadius(collider) {
|
|
|
863
1133
|
const bounds = collider.bounds([0, 0, 0]);
|
|
864
1134
|
return Math.max(Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
|
|
865
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
|
+
}
|
|
866
1141
|
}
|
|
867
1142
|
}
|
|
868
1143
|
//# sourceMappingURL=PhysicsWorld.js.map
|