@ue-too/dynamics 0.5.1
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.txt +19 -0
- package/collision.d.ts +36 -0
- package/constraint.d.ts +35 -0
- package/dynamics.tsbuildinfo +1 -0
- package/index.d.ts +3 -0
- package/index.js +925 -0
- package/index.js.map +1 -0
- package/package.json +22 -0
- package/quadtree.d.ts +26 -0
- package/rigidbody.d.ts +252 -0
- package/world.d.ts +29 -0
package/index.js
ADDED
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
import { PointCal } from '@ue-too/math';
|
|
2
|
+
|
|
3
|
+
class BaseRigidBody {
|
|
4
|
+
constructor(center, _orientationAngle = 0, mass = 50, isStaticBody = false, frictionEnabled = false) {
|
|
5
|
+
this._mass = 50;
|
|
6
|
+
this._orientationAngle = 0;
|
|
7
|
+
this.isStaticBody = false;
|
|
8
|
+
this._staticFrictionCoeff = 0.3;
|
|
9
|
+
this.dynamicFrictionCoeff = 0.3;
|
|
10
|
+
this.frictionEnabled = false;
|
|
11
|
+
this.isMovingStaticBody = false;
|
|
12
|
+
this.angularDampingFactor = 0.005;
|
|
13
|
+
this._center = center;
|
|
14
|
+
this._orientationAngle = _orientationAngle;
|
|
15
|
+
this._mass = mass;
|
|
16
|
+
this.isStaticBody = isStaticBody;
|
|
17
|
+
this.frictionEnabled = frictionEnabled;
|
|
18
|
+
this.force = { x: 0, y: 0 };
|
|
19
|
+
this.linearAcceleartion = { x: 0, y: 0 };
|
|
20
|
+
this._linearVelocity = { x: 0, y: 0 };
|
|
21
|
+
this._angularVelocity = 0;
|
|
22
|
+
}
|
|
23
|
+
move(delta) {
|
|
24
|
+
if (!this.isStatic()) {
|
|
25
|
+
this._center = PointCal.addVector(this._center, delta);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
rotateRadians(angle) {
|
|
29
|
+
this._orientationAngle += angle;
|
|
30
|
+
}
|
|
31
|
+
getCenter() {
|
|
32
|
+
return this._center;
|
|
33
|
+
}
|
|
34
|
+
getOrientationAngle() {
|
|
35
|
+
return this._orientationAngle;
|
|
36
|
+
}
|
|
37
|
+
get angularVelocity() {
|
|
38
|
+
return this._angularVelocity;
|
|
39
|
+
}
|
|
40
|
+
set angularVelocity(angularVelocity) {
|
|
41
|
+
this._angularVelocity = angularVelocity;
|
|
42
|
+
}
|
|
43
|
+
get orientationAngle() {
|
|
44
|
+
return this._orientationAngle;
|
|
45
|
+
}
|
|
46
|
+
isStatic() {
|
|
47
|
+
return this.isStaticBody;
|
|
48
|
+
}
|
|
49
|
+
isMovingStatic() {
|
|
50
|
+
return this.isMovingStaticBody;
|
|
51
|
+
}
|
|
52
|
+
setLinearVelocity(linearVelocity) {
|
|
53
|
+
this._linearVelocity = linearVelocity;
|
|
54
|
+
}
|
|
55
|
+
setMovingStatic(movingStatic) {
|
|
56
|
+
this.isMovingStaticBody = movingStatic;
|
|
57
|
+
}
|
|
58
|
+
setOrientationAngle(angle) {
|
|
59
|
+
this._orientationAngle = angle;
|
|
60
|
+
}
|
|
61
|
+
applyForce(force) {
|
|
62
|
+
if (PointCal.magnitude(this.force) !== 0) {
|
|
63
|
+
this.force = PointCal.addVector(this.force, force);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.force = force;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
applyForceInOrientation(force) {
|
|
70
|
+
let forceTransformed;
|
|
71
|
+
if (typeof force === "number") {
|
|
72
|
+
forceTransformed = PointCal.rotatePoint({ x: force, y: 0 }, this._orientationAngle);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
forceTransformed = PointCal.rotatePoint(force, this._orientationAngle);
|
|
76
|
+
}
|
|
77
|
+
this.applyForce(forceTransformed);
|
|
78
|
+
}
|
|
79
|
+
step(deltaTime) {
|
|
80
|
+
if (this.frictionEnabled) {
|
|
81
|
+
if (this.isStatic() ||
|
|
82
|
+
(this.linearVelocity.x == 0 &&
|
|
83
|
+
this.linearVelocity.y == 0 &&
|
|
84
|
+
PointCal.magnitude(PointCal.subVector({ x: this.force.x, y: this.force.y }, { x: 0, y: 0 })) >= 0 &&
|
|
85
|
+
PointCal.magnitude({ x: this.force.x, y: this.force.y }) < this.staticFrictionCoeff * this.mass * 9.81)) {
|
|
86
|
+
if (this.force.z != undefined) {
|
|
87
|
+
this.force = { x: 0, y: 0, z: this.force.z };
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.force = { x: 0, y: 0 };
|
|
91
|
+
}
|
|
92
|
+
// return;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
let kineticFrictionDirection = PointCal.multiplyVectorByScalar(PointCal.unitVector({ x: this._linearVelocity.x, y: this._linearVelocity.y }), -1);
|
|
96
|
+
let kineticFriction = PointCal.multiplyVectorByScalar(kineticFrictionDirection, this.dynamicFrictionCoeff * this.mass * 9.81);
|
|
97
|
+
this.force = PointCal.addVector(this.force, kineticFriction);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const angularDamping = this._angularVelocity != 0 ? this._angularVelocity > 0 ? -this.angularDampingFactor : this.angularDampingFactor : 0;
|
|
101
|
+
// console.log("angular velocity", this._angularVelocity);
|
|
102
|
+
// console.log("angular damping", angularDamping);
|
|
103
|
+
if (Math.abs(this._angularVelocity) < Math.abs(angularDamping)) {
|
|
104
|
+
this._angularVelocity = 0;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this._angularVelocity += angularDamping;
|
|
108
|
+
}
|
|
109
|
+
this._orientationAngle += this._angularVelocity * deltaTime;
|
|
110
|
+
if (PointCal.magnitude({ x: this._linearVelocity.x, y: this._linearVelocity.y }) < PointCal.magnitude(PointCal.divideVectorByScalar(PointCal.multiplyVectorByScalar(this.force, deltaTime), this.mass))) {
|
|
111
|
+
if (this._linearVelocity.z != undefined) {
|
|
112
|
+
this._linearVelocity = { x: 0, y: 0, z: this._linearVelocity.z };
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
this._linearVelocity = { x: 0, y: 0 };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const gravitationalForce = -9.81 * this._mass;
|
|
119
|
+
this.force = PointCal.addVector(this.force, { x: 0, y: 0, z: gravitationalForce });
|
|
120
|
+
this._linearVelocity = PointCal.addVector(this._linearVelocity, PointCal.divideVectorByScalar(PointCal.multiplyVectorByScalar(this.force, deltaTime), this.mass));
|
|
121
|
+
this._center = PointCal.addVector(this._center, PointCal.multiplyVectorByScalar(this._linearVelocity, deltaTime));
|
|
122
|
+
if (this._center.z != undefined && this._center.z < 0) {
|
|
123
|
+
this._center.z = 0;
|
|
124
|
+
}
|
|
125
|
+
this.force = { x: 0, y: 0 };
|
|
126
|
+
}
|
|
127
|
+
get center() {
|
|
128
|
+
return this._center;
|
|
129
|
+
}
|
|
130
|
+
set center(dest) {
|
|
131
|
+
this._center = dest;
|
|
132
|
+
}
|
|
133
|
+
get linearVelocity() {
|
|
134
|
+
return this._linearVelocity;
|
|
135
|
+
}
|
|
136
|
+
set linearVelocity(dest) {
|
|
137
|
+
this._linearVelocity = dest;
|
|
138
|
+
}
|
|
139
|
+
get mass() {
|
|
140
|
+
return this._mass;
|
|
141
|
+
}
|
|
142
|
+
get staticFrictionCoeff() {
|
|
143
|
+
return this._staticFrictionCoeff;
|
|
144
|
+
}
|
|
145
|
+
set staticFrictionCoeff(coeff) {
|
|
146
|
+
this._staticFrictionCoeff = coeff;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
class VisaulCircleBody {
|
|
150
|
+
constructor(center = { x: 0, y: 0 }, radius, drawingContext, _orientationAngle = 0, mass = 50, isStatic = false, frictionEnabled = true) {
|
|
151
|
+
this._circle = new Circle(center, radius, _orientationAngle, mass, isStatic, frictionEnabled);
|
|
152
|
+
this._context = drawingContext;
|
|
153
|
+
}
|
|
154
|
+
draw(ctx) {
|
|
155
|
+
ctx.beginPath();
|
|
156
|
+
ctx.arc(this._circle.center.x, this._circle.center.y, this._circle.radius, 0, 2 * Math.PI);
|
|
157
|
+
ctx.stroke();
|
|
158
|
+
}
|
|
159
|
+
step(deltaTime) {
|
|
160
|
+
this._circle.step(deltaTime);
|
|
161
|
+
this.draw(this._context);
|
|
162
|
+
}
|
|
163
|
+
isStatic() {
|
|
164
|
+
return this._circle.isStatic();
|
|
165
|
+
}
|
|
166
|
+
isMovingStatic() {
|
|
167
|
+
return this._circle.isMovingStatic();
|
|
168
|
+
}
|
|
169
|
+
getMinMaxProjection(unitvector) {
|
|
170
|
+
return this._circle.getMinMaxProjection(unitvector);
|
|
171
|
+
}
|
|
172
|
+
getCollisionAxes(relativeBody) {
|
|
173
|
+
return this._circle.getCollisionAxes(relativeBody);
|
|
174
|
+
}
|
|
175
|
+
applyForce(force) {
|
|
176
|
+
this._circle.applyForce(force);
|
|
177
|
+
}
|
|
178
|
+
get AABB() {
|
|
179
|
+
return this._circle.AABB;
|
|
180
|
+
}
|
|
181
|
+
getMass() {
|
|
182
|
+
return this._circle.mass;
|
|
183
|
+
}
|
|
184
|
+
applyForceInOrientation(force) {
|
|
185
|
+
this._circle.applyForceInOrientation(force);
|
|
186
|
+
}
|
|
187
|
+
move(delta) {
|
|
188
|
+
this._circle.move(delta);
|
|
189
|
+
}
|
|
190
|
+
getSignificantVertices(collisionNormal) {
|
|
191
|
+
return this._circle.getSignificantVertices(collisionNormal);
|
|
192
|
+
}
|
|
193
|
+
get center() {
|
|
194
|
+
return this._circle.center;
|
|
195
|
+
}
|
|
196
|
+
set center(dest) {
|
|
197
|
+
this._circle.center = dest;
|
|
198
|
+
}
|
|
199
|
+
get linearVelocity() {
|
|
200
|
+
return this._circle.linearVelocity;
|
|
201
|
+
}
|
|
202
|
+
set linearVelocity(dest) {
|
|
203
|
+
this._circle.linearVelocity = dest;
|
|
204
|
+
}
|
|
205
|
+
get orientationAngle() {
|
|
206
|
+
return this._circle.orientationAngle;
|
|
207
|
+
}
|
|
208
|
+
significantVertex(collisionNormal) {
|
|
209
|
+
return this._circle.significantVertex(collisionNormal);
|
|
210
|
+
}
|
|
211
|
+
set angularVelocity(angularVelocity) {
|
|
212
|
+
this._circle.angularVelocity = angularVelocity;
|
|
213
|
+
}
|
|
214
|
+
get angularVelocity() {
|
|
215
|
+
return this._circle.angularVelocity;
|
|
216
|
+
}
|
|
217
|
+
get mass() {
|
|
218
|
+
return this._circle.mass;
|
|
219
|
+
}
|
|
220
|
+
getNormalOfSignificantFace(collisionNormal) {
|
|
221
|
+
return this._circle.getNormalOfSignificantFace(collisionNormal);
|
|
222
|
+
}
|
|
223
|
+
get staticFrictionCoeff() {
|
|
224
|
+
return this._circle.staticFrictionCoeff;
|
|
225
|
+
}
|
|
226
|
+
set staticFrictionCoeff(coeff) {
|
|
227
|
+
this._circle.staticFrictionCoeff = coeff;
|
|
228
|
+
}
|
|
229
|
+
getAdjacentFaces(collisionNormal) {
|
|
230
|
+
return this._circle.getAdjacentFaces(collisionNormal);
|
|
231
|
+
}
|
|
232
|
+
get momentOfInertia() {
|
|
233
|
+
return this._circle.momentOfInertia;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
class VisualPolygonBody {
|
|
237
|
+
constructor(center = { x: 0, y: 0 }, vertices, drawingContext, _orientationAngle = 0, mass = 50, isStatic = false, frictionEnabled = true) {
|
|
238
|
+
this._polygon = new Polygon(center, vertices, _orientationAngle, mass, isStatic, frictionEnabled);
|
|
239
|
+
this._context = drawingContext;
|
|
240
|
+
}
|
|
241
|
+
draw(ctx) {
|
|
242
|
+
ctx.beginPath();
|
|
243
|
+
let vertices = this._polygon.getVerticesAbsCoord();
|
|
244
|
+
ctx.moveTo(vertices[0].x, vertices[0].y);
|
|
245
|
+
vertices.forEach(vertex => {
|
|
246
|
+
ctx.lineTo(vertex.x, vertex.y);
|
|
247
|
+
});
|
|
248
|
+
ctx.lineTo(vertices[0].x, vertices[0].y);
|
|
249
|
+
ctx.stroke();
|
|
250
|
+
// ctx.beginPath();
|
|
251
|
+
// ctx.rect(this._polygon.AABB.min.x, this._polygon.AABB.min.y, this._polygon.AABB.max.x - this._polygon.AABB.min.x, this._polygon.AABB.max.y - this._polygon.AABB.min.y);
|
|
252
|
+
// ctx.stroke();
|
|
253
|
+
}
|
|
254
|
+
step(deltaTime) {
|
|
255
|
+
this._polygon.step(deltaTime);
|
|
256
|
+
this.draw(this._context);
|
|
257
|
+
}
|
|
258
|
+
isStatic() {
|
|
259
|
+
return this._polygon.isStatic();
|
|
260
|
+
}
|
|
261
|
+
isMovingStatic() {
|
|
262
|
+
return this._polygon.isMovingStatic();
|
|
263
|
+
}
|
|
264
|
+
getMinMaxProjection(unitvector) {
|
|
265
|
+
return this._polygon.getMinMaxProjection(unitvector);
|
|
266
|
+
}
|
|
267
|
+
getCollisionAxes(relativeBody) {
|
|
268
|
+
return this._polygon.getCollisionAxes(relativeBody);
|
|
269
|
+
}
|
|
270
|
+
applyForce(force) {
|
|
271
|
+
this._polygon.applyForce(force);
|
|
272
|
+
}
|
|
273
|
+
applyForceInOrientation(force) {
|
|
274
|
+
this._polygon.applyForceInOrientation(force);
|
|
275
|
+
}
|
|
276
|
+
setLinearVelocity(linearVelocity) {
|
|
277
|
+
this._polygon.setLinearVelocity(linearVelocity);
|
|
278
|
+
}
|
|
279
|
+
move(delta) {
|
|
280
|
+
this._polygon.move(delta);
|
|
281
|
+
}
|
|
282
|
+
get center() {
|
|
283
|
+
return this._polygon.center;
|
|
284
|
+
}
|
|
285
|
+
set center(dest) {
|
|
286
|
+
this._polygon.center = dest;
|
|
287
|
+
}
|
|
288
|
+
get linearVelocity() {
|
|
289
|
+
return this._polygon.linearVelocity;
|
|
290
|
+
}
|
|
291
|
+
set linearVelocity(dest) {
|
|
292
|
+
this._polygon.linearVelocity = dest;
|
|
293
|
+
}
|
|
294
|
+
get angularVelocity() {
|
|
295
|
+
return this._polygon.angularVelocity;
|
|
296
|
+
}
|
|
297
|
+
set angularVelocity(angularVelocity) {
|
|
298
|
+
this._polygon.angularVelocity = angularVelocity;
|
|
299
|
+
}
|
|
300
|
+
get orientationAngle() {
|
|
301
|
+
return this._polygon.orientationAngle;
|
|
302
|
+
}
|
|
303
|
+
significantVertex(collisionNormal) {
|
|
304
|
+
return this._polygon.significantVertex(collisionNormal);
|
|
305
|
+
}
|
|
306
|
+
getSignificantVertices(collisionNormal) {
|
|
307
|
+
return this._polygon.getSignificantVertices(collisionNormal);
|
|
308
|
+
}
|
|
309
|
+
get AABB() {
|
|
310
|
+
return this._polygon.AABB;
|
|
311
|
+
}
|
|
312
|
+
get mass() {
|
|
313
|
+
return this._polygon.mass;
|
|
314
|
+
}
|
|
315
|
+
get staticFrictionCoeff() {
|
|
316
|
+
return this._polygon.staticFrictionCoeff;
|
|
317
|
+
}
|
|
318
|
+
set staticFrictionCoeff(coeff) {
|
|
319
|
+
this._polygon.staticFrictionCoeff = coeff;
|
|
320
|
+
}
|
|
321
|
+
get momentOfInertia() {
|
|
322
|
+
return this._polygon.momentOfInertia;
|
|
323
|
+
}
|
|
324
|
+
getNormalOfSignificantFace(collisionNormal) {
|
|
325
|
+
return this._polygon.getNormalOfSignificantFace(collisionNormal);
|
|
326
|
+
}
|
|
327
|
+
getAdjacentFaces(collisionNormal) {
|
|
328
|
+
return this._polygon.getAdjacentFaces(collisionNormal);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
class Polygon extends BaseRigidBody {
|
|
332
|
+
constructor(center = { x: 0, y: 0 }, vertices, _orientationAngle = 0, mass = 50, isStatic = false, frictionEnabled = true) {
|
|
333
|
+
super(center, _orientationAngle, mass, isStatic, frictionEnabled);
|
|
334
|
+
this.vertices = vertices;
|
|
335
|
+
this.step = this.step.bind(this);
|
|
336
|
+
let numerator = this.vertices.reduce((acc, vertex, index) => {
|
|
337
|
+
let nextPointIndex = index < this.vertices.length - 1 ? index + 1 : 0;
|
|
338
|
+
let nextPoint = this.vertices[nextPointIndex];
|
|
339
|
+
let crossProduct = PointCal.crossProduct(nextPoint, vertex);
|
|
340
|
+
return acc + PointCal.magnitude(crossProduct) * (PointCal.dotProduct(vertex, vertex) + PointCal.dotProduct(vertex, nextPoint) + PointCal.dotProduct(nextPoint, nextPoint));
|
|
341
|
+
}, 0);
|
|
342
|
+
let denomiator = this.vertices.reduce((acc, vertex, index) => {
|
|
343
|
+
return acc + PointCal.magnitude(PointCal.crossProduct(this.vertices[index < this.vertices.length - 1 ? index + 1 : 0], vertex));
|
|
344
|
+
}, 0);
|
|
345
|
+
this._momentOfInertia = this._mass * numerator / (6 * denomiator);
|
|
346
|
+
}
|
|
347
|
+
getVerticesAbsCoord() {
|
|
348
|
+
return this.vertices.map(vertex => {
|
|
349
|
+
return PointCal.addVector(this._center, PointCal.rotatePoint(vertex, this._orientationAngle));
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
getCollisionAxes(relativeBody) {
|
|
353
|
+
return this.getVerticesAbsCoord().map((vertex, index, absVertices) => {
|
|
354
|
+
let vector = PointCal.subVector(vertex, absVertices[absVertices.length - 1]);
|
|
355
|
+
if (index > 0) {
|
|
356
|
+
vector = PointCal.subVector(vertex, absVertices[index - 1]);
|
|
357
|
+
}
|
|
358
|
+
return PointCal.unitVector(PointCal.rotatePoint(vector, Math.PI / 2));
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
getMinMaxProjection(unitvector) {
|
|
362
|
+
let vertices = this.getVerticesAbsCoord();
|
|
363
|
+
let projections = vertices.map(vertex => {
|
|
364
|
+
return PointCal.dotProduct(vertex, unitvector);
|
|
365
|
+
});
|
|
366
|
+
return { min: Math.min(...projections), max: Math.max(...projections) };
|
|
367
|
+
}
|
|
368
|
+
get AABB() {
|
|
369
|
+
let points = this.getVerticesAbsCoord();
|
|
370
|
+
let xCoords = points.map(vertex => vertex.x);
|
|
371
|
+
let yCoords = points.map(vertex => vertex.y);
|
|
372
|
+
return { min: { x: Math.min(...xCoords), y: Math.min(...yCoords) }, max: { x: Math.max(...xCoords), y: Math.max(...yCoords) } };
|
|
373
|
+
}
|
|
374
|
+
significantVertex(collisionNormal) {
|
|
375
|
+
let vertices = this.getVerticesAbsCoord();
|
|
376
|
+
let verticesProjected = vertices.map(vertex => PointCal.dotProduct(vertex, collisionNormal));
|
|
377
|
+
let maxIndex = verticesProjected.indexOf(Math.max(...verticesProjected));
|
|
378
|
+
return vertices[maxIndex];
|
|
379
|
+
}
|
|
380
|
+
getSignificantVertices(collisionNormal) {
|
|
381
|
+
let vertices = this.getVerticesAbsCoord();
|
|
382
|
+
let verticesProjected = vertices.map(vertex => PointCal.dotProduct(vertex, collisionNormal));
|
|
383
|
+
let maxIndex = verticesProjected.indexOf(Math.max(...verticesProjected));
|
|
384
|
+
const tipVertex = vertices[maxIndex];
|
|
385
|
+
let prevPointIndex = maxIndex > 0 ? maxIndex - 1 : vertices.length - 1;
|
|
386
|
+
let nextPointIndex = maxIndex < vertices.length - 1 ? maxIndex + 1 : 0;
|
|
387
|
+
const prevPoint = vertices[prevPointIndex];
|
|
388
|
+
const nextPoint = vertices[nextPointIndex];
|
|
389
|
+
const prevPointProjected = PointCal.dotProduct(prevPoint, collisionNormal);
|
|
390
|
+
const nextPointProjected = PointCal.dotProduct(nextPoint, collisionNormal);
|
|
391
|
+
if (prevPointProjected > nextPointProjected) {
|
|
392
|
+
return [prevPoint, tipVertex];
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
return [tipVertex, nextPoint];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
getNormalOfSignificantFace(collisionNormal) {
|
|
399
|
+
const vertices = this.getSignificantVertices(collisionNormal);
|
|
400
|
+
const direction = PointCal.unitVectorFromA2B(vertices[0], vertices[1]);
|
|
401
|
+
return PointCal.rotatePoint(direction, -Math.PI / 2);
|
|
402
|
+
}
|
|
403
|
+
getAdjacentFaces(collisionNormal) {
|
|
404
|
+
let vertices = this.getVerticesAbsCoord();
|
|
405
|
+
let verticesProjected = vertices.map(vertex => PointCal.dotProduct(vertex, collisionNormal));
|
|
406
|
+
let maxIndex = verticesProjected.indexOf(Math.max(...verticesProjected));
|
|
407
|
+
const tipVertex = vertices[maxIndex];
|
|
408
|
+
let prevPointIndex = maxIndex > 0 ? maxIndex - 1 : vertices.length - 1;
|
|
409
|
+
let nextPointIndex = maxIndex < vertices.length - 1 ? maxIndex + 1 : 0;
|
|
410
|
+
const prevPoint = vertices[prevPointIndex];
|
|
411
|
+
const nextPoint = vertices[nextPointIndex];
|
|
412
|
+
const prevPointProjected = PointCal.dotProduct(prevPoint, collisionNormal);
|
|
413
|
+
const nextPointProjected = PointCal.dotProduct(nextPoint, collisionNormal);
|
|
414
|
+
const adjacentFacesWithIndex = [];
|
|
415
|
+
if (prevPointProjected > nextPointProjected) {
|
|
416
|
+
adjacentFacesWithIndex.push({ startPoint: { coord: prevPoint, index: prevPointIndex }, endPoint: { coord: tipVertex, index: maxIndex } });
|
|
417
|
+
// the nextface is the next face
|
|
418
|
+
adjacentFacesWithIndex.unshift({ startPoint: { coord: tipVertex, index: maxIndex }, endPoint: { coord: nextPoint, index: nextPointIndex } });
|
|
419
|
+
// need to get the previous face of the previous face
|
|
420
|
+
let prevPrevPointIndex = prevPointIndex > 0 ? prevPointIndex - 1 : vertices.length - 1;
|
|
421
|
+
adjacentFacesWithIndex.unshift({ startPoint: { coord: vertices[prevPrevPointIndex], index: prevPrevPointIndex }, endPoint: { coord: prevPoint, index: prevPointIndex } });
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
adjacentFacesWithIndex.push({ startPoint: { coord: tipVertex, index: maxIndex }, endPoint: { coord: nextPoint, index: nextPointIndex } });
|
|
425
|
+
// need to get the next face of the next face
|
|
426
|
+
let nextNextPointIndex = nextPointIndex < vertices.length - 1 ? nextPointIndex + 1 : 0;
|
|
427
|
+
adjacentFacesWithIndex.unshift({ startPoint: { coord: nextPoint, index: nextPointIndex }, endPoint: { coord: vertices[nextNextPointIndex], index: nextNextPointIndex } });
|
|
428
|
+
// the prevoius face is the previous face
|
|
429
|
+
adjacentFacesWithIndex.unshift({ startPoint: { coord: prevPoint, index: prevPointIndex }, endPoint: { coord: tipVertex, index: maxIndex } });
|
|
430
|
+
}
|
|
431
|
+
return adjacentFacesWithIndex;
|
|
432
|
+
}
|
|
433
|
+
get momentOfInertia() {
|
|
434
|
+
return this._momentOfInertia;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
class Circle extends BaseRigidBody {
|
|
438
|
+
constructor(center = { x: 0, y: 0 }, radius, _orientationAngle = 0, mass = 50, isStatic = false, frictionEnabled = true) {
|
|
439
|
+
super(center, _orientationAngle, mass, isStatic, frictionEnabled);
|
|
440
|
+
this._radius = radius;
|
|
441
|
+
this.step = this.step.bind(this);
|
|
442
|
+
this._momentOfInertia = this._mass * this._radius * this._radius / 2;
|
|
443
|
+
}
|
|
444
|
+
getMinMaxProjection(unitvector) {
|
|
445
|
+
let PositiveFurthest = PointCal.addVector(this._center, PointCal.multiplyVectorByScalar(unitvector, this._radius));
|
|
446
|
+
let NegativeFurthest = PointCal.addVector(this._center, PointCal.multiplyVectorByScalar(unitvector, -this._radius));
|
|
447
|
+
return { min: PointCal.dotProduct(NegativeFurthest, unitvector), max: PointCal.dotProduct(PositiveFurthest, unitvector) };
|
|
448
|
+
}
|
|
449
|
+
getCollisionAxes(relativeBody) {
|
|
450
|
+
return [PointCal.unitVector(PointCal.subVector(relativeBody.center, this._center))];
|
|
451
|
+
}
|
|
452
|
+
get AABB() {
|
|
453
|
+
return { min: PointCal.subVector(this._center, { x: this._radius, y: this._radius }), max: PointCal.addVector(this._center, { x: this._radius, y: this._radius }) };
|
|
454
|
+
}
|
|
455
|
+
significantVertex(collisionNormal) {
|
|
456
|
+
return PointCal.addVector(this._center, PointCal.multiplyVectorByScalar(collisionNormal, this._radius));
|
|
457
|
+
}
|
|
458
|
+
get radius() {
|
|
459
|
+
return this._radius;
|
|
460
|
+
}
|
|
461
|
+
getSignificantVertices(collisionNormal) {
|
|
462
|
+
return [PointCal.addVector(this._center, PointCal.multiplyVectorByScalar(collisionNormal, this._radius))];
|
|
463
|
+
}
|
|
464
|
+
getNormalOfSignificantFace(collisionNormal) {
|
|
465
|
+
return PointCal.unitVector(collisionNormal);
|
|
466
|
+
}
|
|
467
|
+
getAdjacentFaces(collisionNormal) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
get momentOfInertia() {
|
|
471
|
+
return this._momentOfInertia;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
class RectangleBound {
|
|
476
|
+
constructor(bottomLeft, width, height) {
|
|
477
|
+
this.bottomLeft = bottomLeft;
|
|
478
|
+
this.width = width;
|
|
479
|
+
this.height = height;
|
|
480
|
+
}
|
|
481
|
+
getWidth() {
|
|
482
|
+
return this.width;
|
|
483
|
+
}
|
|
484
|
+
getHeight() {
|
|
485
|
+
return this.height;
|
|
486
|
+
}
|
|
487
|
+
getbottomLeft() {
|
|
488
|
+
return this.bottomLeft;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
class QuadTree {
|
|
492
|
+
constructor(level, bounds) {
|
|
493
|
+
this.MAX_OBJECTS = 10; // per node
|
|
494
|
+
this.MAX_LEVELS = 5;
|
|
495
|
+
this.objects = [];
|
|
496
|
+
this.nodes = [];
|
|
497
|
+
this.level = level;
|
|
498
|
+
this.objects = [];
|
|
499
|
+
this.bounds = bounds;
|
|
500
|
+
this.nodes = [undefined, undefined, undefined, undefined];
|
|
501
|
+
}
|
|
502
|
+
draw(context) {
|
|
503
|
+
context.beginPath();
|
|
504
|
+
context.rect(this.bounds.getbottomLeft().x, this.bounds.getbottomLeft().y, this.bounds.getWidth(), this.bounds.getHeight());
|
|
505
|
+
context.stroke();
|
|
506
|
+
// console.log("objects: ", this.objects.length, "level: ", this.level);
|
|
507
|
+
for (let index = 0; index < this.nodes.length; index++) {
|
|
508
|
+
let node = this.nodes[index];
|
|
509
|
+
if (node != undefined) {
|
|
510
|
+
node.draw(context);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
clear() {
|
|
515
|
+
this.objects = [];
|
|
516
|
+
for (let index = 0; index < this.nodes.length; index++) {
|
|
517
|
+
let node = this.nodes[index];
|
|
518
|
+
if (node != undefined) {
|
|
519
|
+
node.clear();
|
|
520
|
+
node = undefined;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
split() {
|
|
525
|
+
// console.log("split");
|
|
526
|
+
let subWidth = this.bounds.getWidth() / 2;
|
|
527
|
+
let subHeight = this.bounds.getHeight() / 2;
|
|
528
|
+
let bottomLeft = this.bounds.getbottomLeft();
|
|
529
|
+
// bottom left is the first node and it goes clock wise
|
|
530
|
+
this.nodes[0] = new QuadTree(this.level + 1, new RectangleBound({ x: bottomLeft.x, y: bottomLeft.y }, subWidth, subHeight));
|
|
531
|
+
this.nodes[1] = new QuadTree(this.level + 1, new RectangleBound({ x: bottomLeft.x, y: bottomLeft.y + subHeight }, subWidth, subHeight));
|
|
532
|
+
this.nodes[2] = new QuadTree(this.level + 1, new RectangleBound({ x: bottomLeft.x + subWidth, y: bottomLeft.y + subHeight }, subWidth, subHeight));
|
|
533
|
+
this.nodes[3] = new QuadTree(this.level + 1, new RectangleBound({ x: bottomLeft.x + subWidth, y: bottomLeft.y }, subWidth, subHeight));
|
|
534
|
+
}
|
|
535
|
+
getIndex(vBody) {
|
|
536
|
+
let midPoint = { x: this.bounds.getbottomLeft().x + this.bounds.getWidth() / 2, y: this.bounds.getbottomLeft().y + this.bounds.getHeight() / 2 };
|
|
537
|
+
let points = vBody.AABB;
|
|
538
|
+
let bottom = points.max.y < midPoint.y && points.min.y > this.bounds.getbottomLeft().y;
|
|
539
|
+
let left = points.max.x < midPoint.x && points.min.x > this.bounds.getbottomLeft().x;
|
|
540
|
+
let right = points.max.x > midPoint.x && points.min.x > midPoint.x;
|
|
541
|
+
let top = points.max.y > midPoint.y && points.min.y > midPoint.y;
|
|
542
|
+
// console.log("level", this.level);
|
|
543
|
+
if (bottom && left) {
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
else if (bottom && right) {
|
|
547
|
+
return 3;
|
|
548
|
+
}
|
|
549
|
+
else if (top && left) {
|
|
550
|
+
return 1;
|
|
551
|
+
}
|
|
552
|
+
else if (top && right) {
|
|
553
|
+
return 2;
|
|
554
|
+
}
|
|
555
|
+
return -1;
|
|
556
|
+
}
|
|
557
|
+
insert(vBody) {
|
|
558
|
+
let node = this.nodes[0];
|
|
559
|
+
if (node != undefined) {
|
|
560
|
+
let index = this.getIndex(vBody);
|
|
561
|
+
if (index !== -1) {
|
|
562
|
+
node = this.nodes[index];
|
|
563
|
+
node?.insert(vBody);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
this.objects.push(vBody);
|
|
568
|
+
if (this.objects.length > this.MAX_OBJECTS && this.level < this.MAX_LEVELS) {
|
|
569
|
+
if (this.nodes[0] == null || this.nodes[0] == undefined) {
|
|
570
|
+
this.split();
|
|
571
|
+
}
|
|
572
|
+
let i = 0;
|
|
573
|
+
while (i < this.objects.length) {
|
|
574
|
+
let index = this.getIndex(this.objects[i]);
|
|
575
|
+
let node = this.nodes[index];
|
|
576
|
+
if (index != -1 && node !== undefined) {
|
|
577
|
+
let vBody = this.objects[i];
|
|
578
|
+
this.objects.splice(i, 1);
|
|
579
|
+
node.insert(vBody);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
i++;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
retrieve(vBody) {
|
|
588
|
+
let index = this.getIndex(vBody);
|
|
589
|
+
let res = [];
|
|
590
|
+
let node = this.nodes[index];
|
|
591
|
+
if (index !== -1 && node !== undefined) {
|
|
592
|
+
res.push(...node.retrieve(vBody));
|
|
593
|
+
}
|
|
594
|
+
res.push(...this.objects);
|
|
595
|
+
return res;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function resolveCollision(bodyA, bodyB, normal) {
|
|
600
|
+
// console.log("resolve");
|
|
601
|
+
if (bodyA.isStatic() && bodyB.isStatic()) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
let inverseMassA = bodyA.isStatic() || bodyA.isMovingStatic() ? 0 : 1 / bodyA.mass;
|
|
605
|
+
let inverseMassB = bodyB.isStatic() || bodyB.isMovingStatic() ? 0 : 1 / bodyB.mass;
|
|
606
|
+
// console.log("inverse mass a", inverseMassA);
|
|
607
|
+
// console.log("inverse mass b", inverseMassB);
|
|
608
|
+
let relativeVelocity = PointCal.subVector(bodyA.linearVelocity, bodyB.linearVelocity);
|
|
609
|
+
// console.log("relative velocity: ", relativeVelocity);
|
|
610
|
+
// console.log("linear velocity of a", bodyA.getLinearVelocity());
|
|
611
|
+
// console.log("linear veolcity of b", bodyB.getLinearVelocity());
|
|
612
|
+
let J = -1.4 * PointCal.dotProduct(relativeVelocity, normal);
|
|
613
|
+
J /= inverseMassA + inverseMassB;
|
|
614
|
+
let deltaVelocityA = PointCal.multiplyVectorByScalar(normal, J * inverseMassA);
|
|
615
|
+
let deltaVelocityB = PointCal.multiplyVectorByScalar(normal, J * inverseMassB);
|
|
616
|
+
// console.log("delta velocity A:", deltaVelocityA);
|
|
617
|
+
// console.log("delta velocity B:", deltaVelocityB);
|
|
618
|
+
bodyA.linearVelocity = PointCal.addVector(bodyA.linearVelocity, deltaVelocityA);
|
|
619
|
+
bodyB.linearVelocity = PointCal.subVector(bodyB.linearVelocity, deltaVelocityB);
|
|
620
|
+
}
|
|
621
|
+
function resolveCollisionWithRotation(bodyA, bodyB, contactManifold) {
|
|
622
|
+
// console.log("resolve");
|
|
623
|
+
if (bodyA.isStatic() && bodyB.isStatic()) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
let inverseMassA = bodyA.isStatic() || bodyA.isMovingStatic() ? 0 : 1 / bodyA.mass;
|
|
627
|
+
let inverseMassB = bodyB.isStatic() || bodyB.isMovingStatic() ? 0 : 1 / bodyB.mass;
|
|
628
|
+
let inverseMMOIA = bodyA.isStatic() || bodyA.isMovingStatic() ? 0 : 1 / bodyA.momentOfInertia;
|
|
629
|
+
let inverseMMOIB = bodyB.isStatic() || bodyB.isMovingStatic() ? 0 : 1 / bodyB.momentOfInertia;
|
|
630
|
+
const Js = [];
|
|
631
|
+
for (let index = 0; index < contactManifold.contactPoints.length; index++) {
|
|
632
|
+
const contactPoint = contactManifold.contactPoints[index];
|
|
633
|
+
const rA = PointCal.subVector(contactPoint, bodyA.center);
|
|
634
|
+
const rB = PointCal.subVector(contactPoint, bodyB.center);
|
|
635
|
+
const rAPerpendicular = { x: -rA.y, y: rA.x };
|
|
636
|
+
const rBPerpendicular = { x: -rB.y, y: rB.x };
|
|
637
|
+
const angularVelocityA = PointCal.multiplyVectorByScalar(rAPerpendicular, bodyA.angularVelocity);
|
|
638
|
+
const angularVelocityB = PointCal.multiplyVectorByScalar(rBPerpendicular, bodyB.angularVelocity);
|
|
639
|
+
// console.log("inverse mass a", inverseMassA);
|
|
640
|
+
// console.log("inverse mass b", inverseMassB);
|
|
641
|
+
let relativeVelocity = PointCal.subVector(PointCal.addVector(bodyA.linearVelocity, angularVelocityA), PointCal.addVector(bodyB.linearVelocity, angularVelocityB));
|
|
642
|
+
// console.log("relative velocity: ", relativeVelocity);
|
|
643
|
+
// console.log("linear velocity of a", bodyA.getLinearVelocity());
|
|
644
|
+
// console.log("linear veolcity of b", bodyB.getLinearVelocity());
|
|
645
|
+
let relativeVelocityNormal = PointCal.dotProduct(relativeVelocity, contactManifold.normal);
|
|
646
|
+
const rAPerpendicularNormal = PointCal.dotProduct(rAPerpendicular, contactManifold.normal);
|
|
647
|
+
const rBPerpendicularNormal = PointCal.dotProduct(rBPerpendicular, contactManifold.normal);
|
|
648
|
+
const denominator = inverseMassA + inverseMassB + rAPerpendicularNormal * rAPerpendicularNormal * inverseMMOIA + rBPerpendicularNormal * rBPerpendicularNormal * inverseMMOIB;
|
|
649
|
+
let J = -1.4 * relativeVelocityNormal;
|
|
650
|
+
J /= denominator;
|
|
651
|
+
J /= contactManifold.contactPoints.length;
|
|
652
|
+
Js.push(PointCal.multiplyVectorByScalar(contactManifold.normal, J));
|
|
653
|
+
}
|
|
654
|
+
Js.forEach((impulse, index) => {
|
|
655
|
+
let deltaVelocityA = PointCal.multiplyVectorByScalar(impulse, inverseMassA);
|
|
656
|
+
let deltaVelocityB = PointCal.multiplyVectorByScalar(impulse, inverseMassB);
|
|
657
|
+
bodyA.linearVelocity = PointCal.addVector(bodyA.linearVelocity, deltaVelocityA);
|
|
658
|
+
let resA = PointCal.crossProduct(PointCal.subVector(contactManifold.contactPoints[index], bodyA.center), impulse).z;
|
|
659
|
+
resA = resA == undefined ? 0 : resA;
|
|
660
|
+
let resB = PointCal.crossProduct(PointCal.subVector(contactManifold.contactPoints[index], bodyB.center), impulse).z;
|
|
661
|
+
resB = resB == undefined ? 0 : resB;
|
|
662
|
+
bodyA.angularVelocity += resA * inverseMMOIA;
|
|
663
|
+
bodyB.angularVelocity -= resB * inverseMMOIB;
|
|
664
|
+
bodyB.linearVelocity = PointCal.subVector(bodyB.linearVelocity, deltaVelocityB);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function aabbIntersects(aabbA, aabbB) {
|
|
668
|
+
if ((aabbA.min.x <= aabbB.max.x && aabbB.min.x <= aabbA.max.x) && (aabbA.min.y <= aabbB.max.y && aabbB.min.y <= aabbA.max.y)) {
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
function intersects(bodyA, bodyB) {
|
|
674
|
+
let axis = [];
|
|
675
|
+
let bodyAAxes = bodyA.getCollisionAxes(bodyB);
|
|
676
|
+
let bodyBAxes = bodyB.getCollisionAxes(bodyA);
|
|
677
|
+
axis.push(...bodyAAxes);
|
|
678
|
+
axis.push(...bodyBAxes);
|
|
679
|
+
let collision = true;
|
|
680
|
+
let minDepth = Number.MAX_VALUE;
|
|
681
|
+
let minAxis = axis[0];
|
|
682
|
+
axis.forEach(projAxis => {
|
|
683
|
+
let bodyAInterval = bodyA.getMinMaxProjection(projAxis);
|
|
684
|
+
let bodyBInterval = bodyB.getMinMaxProjection(projAxis);
|
|
685
|
+
if (bodyAInterval.min >= bodyBInterval.max || bodyBInterval.min >= bodyAInterval.max) {
|
|
686
|
+
collision = false;
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
let depth = Math.abs(Math.min(bodyAInterval.max, bodyBInterval.max) - Math.max(bodyBInterval.min, bodyAInterval.min));
|
|
690
|
+
if (depth < minDepth) {
|
|
691
|
+
minDepth = depth;
|
|
692
|
+
minAxis = projAxis;
|
|
693
|
+
if (bodyAInterval.max < bodyBInterval.max) {
|
|
694
|
+
minAxis = PointCal.multiplyVectorByScalar(minAxis, -1);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
if (collision) {
|
|
700
|
+
return { collision: collision, depth: minDepth, normal: minAxis };
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
return { collision: false, depth: undefined, normal: undefined };
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function narrowPhaseWithRigidBody(bodies, combinationsToCheck, resolveCollisionFlag) {
|
|
707
|
+
if (!resolveCollisionFlag) {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
const contactPoints = [];
|
|
711
|
+
combinationsToCheck.forEach(combination => {
|
|
712
|
+
let bodyA = combination.bodyA;
|
|
713
|
+
let bodyB = combination.bodyB;
|
|
714
|
+
if (bodyA == bodyB) {
|
|
715
|
+
// console.log("same body");
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
let bodyAZ = bodyA.center.z == undefined ? 0 : bodyA.center.z;
|
|
719
|
+
let bodyBZ = bodyB.center.z == undefined ? 0 : bodyB.center.z;
|
|
720
|
+
if (Math.abs(bodyAZ - bodyBZ) > 0.5) {
|
|
721
|
+
// console.log("z-index difference is too large");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
let { collision, depth, normal: normalAxis } = intersects(bodyA, bodyB);
|
|
725
|
+
if (collision && normalAxis !== undefined && depth !== undefined) {
|
|
726
|
+
// the normal axis points in the direction that push bodyA away from bodyB
|
|
727
|
+
let moveDisplacement = PointCal.multiplyVectorByScalar(normalAxis, depth / 2);
|
|
728
|
+
let revMoveDisplacement = PointCal.multiplyVectorByScalar(normalAxis, -depth / 2);
|
|
729
|
+
if (!bodyA.isStatic()) {
|
|
730
|
+
bodyA.move(moveDisplacement);
|
|
731
|
+
}
|
|
732
|
+
if (!bodyB.isStatic()) {
|
|
733
|
+
bodyB.move(revMoveDisplacement);
|
|
734
|
+
}
|
|
735
|
+
if (bodyA.isStatic()) {
|
|
736
|
+
// bodyA.move(revMoveDisplacement);
|
|
737
|
+
bodyB.move(revMoveDisplacement);
|
|
738
|
+
}
|
|
739
|
+
if (bodyB.isStatic()) {
|
|
740
|
+
bodyA.move(moveDisplacement);
|
|
741
|
+
// bodyB.move(moveDisplacement);
|
|
742
|
+
}
|
|
743
|
+
// finding the collision contact point(s)
|
|
744
|
+
const bodyASigNormal = bodyA.getNormalOfSignificantFace(PointCal.multiplyVectorByScalar(normalAxis, -1));
|
|
745
|
+
const bodyBSigNormal = bodyB.getNormalOfSignificantFace(normalAxis);
|
|
746
|
+
const bodyASigVertices = bodyA.getSignificantVertices(PointCal.multiplyVectorByScalar(normalAxis, -1));
|
|
747
|
+
const bodyBSigVertices = bodyB.getSignificantVertices(normalAxis);
|
|
748
|
+
const bodyAParallelIndicator = Math.abs(PointCal.dotProduct(bodyASigNormal, PointCal.multiplyVectorByScalar(normalAxis, -1)));
|
|
749
|
+
const bodyBParallelIndicator = Math.abs(PointCal.dotProduct(bodyBSigNormal, normalAxis));
|
|
750
|
+
if (bodyBSigVertices.length == 1 || bodyASigVertices.length == 1) {
|
|
751
|
+
// one of the body is a circle
|
|
752
|
+
// console.log("involving a circle");
|
|
753
|
+
if (bodyBSigVertices.length == 1) {
|
|
754
|
+
// bodyB is a circle
|
|
755
|
+
// contact point is on the perimeter of the circle and the direction is the collision normal
|
|
756
|
+
contactPoints.push(bodyBSigVertices[0]);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
// bodyA is a circle
|
|
760
|
+
// contact point is on the perimeter of the circle and the direction is the collision normal
|
|
761
|
+
contactPoints.push(bodyASigVertices[0]);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else if (bodyAParallelIndicator > bodyBParallelIndicator) {
|
|
765
|
+
// bodyA has the normal that is the most parallel to the collision normal
|
|
766
|
+
const adjacentFaces = bodyA.getAdjacentFaces(PointCal.multiplyVectorByScalar(normalAxis, -1));
|
|
767
|
+
let faceToClip = [...bodyBSigVertices];
|
|
768
|
+
for (let index = 0; index < adjacentFaces.length - 1; index++) {
|
|
769
|
+
let startPoint = adjacentFaces[index].startPoint.coord;
|
|
770
|
+
let endPoint = adjacentFaces[index].endPoint.coord;
|
|
771
|
+
let direction = PointCal.subVector(endPoint, startPoint);
|
|
772
|
+
let sigStart = PointCal.subVector(faceToClip[0], startPoint);
|
|
773
|
+
let sigEnd = PointCal.subVector(faceToClip[1], startPoint);
|
|
774
|
+
let startInside = PointCal.angleFromA2B(direction, sigStart) >= 0;
|
|
775
|
+
let endInside = PointCal.angleFromA2B(direction, sigEnd) >= 0;
|
|
776
|
+
if ((startInside ? 1 : 0) ^ (endInside ? 1 : 0)) {
|
|
777
|
+
// one of the point is outside the face
|
|
778
|
+
let intersectionPoint = PointCal.getLineIntersection(startPoint, endPoint, faceToClip[0], faceToClip[1]);
|
|
779
|
+
if (intersectionPoint.intersects && intersectionPoint.intersection !== undefined) {
|
|
780
|
+
if (startInside) {
|
|
781
|
+
faceToClip[1] = intersectionPoint.intersection;
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
faceToClip[0] = intersectionPoint.intersection;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const referenceFace = adjacentFaces[adjacentFaces.length - 1];
|
|
790
|
+
let startPoint = referenceFace.startPoint.coord;
|
|
791
|
+
let endPoint = referenceFace.endPoint.coord;
|
|
792
|
+
let direction = PointCal.subVector(endPoint, startPoint);
|
|
793
|
+
let sigStart = PointCal.subVector(faceToClip[0], startPoint);
|
|
794
|
+
let sigEnd = PointCal.subVector(faceToClip[1], startPoint);
|
|
795
|
+
let startInside = PointCal.angleFromA2B(direction, sigStart) >= 0;
|
|
796
|
+
let endInside = PointCal.angleFromA2B(direction, sigEnd) >= 0;
|
|
797
|
+
if (startInside) {
|
|
798
|
+
contactPoints.push(faceToClip[0]);
|
|
799
|
+
}
|
|
800
|
+
if (endInside) {
|
|
801
|
+
contactPoints.push(faceToClip[1]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
// bodyB has the normal that is the most parallel to the collision normal
|
|
806
|
+
const adjacentFaces = bodyB.getAdjacentFaces(normalAxis);
|
|
807
|
+
let faceToClip = [...bodyASigVertices];
|
|
808
|
+
if (faceToClip.length == 0) {
|
|
809
|
+
console.log("warning");
|
|
810
|
+
}
|
|
811
|
+
for (let index = 0; index < adjacentFaces.length - 1; index++) {
|
|
812
|
+
let startPoint = adjacentFaces[index].startPoint.coord;
|
|
813
|
+
let endPoint = adjacentFaces[index].endPoint.coord;
|
|
814
|
+
let direction = PointCal.subVector(endPoint, startPoint);
|
|
815
|
+
let sigStart = PointCal.subVector(faceToClip[0], startPoint);
|
|
816
|
+
let sigEnd = PointCal.subVector(faceToClip[1], startPoint);
|
|
817
|
+
let startInside = PointCal.angleFromA2B(direction, sigStart) >= 0;
|
|
818
|
+
let endInside = PointCal.angleFromA2B(direction, sigEnd) >= 0;
|
|
819
|
+
if ((startInside ? 1 : 0) ^ (endInside ? 1 : 0)) {
|
|
820
|
+
// one of the point is outside the face
|
|
821
|
+
let intersectionPoint = PointCal.getLineIntersection(startPoint, endPoint, faceToClip[0], faceToClip[1]);
|
|
822
|
+
if (intersectionPoint.intersects && intersectionPoint.intersection !== undefined) {
|
|
823
|
+
if (startInside) {
|
|
824
|
+
faceToClip[1] = intersectionPoint.intersection;
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
faceToClip[0] = intersectionPoint.intersection;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const referenceFace = adjacentFaces[adjacentFaces.length - 1];
|
|
833
|
+
let startPoint = referenceFace.startPoint.coord;
|
|
834
|
+
let endPoint = referenceFace.endPoint.coord;
|
|
835
|
+
let direction = PointCal.subVector(endPoint, startPoint);
|
|
836
|
+
let sigStart = PointCal.subVector(faceToClip[0], startPoint);
|
|
837
|
+
let sigEnd = PointCal.subVector(faceToClip[1], startPoint);
|
|
838
|
+
let startInside = PointCal.angleFromA2B(direction, sigStart) >= 0;
|
|
839
|
+
let endInside = PointCal.angleFromA2B(direction, sigEnd) >= 0;
|
|
840
|
+
if (startInside) {
|
|
841
|
+
contactPoints.push(faceToClip[0]);
|
|
842
|
+
}
|
|
843
|
+
if (endInside) {
|
|
844
|
+
contactPoints.push(faceToClip[1]);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (resolveCollisionFlag) {
|
|
848
|
+
resolveCollisionWithRotation(bodyA, bodyB, { normal: normalAxis, contactPoints: contactPoints });
|
|
849
|
+
// resolveCollision(bodyA, bodyB, normalAxis);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
return contactPoints;
|
|
854
|
+
}
|
|
855
|
+
function narrowPhase(bodies, combinationsToCheck, resolveCollisionFlag) {
|
|
856
|
+
if (!resolveCollisionFlag) {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
combinationsToCheck.forEach(combination => {
|
|
860
|
+
let bodyA = bodies[combination.bodyAIndex];
|
|
861
|
+
let bodyB = bodies[combination.bodyBIndex];
|
|
862
|
+
let { collision, depth, normal: normalAxis } = intersects(bodyA, bodyB);
|
|
863
|
+
if (collision && normalAxis !== undefined && depth !== undefined) {
|
|
864
|
+
// console.log("collision");
|
|
865
|
+
let moveDisplacement = PointCal.multiplyVectorByScalar(normalAxis, depth / 2);
|
|
866
|
+
let revMoveDisplacement = PointCal.multiplyVectorByScalar(normalAxis, -depth / 2);
|
|
867
|
+
if (!bodyA.isStatic()) {
|
|
868
|
+
bodyA.move(moveDisplacement);
|
|
869
|
+
}
|
|
870
|
+
if (!bodyB.isStatic()) {
|
|
871
|
+
bodyB.move(revMoveDisplacement);
|
|
872
|
+
}
|
|
873
|
+
if (bodyA.isStatic()) {
|
|
874
|
+
// bodyA.move(revMoveDisplacement);
|
|
875
|
+
bodyB.move(revMoveDisplacement);
|
|
876
|
+
}
|
|
877
|
+
if (bodyB.isStatic()) {
|
|
878
|
+
bodyA.move(moveDisplacement);
|
|
879
|
+
// bodyB.move(moveDisplacement);
|
|
880
|
+
}
|
|
881
|
+
if (resolveCollisionFlag) {
|
|
882
|
+
resolveCollision(bodyA, bodyB, normalAxis);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function broadPhaseWithRigidBodyReturned(quadTree, bodies) {
|
|
888
|
+
let possibleCombi = [];
|
|
889
|
+
for (let index = 0; index <= bodies.length - 1; index++) {
|
|
890
|
+
let objsToCheck = quadTree.retrieve(bodies[index]);
|
|
891
|
+
for (let jindex = 0; jindex <= objsToCheck.length - 1; jindex++) {
|
|
892
|
+
let bodyA = bodies[index];
|
|
893
|
+
let bodyB = objsToCheck[jindex];
|
|
894
|
+
if (bodyA.isStatic() && bodyB.isStatic()) {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (!aabbIntersects(bodyA.AABB, bodyB.AABB)) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
possibleCombi.push({ bodyA: bodyA, bodyB: bodyB });
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return possibleCombi;
|
|
904
|
+
}
|
|
905
|
+
function broadPhase(quadTree, bodies) {
|
|
906
|
+
let possibleCombi = [];
|
|
907
|
+
for (let index = 0; index <= bodies.length - 1; index++) {
|
|
908
|
+
let objsToCheck = quadTree.retrieve(bodies[index]);
|
|
909
|
+
for (let jindex = 0; jindex <= objsToCheck.length - 1; jindex++) {
|
|
910
|
+
let bodyA = bodies[index];
|
|
911
|
+
let bodyB = objsToCheck[jindex];
|
|
912
|
+
if (bodyA.isStatic() && bodyB.isStatic()) {
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (!aabbIntersects(bodyA.AABB, bodyB.AABB)) {
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
possibleCombi.push({ bodyAIndex: index, bodyBIndex: jindex });
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return possibleCombi;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
export { BaseRigidBody, Circle, Polygon, QuadTree, RectangleBound, VisaulCircleBody, VisualPolygonBody, aabbIntersects, broadPhase, broadPhaseWithRigidBodyReturned, intersects, narrowPhase, narrowPhaseWithRigidBody, resolveCollision, resolveCollisionWithRotation };
|
|
925
|
+
//# sourceMappingURL=index.js.map
|