littlejsengine 1.18.29 → 1.20.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.
Files changed (51) hide show
  1. package/FAQ.md +841 -815
  2. package/README.md +211 -199
  3. package/dist/littlejs.d.ts +4383 -812
  4. package/dist/littlejs.esm.js +31848 -16680
  5. package/dist/littlejs.esm.min.js +14 -1
  6. package/dist/littlejs.js +31230 -16166
  7. package/dist/littlejs.min.js +13 -1
  8. package/dist/littlejs.release.js +27419 -15457
  9. package/package.json +10 -5
  10. package/plugins/audioEffects.js +375 -0
  11. package/plugins/box2d.js +2516 -2073
  12. package/plugins/drawUtilities.js +206 -31
  13. package/plugins/gltf.js +740 -0
  14. package/plugins/jsconfig.json +19 -0
  15. package/plugins/lightSystem.js +719 -330
  16. package/plugins/math3d.js +911 -0
  17. package/plugins/medalSystem.js +118 -72
  18. package/plugins/newgrounds.js +400 -199
  19. package/plugins/pathFinder.js +208 -175
  20. package/plugins/pluginExport.js +196 -115
  21. package/plugins/postProcess.js +284 -166
  22. package/plugins/render3d.js +4072 -0
  23. package/plugins/render3dDebug.js +109 -0
  24. package/plugins/render3dExtras.js +1754 -0
  25. package/plugins/render3dVoxels.js +502 -0
  26. package/plugins/textureSheet.js +86 -48
  27. package/plugins/threejs.js +183 -158
  28. package/plugins/tweakables.js +411 -0
  29. package/plugins/tweenSystem.js +646 -526
  30. package/plugins/uiSystem.js +2024 -1687
  31. package/plugins/zzfxm.js +8 -15
  32. package/src/engine.js +345 -194
  33. package/src/engineAudio.js +1022 -777
  34. package/src/engineBuild.mjs +289 -253
  35. package/src/engineDebug.js +912 -735
  36. package/src/engineDraw.js +1774 -1510
  37. package/src/engineEditor.js +2431 -0
  38. package/src/engineExport.js +419 -396
  39. package/src/engineInput.js +1462 -1293
  40. package/src/engineLogo.js +4 -3
  41. package/src/engineMath.js +1274 -1183
  42. package/src/engineObject.js +674 -551
  43. package/src/engineParticles.js +583 -532
  44. package/src/engineRelease.js +67 -45
  45. package/src/engineSettings.js +808 -726
  46. package/src/engineTileLayer.js +995 -741
  47. package/src/engineUtilities.js +32 -13
  48. package/src/engineWebGL.js +1162 -941
  49. package/src/jsconfig.json +17 -13
  50. package/plugins/box2d.wasm.js +0 -630
  51. package/plugins/box2d.wasm.wasm +0 -0
package/plugins/box2d.js CHANGED
@@ -1,2074 +1,2517 @@
1
- /**
2
- * LittleJS Box2D Physics Plugin
3
- * - Box2dObject extends EngineObject with Box2D physics
4
- * - Call box2dInit() to enable
5
- * - You will also need to include box2d.wasm.js
6
- * - Uses a super fast web assembly port of Box2D v2.3.1
7
- * - More info: https://github.com/kripken/box2d.js
8
- * - Functions to create polygon, circle, and edge shapes
9
- * - Contact begin and end callbacks
10
- * - Wraps b2Vec2 type to/from Vector2
11
- * - Raycasting and querying
12
- * - Box2dTileLayer for grid based collision
13
- * - Every type of joint
14
- * - Debug physics drawing
15
- * @namespace Box2D
16
- */
17
-
18
- 'use strict';
19
-
20
- /** Global Box2d Plugin object
21
- * @type {Box2dPlugin}
22
- * @memberof Box2D */
23
- let box2d;
24
-
25
- /** Enable Box2D debug drawing
26
- * @type {boolean}
27
- * @default
28
- * @memberof Box2D */
29
- let box2dDebug = false;
30
-
31
- /** Enable Box2D debug drawing
32
- * @param {boolean} enable
33
- * @memberof Box2D */
34
- function box2dSetDebug(enable) { box2dDebug = enable; }
35
-
36
- ///////////////////////////////////////////////////////////////////////////////
37
- /**
38
- * Box2D Object - extend with your own custom physics objects
39
- * - A LittleJS object with Box2D physics, dynamic by default
40
- * - Provides interface for Box2D body and fixture functions
41
- * - Each object can have multiple fixtures and joints
42
- * @extends EngineObject
43
- * @memberof Box2D
44
- */
45
- class Box2dObject extends EngineObject
46
- {
47
- /** Create a LittleJS object with Box2d physics
48
- * @param {Vector2} [pos]
49
- * @param {Vector2} [size]
50
- * @param {TileInfo} [tileInfo]
51
- * @param {number} [angle]
52
- * @param {Color} [color]
53
- * @param {number} [bodyType]
54
- * @param {number} [renderOrder] */
55
- constructor(pos=vec2(), size=vec2(), tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
56
- {
57
- super(pos, size, tileInfo, angle, color, renderOrder);
58
-
59
- // create physics body
60
- const bodyDef = new box2d.instance.b2BodyDef();
61
- bodyDef.set_type(bodyType);
62
- bodyDef.set_position(box2d.vec2dTo(pos));
63
- bodyDef.set_angle(-angle);
64
-
65
- /** @property {Object} - The Box2d body */
66
- this.body = box2d.world.CreateBody(bodyDef);
67
- /** @property {Color} - Line color used for default box2d drawing */
68
- this.lineColor = BLACK;
69
- /** @property {Array<Object>} - List of all edges for default box2d drawing */
70
- this.edgeLists = [];
71
- /** @property {Array<Object>} - List of all edge loops for default box2d drawing */
72
- this.edgeLoops = [];
73
-
74
- this.body.object = this; // link body to this object
75
- box2d.objects.push(this); // keep track of all box2d objects
76
- }
77
-
78
- /** Destroy this object and its physics body */
79
- destroy()
80
- {
81
- if (this.destroyed) return;
82
-
83
- // destroy physics body, fixtures, and joints
84
- ASSERT(this.body, 'Box2dObject has no body to destroy');
85
- box2d.world.DestroyBody(this.body);
86
-
87
- // remove from tracked list so paused / headless sessions don't leak
88
- const i = box2d.objects.indexOf(this);
89
- if (i >= 0)
90
- box2d.objects.splice(i, 1);
91
- super.destroy();
92
- }
93
-
94
- /** Box2d objects updated with Box2d world step */
95
- updatePhysics() {}
96
-
97
- /** Render the object, uses box2d drawing if no tile info exists */
98
- render()
99
- {
100
- // use default render or draw fixtures
101
- if (this.tileInfo)
102
- super.render();
103
- else
104
- this.drawFixtures(this.color, this.lineColor, this.lineWidth);
105
- }
106
-
107
- /** Render debug info */
108
- renderDebugInfo()
109
- {
110
- const isAsleep = !this.getIsAwake();
111
- const isStatic = this.getBodyType() === box2d.bodyTypeStatic;
112
- const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
113
- this.drawFixtures(color);
114
- }
115
-
116
- /** Draws all this object's fixtures
117
- * @param {Color} [color]
118
- * @param {Color} [lineColor]
119
- * @param {number} [lineWidth]
120
- * @param {boolean} [useWebGL=glEnable]
121
- * @param {CanvasRenderingContext2D} [context] */
122
- drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
123
- {
124
- // draw non-edge fixtures
125
- this.getFixtureList().forEach((fixture)=>
126
- {
127
- const shape = box2d.castShapeObject(fixture.GetShape());
128
- if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
129
- {
130
- box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
131
- }
132
- });
133
-
134
- // draw edges using a single draw line for better connections
135
- this.edgeLists.forEach(points=>
136
- drawLineList(points, lineWidth, lineColor, false, this.pos, this.angle));
137
- this.edgeLoops.forEach(points=>
138
- drawLineList(points, lineWidth, lineColor, true, this.pos, this.angle));
139
- }
140
-
141
- ///////////////////////////////////////////////////////////////////////////////
142
- // physics contact callbacks
143
-
144
- /** Called when a contact begins
145
- * @param {Box2dObject} otherObject */
146
- beginContact(otherObject) {}
147
-
148
- /** Called when a contact ends
149
- * @param {Box2dObject} otherObject */
150
- endContact(otherObject) {}
151
-
152
- ///////////////////////////////////////////////////////////////////////////////
153
- // physics fixtures and shapes
154
-
155
- /** Add a shape fixture to the body
156
- * @param {Object} shape
157
- * @param {number} [density]
158
- * @param {number} [friction]
159
- * @param {number} [restitution]
160
- * @param {boolean} [isSensor] */
161
- addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
162
- {
163
- ASSERT(isNumber(density), 'density must be a number');
164
- ASSERT(isNumber(friction), 'friction must be a number');
165
- ASSERT(isNumber(restitution), 'restitution must be a number');
166
-
167
- const fd = new box2d.instance.b2FixtureDef();
168
- fd.set_shape(shape);
169
- fd.set_density(density);
170
- fd.set_friction(friction);
171
- fd.set_restitution(restitution);
172
- fd.set_isSensor(isSensor);
173
- return this.body.CreateFixture(fd);
174
- }
175
-
176
- /** Add a box shape to the body
177
- * @param {Vector2} [size]
178
- * @param {Vector2} [offset]
179
- * @param {number} [angle] - LittleJS convention (clockwise positive).
180
- * Negated internally to match Box2D's CCW-positive convention so the
181
- * fixture aligns with the same angle passed to drawRect/drawTile.
182
- * @param {number} [density]
183
- * @param {number} [friction]
184
- * @param {number} [restitution]
185
- * @param {boolean} [isSensor] */
186
- addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
187
- {
188
- ASSERT(isVector2(size), 'size must be a Vector2');
189
- ASSERT(size.x > 0 && size.y > 0, 'size must be positive');
190
- ASSERT(isVector2(offset), 'offset must be a Vector2');
191
- ASSERT(isNumber(angle), 'angle must be a number');
192
-
193
- const shape = new box2d.instance.b2PolygonShape();
194
- shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), -angle);
195
- return this.addShape(shape, density, friction, restitution, isSensor);
196
- }
197
-
198
- /** Add a polygon shape to the body
199
- * @param {Array<Vector2>} points
200
- * @param {number} [density]
201
- * @param {number} [friction]
202
- * @param {number} [restitution]
203
- * @param {boolean} [isSensor] */
204
- addPoly(points, density, friction, restitution, isSensor)
205
- {
206
- ASSERT(isArray(points), 'points must be an array');
207
-
208
- function box2dCreatePolygonShape(points)
209
- {
210
- ASSERT(3 <= points.length && points.length <= 8);
211
- const buffer = box2d.instance._malloc(points.length * 8);
212
- for (let i=0, offset=0; i<points.length; ++i)
213
- {
214
- box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
215
- offset += 4;
216
- box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
217
- offset += 4;
218
- }
219
- const box2dPoints = box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
220
- const shape = new box2d.instance.b2PolygonShape();
221
- shape.Set(box2dPoints, points.length);
222
- box2d.instance._free(buffer);
223
- return shape;
224
- }
225
-
226
- const shape = box2dCreatePolygonShape(points);
227
- return this.addShape(shape, density, friction, restitution, isSensor);
228
- }
229
-
230
- /** Add a regular polygon shape to the body
231
- * @param {number} [diameter]
232
- * @param {number} [sides]
233
- * @param {number} [density]
234
- * @param {number} [friction]
235
- * @param {number} [restitution]
236
- * @param {boolean} [isSensor] */
237
- addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
238
- {
239
- ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
240
- ASSERT(isNumber(sides) && sides>2, 'sides must be a positive number greater than 2');
241
-
242
- const points = [];
243
- const radius = diameter/2;
244
- for (let i=sides; i--;)
245
- points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
246
- return this.addPoly(points, density, friction, restitution, isSensor);
247
- }
248
-
249
- /** Add a random polygon shape to the body
250
- * @param {number} [diameter]
251
- * @param {number} [density]
252
- * @param {number} [friction]
253
- * @param {number} [restitution]
254
- * @param {boolean} [isSensor] */
255
- addRandomPoly(diameter=1, density, friction, restitution, isSensor)
256
- {
257
- ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
258
-
259
- const sides = randInt(3, 9);
260
- const points = [];
261
- const radius = diameter/2;
262
- for (let i=sides; i--;)
263
- points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
264
- return this.addPoly(points, density, friction, restitution, isSensor);
265
- }
266
-
267
- /** Add a circle shape to the body
268
- * @param {number} [diameter]
269
- * @param {Vector2} [offset]
270
- * @param {number} [density]
271
- * @param {number} [friction]
272
- * @param {number} [restitution]
273
- * @param {boolean} [isSensor] */
274
- addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
275
- {
276
- ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
277
- ASSERT(isVector2(offset), 'offset must be a Vector2');
278
-
279
- const shape = new box2d.instance.b2CircleShape();
280
- shape.set_m_p(box2d.vec2dTo(offset));
281
- shape.set_m_radius(diameter/2);
282
- return this.addShape(shape, density, friction, restitution, isSensor);
283
- }
284
-
285
- /** Add an edge shape to the body
286
- * @param {Vector2} point1
287
- * @param {Vector2} point2
288
- * @param {number} [density]
289
- * @param {number} [friction]
290
- * @param {number} [restitution]
291
- * @param {boolean} [isSensor] */
292
- addEdge(point1, point2, density, friction, restitution, isSensor)
293
- {
294
- ASSERT(isVector2(point1), 'point1 must be a Vector2');
295
- ASSERT(isVector2(point2), 'point2 must be a Vector2');
296
-
297
- const shape = new box2d.instance.b2EdgeShape();
298
- shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
299
- return this.addShape(shape, density, friction, restitution, isSensor);
300
- }
301
-
302
- /** Add an edge list to the body
303
- * @param {Array<Vector2>} points
304
- * @param {number} [density]
305
- * @param {number} [friction]
306
- * @param {number} [restitution]
307
- * @param {boolean} [isSensor] */
308
- addEdgeList(points, density, friction, restitution, isSensor)
309
- {
310
- ASSERT(isArray(points), 'points must be an array');
311
- const fixtures = [], edgePoints = [];
312
- for (let i=0; i<points.length-1; ++i)
313
- {
314
- const shape = new box2d.instance.b2EdgeShape();
315
- points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
316
- points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
317
- points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
318
- points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
319
- const f = this.addShape(shape, density, friction, restitution, isSensor);
320
- fixtures.push(f);
321
- edgePoints.push(points[i].copy());
322
- }
323
- edgePoints.push(points[points.length-1].copy());
324
- this.edgeLists.push(edgePoints);
325
- return fixtures;
326
- }
327
-
328
- /** Add an edge loop to the body, an edge loop connects the end points
329
- * @param {Array<Vector2>} points
330
- * @param {number} [density]
331
- * @param {number} [friction]
332
- * @param {number} [restitution]
333
- * @param {boolean} [isSensor] */
334
- addEdgeLoop(points, density, friction, restitution, isSensor)
335
- {
336
- ASSERT(isArray(points), 'points must be an array');
337
- const fixtures = [], edgePoints = [];
338
- const getPoint = i=> points[mod(i,points.length)];
339
- for (let i=0; i<points.length; ++i)
340
- {
341
- const shape = new box2d.instance.b2EdgeShape();
342
- shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
343
- shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
344
- shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
345
- shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
346
- const f = this.addShape(shape, density, friction, restitution, isSensor);
347
- fixtures.push(f);
348
- edgePoints.push(points[i].copy());
349
- }
350
- this.edgeLoops.push(edgePoints);
351
- return fixtures;
352
- }
353
-
354
- /** Destroy a fixture from the body
355
- * @param {Object} [fixture] */
356
- destroyFixture(fixture) { this.body.DestroyFixture(fixture); }
357
-
358
- /** Destroy all fixture from the body */
359
- destroyAllFixtures()
360
- { this.getFixtureList().forEach(fixture=>this.destroyFixture(fixture)); }
361
-
362
- ///////////////////////////////////////////////////////////////////////////////
363
- // physics get functions
364
-
365
- /** Gets the center of mass
366
- * @return {Vector2} */
367
- getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
368
-
369
- /** Gets the linear velocity
370
- * @return {Vector2} */
371
- getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
372
-
373
- /** Gets the angular velocity
374
- * @return {Vector2} */
375
- getAngularVelocity() { return this.body.GetAngularVelocity(); }
376
-
377
- /** Gets the mass
378
- * @return {number} */
379
- getMass() { return this.body.GetMass(); }
380
-
381
- /** Gets the rotational inertia
382
- * @return {number} */
383
- getInertia() { return this.body.GetInertia(); }
384
-
385
- /** Check if this object is awake
386
- * @return {boolean} */
387
- getIsAwake() { return this.body.IsAwake(); }
388
-
389
- /** Gets the physics body type
390
- * @return {number} */
391
- getBodyType() { return this.body.GetType(); }
392
-
393
- /** Get the speed of this object
394
- * @return {number} */
395
- getSpeed() { return this.getLinearVelocity().length(); }
396
-
397
- ///////////////////////////////////////////////////////////////////////////////
398
- // physics set functions
399
-
400
- /** Sets the position and angle
401
- * @param {Vector2} pos
402
- * @param {number} angle */
403
- setTransform(pos, angle)
404
- {
405
- this.pos = pos;
406
- this.angle = angle;
407
- // box2d uses reverse angle
408
- this.body.SetTransform(box2d.vec2dTo(pos), -angle);
409
- }
410
-
411
- /** Sets the position
412
- * @param {Vector2} pos */
413
- setPosition(pos)
414
- { this.setTransform(pos, -this.body.GetAngle()); }
415
-
416
- /** Sets the angle
417
- * @param {number} angle */
418
- setAngle(angle)
419
- { this.setTransform(box2d.vec2From(this.body.GetPosition()), angle); }
420
-
421
- /** Sets the linear velocity
422
- * @param {Vector2} velocity */
423
- setLinearVelocity(velocity)
424
- { this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
425
-
426
- /** Sets the angular velocity
427
- * @param {number} angularVelocity */
428
- setAngularVelocity(angularVelocity)
429
- { this.body.SetAngularVelocity(angularVelocity); }
430
-
431
- /** Sets the linear damping
432
- * @param {number} damping */
433
- setLinearDamping(damping)
434
- { this.body.SetLinearDamping(damping); }
435
-
436
- /** Sets the angular damping
437
- * @param {number} damping */
438
- setAngularDamping(damping)
439
- { this.body.SetAngularDamping(damping); }
440
-
441
- /** Sets the gravity scale
442
- * @param {number} [scale] */
443
- setGravityScale(scale=1)
444
- { this.body.SetGravityScale(this.gravityScale = scale); }
445
-
446
- /** Should be like a bullet for continuous collision detection?
447
- * @param {boolean} [isBullet] */
448
- setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
449
-
450
- /** Set the sleep state of the body
451
- * @param {boolean} [isAwake] */
452
- setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
453
-
454
- /** Set the physics body type
455
- * @param {number} type */
456
- setBodyType(type) { this.body.SetType(type); }
457
-
458
- /** Set whether the body is allowed to sleep
459
- * @param {boolean} [isAllowed] */
460
- setSleepingAllowed(isAllowed=true)
461
- { this.body.SetSleepingAllowed(isAllowed); }
462
-
463
- /** Set whether the body can rotate
464
- * @param {boolean} [isFixed] */
465
- setFixedRotation(isFixed=true)
466
- { this.body.SetFixedRotation(isFixed); }
467
-
468
- /** Set the center of mass of the body
469
- * @param {Vector2} center */
470
- setCenterOfMass(center) { this.setMassData(center) }
471
-
472
- /** Set the mass of the body
473
- * @param {number} mass */
474
- setMass(mass) { this.setMassData(undefined, mass) }
475
-
476
- /** Set the moment of inertia of the body
477
- * @param {number} momentOfInertia */
478
- setMomentOfInertia(momentOfInertia)
479
- { this.setMassData(undefined, undefined, momentOfInertia) }
480
-
481
- /** Reset the mass, center of mass, and moment */
482
- resetMassData() { this.body.ResetMassData(); }
483
-
484
- /** Set the mass data of the body
485
- * @param {Vector2} [localCenter]
486
- * @param {number} [mass]
487
- * @param {number} [momentOfInertia] */
488
- setMassData(localCenter, mass, momentOfInertia)
489
- {
490
- const data = new box2d.instance.b2MassData();
491
- this.body.GetMassData(data);
492
- // use !== undefined so setMass(0) (static-equivalent) isn't silently ignored
493
- if (localCenter !== undefined) data.set_center(box2d.vec2dTo(localCenter));
494
- if (mass !== undefined) data.set_mass(mass);
495
- if (momentOfInertia !== undefined) data.set_I(momentOfInertia);
496
- this.body.SetMassData(data);
497
- }
498
-
499
- /** Set the collision filter data for this body
500
- * @param {number} [categoryBits]
501
- * @param {number} [ignoreCategoryBits]
502
- * @param {number} [groupIndex] */
503
- setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0)
504
- {
505
- this.getFixtureList().forEach(fixture=>
506
- {
507
- const filter = fixture.GetFilterData();
508
- filter.set_categoryBits(categoryBits);
509
- filter.set_maskBits(0xffff & ~ignoreCategoryBits);
510
- filter.set_groupIndex(groupIndex);
511
- fixture.SetFilterData(filter); // applies and refilters contacts
512
- });
513
- }
514
-
515
- /** Set if this body is a sensor
516
- * @param {boolean} [isSensor] */
517
- setSensor(isSensor=true)
518
- { this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
519
-
520
- ///////////////////////////////////////////////////////////////////////////////
521
- // physics force and torque functions
522
-
523
- /** Apply force to this object
524
- * @param {Vector2} force
525
- * @param {Vector2} [pos] */
526
- applyForce(force, pos)
527
- {
528
- pos ||= this.getCenterOfMass();
529
- this.setAwake();
530
- this.body.ApplyForce(box2d.vec2dTo(force), box2d.vec2dTo(pos));
531
- }
532
-
533
- /** Apply acceleration to this object (changes velocity by acceleration,
534
- * mass-independent — matches EngineObject.applyAcceleration semantics).
535
- * Use applyImpulse if you want the mass-dependent velocity change
536
- * Δv = impulse / mass, or applyForce for a Newton-style sustained force.
537
- * @param {Vector2} acceleration
538
- * @param {Vector2} [pos] */
539
- applyAcceleration(acceleration, pos)
540
- {
541
- pos ||= this.getCenterOfMass();
542
- this.setAwake();
543
- const impulse = acceleration.scale(this.getMass());
544
- this.body.ApplyLinearImpulse(box2d.vec2dTo(impulse), box2d.vec2dTo(pos));
545
- }
546
-
547
- /** Apply an instantaneous linear impulse. Changes velocity immediately by
548
- * impulse / mass (so heavier bodies move less for the same impulse).
549
- * @param {Vector2} impulse
550
- * @param {Vector2} [pos] */
551
- applyImpulse(impulse, pos)
552
- {
553
- pos ||= this.getCenterOfMass();
554
- this.setAwake();
555
- this.body.ApplyLinearImpulse(box2d.vec2dTo(impulse), box2d.vec2dTo(pos));
556
- }
557
-
558
- /** Apply torque to this object
559
- * @param {number} torque */
560
- applyTorque(torque)
561
- {
562
- this.setAwake();
563
- this.body.ApplyTorque(torque);
564
- }
565
-
566
- /** Apply angular acceleration to this object (changes angular velocity by
567
- * acceleration, mass-independent — matches EngineObject semantics).
568
- * @param {number} acceleration */
569
- applyAngularAcceleration(acceleration)
570
- {
571
- this.setAwake();
572
- this.body.ApplyAngularImpulse(acceleration * this.getInertia());
573
- }
574
-
575
- /** Apply an instantaneous angular impulse. Changes angular velocity by
576
- * impulse / inertia immediately.
577
- * @param {number} impulse */
578
- applyAngularImpulse(impulse)
579
- {
580
- this.setAwake();
581
- this.body.ApplyAngularImpulse(impulse);
582
- }
583
-
584
- ///////////////////////////////////////////////////////////////////////////////
585
- // lists of fixtures and joints
586
-
587
- /** Check if this object has any fixtures
588
- * @return {boolean} */
589
- hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
590
-
591
- /** Get list of fixtures for this object
592
- * @return {Array<Object>} */
593
- getFixtureList()
594
- {
595
- const fixtures = [];
596
- for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
597
- {
598
- fixtures.push(fixture);
599
- fixture = fixture.GetNext();
600
- }
601
- return fixtures;
602
- }
603
-
604
- /** Check if this object has any joints
605
- * @return {boolean} */
606
- hasJoints() { return !box2d.isNull(this.body.GetJointList()); }
607
-
608
- /** Get list of joints for this object
609
- * @return {Array<Object>} */
610
- getJointList()
611
- {
612
- const joints = [];
613
- for (let joint=this.body.GetJointList(); !box2d.isNull(joint); )
614
- {
615
- joints.push(joint);
616
- joint = joint.get_next();
617
- }
618
- return joints;
619
- }
620
- }
621
-
622
- ///////////////////////////////////////////////////////////////////////////////
623
- /**
624
- * Box2D Static Object - Box2d with a static physics body
625
- * @extends Box2dObject
626
- * @memberof Box2D
627
- */
628
- class Box2dStaticObject extends Box2dObject
629
- {
630
- /** Create a LittleJS object with Box2d physics
631
- * @param {Vector2} [pos]
632
- * @param {Vector2} [size]
633
- * @param {TileInfo} [tileInfo]
634
- * @param {number} [angle]
635
- * @param {Color} [color]
636
- * @param {number} [renderOrder] */
637
- constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
638
- {
639
- const bodyType = box2d.bodyTypeStatic;
640
- super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
641
- }
642
- }
643
-
644
- ///////////////////////////////////////////////////////////////////////////////
645
- /**
646
- * Box2D Kinematic Object - Box2d with a kinematic physics body
647
- * @extends Box2dObject
648
- * @memberof Box2D
649
- */
650
- class Box2dKinematicObject extends Box2dObject
651
- {
652
- /** Create a LittleJS object with Box2d physics
653
- * @param {Vector2} [pos]
654
- * @param {Vector2} [size]
655
- * @param {TileInfo} [tileInfo]
656
- * @param {number} [angle]
657
- * @param {Color} [color]
658
- * @param {number} [renderOrder] */
659
- constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
660
- {
661
- const bodyType = box2d.bodyTypeKinematic;
662
- super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
663
- }
664
- }
665
-
666
- ///////////////////////////////////////////////////////////////////////////////
667
- /**
668
- * Box2d Tile Layer
669
- * - adds Box2d support to tile layers
670
- * - creates static box2d fixtures for solid tiles
671
- * @extends Box2dObject
672
- * @memberof Box2D
673
- */
674
- class Box2dTileLayer extends Box2dStaticObject
675
- {
676
- /** Create a Box2d tile layer object
677
- * @param {TileCollisionLayer} tileLayer - Tile layer for this object */
678
- constructor(tileLayer)
679
- {
680
- ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
681
- super(tileLayer.pos, tileLayer.size);
682
-
683
- /** @property {TileLayer} - The tile layer */
684
- this.tileLayer = tileLayer;
685
- this.addChild(tileLayer);
686
- }
687
-
688
- render()
689
- {
690
- // do not render fixtures, tile layer handles rendering
691
- }
692
-
693
- /** Create box2d collision fixtures for solid tiles
694
- * @param {number} [friction]
695
- * @param {number} [restitution] */
696
- buildCollision(friction=.2, restitution=0)
697
- {
698
- // destroy all fixtures and create new ones
699
- this.destroyAllFixtures();
700
-
701
- // create box2d object for this layer
702
- this.pos = this.tileLayer.pos.copy();
703
- this.size = this.tileLayer.size.copy();
704
-
705
- // track which tiles have been processed
706
- const processed = [];
707
- const getIndex = (x, y)=> x + y * this.size.x;
708
- const isSolidUnprocessed = (x, y)=>
709
- !processed[getIndex(x, y)] &&
710
- this.tileLayer.getCollisionData(vec2(x, y)) > 0;
711
-
712
- // combine tiles into larger boxes
713
- for (let x = 0; x < this.size.x; ++x)
714
- for (let y = 0; y < this.size.y; ++y)
715
- {
716
- if (!isSolidUnprocessed(x, y)) continue;
717
-
718
- // find max width by scanning right
719
- let width = 1, height = 1, canExpand = true;
720
- while (isSolidUnprocessed(x + width, y))
721
- ++width;
722
-
723
- // find max height by scanning up, ensuring all rows have the same width
724
- while (canExpand)
725
- {
726
- for (let checkX = 0; checkX < width; ++checkX)
727
- {
728
- if (!isSolidUnprocessed(x + checkX, y + height))
729
- {
730
- canExpand = false;
731
- break;
732
- }
733
- }
734
- if (canExpand)
735
- ++height;
736
- }
737
-
738
- // mark all tiles in this rectangle as processed
739
- for (let rectX = width; rectX--;)
740
- for (let rectY = height; rectY--;)
741
- processed[getIndex(x + rectX, y + rectY)] = true;
742
-
743
- // create a single fixture for the entire rectangle
744
- const shapeSize = vec2(width, height);
745
- const offset = vec2(x + width/2, y + height/2);
746
- this.addBox(shapeSize, offset, 0, 0, friction, restitution);
747
- }
748
- }
749
- }
750
-
751
- ///////////////////////////////////////////////////////////////////////////////
752
- /**
753
- * Box2D Raycast Result
754
- * - Holds results from a box2d raycast queries
755
- * - Automatically created by box2d raycast functions
756
- */
757
- class Box2dRaycastResult
758
- {
759
- /** Create a raycast result
760
- * @param {Object} fixture
761
- * @param {Vector2} point
762
- * @param {Vector2} normal
763
- * @param {number} fraction */
764
- constructor(fixture, point, normal, fraction)
765
- {
766
- /** @property {Box2dObject} - The box2d object */
767
- this.object = fixture.GetBody().object;
768
- /** @property {Object} - The fixture that was hit */
769
- this.fixture = fixture;
770
- /** @property {Vector2} - The hit point */
771
- this.point = point;
772
- /** @property {Vector2} - The hit normal */
773
- this.normal = normal;
774
- /** @property {number} - Distance fraction at the point of intersection */
775
- this.fraction = fraction;
776
- }
777
- }
778
-
779
- ///////////////////////////////////////////////////////////////////////////////
780
- /**
781
- * Box2D Joint
782
- * - Base class for Box2D joints
783
- * - A joint is used to connect objects together
784
- * @memberof Box2D
785
- */
786
- class Box2dJoint
787
- {
788
- /** Create a box2d joint, the base class is not intended to be used directly
789
- * @param {Object} jointDef */
790
- constructor(jointDef)
791
- {
792
- /** @property {Object} - The Box2d joint */
793
- this.box2dJoint = box2d.castJointObject(box2d.world.CreateJoint(jointDef));
794
- }
795
-
796
- /** Destroy this joint */
797
- destroy() { box2d.world.DestroyJoint(this.box2dJoint); this.box2dJoint = 0; }
798
-
799
- /** Get the first object attached to this joint
800
- * @return {Box2dObject} */
801
- getObjectA() { return this.box2dJoint.GetBodyA().object; }
802
-
803
- /** Get the second object attached to this joint
804
- * @return {Box2dObject} */
805
- getObjectB() { return this.box2dJoint.GetBodyB().object; }
806
-
807
- /** Get the first anchor for this joint in world coordinates
808
- * @return {Vector2} */
809
- getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
810
-
811
- /** Get the second anchor for this joint in world coordinates
812
- * @return {Vector2} */
813
- getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
814
-
815
- /** Get the reaction force on bodyB at the joint anchor given a time step
816
- * @param {number} time
817
- * @return {Vector2} */
818
- getReactionForce(time) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
819
-
820
- /** Get the reaction torque on bodyB in N*m given a time step
821
- * @param {number} time
822
- * @return {number} */
823
- getReactionTorque(time) { return this.box2dJoint.GetReactionTorque(1/time);}
824
-
825
- /** Check if the connected bodies should collide
826
- * @return {boolean} */
827
- getCollideConnected() { return this.box2dJoint.GetCollideConnected();}
828
-
829
- /** Check if either connected body is active
830
- * @return {boolean} */
831
- isActive() { return this.box2dJoint.IsActive();}
832
- }
833
-
834
- ///////////////////////////////////////////////////////////////////////////////
835
- /**
836
- * Box2D Target Joint, also known as a mouse joint
837
- * - Used to make a point on a object track a specific world point target
838
- * - This a soft constraint with a max force
839
- * - This allows the constraint to stretch and without applying huge forces
840
- * @extends Box2dJoint
841
- * @memberof Box2D
842
- */
843
- class Box2dTargetJoint extends Box2dJoint
844
- {
845
- /** Create a target joint
846
- * @param {Box2dObject} object
847
- * @param {Box2dObject} fixedObject
848
- * @param {Vector2} worldPos */
849
- constructor(object, fixedObject, worldPos)
850
- {
851
- object.setAwake();
852
- const jointDef = new box2d.instance.b2MouseJointDef();
853
- jointDef.set_bodyA(fixedObject.body);
854
- jointDef.set_bodyB(object.body);
855
- jointDef.set_target(box2d.vec2dTo(worldPos));
856
- jointDef.set_maxForce(2e3 * object.getMass());
857
- super(jointDef);
858
- }
859
-
860
- /** Set the target point in world coordinates
861
- * @param {Vector2} pos */
862
- setTarget(pos) { this.box2dJoint.SetTarget(box2d.vec2dTo(pos)); }
863
-
864
- /** Get the target point in world coordinates
865
- * @return {Vector2} */
866
- getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
867
-
868
- /** Sets the maximum force in Newtons
869
- * @param {number} force */
870
- setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
871
-
872
- /** Gets the maximum force in Newtons
873
- * @return {number} */
874
- getMaxForce() { return this.box2dJoint.GetMaxForce(); }
875
-
876
- /** Sets the joint frequency in Hertz
877
- * @param {number} hz */
878
- setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
879
-
880
- /** Gets the joint frequency in Hertz
881
- * @return {number} */
882
- getFrequency() { return this.box2dJoint.GetFrequency(); }
883
- }
884
-
885
- ///////////////////////////////////////////////////////////////////////////////
886
- /**
887
- * Box2D Distance Joint
888
- * - Constrains two points on two objects to remain at a fixed distance
889
- * - You can view this as a massless, rigid rod
890
- * @extends Box2dJoint
891
- * @memberof Box2D
892
- */
893
- class Box2dDistanceJoint extends Box2dJoint
894
- {
895
- /** Create a distance joint
896
- * @param {Box2dObject} objectA
897
- * @param {Box2dObject} objectB
898
- * @param {Vector2} anchorA
899
- * @param {Vector2} anchorB
900
- * @param {boolean} [collide] */
901
- constructor(objectA, objectB, anchorA, anchorB, collide=false)
902
- {
903
- anchorA ||= box2d.vec2From(objectA.body.GetPosition());
904
- anchorB ||= box2d.vec2From(objectB.body.GetPosition());
905
- const localAnchorA = objectA.worldToLocal(anchorA);
906
- const localAnchorB = objectB.worldToLocal(anchorB);
907
- const jointDef = new box2d.instance.b2DistanceJointDef();
908
- jointDef.set_bodyA(objectA.body);
909
- jointDef.set_bodyB(objectB.body);
910
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
911
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
912
- jointDef.set_length(anchorA.distance(anchorB));
913
- jointDef.set_collideConnected(collide);
914
- super(jointDef);
915
- }
916
-
917
- /** Get the local anchor point relative to objectA's origin
918
- * @return {Vector2} */
919
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
920
-
921
- /** Get the local anchor point relative to objectB's origin
922
- * @return {Vector2} */
923
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
924
-
925
- /** Set the length of the joint
926
- * @param {number} length */
927
- setLength(length) { this.box2dJoint.SetLength(length); }
928
-
929
- /** Get the length of the joint
930
- * @return {number} */
931
- getLength() { return this.box2dJoint.GetLength(); }
932
-
933
- /** Set the frequency in Hertz
934
- * @param {number} hz */
935
- setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
936
-
937
- /** Get the frequency in Hertz
938
- * @return {number} */
939
- getFrequency() { return this.box2dJoint.GetFrequency(); }
940
-
941
- /** Set the damping ratio
942
- * @param {number} ratio */
943
- setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); }
944
-
945
- /** Get the damping ratio
946
- * @return {number} */
947
- getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
948
- }
949
-
950
- ///////////////////////////////////////////////////////////////////////////////
951
- /**
952
- * Box2D Pin Joint
953
- * - Pins two objects together at a point
954
- * @extends Box2dDistanceJoint
955
- * @memberof Box2D
956
- */
957
- class Box2dPinJoint extends Box2dDistanceJoint
958
- {
959
- /** Create a pin joint
960
- * @param {Box2dObject} objectA
961
- * @param {Box2dObject} objectB
962
- * @param {Vector2} [pos]
963
- * @param {boolean} [collide] */
964
- constructor(objectA, objectB, pos=objectA.pos, collide=false)
965
- {
966
- super(objectA, objectB, undefined, pos, collide);
967
- }
968
- }
969
-
970
- ///////////////////////////////////////////////////////////////////////////////
971
- /**
972
- * Box2D Rope Joint
973
- * - Enforces a maximum distance between two points on two objects
974
- * @extends Box2dJoint
975
- * @memberof Box2D
976
- */
977
- class Box2dRopeJoint extends Box2dJoint
978
- {
979
- /** Create a rope joint
980
- * @param {Box2dObject} objectA
981
- * @param {Box2dObject} objectB
982
- * @param {Vector2} anchorA
983
- * @param {Vector2} anchorB
984
- * @param {number} extraLength
985
- * @param {boolean} [collide] */
986
- constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
987
- {
988
- anchorA ||= box2d.vec2From(objectA.body.GetPosition());
989
- anchorB ||= box2d.vec2From(objectB.body.GetPosition());
990
- const localAnchorA = objectA.worldToLocal(anchorA);
991
- const localAnchorB = objectB.worldToLocal(anchorB);
992
- const jointDef = new box2d.instance.b2RopeJointDef();
993
- jointDef.set_bodyA(objectA.body);
994
- jointDef.set_bodyB(objectB.body);
995
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
996
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
997
- jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
998
- jointDef.set_collideConnected(collide);
999
- super(jointDef);
1000
- }
1001
-
1002
- /** Get the local anchor point relative to objectA's origin
1003
- * @return {Vector2} */
1004
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1005
-
1006
- /** Get the local anchor point relative to objectB's origin
1007
- * @return {Vector2} */
1008
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1009
-
1010
- /** Set the max length of the joint
1011
- * @param {number} length */
1012
- setMaxLength(length) { this.box2dJoint.SetMaxLength(length); }
1013
-
1014
- /** Get the max length of the joint
1015
- * @return {number} */
1016
- getMaxLength() { return this.box2dJoint.GetMaxLength(); }
1017
- }
1018
-
1019
- ///////////////////////////////////////////////////////////////////////////////
1020
- /**
1021
- * Box2D Revolute Joint
1022
- * - Constrains two objects to share a point while they are free to rotate around the point
1023
- * - The relative rotation about the shared point is the joint angle
1024
- * - You can limit the relative rotation with a joint limit
1025
- * - You can use a motor to drive the relative rotation about the shared point
1026
- * - A maximum motor torque is provided so that infinite forces are not generated
1027
- * @extends Box2dJoint
1028
- * @memberof Box2D
1029
- */
1030
- class Box2dRevoluteJoint extends Box2dJoint
1031
- {
1032
- /** Create a revolute joint
1033
- * @param {Box2dObject} objectA
1034
- * @param {Box2dObject} objectB
1035
- * @param {Vector2} anchor
1036
- * @param {boolean} [collide] */
1037
- constructor(objectA, objectB, anchor, collide=false)
1038
- {
1039
- anchor ||= box2d.vec2From(objectB.body.GetPosition());
1040
- const localAnchorA = objectA.worldToLocal(anchor);
1041
- const localAnchorB = objectB.worldToLocal(anchor);
1042
- const jointDef = new box2d.instance.b2RevoluteJointDef();
1043
- jointDef.set_bodyA(objectA.body);
1044
- jointDef.set_bodyB(objectB.body);
1045
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1046
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1047
- jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
1048
- jointDef.set_collideConnected(collide);
1049
- super(jointDef);
1050
- }
1051
-
1052
- /** Get the local anchor point relative to objectA's origin
1053
- * @return {Vector2} */
1054
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1055
-
1056
- /** Get the local anchor point relative to objectB's origin
1057
- * @return {Vector2} */
1058
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1059
-
1060
- /** Get the reference angle, objectB angle minus objectA angle in the reference state
1061
- * @return {number} */
1062
- getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
1063
-
1064
- /** Get the current joint angle
1065
- * @return {number} */
1066
- getJointAngle() { return this.box2dJoint.GetJointAngle(); }
1067
-
1068
- /** Get the current joint angle speed in radians per second
1069
- * @return {number} */
1070
- getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
1071
-
1072
- /** Is the joint limit enabled?
1073
- * @return {boolean} */
1074
- isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
1075
-
1076
- /** Enable/disable the joint limit
1077
- * @param {boolean} [enable] */
1078
- enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
1079
-
1080
- /** Get the lower joint limit
1081
- * @return {number} */
1082
- getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
1083
-
1084
- /** Get the upper joint limit
1085
- * @return {number} */
1086
- getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
1087
-
1088
- /** Set the joint limits
1089
- * @param {number} min
1090
- * @param {number} max */
1091
- setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
1092
-
1093
- /** Is the joint motor enabled?
1094
- * @return {boolean} */
1095
- isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1096
-
1097
- /** Enable/disable the joint motor
1098
- * @param {boolean} [enable] */
1099
- enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
1100
-
1101
- /** Set the motor speed
1102
- * @param {number} speed */
1103
- setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
1104
-
1105
- /** Get the motor speed
1106
- * @return {number} */
1107
- getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
1108
-
1109
- /** Set the motor torque
1110
- * @param {number} torque */
1111
- setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
1112
-
1113
- /** Get the max motor torque
1114
- * @return {number} */
1115
- getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
1116
-
1117
- /** Get the motor torque given a time step
1118
- * @param {number} time
1119
- * @return {number} */
1120
- getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
1121
- }
1122
-
1123
- ///////////////////////////////////////////////////////////////////////////////
1124
- /**
1125
- * Box2D Gear Joint
1126
- * - A gear joint is used to connect two joints together
1127
- * - Either joint can be a revolute or prismatic joint
1128
- * - You specify a gear ratio to bind the motions together
1129
- * @extends Box2dJoint
1130
- * @memberof Box2D
1131
- */
1132
- class Box2dGearJoint extends Box2dJoint
1133
- {
1134
- /** Create a gear joint
1135
- * @param {Box2dObject} objectA
1136
- * @param {Box2dObject} objectB
1137
- * @param {Box2dJoint} joint1
1138
- * @param {Box2dJoint} joint2
1139
- * @param {number} [ratio] */
1140
- constructor(objectA, objectB, joint1, joint2, ratio=1)
1141
- {
1142
- const jointDef = new box2d.instance.b2GearJointDef();
1143
- jointDef.set_bodyA(objectA.body);
1144
- jointDef.set_bodyB(objectB.body);
1145
- jointDef.set_joint1(joint1.box2dJoint);
1146
- jointDef.set_joint2(joint2.box2dJoint);
1147
- jointDef.set_ratio(ratio);
1148
- super(jointDef);
1149
-
1150
- this.joint1 = joint1;
1151
- this.joint2 = joint2;
1152
- }
1153
-
1154
- /** Get the first joint
1155
- * @return {Box2dJoint} */
1156
- getJoint1() { return this.joint1; }
1157
-
1158
- /** Get the second joint
1159
- * @return {Box2dJoint} */
1160
- getJoint2() { return this.joint2; }
1161
-
1162
- /** Set the gear ratio
1163
- * @param {number} ratio */
1164
- setRatio(ratio) { return this.box2dJoint.SetRatio(ratio); }
1165
-
1166
- /** Get the gear ratio
1167
- * @return {number} */
1168
- getRatio() { return this.box2dJoint.GetRatio(); }
1169
- }
1170
-
1171
- ///////////////////////////////////////////////////////////////////////////////
1172
- /**
1173
- * Box2D Prismatic Joint
1174
- * - Provides one degree of freedom: translation along an axis fixed in objectA
1175
- * - Relative rotation is prevented
1176
- * - You can use a joint limit to restrict the range of motion
1177
- * - You can use a joint motor to drive the motion or to model joint friction
1178
- * @extends Box2dJoint
1179
- * @memberof Box2D
1180
- */
1181
- class Box2dPrismaticJoint extends Box2dJoint
1182
- {
1183
- /** Create a prismatic joint
1184
- * @param {Box2dObject} objectA
1185
- * @param {Box2dObject} objectB
1186
- * @param {Vector2} anchor
1187
- * @param {Vector2} worldAxis
1188
- * @param {boolean} [collide] */
1189
- constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
1190
- {
1191
- anchor ||= box2d.vec2From(objectB.body.GetPosition());
1192
- const localAnchorA = objectA.worldToLocal(anchor);
1193
- const localAnchorB = objectB.worldToLocal(anchor);
1194
- const localAxisA = objectA.worldToLocalVector(worldAxis);
1195
- const jointDef = new box2d.instance.b2PrismaticJointDef();
1196
- jointDef.set_bodyA(objectA.body);
1197
- jointDef.set_bodyB(objectB.body);
1198
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1199
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1200
- jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
1201
- jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
1202
- jointDef.set_collideConnected(collide);
1203
- super(jointDef);
1204
- }
1205
-
1206
- /** Get the local anchor point relative to objectA's origin
1207
- * @return {Vector2} */
1208
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1209
-
1210
- /** Get the local anchor point relative to objectB's origin
1211
- * @return {Vector2} */
1212
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1213
-
1214
- /** Get the local joint axis relative to bodyA
1215
- * @return {Vector2} */
1216
- getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
1217
-
1218
- /** Get the reference angle
1219
- * @return {number} */
1220
- getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
1221
-
1222
- /** Get the current joint translation
1223
- * @return {number} */
1224
- getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
1225
-
1226
- /** Get the current joint translation speed
1227
- * @return {number} */
1228
- getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
1229
-
1230
- /** Is the joint limit enabled?
1231
- * @return {boolean} */
1232
- isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
1233
-
1234
- /** Enable/disable the joint limit
1235
- * @param {boolean} [enable] */
1236
- enableLimit(enable=true) { return this.box2dJoint.EnableLimit(enable); }
1237
-
1238
- /** Get the lower joint limit
1239
- * @return {number} */
1240
- getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
1241
-
1242
- /** Get the upper joint limit
1243
- * @return {number} */
1244
- getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
1245
-
1246
- /** Set the joint limits
1247
- * @param {number} min
1248
- * @param {number} max */
1249
- setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
1250
-
1251
- /** Is the motor enabled?
1252
- * @return {boolean} */
1253
- isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1254
-
1255
- /** Enable/disable the joint motor
1256
- * @param {boolean} [enable] */
1257
- enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
1258
-
1259
- /** Set the motor speed
1260
- * @param {number} speed */
1261
- setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
1262
-
1263
- /** Get the motor speed
1264
- * @return {number} */
1265
- getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
1266
-
1267
- /** Set the maximum motor force
1268
- * @param {number} force */
1269
- setMaxMotorForce(force) { return this.box2dJoint.SetMaxMotorForce(force); }
1270
-
1271
- /** Get the maximum motor force
1272
- * @return {number} */
1273
- getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
1274
-
1275
- /** Get the motor force given a time step
1276
- * @param {number} time
1277
- * @return {number} */
1278
- getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
1279
- }
1280
-
1281
- ///////////////////////////////////////////////////////////////////////////////
1282
- /**
1283
- * Box2D Wheel Joint
1284
- * - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
1285
- * - You can use a joint limit to restrict the range of motion
1286
- * - You can use a joint motor to drive the motion or to model joint friction
1287
- * - This joint is designed for vehicle suspensions
1288
- * @extends Box2dJoint
1289
- * @memberof Box2D
1290
- */
1291
- class Box2dWheelJoint extends Box2dJoint
1292
- {
1293
- /** Create a wheel joint
1294
- * @param {Box2dObject} objectA
1295
- * @param {Box2dObject} objectB
1296
- * @param {Vector2} anchor
1297
- * @param {Vector2} worldAxis
1298
- * @param {boolean} [collide] */
1299
- constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
1300
- {
1301
- anchor ||= box2d.vec2From(objectB.body.GetPosition());
1302
- const localAnchorA = objectA.worldToLocal(anchor);
1303
- const localAnchorB = objectB.worldToLocal(anchor);
1304
- const localAxisA = objectA.worldToLocalVector(worldAxis);
1305
- const jointDef = new box2d.instance.b2WheelJointDef();
1306
- jointDef.set_bodyA(objectA.body);
1307
- jointDef.set_bodyB(objectB.body);
1308
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1309
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1310
- jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
1311
- jointDef.set_collideConnected(collide);
1312
- super(jointDef);
1313
- }
1314
-
1315
- /** Get the local anchor point relative to objectA's origin
1316
- * @return {Vector2} */
1317
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1318
-
1319
- /** Get the local anchor point relative to objectB's origin
1320
- * @return {Vector2} */
1321
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1322
-
1323
- /** Get the local joint axis relative to bodyA
1324
- * @return {Vector2} */
1325
- getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
1326
-
1327
- /** Get the current joint translation
1328
- * @return {number} */
1329
- getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
1330
-
1331
- /** Get the current joint translation speed
1332
- * @return {number} */
1333
- getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
1334
-
1335
- /** Is the joint motor enabled?
1336
- * @return {boolean} */
1337
- isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1338
-
1339
- /** Enable/disable the joint motor
1340
- * @param {boolean} [enable] */
1341
- enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
1342
-
1343
- /** Set the motor speed
1344
- * @param {number} speed */
1345
- setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
1346
-
1347
- /** Get the motor speed
1348
- * @return {number} */
1349
- getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
1350
-
1351
- /** Set the maximum motor torque
1352
- * @param {number} torque */
1353
- setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
1354
-
1355
- /** Get the max motor torque
1356
- * @return {number} */
1357
- getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
1358
-
1359
- /** Get the motor torque for a time step
1360
- * @return {number} */
1361
- getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
1362
-
1363
- /** Set the spring frequency in Hertz
1364
- * @param {number} hz */
1365
- setSpringFrequencyHz(hz) { return this.box2dJoint.SetSpringFrequencyHz(hz); }
1366
-
1367
- /** Get the spring frequency in Hertz
1368
- * @return {number} */
1369
- getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
1370
-
1371
- /** Set the spring damping ratio
1372
- * @param {number} ratio */
1373
- setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
1374
-
1375
- /** Get the spring damping ratio
1376
- * @return {number} */
1377
- getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
1378
- }
1379
-
1380
- ///////////////////////////////////////////////////////////////////////////////
1381
- /**
1382
- * Box2D Weld Joint
1383
- * - Glues two objects together
1384
- * @extends Box2dJoint
1385
- * @memberof Box2D
1386
- */
1387
- class Box2dWeldJoint extends Box2dJoint
1388
- {
1389
- /** Create a weld joint
1390
- * @param {Box2dObject} objectA
1391
- * @param {Box2dObject} objectB
1392
- * @param {Vector2} anchor
1393
- * @param {boolean} [collide] */
1394
- constructor(objectA, objectB, anchor, collide=false)
1395
- {
1396
- anchor ||= box2d.vec2From(objectB.body.GetPosition());
1397
- const localAnchorA = objectA.worldToLocal(anchor);
1398
- const localAnchorB = objectB.worldToLocal(anchor);
1399
- const jointDef = new box2d.instance.b2WeldJointDef();
1400
- jointDef.set_bodyA(objectA.body);
1401
- jointDef.set_bodyB(objectB.body);
1402
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1403
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1404
- jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
1405
- jointDef.set_collideConnected(collide);
1406
- super(jointDef);
1407
- }
1408
-
1409
- /** Get the local anchor point relative to objectA's origin
1410
- * @return {Vector2} */
1411
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1412
-
1413
- /** Get the local anchor point relative to objectB's origin
1414
- * @return {Vector2} */
1415
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1416
-
1417
- /** Get the reference angle
1418
- * @return {number} */
1419
- getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
1420
-
1421
- /** Set the frequency in Hertz
1422
- * @param {number} hz */
1423
- setFrequency(hz) { return this.box2dJoint.SetFrequency(hz); }
1424
-
1425
- /** Get the frequency in Hertz
1426
- * @return {number} */
1427
- getFrequency() { return this.box2dJoint.GetFrequency(); }
1428
-
1429
- /** Set the damping ratio
1430
- * @param {number} ratio */
1431
- setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
1432
-
1433
- /** Get the damping ratio
1434
- * @return {number} */
1435
- getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
1436
- }
1437
-
1438
- ///////////////////////////////////////////////////////////////////////////////
1439
- /**
1440
- * Box2D Friction Joint
1441
- * - Used to apply top-down friction
1442
- * - Provides 2D translational friction and angular friction
1443
- * @extends Box2dJoint
1444
- * @memberof Box2D
1445
- */
1446
- class Box2dFrictionJoint extends Box2dJoint
1447
- {
1448
- /** Create a friction joint
1449
- * @param {Box2dObject} objectA
1450
- * @param {Box2dObject} objectB
1451
- * @param {Vector2} anchor
1452
- * @param {boolean} [collide] */
1453
- constructor(objectA, objectB, anchor, collide=false)
1454
- {
1455
- anchor ||= box2d.vec2From(objectB.body.GetPosition());
1456
- const localAnchorA = objectA.worldToLocal(anchor);
1457
- const localAnchorB = objectB.worldToLocal(anchor);
1458
- const jointDef = new box2d.instance.b2FrictionJointDef();
1459
- jointDef.set_bodyA(objectA.body);
1460
- jointDef.set_bodyB(objectB.body);
1461
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1462
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1463
- jointDef.set_collideConnected(collide);
1464
- super(jointDef);
1465
- }
1466
-
1467
- /** Get the local anchor point relative to objectA's origin
1468
- * @return {Vector2} */
1469
- getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1470
-
1471
- /** Get the local anchor point relative to objectB's origin
1472
- * @return {Vector2} */
1473
- getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1474
-
1475
- /** Set the maximum friction force
1476
- * @param {number} force */
1477
- setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
1478
-
1479
- /** Get the maximum friction force
1480
- * @return {number} */
1481
- getMaxForce() { return this.box2dJoint.GetMaxForce(); }
1482
-
1483
- /** Set the maximum friction torque
1484
- * @param {number} torque */
1485
- setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
1486
-
1487
- /** Get the maximum friction torque
1488
- * @return {number} */
1489
- getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
1490
- }
1491
-
1492
- ///////////////////////////////////////////////////////////////////////////////
1493
- /**
1494
- * Box2D Pulley Joint
1495
- * - Connects to two objects and two fixed ground points
1496
- * - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
1497
- * - The force transmitted is scaled by the ratio
1498
- * @extends Box2dJoint
1499
- * @memberof Box2D
1500
- */
1501
- class Box2dPulleyJoint extends Box2dJoint
1502
- {
1503
- /** Create a pulley joint
1504
- * @param {Box2dObject} objectA
1505
- * @param {Box2dObject} objectB
1506
- * @param {Vector2} groundAnchorA
1507
- * @param {Vector2} groundAnchorB
1508
- * @param {Vector2} anchorA
1509
- * @param {Vector2} anchorB
1510
- * @param {number} [ratio]
1511
- * @param {boolean} [collide] */
1512
- constructor(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
1513
- {
1514
- anchorA ||= box2d.vec2From(objectA.body.GetPosition());
1515
- anchorB ||= box2d.vec2From(objectB.body.GetPosition());
1516
- const localAnchorA = objectA.worldToLocal(anchorA);
1517
- const localAnchorB = objectB.worldToLocal(anchorB);
1518
- const jointDef = new box2d.instance.b2PulleyJointDef();
1519
- jointDef.set_bodyA(objectA.body);
1520
- jointDef.set_bodyB(objectB.body);
1521
- jointDef.set_groundAnchorA(box2d.vec2dTo(groundAnchorA));
1522
- jointDef.set_groundAnchorB(box2d.vec2dTo(groundAnchorB));
1523
- jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1524
- jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1525
- jointDef.set_ratio(ratio);
1526
- jointDef.set_lengthA(groundAnchorA.distance(anchorA));
1527
- jointDef.set_lengthB(groundAnchorB.distance(anchorB));
1528
- jointDef.set_collideConnected(collide);
1529
- super(jointDef);
1530
- }
1531
-
1532
- /** Get the first ground anchor
1533
- * @return {Vector2} */
1534
- getGroundAnchorA() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorA()); }
1535
-
1536
- /** Get the second ground anchor
1537
- * @return {Vector2} */
1538
- getGroundAnchorB() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorB()); }
1539
-
1540
- /** Get the current length of the segment attached to objectA
1541
- * @return {number} */
1542
- getLengthA() { return this.box2dJoint.GetLengthA(); }
1543
-
1544
- /** Get the current length of the segment attached to objectB
1545
- * @return {number} */
1546
- getLengthB(){ return this.box2dJoint.GetLengthB(); }
1547
-
1548
- /** Get the pulley ratio
1549
- * @return {number} */
1550
- getRatio() { return this.box2dJoint.GetRatio(); }
1551
-
1552
- /** Get the current length of the segment attached to objectA
1553
- * @return {number} */
1554
- getCurrentLengthA() { return this.box2dJoint.GetCurrentLengthA(); }
1555
-
1556
- /** Get the current length of the segment attached to objectB
1557
- * @return {number} */
1558
- getCurrentLengthB() { return this.box2dJoint.GetCurrentLengthB(); }
1559
- }
1560
-
1561
- ///////////////////////////////////////////////////////////////////////////////
1562
- /**
1563
- * Box2D Motor Joint
1564
- * - Controls the relative motion between two objects
1565
- * - Typical usage is to control the movement of a object with respect to the ground
1566
- * @extends Box2dJoint
1567
- * @memberof Box2D
1568
- */
1569
- class Box2dMotorJoint extends Box2dJoint
1570
- {
1571
- /** Create a motor joint
1572
- * @param {Box2dObject} objectA
1573
- * @param {Box2dObject} objectB */
1574
- constructor(objectA, objectB)
1575
- {
1576
- const linearOffset = objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));
1577
- const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
1578
- const jointDef = new box2d.instance.b2MotorJointDef();
1579
- jointDef.set_bodyA(objectA.body);
1580
- jointDef.set_bodyB(objectB.body);
1581
- jointDef.set_linearOffset(box2d.vec2dTo(linearOffset));
1582
- jointDef.set_angularOffset(angularOffset);
1583
- super(jointDef);
1584
- }
1585
-
1586
- /** Set the target linear offset, in frame A, in meters.
1587
- * @param {Vector2} offset */
1588
- setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2d.vec2dTo(offset)); }
1589
-
1590
- /** Get the target linear offset, in frame A, in meters.
1591
- * @return {Vector2} */
1592
- getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
1593
-
1594
- /** Set the target angular offset
1595
- * @param {number} offset */
1596
- setAngularOffset(offset) { this.box2dJoint.SetAngularOffset(offset); }
1597
-
1598
- /** Get the target angular offset
1599
- * @return {number} */
1600
- getAngularOffset() { return this.box2dJoint.GetAngularOffset(); }
1601
-
1602
- /** Set the maximum friction force
1603
- * @param {number} force */
1604
- setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
1605
-
1606
- /** Get the maximum friction force
1607
- * @return {number} */
1608
- getMaxForce() { return this.box2dJoint.GetMaxForce(); }
1609
-
1610
- /** Set the maximum torque
1611
- * @param {number} torque */
1612
- setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
1613
-
1614
- /** Get the maximum torque
1615
- * @return {number} */
1616
- getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
1617
-
1618
- /** Set the position correction factor in the range [0,1]
1619
- * @param {number} factor */
1620
- setCorrectionFactor(factor) { this.box2dJoint.SetCorrectionFactor(factor); }
1621
-
1622
- /** Get the position correction factor in the range [0,1]
1623
- * @return {number} */
1624
- getCorrectionFactor() { return this.box2dJoint.GetCorrectionFactor(); }
1625
- }
1626
-
1627
- ///////////////////////////////////////////////////////////////////////////////
1628
- /**
1629
- * Box2D Global Object
1630
- * - Wraps Box2d world and provides global functions
1631
- * @memberof Box2D
1632
- */
1633
- class Box2dPlugin
1634
- {
1635
- /** Create the global UI system object
1636
- * @param {Object} instance */
1637
- constructor(instance)
1638
- {
1639
- ASSERT(!box2d, 'Box2D already initialized');
1640
- box2d = this;
1641
-
1642
- /** @property {Object} - The Box2d instance */
1643
- this.instance = instance;
1644
- /** @property {Object} - The Box2d world */
1645
- this.world = new box2d.instance.b2World();
1646
- /** @property {Array<Box2dObject>} - List of all Box2d objects */
1647
- this.objects = [];
1648
- /** @property {number} - Velocity iterations per update*/
1649
- this.velocityIterations = 8;
1650
- /** @property {number} - Position iterations per update*/
1651
- this.positionIterations = 3;
1652
- /** @property {number} - Static, zero mass, zero velocity, may be manually moved */
1653
- this.bodyTypeStatic = instance.b2_staticBody;
1654
- /** @property {number} - Kinematic, zero mass, non-zero velocity set by user, moved by solver */
1655
- this.bodyTypeKinematic = instance.b2_kinematicBody;
1656
- /** @property {number} - Dynamic, positive mass, non-zero velocity determined by forces, moved by solver */
1657
- this.bodyTypeDynamic = instance.b2_dynamicBody;
1658
-
1659
- // setup contact listener
1660
- const listener = new box2d.instance.JSContactListener();
1661
- listener.BeginContact = function(contactPtr)
1662
- {
1663
- const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
1664
- const fixtureA = contact.GetFixtureA();
1665
- const fixtureB = contact.GetFixtureB();
1666
- const objectA = fixtureA.GetBody().object;
1667
- const objectB = fixtureB.GetBody().object;
1668
- // raw user-created b2Bodies may have no .object — skip those
1669
- if (!objectA || !objectB) return;
1670
- objectA.beginContact(objectB);
1671
- objectB.beginContact(objectA);
1672
- }
1673
- listener.EndContact = function(contactPtr)
1674
- {
1675
- const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
1676
- const fixtureA = contact.GetFixtureA();
1677
- const fixtureB = contact.GetFixtureB();
1678
- const objectA = fixtureA.GetBody().object;
1679
- const objectB = fixtureB.GetBody().object;
1680
- if (!objectA || !objectB) return;
1681
- objectA.endContact(objectB);
1682
- objectB.endContact(objectA);
1683
- };
1684
- listener.PreSolve = function() {};
1685
- listener.PostSolve = function() {};
1686
- box2d.world.SetContactListener(listener);
1687
- }
1688
-
1689
- /** Step the physics world simulation
1690
- * @param {number} [frames] */
1691
- step(frames=1)
1692
- {
1693
- box2d.world.SetGravity(box2d.vec2dTo(gravity));
1694
- for (let i=frames; i--;)
1695
- box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
1696
- }
1697
-
1698
- ///////////////////////////////////////////////////////////////////////////////
1699
- // raycasting and querying
1700
-
1701
- /** raycast and return a list of all the results
1702
- * @param {Vector2} start
1703
- * @param {Vector2} end */
1704
- raycastAll(start, end)
1705
- {
1706
- const raycastCallback = new box2d.instance.JSRayCastCallback();
1707
- raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
1708
- {
1709
- const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1710
- point = box2d.vec2FromPointer(point);
1711
- normal = box2d.vec2FromPointer(normal);
1712
- raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
1713
- return 1; // continue getting results
1714
- };
1715
-
1716
- const raycastResults = [];
1717
- box2d.world.RayCast(raycastCallback, box2d.vec2dTo(start), box2d.vec2dTo(end));
1718
- debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02);
1719
- return raycastResults;
1720
- }
1721
-
1722
- /** raycast and return the first result
1723
- * @param {Vector2} start
1724
- * @param {Vector2} end */
1725
- raycast(start, end)
1726
- {
1727
- const raycastResults = box2d.raycastAll(start, end);
1728
- if (!raycastResults.length)
1729
- return undefined;
1730
- return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b);
1731
- }
1732
-
1733
- /** box aabb cast and return all the objects
1734
- * @param {Vector2} pos
1735
- * @param {Vector2} size */
1736
- boxCastAll(pos, size)
1737
- {
1738
- const queryCallback = new box2d.instance.JSQueryCallback();
1739
- queryCallback.ReportFixture = function(fixturePointer)
1740
- {
1741
- const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1742
- const o = fixture.GetBody().object;
1743
- if (!queryObjects.includes(o))
1744
- queryObjects.push(o); // add if not already in list
1745
- return true; // continue getting results
1746
- };
1747
-
1748
- const aabb = new box2d.instance.b2AABB();
1749
- aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
1750
- aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
1751
-
1752
- let queryObjects = [];
1753
- box2d.world.QueryAABB(queryCallback, aabb);
1754
- debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', .02);
1755
- return queryObjects;
1756
- }
1757
-
1758
- /** box aabb cast and return the first object
1759
- * @param {Vector2} pos
1760
- * @param {Vector2} size */
1761
- boxCast(pos, size)
1762
- {
1763
- const queryCallback = new box2d.instance.JSQueryCallback();
1764
- queryCallback.ReportFixture = function(fixturePointer)
1765
- {
1766
- const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1767
- queryObject = fixture.GetBody().object;
1768
- return false; // stop getting results
1769
- };
1770
-
1771
- const aabb = new box2d.instance.b2AABB();
1772
- aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
1773
- aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
1774
-
1775
- let queryObject;
1776
- box2d.world.QueryAABB(queryCallback, aabb);
1777
- debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', .02);
1778
- return queryObject;
1779
- }
1780
-
1781
- /** circle cast and return all the objects
1782
- * @param {Vector2} pos
1783
- * @param {number} diameter */
1784
- circleCastAll(pos, diameter)
1785
- {
1786
- const radius2 = (diameter/2)**2;
1787
- const results = box2d.boxCastAll(pos, vec2(diameter));
1788
- return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
1789
- }
1790
-
1791
- /** circle cast and return the first object
1792
- * @param {Vector2} pos
1793
- * @param {number} diameter */
1794
- circleCast(pos, diameter)
1795
- {
1796
- const radius2 = (diameter/2)**2;
1797
- let results = box2d.boxCastAll(pos, vec2(diameter));
1798
-
1799
- let bestResult, bestDistance2;
1800
- for (const result of results)
1801
- {
1802
- const distance2 = result.pos.distanceSquared(pos);
1803
- if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2))
1804
- {
1805
- bestResult = result;
1806
- bestDistance2 = distance2;
1807
- }
1808
- }
1809
- return bestResult;
1810
- }
1811
-
1812
- /** point cast and return the first object
1813
- * @param {Vector2} pos
1814
- * @param {boolean} dynamicOnly */
1815
- pointCast(pos, dynamicOnly=true)
1816
- {
1817
- const queryCallback = new box2d.instance.JSQueryCallback();
1818
- queryCallback.ReportFixture = function(fixturePointer)
1819
- {
1820
- const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1821
- if (dynamicOnly && fixture.GetBody().GetType() !== box2d.instance.b2_dynamicBody)
1822
- return true; // continue getting results
1823
- if (!fixture.TestPoint(box2d.vec2dTo(pos)))
1824
- return true; // continue getting results
1825
- queryObject = fixture.GetBody().object;
1826
- return false; // stop getting results
1827
- };
1828
-
1829
- const aabb = new box2d.instance.b2AABB();
1830
- aabb.set_lowerBound(box2d.vec2dTo(pos));
1831
- aabb.set_upperBound(box2d.vec2dTo(pos));
1832
-
1833
- let queryObject;
1834
- box2d.world.QueryAABB(queryCallback, aabb);
1835
- debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
1836
- return queryObject;
1837
- }
1838
-
1839
- ///////////////////////////////////////////////////////////////////////////////
1840
- // drawing
1841
-
1842
- /** draws a fixture
1843
- * @param {Object} fixture
1844
- * @param {Vector2} pos
1845
- * @param {number} angle
1846
- * @param {Color} [color]
1847
- * @param {Color} [lineColor]
1848
- * @param {number} [lineWidth]
1849
- * @param {boolean} [useWebGL=glEnable]
1850
- * @param {CanvasRenderingContext2D} [context] */
1851
- drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
1852
- {
1853
- const shape = box2d.castShapeObject(fixture.GetShape());
1854
- switch (shape.GetType())
1855
- {
1856
- case box2d.instance.b2Shape.e_polygon:
1857
- {
1858
- let points = [];
1859
- for (let i=shape.GetVertexCount(); i--;)
1860
- points.push(box2d.vec2From(shape.GetVertex(i)));
1861
- drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
1862
- break;
1863
- }
1864
- case box2d.instance.b2Shape.e_circle:
1865
- {
1866
- const radius = shape.get_m_radius();
1867
- drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebGL, false, context);
1868
- break;
1869
- }
1870
- case box2d.instance.b2Shape.e_edge:
1871
- {
1872
- const v1 = box2d.vec2From(shape.get_m_vertex1());
1873
- const v2 = box2d.vec2From(shape.get_m_vertex2());
1874
- drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
1875
- break;
1876
- }
1877
- }
1878
- }
1879
-
1880
- ///////////////////////////////////////////////////////////////////////////////
1881
- // helper functions
1882
-
1883
- /** converts a box2d vec2 to a Vector2
1884
- * @param {Object} v */
1885
- vec2From(v)
1886
- {
1887
- ASSERT(v instanceof box2d.instance.b2Vec2);
1888
- return new Vector2(v.get_x(), v.get_y());
1889
- }
1890
-
1891
- /** converts a box2d vec2 pointer to a Vector2
1892
- * @param {Object} vp */
1893
- vec2FromPointer(vp)
1894
- {
1895
- const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
1896
- return box2d.vec2From(v);
1897
- }
1898
-
1899
- /** converts a Vector2 to a box2 vec2
1900
- * @param {Vector2} v */
1901
- vec2dTo(v)
1902
- {
1903
- ASSERT(isVector2(v));
1904
- return new box2d.instance.b2Vec2(v.x, v.y);
1905
- }
1906
-
1907
- /** checks if a box2d object is null
1908
- * @param {Object} o */
1909
- isNull(o) { return !box2d.instance.getPointer(o); }
1910
-
1911
- /** casts a box2d object to a shape type
1912
- * @param {Object} o */
1913
- castShapeObject(o)
1914
- {
1915
- switch (o.GetType())
1916
- {
1917
- case box2d.instance.b2Shape.e_circle:
1918
- return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
1919
- case box2d.instance.b2Shape.e_edge:
1920
- return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
1921
- case box2d.instance.b2Shape.e_polygon:
1922
- return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
1923
- case box2d.instance.b2Shape.e_chain:
1924
- return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
1925
- }
1926
-
1927
- ASSERT(false, 'Unknown box2d object type');
1928
- }
1929
-
1930
- /** casts a box2d object to a joint type
1931
- * @param {Object} o */
1932
- castJointObject(o)
1933
- {
1934
- switch (o.GetType())
1935
- {
1936
- case box2d.instance.e_revoluteJoint:
1937
- return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
1938
- case box2d.instance.e_prismaticJoint:
1939
- return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
1940
- case box2d.instance.e_distanceJoint:
1941
- return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
1942
- case box2d.instance.e_pulleyJoint:
1943
- return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
1944
- case box2d.instance.e_mouseJoint:
1945
- return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
1946
- case box2d.instance.e_gearJoint:
1947
- return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
1948
- case box2d.instance.e_wheelJoint:
1949
- return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
1950
- case box2d.instance.e_weldJoint:
1951
- return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
1952
- case box2d.instance.e_frictionJoint:
1953
- return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
1954
- case box2d.instance.e_ropeJoint:
1955
- return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
1956
- case box2d.instance.e_motorJoint:
1957
- return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
1958
- }
1959
-
1960
- ASSERT(false, 'Unknown box2d object type');
1961
- }
1962
- }
1963
-
1964
- ///////////////////////////////////////////////////////////////////////////////
1965
- /** Box2d Init - Call with await to init box2d
1966
- * @example
1967
- * await box2dInit();
1968
- * @return {Promise<Box2dPlugin>}
1969
- * @memberof Box2D */
1970
- async function box2dInit()
1971
- {
1972
- // load box2d
1973
- new Box2dPlugin(await Box2D());
1974
- setupDebugDraw();
1975
- engineAddPlugin(box2dUpdate, box2dRender);
1976
- return box2d;
1977
-
1978
- // add the box2d plugin to the engine
1979
- function box2dUpdate()
1980
- {
1981
- if (paused)
1982
- return;
1983
-
1984
- box2d.step();
1985
-
1986
- // remove destroyed objects
1987
- box2d.objects = box2d.objects.filter(o=>!o.destroyed);
1988
-
1989
- // copy box2d physics results to engine objects
1990
- for (const o of box2d.objects)
1991
- {
1992
- if (o.body)
1993
- {
1994
- // box2d uses reverse angle
1995
- o.pos = box2d.vec2From(o.body.GetPosition());
1996
- o.angle = -o.body.GetAngle();
1997
- }
1998
- }
1999
- }
2000
- function box2dRender()
2001
- {
2002
- if (box2dDebug || debugPhysics)
2003
- box2d.world.DrawDebugData();
2004
- }
2005
-
2006
- // box2d debug drawing
2007
- function setupDebugDraw()
2008
- {
2009
- // setup debug draw
2010
- const debugLineWidth = .1;
2011
- const debugDraw = new box2d.instance.JSDraw();
2012
- const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
2013
- const box2dColorPointer = (c)=>
2014
- box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
2015
- const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
2016
- const getPointsList = (vertices, vertexCount)=>
2017
- {
2018
- const points = [];
2019
- for (let i=vertexCount; i--;)
2020
- points.push(box2d.vec2FromPointer(vertices+i*8));
2021
- return points;
2022
- }
2023
- debugDraw.DrawSegment = function(point1, point2, color)
2024
- {
2025
- color = getDebugColor(color);
2026
- point1 = box2d.vec2FromPointer(point1);
2027
- point2 = box2d.vec2FromPointer(point2);
2028
- drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false);
2029
- };
2030
- debugDraw.DrawPolygon = function(vertices, vertexCount, color)
2031
- {
2032
- color = getDebugColor(color);
2033
- const points = getPointsList(vertices, vertexCount);
2034
- drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false);
2035
- };
2036
- debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
2037
- {
2038
- color = getDebugColor(color);
2039
- const points = getPointsList(vertices, vertexCount);
2040
- drawPoly(points, color, 0, color, vec2(), 0, false);
2041
- };
2042
- debugDraw.DrawCircle = function(center, radius, color)
2043
- {
2044
- color = getDebugColor(color);
2045
- center = box2d.vec2FromPointer(center);
2046
- drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false);
2047
- };
2048
- debugDraw.DrawSolidCircle = function(center, radius, axis, color)
2049
- {
2050
- color = getDebugColor(color);
2051
- center = box2d.vec2FromPointer(center);
2052
- axis = box2d.vec2FromPointer(axis).scale(radius);
2053
- drawCircle(center, radius*2, color, debugLineWidth, color, false);
2054
- drawLine(vec2(), axis, debugLineWidth, color, center, 0, false);
2055
- };
2056
- debugDraw.DrawTransform = function(transform)
2057
- {
2058
- transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
2059
- const pos = box2d.vec2From(transform.get_p());
2060
- const angle = -transform.get_q().GetAngle();
2061
- const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
2062
- const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
2063
- drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false);
2064
- drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false);
2065
- }
2066
-
2067
- debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
2068
- debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
2069
- //debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
2070
- //debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
2071
- //debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
2072
- box2d.world.SetDebugDraw(debugDraw);
2073
- }
1
+ /**
2
+ * LittleJS Box2D Physics Plugin
3
+ * - Box2dObject extends EngineObject with Box2D physics
4
+ * - Call box2dInit() to enable
5
+ * - You will also need to include box2d.wasm.js
6
+ * - Uses a super fast web assembly port of Box2D v2.3.1
7
+ * - More info: https://github.com/kripken/box2d.js
8
+ * - Functions to create polygon, circle, and edge shapes
9
+ * - Contact begin and end callbacks
10
+ * - Wraps b2Vec2 type to/from Vector2
11
+ * - Raycasting and querying
12
+ * - Box2dTileLayer for grid based collision
13
+ * - Every type of joint
14
+ * - Debug physics drawing
15
+ * - Box2D works per second: its velocities and accelerations are in units per second, and it reads the engine's
16
+ * gravity as units per second squared, where an EngineObject's are per frame
17
+ * @namespace Box2D
18
+ */
19
+
20
+ 'use strict';
21
+
22
+ /** Global Box2d Plugin object
23
+ * @type {Box2dPlugin}
24
+ * @memberof Box2D */
25
+ let box2d;
26
+
27
+ /** Enable Box2D debug drawing
28
+ * @type {boolean}
29
+ * @default
30
+ * @memberof Box2D */
31
+ let box2dDebug = false;
32
+
33
+ // Box2D copies every vector it is given, so the plugin hands it these two instead of making a new one each call,
34
+ // which the binding would keep; a call that takes two vectors uses both
35
+ let box2dTempVectors;
36
+ function box2dTemp(v, slot=0)
37
+ {
38
+ ASSERT(isVector2(v));
39
+ const temp = (box2dTempVectors ||= [new box2d.instance.b2Vec2(), new box2d.instance.b2Vec2()])[slot];
40
+ temp.Set(v.x, v.y);
41
+ return temp;
42
+ }
43
+
44
+ // the native objects a query needs, one of each kind made once and reused, since the binding keeps every one made;
45
+ // a query sets the callback's ReportFixture before each use, so the one callback serves every query of its kind
46
+ const box2dQueryObjects = {};
47
+ const box2dGravity = {x:NaN, y:NaN}; // the gravity the world was last given
48
+ function box2dQueryObject(key, type) { return box2dQueryObjects[key] ||= new box2d.instance[type](); }
49
+
50
+ // Box2D finds fixtures by boxes it pads and stretches ahead along the velocity, so a query checks the shape's own box
51
+ function box2dFixtureOverlaps(fixture, aabb)
52
+ {
53
+ const shape = fixture.GetShape(), transform = fixture.GetBody().GetTransform();
54
+ const shapeBox = box2dQueryObject('shapeAABB', 'b2AABB');
55
+ const lower = aabb.get_lowerBound(), upper = aabb.get_upperBound();
56
+ for (let i = shape.GetChildCount(); i--;)
57
+ {
58
+ shape.ComputeAABB(shapeBox, transform, i);
59
+ const a = shapeBox.get_lowerBound(), b = shapeBox.get_upperBound();
60
+ if (a.get_x() <= upper.get_x() && b.get_x() >= lower.get_x() &&
61
+ a.get_y() <= upper.get_y() && b.get_y() >= lower.get_y())
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ // the points of an edge list or loop, without one that repeats the one before, or a loop's closing repeat of the first;
68
+ // an edge of no length gives its neighbors a ghost vertex on their own end, and bodies fall through them
69
+ function box2dEdgePoints(points, loop)
70
+ {
71
+ // each point is kept if it is far enough from the last one kept, so a densely sampled curve keeps every few of
72
+ // its points rather than losing them all
73
+ const slop2 = .005**2; // Box2D's linear slop, what its own chain shape asserts consecutive points are apart
74
+ const kept = [];
75
+ for (const p of points)
76
+ if (!kept.length || p.distanceSquared(kept[kept.length-1]) > slop2)
77
+ kept.push(p);
78
+ while (loop && kept.length > 1 && kept[kept.length-1].distanceSquared(kept[0]) <= slop2)
79
+ kept.pop();
80
+ return kept;
81
+ }
82
+
83
+ // wake a body and whatever touches it, Box2D does not when a body is moved, and a sleeping pair never updates
84
+ function box2dWakeWithContacts(body)
85
+ {
86
+ body.SetAwake(true);
87
+ for (let edge = body.GetContactList(); !box2d.isNull(edge); edge = edge.get_next())
88
+ edge.get_other().SetAwake(true);
89
+ }
90
+
91
+ // wake both bodies of a joint whose length, spring, ratio or strength changed, a sleeping body would stay where it was
92
+ function box2dWakeJoint(joint)
93
+ {
94
+ joint.GetBodyA().SetAwake(true);
95
+ joint.GetBodyB().SetAwake(true);
96
+ }
97
+
98
+ // what Box2D adds to the inertia for a center of mass away from the origin, in float32 as it does it, so taking it
99
+ // off again gives exactly what Box2D keeps: an inertia of 0, a locked rotation, stays 0 and not a speck either side
100
+ function box2dCenterInertia(mass, x, y)
101
+ {
102
+ const f = Math.fround;
103
+ return f(f(mass)*f(f(f(x)*f(x)) + f(f(y)*f(y))));
104
+ }
105
+
106
+ // what cannot happen while the world steps, like losing a body from a contact callback: done now, or queued until
107
+ // the step is done; a body going calls endContact for what it touched, and a destroy from there waits in the queue
108
+ // too, since the contact it would free is still in use, so the queue runs one at a time in the order things came
109
+ const box2dPending = [];
110
+ let box2dPendingBusy = 0;
111
+ function box2dWhenUnlocked(f)
112
+ {
113
+ box2dPending.push(f);
114
+ if (!box2d.world.IsLocked() && !box2dPendingBusy)
115
+ box2dRunPending();
116
+ }
117
+ function box2dRunPending()
118
+ {
119
+ ++box2dPendingBusy;
120
+ try { while (box2dPending.length) box2dPending.shift()(); }
121
+ finally { --box2dPendingBusy; }
122
+ }
123
+
124
+ // each Box2dJoint by its native pointer, so a joint Box2D destroys along with a body can let go of its wrapper
125
+ const box2dJoints = new Map;
126
+
127
+ // the gear joints, kept apart from the others, since every joint that goes looks through them
128
+ const box2dGearJoints = new Set;
129
+
130
+ // a gear joint keeps pointers to the joints it gears and to their bodies, so it goes before either joint does
131
+ function box2dDestroyGears(joint)
132
+ {
133
+ for (const gear of box2dGearJoints)
134
+ {
135
+ if (!gear.box2dJoint)
136
+ box2dGearJoints.delete(gear); // gone already, with a body
137
+ else if (gear.joint1 === joint || gear.joint2 === joint)
138
+ {
139
+ box2dGearJoints.delete(gear);
140
+ gear.destroy();
141
+ }
142
+ }
143
+ }
144
+
145
+ /** Enable Box2D debug drawing
146
+ * @param {boolean} enable
147
+ * @memberof Box2D */
148
+ function box2dSetDebug(enable) { box2dDebug = enable; }
149
+
150
+ ///////////////////////////////////////////////////////////////////////////////
151
+ /**
152
+ * Box2D Object - extend with your own custom physics objects
153
+ * - A LittleJS object with Box2D physics, dynamic by default
154
+ * - Provides interface for Box2D body and fixture functions
155
+ * - Each object can have multiple fixtures and joints
156
+ * - Angular values are clockwise like angle: angular velocity, torque, joint angles, limits and motor speeds
157
+ * @extends EngineObject
158
+ * @memberof Box2D
159
+ */
160
+ class Box2dObject extends EngineObject
161
+ {
162
+ /** Create a LittleJS object with Box2d physics
163
+ * @param {Vector2} [pos]
164
+ * @param {Vector2} [size]
165
+ * @param {TileInfo} [tileInfo]
166
+ * @param {number} [angle]
167
+ * @param {Color} [color]
168
+ * @param {number} [bodyType]
169
+ * @param {number} [renderOrder] */
170
+ constructor(pos=vec2(), size=vec2(), tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
171
+ {
172
+ ASSERT(!box2d.world.IsLocked(), 'cannot create Box2D bodies during a contact callback');
173
+ super(pos, size, tileInfo, angle, color, renderOrder);
174
+
175
+ // create physics body, Box2D copies the def
176
+ const bodyDef = new box2d.instance.b2BodyDef();
177
+ bodyDef.set_type(bodyType);
178
+ bodyDef.set_position(box2dTemp(pos));
179
+ bodyDef.set_angle(-angle);
180
+
181
+ /** @property {Object} - The Box2d body, undefined once it is destroyed */
182
+ this.body = box2d.world.CreateBody(bodyDef);
183
+ box2d.instance.destroy(bodyDef);
184
+ /** @property {Color} - Line color used for default box2d drawing */
185
+ this.lineColor = BLACK.copy();
186
+ /** @property {number} - Line width used for default box2d drawing */
187
+ this.lineWidth = .1;
188
+ /** @property {Array<Array<Vector2>>} - List of all edges for default box2d drawing
189
+ * @type {Array<Array<Vector2>>} */
190
+ this.edgeLists = [];
191
+ /** @property {Array<Array<Vector2>>} - List of all edge loops for default box2d drawing
192
+ * @type {Array<Array<Vector2>>} */
193
+ this.edgeLoops = [];
194
+ // the fixtures of the edge lists and loops, by pointer, each to the points it is drawn with
195
+ this.edgeListFixtures = new Map;
196
+
197
+ this.body.object = this; // link body to this object
198
+ box2d.objects.push(this); // keep track of all box2d objects
199
+ }
200
+
201
+ /** Destroy this object and its physics body
202
+ * @param {boolean} [immediate] - Remove it now, as EngineObject.destroy does, children included */
203
+ destroy(immediate=false)
204
+ {
205
+ if (this.destroyed) return;
206
+
207
+ // destroy physics body, fixtures, and joints; from a contact callback the world is still
208
+ // stepping and cannot lose a body, so it goes as soon as the step is done; the object
209
+ // leaves box2d.objects at the next step, or the next frame while paused or time is stopped;
210
+ // the body lets go of it after, since destroying it calls endContact, which finds it there;
211
+ // that endContact can destroy this same object again, before it is marked destroyed, so the body is
212
+ // destroyed only by the call that still finds it, Box2D would lose count of its bodies on a second
213
+ ASSERT(this.body, 'Box2dObject has no body to destroy');
214
+ const body = this.body;
215
+ box2dWhenUnlocked(()=>
216
+ {
217
+ if (this.body !== body) return; // destroyed already
218
+ box2d.world.DestroyBody(body);
219
+ body.object = undefined;
220
+ this.body = undefined;
221
+ });
222
+ super.destroy(immediate);
223
+ }
224
+
225
+ /** Box2d objects updated with Box2d world step */
226
+ updatePhysics() {}
227
+
228
+ /** Update the object transform, called automatically by engine even when paused;
229
+ * its body places it, so it can be a parent but not a child, connect it to another with a joint
230
+ * @param {boolean} [updateChildren] - Also update the children's transforms */
231
+ updateTransforms(updateChildren=true)
232
+ {
233
+ ASSERT(!this.parent, 'a Box2dObject cannot be a child, its body would stay behind, connect it with a joint');
234
+ super.updateTransforms(updateChildren);
235
+ }
236
+
237
+ /** Render the object, uses box2d drawing if no tile info exists */
238
+ render()
239
+ {
240
+ // use default render or draw fixtures
241
+ if (this.tileInfo)
242
+ super.render();
243
+ else
244
+ this.drawFixtures(this.color, this.lineColor, this.lineWidth);
245
+ }
246
+
247
+ /** Render debug info */
248
+ renderDebugInfo()
249
+ {
250
+ const isAsleep = !this.getIsAwake();
251
+ const isStatic = this.getBodyType() === box2d.bodyTypeStatic;
252
+ const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
253
+ this.drawFixtures(color);
254
+ }
255
+
256
+ /** Draws all this object's fixtures
257
+ * @param {Color} [color]
258
+ * @param {Color} [lineColor]
259
+ * @param {number} [lineWidth]
260
+ * @param {boolean} [useWebGL=glEnable]
261
+ * @param {CanvasRenderingContext2D} [context] */
262
+ drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
263
+ {
264
+ // draw each fixture, but the edges of an edge list or loop, which draw below as one line
265
+ const edgeFixtures = this.edgeListFixtures;
266
+ this.getFixtureList().forEach((fixture)=>
267
+ {
268
+ if (!edgeFixtures.has(box2d.instance.getPointer(fixture)))
269
+ box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
270
+ });
271
+
272
+ // draw edges using a single draw line for better connections
273
+ this.edgeLists.forEach(points=>
274
+ drawLineList(points, lineWidth, lineColor, false, this.pos, this.angle, useWebGL, false, context));
275
+ this.edgeLoops.forEach(points=>
276
+ drawLineList(points, lineWidth, lineColor, true, this.pos, this.angle, useWebGL, false, context));
277
+ }
278
+
279
+ ///////////////////////////////////////////////////////////////////////////////
280
+ // physics contact callbacks
281
+
282
+ /** Called when a contact begins, while the world steps: a destroy or a setter waits until the step is done,
283
+ * and creating objects, fixtures or joints must wait until after the step
284
+ * - The fixtures say which shapes touched, the same objects addBox and the others returned, so a small sensor
285
+ * under a player's feet can tell standing on the ground from touching a wall
286
+ * @param {Box2dObject} otherObject
287
+ * @param {Object} [fixture] - This object's fixture that touched
288
+ * @param {Object} [otherFixture] - The other object's fixture that touched */
289
+ beginContact(otherObject, fixture, otherFixture) {}
290
+
291
+ /** Called when a contact ends, while the world steps or a body is destroyed: a destroy or a setter waits
292
+ * until the step is done, and creating objects, fixtures or joints must wait until after the step
293
+ * @param {Box2dObject} otherObject
294
+ * @param {Object} [fixture] - This object's fixture that touched
295
+ * @param {Object} [otherFixture] - The other object's fixture that touched */
296
+ endContact(otherObject, fixture, otherFixture) {}
297
+
298
+ ///////////////////////////////////////////////////////////////////////////////
299
+ // physics fixtures and shapes
300
+
301
+ /** Add a shape fixture to the body
302
+ * @param {Object} shape
303
+ * @param {number} [density]
304
+ * @param {number} [friction]
305
+ * @param {number} [restitution]
306
+ * @param {boolean} [isSensor] */
307
+ addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
308
+ {
309
+ ASSERT(isNumber(density), 'density must be a number');
310
+ ASSERT(isNumber(friction), 'friction must be a number');
311
+ ASSERT(isNumber(restitution), 'restitution must be a number');
312
+ ASSERT(!box2d.world.IsLocked(), 'cannot create Box2D fixtures during a contact callback');
313
+
314
+ // Box2D copies the def and the shape
315
+ const fd = new box2d.instance.b2FixtureDef();
316
+ fd.set_shape(shape);
317
+ fd.set_density(density);
318
+ fd.set_friction(friction);
319
+ fd.set_restitution(restitution);
320
+ fd.set_isSensor(isSensor);
321
+ const fixture = this.body.CreateFixture(fd);
322
+ box2d.instance.destroy(fd);
323
+ return fixture;
324
+ }
325
+
326
+ /** Add a box shape to the body
327
+ * @param {Vector2} [size]
328
+ * @param {Vector2} [offset]
329
+ * @param {number} [angle] - LittleJS convention (clockwise positive).
330
+ * Negated internally to match Box2D's CCW-positive convention so the
331
+ * fixture aligns with the same angle passed to drawRect/drawTile.
332
+ * @param {number} [density]
333
+ * @param {number} [friction]
334
+ * @param {number} [restitution]
335
+ * @param {boolean} [isSensor] */
336
+ addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
337
+ {
338
+ ASSERT(isVector2(size), 'size must be a Vector2');
339
+ ASSERT(size.x > 0 && size.y > 0, 'size must be positive');
340
+ ASSERT(isVector2(offset), 'offset must be a Vector2');
341
+ ASSERT(isNumber(angle), 'angle must be a number');
342
+
343
+ // Box2D stops for good on a box with almost no area, like addPoly no fixture is made from one
344
+ ASSERT(size.x * size.y > 1e-6, 'box is too small for Box2D');
345
+ if (!(size.x * size.y > 1e-6)) return;
346
+
347
+ const shape = new box2d.instance.b2PolygonShape();
348
+ shape.SetAsBox(size.x/2, size.y/2, box2dTemp(offset), -angle);
349
+ const fixture = this.addShape(shape, density, friction, restitution, isSensor);
350
+ box2d.instance.destroy(shape); // the fixture has its own copy
351
+ return fixture;
352
+ }
353
+
354
+ /** Add a polygon shape to the body, the convex hull of its points; Box2D takes 3 to 8 points,
355
+ * not all in a line, and no fixture is made from any other; points closer than .001 count as one
356
+ * @param {Array<Vector2>} points
357
+ * @param {number} [density]
358
+ * @param {number} [friction]
359
+ * @param {number} [restitution]
360
+ * @param {boolean} [isSensor] */
361
+ addPoly(points, density, friction, restitution, isSensor)
362
+ {
363
+ ASSERT(isArray(points), 'points must be an array');
364
+
365
+ function box2dCreatePolygonShape(points)
366
+ {
367
+ // Box2D stops for good on two hull points that nearly meet, like a loop whose last point is its first
368
+ points = points.filter((p, i)=> points.slice(0, i).every(q=> p.distanceSquared(q) > 1e-6));
369
+
370
+ // Box2D stops for good on a polygon it cannot take, so one with too many points or no area makes none;
371
+ // it takes the convex hull, which is at least as big as the biggest triangle of the points
372
+ let area = 0; // twice the biggest triangle's
373
+ if (3 <= points.length && points.length <= 8)
374
+ for (const a of points) for (const b of points) for (const c of points)
375
+ area = max(area, abs(b.subtract(a).cross(c.subtract(a))));
376
+ ASSERT(area >= 1e-6, 'Box2D polygons need 3 to 8 points, not all in a line');
377
+ if (!(area >= 1e-6)) return;
378
+
379
+ const buffer = box2d.instance._malloc(points.length * 8);
380
+ for (let i=0, offset=0; i<points.length; ++i)
381
+ {
382
+ box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
383
+ offset += 4;
384
+ box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
385
+ offset += 4;
386
+ }
387
+ const box2dPoints = box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
388
+ const shape = new box2d.instance.b2PolygonShape();
389
+ shape.Set(box2dPoints, points.length);
390
+ box2d.instance._free(buffer);
391
+ return shape;
392
+ }
393
+
394
+ const shape = box2dCreatePolygonShape(points);
395
+ if (!shape) return;
396
+ const fixture = this.addShape(shape, density, friction, restitution, isSensor);
397
+ box2d.instance.destroy(shape); // the fixture has its own copy
398
+ return fixture;
399
+ }
400
+
401
+ /** Add a regular polygon shape to the body
402
+ * @param {number} [diameter]
403
+ * @param {number} [sides] - 3 to 8, the most Box2D polygons have
404
+ * @param {number} [density]
405
+ * @param {number} [friction]
406
+ * @param {number} [restitution]
407
+ * @param {boolean} [isSensor] */
408
+ addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
409
+ {
410
+ ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
411
+ ASSERT(isNumber(sides) && sides>2, 'sides must be a positive number greater than 2');
412
+ ASSERT(sides <= 8, 'Box2D polygons have at most 8 sides');
413
+ sides = min(sides, 8); // more would stop Box2D for good
414
+
415
+ const points = [];
416
+ const radius = diameter/2;
417
+ for (let i=sides; i--;)
418
+ points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
419
+ return this.addPoly(points, density, friction, restitution, isSensor);
420
+ }
421
+
422
+ /** Add a random polygon shape to the body
423
+ * @param {number} [diameter]
424
+ * @param {number} [density]
425
+ * @param {number} [friction]
426
+ * @param {number} [restitution]
427
+ * @param {boolean} [isSensor] */
428
+ addRandomPoly(diameter=1, density, friction, restitution, isSensor)
429
+ {
430
+ ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
431
+
432
+ const sides = randInt(3, 9);
433
+ const points = [];
434
+ const radius = diameter/2;
435
+ for (let i=sides; i--;)
436
+ points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
437
+ return this.addPoly(points, density, friction, restitution, isSensor);
438
+ }
439
+
440
+ /** Add a circle shape to the body
441
+ * @param {number} [diameter]
442
+ * @param {Vector2} [offset]
443
+ * @param {number} [density]
444
+ * @param {number} [friction]
445
+ * @param {number} [restitution]
446
+ * @param {boolean} [isSensor] */
447
+ addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
448
+ {
449
+ ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
450
+ ASSERT(isVector2(offset), 'offset must be a Vector2');
451
+
452
+ const shape = new box2d.instance.b2CircleShape();
453
+ shape.set_m_p(box2dTemp(offset));
454
+ shape.set_m_radius(diameter/2);
455
+ const fixture = this.addShape(shape, density, friction, restitution, isSensor);
456
+ box2d.instance.destroy(shape); // the fixture has its own copy
457
+ return fixture;
458
+ }
459
+
460
+ /** Add an edge shape to the body
461
+ * @param {Vector2} point1
462
+ * @param {Vector2} point2
463
+ * @param {number} [density]
464
+ * @param {number} [friction]
465
+ * @param {number} [restitution]
466
+ * @param {boolean} [isSensor] */
467
+ addEdge(point1, point2, density, friction, restitution, isSensor)
468
+ {
469
+ ASSERT(isVector2(point1), 'point1 must be a Vector2');
470
+ ASSERT(isVector2(point2), 'point2 must be a Vector2');
471
+
472
+ const shape = new box2d.instance.b2EdgeShape();
473
+ shape.Set(box2dTemp(point1), box2dTemp(point2, 1));
474
+ const fixture = this.addShape(shape, density, friction, restitution, isSensor);
475
+ box2d.instance.destroy(shape); // the fixture has its own copy
476
+ return fixture;
477
+ }
478
+
479
+ /** Add an edge list to the body
480
+ * @param {Array<Vector2>} points
481
+ * @param {number} [density]
482
+ * @param {number} [friction]
483
+ * @param {number} [restitution]
484
+ * @param {boolean} [isSensor] */
485
+ addEdgeList(points, density, friction, restitution, isSensor)
486
+ {
487
+ ASSERT(isArray(points), 'points must be an array');
488
+ points = box2dEdgePoints(points);
489
+ const fixtures = [], edgePoints = [];
490
+ if (points.length < 2) return fixtures;
491
+ for (let i=0; i<points.length-1; ++i)
492
+ {
493
+ // the ghost vertices, where there is a neighbor, make the edges one smooth surface
494
+ const shape = new box2d.instance.b2EdgeShape();
495
+ points[i-1] && shape.set_m_vertex0(box2dTemp(points[i-1]));
496
+ points[i+0] && shape.set_m_vertex1(box2dTemp(points[i+0]));
497
+ points[i+1] && shape.set_m_vertex2(box2dTemp(points[i+1]));
498
+ points[i+2] && shape.set_m_vertex3(box2dTemp(points[i+2]));
499
+ shape.set_m_hasVertex0(!!points[i-1]);
500
+ shape.set_m_hasVertex3(!!points[i+2]);
501
+ const f = this.addShape(shape, density, friction, restitution, isSensor);
502
+ box2d.instance.destroy(shape); // the fixture has its own copy
503
+ fixtures.push(f);
504
+ edgePoints.push(points[i].copy());
505
+ }
506
+ edgePoints.push(points[points.length-1].copy());
507
+ this.edgeLists.push(edgePoints);
508
+ fixtures.forEach(f=> this.edgeListFixtures.set(box2d.instance.getPointer(f), edgePoints));
509
+ return fixtures;
510
+ }
511
+
512
+ /** Add an edge loop to the body, an edge loop connects the end points
513
+ * @param {Array<Vector2>} points
514
+ * @param {number} [density]
515
+ * @param {number} [friction]
516
+ * @param {number} [restitution]
517
+ * @param {boolean} [isSensor] */
518
+ addEdgeLoop(points, density, friction, restitution, isSensor)
519
+ {
520
+ ASSERT(isArray(points), 'points must be an array');
521
+ points = box2dEdgePoints(points, true);
522
+ const fixtures = [], edgePoints = [];
523
+ if (points.length < 2) return fixtures;
524
+ const getPoint = i=> points[mod(i,points.length)];
525
+ for (let i=0; i<points.length; ++i)
526
+ {
527
+ const shape = new box2d.instance.b2EdgeShape();
528
+ shape.set_m_vertex0(box2dTemp(getPoint(i-1)));
529
+ shape.set_m_vertex1(box2dTemp(getPoint(i+0)));
530
+ shape.set_m_vertex2(box2dTemp(getPoint(i+1)));
531
+ shape.set_m_vertex3(box2dTemp(getPoint(i+2)));
532
+ shape.set_m_hasVertex0(true);
533
+ shape.set_m_hasVertex3(true);
534
+ const f = this.addShape(shape, density, friction, restitution, isSensor);
535
+ box2d.instance.destroy(shape); // the fixture has its own copy
536
+ fixtures.push(f);
537
+ edgePoints.push(points[i].copy());
538
+ }
539
+ this.edgeLoops.push(edgePoints);
540
+ fixtures.forEach(f=> this.edgeListFixtures.set(box2d.instance.getPointer(f), edgePoints));
541
+ return fixtures;
542
+ }
543
+
544
+ /** Destroy a fixture from the body, from a contact callback once the step is done
545
+ * @param {Object} fixture */
546
+ destroyFixture(fixture)
547
+ {
548
+ const pointer = box2d.instance.getPointer(fixture);
549
+
550
+ // an edge list or loop that loses a fixture is no longer one line, what is left of it draws edge by edge
551
+ const edgeFixtures = this.edgeListFixtures, points = edgeFixtures.get(pointer);
552
+ if (points)
553
+ {
554
+ this.edgeLists = this.edgeLists.filter(p=> p !== points);
555
+ this.edgeLoops = this.edgeLoops.filter(p=> p !== points);
556
+ edgeFixtures.forEach((p, pointer)=> p === points && edgeFixtures.delete(pointer));
557
+ }
558
+
559
+ // not once the body is gone, which takes its fixtures with it, or once the fixture is,
560
+ // since a second destroy of it, like one from each of two contacts in a step, stops Box2D for good
561
+ box2dWhenUnlocked(()=> this.body && this.getFixtureList().some(f=> box2d.instance.getPointer(f) === pointer)
562
+ && this.body.DestroyFixture(fixture));
563
+ }
564
+
565
+ /** Destroy all fixtures from the body, from a contact callback once the step is done */
566
+ destroyAllFixtures()
567
+ {
568
+ // the fixtures it has now, each destroyed if still there, in one pass so a big tile layer rebuilds quickly
569
+ this.edgeLists = [];
570
+ this.edgeLoops = [];
571
+ this.edgeListFixtures.clear();
572
+ const fixtures = this.getFixtureList(), getPointer = box2d.instance.getPointer;
573
+ box2dWhenUnlocked(()=>
574
+ {
575
+ if (!this.body) return;
576
+ const alive = new Set(this.getFixtureList().map(getPointer));
577
+ for (const fixture of fixtures)
578
+ alive.has(getPointer(fixture)) && this.body.DestroyFixture(fixture);
579
+ });
580
+ }
581
+
582
+ ///////////////////////////////////////////////////////////////////////////////
583
+ // physics get functions
584
+
585
+ /** Gets the center of mass in world space
586
+ * @return {Vector2} */
587
+ getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
588
+
589
+ /** Gets the linear velocity
590
+ * @return {Vector2} */
591
+ getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
592
+
593
+ /** Gets the angular velocity, clockwise like angle
594
+ * @return {number} */
595
+ getAngularVelocity() { return -this.body.GetAngularVelocity(); } // box2d uses reverse angle
596
+
597
+ /** Gets the mass
598
+ * @return {number} */
599
+ getMass() { return this.body.GetMass(); }
600
+
601
+ /** Gets the rotational inertia about the center of mass
602
+ * @return {number} */
603
+ getInertia()
604
+ {
605
+ // Box2D gives it about the body origin
606
+ const center = this.body.GetLocalCenter();
607
+ return max(0, Math.fround(this.body.GetInertia() - box2dCenterInertia(this.getMass(), center.get_x(), center.get_y())));
608
+ }
609
+
610
+ /** Check if this object is awake
611
+ * @return {boolean} */
612
+ getIsAwake() { return this.body.IsAwake(); }
613
+
614
+ /** Gets the physics body type
615
+ * @return {number} */
616
+ getBodyType() { return this.body.GetType(); }
617
+
618
+ /** Get the speed of this object
619
+ * @return {number} */
620
+ getSpeed() { return this.getLinearVelocity().length(); }
621
+
622
+ ///////////////////////////////////////////////////////////////////////////////
623
+ // physics set functions
624
+
625
+ /** Sets the position and angle, from a contact callback the body moves once the step is done
626
+ * @param {Vector2} pos
627
+ * @param {number} angle */
628
+ setTransform(pos, angle)
629
+ {
630
+ this.pos = pos.copy();
631
+ this.angle = angle;
632
+ // box2d uses reverse angle
633
+ const x = pos.x, y = pos.y;
634
+ box2dWhenUnlocked(()=>
635
+ {
636
+ if (!this.body) return;
637
+ this.body.SetTransform(box2dTemp(vec2(x, y)), -angle);
638
+ box2dWakeWithContacts(this.body); // Box2D leaves a sleeping body, and what rests on it, in the air
639
+ });
640
+ }
641
+
642
+ /** Sets the position, from a contact callback the body moves once the step is done, keeping the angle it has then
643
+ * @param {Vector2} pos */
644
+ setPosition(pos)
645
+ {
646
+ this.pos = pos.copy();
647
+ const x = pos.x, y = pos.y;
648
+ box2dWhenUnlocked(()=>
649
+ {
650
+ if (!this.body) return;
651
+ this.body.SetTransform(box2dTemp(vec2(x, y)), this.body.GetAngle());
652
+ box2dWakeWithContacts(this.body);
653
+ });
654
+ }
655
+
656
+ /** Sets the angle, from a contact callback the body turns once the step is done, keeping the position it has then
657
+ * @param {number} angle */
658
+ setAngle(angle)
659
+ {
660
+ this.angle = angle;
661
+ box2dWhenUnlocked(()=>
662
+ {
663
+ if (!this.body) return;
664
+ this.body.SetTransform(this.body.GetPosition(), -angle); // box2d uses reverse angle
665
+ box2dWakeWithContacts(this.body);
666
+ });
667
+ }
668
+
669
+ /** Sets the linear velocity
670
+ * @param {Vector2} velocity */
671
+ setLinearVelocity(velocity)
672
+ { this.body.SetLinearVelocity(box2dTemp(velocity)); }
673
+
674
+ /** Sets the angular velocity, clockwise like angle
675
+ * @param {number} angularVelocity */
676
+ setAngularVelocity(angularVelocity)
677
+ { this.body.SetAngularVelocity(-angularVelocity); }
678
+
679
+ /** Sets the linear damping
680
+ * @param {number} damping */
681
+ setLinearDamping(damping)
682
+ { this.body.SetLinearDamping(damping); }
683
+
684
+ /** Sets the angular damping
685
+ * @param {number} damping */
686
+ setAngularDamping(damping)
687
+ { this.body.SetAngularDamping(damping); }
688
+
689
+ /** Sets the gravity scale
690
+ * @param {number} [scale] */
691
+ setGravityScale(scale=1)
692
+ {
693
+ this.body.SetGravityScale(this.gravityScale = scale);
694
+ this.body.SetAwake(true); // a sleeping body would not feel it
695
+ }
696
+
697
+ /** Should be like a bullet for continuous collision detection?
698
+ * @param {boolean} [isBullet] */
699
+ setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
700
+
701
+ /** Set the sleep state of the body
702
+ * @param {boolean} [isAwake] */
703
+ setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
704
+
705
+ /** Set the physics body type, from a contact callback it changes once the step is done
706
+ * @param {number} type */
707
+ setBodyType(type) { box2dWhenUnlocked(()=> this.body && this.body.SetType(type)); }
708
+
709
+ /** Set whether the body is allowed to sleep
710
+ * @param {boolean} [isAllowed] */
711
+ setSleepingAllowed(isAllowed=true)
712
+ { this.body.SetSleepingAllowed(isAllowed); }
713
+
714
+ /** Set whether the body can rotate
715
+ * @param {boolean} [isFixed] */
716
+ setFixedRotation(isFixed=true)
717
+ {
718
+ this.body.SetFixedRotation(isFixed);
719
+ this.body.SetAwake(true); // a sleeping body would not tip over once it can turn
720
+ }
721
+
722
+ /** Set the center of mass of the body, local to it
723
+ * @param {Vector2} center */
724
+ setCenterOfMass(center) { this.setMassData(center) }
725
+
726
+ /** Set the mass of the body
727
+ * @param {number} mass */
728
+ setMass(mass) { this.setMassData(undefined, mass) }
729
+
730
+ /** Set the moment of inertia of the body, about its center of mass
731
+ * @param {number} momentOfInertia */
732
+ setMomentOfInertia(momentOfInertia)
733
+ { this.setMassData(undefined, undefined, momentOfInertia) }
734
+
735
+ /** Reset the mass, center of mass, and moment, from a contact callback once the step is done */
736
+ resetMassData() { box2dWhenUnlocked(()=> this.body && this.body.ResetMassData()); }
737
+
738
+ /** Set the mass data of the body, from a contact callback once the step is done;
739
+ * a mass of 0 or less becomes 1, use setBodyType for a static body; call it after adding fixtures and after
740
+ * setFixedRotation, both of which put the mass back to what the fixtures give
741
+ * @param {Vector2} [localCenter]
742
+ * @param {number} [mass]
743
+ * @param {number} [momentOfInertia] - About the center of mass */
744
+ setMassData(localCenter, mass, momentOfInertia)
745
+ {
746
+ localCenter = localCenter && localCenter.copy(); // as it is now, even if it waits for the step
747
+ box2dWhenUnlocked(()=>
748
+ {
749
+ if (!this.body) return;
750
+ const data = box2dQueryObject('massData', 'b2MassData'); // reused, GetMassData fills it in
751
+ this.body.GetMassData(data);
752
+
753
+ // Box2D's inertia is about the body origin, so it is turned to the center of mass and back; kept as
754
+ // it was, one for the old mass and center goes below 0 about the new ones, which stops Box2D for good
755
+ const center = data.get_center(), oldMass = data.get_mass();
756
+ const cx = localCenter ? localCenter.x : center.get_x(), cy = localCenter ? localCenter.y : center.get_y();
757
+ // it is worked out in float32 as Box2D does it, which must come out above 0 or the rotation is locked
758
+ const f = Math.fround;
759
+ const oldInertia = f(data.get_I() - box2dCenterInertia(oldMass, center.get_x(), center.get_y()));
760
+ const inertia = momentOfInertia ?? oldInertia;
761
+ mass ??= oldMass;
762
+ const offset = box2dCenterInertia(mass > 0 ? mass : 1, cx, cy); // a mass of 0 or less is 1 to Box2D
763
+ const I = f(inertia + offset);
764
+ data.set_mass(mass);
765
+ data.set_center(box2dTemp(vec2(cx, cy)));
766
+ data.set_I(inertia > 0 && f(I - offset) > 0 ? I : 0);
767
+ this.body.SetMassData(data);
768
+ this.body.SetAwake(true); // a sleeping body would not tip over a new center of mass
769
+ });
770
+ }
771
+
772
+ /** Set the collision filter data for the fixtures this body has now, a fixture added later has the default
773
+ * filter, category 1 colliding with everything
774
+ * @param {number} [categoryBits]
775
+ * @param {number} [ignoreCategoryBits]
776
+ * @param {number} [groupIndex] */
777
+ setFilterData(categoryBits=1, ignoreCategoryBits=0, groupIndex=0)
778
+ {
779
+ this.getFixtureList().forEach(fixture=>
780
+ {
781
+ const filter = fixture.GetFilterData();
782
+ filter.set_categoryBits(categoryBits);
783
+ filter.set_maskBits(0xffff & ~ignoreCategoryBits);
784
+ filter.set_groupIndex(groupIndex);
785
+ fixture.SetFilterData(filter); // applies and refilters contacts
786
+ });
787
+ }
788
+
789
+ /** Set if this body is a sensor
790
+ * @param {boolean} [isSensor] */
791
+ setSensor(isSensor=true)
792
+ {
793
+ this.getFixtureList().forEach(f=>f.SetSensor(isSensor));
794
+ box2dWakeWithContacts(this.body); // what rests on it or sits in it moves again, a sleeping pair never updates
795
+ }
796
+
797
+ ///////////////////////////////////////////////////////////////////////////////
798
+ // physics force and torque functions
799
+
800
+ /** Apply force to this object
801
+ * @param {Vector2} force
802
+ * @param {Vector2} [pos] */
803
+ applyForce(force, pos)
804
+ {
805
+ pos ||= this.getCenterOfMass();
806
+ this.setAwake();
807
+ this.body.ApplyForce(box2dTemp(force), box2dTemp(pos, 1));
808
+ }
809
+
810
+ /** Apply acceleration to this object (changes velocity by acceleration,
811
+ * mass-independent like EngineObject.applyAcceleration, but in units per second).
812
+ * Use applyImpulse if you want the mass-dependent velocity change
813
+ * Δv = impulse / mass, or applyForce for a Newton-style sustained force.
814
+ * @param {Vector2} acceleration
815
+ * @param {Vector2} [pos] */
816
+ applyAcceleration(acceleration, pos)
817
+ {
818
+ pos ||= this.getCenterOfMass();
819
+ this.setAwake();
820
+ const impulse = acceleration.scale(this.getMass());
821
+ this.body.ApplyLinearImpulse(box2dTemp(impulse), box2dTemp(pos, 1));
822
+ }
823
+
824
+ /** Apply an instantaneous linear impulse. Changes velocity immediately by
825
+ * impulse / mass (so heavier bodies move less for the same impulse).
826
+ * @param {Vector2} impulse
827
+ * @param {Vector2} [pos] */
828
+ applyImpulse(impulse, pos)
829
+ {
830
+ pos ||= this.getCenterOfMass();
831
+ this.setAwake();
832
+ this.body.ApplyLinearImpulse(box2dTemp(impulse), box2dTemp(pos, 1));
833
+ }
834
+
835
+ /** Apply torque to this object, clockwise like angle
836
+ * @param {number} torque */
837
+ applyTorque(torque)
838
+ {
839
+ this.setAwake();
840
+ this.body.ApplyTorque(-torque);
841
+ }
842
+
843
+ /** Apply angular acceleration to this object (changes angular velocity by
844
+ * acceleration, mass-independent, clockwise — matches EngineObject.applyAngularAcceleration).
845
+ * @param {number} acceleration */
846
+ applyAngularAcceleration(acceleration)
847
+ {
848
+ // the velocity itself, since the inertia Box2D gives is about the origin, not the center of mass;
849
+ // like the impulse this was, only a dynamic body turns
850
+ if (this.getBodyType() !== box2d.bodyTypeDynamic || this.body.IsFixedRotation()) return;
851
+ this.setAwake();
852
+ this.setAngularVelocity(this.getAngularVelocity() + acceleration);
853
+ }
854
+
855
+ /** Apply an instantaneous angular impulse. Changes angular velocity by
856
+ * impulse / inertia immediately, clockwise like angle.
857
+ * @param {number} impulse */
858
+ applyAngularImpulse(impulse)
859
+ {
860
+ this.setAwake();
861
+ this.body.ApplyAngularImpulse(-impulse);
862
+ }
863
+
864
+ ///////////////////////////////////////////////////////////////////////////////
865
+ // lists of fixtures and joints
866
+
867
+ /** Check if this object has any fixtures
868
+ * @return {boolean} */
869
+ hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
870
+
871
+ /** Get list of fixtures for this object
872
+ * @return {Array<Object>} */
873
+ getFixtureList()
874
+ {
875
+ const fixtures = [];
876
+ for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
877
+ {
878
+ fixtures.push(fixture);
879
+ fixture = fixture.GetNext();
880
+ }
881
+ return fixtures;
882
+ }
883
+
884
+ /** Check if this object has any joints
885
+ * @return {boolean} */
886
+ hasJoints() { return this.getJointList().length > 0; } // not the ones destroyed and waiting for the step to end
887
+
888
+ /** Get list of joints for this object, the Box2dJoint for each one made through LittleJS,
889
+ * and the Box2D joint, cast to its type, for any made on the world directly
890
+ * @return {Array<Box2dJoint|Object>} */
891
+ getJointList()
892
+ {
893
+ // the body keeps a list of edges, each holding a joint and the next edge
894
+ const joints = [];
895
+ for (let edge=this.body.GetJointList(); !box2d.isNull(edge); edge = edge.get_next())
896
+ {
897
+ const joint = edge.get_joint(), wrapper = box2dJoints.get(box2d.instance.getPointer(joint));
898
+ if (wrapper && !wrapper.box2dJoint)
899
+ continue; // destroyed in a contact callback, Box2D lets go of it once the step is done
900
+ joints.push(wrapper || box2d.castJointObject(joint));
901
+ }
902
+ return joints;
903
+ }
904
+ }
905
+
906
+ ///////////////////////////////////////////////////////////////////////////////
907
+ /**
908
+ * Box2D Static Object - Box2d with a static physics body
909
+ * @extends Box2dObject
910
+ * @memberof Box2D
911
+ */
912
+ class Box2dStaticObject extends Box2dObject
913
+ {
914
+ /** Create a LittleJS object with Box2d physics
915
+ * @param {Vector2} [pos]
916
+ * @param {Vector2} [size]
917
+ * @param {TileInfo} [tileInfo]
918
+ * @param {number} [angle]
919
+ * @param {Color} [color]
920
+ * @param {number} [renderOrder] */
921
+ constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
922
+ {
923
+ const bodyType = box2d.bodyTypeStatic;
924
+ super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
925
+ }
926
+ }
927
+
928
+ ///////////////////////////////////////////////////////////////////////////////
929
+ /**
930
+ * Box2D Kinematic Object - Box2d with a kinematic physics body
931
+ * @extends Box2dObject
932
+ * @memberof Box2D
933
+ */
934
+ class Box2dKinematicObject extends Box2dObject
935
+ {
936
+ /** Create a LittleJS object with Box2d physics
937
+ * @param {Vector2} [pos]
938
+ * @param {Vector2} [size]
939
+ * @param {TileInfo} [tileInfo]
940
+ * @param {number} [angle]
941
+ * @param {Color} [color]
942
+ * @param {number} [renderOrder] */
943
+ constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
944
+ {
945
+ const bodyType = box2d.bodyTypeKinematic;
946
+ super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
947
+ }
948
+ }
949
+
950
+ ///////////////////////////////////////////////////////////////////////////////
951
+ /**
952
+ * Box2d Tile Layer
953
+ * - adds Box2d support to tile layers
954
+ * - creates static box2d fixtures for solid tiles, call buildCollision to rebuild them after the tiles change
955
+ * @extends Box2dStaticObject
956
+ * @memberof Box2D
957
+ */
958
+ class Box2dTileLayer extends Box2dStaticObject
959
+ {
960
+ /** Create a Box2d tile layer object
961
+ * @param {TileCollisionLayer} tileLayer - Tile layer for this object */
962
+ constructor(tileLayer)
963
+ {
964
+ ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
965
+ super(tileLayer.pos, tileLayer.size);
966
+
967
+ /** @property {TileCollisionLayer} - The tile layer */
968
+ this.tileLayer = tileLayer;
969
+ this.addChild(tileLayer);
970
+
971
+ // collision for the solid tiles it has now, call buildCollision again after changing them
972
+ this.buildCollision();
973
+ }
974
+
975
+ render()
976
+ {
977
+ // do not render fixtures, tile layer handles rendering
978
+ }
979
+
980
+ /** Create box2d collision fixtures for solid tiles
981
+ * @param {number} [friction]
982
+ * @param {number} [restitution] */
983
+ buildCollision(friction=.2, restitution=0)
984
+ {
985
+ // destroy all fixtures and create new ones
986
+ this.destroyAllFixtures();
987
+
988
+ // track which tiles have been processed
989
+ const processed = [];
990
+ const getIndex = (x, y)=> x + y * this.size.x;
991
+ const isSolidUnprocessed = (x, y)=>
992
+ !processed[getIndex(x, y)] &&
993
+ this.tileLayer.getCollisionData(vec2(x, y)) > 0;
994
+
995
+ // combine tiles into larger boxes
996
+ for (let x = 0; x < this.size.x; ++x)
997
+ for (let y = 0; y < this.size.y; ++y)
998
+ {
999
+ if (!isSolidUnprocessed(x, y)) continue;
1000
+
1001
+ // find max width by scanning right
1002
+ let width = 1, height = 1, canExpand = true;
1003
+ while (isSolidUnprocessed(x + width, y))
1004
+ ++width;
1005
+
1006
+ // find max height by scanning up, ensuring all rows have the same width
1007
+ while (canExpand)
1008
+ {
1009
+ for (let checkX = 0; checkX < width; ++checkX)
1010
+ {
1011
+ if (!isSolidUnprocessed(x + checkX, y + height))
1012
+ {
1013
+ canExpand = false;
1014
+ break;
1015
+ }
1016
+ }
1017
+ if (canExpand)
1018
+ ++height;
1019
+ }
1020
+
1021
+ // mark all tiles in this rectangle as processed
1022
+ for (let rectX = width; rectX--;)
1023
+ for (let rectY = height; rectY--;)
1024
+ processed[getIndex(x + rectX, y + rectY)] = true;
1025
+
1026
+ // create a single fixture for the entire rectangle
1027
+ const shapeSize = vec2(width, height);
1028
+ const offset = vec2(x + width/2, y + height/2);
1029
+ this.addBox(shapeSize, offset, 0, 0, friction, restitution);
1030
+ }
1031
+ }
1032
+ }
1033
+
1034
+ ///////////////////////////////////////////////////////////////////////////////
1035
+ /**
1036
+ * Box2D Raycast Result
1037
+ * - Holds results from a box2d raycast queries
1038
+ * - Automatically created by box2d raycast functions
1039
+ * @memberof Box2D
1040
+ */
1041
+ class Box2dRaycastResult
1042
+ {
1043
+ /** Create a raycast result
1044
+ * @param {Object} fixture
1045
+ * @param {Vector2} point
1046
+ * @param {Vector2} normal
1047
+ * @param {number} fraction */
1048
+ constructor(fixture, point, normal, fraction)
1049
+ {
1050
+ /** @property {Box2dObject} - The box2d object
1051
+ * @type {Box2dObject} */
1052
+ this.object = fixture.GetBody().object;
1053
+ /** @property {Object} - The fixture that was hit */
1054
+ this.fixture = fixture;
1055
+ /** @property {Vector2} - The hit point */
1056
+ this.point = point;
1057
+ /** @property {Vector2} - The hit normal */
1058
+ this.normal = normal;
1059
+ /** @property {number} - Distance fraction at the point of intersection */
1060
+ this.fraction = fraction;
1061
+ }
1062
+ }
1063
+
1064
+ ///////////////////////////////////////////////////////////////////////////////
1065
+ /**
1066
+ * Box2D Joint
1067
+ * - Base class for Box2D joints
1068
+ * - A joint is used to connect objects together
1069
+ * - Angular values are clockwise like angle: joint angles and speeds, limits, motor speeds and torques
1070
+ * @memberof Box2D
1071
+ */
1072
+ class Box2dJoint
1073
+ {
1074
+ /** Create a box2d joint, the base class is not intended to be used directly
1075
+ * @param {Object} jointDef - Freed once the joint is made, Box2D copies it */
1076
+ constructor(jointDef)
1077
+ {
1078
+ ASSERT(!box2d.world.IsLocked(), 'cannot create Box2D joints during a contact callback');
1079
+ const bodyA = jointDef.get_bodyA(), bodyB = jointDef.get_bodyB();
1080
+ const bothLive = !box2d.isNull(bodyA) && !box2d.isNull(bodyB);
1081
+ ASSERT(bothLive, 'a joint needs two objects that are not destroyed');
1082
+ ASSERT(box2d.instance.getPointer(bodyA) !== box2d.instance.getPointer(bodyB), 'a joint needs two different objects');
1083
+
1084
+ /** @property {Object} - The Box2d joint, 0 once it is destroyed, as it is when either object is */
1085
+ this.box2dJoint = 0;
1086
+ if (!bothLive)
1087
+ {
1088
+ // one of its objects is gone, so it is made destroyed, as it would be had it gone after
1089
+ box2d.instance.destroy(jointDef);
1090
+ return;
1091
+ }
1092
+ this.box2dJoint = box2d.castJointObject(box2d.world.CreateJoint(jointDef));
1093
+ box2d.instance.destroy(jointDef);
1094
+ box2dJoints.set(box2d.instance.getPointer(this.box2dJoint), this);
1095
+ }
1096
+
1097
+ /** Destroy this joint */
1098
+ destroy()
1099
+ {
1100
+ const joint = this.box2dJoint;
1101
+ if (!joint) return; // destroyed already, or with one of its objects
1102
+ this.box2dJoint = 0;
1103
+ box2dDestroyGears(this);
1104
+
1105
+ // a body destroyed before it, in the same step, takes the joint with it and lets go of it here
1106
+ const pointer = box2d.instance.getPointer(joint);
1107
+ box2dWhenUnlocked(()=>
1108
+ {
1109
+ if (box2dJoints.get(pointer) !== this) return;
1110
+ box2dJoints.delete(pointer);
1111
+ box2d.world.DestroyJoint(joint);
1112
+ });
1113
+ }
1114
+
1115
+ /** Get the first object attached to this joint
1116
+ * @return {Box2dObject} */
1117
+ getObjectA() { return this.box2dJoint.GetBodyA().object; }
1118
+
1119
+ /** Get the second object attached to this joint
1120
+ * @return {Box2dObject} */
1121
+ getObjectB() { return this.box2dJoint.GetBodyB().object; }
1122
+
1123
+ /** Get the first anchor for this joint in world coordinates
1124
+ * @return {Vector2} */
1125
+ getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
1126
+
1127
+ /** Get the second anchor for this joint in world coordinates
1128
+ * @return {Vector2} */
1129
+ getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
1130
+
1131
+ /** Get the reaction force on bodyB at the joint anchor over the last step
1132
+ * @param {number} [time] - The step length in seconds, the world steps by timeDelta
1133
+ * @return {Vector2} */
1134
+ getReactionForce(time=timeDelta) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
1135
+
1136
+ /** Get the reaction torque on bodyB in N*m over the last step, clockwise like angle
1137
+ * @param {number} [time] - The step length in seconds, the world steps by timeDelta
1138
+ * @return {number} */
1139
+ getReactionTorque(time=timeDelta) { return -this.box2dJoint.GetReactionTorque(1/time);} // box2d uses reverse angle
1140
+
1141
+ /** Check if the connected bodies should collide
1142
+ * @return {boolean} */
1143
+ getCollideConnected() { return this.box2dJoint.GetCollideConnected();}
1144
+
1145
+ /** Check if either connected body is active
1146
+ * @return {boolean} */
1147
+ isActive() { return !!this.box2dJoint && this.box2dJoint.IsActive(); }
1148
+
1149
+ /** Check if the joint is gone, destroyed or taken along with one of its objects; its other methods can not be used then
1150
+ * @return {boolean} */
1151
+ isDestroyed() { return !this.box2dJoint; }
1152
+ }
1153
+
1154
+ ///////////////////////////////////////////////////////////////////////////////
1155
+ /**
1156
+ * Box2D Target Joint, also known as a mouse joint
1157
+ * - Used to make a point on a object track a specific world point target
1158
+ * - This a soft constraint with a max force
1159
+ * - This allows the constraint to stretch and without applying huge forces
1160
+ * - The object must be dynamic, and stay dynamic while the joint holds it, Box2D stops for good on one with no mass
1161
+ * @extends Box2dJoint
1162
+ * @memberof Box2D
1163
+ */
1164
+ class Box2dTargetJoint extends Box2dJoint
1165
+ {
1166
+ /** Create a target joint
1167
+ * @param {Box2dObject} object
1168
+ * @param {Box2dObject} fixedObject
1169
+ * @param {Vector2} worldPos */
1170
+ constructor(object, fixedObject, worldPos)
1171
+ {
1172
+ ASSERT(object.getBodyType() === box2d.bodyTypeDynamic, 'a target joint needs a dynamic object');
1173
+ object.setAwake();
1174
+ const jointDef = new box2d.instance.b2MouseJointDef();
1175
+ jointDef.set_bodyA(fixedObject.body);
1176
+ jointDef.set_bodyB(object.body);
1177
+ jointDef.set_target(box2dTemp(worldPos));
1178
+ jointDef.set_maxForce(2e3 * object.getMass());
1179
+ super(jointDef);
1180
+ }
1181
+
1182
+ /** Set the target point in world coordinates
1183
+ * @param {Vector2} pos */
1184
+ setTarget(pos) { this.box2dJoint.SetTarget(box2dTemp(pos)); }
1185
+
1186
+ /** Get the target point in world coordinates
1187
+ * @return {Vector2} */
1188
+ getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
1189
+
1190
+ /** Sets the maximum force in Newtons
1191
+ * @param {number} force */
1192
+ setMaxForce(force) { this.box2dJoint.SetMaxForce(force); box2dWakeJoint(this.box2dJoint); }
1193
+
1194
+ /** Gets the maximum force in Newtons
1195
+ * @return {number} */
1196
+ getMaxForce() { return this.box2dJoint.GetMaxForce(); }
1197
+
1198
+ /** Sets the joint frequency in Hertz, above 0, Box2D stops for good on 0
1199
+ * @param {number} hz */
1200
+ setFrequency(hz) { this.box2dJoint.SetFrequency(max(hz, 1e-3)); box2dWakeJoint(this.box2dJoint); }
1201
+
1202
+ /** Gets the joint frequency in Hertz
1203
+ * @return {number} */
1204
+ getFrequency() { return this.box2dJoint.GetFrequency(); }
1205
+ }
1206
+
1207
+ ///////////////////////////////////////////////////////////////////////////////
1208
+ /**
1209
+ * Box2D Distance Joint
1210
+ * - Constrains two points on two objects to remain at a fixed distance
1211
+ * - You can view this as a massless, rigid rod
1212
+ * @extends Box2dJoint
1213
+ * @memberof Box2D
1214
+ */
1215
+ class Box2dDistanceJoint extends Box2dJoint
1216
+ {
1217
+ /** Create a distance joint
1218
+ * @param {Box2dObject} objectA
1219
+ * @param {Box2dObject} objectB
1220
+ * @param {Vector2} [anchorA] - World position, objectA's position if not given
1221
+ * @param {Vector2} [anchorB] - World position, objectB's position if not given
1222
+ * @param {boolean} [collide] */
1223
+ constructor(objectA, objectB, anchorA, anchorB, collide=false)
1224
+ {
1225
+ anchorA ||= box2d.vec2From(objectA.body.GetPosition());
1226
+ anchorB ||= box2d.vec2From(objectB.body.GetPosition());
1227
+ const localAnchorA = objectA.worldToLocal(anchorA);
1228
+ const localAnchorB = objectB.worldToLocal(anchorB);
1229
+ const jointDef = new box2d.instance.b2DistanceJointDef();
1230
+ jointDef.set_bodyA(objectA.body);
1231
+ jointDef.set_bodyB(objectB.body);
1232
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1233
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1234
+ jointDef.set_length(anchorA.distance(anchorB));
1235
+ jointDef.set_collideConnected(collide);
1236
+ super(jointDef);
1237
+ }
1238
+
1239
+ /** Get the local anchor point relative to objectA's origin
1240
+ * @return {Vector2} */
1241
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1242
+
1243
+ /** Get the local anchor point relative to objectB's origin
1244
+ * @return {Vector2} */
1245
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1246
+
1247
+ /** Set the length of the joint
1248
+ * @param {number} length */
1249
+ setLength(length) { this.box2dJoint.SetLength(length); box2dWakeJoint(this.box2dJoint); }
1250
+
1251
+ /** Get the length of the joint
1252
+ * @return {number} */
1253
+ getLength() { return this.box2dJoint.GetLength(); }
1254
+
1255
+ /** Set the frequency in Hertz
1256
+ * @param {number} hz */
1257
+ setFrequency(hz) { this.box2dJoint.SetFrequency(hz); box2dWakeJoint(this.box2dJoint); }
1258
+
1259
+ /** Get the frequency in Hertz
1260
+ * @return {number} */
1261
+ getFrequency() { return this.box2dJoint.GetFrequency(); }
1262
+
1263
+ /** Set the damping ratio
1264
+ * @param {number} ratio */
1265
+ setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
1266
+
1267
+ /** Get the damping ratio
1268
+ * @return {number} */
1269
+ getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
1270
+ }
1271
+
1272
+ ///////////////////////////////////////////////////////////////////////////////
1273
+ /**
1274
+ * Box2D Rope Joint
1275
+ * - Enforces a maximum distance between two points on two objects
1276
+ * @extends Box2dJoint
1277
+ * @memberof Box2D
1278
+ */
1279
+ class Box2dRopeJoint extends Box2dJoint
1280
+ {
1281
+ /** Create a rope joint
1282
+ * @param {Box2dObject} objectA
1283
+ * @param {Box2dObject} objectB
1284
+ * @param {Vector2} [anchorA] - World position, objectA's position if not given
1285
+ * @param {Vector2} [anchorB] - World position, objectB's position if not given
1286
+ * @param {number} [extraLength]
1287
+ * @param {boolean} [collide] */
1288
+ constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
1289
+ {
1290
+ anchorA ||= box2d.vec2From(objectA.body.GetPosition());
1291
+ anchorB ||= box2d.vec2From(objectB.body.GetPosition());
1292
+ const localAnchorA = objectA.worldToLocal(anchorA);
1293
+ const localAnchorB = objectB.worldToLocal(anchorB);
1294
+ const jointDef = new box2d.instance.b2RopeJointDef();
1295
+ jointDef.set_bodyA(objectA.body);
1296
+ jointDef.set_bodyB(objectB.body);
1297
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1298
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1299
+ jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
1300
+ jointDef.set_collideConnected(collide);
1301
+ super(jointDef);
1302
+ }
1303
+
1304
+ /** Get the local anchor point relative to objectA's origin
1305
+ * @return {Vector2} */
1306
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1307
+
1308
+ /** Get the local anchor point relative to objectB's origin
1309
+ * @return {Vector2} */
1310
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1311
+
1312
+ /** Set the max length of the joint
1313
+ * @param {number} length */
1314
+ setMaxLength(length) { this.box2dJoint.SetMaxLength(length); box2dWakeJoint(this.box2dJoint); }
1315
+
1316
+ /** Get the max length of the joint
1317
+ * @return {number} */
1318
+ getMaxLength() { return this.box2dJoint.GetMaxLength(); }
1319
+ }
1320
+
1321
+ ///////////////////////////////////////////////////////////////////////////////
1322
+ /**
1323
+ * Box2D Revolute Joint
1324
+ * - Constrains two objects to share a point while they are free to rotate around the point
1325
+ * - The relative rotation about the shared point is the joint angle
1326
+ * - You can limit the relative rotation with a joint limit
1327
+ * - You can use a motor to drive the relative rotation about the shared point
1328
+ * - A maximum motor torque is provided so that infinite forces are not generated
1329
+ * @extends Box2dJoint
1330
+ * @memberof Box2D
1331
+ */
1332
+ class Box2dRevoluteJoint extends Box2dJoint
1333
+ {
1334
+ /** Create a revolute joint
1335
+ * @param {Box2dObject} objectA
1336
+ * @param {Box2dObject} objectB
1337
+ * @param {Vector2} [anchor] - World position, objectB's position if not given
1338
+ * @param {boolean} [collide] */
1339
+ constructor(objectA, objectB, anchor, collide=false)
1340
+ {
1341
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1342
+ const localAnchorA = objectA.worldToLocal(anchor);
1343
+ const localAnchorB = objectB.worldToLocal(anchor);
1344
+ const jointDef = new box2d.instance.b2RevoluteJointDef();
1345
+ jointDef.set_bodyA(objectA.body);
1346
+ jointDef.set_bodyB(objectB.body);
1347
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1348
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1349
+ jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
1350
+ jointDef.set_collideConnected(collide);
1351
+ super(jointDef);
1352
+ }
1353
+
1354
+ /** Get the local anchor point relative to objectA's origin
1355
+ * @return {Vector2} */
1356
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1357
+
1358
+ /** Get the local anchor point relative to objectB's origin
1359
+ * @return {Vector2} */
1360
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1361
+
1362
+ /** Get the reference angle, objectB angle minus objectA angle in the reference state
1363
+ * @return {number} */
1364
+ getReferenceAngle() { return -this.box2dJoint.GetReferenceAngle(); } // box2d uses reverse angle
1365
+
1366
+ /** Get the current joint angle, clockwise like angle
1367
+ * @return {number} */
1368
+ getJointAngle() { return -this.box2dJoint.GetJointAngle(); }
1369
+
1370
+ /** Get the current joint angle speed in radians per second, clockwise like angle
1371
+ * @return {number} */
1372
+ getJointSpeed() { return -this.box2dJoint.GetJointSpeed(); }
1373
+
1374
+ /** Is the joint limit enabled?
1375
+ * @return {boolean} */
1376
+ isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
1377
+
1378
+ /** Enable/disable the joint limit
1379
+ * @param {boolean} [enable] */
1380
+ enableLimit(enable=true) { this.box2dJoint.EnableLimit(enable); }
1381
+
1382
+ /** Get the lower joint limit, clockwise like angle
1383
+ * @return {number} */
1384
+ getLowerLimit() { return -this.box2dJoint.GetUpperLimit(); } // reversed, so Box2D's upper is the lower
1385
+
1386
+ /** Get the upper joint limit, clockwise like angle
1387
+ * @return {number} */
1388
+ getUpperLimit() { return -this.box2dJoint.GetLowerLimit(); }
1389
+
1390
+ /** Set the joint limits, clockwise like angle
1391
+ * @param {number} min
1392
+ * @param {number} max */
1393
+ setLimits(min, max)
1394
+ {
1395
+ ASSERT(min <= max, 'the lower limit must not be above the upper one');
1396
+ if (min > max) [min, max] = [max, min]; // Box2D stops on them reversed
1397
+ this.box2dJoint.SetLimits(-max, -min);
1398
+ }
1399
+
1400
+ /** Is the joint motor enabled?
1401
+ * @return {boolean} */
1402
+ isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1403
+
1404
+ /** Enable/disable the joint motor
1405
+ * @param {boolean} [enable] */
1406
+ enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
1407
+
1408
+ /** Set the motor speed, clockwise like angle
1409
+ * @param {number} speed */
1410
+ setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(-speed); }
1411
+
1412
+ /** Get the motor speed, clockwise like angle
1413
+ * @return {number} */
1414
+ getMotorSpeed() { return -this.box2dJoint.GetMotorSpeed(); }
1415
+
1416
+ /** Set the max motor torque, a magnitude
1417
+ * @param {number} torque */
1418
+ setMaxMotorTorque(torque) { this.box2dJoint.SetMaxMotorTorque(torque); }
1419
+
1420
+ /** Get the max motor torque
1421
+ * @return {number} */
1422
+ getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
1423
+
1424
+ /** Get the motor torque over the last step, clockwise like angle
1425
+ * @param {number} [time] - The step length in seconds, the world steps by timeDelta
1426
+ * @return {number} */
1427
+ getMotorTorque(time=timeDelta) { return -this.box2dJoint.GetMotorTorque(1/time); }
1428
+ }
1429
+
1430
+ ///////////////////////////////////////////////////////////////////////////////
1431
+ /**
1432
+ * Box2D Pin Joint
1433
+ * - Pins two objects together at a point, where they still turn freely, like a nail through two boards
1434
+ * - A revolute joint at that point, so it holds exactly and its limits and motor work too
1435
+ * @extends Box2dRevoluteJoint
1436
+ * @memberof Box2D
1437
+ */
1438
+ class Box2dPinJoint extends Box2dRevoluteJoint
1439
+ {
1440
+ /** Create a pin joint
1441
+ * @param {Box2dObject} objectA
1442
+ * @param {Box2dObject} objectB
1443
+ * @param {Vector2} [pos] - World position, objectA's position if not given
1444
+ * @param {boolean} [collide] */
1445
+ constructor(objectA, objectB, pos=objectA.pos, collide=false)
1446
+ {
1447
+ super(objectA, objectB, pos, collide);
1448
+ }
1449
+ }
1450
+
1451
+ ///////////////////////////////////////////////////////////////////////////////
1452
+ /**
1453
+ * Box2D Gear Joint
1454
+ * - A gear joint is used to connect two joints together
1455
+ * - Either joint can be a revolute or prismatic joint
1456
+ * - You specify a gear ratio to bind the motions together
1457
+ * - joint1's angle or translation plus ratio times joint2's stays constant, angles clockwise like angle
1458
+ * - It is destroyed along with either joint, or an object either joint is on
1459
+ * - It turns objectB of each joint, so make each with its fixed or carrying object first, and a dynamic objectB
1460
+ * @extends Box2dJoint
1461
+ * @memberof Box2D
1462
+ */
1463
+ class Box2dGearJoint extends Box2dJoint
1464
+ {
1465
+ /** Create a gear joint
1466
+ * @param {Box2dObject} objectA - objectB of joint1, Box2D joins that one whatever is passed
1467
+ * @param {Box2dObject} objectB - objectB of joint2, Box2D joins that one whatever is passed
1468
+ * @param {Box2dJoint} joint1
1469
+ * @param {Box2dJoint} joint2
1470
+ * @param {number} [ratio] */
1471
+ constructor(objectA, objectB, joint1, joint2, ratio=1)
1472
+ {
1473
+ // Box2D's angles are reversed and its translations are not, so a revolute joint geared to a prismatic one
1474
+ // needs the ratio reversed too, two of a kind keep it
1475
+ const isGearable = (j)=> (j instanceof Box2dRevoluteJoint || j instanceof Box2dPrismaticJoint) && !!j.box2dJoint;
1476
+ ASSERT(isGearable(joint1) && isGearable(joint2), 'a gear joint needs two revolute or prismatic joints that exist');
1477
+ // Box2D turns objectB of each joint, with objectA as its carrier, so a static objectB leaves the gear doing nothing
1478
+ ASSERT(joint1.getObjectB()?.getBodyType() === box2d.bodyTypeDynamic &&
1479
+ joint2.getObjectB()?.getBodyType() === box2d.bodyTypeDynamic,
1480
+ 'a gear joint turns objectB of each joint, make each joint with its fixed or carrying object first');
1481
+ ASSERT(objectA === joint1.getObjectB() && objectB === joint2.getObjectB(),
1482
+ 'a gear joint joins objectB of joint1 and objectB of joint2, pass those');
1483
+ const ratioSign = (joint1 instanceof Box2dRevoluteJoint) === (joint2 instanceof Box2dRevoluteJoint) ? 1 : -1;
1484
+ const jointDef = new box2d.instance.b2GearJointDef();
1485
+ jointDef.set_bodyA(objectA.body);
1486
+ jointDef.set_bodyB(objectB.body);
1487
+ jointDef.set_joint1(joint1.box2dJoint);
1488
+ jointDef.set_joint2(joint2.box2dJoint);
1489
+ jointDef.set_ratio(ratio * ratioSign);
1490
+ super(jointDef);
1491
+
1492
+ this.joint1 = joint1;
1493
+ this.joint2 = joint2;
1494
+ this.ratioSign = ratioSign;
1495
+ box2dGearJoints.add(this);
1496
+ }
1497
+
1498
+ /** Get the first joint
1499
+ * @return {Box2dJoint} */
1500
+ getJoint1() { return this.joint1; }
1501
+
1502
+ /** Get the second joint
1503
+ * @return {Box2dJoint} */
1504
+ getJoint2() { return this.joint2; }
1505
+
1506
+ /** Set the gear ratio
1507
+ * @param {number} ratio */
1508
+ setRatio(ratio) { this.box2dJoint.SetRatio(ratio * this.ratioSign); box2dWakeJoint(this.box2dJoint); }
1509
+
1510
+ /** Get the gear ratio
1511
+ * @return {number} */
1512
+ getRatio() { return this.box2dJoint.GetRatio() * this.ratioSign; }
1513
+ }
1514
+
1515
+ ///////////////////////////////////////////////////////////////////////////////
1516
+ /**
1517
+ * Box2D Prismatic Joint
1518
+ * - Provides one degree of freedom: translation along an axis fixed in objectA
1519
+ * - Relative rotation is prevented
1520
+ * - You can use a joint limit to restrict the range of motion
1521
+ * - You can use a joint motor to drive the motion or to model joint friction
1522
+ * @extends Box2dJoint
1523
+ * @memberof Box2D
1524
+ */
1525
+ class Box2dPrismaticJoint extends Box2dJoint
1526
+ {
1527
+ /** Create a prismatic joint
1528
+ * @param {Box2dObject} objectA
1529
+ * @param {Box2dObject} objectB
1530
+ * @param {Vector2} [anchor] - World position, objectB's position if not given
1531
+ * @param {Vector2} [worldAxis]
1532
+ * @param {boolean} [collide] */
1533
+ constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
1534
+ {
1535
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1536
+ const localAnchorA = objectA.worldToLocal(anchor);
1537
+ const localAnchorB = objectB.worldToLocal(anchor);
1538
+ const localAxisA = objectA.worldToLocalVector(worldAxis);
1539
+ const jointDef = new box2d.instance.b2PrismaticJointDef();
1540
+ jointDef.set_bodyA(objectA.body);
1541
+ jointDef.set_bodyB(objectB.body);
1542
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1543
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1544
+ jointDef.set_localAxisA(box2dTemp(localAxisA));
1545
+ jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
1546
+ jointDef.set_collideConnected(collide);
1547
+ super(jointDef);
1548
+ }
1549
+
1550
+ /** Get the local anchor point relative to objectA's origin
1551
+ * @return {Vector2} */
1552
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1553
+
1554
+ /** Get the local anchor point relative to objectB's origin
1555
+ * @return {Vector2} */
1556
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1557
+
1558
+ /** Get the local joint axis relative to bodyA
1559
+ * @return {Vector2} */
1560
+ getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
1561
+
1562
+ /** Get the reference angle, objectB angle minus objectA angle in the reference state
1563
+ * @return {number} */
1564
+ getReferenceAngle() { return -this.box2dJoint.GetReferenceAngle(); } // box2d uses reverse angle
1565
+
1566
+ /** Get the current joint translation
1567
+ * @return {number} */
1568
+ getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
1569
+
1570
+ /** Get the current joint translation speed
1571
+ * @return {number} */
1572
+ getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
1573
+
1574
+ /** Is the joint limit enabled?
1575
+ * @return {boolean} */
1576
+ isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
1577
+
1578
+ /** Enable/disable the joint limit
1579
+ * @param {boolean} [enable] */
1580
+ enableLimit(enable=true) { this.box2dJoint.EnableLimit(enable); }
1581
+
1582
+ /** Get the lower joint limit
1583
+ * @return {number} */
1584
+ getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
1585
+
1586
+ /** Get the upper joint limit
1587
+ * @return {number} */
1588
+ getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
1589
+
1590
+ /** Set the joint limits
1591
+ * @param {number} min
1592
+ * @param {number} max */
1593
+ setLimits(min, max)
1594
+ {
1595
+ ASSERT(min <= max, 'the lower limit must not be above the upper one');
1596
+ if (min > max) [min, max] = [max, min]; // Box2D stops on them reversed
1597
+ this.box2dJoint.SetLimits(min, max);
1598
+ }
1599
+
1600
+ /** Is the motor enabled?
1601
+ * @return {boolean} */
1602
+ isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1603
+
1604
+ /** Enable/disable the joint motor
1605
+ * @param {boolean} [enable] */
1606
+ enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
1607
+
1608
+ /** Set the motor speed
1609
+ * @param {number} speed */
1610
+ setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(speed); }
1611
+
1612
+ /** Get the motor speed
1613
+ * @return {number} */
1614
+ getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
1615
+
1616
+ /** Set the maximum motor force
1617
+ * @param {number} force */
1618
+ setMaxMotorForce(force) { this.box2dJoint.SetMaxMotorForce(force); }
1619
+
1620
+ /** Get the maximum motor force
1621
+ * @return {number} */
1622
+ getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
1623
+
1624
+ /** Get the motor force over the last step
1625
+ * @param {number} [time] - The step length in seconds, the world steps by timeDelta
1626
+ * @return {number} */
1627
+ getMotorForce(time=timeDelta) { return this.box2dJoint.GetMotorForce(1/time); }
1628
+ }
1629
+
1630
+ ///////////////////////////////////////////////////////////////////////////////
1631
+ /**
1632
+ * Box2D Wheel Joint
1633
+ * - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
1634
+ * - You can use a joint motor to drive the motion or to model joint friction
1635
+ * - This joint is designed for vehicle suspensions
1636
+ * @extends Box2dJoint
1637
+ * @memberof Box2D
1638
+ */
1639
+ class Box2dWheelJoint extends Box2dJoint
1640
+ {
1641
+ /** Create a wheel joint
1642
+ * @param {Box2dObject} objectA
1643
+ * @param {Box2dObject} objectB
1644
+ * @param {Vector2} [anchor] - World position, objectB's position if not given
1645
+ * @param {Vector2} [worldAxis]
1646
+ * @param {boolean} [collide] */
1647
+ constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
1648
+ {
1649
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1650
+ const localAnchorA = objectA.worldToLocal(anchor);
1651
+ const localAnchorB = objectB.worldToLocal(anchor);
1652
+ const localAxisA = objectA.worldToLocalVector(worldAxis).normalize(); // Box2D uses the wheel axis as given
1653
+ const jointDef = new box2d.instance.b2WheelJointDef();
1654
+ jointDef.set_bodyA(objectA.body);
1655
+ jointDef.set_bodyB(objectB.body);
1656
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1657
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1658
+ jointDef.set_localAxisA(box2dTemp(localAxisA));
1659
+ jointDef.set_collideConnected(collide);
1660
+ super(jointDef);
1661
+ }
1662
+
1663
+ /** Get the local anchor point relative to objectA's origin
1664
+ * @return {Vector2} */
1665
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1666
+
1667
+ /** Get the local anchor point relative to objectB's origin
1668
+ * @return {Vector2} */
1669
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1670
+
1671
+ /** Get the local joint axis relative to bodyA
1672
+ * @return {Vector2} */
1673
+ getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
1674
+
1675
+ /** Get the current joint translation
1676
+ * @return {number} */
1677
+ getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
1678
+
1679
+ /** Get the current joint rotation speed in radians per second, clockwise like angle,
1680
+ * which is what this version of Box2D measures for a wheel joint
1681
+ * @return {number} */
1682
+ getJointSpeed() { return -this.box2dJoint.GetJointSpeed(); } // box2d uses reverse angle
1683
+
1684
+ /** Is the joint motor enabled?
1685
+ * @return {boolean} */
1686
+ isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1687
+
1688
+ /** Enable/disable the joint motor
1689
+ * @param {boolean} [enable] */
1690
+ enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
1691
+
1692
+ /** Set the motor speed, the wheel's turn in radians per second, clockwise like angle
1693
+ * @param {number} speed */
1694
+ setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(-speed); }
1695
+
1696
+ /** Get the motor speed, clockwise like angle
1697
+ * @return {number} */
1698
+ getMotorSpeed() { return -this.box2dJoint.GetMotorSpeed(); }
1699
+
1700
+ /** Set the maximum motor torque, a magnitude
1701
+ * @param {number} torque */
1702
+ setMaxMotorTorque(torque) { this.box2dJoint.SetMaxMotorTorque(torque); }
1703
+
1704
+ /** Get the max motor torque
1705
+ * @return {number} */
1706
+ getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
1707
+
1708
+ /** Get the motor torque over the last step, clockwise like angle
1709
+ * @param {number} [time] - The step length in seconds, the world steps by timeDelta
1710
+ * @return {number} */
1711
+ getMotorTorque(time=timeDelta) { return -this.box2dJoint.GetMotorTorque(1/time); }
1712
+
1713
+ /** Set the spring frequency in Hertz
1714
+ * @param {number} hz */
1715
+ setSpringFrequencyHz(hz) { this.box2dJoint.SetSpringFrequencyHz(hz); box2dWakeJoint(this.box2dJoint); }
1716
+
1717
+ /** Get the spring frequency in Hertz
1718
+ * @return {number} */
1719
+ getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
1720
+
1721
+ /** Set the spring damping ratio
1722
+ * @param {number} ratio */
1723
+ setSpringDampingRatio(ratio) { this.box2dJoint.SetSpringDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
1724
+
1725
+ /** Get the spring damping ratio
1726
+ * @return {number} */
1727
+ getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
1728
+ }
1729
+
1730
+ ///////////////////////////////////////////////////////////////////////////////
1731
+ /**
1732
+ * Box2D Weld Joint
1733
+ * - Glues two objects together
1734
+ * @extends Box2dJoint
1735
+ * @memberof Box2D
1736
+ */
1737
+ class Box2dWeldJoint extends Box2dJoint
1738
+ {
1739
+ /** Create a weld joint
1740
+ * @param {Box2dObject} objectA
1741
+ * @param {Box2dObject} objectB
1742
+ * @param {Vector2} [anchor] - World position, objectB's position if not given
1743
+ * @param {boolean} [collide] */
1744
+ constructor(objectA, objectB, anchor, collide=false)
1745
+ {
1746
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1747
+ const localAnchorA = objectA.worldToLocal(anchor);
1748
+ const localAnchorB = objectB.worldToLocal(anchor);
1749
+ const referenceAngle = objectB.body.GetAngle() - objectA.body.GetAngle();
1750
+ const jointDef = new box2d.instance.b2WeldJointDef();
1751
+ jointDef.set_bodyA(objectA.body);
1752
+ jointDef.set_bodyB(objectB.body);
1753
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1754
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1755
+ jointDef.set_referenceAngle(referenceAngle);
1756
+ jointDef.set_collideConnected(collide);
1757
+ super(jointDef);
1758
+
1759
+ // kept here, since the binding cannot read it back from a weld joint; box2d uses reverse angle
1760
+ this.referenceAngle = -referenceAngle;
1761
+ }
1762
+
1763
+ /** Get the local anchor point relative to objectA's origin
1764
+ * @return {Vector2} */
1765
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1766
+
1767
+ /** Get the local anchor point relative to objectB's origin
1768
+ * @return {Vector2} */
1769
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1770
+
1771
+ /** Get the reference angle, objectB angle minus objectA angle in the reference state
1772
+ * @return {number} */
1773
+ getReferenceAngle() { return this.referenceAngle; }
1774
+
1775
+ /** Set the frequency in Hertz
1776
+ * @param {number} hz */
1777
+ setFrequency(hz) { this.box2dJoint.SetFrequency(hz); box2dWakeJoint(this.box2dJoint); }
1778
+
1779
+ /** Get the frequency in Hertz
1780
+ * @return {number} */
1781
+ getFrequency() { return this.box2dJoint.GetFrequency(); }
1782
+
1783
+ /** Set the damping ratio
1784
+ * @param {number} ratio */
1785
+ setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
1786
+
1787
+ /** Get the damping ratio
1788
+ * @return {number} */
1789
+ getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
1790
+
1791
+ /** @deprecated since 1.20, use setDampingRatio
1792
+ * @param {number} ratio */
1793
+ setSpringDampingRatio(ratio) { this.setDampingRatio(ratio); }
1794
+
1795
+ /** @deprecated since 1.20, use getDampingRatio
1796
+ * @return {number} */
1797
+ getSpringDampingRatio() { return this.getDampingRatio(); }
1798
+ }
1799
+
1800
+ ///////////////////////////////////////////////////////////////////////////////
1801
+ /**
1802
+ * Box2D Friction Joint
1803
+ * - Used to apply top-down friction
1804
+ * - Provides 2D translational friction and angular friction
1805
+ * @extends Box2dJoint
1806
+ * @memberof Box2D
1807
+ */
1808
+ class Box2dFrictionJoint extends Box2dJoint
1809
+ {
1810
+ /** Create a friction joint
1811
+ * @param {Box2dObject} objectA
1812
+ * @param {Box2dObject} objectB
1813
+ * @param {Vector2} [anchor] - World position, objectB's position if not given
1814
+ * @param {boolean} [collide] */
1815
+ constructor(objectA, objectB, anchor, collide=false)
1816
+ {
1817
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1818
+ const localAnchorA = objectA.worldToLocal(anchor);
1819
+ const localAnchorB = objectB.worldToLocal(anchor);
1820
+ const jointDef = new box2d.instance.b2FrictionJointDef();
1821
+ jointDef.set_bodyA(objectA.body);
1822
+ jointDef.set_bodyB(objectB.body);
1823
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1824
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1825
+ jointDef.set_collideConnected(collide);
1826
+ super(jointDef);
1827
+ }
1828
+
1829
+ /** Get the local anchor point relative to objectA's origin
1830
+ * @return {Vector2} */
1831
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1832
+
1833
+ /** Get the local anchor point relative to objectB's origin
1834
+ * @return {Vector2} */
1835
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1836
+
1837
+ /** Set the maximum friction force
1838
+ * @param {number} force */
1839
+ setMaxForce(force) { this.box2dJoint.SetMaxForce(max(force, 0)); } // Box2D stops on a negative one
1840
+
1841
+ /** Get the maximum friction force
1842
+ * @return {number} */
1843
+ getMaxForce() { return this.box2dJoint.GetMaxForce(); }
1844
+
1845
+ /** Set the maximum friction torque
1846
+ * @param {number} torque */
1847
+ setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(torque, 0)); } // Box2D stops on a negative one
1848
+
1849
+ /** Get the maximum friction torque
1850
+ * @return {number} */
1851
+ getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
1852
+ }
1853
+
1854
+ ///////////////////////////////////////////////////////////////////////////////
1855
+ /**
1856
+ * Box2D Pulley Joint
1857
+ * - Connects to two objects and two fixed ground points
1858
+ * - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
1859
+ * - The force transmitted is scaled by the ratio
1860
+ * @extends Box2dJoint
1861
+ * @memberof Box2D
1862
+ */
1863
+ class Box2dPulleyJoint extends Box2dJoint
1864
+ {
1865
+ /** Create a pulley joint
1866
+ * @param {Box2dObject} objectA
1867
+ * @param {Box2dObject} objectB
1868
+ * @param {Vector2} groundAnchorA
1869
+ * @param {Vector2} groundAnchorB
1870
+ * @param {Vector2} [anchorA] - World position, objectA's position if not given
1871
+ * @param {Vector2} [anchorB] - World position, objectB's position if not given
1872
+ * @param {number} [ratio]
1873
+ * @param {boolean} [collide] */
1874
+ constructor(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
1875
+ {
1876
+ anchorA ||= box2d.vec2From(objectA.body.GetPosition());
1877
+ anchorB ||= box2d.vec2From(objectB.body.GetPosition());
1878
+ const localAnchorA = objectA.worldToLocal(anchorA);
1879
+ const localAnchorB = objectB.worldToLocal(anchorB);
1880
+ const jointDef = new box2d.instance.b2PulleyJointDef();
1881
+ jointDef.set_bodyA(objectA.body);
1882
+ jointDef.set_bodyB(objectB.body);
1883
+ jointDef.set_groundAnchorA(box2dTemp(groundAnchorA));
1884
+ jointDef.set_groundAnchorB(box2dTemp(groundAnchorB));
1885
+ jointDef.set_localAnchorA(box2dTemp(localAnchorA));
1886
+ jointDef.set_localAnchorB(box2dTemp(localAnchorB));
1887
+ ASSERT(ratio, 'a pulley ratio can not be 0');
1888
+ jointDef.set_ratio(ratio);
1889
+ jointDef.set_lengthA(groundAnchorA.distance(anchorA));
1890
+ jointDef.set_lengthB(groundAnchorB.distance(anchorB));
1891
+ jointDef.set_collideConnected(collide);
1892
+ super(jointDef);
1893
+ }
1894
+
1895
+ /** Get the first ground anchor
1896
+ * @return {Vector2} */
1897
+ getGroundAnchorA() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorA()); }
1898
+
1899
+ /** Get the second ground anchor
1900
+ * @return {Vector2} */
1901
+ getGroundAnchorB() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorB()); }
1902
+
1903
+ /** Get the rest length of the segment attached to objectA, set at creation
1904
+ * @return {number} */
1905
+ getLengthA() { return this.box2dJoint.GetLengthA(); }
1906
+
1907
+ /** Get the rest length of the segment attached to objectB, set at creation
1908
+ * @return {number} */
1909
+ getLengthB(){ return this.box2dJoint.GetLengthB(); }
1910
+
1911
+ /** Get the pulley ratio
1912
+ * @return {number} */
1913
+ getRatio() { return this.box2dJoint.GetRatio(); }
1914
+
1915
+ /** Get the current length of the segment attached to objectA
1916
+ * @return {number} */
1917
+ getCurrentLengthA() { return this.box2dJoint.GetCurrentLengthA(); }
1918
+
1919
+ /** Get the current length of the segment attached to objectB
1920
+ * @return {number} */
1921
+ getCurrentLengthB() { return this.box2dJoint.GetCurrentLengthB(); }
1922
+ }
1923
+
1924
+ ///////////////////////////////////////////////////////////////////////////////
1925
+ /**
1926
+ * Box2D Motor Joint
1927
+ * - Controls the relative motion between two objects
1928
+ * - Typical usage is to control the movement of a object with respect to the ground
1929
+ * @extends Box2dJoint
1930
+ * @memberof Box2D
1931
+ */
1932
+ class Box2dMotorJoint extends Box2dJoint
1933
+ {
1934
+ /** Create a motor joint
1935
+ * @param {Box2dObject} objectA
1936
+ * @param {Box2dObject} objectB */
1937
+ constructor(objectA, objectB)
1938
+ {
1939
+ const linearOffset = objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));
1940
+ const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
1941
+ const jointDef = new box2d.instance.b2MotorJointDef();
1942
+ jointDef.set_bodyA(objectA.body);
1943
+ jointDef.set_bodyB(objectB.body);
1944
+ jointDef.set_linearOffset(box2dTemp(linearOffset));
1945
+ jointDef.set_angularOffset(angularOffset);
1946
+ super(jointDef);
1947
+ }
1948
+
1949
+ /** Set the target linear offset, in frame A, in meters.
1950
+ * @param {Vector2} offset */
1951
+ setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2dTemp(offset)); }
1952
+
1953
+ /** Get the target linear offset, in frame A, in meters.
1954
+ * @return {Vector2} */
1955
+ getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
1956
+
1957
+ /** Set the target angular offset, objectB angle minus objectA angle, clockwise like angle
1958
+ * @param {number} offset */
1959
+ setAngularOffset(offset) { this.box2dJoint.SetAngularOffset(-offset); } // box2d uses reverse angle
1960
+
1961
+ /** Get the target angular offset, objectB angle minus objectA angle, clockwise like angle
1962
+ * @return {number} */
1963
+ getAngularOffset() { return -this.box2dJoint.GetAngularOffset(); }
1964
+
1965
+ /** Set the maximum force
1966
+ * @param {number} force */
1967
+ setMaxForce(force) { this.box2dJoint.SetMaxForce(max(force, 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
1968
+
1969
+ /** Get the maximum force
1970
+ * @return {number} */
1971
+ getMaxForce() { return this.box2dJoint.GetMaxForce(); }
1972
+
1973
+ /** Set the maximum torque
1974
+ * @param {number} torque */
1975
+ setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(torque, 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
1976
+
1977
+ /** Get the maximum torque
1978
+ * @return {number} */
1979
+ getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
1980
+
1981
+ /** Set the position correction factor in the range [0,1]
1982
+ * @param {number} factor */
1983
+ setCorrectionFactor(factor) { this.box2dJoint.SetCorrectionFactor(clamp(factor)); box2dWakeJoint(this.box2dJoint); }
1984
+
1985
+ /** Get the position correction factor in the range [0,1]
1986
+ * @return {number} */
1987
+ getCorrectionFactor() { return this.box2dJoint.GetCorrectionFactor(); }
1988
+ }
1989
+
1990
+ ///////////////////////////////////////////////////////////////////////////////
1991
+ /**
1992
+ * Box2D Global Object
1993
+ * - Wraps Box2d world and provides global functions
1994
+ * @memberof Box2D
1995
+ */
1996
+ class Box2dPlugin
1997
+ {
1998
+ /** Create the global Box2D plugin object, box2dInit does this
1999
+ * @param {Object} instance */
2000
+ constructor(instance)
2001
+ {
2002
+ ASSERT(!box2d, 'Box2D already initialized');
2003
+ box2d = this;
2004
+
2005
+ /** @property {Object} - The Box2d instance */
2006
+ this.instance = instance;
2007
+ /** @property {Object} - The Box2d world */
2008
+ this.world = new box2d.instance.b2World();
2009
+ /** @property {Array<Box2dObject>} - List of all Box2d objects, a destroyed one stays until the next step
2010
+ * with its body undefined
2011
+ * @type {Array<Box2dObject>} */
2012
+ this.objects = [];
2013
+ /** @property {number} - Velocity iterations per update*/
2014
+ this.velocityIterations = 8;
2015
+ /** @property {number} - Position iterations per update*/
2016
+ this.positionIterations = 3;
2017
+ /** @property {number} - Static, zero mass, zero velocity, may be manually moved
2018
+ * @type {number} */
2019
+ this.bodyTypeStatic = instance.b2_staticBody;
2020
+ /** @property {number} - Kinematic, zero mass, non-zero velocity set by user, moved by solver
2021
+ * @type {number} */
2022
+ this.bodyTypeKinematic = instance.b2_kinematicBody;
2023
+ /** @property {number} - Dynamic, positive mass, non-zero velocity determined by forces, moved by solver
2024
+ * @type {number} */
2025
+ this.bodyTypeDynamic = instance.b2_dynamicBody;
2026
+
2027
+ // a body that goes takes its joints with it, their wrappers let go of them, and a gear joint on one of them
2028
+ // goes too, since it would keep pointers to what was freed; that destroy waits in the queue, which DestroyBody
2029
+ // runs from, and is skipped if the gear hung off this same body and went with it
2030
+ const destructionListener = new box2d.instance.JSDestructionListener();
2031
+ destructionListener.SayGoodbyeJoint = function(jointPointer)
2032
+ {
2033
+ const joint = box2dJoints.get(jointPointer);
2034
+ if (joint)
2035
+ {
2036
+ box2dDestroyGears(joint);
2037
+ joint.box2dJoint = 0;
2038
+ }
2039
+ box2dJoints.delete(jointPointer);
2040
+ };
2041
+ destructionListener.SayGoodbyeFixture = function() {};
2042
+ box2d.world.SetDestructionListener(destructionListener);
2043
+
2044
+ // setup contact listener
2045
+ const listener = new box2d.instance.JSContactListener();
2046
+ listener.BeginContact = function(contactPtr)
2047
+ {
2048
+ const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
2049
+ const fixtureA = contact.GetFixtureA();
2050
+ const fixtureB = contact.GetFixtureB();
2051
+ const objectA = fixtureA.GetBody().object;
2052
+ const objectB = fixtureB.GetBody().object;
2053
+ // raw user-created b2Bodies may have no .object — skip those
2054
+ if (!objectA || !objectB) return;
2055
+ objectA.beginContact(objectB, fixtureA, fixtureB);
2056
+ objectB.beginContact(objectA, fixtureB, fixtureA);
2057
+ }
2058
+ listener.EndContact = function(contactPtr)
2059
+ {
2060
+ const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
2061
+ const fixtureA = contact.GetFixtureA();
2062
+ const fixtureB = contact.GetFixtureB();
2063
+ const objectA = fixtureA.GetBody().object;
2064
+ const objectB = fixtureB.GetBody().object;
2065
+ if (!objectA || !objectB) return;
2066
+ objectA.endContact(objectB, fixtureA, fixtureB);
2067
+ objectB.endContact(objectA, fixtureB, fixtureA);
2068
+ };
2069
+ listener.PreSolve = function() {};
2070
+ listener.PostSolve = function() {};
2071
+ box2d.world.SetContactListener(listener);
2072
+ }
2073
+
2074
+ /** Step the physics world simulation
2075
+ * @param {number} [frames] */
2076
+ step(frames=1)
2077
+ {
2078
+ // the engine's gravity, Box2D does not wake a sleeping body for a new one, so a change wakes them all
2079
+ if (gravity.x !== box2dGravity.x || gravity.y !== box2dGravity.y)
2080
+ {
2081
+ box2dGravity.x = gravity.x, box2dGravity.y = gravity.y;
2082
+ box2d.world.SetGravity(box2dTemp(gravity));
2083
+ for (let b = box2d.world.GetBodyList(); !box2d.isNull(b); b = b.GetNext())
2084
+ b.SetAwake(true);
2085
+ }
2086
+ for (let i=frames; i--;)
2087
+ {
2088
+ box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
2089
+
2090
+ // what a contact callback destroyed or changed, now the world can take it
2091
+ box2dRunPending();
2092
+ }
2093
+
2094
+ // remove destroyed objects, once for all of them
2095
+ this.objects = this.objects.filter(o=>!o.destroyed);
2096
+ }
2097
+
2098
+ ///////////////////////////////////////////////////////////////////////////////
2099
+ // raycasting and querying
2100
+
2101
+ /** raycast and return a list of all the results, nearest first
2102
+ * @param {Vector2} start
2103
+ * @param {Vector2} end
2104
+ * @param {boolean} [includeSensors] - Also hit sensors, trigger zones are passed through by default
2105
+ * @return {Array<Box2dRaycastResult>} */
2106
+ raycastAll(start, end, includeSensors=false)
2107
+ {
2108
+ // a ray with no length fails an assert that stops Box2D for good, measured as Box2D does in 32 bit floats,
2109
+ // where two ends a float apart are one point; one that is not a number has no length either
2110
+ const f = Math.fround, dx = f(f(end.x) - f(start.x)), dy = f(f(end.y) - f(start.y));
2111
+ const lengthSquared = f(f(dx*dx) + f(dy*dy));
2112
+ if (!(lengthSquared > 0 && lengthSquared < Infinity))
2113
+ return [];
2114
+
2115
+ const raycastCallback = box2dQueryObject('rayCast', 'JSRayCastCallback');
2116
+ raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
2117
+ {
2118
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
2119
+ const o = fixture.GetBody().object;
2120
+ if (!o || o.destroyed)
2121
+ return 1; // a raw body with no Box2dObject or one destroyed this step, continue getting results
2122
+ if (!includeSensors && fixture.IsSensor())
2123
+ return -1; // skip it, Box2D goes on as if it were not there
2124
+ point = box2d.vec2FromPointer(point);
2125
+ normal = box2d.vec2FromPointer(normal);
2126
+ raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
2127
+ return 1; // continue getting results
2128
+ };
2129
+
2130
+ const raycastResults = [];
2131
+ box2d.world.RayCast(raycastCallback, box2dTemp(start), box2dTemp(end, 1));
2132
+ raycastResults.sort((a,b)=> a.fraction - b.fraction); // Box2D reports them in its tree's order
2133
+ debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02, 0, false);
2134
+ return raycastResults;
2135
+ }
2136
+
2137
+ /** raycast and return the first result
2138
+ * @param {Vector2} start
2139
+ * @param {Vector2} end
2140
+ * @param {boolean} [includeSensors] - Also hit sensors, trigger zones are passed through by default
2141
+ * @return {Box2dRaycastResult|undefined} */
2142
+ raycast(start, end, includeSensors=false)
2143
+ {
2144
+ return box2d.raycastAll(start, end, includeSensors)[0];
2145
+ }
2146
+
2147
+ /** box aabb cast and return all the objects
2148
+ * @param {Vector2} pos
2149
+ * @param {Vector2} size
2150
+ * @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
2151
+ * @return {Array<Box2dObject>} */
2152
+ boxCastAll(pos, size, includeSensors=false)
2153
+ {
2154
+ const queryCallback = box2dQueryObject('query', 'JSQueryCallback');
2155
+ queryCallback.ReportFixture = function(fixturePointer)
2156
+ {
2157
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
2158
+ if (!includeSensors && fixture.IsSensor())
2159
+ return true; // a trigger zone, continue getting results
2160
+ const o = fixture.GetBody().object;
2161
+ if (o && !o.destroyed && !queryObjects.includes(o) // skip raw bodies and ones destroyed this step
2162
+ && box2dFixtureOverlaps(fixture, aabb))
2163
+ queryObjects.push(o); // add if not already in list
2164
+ return true; // continue getting results
2165
+ };
2166
+
2167
+ const aabb = box2dQueryObject('aabb', 'b2AABB');
2168
+ aabb.set_lowerBound(box2dTemp(pos.subtract(size.scale(.5))));
2169
+ aabb.set_upperBound(box2dTemp(pos.add(size.scale(.5))));
2170
+
2171
+ let queryObjects = [];
2172
+ box2d.world.QueryAABB(queryCallback, aabb);
2173
+ debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', 0, 0, false, false);
2174
+ return queryObjects;
2175
+ }
2176
+
2177
+ /** box aabb cast and return the first object
2178
+ * @param {Vector2} pos
2179
+ * @param {Vector2} size
2180
+ * @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
2181
+ * @return {Box2dObject|undefined} */
2182
+ boxCast(pos, size, includeSensors=false)
2183
+ {
2184
+ const queryCallback = box2dQueryObject('query', 'JSQueryCallback');
2185
+ queryCallback.ReportFixture = function(fixturePointer)
2186
+ {
2187
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
2188
+ if (!includeSensors && fixture.IsSensor())
2189
+ return true; // a trigger zone, continue getting results
2190
+ const o = fixture.GetBody().object;
2191
+ if (!o || o.destroyed)
2192
+ return true; // a raw body with no Box2dObject or one destroyed this step, continue getting results
2193
+ if (!box2dFixtureOverlaps(fixture, aabb))
2194
+ return true; // only near the box, continue getting results
2195
+ queryObject = o;
2196
+ return false; // stop getting results
2197
+ };
2198
+
2199
+ const aabb = box2dQueryObject('aabb', 'b2AABB');
2200
+ aabb.set_lowerBound(box2dTemp(pos.subtract(size.scale(.5))));
2201
+ aabb.set_upperBound(box2dTemp(pos.add(size.scale(.5))));
2202
+
2203
+ let queryObject;
2204
+ box2d.world.QueryAABB(queryCallback, aabb);
2205
+ debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', 0, 0, false, false);
2206
+ return queryObject;
2207
+ }
2208
+
2209
+ /** circle cast and return all the objects whose position is within the circle, wherever their shapes are
2210
+ * @param {Vector2} pos
2211
+ * @param {number} diameter
2212
+ * @param {boolean} [includeSensors] - Also find objects whose shapes are all sensors, like trigger zones
2213
+ * @return {Array<Box2dObject>} */
2214
+ circleCastAll(pos, diameter, includeSensors=false)
2215
+ {
2216
+ // by each object's position, so an object whose shapes are offset from it is still found
2217
+ const radius2 = (diameter/2)**2;
2218
+ const results = box2d.objects.filter(o=> !o.destroyed && o.body && o.pos.distanceSquared(pos) < radius2 &&
2219
+ (includeSensors || o.getFixtureList().some(fixture=> !fixture.IsSensor())));
2220
+ debugRaycast && debugCircle(pos, diameter, results.length ? '#f00' : '#00f', 0, false, false);
2221
+ return results;
2222
+ }
2223
+
2224
+ /** circle cast and return the object whose position is nearest, of those within the circle
2225
+ * @param {Vector2} pos
2226
+ * @param {number} diameter
2227
+ * @param {boolean} [includeSensors] - Also find objects whose shapes are all sensors, like trigger zones
2228
+ * @return {Box2dObject|undefined} */
2229
+ circleCast(pos, diameter, includeSensors=false)
2230
+ {
2231
+ let bestResult, bestDistance2;
2232
+ for (const result of box2d.circleCastAll(pos, diameter, includeSensors))
2233
+ {
2234
+ const distance2 = result.pos.distanceSquared(pos);
2235
+ if (!bestResult || distance2 < bestDistance2)
2236
+ {
2237
+ bestResult = result;
2238
+ bestDistance2 = distance2;
2239
+ }
2240
+ }
2241
+ return bestResult;
2242
+ }
2243
+
2244
+ /** point cast and return the first object
2245
+ * @param {Vector2} pos
2246
+ * @param {boolean} [dynamicOnly]
2247
+ * @param {boolean} [includeSensors] - Also find sensors, so a pickup radius does not grab its object from afar
2248
+ * @return {Box2dObject|undefined} */
2249
+ pointCast(pos, dynamicOnly=true, includeSensors=false)
2250
+ {
2251
+ const queryCallback = box2dQueryObject('query', 'JSQueryCallback');
2252
+ queryCallback.ReportFixture = function(fixturePointer)
2253
+ {
2254
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
2255
+ if (!includeSensors && fixture.IsSensor())
2256
+ return true; // a trigger zone, continue getting results
2257
+ if (dynamicOnly && fixture.GetBody().GetType() !== box2d.bodyTypeDynamic)
2258
+ return true; // continue getting results
2259
+ if (!fixture.TestPoint(box2dTemp(pos)))
2260
+ return true; // continue getting results
2261
+ const o = fixture.GetBody().object;
2262
+ if (!o || o.destroyed)
2263
+ return true; // a raw body with no Box2dObject or one destroyed this step, continue getting results
2264
+ queryObject = o;
2265
+ return false; // stop getting results
2266
+ };
2267
+
2268
+ const aabb = box2dQueryObject('aabb', 'b2AABB');
2269
+ aabb.set_lowerBound(box2dTemp(pos));
2270
+ aabb.set_upperBound(box2dTemp(pos));
2271
+
2272
+ let queryObject;
2273
+ box2d.world.QueryAABB(queryCallback, aabb);
2274
+ debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', 0, 0, false, false);
2275
+ return queryObject;
2276
+ }
2277
+
2278
+ ///////////////////////////////////////////////////////////////////////////////
2279
+ // drawing
2280
+
2281
+ /** draws a fixture
2282
+ * @param {Object} fixture
2283
+ * @param {Vector2} pos
2284
+ * @param {number} angle
2285
+ * @param {Color} [color]
2286
+ * @param {Color} [lineColor]
2287
+ * @param {number} [lineWidth]
2288
+ * @param {boolean} [useWebGL=glEnable]
2289
+ * @param {CanvasRenderingContext2D} [context] */
2290
+ drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
2291
+ {
2292
+ const shape = box2d.castShapeObject(fixture.GetShape());
2293
+ switch (shape.GetType())
2294
+ {
2295
+ case box2d.instance.b2Shape.e_polygon:
2296
+ {
2297
+ let points = [];
2298
+ for (let i=shape.GetVertexCount(); i--;)
2299
+ points.push(box2d.vec2From(shape.GetVertex(i)));
2300
+ drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
2301
+ break;
2302
+ }
2303
+ case box2d.instance.b2Shape.e_circle:
2304
+ {
2305
+ const radius = shape.get_m_radius(), offset = box2d.vec2From(shape.get_m_p());
2306
+ drawCircle(pos.add(offset.rotate(angle)), radius*2, color, lineWidth, lineColor, useWebGL, false, context);
2307
+ break;
2308
+ }
2309
+ case box2d.instance.b2Shape.e_edge:
2310
+ {
2311
+ const v1 = box2d.vec2From(shape.get_m_vertex1());
2312
+ const v2 = box2d.vec2From(shape.get_m_vertex2());
2313
+ drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
2314
+ break;
2315
+ }
2316
+ }
2317
+ }
2318
+
2319
+ ///////////////////////////////////////////////////////////////////////////////
2320
+ // helper functions
2321
+
2322
+ /** converts a box2d vec2 to a Vector2
2323
+ * @param {Object} v */
2324
+ vec2From(v)
2325
+ {
2326
+ ASSERT(v instanceof box2d.instance.b2Vec2);
2327
+ return new Vector2(v.get_x(), v.get_y());
2328
+ }
2329
+
2330
+ /** converts a box2d vec2 pointer to a Vector2
2331
+ * @param {Object} vp */
2332
+ vec2FromPointer(vp)
2333
+ {
2334
+ const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
2335
+ return box2d.vec2From(v);
2336
+ }
2337
+
2338
+ /** converts a Vector2 to a new box2d vec2, which stays until destroyed with box2d.instance.destroy;
2339
+ * the plugin itself passes Box2D reused ones, since Box2D copies every vector it is given
2340
+ * @param {Vector2} v */
2341
+ vec2dTo(v)
2342
+ {
2343
+ ASSERT(isVector2(v));
2344
+ return new box2d.instance.b2Vec2(v.x, v.y);
2345
+ }
2346
+
2347
+ /** checks if a box2d object is null
2348
+ * @param {Object} o */
2349
+ isNull(o) { return !box2d.instance.getPointer(o); }
2350
+
2351
+ /** casts a box2d object to a shape type
2352
+ * @param {Object} o */
2353
+ castShapeObject(o)
2354
+ {
2355
+ switch (o.GetType())
2356
+ {
2357
+ case box2d.instance.b2Shape.e_circle:
2358
+ return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
2359
+ case box2d.instance.b2Shape.e_edge:
2360
+ return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
2361
+ case box2d.instance.b2Shape.e_polygon:
2362
+ return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
2363
+ case box2d.instance.b2Shape.e_chain:
2364
+ return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
2365
+ }
2366
+
2367
+ ASSERT(false, 'Unknown box2d object type');
2368
+ }
2369
+
2370
+ /** casts a box2d object to a joint type
2371
+ * @param {Object} o */
2372
+ castJointObject(o)
2373
+ {
2374
+ switch (o.GetType())
2375
+ {
2376
+ case box2d.instance.e_revoluteJoint:
2377
+ return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
2378
+ case box2d.instance.e_prismaticJoint:
2379
+ return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
2380
+ case box2d.instance.e_distanceJoint:
2381
+ return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
2382
+ case box2d.instance.e_pulleyJoint:
2383
+ return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
2384
+ case box2d.instance.e_mouseJoint:
2385
+ return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
2386
+ case box2d.instance.e_gearJoint:
2387
+ return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
2388
+ case box2d.instance.e_wheelJoint:
2389
+ return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
2390
+ case box2d.instance.e_weldJoint:
2391
+ return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
2392
+ case box2d.instance.e_frictionJoint:
2393
+ return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
2394
+ case box2d.instance.e_ropeJoint:
2395
+ return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
2396
+ case box2d.instance.e_motorJoint:
2397
+ return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
2398
+ }
2399
+
2400
+ ASSERT(false, 'Unknown box2d object type');
2401
+ }
2402
+ }
2403
+
2404
+ ///////////////////////////////////////////////////////////////////////////////
2405
+ /** Box2d Init - Call with await to init box2d
2406
+ * @example
2407
+ * await box2dInit();
2408
+ * @return {Promise<Box2dPlugin>}
2409
+ * @memberof Box2D */
2410
+ async function box2dInit()
2411
+ {
2412
+ // load box2d
2413
+ // @ts-ignore - Box2D is the global that box2d.wasm.js defines
2414
+ new Box2dPlugin(await Box2D());
2415
+ setupDebugDraw();
2416
+ engineAddPlugin(box2dUpdate, box2dRender);
2417
+ return box2d;
2418
+
2419
+ // add the box2d plugin to the engine
2420
+ function box2dUpdate()
2421
+ {
2422
+ // frozen like the engine objects while paused or time is stopped
2423
+ if (paused || !timeScale)
2424
+ {
2425
+ // what was destroyed while frozen leaves the list, the step does it otherwise
2426
+ box2d.objects = box2d.objects.filter(o=>!o.destroyed);
2427
+ return;
2428
+ }
2429
+
2430
+ box2d.step();
2431
+
2432
+ // copy box2d physics results to engine objects
2433
+ for (const o of box2d.objects)
2434
+ {
2435
+ if (o.body)
2436
+ {
2437
+ // box2d uses reverse angle
2438
+ o.pos = box2d.vec2From(o.body.GetPosition());
2439
+ o.angle = -o.body.GetAngle();
2440
+ }
2441
+ }
2442
+ }
2443
+ function box2dRender()
2444
+ {
2445
+ if (box2dDebug || debugPhysics)
2446
+ box2d.world.DrawDebugData();
2447
+ }
2448
+
2449
+ // box2d debug drawing
2450
+ function setupDebugDraw()
2451
+ {
2452
+ // setup debug draw
2453
+ const debugLineWidth = .1;
2454
+ const debugDraw = new box2d.instance.JSDraw();
2455
+ const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
2456
+ const box2dColorPointer = (c)=>
2457
+ box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
2458
+ const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
2459
+ const getPointsList = (vertices, vertexCount)=>
2460
+ {
2461
+ const points = [];
2462
+ for (let i=vertexCount; i--;)
2463
+ points.push(box2d.vec2FromPointer(vertices+i*8));
2464
+ return points;
2465
+ }
2466
+ debugDraw.DrawSegment = function(point1, point2, color)
2467
+ {
2468
+ color = getDebugColor(color);
2469
+ point1 = box2d.vec2FromPointer(point1);
2470
+ point2 = box2d.vec2FromPointer(point2);
2471
+ drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false, false);
2472
+ };
2473
+ debugDraw.DrawPolygon = function(vertices, vertexCount, color)
2474
+ {
2475
+ color = getDebugColor(color);
2476
+ const points = getPointsList(vertices, vertexCount);
2477
+ drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false, false);
2478
+ };
2479
+ debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
2480
+ {
2481
+ color = getDebugColor(color);
2482
+ const points = getPointsList(vertices, vertexCount);
2483
+ drawPoly(points, color, 0, color, vec2(), 0, false, false);
2484
+ };
2485
+ debugDraw.DrawCircle = function(center, radius, color)
2486
+ {
2487
+ color = getDebugColor(color);
2488
+ center = box2d.vec2FromPointer(center);
2489
+ drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false, false);
2490
+ };
2491
+ debugDraw.DrawSolidCircle = function(center, radius, axis, color)
2492
+ {
2493
+ color = getDebugColor(color);
2494
+ center = box2d.vec2FromPointer(center);
2495
+ axis = box2d.vec2FromPointer(axis).scale(radius);
2496
+ drawCircle(center, radius*2, color, debugLineWidth, color, false, false);
2497
+ drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false);
2498
+ };
2499
+ debugDraw.DrawTransform = function(transform)
2500
+ {
2501
+ transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
2502
+ const pos = box2d.vec2From(transform.get_p());
2503
+ const angle = -transform.get_q().GetAngle();
2504
+ const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
2505
+ const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
2506
+ drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false, false);
2507
+ drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false, false);
2508
+ }
2509
+
2510
+ debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
2511
+ debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
2512
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
2513
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
2514
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
2515
+ box2d.world.SetDebugDraw(debugDraw);
2516
+ }
2074
2517
  }