emeraldengine 3.0.0 → 3.1.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/README.md +1498 -1659
- package/dist/types/index.d.ts +4 -1
- package/dist/types/src/Animator.d.ts +1 -1
- package/dist/types/src/CollisionLayers.d.ts +2 -2
- package/dist/types/src/Color.d.ts +1 -0
- package/dist/types/src/Drawable.d.ts +1 -1
- package/dist/types/src/EmeraldDB.d.ts +2 -2
- package/dist/types/src/InstancedTexture.d.ts +17 -2
- package/dist/types/src/Material.d.ts +2 -2
- package/dist/types/src/MathUtils.d.ts +2 -1
- package/dist/types/src/ParticleEmitter.d.ts +1 -1
- package/dist/types/src/Physics.d.ts +148 -18
- package/dist/types/src/Scene.d.ts +1 -1
- package/dist/types/src/Shaders.d.ts +2 -2
- package/dist/types/src/Tilemap.d.ts +1 -1
- package/dist/types/src/UI.d.ts +1 -1
- package/dist/types/src/components/Behaviour.d.ts +2 -2
- package/dist/types/src/components/Collider.d.ts +7 -1
- package/dist/types/src/components/GameObject.d.ts +2 -2
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +281 -8
- package/dist/types/src/importers/Aseprite.d.ts +2 -2
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +1 -1
- package/dist/types/src/managers/EventManager.d.ts +1 -1
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/InputManager.d.ts +93 -2
- package/dist/types/src/managers/NetworkManager.d.ts +2 -2
- package/dist/types/src/managers/RenderStats.d.ts +1 -1
- package/dist/types/src/managers/TextureManager.d.ts +1 -1
- package/dist/types/src/physics/AABB.d.ts +92 -0
- package/dist/types/src/physics/Body.d.ts +435 -0
- package/dist/types/src/physics/BodyType.d.ts +6 -0
- package/dist/types/src/physics/BroadPhase.d.ts +210 -0
- package/dist/types/src/physics/Collision.d.ts +102 -0
- package/dist/types/src/physics/Contact.d.ts +206 -0
- package/dist/types/src/physics/ContactSolver.d.ts +108 -0
- package/dist/types/src/physics/Distance.d.ts +54 -0
- package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
- package/dist/types/src/physics/Fixture.d.ts +221 -0
- package/dist/types/src/physics/Island.d.ts +52 -0
- package/dist/types/src/physics/Joint.d.ts +59 -0
- package/dist/types/src/physics/Math2D.d.ts +371 -0
- package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
- package/dist/types/src/physics/Settings.d.ts +22 -0
- package/dist/types/src/physics/Shapes.d.ts +207 -0
- package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
- package/dist/types/src/physics/World.d.ts +274 -0
- package/dist/types/src/physics/index.d.ts +34 -0
- package/index.js +6 -0
- package/package.json +2 -3
- package/src/Animator.js +1 -1
- package/src/CollisionLayers.js +3 -3
- package/src/Color.js +8 -0
- package/src/Drawable.js +1 -1
- package/src/Emerald.js +1 -1
- package/src/EmeraldDB.js +2 -2
- package/src/InstancedTexture.js +57 -9
- package/src/Material.js +2 -2
- package/src/MathUtils.js +2 -1
- package/src/ParticleEmitter.js +1 -1
- package/src/Physics.js +270 -60
- package/src/Scene.js +1 -1
- package/src/Shaders.js +20 -20
- package/src/Tilemap.js +1 -1
- package/src/UI.js +1 -1
- package/src/components/Behaviour.js +2 -2
- package/src/components/BoxCollider.js +7 -9
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +7 -9
- package/src/components/CircleColliderDebug.js +3 -2
- package/src/components/Collider.js +13 -3
- package/src/components/GameObject.js +2 -2
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +441 -14
- package/src/importers/Aseprite.js +2 -2
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +1 -1
- package/src/managers/EventManager.js +1 -1
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/InputManager.js +129 -3
- package/src/managers/NetworkManager.js +2 -2
- package/src/managers/RenderStats.js +1 -1
- package/src/managers/TextureManager.js +1 -1
- package/src/physics/AABB.js +207 -0
- package/src/physics/Body.js +862 -0
- package/src/physics/BodyType.js +16 -0
- package/src/physics/BroadPhase.js +641 -0
- package/src/physics/Collision.js +534 -0
- package/src/physics/Contact.js +500 -0
- package/src/physics/ContactSolver.js +526 -0
- package/src/physics/Distance.js +403 -0
- package/src/physics/DistanceJoint.js +227 -0
- package/src/physics/Fixture.js +346 -0
- package/src/physics/Island.js +203 -0
- package/src/physics/Joint.js +78 -0
- package/src/physics/Math2D.js +573 -0
- package/src/physics/RevoluteJoint.js +278 -0
- package/src/physics/Settings.js +78 -0
- package/src/physics/Shapes.js +549 -0
- package/src/physics/TimeOfImpact.js +87 -0
- package/src/physics/World.js +731 -0
- package/src/physics/index.js +79 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { Vec2, Transform2, clamp } from "./Math2D.js";
|
|
2
|
+
import Settings from "./Settings.js";
|
|
3
|
+
import { ManifoldType, WorldManifold } from "./Collision.js";
|
|
4
|
+
|
|
5
|
+
const MAX_CONDITION_NUMBER = 1000;
|
|
6
|
+
const worldManifold = new WorldManifold();
|
|
7
|
+
const xfA = new Transform2();
|
|
8
|
+
const xfB = new Transform2();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @class ContactSolver
|
|
12
|
+
* @description Resolves every contact in an island with sequential impulses:
|
|
13
|
+
* for each contact point it works out the impulse that removes the approaching
|
|
14
|
+
* velocity (plus any bounce), applies it, and repeats over all points for a
|
|
15
|
+
* fixed number of iterations. Impulses are accumulated and clamped so they can
|
|
16
|
+
* only ever push, never pull, and they carry over between steps (warm starting)
|
|
17
|
+
* so stacks settle instead of sinking.
|
|
18
|
+
*
|
|
19
|
+
* Position error left over after the velocity pass is fixed separately, by
|
|
20
|
+
* nudging the bodies apart without injecting energy.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} def - `{ contacts, positions, velocities, dt, velocityThreshold }`
|
|
23
|
+
*/
|
|
24
|
+
class ContactSolver {
|
|
25
|
+
constructor(def) {
|
|
26
|
+
this.positions = def.positions;
|
|
27
|
+
this.velocities = def.velocities;
|
|
28
|
+
this.contacts = def.contacts;
|
|
29
|
+
this.dt = def.dt;
|
|
30
|
+
this.velocityThreshold =
|
|
31
|
+
def.velocityThreshold ?? Settings.velocityThreshold;
|
|
32
|
+
|
|
33
|
+
this.velocityConstraints = [];
|
|
34
|
+
this.positionConstraints = [];
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < this.contacts.length; i++) {
|
|
37
|
+
const contact = this.contacts[i];
|
|
38
|
+
const fixtureA = contact.fixtureA;
|
|
39
|
+
const fixtureB = contact.fixtureB;
|
|
40
|
+
const bodyA = fixtureA.body;
|
|
41
|
+
const bodyB = fixtureB.body;
|
|
42
|
+
const manifold = contact.manifold;
|
|
43
|
+
const pointCount = manifold.pointCount;
|
|
44
|
+
if (pointCount === 0) continue;
|
|
45
|
+
|
|
46
|
+
const vc = {
|
|
47
|
+
friction: contact.friction,
|
|
48
|
+
restitution: contact.restitution,
|
|
49
|
+
tangentSpeed: contact.tangentSpeed,
|
|
50
|
+
indexA: bodyA.islandIndex,
|
|
51
|
+
indexB: bodyB.islandIndex,
|
|
52
|
+
invMassA: bodyA.invMass,
|
|
53
|
+
invMassB: bodyB.invMass,
|
|
54
|
+
invIA: bodyA.invI,
|
|
55
|
+
invIB: bodyB.invI,
|
|
56
|
+
contactIndex: i,
|
|
57
|
+
pointCount,
|
|
58
|
+
normal: new Vec2(),
|
|
59
|
+
tangent: new Vec2(),
|
|
60
|
+
K: { k11: 0, k12: 0, k22: 0 },
|
|
61
|
+
normalMass: { k11: 0, k12: 0, k22: 0 },
|
|
62
|
+
points: [],
|
|
63
|
+
};
|
|
64
|
+
for (let j = 0; j < pointCount; j++) {
|
|
65
|
+
vc.points.push({
|
|
66
|
+
rA: new Vec2(),
|
|
67
|
+
rB: new Vec2(),
|
|
68
|
+
normalImpulse: manifold.points[j].normalImpulse,
|
|
69
|
+
tangentImpulse: manifold.points[j].tangentImpulse,
|
|
70
|
+
normalMass: 0,
|
|
71
|
+
tangentMass: 0,
|
|
72
|
+
velocityBias: 0,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const pc = {
|
|
77
|
+
indexA: bodyA.islandIndex,
|
|
78
|
+
indexB: bodyB.islandIndex,
|
|
79
|
+
invMassA: bodyA.invMass,
|
|
80
|
+
invMassB: bodyB.invMass,
|
|
81
|
+
invIA: bodyA.invI,
|
|
82
|
+
invIB: bodyB.invI,
|
|
83
|
+
localCenterA: bodyA.sweep.localCenter,
|
|
84
|
+
localCenterB: bodyB.sweep.localCenter,
|
|
85
|
+
radiusA: fixtureA.shape.radius,
|
|
86
|
+
radiusB: fixtureB.shape.radius,
|
|
87
|
+
type: manifold.type,
|
|
88
|
+
localNormal: manifold.localNormal,
|
|
89
|
+
localPoint: manifold.localPoint,
|
|
90
|
+
localPoints: [],
|
|
91
|
+
pointCount,
|
|
92
|
+
};
|
|
93
|
+
for (let j = 0; j < pointCount; j++) {
|
|
94
|
+
pc.localPoints.push(manifold.points[j].localPoint);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.velocityConstraints.push(vc);
|
|
98
|
+
this.positionConstraints.push(pc);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @method initializeVelocityConstraints
|
|
104
|
+
* @description Computes the effective mass at every contact point and the
|
|
105
|
+
* bounce target, from the state at the start of the step.
|
|
106
|
+
*/
|
|
107
|
+
initializeVelocityConstraints() {
|
|
108
|
+
for (let i = 0; i < this.velocityConstraints.length; i++) {
|
|
109
|
+
const vc = this.velocityConstraints[i];
|
|
110
|
+
const pc = this.positionConstraints[i];
|
|
111
|
+
const contact = this.contacts[vc.contactIndex];
|
|
112
|
+
const manifold = contact.manifold;
|
|
113
|
+
|
|
114
|
+
const posA = this.positions[vc.indexA];
|
|
115
|
+
const posB = this.positions[vc.indexB];
|
|
116
|
+
const velA = this.velocities[vc.indexA];
|
|
117
|
+
const velB = this.velocities[vc.indexB];
|
|
118
|
+
|
|
119
|
+
xfA.q.set(posA.a);
|
|
120
|
+
xfB.q.set(posB.a);
|
|
121
|
+
xfA.p.set(
|
|
122
|
+
posA.c.x - (xfA.q.c * pc.localCenterA.x - xfA.q.s * pc.localCenterA.y),
|
|
123
|
+
posA.c.y - (xfA.q.s * pc.localCenterA.x + xfA.q.c * pc.localCenterA.y)
|
|
124
|
+
);
|
|
125
|
+
xfB.p.set(
|
|
126
|
+
posB.c.x - (xfB.q.c * pc.localCenterB.x - xfB.q.s * pc.localCenterB.y),
|
|
127
|
+
posB.c.y - (xfB.q.s * pc.localCenterB.x + xfB.q.c * pc.localCenterB.y)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
worldManifold.initialize(manifold, xfA, pc.radiusA, xfB, pc.radiusB);
|
|
131
|
+
vc.normal.copy(worldManifold.normal);
|
|
132
|
+
vc.tangent.set(vc.normal.y, -vc.normal.x);
|
|
133
|
+
|
|
134
|
+
const mA = vc.invMassA;
|
|
135
|
+
const mB = vc.invMassB;
|
|
136
|
+
const iA = vc.invIA;
|
|
137
|
+
const iB = vc.invIB;
|
|
138
|
+
|
|
139
|
+
for (let j = 0; j < vc.pointCount; j++) {
|
|
140
|
+
const vcp = vc.points[j];
|
|
141
|
+
vcp.rA.set(
|
|
142
|
+
worldManifold.points[j].x - posA.c.x,
|
|
143
|
+
worldManifold.points[j].y - posA.c.y
|
|
144
|
+
);
|
|
145
|
+
vcp.rB.set(
|
|
146
|
+
worldManifold.points[j].x - posB.c.x,
|
|
147
|
+
worldManifold.points[j].y - posB.c.y
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const rnA = Vec2.cross(vcp.rA, vc.normal);
|
|
151
|
+
const rnB = Vec2.cross(vcp.rB, vc.normal);
|
|
152
|
+
const kNormal = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
|
|
153
|
+
vcp.normalMass = kNormal > 0 ? 1 / kNormal : 0;
|
|
154
|
+
|
|
155
|
+
const rtA = Vec2.cross(vcp.rA, vc.tangent);
|
|
156
|
+
const rtB = Vec2.cross(vcp.rB, vc.tangent);
|
|
157
|
+
const kTangent = mA + mB + iA * rtA * rtA + iB * rtB * rtB;
|
|
158
|
+
vcp.tangentMass = kTangent > 0 ? 1 / kTangent : 0;
|
|
159
|
+
|
|
160
|
+
vcp.velocityBias = 0;
|
|
161
|
+
const vRel =
|
|
162
|
+
vc.normal.x *
|
|
163
|
+
(velB.v.x - velB.w * vcp.rB.y - (velA.v.x - velA.w * vcp.rA.y)) +
|
|
164
|
+
vc.normal.y *
|
|
165
|
+
(velB.v.y + velB.w * vcp.rB.x - (velA.v.y + velA.w * vcp.rA.x));
|
|
166
|
+
if (vRel < -this.velocityThreshold) {
|
|
167
|
+
vcp.velocityBias = -vc.restitution * vRel;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (vc.pointCount === 2) {
|
|
172
|
+
const vcp1 = vc.points[0];
|
|
173
|
+
const vcp2 = vc.points[1];
|
|
174
|
+
const rn1A = Vec2.cross(vcp1.rA, vc.normal);
|
|
175
|
+
const rn1B = Vec2.cross(vcp1.rB, vc.normal);
|
|
176
|
+
const rn2A = Vec2.cross(vcp2.rA, vc.normal);
|
|
177
|
+
const rn2B = Vec2.cross(vcp2.rB, vc.normal);
|
|
178
|
+
|
|
179
|
+
const k11 = mA + mB + iA * rn1A * rn1A + iB * rn1B * rn1B;
|
|
180
|
+
const k22 = mA + mB + iA * rn2A * rn2A + iB * rn2B * rn2B;
|
|
181
|
+
const k12 = mA + mB + iA * rn1A * rn2A + iB * rn1B * rn2B;
|
|
182
|
+
|
|
183
|
+
if (k11 * k11 < MAX_CONDITION_NUMBER * (k11 * k22 - k12 * k12)) {
|
|
184
|
+
vc.K.k11 = k11;
|
|
185
|
+
vc.K.k12 = k12;
|
|
186
|
+
vc.K.k22 = k22;
|
|
187
|
+
const det = k11 * k22 - k12 * k12;
|
|
188
|
+
const invDet = det !== 0 ? 1 / det : 0;
|
|
189
|
+
vc.normalMass.k11 = invDet * k22;
|
|
190
|
+
vc.normalMass.k12 = -invDet * k12;
|
|
191
|
+
vc.normalMass.k22 = invDet * k11;
|
|
192
|
+
} else {
|
|
193
|
+
vc.pointCount = 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @method warmStart
|
|
201
|
+
* @description Re-applies last step's impulses before solving, which gets the
|
|
202
|
+
* solver most of the way to the answer immediately.
|
|
203
|
+
*/
|
|
204
|
+
warmStart() {
|
|
205
|
+
for (const vc of this.velocityConstraints) {
|
|
206
|
+
const velA = this.velocities[vc.indexA];
|
|
207
|
+
const velB = this.velocities[vc.indexB];
|
|
208
|
+
const mA = vc.invMassA;
|
|
209
|
+
const mB = vc.invMassB;
|
|
210
|
+
const iA = vc.invIA;
|
|
211
|
+
const iB = vc.invIB;
|
|
212
|
+
|
|
213
|
+
for (let j = 0; j < vc.pointCount; j++) {
|
|
214
|
+
const vcp = vc.points[j];
|
|
215
|
+
const px =
|
|
216
|
+
vcp.normalImpulse * vc.normal.x + vcp.tangentImpulse * vc.tangent.x;
|
|
217
|
+
const py =
|
|
218
|
+
vcp.normalImpulse * vc.normal.y + vcp.tangentImpulse * vc.tangent.y;
|
|
219
|
+
|
|
220
|
+
velA.w -= iA * (vcp.rA.x * py - vcp.rA.y * px);
|
|
221
|
+
velA.v.x -= mA * px;
|
|
222
|
+
velA.v.y -= mA * py;
|
|
223
|
+
velB.w += iB * (vcp.rB.x * py - vcp.rB.y * px);
|
|
224
|
+
velB.v.x += mB * px;
|
|
225
|
+
velB.v.y += mB * py;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @method solveVelocityConstraints
|
|
232
|
+
* @description One relaxation pass over every contact point: friction first,
|
|
233
|
+
* then the normal impulses that stop the bodies interpenetrating.
|
|
234
|
+
*/
|
|
235
|
+
solveVelocityConstraints() {
|
|
236
|
+
for (const vc of this.velocityConstraints) {
|
|
237
|
+
const velA = this.velocities[vc.indexA];
|
|
238
|
+
const velB = this.velocities[vc.indexB];
|
|
239
|
+
const mA = vc.invMassA;
|
|
240
|
+
const mB = vc.invMassB;
|
|
241
|
+
const iA = vc.invIA;
|
|
242
|
+
const iB = vc.invIB;
|
|
243
|
+
const normal = vc.normal;
|
|
244
|
+
const tangent = vc.tangent;
|
|
245
|
+
const friction = vc.friction;
|
|
246
|
+
|
|
247
|
+
for (let j = 0; j < vc.pointCount; j++) {
|
|
248
|
+
const vcp = vc.points[j];
|
|
249
|
+
const dvx =
|
|
250
|
+
velB.v.x - velB.w * vcp.rB.y - (velA.v.x - velA.w * vcp.rA.y);
|
|
251
|
+
const dvy =
|
|
252
|
+
velB.v.y + velB.w * vcp.rB.x - (velA.v.y + velA.w * vcp.rA.x);
|
|
253
|
+
|
|
254
|
+
const vt = dvx * tangent.x + dvy * tangent.y - vc.tangentSpeed;
|
|
255
|
+
let lambda = vcp.tangentMass * -vt;
|
|
256
|
+
|
|
257
|
+
const maxFriction = friction * vcp.normalImpulse;
|
|
258
|
+
const newImpulse = clamp(
|
|
259
|
+
vcp.tangentImpulse + lambda,
|
|
260
|
+
-maxFriction,
|
|
261
|
+
maxFriction
|
|
262
|
+
);
|
|
263
|
+
lambda = newImpulse - vcp.tangentImpulse;
|
|
264
|
+
vcp.tangentImpulse = newImpulse;
|
|
265
|
+
|
|
266
|
+
const px = lambda * tangent.x;
|
|
267
|
+
const py = lambda * tangent.y;
|
|
268
|
+
velA.v.x -= mA * px;
|
|
269
|
+
velA.v.y -= mA * py;
|
|
270
|
+
velA.w -= iA * (vcp.rA.x * py - vcp.rA.y * px);
|
|
271
|
+
velB.v.x += mB * px;
|
|
272
|
+
velB.v.y += mB * py;
|
|
273
|
+
velB.w += iB * (vcp.rB.x * py - vcp.rB.y * px);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (vc.pointCount === 1) {
|
|
277
|
+
const vcp = vc.points[0];
|
|
278
|
+
const dvx =
|
|
279
|
+
velB.v.x - velB.w * vcp.rB.y - (velA.v.x - velA.w * vcp.rA.y);
|
|
280
|
+
const dvy =
|
|
281
|
+
velB.v.y + velB.w * vcp.rB.x - (velA.v.y + velA.w * vcp.rA.x);
|
|
282
|
+
const vn = dvx * normal.x + dvy * normal.y;
|
|
283
|
+
|
|
284
|
+
let lambda = -vcp.normalMass * (vn - vcp.velocityBias);
|
|
285
|
+
const newImpulse = Math.max(vcp.normalImpulse + lambda, 0);
|
|
286
|
+
lambda = newImpulse - vcp.normalImpulse;
|
|
287
|
+
vcp.normalImpulse = newImpulse;
|
|
288
|
+
|
|
289
|
+
const px = lambda * normal.x;
|
|
290
|
+
const py = lambda * normal.y;
|
|
291
|
+
velA.v.x -= mA * px;
|
|
292
|
+
velA.v.y -= mA * py;
|
|
293
|
+
velA.w -= iA * (vcp.rA.x * py - vcp.rA.y * px);
|
|
294
|
+
velB.v.x += mB * px;
|
|
295
|
+
velB.v.y += mB * py;
|
|
296
|
+
velB.w += iB * (vcp.rB.x * py - vcp.rB.y * px);
|
|
297
|
+
} else {
|
|
298
|
+
this._solveBlock(vc, velA, velB, mA, mB, iA, iB);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @method _solveBlock
|
|
305
|
+
* @description Two-point block solve. Solving both points of a manifold
|
|
306
|
+
* together (rather than one after the other) is what keeps a box resting on
|
|
307
|
+
* the ground from rocking between its corners. Four candidate solutions are
|
|
308
|
+
* tried in turn: both points pushing, either one alone, or neither.
|
|
309
|
+
* @private
|
|
310
|
+
*/
|
|
311
|
+
_solveBlock(vc, velA, velB, mA, mB, iA, iB) {
|
|
312
|
+
const cp1 = vc.points[0];
|
|
313
|
+
const cp2 = vc.points[1];
|
|
314
|
+
const normal = vc.normal;
|
|
315
|
+
|
|
316
|
+
const ax = cp1.normalImpulse;
|
|
317
|
+
const ay = cp2.normalImpulse;
|
|
318
|
+
|
|
319
|
+
const dv1x = velB.v.x - velB.w * cp1.rB.y - (velA.v.x - velA.w * cp1.rA.y);
|
|
320
|
+
const dv1y = velB.v.y + velB.w * cp1.rB.x - (velA.v.y + velA.w * cp1.rA.x);
|
|
321
|
+
const dv2x = velB.v.x - velB.w * cp2.rB.y - (velA.v.x - velA.w * cp2.rA.y);
|
|
322
|
+
const dv2y = velB.v.y + velB.w * cp2.rB.x - (velA.v.y + velA.w * cp2.rA.x);
|
|
323
|
+
|
|
324
|
+
let vn1 = dv1x * normal.x + dv1y * normal.y;
|
|
325
|
+
let vn2 = dv2x * normal.x + dv2y * normal.y;
|
|
326
|
+
|
|
327
|
+
const bx = vn1 - cp1.velocityBias - (vc.K.k11 * ax + vc.K.k12 * ay);
|
|
328
|
+
const by = vn2 - cp2.velocityBias - (vc.K.k12 * ax + vc.K.k22 * ay);
|
|
329
|
+
|
|
330
|
+
let xx;
|
|
331
|
+
let xy;
|
|
332
|
+
|
|
333
|
+
for (;;) {
|
|
334
|
+
xx = -(vc.normalMass.k11 * bx + vc.normalMass.k12 * by);
|
|
335
|
+
xy = -(vc.normalMass.k12 * bx + vc.normalMass.k22 * by);
|
|
336
|
+
if (xx >= 0 && xy >= 0) break;
|
|
337
|
+
|
|
338
|
+
xx = -cp1.normalMass * bx;
|
|
339
|
+
xy = 0;
|
|
340
|
+
vn2 = vc.K.k12 * xx + by;
|
|
341
|
+
if (xx >= 0 && vn2 >= 0) break;
|
|
342
|
+
|
|
343
|
+
xx = 0;
|
|
344
|
+
xy = -cp2.normalMass * by;
|
|
345
|
+
vn1 = vc.K.k12 * xy + bx;
|
|
346
|
+
if (xy >= 0 && vn1 >= 0) break;
|
|
347
|
+
|
|
348
|
+
xx = 0;
|
|
349
|
+
xy = 0;
|
|
350
|
+
vn1 = bx;
|
|
351
|
+
vn2 = by;
|
|
352
|
+
if (vn1 >= 0 && vn2 >= 0) break;
|
|
353
|
+
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const dx = xx - ax;
|
|
358
|
+
const dy = xy - ay;
|
|
359
|
+
const p1x = dx * normal.x;
|
|
360
|
+
const p1y = dx * normal.y;
|
|
361
|
+
const p2x = dy * normal.x;
|
|
362
|
+
const p2y = dy * normal.y;
|
|
363
|
+
|
|
364
|
+
velA.v.x -= mA * (p1x + p2x);
|
|
365
|
+
velA.v.y -= mA * (p1y + p2y);
|
|
366
|
+
velA.w -=
|
|
367
|
+
iA *
|
|
368
|
+
(cp1.rA.x * p1y - cp1.rA.y * p1x + (cp2.rA.x * p2y - cp2.rA.y * p2x));
|
|
369
|
+
|
|
370
|
+
velB.v.x += mB * (p1x + p2x);
|
|
371
|
+
velB.v.y += mB * (p1y + p2y);
|
|
372
|
+
velB.w +=
|
|
373
|
+
iB *
|
|
374
|
+
(cp1.rB.x * p1y - cp1.rB.y * p1x + (cp2.rB.x * p2y - cp2.rB.y * p2x));
|
|
375
|
+
|
|
376
|
+
cp1.normalImpulse = xx;
|
|
377
|
+
cp2.normalImpulse = xy;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @method storeImpulses
|
|
382
|
+
* @description Writes the accumulated impulses back onto the manifold so the
|
|
383
|
+
* next step can warm-start from them.
|
|
384
|
+
*/
|
|
385
|
+
storeImpulses() {
|
|
386
|
+
for (const vc of this.velocityConstraints) {
|
|
387
|
+
const manifold = this.contacts[vc.contactIndex].manifold;
|
|
388
|
+
for (let j = 0; j < vc.pointCount; j++) {
|
|
389
|
+
manifold.points[j].normalImpulse = vc.points[j].normalImpulse;
|
|
390
|
+
manifold.points[j].tangentImpulse = vc.points[j].tangentImpulse;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @method solvePositionConstraints
|
|
397
|
+
* @description Pushes overlapping bodies apart geometrically. This runs on
|
|
398
|
+
* positions only, no velocity is added, so separating a deep overlap can't
|
|
399
|
+
* fling bodies across the level.
|
|
400
|
+
* @returns {boolean} - True once every overlap is within tolerance
|
|
401
|
+
*/
|
|
402
|
+
solvePositionConstraints() {
|
|
403
|
+
let minSeparation = 0;
|
|
404
|
+
|
|
405
|
+
for (const pc of this.positionConstraints) {
|
|
406
|
+
const posA = this.positions[pc.indexA];
|
|
407
|
+
const posB = this.positions[pc.indexB];
|
|
408
|
+
const mA = pc.invMassA;
|
|
409
|
+
const iA = pc.invIA;
|
|
410
|
+
const mB = pc.invMassB;
|
|
411
|
+
const iB = pc.invIB;
|
|
412
|
+
|
|
413
|
+
for (let j = 0; j < pc.pointCount; j++) {
|
|
414
|
+
xfA.q.set(posA.a);
|
|
415
|
+
xfB.q.set(posB.a);
|
|
416
|
+
xfA.p.set(
|
|
417
|
+
posA.c.x -
|
|
418
|
+
(xfA.q.c * pc.localCenterA.x - xfA.q.s * pc.localCenterA.y),
|
|
419
|
+
posA.c.y - (xfA.q.s * pc.localCenterA.x + xfA.q.c * pc.localCenterA.y)
|
|
420
|
+
);
|
|
421
|
+
xfB.p.set(
|
|
422
|
+
posB.c.x -
|
|
423
|
+
(xfB.q.c * pc.localCenterB.x - xfB.q.s * pc.localCenterB.y),
|
|
424
|
+
posB.c.y - (xfB.q.s * pc.localCenterB.x + xfB.q.c * pc.localCenterB.y)
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const psm = solverManifold(pc, xfA, xfB, j);
|
|
428
|
+
const normal = psm.normal;
|
|
429
|
+
const point = psm.point;
|
|
430
|
+
const separation = psm.separation;
|
|
431
|
+
|
|
432
|
+
const rAx = point.x - posA.c.x;
|
|
433
|
+
const rAy = point.y - posA.c.y;
|
|
434
|
+
const rBx = point.x - posB.c.x;
|
|
435
|
+
const rBy = point.y - posB.c.y;
|
|
436
|
+
|
|
437
|
+
if (separation < minSeparation) minSeparation = separation;
|
|
438
|
+
|
|
439
|
+
const C = clamp(
|
|
440
|
+
Settings.baumgarte * (separation + Settings.linearSlop),
|
|
441
|
+
-Settings.maxLinearCorrection,
|
|
442
|
+
0
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const rnA = rAx * normal.y - rAy * normal.x;
|
|
446
|
+
const rnB = rBx * normal.y - rBy * normal.x;
|
|
447
|
+
const K = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
|
|
448
|
+
const impulse = K > 0 ? -C / K : 0;
|
|
449
|
+
|
|
450
|
+
const px = impulse * normal.x;
|
|
451
|
+
const py = impulse * normal.y;
|
|
452
|
+
|
|
453
|
+
posA.c.x -= mA * px;
|
|
454
|
+
posA.c.y -= mA * py;
|
|
455
|
+
posA.a -= iA * (rAx * py - rAy * px);
|
|
456
|
+
posB.c.x += mB * px;
|
|
457
|
+
posB.c.y += mB * py;
|
|
458
|
+
posB.a += iB * (rBx * py - rBy * px);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return minSeparation >= -3 * Settings.linearSlop;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const psmNormal = new Vec2();
|
|
467
|
+
const psmPoint = new Vec2();
|
|
468
|
+
const psmResult = { normal: psmNormal, point: psmPoint, separation: 0 };
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @function solverManifold
|
|
472
|
+
* @description Rebuilds one contact point in world space from the position the
|
|
473
|
+
* solver is currently proposing.
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
function solverManifold(pc, xfA, xfB, index) {
|
|
477
|
+
switch (pc.type) {
|
|
478
|
+
case ManifoldType.CIRCLES: {
|
|
479
|
+
const pointA = Transform2.mulVec(xfA, pc.localPoint);
|
|
480
|
+
const pointB = Transform2.mulVec(xfB, pc.localPoints[0]);
|
|
481
|
+
psmNormal.set(pointB.x - pointA.x, pointB.y - pointA.y);
|
|
482
|
+
psmNormal.normalize();
|
|
483
|
+
psmPoint.set(0.5 * (pointA.x + pointB.x), 0.5 * (pointA.y + pointB.y));
|
|
484
|
+
psmResult.separation =
|
|
485
|
+
(pointB.x - pointA.x) * psmNormal.x +
|
|
486
|
+
(pointB.y - pointA.y) * psmNormal.y -
|
|
487
|
+
pc.radiusA -
|
|
488
|
+
pc.radiusB;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case ManifoldType.FACE_A: {
|
|
492
|
+
psmNormal.set(
|
|
493
|
+
xfA.q.c * pc.localNormal.x - xfA.q.s * pc.localNormal.y,
|
|
494
|
+
xfA.q.s * pc.localNormal.x + xfA.q.c * pc.localNormal.y
|
|
495
|
+
);
|
|
496
|
+
const planePoint = Transform2.mulVec(xfA, pc.localPoint);
|
|
497
|
+
const clipPoint = Transform2.mulVec(xfB, pc.localPoints[index]);
|
|
498
|
+
psmResult.separation =
|
|
499
|
+
(clipPoint.x - planePoint.x) * psmNormal.x +
|
|
500
|
+
(clipPoint.y - planePoint.y) * psmNormal.y -
|
|
501
|
+
pc.radiusA -
|
|
502
|
+
pc.radiusB;
|
|
503
|
+
psmPoint.copy(clipPoint);
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
default: {
|
|
507
|
+
psmNormal.set(
|
|
508
|
+
xfB.q.c * pc.localNormal.x - xfB.q.s * pc.localNormal.y,
|
|
509
|
+
xfB.q.s * pc.localNormal.x + xfB.q.c * pc.localNormal.y
|
|
510
|
+
);
|
|
511
|
+
const planePoint = Transform2.mulVec(xfB, pc.localPoint);
|
|
512
|
+
const clipPoint = Transform2.mulVec(xfA, pc.localPoints[index]);
|
|
513
|
+
psmResult.separation =
|
|
514
|
+
(clipPoint.x - planePoint.x) * psmNormal.x +
|
|
515
|
+
(clipPoint.y - planePoint.y) * psmNormal.y -
|
|
516
|
+
pc.radiusA -
|
|
517
|
+
pc.radiusB;
|
|
518
|
+
psmPoint.copy(clipPoint);
|
|
519
|
+
psmNormal.neg();
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return psmResult;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export default ContactSolver;
|