@perplexdotgg/bounce 1.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/LICENSE +21 -0
- package/README.md +30 -0
- package/package.json +32 -0
- package/src/builders/ConvexHullBuilder.ts +437 -0
- package/src/builders/ConvexHullBuilder2d.ts +344 -0
- package/src/builders/ConvexHullBuilder3d.ts +1689 -0
- package/src/builders/HeightMapBuilder.ts +414 -0
- package/src/builders/TriangleMeshBuilder.ts +92 -0
- package/src/collision/CastShapesModule.ts +184 -0
- package/src/collision/CollideShapesModule.ts +152 -0
- package/src/collision/HeightMapCaster.ts +38 -0
- package/src/collision/HeightMapCollider.ts +33 -0
- package/src/collision/TriangleCaster.ts +249 -0
- package/src/collision/TriangleCollider.ts +308 -0
- package/src/collision/TriangleCollider2.ts +379 -0
- package/src/collision/activeEdge.ts +146 -0
- package/src/collision/cast/cast.ts +139 -0
- package/src/collision/cast/castCompoundVsCompound.ts +59 -0
- package/src/collision/cast/castCompoundVsConvex.ts +116 -0
- package/src/collision/cast/castConvexVsCompound.ts +123 -0
- package/src/collision/cast/castConvexVsConvex.ts +213 -0
- package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
- package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
- package/src/collision/cast/castRayVsCompound.ts +44 -0
- package/src/collision/cast/castRayVsConvex.ts +45 -0
- package/src/collision/cast/castRayVsHeightMap.ts +58 -0
- package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
- package/src/collision/closestPoints/closestPoints.ts +23 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
- package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
- package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
- package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
- package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
- package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
- package/src/collision/collide/collide.ts +146 -0
- package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
- package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
- package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
- package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
- package/src/collision/collide/collideConvexVsCompound.ts +57 -0
- package/src/collision/collide/collideConvexVsConvex.ts +225 -0
- package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
- package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
- package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
- package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
- package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
- package/src/collision/collide/collideSphereVsSphere.ts +81 -0
- package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
- package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
- package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
- package/src/collision/epa/StaticArray.ts +154 -0
- package/src/collision/epa/TriangleFactory.ts +32 -0
- package/src/collision/epa/arrays.ts +99 -0
- package/src/collision/epa/binaryHeap.ts +82 -0
- package/src/collision/epa/structs.ts +227 -0
- package/src/collision/gjk/GjkModule.ts +864 -0
- package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
- package/src/collision/gjk/SupportPoints.ts +50 -0
- package/src/collision/imp/MinkowskiDifference.ts +36 -0
- package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
- package/src/collision/imp/finalizeImpResult.ts +69 -0
- package/src/collision/imp/findContactImp.ts +196 -0
- package/src/collision/imp/imp.ts +28 -0
- package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
- package/src/collision/mpr/findPortal.ts +152 -0
- package/src/collision/mpr/mpr.ts +29 -0
- package/src/collision/mpr/updatePortal.ts +52 -0
- package/src/constraints/BaseConstraint.ts +50 -0
- package/src/constraints/ConstraintOptions.ts +22 -0
- package/src/constraints/ConstraintSolver.ts +119 -0
- package/src/constraints/DistanceConstraint.ts +229 -0
- package/src/constraints/FixedConstraint.ts +203 -0
- package/src/constraints/HingeConstraint.ts +460 -0
- package/src/constraints/PointConstraint.ts +108 -0
- package/src/constraints/components/AngleComponent.ts +226 -0
- package/src/constraints/components/AxisComponent.ts +263 -0
- package/src/constraints/components/HingeComponent.ts +215 -0
- package/src/constraints/components/Motor.ts +36 -0
- package/src/constraints/components/PointConstraintComponent.ts +179 -0
- package/src/constraints/components/RotationEulerComponent.ts +139 -0
- package/src/constraints/components/Spring.ts +30 -0
- package/src/constraints/components/SpringComponent.ts +71 -0
- package/src/constraints/types.ts +6 -0
- package/src/helpers.ts +147 -0
- package/src/index.ts +50 -0
- package/src/math/BasicTransform.ts +19 -0
- package/src/math/NumberValue.ts +13 -0
- package/src/math/isometry.ts +64 -0
- package/src/math/mat3.ts +529 -0
- package/src/math/mat4.ts +588 -0
- package/src/math/quat.ts +193 -0
- package/src/math/scalar.ts +81 -0
- package/src/math/tensor.ts +17 -0
- package/src/math/vec3.ts +589 -0
- package/src/math/vec4.ts +10 -0
- package/src/physics/Body.ts +581 -0
- package/src/physics/CollisionFilter.ts +52 -0
- package/src/physics/SleepModule.ts +163 -0
- package/src/physics/broadphase/BodyPairsModule.ts +363 -0
- package/src/physics/broadphase/BvhModule.ts +237 -0
- package/src/physics/broadphase/BvhTree.ts +803 -0
- package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
- package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
- package/src/physics/manifold/ContactManifold.ts +227 -0
- package/src/physics/manifold/ContactManifoldModule.ts +623 -0
- package/src/physics/manifold/Face.ts +119 -0
- package/src/physics/manifold/ManifoldCache.ts +116 -0
- package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
- package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
- package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
- package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
- package/src/physics/solver/ContactConstraintModule.ts +659 -0
- package/src/physics/solver/ManifoldConstraint.ts +420 -0
- package/src/physics/solver/estimateCollisionResponse.ts +146 -0
- package/src/shape/Aabb.ts +400 -0
- package/src/shape/Box.ts +231 -0
- package/src/shape/Capsule.ts +332 -0
- package/src/shape/CompoundShape.ts +288 -0
- package/src/shape/Convex.ts +130 -0
- package/src/shape/ConvexHull.ts +423 -0
- package/src/shape/Cylinder.ts +313 -0
- package/src/shape/HeightMap.ts +511 -0
- package/src/shape/Line.ts +14 -0
- package/src/shape/Plane.ts +116 -0
- package/src/shape/Ray.ts +81 -0
- package/src/shape/Segment.ts +25 -0
- package/src/shape/Shape.ts +77 -0
- package/src/shape/Sphere.ts +181 -0
- package/src/shape/TransformedShape.ts +51 -0
- package/src/shape/Triangle.ts +122 -0
- package/src/shape/TriangleMesh.ts +186 -0
- package/src/types.ts +1 -0
- package/src/world.ts +1335 -0
- package/tests/BodyPairsModule.test.ts +71 -0
- package/tests/BvhTree.test.ts +406 -0
- package/tests/test.md +642 -0
- package/tests/vec3.test.ts +12 -0
- package/tsconfig.json +20 -0
- package/vite.config.js +40 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClass,
|
|
3
|
+
InputType,
|
|
4
|
+
LazyReferenceType,
|
|
5
|
+
MonomorphType,
|
|
6
|
+
NumberType,
|
|
7
|
+
PoolClass,
|
|
8
|
+
PropertyDefinitionMap,
|
|
9
|
+
PropertyDefinitionReference,
|
|
10
|
+
ReferenceListType,
|
|
11
|
+
ReferenceType,
|
|
12
|
+
} from "monomorph";
|
|
13
|
+
import { Body, BodyType } from "../Body";
|
|
14
|
+
import { Vec3 } from "../../math/vec3";
|
|
15
|
+
import { ContactManifold, createContactPairKey } from "../manifold/ContactManifold";
|
|
16
|
+
import { Mat3 } from "../../math/mat3";
|
|
17
|
+
|
|
18
|
+
const linearVelocityA = /*@__PURE__*/ Vec3.create();
|
|
19
|
+
const linearVelocityB = /*@__PURE__*/ Vec3.create();
|
|
20
|
+
const linearVelocityBA = /*@__PURE__*/ Vec3.create();
|
|
21
|
+
const angularVelocityA = /*@__PURE__*/ Vec3.create();
|
|
22
|
+
const angularVelocityB = /*@__PURE__*/ Vec3.create();
|
|
23
|
+
|
|
24
|
+
const inverseInertiaA = /*@__PURE__*/ Mat3.create();
|
|
25
|
+
const inverseInertiaB = /*@__PURE__*/ Mat3.create();
|
|
26
|
+
|
|
27
|
+
const directionalConstraintProps = {
|
|
28
|
+
mR1PlusUxAxis: MonomorphType(Vec3),
|
|
29
|
+
mR2xAxis: MonomorphType(Vec3),
|
|
30
|
+
mInvI1_R1PlusUxAxis: MonomorphType(Vec3),
|
|
31
|
+
mInvI2_R2xAxis: MonomorphType(Vec3),
|
|
32
|
+
effectiveMass: NumberType(0.0),
|
|
33
|
+
bias: NumberType(0.0),
|
|
34
|
+
softness: NumberType(0.0),
|
|
35
|
+
totalLambda: NumberType(0.0),
|
|
36
|
+
} as const satisfies PropertyDefinitionMap;
|
|
37
|
+
|
|
38
|
+
export class DirectionalConstraint extends createClass<DirectionalConstraint, typeof directionalConstraintProps>(
|
|
39
|
+
directionalConstraintProps
|
|
40
|
+
) {
|
|
41
|
+
isSpringActive(): boolean {
|
|
42
|
+
return this.softness !== 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
computeInverseEffectiveMassWithMassOverride(
|
|
46
|
+
bodyA: Body,
|
|
47
|
+
bodyB: Body,
|
|
48
|
+
inverseMassA: number,
|
|
49
|
+
inverseMassB: number,
|
|
50
|
+
momentArmA: Vec3,
|
|
51
|
+
momentArmB: Vec3,
|
|
52
|
+
direction: Vec3
|
|
53
|
+
): number {
|
|
54
|
+
// recompute the inverse inertia tensors
|
|
55
|
+
bodyA.computeInverseInertiaTensor(inverseInertiaA);
|
|
56
|
+
bodyB.computeInverseInertiaTensor(inverseInertiaB);
|
|
57
|
+
|
|
58
|
+
return this.computeInverseEffectiveMass(
|
|
59
|
+
bodyA,
|
|
60
|
+
bodyB,
|
|
61
|
+
inverseMassA,
|
|
62
|
+
inverseMassB,
|
|
63
|
+
inverseInertiaA,
|
|
64
|
+
inverseInertiaB,
|
|
65
|
+
momentArmA,
|
|
66
|
+
momentArmB,
|
|
67
|
+
direction
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
initializeWithMassOverride(
|
|
72
|
+
bodyA: Body,
|
|
73
|
+
bodyB: Body,
|
|
74
|
+
inverseMassA: number,
|
|
75
|
+
inverseMassB: number,
|
|
76
|
+
momentArmA: Vec3,
|
|
77
|
+
momentArmB: Vec3,
|
|
78
|
+
direction: Vec3,
|
|
79
|
+
bias: number = 0
|
|
80
|
+
): void {
|
|
81
|
+
const inverseEffectiveMass = this.computeInverseEffectiveMassWithMassOverride(
|
|
82
|
+
bodyA,
|
|
83
|
+
bodyB,
|
|
84
|
+
inverseMassA,
|
|
85
|
+
inverseMassB,
|
|
86
|
+
momentArmA,
|
|
87
|
+
momentArmB,
|
|
88
|
+
direction
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (inverseEffectiveMass === 0) {
|
|
92
|
+
this.effectiveMass = 0;
|
|
93
|
+
this.totalLambda = 0;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.effectiveMass = 1 / inverseEffectiveMass;
|
|
98
|
+
this.bias = bias;
|
|
99
|
+
this.softness = 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
applyImpulse(
|
|
103
|
+
bodyA: Body,
|
|
104
|
+
bodyB: Body,
|
|
105
|
+
inverseMassA: number,
|
|
106
|
+
inverseMassB: number,
|
|
107
|
+
direction: Vec3,
|
|
108
|
+
inTotalLambda: number
|
|
109
|
+
): void {
|
|
110
|
+
const deltaLambda = inTotalLambda - this.totalLambda;
|
|
111
|
+
this.totalLambda = inTotalLambda;
|
|
112
|
+
|
|
113
|
+
// if lambda is zero, do nothing
|
|
114
|
+
if (deltaLambda === 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
119
|
+
bodyA.linearVelocity.addScaled(direction, -deltaLambda * inverseMassA);
|
|
120
|
+
// console.log("applying delta-v of magnitude", -deltaLambda * inverseMassA, "in direction", direction.toObject());
|
|
121
|
+
bodyA.angularVelocity.addScaled(this.mInvI1_R1PlusUxAxis, -deltaLambda);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
125
|
+
bodyB.linearVelocity.addScaled(direction, +deltaLambda * inverseMassB);
|
|
126
|
+
bodyB.angularVelocity.addScaled(this.mInvI2_R2xAxis, +deltaLambda);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
getBias(totalLambda: number): number {
|
|
130
|
+
return this.softness * totalLambda + this.bias;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getTotalLambda(bodyA: Body, bodyB: Body, direction: Vec3): number {
|
|
134
|
+
// #v-ifdef DEV
|
|
135
|
+
// assert at least one body is dynamic
|
|
136
|
+
if (bodyA.type !== BodyType.dynamic && bodyB.type !== BodyType.dynamic) {
|
|
137
|
+
throw new Error("at least one body must be dynamic");
|
|
138
|
+
}
|
|
139
|
+
// #v-endif
|
|
140
|
+
// calculate the jacobian multiplied by the velocity
|
|
141
|
+
let jv = 0;
|
|
142
|
+
|
|
143
|
+
// jacobian multiplied by the linear relative velocity
|
|
144
|
+
linearVelocityA.copy(bodyA.linearVelocity);
|
|
145
|
+
linearVelocityB.copy(bodyB.linearVelocity);
|
|
146
|
+
linearVelocityBA.subtractVectors(linearVelocityA, linearVelocityB);
|
|
147
|
+
|
|
148
|
+
jv += direction.dot(linearVelocityBA);
|
|
149
|
+
|
|
150
|
+
// jacobian multiplied by the angular relative velocity
|
|
151
|
+
angularVelocityA.copy(bodyA.angularVelocity);
|
|
152
|
+
angularVelocityB.copy(bodyB.angularVelocity);
|
|
153
|
+
|
|
154
|
+
if (bodyA.type !== BodyType.static) {
|
|
155
|
+
jv += this.mR1PlusUxAxis.dot(angularVelocityA);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (bodyB.type !== BodyType.static) {
|
|
159
|
+
jv -= this.mR2xAxis.dot(angularVelocityB);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// compute lambda = -K^-1 * (jv - b)
|
|
163
|
+
const lambda = this.effectiveMass * (jv - this.getBias(this.totalLambda));
|
|
164
|
+
const accumulatedLambda = this.totalLambda + lambda;
|
|
165
|
+
return accumulatedLambda;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
computeInverseEffectiveMass(
|
|
169
|
+
bodyA: Body,
|
|
170
|
+
bodyB: Body,
|
|
171
|
+
inverseMassA: number,
|
|
172
|
+
inverseMassB: number,
|
|
173
|
+
inverseInertiaA: Mat3,
|
|
174
|
+
inverseInertiaB: Mat3,
|
|
175
|
+
momentArmA: Vec3,
|
|
176
|
+
momentArmB: Vec3,
|
|
177
|
+
direction: Vec3
|
|
178
|
+
): number {
|
|
179
|
+
// #v-ifdef DEV
|
|
180
|
+
// assert that the direction is normalized within some tolerance
|
|
181
|
+
if (direction.isNormalized(1e-5) === false) {
|
|
182
|
+
throw new Error(`expected normalized direction, got ${direction} instead`);
|
|
183
|
+
}
|
|
184
|
+
// #v-endif
|
|
185
|
+
|
|
186
|
+
// zero out the linear jacobians (to handle static bodies)
|
|
187
|
+
this.mR1PlusUxAxis.zero();
|
|
188
|
+
this.mR2xAxis.zero();
|
|
189
|
+
|
|
190
|
+
// if not a static body, calculate the linear jacobians
|
|
191
|
+
if (bodyA.type !== BodyType.static) {
|
|
192
|
+
this.mR1PlusUxAxis.crossVectors(momentArmA, direction);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (bodyB.type !== BodyType.static) {
|
|
196
|
+
this.mR2xAxis.crossVectors(momentArmB, direction);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// the inverse effective mass is K = J * M^-1 * J_T
|
|
200
|
+
let inverseEffectiveMass = 0;
|
|
201
|
+
|
|
202
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
203
|
+
this.mInvI1_R1PlusUxAxis.transformVectorFromMat3(this.mR1PlusUxAxis, inverseInertiaA);
|
|
204
|
+
inverseEffectiveMass += inverseMassA + this.mInvI1_R1PlusUxAxis.dot(this.mR1PlusUxAxis);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
208
|
+
this.mInvI2_R2xAxis.transformVectorFromMat3(this.mR2xAxis, inverseInertiaB);
|
|
209
|
+
inverseEffectiveMass += inverseMassB + this.mInvI2_R2xAxis.dot(this.mR2xAxis);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return inverseEffectiveMass;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
initialize(
|
|
216
|
+
bodyA: Body,
|
|
217
|
+
bodyB: Body,
|
|
218
|
+
inverseMassA: number,
|
|
219
|
+
inverseMassB: number,
|
|
220
|
+
inverseInertiaA: Mat3,
|
|
221
|
+
inverseInertiaB: Mat3,
|
|
222
|
+
momentArmA: Vec3,
|
|
223
|
+
momentArmB: Vec3,
|
|
224
|
+
direction: Vec3,
|
|
225
|
+
bias: number
|
|
226
|
+
): void {
|
|
227
|
+
const inverseEffectiveMass = this.computeInverseEffectiveMass(
|
|
228
|
+
bodyA,
|
|
229
|
+
bodyB,
|
|
230
|
+
inverseMassA,
|
|
231
|
+
inverseMassB,
|
|
232
|
+
inverseInertiaA,
|
|
233
|
+
inverseInertiaB,
|
|
234
|
+
momentArmA,
|
|
235
|
+
momentArmB,
|
|
236
|
+
direction
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (inverseEffectiveMass === 0) {
|
|
240
|
+
this.effectiveMass = 0;
|
|
241
|
+
this.totalLambda = 0;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.effectiveMass = 1 / inverseEffectiveMass;
|
|
246
|
+
this.bias = bias;
|
|
247
|
+
this.softness = 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
isActive(): boolean {
|
|
251
|
+
return this.effectiveMass !== 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
warmStart(bodyA: Body, bodyB: Body, inverseMassA: number, inverseMassB: number, direction: Vec3): void {
|
|
255
|
+
// this.applyImpulse(bodyA, bodyB, inverseMassA, inverseMassB, direction, this.totalLambda);
|
|
256
|
+
const deltaLambda = this.totalLambda;
|
|
257
|
+
|
|
258
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
259
|
+
bodyA.linearVelocity.addScaled(direction, -deltaLambda * inverseMassA);
|
|
260
|
+
bodyA.angularVelocity.addScaled(this.mInvI1_R1PlusUxAxis, -deltaLambda);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
264
|
+
bodyB.linearVelocity.addScaled(direction, +deltaLambda * inverseMassB);
|
|
265
|
+
bodyB.angularVelocity.addScaled(this.mInvI2_R2xAxis, +deltaLambda);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const contactConstraintProps = {
|
|
271
|
+
normalConstraint: MonomorphType(DirectionalConstraint),
|
|
272
|
+
tangentConstraint: MonomorphType(DirectionalConstraint),
|
|
273
|
+
bitangentConstraint: MonomorphType(DirectionalConstraint),
|
|
274
|
+
localPositionA: MonomorphType(Vec3),
|
|
275
|
+
localPositionB: MonomorphType(Vec3),
|
|
276
|
+
} as const satisfies PropertyDefinitionMap;
|
|
277
|
+
|
|
278
|
+
export class ContactConstraint extends createClass<ContactConstraint, typeof contactConstraintProps>(
|
|
279
|
+
contactConstraintProps
|
|
280
|
+
) {
|
|
281
|
+
getLambda(out: Vec3, index: number): void {
|
|
282
|
+
out.x = this.normalConstraint.totalLambda;
|
|
283
|
+
out.y = this.tangentConstraint.totalLambda;
|
|
284
|
+
out.z = this.bitangentConstraint.totalLambda;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
setLambda(value: Vec3): void {
|
|
288
|
+
this.normalConstraint.totalLambda = value.x;
|
|
289
|
+
this.tangentConstraint.totalLambda = value.y;
|
|
290
|
+
this.bitangentConstraint.totalLambda = value.z;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
copyLambdaFromManifold(manifold: ContactManifold, index: number): void {
|
|
294
|
+
this.setLambda(manifold.lambdas!.getAtIndex(index)!);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
warmStart(
|
|
298
|
+
bodyA: Body,
|
|
299
|
+
bodyB: Body,
|
|
300
|
+
inverseMassA: number,
|
|
301
|
+
inverseMassB: number,
|
|
302
|
+
normal: Vec3,
|
|
303
|
+
tangent: Vec3,
|
|
304
|
+
bitangent: Vec3
|
|
305
|
+
): void {
|
|
306
|
+
if (this.tangentConstraint.isActive() || this.bitangentConstraint.isActive()) {
|
|
307
|
+
this.tangentConstraint.warmStart(bodyA, bodyB, inverseMassA, inverseMassB, tangent);
|
|
308
|
+
this.bitangentConstraint.warmStart(bodyA, bodyB, inverseMassA, inverseMassB, bitangent);
|
|
309
|
+
}
|
|
310
|
+
this.normalConstraint.warmStart(bodyA, bodyB, inverseMassA, inverseMassB, normal);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const manifoldConstraintProps = {
|
|
315
|
+
bodyA: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
|
|
316
|
+
bodyB: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
|
|
317
|
+
|
|
318
|
+
subShapeIdA: NumberType(0),
|
|
319
|
+
subShapeIdB: NumberType(0),
|
|
320
|
+
|
|
321
|
+
friction: NumberType(0.0),
|
|
322
|
+
restitution: NumberType(0.0),
|
|
323
|
+
|
|
324
|
+
worldSpaceNormal: MonomorphType(Vec3),
|
|
325
|
+
worldSpaceTangent: MonomorphType(Vec3),
|
|
326
|
+
worldSpaceBitangent: MonomorphType(Vec3),
|
|
327
|
+
|
|
328
|
+
inverseMassA: NumberType(0.0),
|
|
329
|
+
inverseMassB: NumberType(0.0),
|
|
330
|
+
|
|
331
|
+
contactConstraints: ReferenceListType(ContactConstraint),
|
|
332
|
+
numContacts: NumberType(0),
|
|
333
|
+
} as const satisfies PropertyDefinitionMap;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* a contact manifold constraint for one or more points
|
|
337
|
+
*/
|
|
338
|
+
export class ManifoldConstraint extends createClass<ManifoldConstraint, typeof manifoldConstraintProps>(
|
|
339
|
+
manifoldConstraintProps
|
|
340
|
+
) {
|
|
341
|
+
updateTangentDirections(): void {
|
|
342
|
+
this.worldSpaceTangent.computeNormalizedPerpendicular(this.worldSpaceNormal);
|
|
343
|
+
this.worldSpaceBitangent.crossVectors(this.worldSpaceNormal, this.worldSpaceTangent);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
get key() {
|
|
347
|
+
return createContactPairKey(this.bodyA!, this.bodyB!);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
copyLambdasToManifold(manifold: ContactManifold): void {
|
|
351
|
+
for (let i = 0; i < manifold.numContacts; i++) {
|
|
352
|
+
this.contactConstraints!.getAtIndex(i)!.getLambda(manifold.lambdas!.getAtIndex(i)!, i);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
warmStart(): void {
|
|
357
|
+
// // recalculate tangents ?
|
|
358
|
+
this.updateTangentDirections();
|
|
359
|
+
|
|
360
|
+
// warm start the contact constraints
|
|
361
|
+
for (let i = 0; i < this.numContacts; i++) {
|
|
362
|
+
this.contactConstraints!.getAtIndex(i)!.warmStart(
|
|
363
|
+
this.bodyA!,
|
|
364
|
+
this.bodyB!,
|
|
365
|
+
this.inverseMassA,
|
|
366
|
+
this.inverseMassB,
|
|
367
|
+
this.worldSpaceNormal,
|
|
368
|
+
this.worldSpaceTangent,
|
|
369
|
+
this.worldSpaceBitangent
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export class ManifoldConstraintPool {
|
|
376
|
+
// the contact manifold pool
|
|
377
|
+
manifoldConstraintPool: PoolClass<ManifoldConstraint>;
|
|
378
|
+
contactConstraintPool: PoolClass<ContactConstraint>;
|
|
379
|
+
|
|
380
|
+
numMaxManifolds: number;
|
|
381
|
+
numMaxContacts: number;
|
|
382
|
+
|
|
383
|
+
// list of pools for contact constraints
|
|
384
|
+
// contactConstraintsPoolList: PoolClass<ContactConstraint>[];
|
|
385
|
+
|
|
386
|
+
constructor(numMaxManifolds: number, numMaxContacts: number) {
|
|
387
|
+
this.manifoldConstraintPool = new ManifoldConstraint.Pool(); // numMaxManifolds
|
|
388
|
+
this.contactConstraintPool = new ContactConstraint.Pool(); // numMaxContacts * numMaxManifolds
|
|
389
|
+
|
|
390
|
+
this.numMaxManifolds = numMaxManifolds;
|
|
391
|
+
this.numMaxContacts = numMaxContacts;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
createManifoldConstraint(data: InputType<ManifoldConstraint>): ManifoldConstraint {
|
|
395
|
+
data = data ?? {};
|
|
396
|
+
|
|
397
|
+
if (data.contactConstraints) {
|
|
398
|
+
data.contactConstraints.pool = this.contactConstraintPool;
|
|
399
|
+
} else {
|
|
400
|
+
data.contactConstraints = { pool: this.contactConstraintPool };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const manifold = ManifoldConstraint.create(data, this.manifoldConstraintPool);
|
|
404
|
+
|
|
405
|
+
// todo this behavior probably should change?
|
|
406
|
+
// currently, we create all the contactConstraints once the first time, and never destroy them.
|
|
407
|
+
// they technically should be destroyed, but this is more efficient for this use case
|
|
408
|
+
// since we will be reusing the same manifolds over and over again, so we should just
|
|
409
|
+
// increment a numManifolds instead that is reset to 0
|
|
410
|
+
if (manifold.contactConstraints.length >= this.numMaxContacts) {
|
|
411
|
+
return manifold;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (let i = 0; i < this.numMaxContacts; i++) {
|
|
415
|
+
manifold.contactConstraints!.create();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return manifold;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createClass, MonomorphType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
import { ContactManifold } from "../manifold/ContactManifold";
|
|
3
|
+
import { Vec3 } from "../../math/vec3";
|
|
4
|
+
import { BodyType } from "../Body";
|
|
5
|
+
import { Mat3 } from "../../math/mat3";
|
|
6
|
+
|
|
7
|
+
const estimateCollisionResponseResultProps = {
|
|
8
|
+
deltaLinearVelocityA: MonomorphType(Vec3),
|
|
9
|
+
deltaAngularVelocityA: MonomorphType(Vec3),
|
|
10
|
+
deltaLinearVelocityB: MonomorphType(Vec3),
|
|
11
|
+
deltaAngularVelocityB: MonomorphType(Vec3),
|
|
12
|
+
} as const satisfies PropertyDefinitionMap;
|
|
13
|
+
|
|
14
|
+
export class EstimateCollisionResponseResult extends createClass<
|
|
15
|
+
EstimateCollisionResponseResult,
|
|
16
|
+
typeof estimateCollisionResponseResultProps
|
|
17
|
+
>(estimateCollisionResponseResultProps) {
|
|
18
|
+
reset() {
|
|
19
|
+
this.deltaLinearVelocityA.zero();
|
|
20
|
+
this.deltaAngularVelocityA.zero();
|
|
21
|
+
this.deltaLinearVelocityB.zero();
|
|
22
|
+
this.deltaAngularVelocityB.zero();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function estimateCollisionResponse(
|
|
27
|
+
result: EstimateCollisionResponseResult,
|
|
28
|
+
manifold: ContactManifold,
|
|
29
|
+
timeStepSizeSeconds: number
|
|
30
|
+
) {
|
|
31
|
+
result.reset();
|
|
32
|
+
const damping = 0.9;
|
|
33
|
+
const penetrationResolution = 0.4;
|
|
34
|
+
const bodyA = manifold.bodyA!;
|
|
35
|
+
const bodyB = manifold.bodyB!;
|
|
36
|
+
|
|
37
|
+
for (let index = 0; index < manifold.numContacts; index++) {
|
|
38
|
+
manifold.getContactPointA(worldContactPointA, index);
|
|
39
|
+
worldContactPointA.addVector(manifold.baseTranslation);
|
|
40
|
+
|
|
41
|
+
manifold.getContactPointB(worldContactPointB, index);
|
|
42
|
+
worldContactPointB.addVector(manifold.baseTranslation);
|
|
43
|
+
|
|
44
|
+
combinedWorldContactPoint.averageOfVectors(worldContactPointA, worldContactPointB);
|
|
45
|
+
worldMomentArmA.subtractVectors(combinedWorldContactPoint, bodyA.computedCenterOfMassPosition);
|
|
46
|
+
worldMomentArmB.subtractVectors(combinedWorldContactPoint, bodyB.computedCenterOfMassPosition);
|
|
47
|
+
|
|
48
|
+
bodyA.computeVelocityOfPointRelativeToCenterOfMass(contactVelocityA, worldMomentArmA);
|
|
49
|
+
bodyB.computeVelocityOfPointRelativeToCenterOfMass(contactVelocityB, worldMomentArmB);
|
|
50
|
+
contactVelocityAB.subtractVectors(contactVelocityB, contactVelocityA);
|
|
51
|
+
|
|
52
|
+
// get the velocity along the contact normal
|
|
53
|
+
const normalSpeed = contactVelocityAB.dot(manifold.worldSpaceNormal);
|
|
54
|
+
const deltaSpeed =
|
|
55
|
+
-normalSpeed * damping - (Math.min(manifold.penetrationDepth, 0) * penetrationResolution) / timeStepSizeSeconds;
|
|
56
|
+
|
|
57
|
+
// don't apply impulses if separating
|
|
58
|
+
if (deltaSpeed < 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
centerOfMassA.copy(bodyA.computedCenterOfMassPosition);
|
|
63
|
+
centerOfMassB.copy(bodyB.computedCenterOfMassPosition);
|
|
64
|
+
|
|
65
|
+
jacobianA.crossVectors(worldMomentArmA, manifold.worldSpaceNormal);
|
|
66
|
+
jacobianB.crossVectors(worldMomentArmB, manifold.worldSpaceNormal);
|
|
67
|
+
|
|
68
|
+
// compute inverse effective mass
|
|
69
|
+
const direction = manifold.worldSpaceNormal;
|
|
70
|
+
bodyA.computeInverseInertiaTensor(inverseInertiaA);
|
|
71
|
+
bodyB.computeInverseInertiaTensor(inverseInertiaB);
|
|
72
|
+
const inverseMassA = bodyA.inverseMass;
|
|
73
|
+
const inverseMassB = bodyB.inverseMass;
|
|
74
|
+
|
|
75
|
+
// #v-ifdef DEV
|
|
76
|
+
// assert that the direction is normalized within some tolerance
|
|
77
|
+
if (direction.isNormalized(1e-5) === false) {
|
|
78
|
+
throw new Error(`expected normalized direction, got ${direction} instead`);
|
|
79
|
+
}
|
|
80
|
+
// #v-endif
|
|
81
|
+
|
|
82
|
+
// zero out the linear jacobians (to handle static bodies)
|
|
83
|
+
mR1PlusUxAxis.zero();
|
|
84
|
+
mR2xAxis.zero();
|
|
85
|
+
|
|
86
|
+
// if not a static body, calculate the linear jacobians
|
|
87
|
+
if (bodyA.type !== BodyType.static) {
|
|
88
|
+
mR1PlusUxAxis.crossVectors(worldMomentArmA, direction);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (bodyB.type !== BodyType.static) {
|
|
92
|
+
mR2xAxis.crossVectors(worldMomentArmB, direction);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// the inverse effective mass is K = J * M^-1 * J_T
|
|
96
|
+
let inverseEffectiveMass = 0;
|
|
97
|
+
|
|
98
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
99
|
+
mInvI1_R1PlusUxAxis.transformVectorFromMat3(mR1PlusUxAxis, inverseInertiaA);
|
|
100
|
+
inverseEffectiveMass += inverseMassA + mInvI1_R1PlusUxAxis.dot(mR1PlusUxAxis);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
104
|
+
mInvI2_R2xAxis.transformVectorFromMat3(mR2xAxis, inverseInertiaB);
|
|
105
|
+
inverseEffectiveMass += inverseMassB + mInvI2_R2xAxis.dot(mR2xAxis);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Impulse P = M dv
|
|
109
|
+
let impulseMagnitude = deltaSpeed / inverseEffectiveMass;
|
|
110
|
+
|
|
111
|
+
// Calculate the world space impulse to apply
|
|
112
|
+
impulse.scaleVector(manifold.worldSpaceNormal, impulseMagnitude);
|
|
113
|
+
|
|
114
|
+
const deltaLambda = impulseMagnitude;
|
|
115
|
+
|
|
116
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
117
|
+
result.deltaLinearVelocityA.addScaledToVector(bodyA.linearVelocity, direction, -deltaLambda * inverseMassA);
|
|
118
|
+
result.deltaAngularVelocityA.addScaledToVector(bodyA.angularVelocity, mInvI1_R1PlusUxAxis, -deltaLambda);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
122
|
+
result.deltaLinearVelocityA.addScaledToVector(bodyB.linearVelocity, direction, +deltaLambda * inverseMassB);
|
|
123
|
+
result.deltaAngularVelocityB.addScaledToVector(bodyB.angularVelocity, mInvI2_R2xAxis, +deltaLambda);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const worldContactPointA = /*@__PURE__*/ Vec3.create();
|
|
129
|
+
const worldContactPointB = /*@__PURE__*/ Vec3.create();
|
|
130
|
+
const combinedWorldContactPoint = /*@__PURE__*/ Vec3.create();
|
|
131
|
+
const worldMomentArmA = /*@__PURE__*/ Vec3.create();
|
|
132
|
+
const worldMomentArmB = /*@__PURE__*/ Vec3.create();
|
|
133
|
+
const contactVelocityA = /*@__PURE__*/ Vec3.create();
|
|
134
|
+
const contactVelocityB = /*@__PURE__*/ Vec3.create();
|
|
135
|
+
const contactVelocityAB = /*@__PURE__*/ Vec3.create();
|
|
136
|
+
const centerOfMassA = /*@__PURE__*/ Vec3.create();
|
|
137
|
+
const centerOfMassB = /*@__PURE__*/ Vec3.create();
|
|
138
|
+
const jacobianA = /*@__PURE__*/ Vec3.create();
|
|
139
|
+
const jacobianB = /*@__PURE__*/ Vec3.create();
|
|
140
|
+
const impulse = /*@__PURE__*/ Vec3.create();
|
|
141
|
+
const mR1PlusUxAxis = /*@__PURE__*/ Vec3.create();
|
|
142
|
+
const mR2xAxis = /*@__PURE__*/ Vec3.create();
|
|
143
|
+
const mInvI1_R1PlusUxAxis = /*@__PURE__*/ Vec3.create();
|
|
144
|
+
const mInvI2_R2xAxis = /*@__PURE__*/ Vec3.create();
|
|
145
|
+
const inverseInertiaA = /*@__PURE__*/ Mat3.create();
|
|
146
|
+
const inverseInertiaB = /*@__PURE__*/ Mat3.create();
|