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.

Files changed (80) hide show
  1. package/LICENSE.txt +20 -0
  2. package/README.md +21 -0
  3. package/dist/planck-with-testbed.d.ts +4433 -0
  4. package/dist/planck-with-testbed.js +20730 -0
  5. package/dist/planck-with-testbed.js.map +1 -0
  6. package/dist/planck-with-testbed.umd.cjs +20730 -0
  7. package/dist/planck-with-testbed.umd.cjs.map +1 -0
  8. package/dist/planck.d.ts +4343 -0
  9. package/dist/planck.js +13516 -0
  10. package/dist/planck.js.map +1 -0
  11. package/dist/planck.umd.cjs +13516 -0
  12. package/dist/planck.umd.cjs.map +1 -0
  13. package/package.json +105 -0
  14. package/src/Settings.ts +238 -0
  15. package/src/__test__/Basic.test.ts +43 -0
  16. package/src/__test__/CCD.test.ts +70 -0
  17. package/src/__test__/Collision.test.ts +133 -0
  18. package/src/__test__/Math.test.ts +105 -0
  19. package/src/__test__/Pool.test.ts +48 -0
  20. package/src/__test__/World.test.ts +73 -0
  21. package/src/collision/AABB.ts +287 -0
  22. package/src/collision/BroadPhase.ts +210 -0
  23. package/src/collision/Distance.ts +962 -0
  24. package/src/collision/DynamicTree.ts +907 -0
  25. package/src/collision/Manifold.ts +420 -0
  26. package/src/collision/Raycast.ts +30 -0
  27. package/src/collision/Shape.ts +114 -0
  28. package/src/collision/TimeOfImpact.ts +502 -0
  29. package/src/collision/shape/BoxShape.ts +34 -0
  30. package/src/collision/shape/ChainShape.ts +360 -0
  31. package/src/collision/shape/CircleShape.ts +202 -0
  32. package/src/collision/shape/CollideCircle.ts +66 -0
  33. package/src/collision/shape/CollideCirclePolygon.ts +142 -0
  34. package/src/collision/shape/CollideEdgeCircle.ts +185 -0
  35. package/src/collision/shape/CollideEdgePolygon.ts +528 -0
  36. package/src/collision/shape/CollidePolygon.ts +280 -0
  37. package/src/collision/shape/EdgeShape.ts +316 -0
  38. package/src/collision/shape/PolygonShape.ts +581 -0
  39. package/src/common/Geo.ts +589 -0
  40. package/src/common/Jacobian.ts +17 -0
  41. package/src/common/Mat22.ts +221 -0
  42. package/src/common/Mat33.ts +224 -0
  43. package/src/common/Math.ts +96 -0
  44. package/src/common/Rot.ts +218 -0
  45. package/src/common/Sweep.ts +119 -0
  46. package/src/common/Transform.ts +203 -0
  47. package/src/common/Vec2.ts +624 -0
  48. package/src/common/Vec3.ts +188 -0
  49. package/src/dynamics/Body.ts +1198 -0
  50. package/src/dynamics/Contact.ts +1366 -0
  51. package/src/dynamics/Fixture.ts +506 -0
  52. package/src/dynamics/Joint.ts +226 -0
  53. package/src/dynamics/Position.ts +44 -0
  54. package/src/dynamics/Solver.ts +890 -0
  55. package/src/dynamics/Velocity.ts +18 -0
  56. package/src/dynamics/World.ts +1169 -0
  57. package/src/dynamics/joint/DistanceJoint.ts +463 -0
  58. package/src/dynamics/joint/FrictionJoint.ts +396 -0
  59. package/src/dynamics/joint/GearJoint.ts +591 -0
  60. package/src/dynamics/joint/MotorJoint.ts +430 -0
  61. package/src/dynamics/joint/MouseJoint.ts +390 -0
  62. package/src/dynamics/joint/PrismaticJoint.ts +903 -0
  63. package/src/dynamics/joint/PulleyJoint.ts +529 -0
  64. package/src/dynamics/joint/RevoluteJoint.ts +745 -0
  65. package/src/dynamics/joint/RopeJoint.ts +383 -0
  66. package/src/dynamics/joint/WeldJoint.ts +544 -0
  67. package/src/dynamics/joint/WheelJoint.ts +683 -0
  68. package/src/dynamics/joint/__test__/DistanceJoint.test.ts +66 -0
  69. package/src/index.ts +60 -0
  70. package/src/internal.ts +20 -0
  71. package/src/main.ts +3 -0
  72. package/src/serializer/__test__/Serialize.test.ts +52 -0
  73. package/src/serializer/__test__/Validator.test.ts +55 -0
  74. package/src/serializer/index.ts +257 -0
  75. package/src/serializer/schema.json +168 -0
  76. package/src/util/Pool.ts +120 -0
  77. package/src/util/Testbed.ts +157 -0
  78. package/src/util/Timer.ts +15 -0
  79. package/src/util/options.ts +28 -0
  80. 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 };