planck-v2 2.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.
Potentially problematic release.
This version of planck-v2 might be problematic. Click here for more details.
- package/LICENSE.txt +20 -0
- package/README.md +21 -0
- package/dist/planck-with-testbed.d.ts +4433 -0
- package/dist/planck-with-testbed.js +20730 -0
- package/dist/planck-with-testbed.js.map +1 -0
- package/dist/planck-with-testbed.umd.cjs +20730 -0
- package/dist/planck-with-testbed.umd.cjs.map +1 -0
- package/dist/planck.d.ts +4343 -0
- package/dist/planck.js +13516 -0
- package/dist/planck.js.map +1 -0
- package/dist/planck.umd.cjs +13516 -0
- package/dist/planck.umd.cjs.map +1 -0
- package/package.json +105 -0
- package/src/Settings.ts +238 -0
- package/src/__test__/Basic.test.ts +43 -0
- package/src/__test__/CCD.test.ts +70 -0
- package/src/__test__/Collision.test.ts +133 -0
- package/src/__test__/Math.test.ts +105 -0
- package/src/__test__/Pool.test.ts +48 -0
- package/src/__test__/World.test.ts +73 -0
- package/src/collision/AABB.ts +287 -0
- package/src/collision/BroadPhase.ts +210 -0
- package/src/collision/Distance.ts +962 -0
- package/src/collision/DynamicTree.ts +907 -0
- package/src/collision/Manifold.ts +420 -0
- package/src/collision/Raycast.ts +30 -0
- package/src/collision/Shape.ts +114 -0
- package/src/collision/TimeOfImpact.ts +502 -0
- package/src/collision/shape/BoxShape.ts +34 -0
- package/src/collision/shape/ChainShape.ts +360 -0
- package/src/collision/shape/CircleShape.ts +202 -0
- package/src/collision/shape/CollideCircle.ts +66 -0
- package/src/collision/shape/CollideCirclePolygon.ts +142 -0
- package/src/collision/shape/CollideEdgeCircle.ts +185 -0
- package/src/collision/shape/CollideEdgePolygon.ts +528 -0
- package/src/collision/shape/CollidePolygon.ts +280 -0
- package/src/collision/shape/EdgeShape.ts +316 -0
- package/src/collision/shape/PolygonShape.ts +581 -0
- package/src/common/Geo.ts +589 -0
- package/src/common/Jacobian.ts +17 -0
- package/src/common/Mat22.ts +221 -0
- package/src/common/Mat33.ts +224 -0
- package/src/common/Math.ts +96 -0
- package/src/common/Rot.ts +218 -0
- package/src/common/Sweep.ts +119 -0
- package/src/common/Transform.ts +203 -0
- package/src/common/Vec2.ts +624 -0
- package/src/common/Vec3.ts +188 -0
- package/src/dynamics/Body.ts +1198 -0
- package/src/dynamics/Contact.ts +1366 -0
- package/src/dynamics/Fixture.ts +506 -0
- package/src/dynamics/Joint.ts +226 -0
- package/src/dynamics/Position.ts +44 -0
- package/src/dynamics/Solver.ts +890 -0
- package/src/dynamics/Velocity.ts +18 -0
- package/src/dynamics/World.ts +1169 -0
- package/src/dynamics/joint/DistanceJoint.ts +463 -0
- package/src/dynamics/joint/FrictionJoint.ts +396 -0
- package/src/dynamics/joint/GearJoint.ts +591 -0
- package/src/dynamics/joint/MotorJoint.ts +430 -0
- package/src/dynamics/joint/MouseJoint.ts +390 -0
- package/src/dynamics/joint/PrismaticJoint.ts +903 -0
- package/src/dynamics/joint/PulleyJoint.ts +529 -0
- package/src/dynamics/joint/RevoluteJoint.ts +745 -0
- package/src/dynamics/joint/RopeJoint.ts +383 -0
- package/src/dynamics/joint/WeldJoint.ts +544 -0
- package/src/dynamics/joint/WheelJoint.ts +683 -0
- package/src/dynamics/joint/__test__/DistanceJoint.test.ts +66 -0
- package/src/index.ts +60 -0
- package/src/internal.ts +20 -0
- package/src/main.ts +3 -0
- package/src/serializer/__test__/Serialize.test.ts +52 -0
- package/src/serializer/__test__/Validator.test.ts +55 -0
- package/src/serializer/index.ts +257 -0
- package/src/serializer/schema.json +168 -0
- package/src/util/Pool.ts +120 -0
- package/src/util/Testbed.ts +157 -0
- package/src/util/Timer.ts +15 -0
- package/src/util/options.ts +28 -0
- package/src/util/stats.ts +26 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Planck.js
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Erin Catto, Ali Shakiba
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as geo from "../common/Geo";
|
|
11
|
+
import { SettingsInternal as Settings } from "../Settings";
|
|
12
|
+
import { stats } from "../util/stats";
|
|
13
|
+
import Timer from "../util/Timer";
|
|
14
|
+
import { Sweep } from "../common/Sweep";
|
|
15
|
+
import { Distance, DistanceInput, DistanceOutput, DistanceProxy, SimplexCache } from "./Distance";
|
|
16
|
+
|
|
17
|
+
/** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT;
|
|
18
|
+
/** @internal */ const math_abs = Math.abs;
|
|
19
|
+
/** @internal */ const math_max = Math.max;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Input parameters for TimeOfImpact.
|
|
23
|
+
*/
|
|
24
|
+
export class TOIInput {
|
|
25
|
+
proxyA = new DistanceProxy();
|
|
26
|
+
proxyB = new DistanceProxy();
|
|
27
|
+
sweepA = new Sweep();
|
|
28
|
+
sweepB = new Sweep();
|
|
29
|
+
/** defines sweep interval [0, tMax] */
|
|
30
|
+
tMax: number;
|
|
31
|
+
recycle() {
|
|
32
|
+
this.proxyA.recycle();
|
|
33
|
+
this.proxyB.recycle();
|
|
34
|
+
this.sweepA.recycle();
|
|
35
|
+
this.sweepB.recycle();
|
|
36
|
+
this.tMax = -1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export enum TOIOutputState {
|
|
41
|
+
e_unset = -1,
|
|
42
|
+
e_unknown = 0,
|
|
43
|
+
e_failed = 1,
|
|
44
|
+
e_overlapped = 2,
|
|
45
|
+
e_touching = 3,
|
|
46
|
+
e_separated = 4,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Output parameters for TimeOfImpact.
|
|
51
|
+
*/
|
|
52
|
+
export class TOIOutput {
|
|
53
|
+
state = TOIOutputState.e_unset;
|
|
54
|
+
t = -1;
|
|
55
|
+
recycle() {
|
|
56
|
+
this.state = TOIOutputState.e_unset;
|
|
57
|
+
this.t = -1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
stats.toiTime = 0;
|
|
62
|
+
stats.toiMaxTime = 0;
|
|
63
|
+
stats.toiCalls = 0;
|
|
64
|
+
stats.toiIters = 0;
|
|
65
|
+
stats.toiMaxIters = 0;
|
|
66
|
+
stats.toiRootIters = 0;
|
|
67
|
+
stats.toiMaxRootIters = 0;
|
|
68
|
+
|
|
69
|
+
/** @internal */ const distanceInput = new DistanceInput();
|
|
70
|
+
/** @internal */ const distanceOutput = new DistanceOutput();
|
|
71
|
+
// this is passed to Distance and SeparationFunction
|
|
72
|
+
/** @internal */ const cache = new SimplexCache();
|
|
73
|
+
|
|
74
|
+
/** @internal */ const xfA = geo.transform(0, 0, 0);
|
|
75
|
+
/** @internal */ const xfB = geo.transform(0, 0, 0);
|
|
76
|
+
/** @internal */ const pointA = geo.vec2(0, 0);
|
|
77
|
+
/** @internal */ const pointB = geo.vec2(0, 0);
|
|
78
|
+
/** @internal */ const normal = geo.vec2(0, 0);
|
|
79
|
+
/** @internal */ const axisA = geo.vec2(0, 0);
|
|
80
|
+
/** @internal */ const axisB = geo.vec2(0, 0);
|
|
81
|
+
/** @internal */ const localPointA = geo.vec2(0, 0);
|
|
82
|
+
/** @internal */ const localPointB = geo.vec2(0, 0);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Compute the upper bound on time before two shapes penetrate. Time is
|
|
86
|
+
* represented as a fraction between [0,tMax]. This uses a swept separating axis
|
|
87
|
+
* and may miss some intermediate, non-tunneling collisions. If you change the
|
|
88
|
+
* time interval, you should call this function again.
|
|
89
|
+
*
|
|
90
|
+
* Note: use Distance to compute the contact point and normal at the time of
|
|
91
|
+
* impact.
|
|
92
|
+
*
|
|
93
|
+
* CCD via the local separating axis method. This seeks progression by computing
|
|
94
|
+
* the largest time at which separation is maintained.
|
|
95
|
+
*/
|
|
96
|
+
export const TimeOfImpact = function (output: TOIOutput, input: TOIInput): void {
|
|
97
|
+
const timer = Timer.now();
|
|
98
|
+
|
|
99
|
+
++stats.toiCalls;
|
|
100
|
+
|
|
101
|
+
output.state = TOIOutputState.e_unknown;
|
|
102
|
+
output.t = input.tMax;
|
|
103
|
+
|
|
104
|
+
const proxyA = input.proxyA; // DistanceProxy
|
|
105
|
+
const proxyB = input.proxyB; // DistanceProxy
|
|
106
|
+
|
|
107
|
+
const sweepA = input.sweepA; // Sweep
|
|
108
|
+
const sweepB = input.sweepB; // Sweep
|
|
109
|
+
|
|
110
|
+
// Large rotations can make the root finder fail, so we normalize the
|
|
111
|
+
// sweep angles.
|
|
112
|
+
sweepA.normalize();
|
|
113
|
+
sweepB.normalize();
|
|
114
|
+
|
|
115
|
+
const tMax = input.tMax;
|
|
116
|
+
|
|
117
|
+
const totalRadius = proxyA.m_radius + proxyB.m_radius;
|
|
118
|
+
const target = math_max(Settings.linearSlop, totalRadius - 3.0 * Settings.linearSlop);
|
|
119
|
+
const tolerance = 0.25 * Settings.linearSlop;
|
|
120
|
+
if (_ASSERT) console.assert(target > tolerance);
|
|
121
|
+
|
|
122
|
+
let t1 = 0.0;
|
|
123
|
+
const k_maxIterations = Settings.maxTOIIterations;
|
|
124
|
+
let iter = 0;
|
|
125
|
+
|
|
126
|
+
// Prepare input for distance query.
|
|
127
|
+
// const cache = new SimplexCache();
|
|
128
|
+
cache.recycle();
|
|
129
|
+
|
|
130
|
+
distanceInput.proxyA.setVertices(proxyA.m_vertices, proxyA.m_count, proxyA.m_radius);
|
|
131
|
+
distanceInput.proxyB.setVertices(proxyB.m_vertices, proxyB.m_count, proxyB.m_radius);
|
|
132
|
+
distanceInput.useRadii = false;
|
|
133
|
+
|
|
134
|
+
// The outer loop progressively attempts to compute new separating axes.
|
|
135
|
+
// This loop terminates when an axis is repeated (no progress is made).
|
|
136
|
+
while (true) {
|
|
137
|
+
sweepA.getTransform(xfA, t1);
|
|
138
|
+
sweepB.getTransform(xfB, t1);
|
|
139
|
+
|
|
140
|
+
// Get the distance between shapes. We can also use the results
|
|
141
|
+
// to get a separating axis.
|
|
142
|
+
geo.copyTransform(distanceInput.transformA, xfA);
|
|
143
|
+
geo.copyTransform(distanceInput.transformB, xfB);
|
|
144
|
+
Distance(distanceOutput, cache, distanceInput);
|
|
145
|
+
|
|
146
|
+
// If the shapes are overlapped, we give up on continuous collision.
|
|
147
|
+
if (distanceOutput.distance <= 0.0) {
|
|
148
|
+
// Failure!
|
|
149
|
+
output.state = TOIOutputState.e_overlapped;
|
|
150
|
+
output.t = 0.0;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (distanceOutput.distance < target + tolerance) {
|
|
155
|
+
// Victory!
|
|
156
|
+
output.state = TOIOutputState.e_touching;
|
|
157
|
+
output.t = t1;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Initialize the separating axis.
|
|
162
|
+
separationFunction.initialize(cache, proxyA, sweepA, proxyB, sweepB, t1);
|
|
163
|
+
|
|
164
|
+
// if (false) {
|
|
165
|
+
// // Dump the curve seen by the root finder
|
|
166
|
+
// const N = 100;
|
|
167
|
+
// const dx = 1.0 / N;
|
|
168
|
+
// const xs = []; // [ N + 1 ];
|
|
169
|
+
// const fs = []; // [ N + 1 ];
|
|
170
|
+
// const x = 0.0;
|
|
171
|
+
// for (const i = 0; i <= N; ++i) {
|
|
172
|
+
// sweepA.getTransform(xfA, x);
|
|
173
|
+
// sweepB.getTransform(xfB, x);
|
|
174
|
+
// const f = fcn.evaluate(xfA, xfB) - target;
|
|
175
|
+
// printf("%g %g\n", x, f);
|
|
176
|
+
// xs[i] = x;
|
|
177
|
+
// fs[i] = f;
|
|
178
|
+
// x += dx;
|
|
179
|
+
// }
|
|
180
|
+
// }
|
|
181
|
+
|
|
182
|
+
// Compute the TOI on the separating axis. We do this by successively
|
|
183
|
+
// resolving the deepest point. This loop is bounded by the number of
|
|
184
|
+
// vertices.
|
|
185
|
+
let done = false;
|
|
186
|
+
let t2 = tMax;
|
|
187
|
+
let pushBackIter = 0;
|
|
188
|
+
while (true) {
|
|
189
|
+
// Find the deepest point at t2. Store the witness point indices.
|
|
190
|
+
let s2 = separationFunction.findMinSeparation(t2);
|
|
191
|
+
|
|
192
|
+
// Is the final configuration separated?
|
|
193
|
+
if (s2 > target + tolerance) {
|
|
194
|
+
// Victory!
|
|
195
|
+
output.state = TOIOutputState.e_separated;
|
|
196
|
+
output.t = tMax;
|
|
197
|
+
done = true;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Has the separation reached tolerance?
|
|
202
|
+
if (s2 > target - tolerance) {
|
|
203
|
+
// Advance the sweeps
|
|
204
|
+
t1 = t2;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Compute the initial separation of the witness points.
|
|
209
|
+
let s1 = separationFunction.evaluate(t1);
|
|
210
|
+
|
|
211
|
+
// Check for initial overlap. This might happen if the root finder
|
|
212
|
+
// runs out of iterations.
|
|
213
|
+
if (s1 < target - tolerance) {
|
|
214
|
+
output.state = TOIOutputState.e_failed;
|
|
215
|
+
output.t = t1;
|
|
216
|
+
done = true;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for touching
|
|
221
|
+
if (s1 <= target + tolerance) {
|
|
222
|
+
// Victory! t1 should hold the TOI (could be 0.0).
|
|
223
|
+
output.state = TOIOutputState.e_touching;
|
|
224
|
+
output.t = t1;
|
|
225
|
+
done = true;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Compute 1D root of: f(x) - target = 0
|
|
230
|
+
let rootIterCount = 0;
|
|
231
|
+
let a1 = t1;
|
|
232
|
+
let a2 = t2;
|
|
233
|
+
while (true) {
|
|
234
|
+
// Use a mix of the secant rule and bisection.
|
|
235
|
+
let t;
|
|
236
|
+
if (rootIterCount & 1) {
|
|
237
|
+
// Secant rule to improve convergence.
|
|
238
|
+
t = a1 + ((target - s1) * (a2 - a1)) / (s2 - s1);
|
|
239
|
+
} else {
|
|
240
|
+
// Bisection to guarantee progress.
|
|
241
|
+
t = 0.5 * (a1 + a2);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
++rootIterCount;
|
|
245
|
+
++stats.toiRootIters;
|
|
246
|
+
|
|
247
|
+
const s = separationFunction.evaluate(t);
|
|
248
|
+
|
|
249
|
+
if (math_abs(s - target) < tolerance) {
|
|
250
|
+
// t2 holds a tentative value for t1
|
|
251
|
+
t2 = t;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Ensure we continue to bracket the root.
|
|
256
|
+
if (s > target) {
|
|
257
|
+
a1 = t;
|
|
258
|
+
s1 = s;
|
|
259
|
+
} else {
|
|
260
|
+
a2 = t;
|
|
261
|
+
s2 = s;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (rootIterCount === 50) {
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
stats.toiMaxRootIters = math_max(stats.toiMaxRootIters, rootIterCount);
|
|
270
|
+
|
|
271
|
+
++pushBackIter;
|
|
272
|
+
|
|
273
|
+
if (pushBackIter === Settings.maxPolygonVertices) {
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
++iter;
|
|
279
|
+
++stats.toiIters;
|
|
280
|
+
|
|
281
|
+
if (done) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (iter === k_maxIterations) {
|
|
286
|
+
// Root finder got stuck. Semi-victory.
|
|
287
|
+
output.state = TOIOutputState.e_failed;
|
|
288
|
+
output.t = t1;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
stats.toiMaxIters = math_max(stats.toiMaxIters, iter);
|
|
294
|
+
|
|
295
|
+
const time = Timer.diff(timer);
|
|
296
|
+
stats.toiMaxTime = math_max(stats.toiMaxTime, time);
|
|
297
|
+
stats.toiTime += time;
|
|
298
|
+
|
|
299
|
+
separationFunction.recycle();
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
enum SeparationFunctionType {
|
|
303
|
+
e_unset = -1,
|
|
304
|
+
e_points = 1,
|
|
305
|
+
e_faceA = 2,
|
|
306
|
+
e_faceB = 3,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
class SeparationFunction {
|
|
310
|
+
// input cache
|
|
311
|
+
// todo: maybe assign by copy instead of reference?
|
|
312
|
+
m_proxyA: DistanceProxy = null;
|
|
313
|
+
m_proxyB: DistanceProxy = null;
|
|
314
|
+
m_sweepA: Sweep = null;
|
|
315
|
+
m_sweepB: Sweep = null;
|
|
316
|
+
|
|
317
|
+
// initialize cache
|
|
318
|
+
m_type = SeparationFunctionType.e_unset;
|
|
319
|
+
m_localPoint = geo.vec2(0, 0);
|
|
320
|
+
m_axis = geo.vec2(0, 0);
|
|
321
|
+
|
|
322
|
+
// compute output
|
|
323
|
+
indexA = -1;
|
|
324
|
+
indexB = -1;
|
|
325
|
+
|
|
326
|
+
recycle() {
|
|
327
|
+
this.m_proxyA = null;
|
|
328
|
+
this.m_proxyB = null;
|
|
329
|
+
this.m_sweepA = null;
|
|
330
|
+
this.m_sweepB = null;
|
|
331
|
+
|
|
332
|
+
this.m_type = SeparationFunctionType.e_unset;
|
|
333
|
+
geo.zeroVec2(this.m_localPoint);
|
|
334
|
+
geo.zeroVec2(this.m_axis);
|
|
335
|
+
|
|
336
|
+
this.indexA = -1;
|
|
337
|
+
this.indexB = -1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// TODO_ERIN might not need to return the separation
|
|
341
|
+
|
|
342
|
+
initialize(
|
|
343
|
+
cache: SimplexCache,
|
|
344
|
+
proxyA: DistanceProxy,
|
|
345
|
+
sweepA: Sweep,
|
|
346
|
+
proxyB: DistanceProxy,
|
|
347
|
+
sweepB: Sweep,
|
|
348
|
+
t1: number,
|
|
349
|
+
): number {
|
|
350
|
+
const count = cache.count;
|
|
351
|
+
if (_ASSERT) console.assert(0 < count && count < 3);
|
|
352
|
+
|
|
353
|
+
this.m_proxyA = proxyA;
|
|
354
|
+
this.m_proxyB = proxyB;
|
|
355
|
+
this.m_sweepA = sweepA;
|
|
356
|
+
this.m_sweepB = sweepB;
|
|
357
|
+
|
|
358
|
+
this.m_sweepA.getTransform(xfA, t1);
|
|
359
|
+
this.m_sweepB.getTransform(xfB, t1);
|
|
360
|
+
|
|
361
|
+
if (count === 1) {
|
|
362
|
+
this.m_type = SeparationFunctionType.e_points;
|
|
363
|
+
const localPointA = this.m_proxyA.getVertex(cache.indexA[0]);
|
|
364
|
+
const localPointB = this.m_proxyB.getVertex(cache.indexB[0]);
|
|
365
|
+
geo.transformVec2(pointA, xfA, localPointA);
|
|
366
|
+
geo.transformVec2(pointB, xfB, localPointB);
|
|
367
|
+
geo.subVec2(this.m_axis, pointB, pointA);
|
|
368
|
+
const s = geo.normalizeVec2Length(this.m_axis);
|
|
369
|
+
return s;
|
|
370
|
+
} else if (cache.indexA[0] === cache.indexA[1]) {
|
|
371
|
+
// Two points on B and one on A.
|
|
372
|
+
this.m_type = SeparationFunctionType.e_faceB;
|
|
373
|
+
const localPointB1 = proxyB.getVertex(cache.indexB[0]);
|
|
374
|
+
const localPointB2 = proxyB.getVertex(cache.indexB[1]);
|
|
375
|
+
|
|
376
|
+
geo.crossSubVec2Num(this.m_axis, localPointB2, localPointB1, 1.0);
|
|
377
|
+
geo.normalizeVec2(this.m_axis);
|
|
378
|
+
geo.rotVec2(normal, xfB.q, this.m_axis);
|
|
379
|
+
|
|
380
|
+
geo.combine2Vec2(this.m_localPoint, 0.5, localPointB1, 0.5, localPointB2);
|
|
381
|
+
geo.transformVec2(pointB, xfB, this.m_localPoint);
|
|
382
|
+
|
|
383
|
+
const localPointA = proxyA.getVertex(cache.indexA[0]);
|
|
384
|
+
|
|
385
|
+
geo.transformVec2(pointA, xfA, localPointA);
|
|
386
|
+
|
|
387
|
+
let s = geo.dotVec2(pointA, normal) - geo.dotVec2(pointB, normal);
|
|
388
|
+
if (s < 0.0) {
|
|
389
|
+
geo.negVec2(this.m_axis);
|
|
390
|
+
s = -s;
|
|
391
|
+
}
|
|
392
|
+
return s;
|
|
393
|
+
} else {
|
|
394
|
+
// Two points on A and one or two points on B.
|
|
395
|
+
this.m_type = SeparationFunctionType.e_faceA;
|
|
396
|
+
const localPointA1 = this.m_proxyA.getVertex(cache.indexA[0]);
|
|
397
|
+
const localPointA2 = this.m_proxyA.getVertex(cache.indexA[1]);
|
|
398
|
+
|
|
399
|
+
geo.crossSubVec2Num(this.m_axis, localPointA2, localPointA1, 1.0);
|
|
400
|
+
geo.normalizeVec2(this.m_axis);
|
|
401
|
+
geo.rotVec2(normal, xfA.q, this.m_axis);
|
|
402
|
+
|
|
403
|
+
geo.combine2Vec2(this.m_localPoint, 0.5, localPointA1, 0.5, localPointA2);
|
|
404
|
+
geo.transformVec2(pointA, xfA, this.m_localPoint);
|
|
405
|
+
|
|
406
|
+
const localPointB = this.m_proxyB.getVertex(cache.indexB[0]);
|
|
407
|
+
geo.transformVec2(pointB, xfB, localPointB);
|
|
408
|
+
|
|
409
|
+
let s = geo.dotVec2(pointB, normal) - geo.dotVec2(pointA, normal);
|
|
410
|
+
if (s < 0.0) {
|
|
411
|
+
geo.negVec2(this.m_axis);
|
|
412
|
+
s = -s;
|
|
413
|
+
}
|
|
414
|
+
return s;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
compute(find: boolean, t: number): number {
|
|
419
|
+
// It was findMinSeparation and evaluate
|
|
420
|
+
this.m_sweepA.getTransform(xfA, t);
|
|
421
|
+
this.m_sweepB.getTransform(xfB, t);
|
|
422
|
+
|
|
423
|
+
switch (this.m_type) {
|
|
424
|
+
case SeparationFunctionType.e_points: {
|
|
425
|
+
if (find) {
|
|
426
|
+
geo.derotVec2(axisA, xfA.q, this.m_axis);
|
|
427
|
+
geo.derotNegVec2(axisB, xfB.q, this.m_axis);
|
|
428
|
+
|
|
429
|
+
this.indexA = this.m_proxyA.getSupport(axisA);
|
|
430
|
+
this.indexB = this.m_proxyB.getSupport(axisB);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
geo.copyVec2(localPointA, this.m_proxyA.getVertex(this.indexA));
|
|
434
|
+
geo.copyVec2(localPointB, this.m_proxyB.getVertex(this.indexB));
|
|
435
|
+
|
|
436
|
+
geo.transformVec2(pointA, xfA, localPointA);
|
|
437
|
+
geo.transformVec2(pointB, xfB, localPointB);
|
|
438
|
+
|
|
439
|
+
const sep = geo.dotVec2(pointB, this.m_axis) - geo.dotVec2(pointA, this.m_axis);
|
|
440
|
+
return sep;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
case SeparationFunctionType.e_faceA: {
|
|
444
|
+
geo.rotVec2(normal, xfA.q, this.m_axis);
|
|
445
|
+
geo.transformVec2(pointA, xfA, this.m_localPoint);
|
|
446
|
+
|
|
447
|
+
if (find) {
|
|
448
|
+
geo.derotNegVec2(axisB, xfB.q, normal);
|
|
449
|
+
|
|
450
|
+
this.indexA = -1;
|
|
451
|
+
this.indexB = this.m_proxyB.getSupport(axisB);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
geo.copyVec2(localPointB, this.m_proxyB.getVertex(this.indexB));
|
|
455
|
+
geo.transformVec2(pointB, xfB, localPointB);
|
|
456
|
+
|
|
457
|
+
const sep = geo.dotVec2(pointB, normal) - geo.dotVec2(pointA, normal);
|
|
458
|
+
return sep;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
case SeparationFunctionType.e_faceB: {
|
|
462
|
+
geo.rotVec2(normal, xfB.q, this.m_axis);
|
|
463
|
+
geo.transformVec2(pointB, xfB, this.m_localPoint);
|
|
464
|
+
|
|
465
|
+
if (find) {
|
|
466
|
+
geo.derotNegVec2(axisA, xfA.q, normal);
|
|
467
|
+
|
|
468
|
+
this.indexB = -1;
|
|
469
|
+
this.indexA = this.m_proxyA.getSupport(axisA);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
geo.copyVec2(localPointA, this.m_proxyA.getVertex(this.indexA));
|
|
473
|
+
geo.transformVec2(pointA, xfA, localPointA);
|
|
474
|
+
|
|
475
|
+
const sep = geo.dotVec2(pointA, normal) - geo.dotVec2(pointB, normal);
|
|
476
|
+
return sep;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
default:
|
|
480
|
+
if (_ASSERT) console.assert(false);
|
|
481
|
+
if (find) {
|
|
482
|
+
this.indexA = -1;
|
|
483
|
+
this.indexB = -1;
|
|
484
|
+
}
|
|
485
|
+
return 0.0;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
findMinSeparation(t: number): number {
|
|
490
|
+
return this.compute(true, t);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
evaluate(t: number): number {
|
|
494
|
+
return this.compute(false, t);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** @internal */ const separationFunction = new SeparationFunction();
|
|
499
|
+
|
|
500
|
+
// legacy exports
|
|
501
|
+
TimeOfImpact.Input = TOIInput;
|
|
502
|
+
TimeOfImpact.Output = TOIOutput;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Planck.js
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Erin Catto, Ali Shakiba
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Vec2Value } from "../../common/Vec2";
|
|
11
|
+
import { PolygonShape } from "./PolygonShape";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A rectangle polygon which extend PolygonShape.
|
|
15
|
+
*/
|
|
16
|
+
export class BoxShape extends PolygonShape {
|
|
17
|
+
// note that box is serialized/deserialized as polygon
|
|
18
|
+
static TYPE = "polygon" as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param halfWidth
|
|
23
|
+
* @param halfHeight
|
|
24
|
+
* @param center coordinate of the center of the box relative to the body
|
|
25
|
+
* @param angle angle of the box relative to the body
|
|
26
|
+
*/
|
|
27
|
+
constructor(halfWidth: number, halfHeight: number, center?: Vec2Value, angle?: number) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
this._setAsBox(halfWidth, halfHeight, center, angle);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { BoxShape as Box };
|