@flighthq/physics3d 0.4.0-edge.1908.751021c
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/broadphase.d.ts +4 -0
- package/dist/broadphase.d.ts.map +1 -0
- package/dist/broadphase.js +158 -0
- package/dist/broadphase.js.map +1 -0
- package/dist/colliderCollision.d.ts +5 -0
- package/dist/colliderCollision.d.ts.map +1 -0
- package/dist/colliderCollision.js +55 -0
- package/dist/colliderCollision.js.map +1 -0
- package/dist/colliderTransform.d.ts +5 -0
- package/dist/colliderTransform.d.ts.map +1 -0
- package/dist/colliderTransform.js +424 -0
- package/dist/colliderTransform.js.map +1 -0
- package/dist/contactIntake.d.ts +5 -0
- package/dist/contactIntake.d.ts.map +1 -0
- package/dist/contactIntake.js +237 -0
- package/dist/contactIntake.js.map +1 -0
- package/dist/contacts.d.ts +4 -0
- package/dist/contacts.d.ts.map +1 -0
- package/dist/contacts.js +64 -0
- package/dist/contacts.js.map +1 -0
- package/dist/continuous.d.ts +5 -0
- package/dist/continuous.d.ts.map +1 -0
- package/dist/continuous.js +829 -0
- package/dist/continuous.js.map +1 -0
- package/dist/contract.d.ts +28 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +28 -0
- package/dist/contract.js.map +1 -0
- package/dist/debugGeometry.d.ts +4 -0
- package/dist/debugGeometry.d.ts.map +1 -0
- package/dist/debugGeometry.js +333 -0
- package/dist/debugGeometry.js.map +1 -0
- package/dist/enablePhysics3DGuards.d.ts +4 -0
- package/dist/enablePhysics3DGuards.d.ts.map +1 -0
- package/dist/enablePhysics3DGuards.js +155 -0
- package/dist/enablePhysics3DGuards.js.map +1 -0
- package/dist/explainPhysics3DCollision.d.ts +3 -0
- package/dist/explainPhysics3DCollision.d.ts.map +1 -0
- package/dist/explainPhysics3DCollision.js +30 -0
- package/dist/explainPhysics3DCollision.js.map +1 -0
- package/dist/explainPhysics3DJoints.d.ts +3 -0
- package/dist/explainPhysics3DJoints.d.ts.map +1 -0
- package/dist/explainPhysics3DJoints.js +32 -0
- package/dist/explainPhysics3DJoints.js.map +1 -0
- package/dist/explainPhysics3DStep.d.ts +3 -0
- package/dist/explainPhysics3DStep.d.ts.map +1 -0
- package/dist/explainPhysics3DStep.js +51 -0
- package/dist/explainPhysics3DStep.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/integrate.d.ts +6 -0
- package/dist/integrate.d.ts.map +1 -0
- package/dist/integrate.js +184 -0
- package/dist/integrate.js.map +1 -0
- package/dist/islands.d.ts +6 -0
- package/dist/islands.d.ts.map +1 -0
- package/dist/islands.js +333 -0
- package/dist/islands.js.map +1 -0
- package/dist/jointBreakage.d.ts +5 -0
- package/dist/jointBreakage.d.ts.map +1 -0
- package/dist/jointBreakage.js +44 -0
- package/dist/jointBreakage.js.map +1 -0
- package/dist/jointCollisionSuppression.d.ts +4 -0
- package/dist/jointCollisionSuppression.d.ts.map +1 -0
- package/dist/jointCollisionSuppression.js +40 -0
- package/dist/jointCollisionSuppression.js.map +1 -0
- package/dist/jointFactories.d.ts +9 -0
- package/dist/jointFactories.d.ts.map +1 -0
- package/dist/jointFactories.js +159 -0
- package/dist/jointFactories.js.map +1 -0
- package/dist/jointMath.d.ts +17 -0
- package/dist/jointMath.d.ts.map +1 -0
- package/dist/jointMath.js +361 -0
- package/dist/jointMath.js.map +1 -0
- package/dist/jointReaction.d.ts +8 -0
- package/dist/jointReaction.d.ts.map +1 -0
- package/dist/jointReaction.js +65 -0
- package/dist/jointReaction.js.map +1 -0
- package/dist/jointRegistry.d.ts +8 -0
- package/dist/jointRegistry.d.ts.map +1 -0
- package/dist/jointRegistry.js +144 -0
- package/dist/jointRegistry.js.map +1 -0
- package/dist/jointRows.d.ts +33 -0
- package/dist/jointRows.d.ts.map +1 -0
- package/dist/jointRows.js +352 -0
- package/dist/jointRows.js.map +1 -0
- package/dist/joints.d.ts +16 -0
- package/dist/joints.d.ts.map +1 -0
- package/dist/joints.js +1060 -0
- package/dist/joints.js.map +1 -0
- package/dist/massProperties.d.ts +13 -0
- package/dist/massProperties.d.ts.map +1 -0
- package/dist/massProperties.js +462 -0
- package/dist/massProperties.js.map +1 -0
- package/dist/material.d.ts +3 -0
- package/dist/material.d.ts.map +1 -0
- package/dist/material.js +13 -0
- package/dist/material.js.map +1 -0
- package/dist/nodeSync.d.ts +3 -0
- package/dist/nodeSync.d.ts.map +1 -0
- package/dist/nodeSync.js +16 -0
- package/dist/nodeSync.js.map +1 -0
- package/dist/ownership.d.ts +8 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/ownership.js +26 -0
- package/dist/ownership.js.map +1 -0
- package/dist/physics3DBroadphasePublication.d.ts +5 -0
- package/dist/physics3DBroadphasePublication.d.ts.map +1 -0
- package/dist/physics3DBroadphasePublication.js +49 -0
- package/dist/physics3DBroadphasePublication.js.map +1 -0
- package/dist/physics3DSpatialIndexingGuards.d.ts +4 -0
- package/dist/physics3DSpatialIndexingGuards.d.ts.map +1 -0
- package/dist/physics3DSpatialIndexingGuards.js +11 -0
- package/dist/physics3DSpatialIndexingGuards.js.map +1 -0
- package/dist/registerBuiltInPhysics3DJointSolvers.d.ts +3 -0
- package/dist/registerBuiltInPhysics3DJointSolvers.d.ts.map +1 -0
- package/dist/registerBuiltInPhysics3DJointSolvers.js +15 -0
- package/dist/registerBuiltInPhysics3DJointSolvers.js.map +1 -0
- package/dist/solver.d.ts +8 -0
- package/dist/solver.d.ts.map +1 -0
- package/dist/solver.js +460 -0
- package/dist/solver.js.map +1 -0
- package/dist/step.d.ts +5 -0
- package/dist/step.d.ts.map +1 -0
- package/dist/step.js +302 -0
- package/dist/step.js.map +1 -0
- package/dist/stepValidation.d.ts +13 -0
- package/dist/stepValidation.d.ts.map +1 -0
- package/dist/stepValidation.js +284 -0
- package/dist/stepValidation.js.map +1 -0
- package/dist/symmetricTensor.d.ts +11 -0
- package/dist/symmetricTensor.d.ts.map +1 -0
- package/dist/symmetricTensor.js +150 -0
- package/dist/symmetricTensor.js.map +1 -0
- package/dist/world.d.ts +27 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +762 -0
- package/dist/world.js.map +1 -0
- package/dist/worldQueries.d.ts +11 -0
- package/dist/worldQueries.d.ts.map +1 -0
- package/dist/worldQueries.js +409 -0
- package/dist/worldQueries.js.map +1 -0
- package/package.json +55 -0
- package/src/broadphase.test.ts +265 -0
- package/src/colliderCollision.test.ts +57 -0
- package/src/colliderTransform.test.ts +221 -0
- package/src/contactIntake.test.ts +370 -0
- package/src/contacts.test.ts +64 -0
- package/src/continuous.test.ts +679 -0
- package/src/debugGeometry.test.ts +288 -0
- package/src/enablePhysics3DGuards.test.ts +206 -0
- package/src/explainPhysics3DCollision.test.ts +78 -0
- package/src/explainPhysics3DJoints.test.ts +101 -0
- package/src/explainPhysics3DStep.test.ts +142 -0
- package/src/integrate.test.ts +279 -0
- package/src/islands.test.ts +384 -0
- package/src/jointBreakage.test.ts +143 -0
- package/src/jointCollisionSuppression.test.ts +94 -0
- package/src/jointFactories.test.ts +244 -0
- package/src/jointMath.test.ts +646 -0
- package/src/jointReaction.test.ts +241 -0
- package/src/jointRegistry.test.ts +229 -0
- package/src/jointRows.test.ts +548 -0
- package/src/joints.test.ts +1066 -0
- package/src/massProperties.test.ts +611 -0
- package/src/material.test.ts +40 -0
- package/src/nodeSync.test.ts +50 -0
- package/src/ownership.test.ts +61 -0
- package/src/physics3DBroadphasePublication.test.ts +63 -0
- package/src/physics3DSpatialIndexingGuards.test.ts +43 -0
- package/src/registerBuiltInPhysics3DJointSolvers.test.ts +51 -0
- package/src/solver.test.ts +567 -0
- package/src/step.test.ts +490 -0
- package/src/stepValidation.test.ts +346 -0
- package/src/stress.test.ts +526 -0
- package/src/symmetricTensor.test.ts +166 -0
- package/src/world.test.ts +1127 -0
- package/src/worldQueries.test.ts +565 -0
package/dist/world.js
ADDED
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import { createUniformGridSpatialBackend3D } from '@flighthq/spatial/contract';
|
|
2
|
+
import { synchronizePhysics3DBroadphase } from './broadphase';
|
|
3
|
+
import { createPhysics3DColliderWorldShape } from './colliderTransform';
|
|
4
|
+
import { refreshRigidBody3DWorldInertia } from './integrate';
|
|
5
|
+
import { rebuildPhysics3DJointCollisionSuppressions } from './jointCollisionSuppression';
|
|
6
|
+
import { createPhysics3DMassData, setRigidBody3DMassData, updateRigidBody3DMassData } from './massProperties';
|
|
7
|
+
import { assertPhysics3DBodyNotStepping, assertPhysics3DWorldNotStepping, physics3DBodyOwners, physics3DColliderOwners, physics3DJointOwners, } from './ownership';
|
|
8
|
+
import { withdrawPhysics3DBroadphaseBody } from './physics3DBroadphasePublication';
|
|
9
|
+
import { applySymmetricTensor, TENSOR_XX, TENSOR_XY, TENSOR_XZ, TENSOR_YY, TENSOR_YZ, TENSOR_ZZ, } from './symmetricTensor';
|
|
10
|
+
// World and body lifecycle: allocation, membership, and the mutations that have to run through a
|
|
11
|
+
// function because something derived follows from them.
|
|
12
|
+
//
|
|
13
|
+
// A bare field assignment is the API wherever nothing follows from it — writing `body.linearDamping`
|
|
14
|
+
// is complete on its own. The setters here exist only where they are not: changing a body's type
|
|
15
|
+
// changes its inverse mass, teleporting it invalidates its world-space inertia, and both would leave a
|
|
16
|
+
// world quietly inconsistent if left to assignment.
|
|
17
|
+
// Adds an already-created body to the world and assigns its persistent index. Returns the index.
|
|
18
|
+
//
|
|
19
|
+
// The index comes from the world's monotonic counter rather than the body array's length, so removing
|
|
20
|
+
// a body never lets a later one inherit its identity — which is what stops a stale contact or joint
|
|
21
|
+
// from being revived against a different body that happened to take its slot.
|
|
22
|
+
export function addPhysics3DBody(world, body) {
|
|
23
|
+
assertPhysics3DWorldNotStepping(world);
|
|
24
|
+
if (physics3DBodyOwners.has(body) || body.index !== -1 || world.bodies.includes(body)) {
|
|
25
|
+
throw new Error('Cannot add a rigid body that already belongs to a physics world');
|
|
26
|
+
}
|
|
27
|
+
const colliders = new Set();
|
|
28
|
+
for (const collider of body.colliders) {
|
|
29
|
+
if (colliders.has(collider))
|
|
30
|
+
throw new Error('Cannot add a rigid body containing the same collider twice');
|
|
31
|
+
colliders.add(collider);
|
|
32
|
+
const owner = physics3DColliderOwners.get(collider);
|
|
33
|
+
if (owner !== undefined && owner !== body) {
|
|
34
|
+
throw new Error('Cannot share a physics collider between rigid bodies');
|
|
35
|
+
}
|
|
36
|
+
if (body.type !== 'static' && isPhysics3DStaticSurfaceCollider(collider)) {
|
|
37
|
+
throw new Error('Triangle-mesh and heightfield colliders require a static rigid body');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
body.index = world.nextBodyIndex;
|
|
41
|
+
world.nextBodyIndex += 1;
|
|
42
|
+
world.bodies.push(body);
|
|
43
|
+
world.bodyByIndex.set(body.index, body);
|
|
44
|
+
physics3DBodyOwners.set(body, world);
|
|
45
|
+
for (const collider of colliders)
|
|
46
|
+
physics3DColliderOwners.set(collider, body);
|
|
47
|
+
// A collider-authored body derives its tensor here; a colliderless body may deliberately carry mass
|
|
48
|
+
// supplied through `setRigidBody3DMassData`, which is the custom-narrow-phase escape hatch.
|
|
49
|
+
if (colliders.size > 0)
|
|
50
|
+
updateRigidBody3DMassData(body);
|
|
51
|
+
refreshRigidBody3DWorldInertia(body);
|
|
52
|
+
// Publish immediately rather than waiting for the next step, so a query made between insertion and
|
|
53
|
+
// the first step finds the body where the caller just put it.
|
|
54
|
+
synchronizePhysics3DBroadphase(world);
|
|
55
|
+
return body.index;
|
|
56
|
+
}
|
|
57
|
+
// Attaches a collider to a body and rebuilds the mass properties that follow from it. Returns the
|
|
58
|
+
// collider.
|
|
59
|
+
//
|
|
60
|
+
// Mass, centre of mass, and inertia are all derived from collider geometry, so adding one changes what
|
|
61
|
+
// the body weighs and where it balances. Republishing the broadphase bounds in the same breath is what
|
|
62
|
+
// keeps a query made between two steps from missing geometry that already exists.
|
|
63
|
+
export function addPhysics3DCollider(world, body, collider) {
|
|
64
|
+
assertPhysics3DWorldNotStepping(world);
|
|
65
|
+
const bodyOwner = physics3DBodyOwners.get(body);
|
|
66
|
+
if (bodyOwner !== undefined && bodyOwner !== world) {
|
|
67
|
+
throw new Error('Cannot mutate a rigid body through a physics world that does not own it');
|
|
68
|
+
}
|
|
69
|
+
if (body.colliders.includes(collider)) {
|
|
70
|
+
throw new Error('Cannot add the same physics collider to a rigid body twice');
|
|
71
|
+
}
|
|
72
|
+
const owner = physics3DColliderOwners.get(collider);
|
|
73
|
+
if (owner !== undefined && owner !== body) {
|
|
74
|
+
throw new Error('Cannot share a physics collider between rigid bodies');
|
|
75
|
+
}
|
|
76
|
+
if (body.type !== 'static' && isPhysics3DStaticSurfaceCollider(collider)) {
|
|
77
|
+
throw new Error('Triangle-mesh and heightfield colliders require a static rigid body');
|
|
78
|
+
}
|
|
79
|
+
body.colliders.push(collider);
|
|
80
|
+
physics3DColliderOwners.set(collider, body);
|
|
81
|
+
updateRigidBody3DMassData(body);
|
|
82
|
+
refreshRigidBody3DWorldInertia(body);
|
|
83
|
+
if (world.bodyByIndex.get(body.index) === body) {
|
|
84
|
+
// The pair's contacts are dropped rather than kept: the body's centre of mass just moved, so every
|
|
85
|
+
// cached lever arm is measured from a point the body no longer balances on.
|
|
86
|
+
invalidatePhysics3DBodyConstraints(world, body.index);
|
|
87
|
+
wakePhysics3DBody(body);
|
|
88
|
+
synchronizePhysics3DBroadphase(world);
|
|
89
|
+
}
|
|
90
|
+
return collider;
|
|
91
|
+
}
|
|
92
|
+
// Accumulates a finite force at the body's centre of mass, to be consumed by the next step. Returning
|
|
93
|
+
// false makes an ignored non-dynamic or invalid action observable without letting it poison the world.
|
|
94
|
+
export function applyPhysics3DForce(body, x, y, z) {
|
|
95
|
+
assertPhysics3DBodyNotStepping(body);
|
|
96
|
+
if (body.type !== 'dynamic' || !isFinitePhysics3DVector(x, y, z))
|
|
97
|
+
return false;
|
|
98
|
+
body.forceX += x;
|
|
99
|
+
body.forceY += y;
|
|
100
|
+
body.forceZ += z;
|
|
101
|
+
if (x !== 0 || y !== 0 || z !== 0)
|
|
102
|
+
wakePhysics3DBody(body);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
// Accumulates a force at a world-space point, which produces both a force at the centre of mass and the
|
|
106
|
+
// torque of its lever arm. This is the difference between pushing a crate and spinning it.
|
|
107
|
+
export function applyPhysics3DForceAtPoint(body, x, y, z, pointX, pointY, pointZ) {
|
|
108
|
+
assertPhysics3DBodyNotStepping(body);
|
|
109
|
+
if (body.type !== 'dynamic' ||
|
|
110
|
+
!isFinitePhysics3DVector(x, y, z) ||
|
|
111
|
+
!isFinitePhysics3DVector(pointX, pointY, pointZ)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
body.forceX += x;
|
|
115
|
+
body.forceY += y;
|
|
116
|
+
body.forceZ += z;
|
|
117
|
+
if (!body.fixedRotation) {
|
|
118
|
+
writeRigidBody3DWorldCenter(body, scratchCenter);
|
|
119
|
+
const rX = pointX - scratchCenter[0];
|
|
120
|
+
const rY = pointY - scratchCenter[1];
|
|
121
|
+
const rZ = pointZ - scratchCenter[2];
|
|
122
|
+
body.torqueX += rY * z - rZ * y;
|
|
123
|
+
body.torqueY += rZ * x - rX * z;
|
|
124
|
+
body.torqueZ += rX * y - rY * x;
|
|
125
|
+
}
|
|
126
|
+
if (x !== 0 || y !== 0 || z !== 0)
|
|
127
|
+
wakePhysics3DBody(body);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Applies an instantaneous change in momentum at the centre of mass, bypassing the force accumulator.
|
|
131
|
+
// An impulse is what a jump or a hit is: a velocity change now, not a force integrated over the step.
|
|
132
|
+
export function applyPhysics3DLinearImpulse(body, x, y, z) {
|
|
133
|
+
assertPhysics3DBodyNotStepping(body);
|
|
134
|
+
if (body.type !== 'dynamic' || !isFinitePhysics3DVector(x, y, z))
|
|
135
|
+
return false;
|
|
136
|
+
body.velocityX += x * body.inverseMass;
|
|
137
|
+
body.velocityY += y * body.inverseMass;
|
|
138
|
+
body.velocityZ += z * body.inverseMass;
|
|
139
|
+
if (x !== 0 || y !== 0 || z !== 0)
|
|
140
|
+
wakePhysics3DBody(body);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
// Applies a finite instantaneous impulse at a world-space point. The linear half changes momentum at
|
|
144
|
+
// the centre; the lever arm's cross product is transformed through the current world inverse inertia
|
|
145
|
+
// to produce angular velocity. This is the impulse counterpart to `applyPhysics3DForceAtPoint`.
|
|
146
|
+
export function applyPhysics3DLinearImpulseAtPoint(body, x, y, z, pointX, pointY, pointZ) {
|
|
147
|
+
assertPhysics3DBodyNotStepping(body);
|
|
148
|
+
if (body.type !== 'dynamic' ||
|
|
149
|
+
!isFinitePhysics3DVector(x, y, z) ||
|
|
150
|
+
!isFinitePhysics3DVector(pointX, pointY, pointZ)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
body.velocityX += x * body.inverseMass;
|
|
154
|
+
body.velocityY += y * body.inverseMass;
|
|
155
|
+
body.velocityZ += z * body.inverseMass;
|
|
156
|
+
if (!body.fixedRotation) {
|
|
157
|
+
writeRigidBody3DWorldCenter(body, scratchCenter);
|
|
158
|
+
const rX = pointX - scratchCenter[0];
|
|
159
|
+
const rY = pointY - scratchCenter[1];
|
|
160
|
+
const rZ = pointZ - scratchCenter[2];
|
|
161
|
+
const angularImpulseX = rY * z - rZ * y;
|
|
162
|
+
const angularImpulseY = rZ * x - rX * z;
|
|
163
|
+
const angularImpulseZ = rX * y - rY * x;
|
|
164
|
+
// Mass data can be authored before insertion, when there is no world step to refresh this derived
|
|
165
|
+
// tensor. Refresh here so an immediate at-point impulse observes the body the caller just authored.
|
|
166
|
+
refreshRigidBody3DWorldInertia(body);
|
|
167
|
+
readRigidBody3DWorldInverseInertia(body, scratchInverseInertia);
|
|
168
|
+
applySymmetricTensor(scratchInverseInertia, angularImpulseX, angularImpulseY, angularImpulseZ, scratchVector);
|
|
169
|
+
body.angularVelocityX += scratchVector[0];
|
|
170
|
+
body.angularVelocityY += scratchVector[1];
|
|
171
|
+
body.angularVelocityZ += scratchVector[2];
|
|
172
|
+
}
|
|
173
|
+
if (x !== 0 || y !== 0 || z !== 0)
|
|
174
|
+
wakePhysics3DBody(body);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
// Accumulates a torque about the centre of mass, to be consumed by the next step.
|
|
178
|
+
export function applyPhysics3DTorque(body, x, y, z) {
|
|
179
|
+
assertPhysics3DBodyNotStepping(body);
|
|
180
|
+
if (body.type !== 'dynamic' || body.fixedRotation || !isFinitePhysics3DVector(x, y, z))
|
|
181
|
+
return false;
|
|
182
|
+
body.torqueX += x;
|
|
183
|
+
body.torqueY += y;
|
|
184
|
+
body.torqueZ += z;
|
|
185
|
+
if (x !== 0 || y !== 0 || z !== 0)
|
|
186
|
+
wakePhysics3DBody(body);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
// Allocates a collider around an authored LOCAL shape, with its world-space counterpart sized and ready.
|
|
190
|
+
//
|
|
191
|
+
// The local shape is CLONED rather than referenced, because a caller reusing one shape object across
|
|
192
|
+
// several colliders would otherwise have every one of them follow a later edit to it. The world shape is
|
|
193
|
+
// allocated here and only ever mutated in place afterwards, which is what makes the per-step transform
|
|
194
|
+
// allocation-free.
|
|
195
|
+
export function createPhysics3DCollider(local, material, filter, sensor = false) {
|
|
196
|
+
const ownedLocal = cloneCollisionColliderShape3D(local);
|
|
197
|
+
return {
|
|
198
|
+
local: ownedLocal,
|
|
199
|
+
world: createPhysics3DColliderWorldShape(ownedLocal),
|
|
200
|
+
material: {
|
|
201
|
+
density: material?.density ?? 1,
|
|
202
|
+
friction: material?.friction ?? 0.2,
|
|
203
|
+
restitution: material?.restitution ?? 0,
|
|
204
|
+
},
|
|
205
|
+
filter: {
|
|
206
|
+
categoryBits: filter?.categoryBits ?? 1,
|
|
207
|
+
maskBits: filter?.maskBits ?? 0xffff,
|
|
208
|
+
groupIndex: filter?.groupIndex ?? 0,
|
|
209
|
+
},
|
|
210
|
+
sensor,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// The default sequential-impulse tuning. Eight velocity iterations and three position iterations are
|
|
214
|
+
// the values a Box2D-lineage solver converges acceptably at for ordinary scenes; a scene of tall stacks
|
|
215
|
+
// wants more of the first, and one with deep initial overlap more of the second.
|
|
216
|
+
export function createPhysics3DSequentialImpulseConfig() {
|
|
217
|
+
return {
|
|
218
|
+
velocityIterations: 8,
|
|
219
|
+
positionIterations: 3,
|
|
220
|
+
penetrationSlop: 0.005,
|
|
221
|
+
positionCorrection: 0.2,
|
|
222
|
+
restitutionThreshold: 1,
|
|
223
|
+
warmStarting: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// The default solver tuning. `substeps` is 1, which reproduces a single discrete step per call.
|
|
227
|
+
export function createPhysics3DSolverConfig() {
|
|
228
|
+
return {
|
|
229
|
+
allowSleeping: true,
|
|
230
|
+
sleepLinearThreshold: 0.01,
|
|
231
|
+
sleepAngularThreshold: 0.02,
|
|
232
|
+
timeToSleep: 0.5,
|
|
233
|
+
substeps: 1,
|
|
234
|
+
continuousCollision: false,
|
|
235
|
+
maxCcdSubsteps: 4,
|
|
236
|
+
maxCcdRotationSubsteps: 64,
|
|
237
|
+
sequentialImpulse: createPhysics3DSequentialImpulseConfig(),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// Allocates an empty world under earth gravity along -Y.
|
|
241
|
+
//
|
|
242
|
+
// The broadphase defaults to a uniform grid and is a constructor PARAMETER rather than a fixed choice:
|
|
243
|
+
// `SpatialIndexBackend3D` is the swap point, so a caller with a scene the grid suits badly — a sparse
|
|
244
|
+
// world spanning kilometres or carrying bodies at widely different scales, say — hands over the BVH or
|
|
245
|
+
// its own implementation without this package changing.
|
|
246
|
+
export function createPhysics3DWorld(index) {
|
|
247
|
+
return {
|
|
248
|
+
version: Physics3DWorldVersion,
|
|
249
|
+
bodies: [],
|
|
250
|
+
bodyByIndex: new Map(),
|
|
251
|
+
// One world unit per cell, matching `createPhysics2DWorld`. A physics world is authored in metres and
|
|
252
|
+
// its bodies are metre-scale, so `@flighthq/spatial`'s own 128-unit default — sized for a scene-graph
|
|
253
|
+
// culling index measured in pixels — would put an entire scene in one cell and reduce the broadphase
|
|
254
|
+
// to a full pairwise scan.
|
|
255
|
+
index: index ?? createUniformGridSpatialBackend3D(1),
|
|
256
|
+
contacts: [],
|
|
257
|
+
joints: [],
|
|
258
|
+
jointSolvers: new Map(),
|
|
259
|
+
jointCollisionSuppressions: new Map(),
|
|
260
|
+
events: { began: [], ended: [] },
|
|
261
|
+
jointEvents: { broke: [] },
|
|
262
|
+
contactHooks: { preSolve: null, postSolve: null },
|
|
263
|
+
solver: { constraints: [], constraintByContact: new Map() },
|
|
264
|
+
config: createPhysics3DSolverConfig(),
|
|
265
|
+
islandParents: new Map(),
|
|
266
|
+
islandSleepTimers: new Map(),
|
|
267
|
+
solveIslandByRoot: new Map(),
|
|
268
|
+
solveIslandRoots: [],
|
|
269
|
+
solveIslandBodyStarts: [],
|
|
270
|
+
solveIslandBodyCounts: [],
|
|
271
|
+
solveIslandContactStarts: [],
|
|
272
|
+
solveIslandContactCounts: [],
|
|
273
|
+
solveIslandJointStarts: [],
|
|
274
|
+
solveIslandJointCounts: [],
|
|
275
|
+
solveIslandBodyIndices: [],
|
|
276
|
+
solveIslandContactIndices: [],
|
|
277
|
+
solveIslandJointIndices: [],
|
|
278
|
+
solveIslandCursors: [],
|
|
279
|
+
gravityX: 0,
|
|
280
|
+
gravityY: -9.80665,
|
|
281
|
+
gravityZ: 0,
|
|
282
|
+
previousTimestep: 0,
|
|
283
|
+
nextBodyIndex: 0,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// Allocates a body at rest at the origin, with no mass. It is not in any world until added, and it will
|
|
287
|
+
// not move until given mass: a dynamic body with zero mass carries a zero inverse mass, which is the
|
|
288
|
+
// same immovable sentinel a static body carries.
|
|
289
|
+
export function createRigidBody3D(type = 'dynamic') {
|
|
290
|
+
return {
|
|
291
|
+
index: -1,
|
|
292
|
+
type,
|
|
293
|
+
x: 0,
|
|
294
|
+
y: 0,
|
|
295
|
+
z: 0,
|
|
296
|
+
orientationX: 0,
|
|
297
|
+
orientationY: 0,
|
|
298
|
+
orientationZ: 0,
|
|
299
|
+
orientationW: 1,
|
|
300
|
+
velocityX: 0,
|
|
301
|
+
velocityY: 0,
|
|
302
|
+
velocityZ: 0,
|
|
303
|
+
angularVelocityX: 0,
|
|
304
|
+
angularVelocityY: 0,
|
|
305
|
+
angularVelocityZ: 0,
|
|
306
|
+
forceX: 0,
|
|
307
|
+
forceY: 0,
|
|
308
|
+
forceZ: 0,
|
|
309
|
+
torqueX: 0,
|
|
310
|
+
torqueY: 0,
|
|
311
|
+
torqueZ: 0,
|
|
312
|
+
mass: 0,
|
|
313
|
+
inverseMass: 0,
|
|
314
|
+
inertiaXX: 0,
|
|
315
|
+
inertiaYY: 0,
|
|
316
|
+
inertiaZZ: 0,
|
|
317
|
+
inertiaXY: 0,
|
|
318
|
+
inertiaXZ: 0,
|
|
319
|
+
inertiaYZ: 0,
|
|
320
|
+
inverseInertiaXX: 0,
|
|
321
|
+
inverseInertiaYY: 0,
|
|
322
|
+
inverseInertiaZZ: 0,
|
|
323
|
+
inverseInertiaXY: 0,
|
|
324
|
+
inverseInertiaXZ: 0,
|
|
325
|
+
inverseInertiaYZ: 0,
|
|
326
|
+
inverseInertiaWorldXX: 0,
|
|
327
|
+
inverseInertiaWorldYY: 0,
|
|
328
|
+
inverseInertiaWorldZZ: 0,
|
|
329
|
+
inverseInertiaWorldXY: 0,
|
|
330
|
+
inverseInertiaWorldXZ: 0,
|
|
331
|
+
inverseInertiaWorldYZ: 0,
|
|
332
|
+
centerX: 0,
|
|
333
|
+
centerY: 0,
|
|
334
|
+
centerZ: 0,
|
|
335
|
+
linearDamping: 0,
|
|
336
|
+
angularDamping: 0,
|
|
337
|
+
gravityScale: 1,
|
|
338
|
+
fixedRotation: false,
|
|
339
|
+
bullet: false,
|
|
340
|
+
sleeping: false,
|
|
341
|
+
sleepEnabled: true,
|
|
342
|
+
sleepTimer: 0,
|
|
343
|
+
colliders: [],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// Looks a body up by its persistent index, or null when no body in this world carries it. The
|
|
347
|
+
// expected-failure sentinel: a caller holding an index across a removal gets null, not a throw.
|
|
348
|
+
export function findPhysics3DBody(world, index) {
|
|
349
|
+
return world.bodyByIndex.get(index) ?? null;
|
|
350
|
+
}
|
|
351
|
+
// Upgrades a reconstructed serialized world to the current plain-data shape. Runtime registries remain
|
|
352
|
+
// the format layer's responsibility; this function owns only versioned physics fields and replaces
|
|
353
|
+
// solver caches, which are answers about a previous process and must never survive a load.
|
|
354
|
+
export function hydratePhysics3DWorld(world) {
|
|
355
|
+
assertPhysics3DWorldNotStepping(world);
|
|
356
|
+
const serializedVersion = world.version;
|
|
357
|
+
if (serializedVersion !== undefined && typeof serializedVersion !== 'number')
|
|
358
|
+
return false;
|
|
359
|
+
const version = serializedVersion ?? 0;
|
|
360
|
+
if (!Number.isSafeInteger(version) || version < 0 || version > Physics3DWorldVersion) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
if (version === Physics3DWorldVersion)
|
|
364
|
+
return true;
|
|
365
|
+
const legacyWorld = world;
|
|
366
|
+
const legacyConfig = world.config;
|
|
367
|
+
if (version < 2) {
|
|
368
|
+
legacyWorld.index ??= createUniformGridSpatialBackend3D(1);
|
|
369
|
+
for (const body of world.bodies) {
|
|
370
|
+
const legacyBody = body;
|
|
371
|
+
legacyBody.colliders ??= [];
|
|
372
|
+
}
|
|
373
|
+
for (const contact of world.contacts) {
|
|
374
|
+
const legacyContact = contact;
|
|
375
|
+
legacyContact.colliderA ??= 0;
|
|
376
|
+
legacyContact.colliderB ??= 0;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
legacyWorld.jointEvents ??= { broke: [] };
|
|
380
|
+
legacyConfig.maxCcdRotationSubsteps ??= createPhysics3DSolverConfig().maxCcdRotationSubsteps;
|
|
381
|
+
world.solver.constraints = [];
|
|
382
|
+
world.solver.constraintByContact = new Map();
|
|
383
|
+
delete legacyWorld.solver.constraintByPair;
|
|
384
|
+
world.version = Physics3DWorldVersion;
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
// Rebuilds a collider's derived state after its authored LOCAL shape has been edited in place. Returns
|
|
388
|
+
// false when the collider does not belong to the body.
|
|
389
|
+
//
|
|
390
|
+
// This exists because the local shape is a plain mutable record, so a caller CAN edit it — and two
|
|
391
|
+
// things then no longer follow: the world shape may need a different kind or a different-sized point
|
|
392
|
+
// array, and the body's mass, centre, and inertia were all derived from the old geometry. Skipping this
|
|
393
|
+
// leaves a body whose tensor describes a shape it no longer has.
|
|
394
|
+
export function invalidatePhysics3DCollider(world, body, collider) {
|
|
395
|
+
assertPhysics3DWorldNotStepping(world);
|
|
396
|
+
const bodyOwner = physics3DBodyOwners.get(body);
|
|
397
|
+
if (bodyOwner !== undefined && bodyOwner !== world)
|
|
398
|
+
return false;
|
|
399
|
+
if (!body.colliders.includes(collider))
|
|
400
|
+
return false;
|
|
401
|
+
collider.world = createPhysics3DColliderWorldShape(collider.local);
|
|
402
|
+
updateRigidBody3DMassData(body);
|
|
403
|
+
refreshRigidBody3DWorldInertia(body);
|
|
404
|
+
if (world.bodyByIndex.get(body.index) === body) {
|
|
405
|
+
invalidatePhysics3DBodyConstraints(world, body.index);
|
|
406
|
+
wakePhysics3DBody(body);
|
|
407
|
+
synchronizePhysics3DBroadphase(world);
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
// Removes a body and every contact and joint that referenced it. Returns false when the body is not in
|
|
412
|
+
// this world.
|
|
413
|
+
//
|
|
414
|
+
// Constraints go with it because a contact naming a removed body would resolve to null inside a solver
|
|
415
|
+
// loop, and the alternative — a null check per constraint per iteration — pays on every step for a case
|
|
416
|
+
// that only arises at removal.
|
|
417
|
+
//
|
|
418
|
+
// A joint dropped here leaves by the SAME two exits `removePhysics3DJoint` uses: its ownership entry is
|
|
419
|
+
// released, and the suppression index is rebuilt. Splicing it out of the array alone leaves a joint that
|
|
420
|
+
// no world holds but that every world still refuses to accept, and leaves the pair it connected suppressed
|
|
421
|
+
// against a joint that no longer exists — a contact that silently never reports.
|
|
422
|
+
export function removePhysics3DBody(world, body) {
|
|
423
|
+
assertPhysics3DWorldNotStepping(world);
|
|
424
|
+
const at = world.bodies.indexOf(body);
|
|
425
|
+
if (at < 0)
|
|
426
|
+
return false;
|
|
427
|
+
world.bodies.splice(at, 1);
|
|
428
|
+
world.bodyByIndex.delete(body.index);
|
|
429
|
+
const index = body.index;
|
|
430
|
+
invalidatePhysics3DBodyContacts(world, index);
|
|
431
|
+
for (let i = world.joints.length - 1; i >= 0; i--) {
|
|
432
|
+
const joint = world.joints[i];
|
|
433
|
+
const solver = world.jointSolvers.get(joint.kind);
|
|
434
|
+
const usesBodyA = solver?.usesBodyA !== false;
|
|
435
|
+
const removesBodyA = usesBodyA && joint.bodyA === index;
|
|
436
|
+
const removesBodyB = joint.bodyB === index;
|
|
437
|
+
if (!removesBodyA && !removesBodyB)
|
|
438
|
+
continue;
|
|
439
|
+
if (solver !== undefined && usesBodyA) {
|
|
440
|
+
const otherIndex = removesBodyA ? joint.bodyB : joint.bodyA;
|
|
441
|
+
const other = findPhysics3DBody(world, otherIndex);
|
|
442
|
+
if (other !== null)
|
|
443
|
+
wakePhysics3DBody(other);
|
|
444
|
+
}
|
|
445
|
+
world.joints.splice(i, 1);
|
|
446
|
+
physics3DJointOwners.delete(joint);
|
|
447
|
+
}
|
|
448
|
+
rebuildPhysics3DJointCollisionSuppressions(world);
|
|
449
|
+
withdrawPhysics3DBroadphaseBody(world, index);
|
|
450
|
+
body.index = -1;
|
|
451
|
+
physics3DBodyOwners.delete(body);
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
// Detaches a collider from a body and rebuilds what followed from it. Returns false when the collider
|
|
455
|
+
// does not belong to the body.
|
|
456
|
+
//
|
|
457
|
+
// The remaining colliders' INDICES shift, which is why every contact naming this body is dropped rather
|
|
458
|
+
// than repaired: a contact stores `colliderA`/`colliderB` as positions in the list, and a surviving
|
|
459
|
+
// contact would silently come to name a different piece of geometry. They regenerate on the next step.
|
|
460
|
+
export function removePhysics3DCollider(world, body, collider) {
|
|
461
|
+
assertPhysics3DWorldNotStepping(world);
|
|
462
|
+
const bodyOwner = physics3DBodyOwners.get(body);
|
|
463
|
+
if (bodyOwner !== undefined && bodyOwner !== world)
|
|
464
|
+
return false;
|
|
465
|
+
const at = body.colliders.indexOf(collider);
|
|
466
|
+
if (at < 0)
|
|
467
|
+
return false;
|
|
468
|
+
body.colliders.splice(at, 1);
|
|
469
|
+
physics3DColliderOwners.delete(collider);
|
|
470
|
+
updateRigidBody3DMassData(body);
|
|
471
|
+
refreshRigidBody3DWorldInertia(body);
|
|
472
|
+
if (world.bodyByIndex.get(body.index) === body) {
|
|
473
|
+
invalidatePhysics3DBodyConstraints(world, body.index);
|
|
474
|
+
wakePhysics3DBody(body);
|
|
475
|
+
synchronizePhysics3DBroadphase(world);
|
|
476
|
+
}
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
// Changes whether a dynamic body receives continuous collision treatment. The policy is retained across
|
|
480
|
+
// body-type changes, and either transition wakes an owned body so a newly enabled bullet cannot remain
|
|
481
|
+
// outside the continuous path until something else disturbs it.
|
|
482
|
+
export function setPhysics3DBodyBullet(body, bullet) {
|
|
483
|
+
assertPhysics3DBodyNotStepping(body);
|
|
484
|
+
if (typeof bullet !== 'boolean')
|
|
485
|
+
return false;
|
|
486
|
+
if (body.bullet === bullet)
|
|
487
|
+
return true;
|
|
488
|
+
body.bullet = bullet;
|
|
489
|
+
wakePhysics3DBody(body);
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
// Opts a dynamic body into or out of rotating at all, rebuilding its inverse inertia.
|
|
493
|
+
//
|
|
494
|
+
// A fixed-rotation body keeps its translational mass and exposes a zero inverse inertia tensor to every
|
|
495
|
+
// contact and joint equation — the same sentinel a static body uses, applied to rotation alone. Its
|
|
496
|
+
// angular velocity is cleared, because leaving one behind would let a body that cannot be rotated by
|
|
497
|
+
// anything keep spinning forever from whatever it had before.
|
|
498
|
+
export function setPhysics3DBodyFixedRotation(body, fixedRotation) {
|
|
499
|
+
assertPhysics3DBodyNotStepping(body);
|
|
500
|
+
if (typeof fixedRotation !== 'boolean')
|
|
501
|
+
return false;
|
|
502
|
+
if (body.fixedRotation === fixedRotation)
|
|
503
|
+
return true;
|
|
504
|
+
const world = physics3DBodyOwners.get(body);
|
|
505
|
+
if (world !== undefined)
|
|
506
|
+
invalidatePhysics3DBodyConstraints(world, body.index);
|
|
507
|
+
body.fixedRotation = fixedRotation;
|
|
508
|
+
if (fixedRotation) {
|
|
509
|
+
body.angularVelocityX = 0;
|
|
510
|
+
body.angularVelocityY = 0;
|
|
511
|
+
body.angularVelocityZ = 0;
|
|
512
|
+
body.torqueX = 0;
|
|
513
|
+
body.torqueY = 0;
|
|
514
|
+
body.torqueZ = 0;
|
|
515
|
+
}
|
|
516
|
+
refreshRigidBody3DMass(body);
|
|
517
|
+
wakePhysics3DBody(body);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
// Changes whether this body may participate in sleeping. Both transitions reset stillness so enabling
|
|
521
|
+
// sleep still requires one new uninterrupted quiet interval, and disabling it cannot leave a body frozen.
|
|
522
|
+
export function setPhysics3DBodySleepEnabled(body, sleepEnabled) {
|
|
523
|
+
assertPhysics3DBodyNotStepping(body);
|
|
524
|
+
if (typeof sleepEnabled !== 'boolean')
|
|
525
|
+
return false;
|
|
526
|
+
if (body.sleepEnabled === sleepEnabled)
|
|
527
|
+
return true;
|
|
528
|
+
body.sleepEnabled = sleepEnabled;
|
|
529
|
+
wakePhysics3DBody(body);
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
// Teleports a body, refreshing the world-space inertia its new orientation implies.
|
|
533
|
+
//
|
|
534
|
+
// The quaternion is normalized on the way in rather than trusted: a caller composing one from euler
|
|
535
|
+
// angles or interpolating between two will hand over something fractionally off the unit sphere, and
|
|
536
|
+
// every rotation downstream would inherit the error.
|
|
537
|
+
export function setPhysics3DBodyTransform(body, x, y, z, orientationX, orientationY, orientationZ, orientationW) {
|
|
538
|
+
assertPhysics3DBodyNotStepping(body);
|
|
539
|
+
if (!isFinitePhysics3DVector(x, y, z) ||
|
|
540
|
+
!isFinitePhysics3DVector(orientationX, orientationY, orientationZ) ||
|
|
541
|
+
!Number.isFinite(orientationW)) {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
const world = physics3DBodyOwners.get(body);
|
|
545
|
+
if (world !== undefined)
|
|
546
|
+
invalidatePhysics3DBodyConstraints(world, body.index);
|
|
547
|
+
body.x = x;
|
|
548
|
+
body.y = y;
|
|
549
|
+
body.z = z;
|
|
550
|
+
const lengthSquared = orientationX * orientationX +
|
|
551
|
+
orientationY * orientationY +
|
|
552
|
+
orientationZ * orientationZ +
|
|
553
|
+
orientationW * orientationW;
|
|
554
|
+
if (lengthSquared > 0) {
|
|
555
|
+
const scale = 1 / Math.sqrt(lengthSquared);
|
|
556
|
+
body.orientationX = orientationX * scale;
|
|
557
|
+
body.orientationY = orientationY * scale;
|
|
558
|
+
body.orientationZ = orientationZ * scale;
|
|
559
|
+
body.orientationW = orientationW * scale;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
body.orientationX = 0;
|
|
563
|
+
body.orientationY = 0;
|
|
564
|
+
body.orientationZ = 0;
|
|
565
|
+
body.orientationW = 1;
|
|
566
|
+
}
|
|
567
|
+
wakePhysics3DBody(body);
|
|
568
|
+
refreshRigidBody3DWorldInertia(body);
|
|
569
|
+
if (world !== undefined)
|
|
570
|
+
synchronizePhysics3DBroadphase(world);
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
// Changes how a body participates, rebuilding the mass properties that follow from it.
|
|
574
|
+
//
|
|
575
|
+
// Leaving a body's velocity behind when it becomes static would strand motion nothing can consume, so
|
|
576
|
+
// the velocities are cleared; becoming dynamic keeps them, since a body that was being pushed as
|
|
577
|
+
// kinematic is plausibly still moving.
|
|
578
|
+
export function setPhysics3DBodyType(body, type) {
|
|
579
|
+
assertPhysics3DBodyNotStepping(body);
|
|
580
|
+
if (type !== 'dynamic' && type !== 'kinematic' && type !== 'static')
|
|
581
|
+
return false;
|
|
582
|
+
if (type !== 'static' && body.colliders.some(isPhysics3DStaticSurfaceCollider))
|
|
583
|
+
return false;
|
|
584
|
+
if (body.type === type)
|
|
585
|
+
return true;
|
|
586
|
+
const world = physics3DBodyOwners.get(body);
|
|
587
|
+
if (world !== undefined)
|
|
588
|
+
invalidatePhysics3DBodyConstraints(world, body.index);
|
|
589
|
+
body.type = type;
|
|
590
|
+
if (type === 'static') {
|
|
591
|
+
body.velocityX = 0;
|
|
592
|
+
body.velocityY = 0;
|
|
593
|
+
body.velocityZ = 0;
|
|
594
|
+
body.angularVelocityX = 0;
|
|
595
|
+
body.angularVelocityY = 0;
|
|
596
|
+
body.angularVelocityZ = 0;
|
|
597
|
+
body.sleeping = false;
|
|
598
|
+
}
|
|
599
|
+
body.forceX = 0;
|
|
600
|
+
body.forceY = 0;
|
|
601
|
+
body.forceZ = 0;
|
|
602
|
+
body.torqueX = 0;
|
|
603
|
+
body.torqueY = 0;
|
|
604
|
+
body.torqueZ = 0;
|
|
605
|
+
refreshRigidBody3DMass(body);
|
|
606
|
+
refreshRigidBody3DWorldInertia(body);
|
|
607
|
+
wakePhysics3DBody(body);
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
// Wakes a body and resets its stillness timer. A no-op for a static body, which is neither awake nor
|
|
611
|
+
// asleep.
|
|
612
|
+
export function wakePhysics3DBody(body) {
|
|
613
|
+
assertPhysics3DBodyNotStepping(body);
|
|
614
|
+
if (body.type === 'static')
|
|
615
|
+
return;
|
|
616
|
+
body.sleeping = false;
|
|
617
|
+
body.sleepTimer = 0;
|
|
618
|
+
}
|
|
619
|
+
// Writes the body's centre of mass in WORLD space into `out`, rotating the local centre by the body's
|
|
620
|
+
// orientation and adding its position. The lever arm for every force and contact starts here.
|
|
621
|
+
export function writeRigidBody3DWorldCenter(body, out) {
|
|
622
|
+
const cX = body.centerX;
|
|
623
|
+
const cY = body.centerY;
|
|
624
|
+
const cZ = body.centerZ;
|
|
625
|
+
if (cX === 0 && cY === 0 && cZ === 0) {
|
|
626
|
+
out[0] = body.x;
|
|
627
|
+
out[1] = body.y;
|
|
628
|
+
out[2] = body.z;
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// Rotate by the quaternion as `v + 2 * cross(q.xyz, cross(q.xyz, v) + q.w * v)`.
|
|
632
|
+
const qX = body.orientationX;
|
|
633
|
+
const qY = body.orientationY;
|
|
634
|
+
const qZ = body.orientationZ;
|
|
635
|
+
const qW = body.orientationW;
|
|
636
|
+
const tX = qY * cZ - qZ * cY + qW * cX;
|
|
637
|
+
const tY = qZ * cX - qX * cZ + qW * cY;
|
|
638
|
+
const tZ = qX * cY - qY * cX + qW * cZ;
|
|
639
|
+
out[0] = body.x + cX + 2 * (qY * tZ - qZ * tY);
|
|
640
|
+
out[1] = body.y + cY + 2 * (qZ * tX - qX * tZ);
|
|
641
|
+
out[2] = body.z + cZ + 2 * (qX * tY - qY * tX);
|
|
642
|
+
}
|
|
643
|
+
// The serializable-shape version of `Physics3DWorld`. Bumped when a field a format layer would have
|
|
644
|
+
// written changes meaning, so a reconstructed world can be recognized as older and upgraded.
|
|
645
|
+
export const Physics3DWorldVersion = 4;
|
|
646
|
+
// Copies an authored shape so a collider owns its own geometry. The point list is copied too — sharing
|
|
647
|
+
// the array would let two colliders that look independent move together.
|
|
648
|
+
function cloneCollisionColliderShape3D(shape) {
|
|
649
|
+
if (shape.kind === 'convex')
|
|
650
|
+
return { kind: 'convex', points: shape.points.slice() };
|
|
651
|
+
if (shape.kind === 'triangle-mesh') {
|
|
652
|
+
return { ...shape, points: shape.points.slice(), indices: shape.indices.slice() };
|
|
653
|
+
}
|
|
654
|
+
if (shape.kind === 'heightfield')
|
|
655
|
+
return { ...shape, heights: shape.heights.slice() };
|
|
656
|
+
return { ...shape };
|
|
657
|
+
}
|
|
658
|
+
function isPhysics3DStaticSurfaceCollider(collider) {
|
|
659
|
+
return collider.local.kind === 'triangle-mesh' || collider.local.kind === 'heightfield';
|
|
660
|
+
}
|
|
661
|
+
// Drops every contact naming `index`, along with the solver's cached constraints. Called from the
|
|
662
|
+
// topology mutations that make an existing contact describe geometry the body no longer has.
|
|
663
|
+
function invalidatePhysics3DBodyConstraints(world, bodyIndex) {
|
|
664
|
+
invalidatePhysics3DBodyContacts(world, bodyIndex);
|
|
665
|
+
for (const joint of world.joints) {
|
|
666
|
+
const solver = world.jointSolvers.get(joint.kind);
|
|
667
|
+
const usesBodyA = solver?.usesBodyA !== false;
|
|
668
|
+
const connectedA = usesBodyA && joint.bodyA === bodyIndex;
|
|
669
|
+
const connectedB = joint.bodyB === bodyIndex;
|
|
670
|
+
if (!connectedA && !connectedB)
|
|
671
|
+
continue;
|
|
672
|
+
solver?.clearAccumulatedImpulses?.(joint);
|
|
673
|
+
joint.impulse0 = 0;
|
|
674
|
+
joint.impulse1 = 0;
|
|
675
|
+
joint.impulse2 = 0;
|
|
676
|
+
joint.impulse3 = 0;
|
|
677
|
+
joint.impulse4 = 0;
|
|
678
|
+
joint.impulse5 = 0;
|
|
679
|
+
if (solver === undefined || !usesBodyA)
|
|
680
|
+
continue;
|
|
681
|
+
const otherIndex = connectedA ? joint.bodyB : joint.bodyA;
|
|
682
|
+
if (otherIndex === bodyIndex)
|
|
683
|
+
continue;
|
|
684
|
+
const other = findPhysics3DBody(world, otherIndex);
|
|
685
|
+
if (other !== null)
|
|
686
|
+
wakePhysics3DBody(other);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function invalidatePhysics3DBodyContacts(world, index) {
|
|
690
|
+
for (let i = world.contacts.length - 1; i >= 0; i--) {
|
|
691
|
+
const contact = world.contacts[i];
|
|
692
|
+
if (contact.bodyA !== index && contact.bodyB !== index)
|
|
693
|
+
continue;
|
|
694
|
+
if (contact.enabled && !contact.sensor) {
|
|
695
|
+
const otherIndex = contact.bodyA === index ? contact.bodyB : contact.bodyA;
|
|
696
|
+
const other = findPhysics3DBody(world, otherIndex);
|
|
697
|
+
if (other !== null)
|
|
698
|
+
wakePhysics3DBody(other);
|
|
699
|
+
}
|
|
700
|
+
world.contacts.splice(i, 1);
|
|
701
|
+
}
|
|
702
|
+
removePhysics3DContactEventsForBody(world.events.began, index);
|
|
703
|
+
removePhysics3DContactEventsForBody(world.events.ended, index);
|
|
704
|
+
world.solver.constraints.length = 0;
|
|
705
|
+
world.solver.constraintByContact.clear();
|
|
706
|
+
clearPhysics3DSolveWorkspace(world);
|
|
707
|
+
}
|
|
708
|
+
function removePhysics3DContactEventsForBody(events, bodyIndex) {
|
|
709
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
710
|
+
if (events[i].bodyA === bodyIndex || events[i].bodyB === bodyIndex)
|
|
711
|
+
events.splice(i, 1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function clearPhysics3DSolveWorkspace(world) {
|
|
715
|
+
world.solveIslandByRoot.clear();
|
|
716
|
+
world.solveIslandRoots.length = 0;
|
|
717
|
+
world.solveIslandBodyStarts.length = 0;
|
|
718
|
+
world.solveIslandBodyCounts.length = 0;
|
|
719
|
+
world.solveIslandContactStarts.length = 0;
|
|
720
|
+
world.solveIslandContactCounts.length = 0;
|
|
721
|
+
world.solveIslandJointStarts.length = 0;
|
|
722
|
+
world.solveIslandJointCounts.length = 0;
|
|
723
|
+
world.solveIslandBodyIndices.length = 0;
|
|
724
|
+
world.solveIslandContactIndices.length = 0;
|
|
725
|
+
world.solveIslandJointIndices.length = 0;
|
|
726
|
+
world.solveIslandCursors.length = 0;
|
|
727
|
+
}
|
|
728
|
+
// Recomputes the inverse mass and local inverse inertia after something that changes a body's
|
|
729
|
+
// ELIGIBILITY to move rather than its geometry — a type change, a fixed-rotation change.
|
|
730
|
+
//
|
|
731
|
+
// It reads the forward mass and inertia the body already carries, which is exactly why those are
|
|
732
|
+
// stored: the inverse is zero for every body this is called on the way out of, so it holds nothing to
|
|
733
|
+
// recover them from.
|
|
734
|
+
function refreshRigidBody3DMass(body) {
|
|
735
|
+
scratchMassData.mass = body.mass;
|
|
736
|
+
scratchMassData.centerX = body.centerX;
|
|
737
|
+
scratchMassData.centerY = body.centerY;
|
|
738
|
+
scratchMassData.centerZ = body.centerZ;
|
|
739
|
+
scratchMassData.inertiaXX = body.inertiaXX;
|
|
740
|
+
scratchMassData.inertiaYY = body.inertiaYY;
|
|
741
|
+
scratchMassData.inertiaZZ = body.inertiaZZ;
|
|
742
|
+
scratchMassData.inertiaXY = body.inertiaXY;
|
|
743
|
+
scratchMassData.inertiaXZ = body.inertiaXZ;
|
|
744
|
+
scratchMassData.inertiaYZ = body.inertiaYZ;
|
|
745
|
+
setRigidBody3DMassData(body, scratchMassData);
|
|
746
|
+
}
|
|
747
|
+
function isFinitePhysics3DVector(x, y, z) {
|
|
748
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z);
|
|
749
|
+
}
|
|
750
|
+
function readRigidBody3DWorldInverseInertia(body, out) {
|
|
751
|
+
out[TENSOR_XX] = body.inverseInertiaWorldXX;
|
|
752
|
+
out[TENSOR_YY] = body.inverseInertiaWorldYY;
|
|
753
|
+
out[TENSOR_ZZ] = body.inverseInertiaWorldZZ;
|
|
754
|
+
out[TENSOR_XY] = body.inverseInertiaWorldXY;
|
|
755
|
+
out[TENSOR_XZ] = body.inverseInertiaWorldXZ;
|
|
756
|
+
out[TENSOR_YZ] = body.inverseInertiaWorldYZ;
|
|
757
|
+
}
|
|
758
|
+
const scratchCenter = [0, 0, 0];
|
|
759
|
+
const scratchInverseInertia = [0, 0, 0, 0, 0, 0];
|
|
760
|
+
const scratchMassData = createPhysics3DMassData();
|
|
761
|
+
const scratchVector = [0, 0, 0];
|
|
762
|
+
//# sourceMappingURL=world.js.map
|