littlejsengine 1.11.13 → 1.11.17

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/dist/littlejs.d.ts +1306 -66
  2. package/dist/littlejs.esm.js +3097 -269
  3. package/dist/littlejs.esm.min.js +1 -1
  4. package/dist/littlejs.js +253 -265
  5. package/dist/littlejs.min.js +1 -1
  6. package/dist/littlejs.release.js +155 -260
  7. package/examples/box2d/game.js +7 -10
  8. package/examples/box2d/gameObjects.js +33 -33
  9. package/examples/box2d/index.html +6 -6
  10. package/examples/box2d/scenes.js +28 -28
  11. package/examples/breakout/game.js +1 -4
  12. package/examples/breakout/index.html +4 -4
  13. package/examples/breakoutTutorial/index.html +2 -2
  14. package/examples/htmlMenu/game.js +3 -6
  15. package/examples/htmlMenu/index.html +2 -2
  16. package/examples/module/index.html +1 -1
  17. package/examples/particles/index.html +1 -1
  18. package/examples/platformer/game.js +0 -3
  19. package/examples/platformer/index.html +8 -8
  20. package/examples/puzzle/game.js +0 -3
  21. package/examples/puzzle/index.html +2 -2
  22. package/examples/shorts/base.html +1 -1
  23. package/examples/starter/build.js +4 -4
  24. package/examples/starter/index.html +13 -13
  25. package/examples/stress/index.html +3 -2
  26. package/examples/uiSystem/game.js +1 -7
  27. package/examples/uiSystem/index.html +3 -3
  28. package/package.json +3 -3
  29. package/plugins/box2d.js +1552 -640
  30. package/plugins/{Box2D_v2.3.1_min.wasm.js → box2d.wasm.js} +1 -1
  31. package/plugins/newgrounds.js +7 -8
  32. package/plugins/pluginExport.js +51 -0
  33. package/plugins/postProcess.js +94 -93
  34. package/plugins/uiSystem.js +145 -161
  35. package/plugins/zzfxm.js +163 -0
  36. package/src/engine.js +15 -20
  37. package/src/engineAudio.js +74 -204
  38. package/src/engineBuild.js +21 -3
  39. package/src/engineDebug.js +104 -6
  40. package/src/engineDraw.js +27 -14
  41. package/src/engineExport.js +12 -4
  42. package/src/engineParticles.js +6 -1
  43. package/src/engineRelease.js +6 -1
  44. package/src/engineSettings.js +2 -2
  45. package/src/engineUtilities.js +5 -11
  46. package/src/engineWebGL.js +19 -7
  47. package/examples/starter/build/index.html +0 -2
  48. package/examples/starter/build/index.js +0 -1
  49. package/examples/starter/build/tiles.png +0 -0
  50. package/examples/starter/game.zip +0 -0
  51. /package/plugins/{Box2D_v2.3.1_min.wasm.wasm → box2d.wasm.wasm} +0 -0
package/plugins/box2d.js CHANGED
@@ -1,113 +1,195 @@
1
1
  /**
2
- * LittleJS Box2D Plugin
2
+ * LittleJS Box2D Physics Plugin
3
3
  * - Box2dObject extends EngineObject with Box2D physics
4
+ * - Call box2dEngineInit() to start instead of normal engineInit()
5
+ * - You will also need to include box2d.wasm.js
4
6
  * - Uses box2d.js super fast web assembly port of Box2D
5
7
  * - More info: https://github.com/kripken/box2d.js
8
+ * - Fully wraps everything in Box2d
6
9
  * - Functions to create polygon, circle, and edge shapes
7
10
  * - Raycasting and querying
8
11
  * - Joint creation
9
12
  * - Contact begin and end callbacks
10
13
  * - Debug physics drawing
11
- * - Call box2dEngineInit to start
12
14
  */
13
15
 
14
16
  'use strict';
15
-
17
+
18
+ /** Global Box2d Plugin object
19
+ * @type {Box2dPlugin} */
16
20
  let box2d;
17
- let box2dWorld;
18
- let box2dDebugDraw;
21
+
22
+ /** Enable Box2D debug drawing
23
+ * @type {boolean}
24
+ * @default */
19
25
  let box2dDebug = false;
20
- let box2dStepIterations = 3;
21
- const box2dBodyTypeStatic = 0;
22
- const box2dBodyTypeKinematic = 1;
23
- const box2dBodyTypeDynamic = 2;
24
26
 
25
27
  ///////////////////////////////////////////////////////////////////////////////
26
- // Box2D Object - extend with your own custom physics objects
27
-
28
+ /**
29
+ * Box2D Object - extend with your own custom physics objects
30
+ * - A LittleJS object with Box2D physics
31
+ * - Each object has a Box2D body which can have multiple fixtures and joints
32
+ * - Provides interface for Box2D body and fixture functions
33
+ * @extends EngineObject
34
+ */
28
35
  class Box2dObject extends EngineObject
29
36
  {
30
- constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2dBodyTypeDynamic, renderOrder=0)
37
+ /** Create a LittleJS object with Box2d physics
38
+ * @param {Vector2} [pos]
39
+ * @param {Vector2} [size]
40
+ * @param {TileInfo} [tileInfo]
41
+ * @param {number} [angle]
42
+ * @param {Color} [color]
43
+ * @param {number} [bodyType]
44
+ * @param {number} [renderOrder] */
45
+ constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
31
46
  {
32
47
  super(pos, size, tileInfo, angle, color, renderOrder);
33
48
 
34
49
  // create physics body
35
- const bodyDef = new box2d.b2BodyDef();
50
+ const bodyDef = new box2d.instance.b2BodyDef();
36
51
  bodyDef.set_type(bodyType);
37
- bodyDef.set_position(pos.getBox2d());
52
+ bodyDef.set_position(box2d.vec2dTo(pos));
38
53
  bodyDef.set_angle(-angle);
39
- this.body = box2dWorld.CreateBody(bodyDef);
54
+ this.body = box2d.world.CreateBody(bodyDef);
40
55
  this.body.object = this;
41
56
  this.outlineColor = BLACK;
42
57
  }
43
58
 
59
+ /** Destroy this object and it's physics body */
44
60
  destroy()
45
61
  {
46
62
  // destroy physics body, fixtures, and joints
47
- this.body && box2dWorld.DestroyBody(this.body);
63
+ this.body && box2d.world.DestroyBody(this.body);
48
64
  this.body = 0;
49
65
  super.destroy();
50
66
  }
51
67
 
68
+ /** Copy box2d update sim data */
52
69
  update()
53
70
  {
54
71
  // use box2d physics update
55
- this.pos.setBox2d(this.body.GetPosition());
72
+ this.pos = box2d.vec2From(this.body.GetPosition());
56
73
  this.angle = -this.body.GetAngle();
57
74
  }
58
75
 
76
+ /** Render the object, uses box2d drawing if no tile info exists */
59
77
  render()
60
78
  {
61
79
  // use default render or draw fixtures
62
80
  if (this.tileInfo)
63
81
  super.render();
64
82
  else
65
- this.box2dDrawFixtures(this.color, this.outlineColor, this.lineWidth);
83
+ this.drawFixtures(this.color, this.outlineColor, this.lineWidth, mainContext);
66
84
  }
67
85
 
86
+ /** Render debug info */
68
87
  renderDebugInfo()
69
88
  {
70
89
  const isAsleep = !this.getIsAwake();
71
- const isStatic = this.getIsStatic();
72
- const color = rgb(isAsleep?1:0 ,isAsleep?1:0, isStatic?1:0, .5);
73
- this.box2dDrawFixtures(color);
90
+ const isStatic = this.getBodyType() == box2d.bodyTypeStatic;
91
+ const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
92
+ this.drawFixtures(color);
74
93
  }
75
94
 
76
- box2dDrawFixtures(fillColor=WHITE, outlineColor, lineWidth=.1)
95
+ /** Draws all this object's fixtures
96
+ * @param {Color} [color]
97
+ * @param {Color} [outlineColor]
98
+ * @param {number} [lineWidth]
99
+ * @param {CanvasRenderingContext2D} [context] */
100
+ drawFixtures(color=WHITE, outlineColor, lineWidth=.1, context)
77
101
  {
78
102
  this.getFixtureList().forEach(fixture=>
79
- box2dDrawFixture(fixture, this.pos, this.angle, fillColor, outlineColor, lineWidth));
103
+ box2d.drawFixture(fixture, this.pos, this.angle, color, outlineColor, lineWidth, context));
80
104
  }
81
105
 
82
106
  ///////////////////////////////////////////////////////////////////////////////
83
107
  // physics contact callbacks
84
108
 
85
- beginContact(otherObject, contact) {}
86
- endContact(otherObject, contact) {}
109
+ /** Called when a contact begins
110
+ * @param {Box2dObject} otherObject */
111
+ beginContact(otherObject) {}
112
+
113
+ /** Called when a contact ends
114
+ * @param {Box2dObject} otherObject */
115
+ endContact(otherObject) {}
87
116
 
88
117
  ///////////////////////////////////////////////////////////////////////////////
89
118
  // physics fixtures and shapes
90
119
 
91
- addFixture(fixtureDef) { return this.body.CreateFixture(fixtureDef); }
92
- addShape(shape, density, friction, restitution, isSensor)
120
+ /** Add a shape fixture to the body
121
+ * @param {Object} shape
122
+ * @param {number} [density]
123
+ * @param {number} [friction]
124
+ * @param {number} [restitution]
125
+ * @param {boolean} [isSensor] */
126
+ addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
93
127
  {
94
- const fd = box2dCreateFixtureDef(shape, density, friction, restitution, isSensor);
95
- return this.addFixture(fd);
128
+ const fd = new box2d.instance.b2FixtureDef();
129
+ fd.set_shape(shape);
130
+ fd.set_density(density);
131
+ fd.set_friction(friction);
132
+ fd.set_restitution(restitution);
133
+ fd.set_isSensor(isSensor);
134
+ return this.body.CreateFixture(fd);
96
135
  }
97
136
 
137
+ /** Add a box shape to the body
138
+ * @param {Vector2} [size]
139
+ * @param {Vector2} [offset]
140
+ * @param {number} [angle]
141
+ * @param {number} [density]
142
+ * @param {number} [friction]
143
+ * @param {number} [restitution]
144
+ * @param {boolean} [isSensor] */
98
145
  addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
99
146
  {
100
- const shape = new box2d.b2PolygonShape();
101
- shape.SetAsBox(size.x/2, size.y/2, offset.getBox2d(), angle);
147
+ const shape = new box2d.instance.b2PolygonShape();
148
+ shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), angle);
102
149
  return this.addShape(shape, density, friction, restitution, isSensor);
103
150
  }
104
151
 
152
+ /** Add a polygon shape to the body
153
+ * @param {Array<Vector2>} points
154
+ * @param {number} [density]
155
+ * @param {number} [friction]
156
+ * @param {number} [restitution]
157
+ * @param {boolean} [isSensor] */
105
158
  addPoly(points, density, friction, restitution, isSensor)
106
159
  {
160
+ function box2dCreatePolygonShape(points)
161
+ {
162
+ function box2dCreatePointList(points)
163
+ {
164
+ const buffer = box2d.instance._malloc(points.length * 8);
165
+ for (let i=0, offset=0; i<points.length; ++i)
166
+ {
167
+ box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
168
+ offset += 4;
169
+ box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
170
+ offset += 4;
171
+ }
172
+ return box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
173
+ }
174
+
175
+ ASSERT(3 <= points.length && points.length <= 8);
176
+ const shape = new box2d.instance.b2PolygonShape();
177
+ const box2dPoints = box2dCreatePointList(points);
178
+ shape.Set(box2dPoints, points.length);
179
+ return shape;
180
+ }
181
+
107
182
  const shape = box2dCreatePolygonShape(points);
108
183
  return this.addShape(shape, density, friction, restitution, isSensor);
109
184
  }
110
185
 
186
+ /** Add a regular polygon shape to the body
187
+ * @param {number} [diameter]
188
+ * @param {number} [sides]
189
+ * @param {number} [density]
190
+ * @param {number} [friction]
191
+ * @param {number} [restitution]
192
+ * @param {boolean} [isSensor] */
111
193
  addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
112
194
  {
113
195
  const points = [];
@@ -117,6 +199,12 @@ class Box2dObject extends EngineObject
117
199
  return this.addPoly(points, density, friction, restitution, isSensor);
118
200
  }
119
201
 
202
+ /** Add a random polygon shape to the body
203
+ * @param {number} [diameter]
204
+ * @param {number} [density]
205
+ * @param {number} [friction]
206
+ * @param {number} [restitution]
207
+ * @param {boolean} [isSensor] */
120
208
  addRandomPoly(diameter=1, density, friction, restitution, isSensor)
121
209
  {
122
210
  const sides = randInt(3, 9);
@@ -127,48 +215,74 @@ class Box2dObject extends EngineObject
127
215
  return this.addPoly(points, density, friction, restitution, isSensor);
128
216
  }
129
217
 
218
+ /** Add a circle shape to the body
219
+ * @param {number} [diameter]
220
+ * @param {Vector2} [offset]
221
+ * @param {number} [density]
222
+ * @param {number} [friction]
223
+ * @param {number} [restitution]
224
+ * @param {boolean} [isSensor] */
130
225
  addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
131
226
  {
132
- const shape = new box2d.b2CircleShape();
133
- shape.set_m_p(offset.getBox2d());
227
+ const shape = new box2d.instance.b2CircleShape();
228
+ shape.set_m_p(box2d.vec2dTo(offset));
134
229
  shape.set_m_radius(diameter/2);
135
230
  return this.addShape(shape, density, friction, restitution, isSensor);
136
231
  }
137
232
 
233
+ /** Add an edge shape to the body
234
+ * @param {Vector2} point1
235
+ * @param {Vector2} point2
236
+ * @param {number} [density]
237
+ * @param {number} [friction]
238
+ * @param {number} [restitution]
239
+ * @param {boolean} [isSensor] */
138
240
  addEdge(point1, point2, density, friction, restitution, isSensor)
139
241
  {
140
- const shape = new box2d.b2EdgeShape();
141
- shape.Set(point1.getBox2d(), point2.getBox2d());
242
+ const shape = new box2d.instance.b2EdgeShape();
243
+ shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
142
244
  return this.addShape(shape, density, friction, restitution, isSensor);
143
245
  }
144
246
 
247
+ /** Add an edge loop to the body, an edge loop connects the end points
248
+ * @param {Array<Vector2>} points
249
+ * @param {number} [density]
250
+ * @param {number} [friction]
251
+ * @param {number} [restitution]
252
+ * @param {boolean} [isSensor] */
145
253
  addEdgeLoop(points, density, friction, restitution, isSensor)
146
254
  {
147
255
  const fixtures = [];
148
256
  const getPoint = i=> points[mod(i,points.length)];
149
257
  for (let i=0; i<points.length; ++i)
150
258
  {
151
- const shape = new box2d.b2EdgeShape();
152
- shape.set_m_vertex0(getPoint(i-1).getBox2d());
153
- shape.set_m_vertex1(getPoint(i+0).getBox2d());
154
- shape.set_m_vertex2(getPoint(i+1).getBox2d());
155
- shape.set_m_vertex3(getPoint(i+2).getBox2d());
259
+ const shape = new box2d.instance.b2EdgeShape();
260
+ shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
261
+ shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
262
+ shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
263
+ shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
156
264
  const f = this.addShape(shape, density, friction, restitution, isSensor);
157
265
  fixtures.push(f);
158
266
  }
159
267
  return fixtures;
160
268
  }
161
269
 
270
+ /** Add an edge list to the body
271
+ * @param {Array<Vector2>} points
272
+ * @param {number} [density]
273
+ * @param {number} [friction]
274
+ * @param {number} [restitution]
275
+ * @param {boolean} [isSensor] */
162
276
  addEdgeList(points, density, friction, restitution, isSensor)
163
277
  {
164
278
  const fixtures = [];
165
279
  for (let i=0; i<points.length-1; ++i)
166
280
  {
167
- const shape = new box2d.b2EdgeShape();
168
- points[i-1] && shape.set_m_vertex0(points[i-1].getBox2d());
169
- points[i+0] && shape.set_m_vertex1(points[i+0].getBox2d());
170
- points[i+1] && shape.set_m_vertex2(points[i+1].getBox2d());
171
- points[i+2] && shape.set_m_vertex3(points[i+2].getBox2d());
281
+ const shape = new box2d.instance.b2EdgeShape();
282
+ points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
283
+ points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
284
+ points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
285
+ points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
172
286
  const f = this.addShape(shape, density, friction, restitution, isSensor);
173
287
  fixtures.push(f);
174
288
  }
@@ -176,80 +290,130 @@ class Box2dObject extends EngineObject
176
290
  }
177
291
 
178
292
  ///////////////////////////////////////////////////////////////////////////////
179
- // lists of fixtures and joints
293
+ // physics get functions
180
294
 
181
- hasFixtures() { return !box2dIsNull(this.body.GetFixtureList()); }
182
- getFixtureList()
183
- {
184
- const fixtures = [];
185
- for (let fixture=this.body.GetFixtureList(); !box2dIsNull(fixture); )
186
- {
187
- fixtures.push(fixture);
188
- fixture = fixture.GetNext();
189
- }
190
- return fixtures;
191
- }
295
+ /** Gets the center of mass
296
+ * @return {Vector2} */
297
+ getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
192
298
 
193
- hasJoints() { return !box2dIsNull(this.body.GetJointList()); }
194
- getJointList()
195
- {
196
- const joints = [];
197
- for (let joint=this.body.GetJointList(); !box2dIsNull(joint); )
198
- {
199
- joints.push(joint);
200
- joint = joint.get_next();
201
- }
202
- return joints;
203
- }
299
+ /** Gets the linear velocity
300
+ * @return {Vector2} */
301
+ getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
204
302
 
205
- ///////////////////////////////////////////////////////////////////////////////
206
- // physics get functions
207
-
208
- getCenterOfMass() { return vec2(this.body.GetWorldCenter()); }
209
- getLinearVelocity() { return vec2(this.body.GetLinearVelocity()); }
303
+ /** Gets the angular velocity
304
+ * @return {Vector2} */
210
305
  getAngularVelocity() { return this.body.GetAngularVelocity(); }
211
- getMass() { return this.body.GetMass(); }
212
- getInertia() { return this.body.GetInertia(); }
213
- getIsAwake() { return this.body.IsAwake(); }
214
- getBodyType() { return this.body.GetType(); }
215
- getIsStatic() { return this.getBodyType() == box2dBodyTypeStatic; }
216
- getIsKinematic() { return this.getBodyType() == box2dBodyTypeStatic; }
217
- getIsDynamic() { return this.getBodyType() == box2dBodyTypeDynamic; }
306
+
307
+ /** Gets the mass
308
+ * @return {number} */
309
+ getMass() { return this.body.GetMass(); }
310
+
311
+ /** Gets the rotational inertia
312
+ * @return {number} */
313
+ getInertia() { return this.body.GetInertia(); }
314
+
315
+ /** Check if this object is awake
316
+ * @return {boolean} */
317
+ getIsAwake() { return this.body.IsAwake(); }
318
+
319
+ /** Gets the physics body type
320
+ * @return {number} */
321
+ getBodyType() { return this.body.GetType(); }
218
322
 
219
323
  ///////////////////////////////////////////////////////////////////////////////
220
324
  // physics set functions
221
325
 
222
- setTransform(position, angle)
326
+ /** Sets the position and angle
327
+ * @param {Vector2} pos
328
+ * @param {number} angle */
329
+ setTransform(pos, angle)
223
330
  {
224
- this.pos = position;
331
+ this.pos = pos;
225
332
  this.angle = angle;
226
- this.body.SetTransform(position.getBox2d(), angle);
333
+ this.body.SetTransform(box2d.vec2dTo(pos), angle);
227
334
  }
228
- setPosition(position) { this.setTransform(position, this.body.GetAngle()); }
229
- setAngle(angle) { this.setTransform(vec2(this.body.GetPosition()), -angle); }
230
- setLinearVelocity(velocity) { this.body.SetLinearVelocity(velocity.getBox2d()); }
335
+
336
+ /** Sets the position
337
+ * @param {Vector2} pos */
338
+ setPosition(pos) { this.setTransform(pos, this.body.GetAngle()); }
339
+
340
+ /** Sets the angle
341
+ * @param {number} angle */
342
+ setAngle(angle) { this.setTransform(box2d.vec2From(this.body.GetPosition()), -angle); }
343
+
344
+ /** Sets the linear velocity
345
+ * @param {Vector2} velocity */
346
+ setLinearVelocity(velocity) { this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
347
+
348
+ /** Sets the angular velocity
349
+ * @param {number} angularVelocity */
231
350
  setAngularVelocity(angularVelocity) { this.body.SetAngularVelocity(angularVelocity); }
232
- setLinearDamping(damping) { this.body.SetLinearDamping(damping); }
351
+
352
+ /** Sets the linear damping
353
+ * @param {number} damping */
354
+ setLinearDamping(damping) { this.body.SetLinearDamping(damping); }
355
+
356
+ /** Sets the angular damping
357
+ * @param {number} damping */
233
358
  setAngularDamping(damping) { this.body.SetAngularDamping(damping); }
234
- setGravityScale(scale=1) { this.body.SetGravityScale(this.gravityScale = scale); }
235
- setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
236
- setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
237
- setBodyType(type) { this.body.SetType(type); }
359
+
360
+ /** Sets the gravity scale
361
+ * @param {number} [scale] */
362
+ setGravityScale(scale=1) { this.body.SetGravityScale(this.gravityScale = scale); }
363
+
364
+ /** Should this body be treated like a bullet for continuous collision detection?
365
+ * @param {boolean} [isBullet] */
366
+ setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
367
+
368
+ /** Set the sleep state of the body
369
+ * @param {boolean} [isAwake] */
370
+ setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
371
+
372
+ /** Set the physics body type
373
+ * @param {number} type */
374
+ setBodyType(type) { this.body.SetType(type); }
375
+
376
+ /** Set whether the body is allowed to sleep
377
+ * @param {boolean} [isAllowed] */
238
378
  setSleepingAllowed(isAllowed=true) { this.body.SetSleepingAllowed(isAllowed); }
239
- setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); }
240
- setCenterOfMass(center) { this.setMassData(center) }
241
- setMass(mass) { this.setMassData(undefined, mass) }
242
- setMomentOfInertia(I) { this.setMassData(undefined, undefined, I) }
243
- resetMassData() { this.body.ResetMassData(); }
379
+
380
+ /** Set whether the body can rotate
381
+ * @param {boolean} [isFixed] */
382
+ setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); }
383
+
384
+ /** Set the center of mass of the body
385
+ * @param {Vector2} center */
386
+ setCenterOfMass(center) { this.setMassData(center) }
387
+
388
+ /** Set the mass of the body
389
+ * @param {number} mass */
390
+ setMass(mass) { this.setMassData(undefined, mass) }
391
+
392
+ /** Set the moment of inertia of the body
393
+ * @param {number} momentOfInertia */
394
+ setMomentOfInertia(momentOfInertia) { this.setMassData(undefined, undefined, momentOfInertia) }
395
+
396
+ /** Reset the mass, center of mass, and moment */
397
+ resetMassData() { this.body.ResetMassData(); }
398
+
399
+ /** Set the mass data of the body
400
+ * @param {Vector2} [localCenter]
401
+ * @param {number} [mass]
402
+ * @param {number} [momentOfInertia] */
244
403
  setMassData(localCenter, mass, momentOfInertia)
245
404
  {
246
- const data = new box2d.b2MassData();
405
+ const data = new box2d.instance.b2MassData();
247
406
  this.body.GetMassData(data);
248
- localCenter && data.set_center(localCenter.getBox2d());
407
+ localCenter && data.set_center(box2d.vec2dTo(localCenter));
249
408
  mass && data.set_mass(mass);
250
409
  momentOfInertia && data.set_I(momentOfInertia);
251
410
  this.body.SetMassData(data);
252
411
  }
412
+
413
+ /** Set the collision filter data for this body
414
+ * @param {number} [categoryBits]
415
+ * @param {number} [ignoreCategoryBits]
416
+ * @param {number} [groupIndex] */
253
417
  setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0)
254
418
  {
255
419
  this.getFixtureList().forEach(fixture=>
@@ -260,694 +424,1442 @@ class Box2dObject extends EngineObject
260
424
  filter.set_groupIndex(groupIndex);
261
425
  });
262
426
  }
427
+
428
+ /** Set if this body is a sensor
429
+ * @param {boolean} [isSensor] */
263
430
  setSensor(isSensor=true)
264
431
  { this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
265
432
 
266
433
  ///////////////////////////////////////////////////////////////////////////////
267
434
  // physics force and torque functions
268
435
 
436
+ /** Apply force to this object
437
+ * @param {Vector2} force
438
+ * @param {Vector2} [pos] */
269
439
  applyForce(force, pos)
270
440
  {
271
441
  pos ||= this.getCenterOfMass();
272
442
  this.setAwake();
273
- this.body.ApplyForce(force.getBox2d(), pos.getBox2d());
443
+ this.body.ApplyForce(box2d.vec2dTo(force), box2d.vec2dTo(pos));
274
444
  }
445
+
446
+ /** Apply acceleration to this object
447
+ * @param {Vector2} acceleration
448
+ * @param {Vector2} [pos] */
275
449
  applyAcceleration(acceleration, pos)
276
450
  {
277
451
  pos ||= this.getCenterOfMass();
278
452
  this.setAwake();
279
- this.body.ApplyLinearImpulse(acceleration.getBox2d(), pos.getBox2d());
453
+ this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration), box2d.vec2dTo(pos));
280
454
  }
455
+
456
+ /** Apply torque to this object
457
+ * @param {number} torque */
281
458
  applyTorque(torque)
282
459
  {
283
460
  this.setAwake();
284
461
  this.body.ApplyTorque(torque);
285
462
  }
463
+
464
+ /** Apply angular acceleration to this object
465
+ * @param {number} acceleration */
286
466
  applyAngularAcceleration(acceleration)
287
467
  {
288
468
  this.setAwake();
289
- this.ApplyAngularImpulse(acceleration);
469
+ this.body.ApplyAngularImpulse(acceleration);
470
+ }
471
+
472
+ ///////////////////////////////////////////////////////////////////////////////
473
+ // lists of fixtures and joints
474
+
475
+ /** Check if this object has any fixtures
476
+ * @return {boolean} */
477
+ hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
478
+
479
+ /** Get list of fixtures for this object
480
+ * @return {Array<Object>} */
481
+ getFixtureList()
482
+ {
483
+ const fixtures = [];
484
+ for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
485
+ {
486
+ fixtures.push(fixture);
487
+ fixture = fixture.GetNext();
488
+ }
489
+ return fixtures;
490
+ }
491
+
492
+ /** Check if this object has any joints
493
+ * @return {boolean} */
494
+ hasJoints() { return !box2d.isNull(this.body.GetJointList()); }
495
+
496
+ /** Get list of joints for this object
497
+ * @return {Array<Object>} */
498
+ getJointList()
499
+ {
500
+ const joints = [];
501
+ for (let joint=this.body.GetJointList(); !box2d.isNull(joint); )
502
+ {
503
+ joints.push(joint);
504
+ joint = joint.get_next();
505
+ }
506
+ return joints;
290
507
  }
291
508
  }
292
509
 
293
510
  ///////////////////////////////////////////////////////////////////////////////
294
- // Box2D Raycasting and Querying
295
-
296
- // result info for raycasts
511
+ /**
512
+ * Box2D Raycast Result
513
+ * - Holds results from a box2d raycast queries
514
+ * - Automatically created by box2d raycast functions
515
+ */
297
516
  class Box2dRaycastResult
298
517
  {
518
+ /** Create a raycast result
519
+ * @param {Object} fixture
520
+ * @param {Vector2} point
521
+ * @param {Vector2} normal
522
+ * @param {number} fraction */
299
523
  constructor(fixture, point, normal, fraction)
300
524
  {
525
+ /** @property {Box2dObject} - The box2d object */
526
+ this.object = fixture.GetBody().object;
527
+ /** @property {Object} - The fixture that was hit */
301
528
  this.fixture = fixture;
529
+ /** @property {Vector2} - The hit point */
302
530
  this.point = point;
531
+ /** @property {Vector2} - The hit normal */
303
532
  this.normal = normal;
533
+ /** @property {number} - Distance fraction at the point of intersection */
304
534
  this.fraction = fraction;
305
- this.object = fixture.GetBody().object;
306
535
  }
307
536
  }
308
537
 
309
- // raycast and return a list of all the results
310
- function box2dRaycastAll(start, end)
538
+ ///////////////////////////////////////////////////////////////////////////////
539
+ /**
540
+ * Box2D Joint
541
+ * - Base class for Box2D joints
542
+ * - A joint is used to connect objects together
543
+ */
544
+ class Box2dJoint
311
545
  {
312
- const raycastCallback = new box2d.JSRayCastCallback();
313
- raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
314
- {
315
- const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
316
- point = vec2().setBox2dPointer(point);
317
- normal = vec2().setBox2dPointer(normal);
318
- raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
319
- return 1; // continue getting results
320
- };
321
-
322
- const raycastResults = [];
323
- box2dWorld.RayCast(raycastCallback, start.getBox2d(), end.getBox2d());
324
- debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02);
325
- return raycastResults;
326
- }
546
+ /** Create a box2d joint, the base class is not intended to be used directly
547
+ * @param {Object} jointDef */
548
+ constructor(jointDef)
549
+ {
550
+ this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
551
+ }
327
552
 
328
- // raycast and return the first result
329
- function box2dRaycast(start, end)
330
- {
331
- const raycastResults = box2dRaycastAll(start, end);
332
- if (!raycastResults.length)
333
- return undefined;
334
- return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b);
335
- }
553
+ /** Destroy this joint */
554
+ destroy() { box2d.world.DestroyJoint(this.box2dJoint); this.box2dJoint = 0; }
336
555
 
337
- // box aabb cast and return all the objects
338
- function box2dBoxCastAll(pos, size)
339
- {
340
- const queryCallback = new box2d.JSQueryCallback();
341
- queryCallback.ReportFixture = function(fixturePointer)
342
- {
343
- const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
344
- const o = fixture.GetBody().object;
345
- if (!queryObjects.includes(o))
346
- queryObjects.push(o); // add if not already in list
347
- return true; // continue getting results
348
- };
349
-
350
- const aabb = new box2d.b2AABB();
351
- aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d());
352
- aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d());
353
-
354
- let queryObjects = [];
355
- box2dWorld.QueryAABB(queryCallback, aabb);
356
- debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', .02);
357
- return queryObjects;
358
- }
556
+ /** Get the first object attached to this joint
557
+ * @return {Box2dObject} */
558
+ getObjectA() { return this.box2dJoint.GetBodyA().object; }
559
+
560
+ /** Get the second object attached to this joint
561
+ * @return {Box2dObject} */
562
+ getObjectB() { return this.box2dJoint.GetBodyB().object; }
563
+
564
+ /** Get the first anchor for this joint in world coordinates
565
+ * @return {Vector2} */
566
+ getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
359
567
 
360
- // box aabb cast and return the first object
361
- function box2dBoxCast(pos, size)
362
- {
363
- const queryCallback = new box2d.JSQueryCallback();
364
- queryCallback.ReportFixture = function(fixturePointer)
365
- {
366
- const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
367
- queryObject = fixture.GetBody().object;
368
- return false; // stop getting results
369
- };
370
-
371
- const aabb = new box2d.b2AABB();
372
- aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d());
373
- aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d());
374
-
375
- let queryObject;
376
- box2dWorld.QueryAABB(queryCallback, aabb);
377
- debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', .02);
378
- return queryObject;
379
- }
568
+ /** Get the second anchor for this joint in world coordinates
569
+ * @return {Vector2} */
570
+ getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
571
+
572
+ /** Get the reaction force on bodyB at the joint anchor given a time step
573
+ * @param {number} time
574
+ * @return {Vector2} */
575
+ getReactionForce(time) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
576
+
577
+ /** Get the reaction torque on bodyB in N*m given a time step
578
+ * @param {number} time
579
+ * @return {number} */
580
+ getReactionTorque(time) { return this.box2dJoint.GetReactionTorque(1/time);}
581
+
582
+ /** Check if the connected bodies should collide
583
+ * @return {boolean} */
584
+ getCollideConnected() { return this.box2dJoint.getCollideConnected();}
380
585
 
381
- // circle cast and return all the objects
382
- function box2dCircleCastAll(pos, diameter)
383
- {
384
- const radius2 = (diameter/2)**2;
385
- const results = box2dBoxCastAll(pos, vec2(diameter));
386
- return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
586
+ /** Check if either connected body is active
587
+ * @return {boolean} */
588
+ isActive() { return this.box2dJoint.IsActive();}
387
589
  }
388
590
 
389
- // circle cast and return the first object
390
- function box2dCircleCast(pos, diameter)
591
+ ///////////////////////////////////////////////////////////////////////////////
592
+ /**
593
+ * Box2D Target Joint, also known as a mouse joint
594
+ * - Used to make a point on a object track a specific world point target
595
+ * - This a soft constraint with a max force
596
+ * - This allows the constraint to stretch and without applying huge forces
597
+ * @extends Box2dJoint
598
+ */
599
+ class Box2dTargetJoint extends Box2dJoint
391
600
  {
392
- const radius2 = (diameter/2)**2;
393
- let results = box2dBoxCastAll(pos, vec2(diameter));
394
-
395
- let bestResult, bestDistance2;
396
- for (const result of results)
601
+ /** Create a target joint
602
+ * @param {Box2dObject} object
603
+ * @param {Box2dObject} fixedObject
604
+ * @param {Vector2} worldPos */
605
+ constructor(object, fixedObject, worldPos)
397
606
  {
398
- const distance2 = result.pos.distanceSquared(pos);
399
- if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2))
400
- {
401
- bestResult = result;
402
- bestDistance2 = distance2;
403
- }
607
+ object.setAwake();
608
+ const jointDef = new box2d.instance.b2MouseJointDef();
609
+ jointDef.set_bodyA(fixedObject.body);
610
+ jointDef.set_bodyB(object.body);
611
+ jointDef.set_target(box2d.vec2dTo(worldPos));
612
+ jointDef.set_maxForce(2e3 * object.getMass());
613
+ super(jointDef);
404
614
  }
405
- return bestResult;
615
+
616
+ /** Set the target point in world coordinates
617
+ * @param {Vector2} pos */
618
+ setTarget(pos) { this.box2dJoint.SetTarget(box2d.vec2dTo(pos)); }
619
+
620
+ /** Get the target point in world coordinates
621
+ * @return {Vector2} */
622
+ getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
623
+
624
+ /** Sets the maximum force in Newtons
625
+ * @param {number} force */
626
+ setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
627
+
628
+ /** Gets the maximum force in Newtons
629
+ * @return {number} */
630
+ getMaxForce() { return this.box2dJoint.GetMaxForce(); }
631
+
632
+ /** Sets the joint frequency in Hertz
633
+ * @param {number} hz */
634
+ setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
635
+
636
+ /** Gets the joint frequency in Hertz
637
+ * @return {number} */
638
+ getFrequency() { return this.box2dJoint.GetFrequency(); }
406
639
  }
407
640
 
408
- // point cast and return the first object
409
- function box2dPointCast(pos, dynamicOnly=true)
641
+ ///////////////////////////////////////////////////////////////////////////////
642
+ /**
643
+ * Box2D Distance Joint
644
+ * - Constrains two points on two objects to remain at a fixed distance
645
+ * - You can view this as a massless, rigid rod
646
+ * @extends Box2dJoint
647
+ */
648
+ class Box2dDistanceJoint extends Box2dJoint
410
649
  {
411
- const queryCallback = new box2d.JSQueryCallback();
412
- queryCallback.ReportFixture = function(fixturePointer)
650
+ /** Create a distance joint
651
+ * @param {Box2dObject} objectA
652
+ * @param {Box2dObject} objectB
653
+ * @param {Vector2} anchorA
654
+ * @param {Vector2} anchorB
655
+ * @param {boolean} [collide] */
656
+ constructor(objectA, objectB, anchorA, anchorB, collide=false)
413
657
  {
414
- const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
415
- if (dynamicOnly && fixture.GetBody().GetType() != box2d.b2_dynamicBody)
416
- return true; // continue getting results
417
- if (!fixture.TestPoint(pos.getBox2d()))
418
- return true; // continue getting results
419
- queryObject = fixture.GetBody().object;
420
- return false; // stop getting results
421
- };
422
-
423
- const aabb = new box2d.b2AABB();
424
- aabb.set_lowerBound(pos.getBox2d());
425
- aabb.set_upperBound(pos.getBox2d());
426
-
427
- let queryObject;
428
- debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
429
- box2dWorld.QueryAABB(queryCallback, aabb);
430
- return queryObject;
431
- }
658
+ anchorA ||= box2d.vec2From(objectA.body.GetPosition());
659
+ anchorB ||= box2d.vec2From(objectB.body.GetPosition());
660
+ const localAnchorA = objectA.worldToLocal(anchorA);
661
+ const localAnchorB = objectB.worldToLocal(anchorB);
662
+ const jointDef = new box2d.instance.b2DistanceJointDef();
663
+ jointDef.set_bodyA(objectA.body);
664
+ jointDef.set_bodyB(objectB.body);
665
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
666
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
667
+ jointDef.set_length(anchorA.distance(anchorB));
668
+ jointDef.set_collideConnected(collide);
669
+ super(jointDef);
670
+ }
432
671
 
433
- // box aabb cast and return all the fixtures
434
- function box2dBoxCastAllFixtures(pos, size)
435
- {
436
- const queryCallback = new box2d.JSQueryCallback();
437
- queryCallback.ReportFixture = function(fixturePointer)
438
- {
439
- const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
440
- if (!queryFixtures.includes(fixture))
441
- queryFixtures.push(fixture); // add if not already in list
442
- return true; // continue getting results
443
- };
444
-
445
- const aabb = new box2d.b2AABB();
446
- aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d());
447
- aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d());
448
-
449
- let queryFixtures = [];
450
- box2dWorld.QueryAABB(queryCallback, aabb);
451
- debugRaycast && debugRect(pos, size, queryFixtures.length ? '#f00' : '#00f', .02);
452
- return queryFixtures;
672
+ /** Get the local anchor point relative to objectA's origin
673
+ * @return {Vector2} */
674
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
675
+
676
+ /** Get the local anchor point relative to objectB's origin
677
+ * @return {Vector2} */
678
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
679
+
680
+ /** Set the length of the joint
681
+ * @param {number} length */
682
+ setLength(length) { this.box2dJoint.SetLength(length); }
683
+
684
+ /** Get the length of the joint
685
+ * @return {number} */
686
+ getLength() { return this.box2dJoint.GetLength(); }
687
+
688
+ /** Set the frequency in Hertz
689
+ * @param {number} hz */
690
+ setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
691
+
692
+ /** Get the frequency in Hertz
693
+ * @return {number} */
694
+ getFrequency() { return this.box2dJoint.GetFrequency(); }
695
+
696
+ /** Set the damping ratio
697
+ * @param {number} ratio */
698
+ setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); }
699
+
700
+ /** Get the damping ratio
701
+ * @return {number} */
702
+ getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
453
703
  }
454
704
 
455
705
  ///////////////////////////////////////////////////////////////////////////////
456
- // Box2D Joints
457
-
458
- function box2dCreateMouseJoint(object, fixedObject, worldPos)
706
+ /**
707
+ * Box2D Pin Joint
708
+ * - Pins two objects together at a point
709
+ * @extends Box2dDistanceJoint
710
+ */
711
+ class Box2dPinJoint extends Box2dDistanceJoint
459
712
  {
460
- object.setAwake();
461
- const jointDef = new box2d.b2MouseJointDef();
462
- jointDef.set_bodyA(fixedObject.body);
463
- jointDef.set_bodyB(object.body);
464
- jointDef.set_target(worldPos.getBox2d());
465
- jointDef.set_maxForce(2e3 * object.getMass());
466
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
713
+ /** Create a pin joint
714
+ * @param {Box2dObject} objectA
715
+ * @param {Box2dObject} objectB
716
+ * @param {Vector2} [pos]
717
+ * @param {boolean} [collide] */
718
+ constructor(objectA, objectB, pos=objectA.pos, collide=false)
719
+ {
720
+ super(objectA, objectB, undefined, pos, collide);
721
+ }
467
722
  }
468
723
 
469
- function box2dCreatePinJoint(objectA, objectB, collide=false)
724
+ ///////////////////////////////////////////////////////////////////////////////
725
+ /**
726
+ * Box2D Rope Joint
727
+ * - Enforces a maximum distance between two points on two objects
728
+ * @extends Box2dJoint
729
+ */
730
+ class Box2dRopeJoint extends Box2dJoint
470
731
  {
471
- return box2dCreateDistanceJoint(objectA, objectB, objectB.pos, undefined, collide);
472
- }
732
+ /** Create a rope joint
733
+ * @param {Box2dObject} objectA
734
+ * @param {Box2dObject} objectB
735
+ * @param {Vector2} anchorA
736
+ * @param {Vector2} anchorB
737
+ * @param {number} extraLength
738
+ * @param {boolean} [collide] */
739
+ constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
740
+ {
741
+ anchorA ||= box2d.vec2From(objectA.body.GetPosition());
742
+ anchorB ||= box2d.vec2From(objectB.body.GetPosition());
743
+ const localAnchorA = objectA.worldToLocal(anchorA);
744
+ const localAnchorB = objectB.worldToLocal(anchorB);
745
+ const jointDef = new box2d.instance.b2RopeJointDef();
746
+ jointDef.set_bodyA(objectA.body);
747
+ jointDef.set_bodyB(objectB.body);
748
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
749
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
750
+ jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
751
+ jointDef.set_collideConnected(collide);
752
+ super(jointDef);
753
+ }
473
754
 
474
- function box2dCreateDistanceJoint(objectA, objectB, anchorA, anchorB, collide=false)
475
- {
476
- anchorA ||= vec2(objectA.body.GetPosition());
477
- anchorB ||= vec2(objectB.body.GetPosition());
478
- const localAnchorA = objectA.worldToLocal(anchorA);
479
- const localAnchorB = objectB.worldToLocal(anchorB);
480
- const jointDef = new box2d.b2DistanceJointDef();
481
- jointDef.set_bodyA(objectA.body);
482
- jointDef.set_bodyB(objectB.body);
483
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
484
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
485
- jointDef.set_length(anchorA.distance(anchorB));
486
- jointDef.set_collideConnected(collide);
487
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
488
- }
755
+ /** Get the local anchor point relative to objectA's origin
756
+ * @return {Vector2} */
757
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
489
758
 
490
- function box2dCreateRevoluteJoint(objectA, objectB, anchor, collide=false)
491
- {
492
- anchor ||= vec2(objectB.body.GetPosition());
493
- const localAnchorA = objectA.worldToLocal(anchor);
494
- const localAnchorB = objectB.worldToLocal(anchor);
495
- const jointDef = new box2d.b2RevoluteJointDef();
496
- jointDef.set_bodyA(objectA.body);
497
- jointDef.set_bodyB(objectB.body);
498
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
499
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
500
- jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
501
- jointDef.set_collideConnected(collide);
502
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
503
- }
759
+ /** Get the local anchor point relative to objectB's origin
760
+ * @return {Vector2} */
761
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
762
+
763
+ /** Set the max length of the joint
764
+ * @param {number} length */
765
+ setMaxLength(length) { this.box2dJoint.SetMaxLength(length); }
504
766
 
505
- function box2dCreatePrismaticJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
506
- {
507
- anchor ||= vec2(objectB.body.GetPosition());
508
- const localAnchorA = objectA.worldToLocal(anchor);
509
- const localAnchorB = objectB.worldToLocal(anchor);
510
- const localAxisA = objectB.worldToLocalVector(worldAxis);
511
- const jointDef = new box2d.b2PrismaticJointDef();
512
- jointDef.set_bodyA(objectA.body);
513
- jointDef.set_bodyB(objectB.body);
514
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
515
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
516
- jointDef.set_localAxisA(localAxisA.getBox2d());
517
- jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
518
- jointDef.set_collideConnected(collide);
519
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
767
+ /** Get the max length of the joint
768
+ * @return {number} */
769
+ getMaxLength() { return this.box2dJoint.GetMaxLength(); }
520
770
  }
521
771
 
522
- function box2dCreateWheelJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
772
+ ///////////////////////////////////////////////////////////////////////////////
773
+ /**
774
+ * Box2D Revolute Joint
775
+ * - Constrains two objects to share a point while they are free to rotate around the point
776
+ * - The relative rotation about the shared point is the joint angle
777
+ * - You can limit the relative rotation with a joint limit
778
+ * - You can use a motor to drive the relative rotation about the shared point
779
+ * - A maximum motor torque is provided so that infinite forces are not generated
780
+ * @extends Box2dJoint
781
+ */
782
+ class Box2dRevoluteJoint extends Box2dJoint
523
783
  {
524
- anchor ||= vec2(objectB.body.GetPosition());
525
- const localAnchorA = objectA.worldToLocal(anchor);
526
- const localAnchorB = objectB.worldToLocal(anchor);
527
- const localAxisA = objectB.worldToLocalVector(worldAxis);
528
- const jointDef = new box2d.b2WheelJointDef();
529
- jointDef.set_bodyA(objectA.body);
530
- jointDef.set_bodyB(objectB.body);
531
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
532
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
533
- jointDef.set_localAxisA(localAxisA.getBox2d());
534
- jointDef.set_collideConnected(collide);
535
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
536
- }
784
+ /** Create a revolute joint
785
+ * @param {Box2dObject} objectA
786
+ * @param {Box2dObject} objectB
787
+ * @param {Vector2} anchor
788
+ * @param {boolean} [collide] */
789
+ constructor(objectA, objectB, anchor, collide=false)
790
+ {
791
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
792
+ const localAnchorA = objectA.worldToLocal(anchor);
793
+ const localAnchorB = objectB.worldToLocal(anchor);
794
+ const jointDef = new box2d.instance.b2RevoluteJointDef();
795
+ jointDef.set_bodyA(objectA.body);
796
+ jointDef.set_bodyB(objectB.body);
797
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
798
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
799
+ jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
800
+ jointDef.set_collideConnected(collide);
801
+ super(jointDef);
802
+ }
537
803
 
538
- function box2dCreateWeldJoint(objectA, objectB, anchor, collide=false)
539
- {
540
- anchor ||= vec2(objectB.body.GetPosition());
541
- const localAnchorA = objectA.worldToLocal(anchor);
542
- const localAnchorB = objectB.worldToLocal(anchor);
543
- const jointDef = new box2d.b2WeldJointDef();
544
- jointDef.set_bodyA(objectA.body);
545
- jointDef.set_bodyB(objectB.body);
546
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
547
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
548
- jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
549
- jointDef.set_collideConnected(collide);
550
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
551
- }
804
+ /** Get the local anchor point relative to objectA's origin
805
+ * @return {Vector2} */
806
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
552
807
 
553
- function box2dCreateFrictionJoint(objectA, objectB, anchor, collide=false)
554
- {
555
- anchor ||= vec2(objectB.body.GetPosition());
556
- const localAnchorA = objectA.worldToLocal(anchor);
557
- const localAnchorB = objectB.worldToLocal(anchor);
558
- const jointDef = new box2d.b2FrictionJointDef();
559
- jointDef.set_bodyA(objectA.body);
560
- jointDef.set_bodyB(objectB.body);
561
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
562
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
563
- jointDef.set_collideConnected(collide);
564
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
565
- }
808
+ /** Get the local anchor point relative to objectB's origin
809
+ * @return {Vector2} */
810
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
566
811
 
567
- function box2dCreateRopeJoint(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
568
- {
569
- anchorA ||= vec2(objectA.body.GetPosition());
570
- anchorB ||= vec2(objectB.body.GetPosition());
571
- const localAnchorA = objectA.worldToLocal(anchorA);
572
- const localAnchorB = objectB.worldToLocal(anchorB);
573
- const jointDef = new box2d.b2RopeJointDef();
574
- jointDef.set_bodyA(objectA.body);
575
- jointDef.set_bodyB(objectB.body);
576
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
577
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
578
- jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
579
- jointDef.set_collideConnected(collide);
580
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
581
- }
812
+ /** Get the reference angle, objectB angle minus objectA angle in the reference state
813
+ * @return {number} */
814
+ getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
582
815
 
583
- function box2dCreatePulleyJoint(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
584
- {
585
- anchorA ||= vec2(objectA.body.GetPosition());
586
- anchorB ||= vec2(objectB.body.GetPosition());
587
- const localAnchorA = objectA.worldToLocal(anchorA);
588
- const localAnchorB = objectB.worldToLocal(anchorB);
589
- const jointDef = new box2d.b2PulleyJointDef();
590
- jointDef.set_bodyA(objectA.body);
591
- jointDef.set_bodyB(objectB.body);
592
- jointDef.set_groundAnchorA(groundAnchorA.getBox2d());
593
- jointDef.set_groundAnchorB(groundAnchorB.getBox2d());
594
- jointDef.set_localAnchorA(localAnchorA.getBox2d());
595
- jointDef.set_localAnchorB(localAnchorB.getBox2d());
596
- jointDef.set_ratio(ratio);
597
- jointDef.set_lengthA(groundAnchorA.distance(anchorA));
598
- jointDef.set_lengthB(groundAnchorB.distance(anchorB));
599
- jointDef.set_collideConnected(collide);
600
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
601
- }
816
+ /** Get the current joint angle
817
+ * @return {number} */
818
+ getJointAngle() { return this.box2dJoint.GetJointAngle(); }
602
819
 
603
- function box2dCreateMotorJoint(objectA, objectB)
604
- {
605
- const linearOffset = objectA.worldToLocal(vec2(objectB.body.GetPosition()));
606
- const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
607
- const jointDef = new box2d.b2MotorJointDef();
608
- jointDef.set_bodyA(objectA.body);
609
- jointDef.set_bodyB(objectB.body);
610
- jointDef.set_linearOffset(linearOffset.getBox2d());
611
- jointDef.set_angularOffset(angularOffset);
612
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
613
- }
820
+ /** Get the current joint angle speed in radians per second
821
+ * @return {number} */
822
+ getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
614
823
 
615
- function box2dCreateGearJoint(objectA, objectB, joint1, joint2, ratio=1)
616
- {
617
- const jointDef = new box2d.b2GearJointDef();
618
- jointDef.set_bodyA(objectA.body);
619
- jointDef.set_bodyB(objectB.body);
620
- jointDef.set_joint1(joint1);
621
- jointDef.set_joint2(joint2);
622
- jointDef.set_ratio(ratio);
623
- return box2dCastObject(box2dWorld.CreateJoint(jointDef));
624
- }
824
+ /** Is the joint limit enabled?
825
+ * @return {boolean} */
826
+ isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
625
827
 
626
- function box2dDestroyJoint(joint) { box2dWorld.DestroyJoint(joint); }
828
+ /** Enable/disable the joint limit
829
+ * @param {boolean} [enable] */
830
+ enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
627
831
 
628
- ///////////////////////////////////////////////////////////////////////////////
629
- // Box2D Helper Functions
832
+ /** Get the lower joint limit
833
+ * @return {number} */
834
+ getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
630
835
 
631
- function box2dIsNull(object) { return !box2d.getPointer(object); }
836
+ /** Get the upper joint limit
837
+ * @return {number} */
838
+ getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
632
839
 
633
- function box2dCreateFixtureDef(shape, density=1, friction=.2, restitution=0, isSensor=false)
634
- {
635
- const fd = new box2d.b2FixtureDef();
636
- fd.set_shape(shape);
637
- fd.set_density(density);
638
- fd.set_friction(friction);
639
- fd.set_restitution(restitution);
640
- fd.set_isSensor(isSensor);
641
- return fd;
642
- }
840
+ /** Set the joint limits
841
+ * @param {number} min
842
+ * @param {number} max */
843
+ setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
643
844
 
644
- function box2dCreatePointList(points)
645
- {
646
- const buffer = box2d._malloc(points.length * 8);
647
- for (let i=0, offset=0; i<points.length; ++i)
648
- {
649
- box2d.HEAPF32[buffer + offset >> 2] = points[i].x;
650
- offset += 4;
651
- box2d.HEAPF32[buffer + offset >> 2] = points[i].y;
652
- offset += 4;
653
- }
654
- return box2d.wrapPointer(buffer, box2d.b2Vec2);
655
- }
845
+ /** Is the joint motor enabled?
846
+ * @return {boolean} */
847
+ isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
656
848
 
657
- function box2dCreatePolygonShape(points)
658
- {
659
- ASSERT(3 <= points.length && points.length <= 8);
660
- const shape = new box2d.b2PolygonShape();
661
- const box2dPoints = box2dCreatePointList(points);
662
- shape.Set(box2dPoints, points.length);
663
- return shape;
849
+ /** Enable/disable the joint motor
850
+ * @param {boolean} [enable] */
851
+ enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
852
+
853
+ /** Set the motor speed
854
+ * @param {number} speed */
855
+ setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
856
+
857
+ /** Get the motor speed
858
+ * @return {number} */
859
+ getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
860
+
861
+ /** Set the motor torque
862
+ * @param {number} torque */
863
+ setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
864
+
865
+ /** Get the max motor torque
866
+ * @return {number} */
867
+ getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
868
+
869
+ /** Get the motor torque given a time step
870
+ * @param {number} time
871
+ * @return {number} */
872
+ getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
664
873
  }
665
874
 
666
- function box2dCastObject(object)
875
+ ///////////////////////////////////////////////////////////////////////////////
876
+ /**
877
+ * Box2D Gear Joint
878
+ * - A gear joint is used to connect two joints together
879
+ * - Either joint can be a revolute or prismatic joint
880
+ * - You specify a gear ratio to bind the motions together
881
+ * @extends Box2dJoint
882
+ */
883
+ class Box2dGearJoint extends Box2dJoint
667
884
  {
668
- if (object instanceof box2d.b2Shape)
669
- {
670
- switch (object.GetType())
671
- {
672
- case box2d.b2Shape.e_circle:
673
- return box2d.castObject(object, box2d.b2CircleShape);
674
- case box2d.b2Shape.e_edge:
675
- return box2d.castObject(object, box2d.b2EdgeShape);
676
- case box2d.b2Shape.e_polygon:
677
- return box2d.castObject(object, box2d.b2PolygonShape);
678
- case box2d.b2Shape.e_chain:
679
- return box2d.castObject(object, box2d.b2ChainShape);
680
- }
681
- }
682
- else if (object instanceof box2d.b2Joint)
885
+ /** Create a gear joint
886
+ * @param {Box2dObject} objectA
887
+ * @param {Box2dObject} objectB
888
+ * @param {Box2dJoint} joint1
889
+ * @param {Box2dJoint} joint2
890
+ * @param {ratio} [ratio] */
891
+ constructor(objectA, objectB, joint1, joint2, ratio=1)
683
892
  {
684
- switch (object.GetType())
685
- {
686
- case box2d.e_revoluteJoint:
687
- return box2d.castObject(object, box2d.b2RevoluteJoint);
688
- case box2d.e_prismaticJoint:
689
- return box2d.castObject(object, box2d.b2PrismaticJoint);
690
- case box2d.e_distanceJoint:
691
- return box2d.castObject(object, box2d.b2DistanceJoint);
692
- case box2d.e_pulleyJoint:
693
- return box2d.castObject(object, box2d.b2PulleyJoint);
694
- case box2d.e_mouseJoint:
695
- return box2d.castObject(object, box2d.b2MouseJoint);
696
- case box2d.e_gearJoint:
697
- return box2d.castObject(object, box2d.b2GearJoint);
698
- case box2d.e_wheelJoint:
699
- return box2d.castObject(object, box2d.b2WheelJoint);
700
- case box2d.e_weldJoint:
701
- return box2d.castObject(object, box2d.b2WeldJoint);
702
- case box2d.e_frictionJoint:
703
- return box2d.castObject(object, box2d.b2FrictionJoint);
704
- case box2d.e_ropeJoint:
705
- return box2d.castObject(object, box2d.b2RopeJoint);
706
- case box2d.e_motorJoint:
707
- return box2d.castObject(object, box2d.b2MotorJoint);
708
- }
893
+ const jointDef = new box2d.instance.b2GearJointDef();
894
+ jointDef.set_bodyA(objectA.body);
895
+ jointDef.set_bodyB(objectB.body);
896
+ jointDef.set_joint1(joint1.box2dJoint);
897
+ jointDef.set_joint2(joint2.box2dJoint);
898
+ jointDef.set_ratio(ratio);
899
+ super(jointDef);
900
+
901
+ this.joint1 = joint1;
902
+ this.joint2 = joint2;
709
903
  }
710
-
711
- ASSERT(false, 'Unknown object type');
712
- }
713
904
 
714
- function box2dWarmup(frames=100)
715
- {
716
- // run the sim for a few frames to let objects settle
717
- for (let i=frames; i--;)
718
- box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations);
905
+ /** Get the first joint
906
+ * @return {Box2dJoint} */
907
+ getJoint1() { return this.joint1; }
908
+
909
+ /** Get the second joint
910
+ * @return {Box2dJoint} */
911
+ getJoint2() { return this.joint2; }
912
+
913
+ /** Set the gear ratio
914
+ * @param {number} ratio */
915
+ setRatio(ratio) { return this.box2dJoint.SetRatio(ratio); }
916
+
917
+ /** Get the gear ratio
918
+ * @return {number} */
919
+ getRatio() { return this.box2dJoint.GetRatio(); }
719
920
  }
720
921
 
721
922
  ///////////////////////////////////////////////////////////////////////////////
722
- // Box2D Drawing
723
-
724
- function box2dDrawFixture(fixture, pos, angle, fillColor, outlineColor, lineWidth)
923
+ /**
924
+ * Box2D Prismatic Joint
925
+ * - Provides one degree of freedom: translation along an axis fixed in objectA
926
+ * - Relative rotation is prevented
927
+ * - You can use a joint limit to restrict the range of motion
928
+ * - You can use a joint motor to drive the motion or to model joint friction
929
+ * @extends Box2dJoint
930
+ */
931
+ class Box2dPrismaticJoint extends Box2dJoint
725
932
  {
726
- const shape = box2dCastObject(fixture.GetShape());
727
- switch (shape.GetType())
933
+ /** Create a prismatic joint
934
+ * @param {Box2dObject} objectA
935
+ * @param {Box2dObject} objectB
936
+ * @param {Vector2} anchor
937
+ * @param {Vector2} worldAxis
938
+ * @param {boolean} [collide] */
939
+ constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
728
940
  {
729
- case box2d.b2Shape.e_polygon:
730
- {
731
- let points = [];
732
- for (let i=shape.GetVertexCount(); i--;)
733
- points.push(vec2(shape.GetVertex(i)));
734
- box2dDrawPoly(pos, angle, points, fillColor, outlineColor, lineWidth);
735
- break;
736
- }
737
- case box2d.b2Shape.e_circle:
738
- {
739
- const radius = shape.get_m_radius();
740
- box2dDrawCircle(pos, radius, fillColor, outlineColor, lineWidth);
741
- break;
742
- }
743
- case box2d.b2Shape.e_edge:
744
- {
745
- const v1 = vec2(shape.get_m_vertex1());
746
- const v2 = vec2(shape.get_m_vertex2());
747
- box2dDrawLine(pos, angle, v1, v2, fillColor, lineWidth);
748
- break;
749
- }
941
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
942
+ const localAnchorA = objectA.worldToLocal(anchor);
943
+ const localAnchorB = objectB.worldToLocal(anchor);
944
+ const localAxisA = objectB.worldToLocalVector(worldAxis);
945
+ const jointDef = new box2d.instance.b2PrismaticJointDef();
946
+ jointDef.set_bodyA(objectA.body);
947
+ jointDef.set_bodyB(objectB.body);
948
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
949
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
950
+ jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
951
+ jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
952
+ jointDef.set_collideConnected(collide);
953
+ super(jointDef);
750
954
  }
955
+
956
+ /** Get the local anchor point relative to objectA's origin
957
+ * @return {Vector2} */
958
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
959
+
960
+ /** Get the local anchor point relative to objectB's origin
961
+ * @return {Vector2} */
962
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
963
+
964
+ /** Get the local joint axis relative to bodyA
965
+ * @return {Vector2} */
966
+ getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
967
+
968
+ /** Get the reference angle
969
+ * @return {number} */
970
+ getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
971
+
972
+ /** Get the current joint translation
973
+ * @return {number} */
974
+ getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
975
+
976
+ /** Get the current joint translation speed
977
+ * @return {number} */
978
+ getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
979
+
980
+ /** Is the joint limit enabled?
981
+ * @return {boolean} */
982
+ isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
983
+
984
+ /** Enable/disable the joint limit
985
+ * @param {boolean} [enable] */
986
+ enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
987
+
988
+ /** Get the lower joint limit
989
+ * @return {number} */
990
+ getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
991
+
992
+ /** Get the upper joint limit
993
+ * @return {number} */
994
+ getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
995
+
996
+ /** Set the joint limits
997
+ * @param {number} min
998
+ * @param {number} max */
999
+ setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
1000
+
1001
+ /** Is the motor enabled?
1002
+ * @return {boolean} */
1003
+ isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1004
+
1005
+ /** Enable/disable the joint motor
1006
+ * @param {boolean} [enable] */
1007
+ enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
1008
+
1009
+ /** Set the motor speed
1010
+ * @param {number} speed */
1011
+ setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
1012
+
1013
+ /** Get the motor speed
1014
+ * @return {number} */
1015
+ getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
1016
+
1017
+ /** Set the maximum motor force
1018
+ * @param {number} force */
1019
+ setMaxMotorForce(force) { return this.box2dJoint.SetMaxMotorForce(force); }
1020
+
1021
+ /** Get the maximum motor force
1022
+ * @return {number} */
1023
+ getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
1024
+
1025
+ /** Get the motor force given a time step
1026
+ * @param {number} time
1027
+ * @return {number} */
1028
+ getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
751
1029
  }
752
1030
 
753
- function box2dDrawCircle(pos, radius, color=WHITE, outlineColor, lineWidth=.1, context)
1031
+ ///////////////////////////////////////////////////////////////////////////////
1032
+ /**
1033
+ * Box2D Wheel Joint
1034
+ * - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
1035
+ * - You can use a joint limit to restrict the range of motion
1036
+ * - You can use a joint motor to drive the motion or to model joint friction
1037
+ * - This joint is designed for vehicle suspensions
1038
+ * @extends Box2dJoint
1039
+ */
1040
+ class Box2dWheelJoint extends Box2dJoint
754
1041
  {
755
- drawCanvas2D(pos, vec2(1), 0, 0, context=>
1042
+ /** Create a wheel joint
1043
+ * @param {Box2dObject} objectA
1044
+ * @param {Box2dObject} objectB
1045
+ * @param {Vector2} anchor
1046
+ * @param {Vector2} worldAxis
1047
+ * @param {boolean} [collide] */
1048
+ constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
756
1049
  {
757
- context.beginPath();
758
- context.arc(0, 0, radius, 0, 9);
759
- box2dDrawFillStroke(context, color, outlineColor, lineWidth);
760
- }, 0, context);
1050
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1051
+ const localAnchorA = objectA.worldToLocal(anchor);
1052
+ const localAnchorB = objectB.worldToLocal(anchor);
1053
+ const localAxisA = objectB.worldToLocalVector(worldAxis);
1054
+ const jointDef = new box2d.instance.b2WheelJointDef();
1055
+ jointDef.set_bodyA(objectA.body);
1056
+ jointDef.set_bodyB(objectB.body);
1057
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1058
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1059
+ jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
1060
+ jointDef.set_collideConnected(collide);
1061
+ super(jointDef);
1062
+ }
1063
+
1064
+ /** Get the local anchor point relative to objectA's origin
1065
+ * @return {Vector2} */
1066
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1067
+
1068
+ /** Get the local anchor point relative to objectB's origin
1069
+ * @return {Vector2} */
1070
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1071
+
1072
+ /** Get the local joint axis relative to bodyA
1073
+ * @return {Vector2} */
1074
+ getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
1075
+
1076
+ /** Get the current joint translation
1077
+ * @return {number} */
1078
+ getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
1079
+
1080
+ /** Get the current joint translation speed
1081
+ * @return {number} */
1082
+ getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
1083
+
1084
+ /** Is the joint motor enabled?
1085
+ * @return {boolean} */
1086
+ isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
1087
+
1088
+ /** Enable/disable the joint motor
1089
+ * @param {boolean} [enable] */
1090
+ enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
1091
+
1092
+ /** Set the motor speed
1093
+ * @param {number} speed */
1094
+ setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
1095
+
1096
+ /** Get the motor speed
1097
+ * @return {number} */
1098
+ getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
1099
+
1100
+ /** Set the maximum motor torque
1101
+ * @param {number} torque */
1102
+ setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
1103
+
1104
+ /** Get the max motor torque
1105
+ * @return {number} */
1106
+ getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
1107
+
1108
+ /** Get the motor torque for a time step
1109
+ * @return {number} */
1110
+ getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
1111
+
1112
+ /** Set the spring frequency in Hertz
1113
+ * @param {number} hz */
1114
+ setSpringFrequencyHz(hz) { return this.box2dJoint.SetSpringFrequencyHz(hz); }
1115
+
1116
+ /** Get the spring frequency in Hertz
1117
+ * @return {number} */
1118
+ getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
1119
+
1120
+ /** Set the spring damping ratio
1121
+ * @param {number} ratio */
1122
+ setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
1123
+
1124
+ /** Get the spring damping ratio
1125
+ * @return {number} */
1126
+ getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
761
1127
  }
762
1128
 
763
- function box2dDrawPoly(pos, angle, points, color=WHITE, outlineColor, lineWidth=.1, context)
1129
+ ///////////////////////////////////////////////////////////////////////////////
1130
+ /**
1131
+ * Box2D Weld Joint
1132
+ * - Glues two objects together
1133
+ * @extends Box2dJoint
1134
+ */
1135
+ class Box2dWeldJoint extends Box2dJoint
764
1136
  {
765
- drawCanvas2D(pos, vec2(1), angle, 0, context=>
1137
+ /** Create a weld joint
1138
+ * @param {Box2dObject} objectA
1139
+ * @param {Box2dObject} objectB
1140
+ * @param {Vector2} anchor
1141
+ * @param {boolean} [collide] */
1142
+ constructor(objectA, objectB, anchor, collide=false)
766
1143
  {
767
- context.beginPath();
768
- points.forEach(p=>context.lineTo(p.x, p.y));
769
- context.closePath();
770
- box2dDrawFillStroke(context, color, outlineColor, lineWidth);
771
- }, 0, context);
1144
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1145
+ const localAnchorA = objectA.worldToLocal(anchor);
1146
+ const localAnchorB = objectB.worldToLocal(anchor);
1147
+ const jointDef = new box2d.instance.b2WeldJointDef();
1148
+ jointDef.set_bodyA(objectA.body);
1149
+ jointDef.set_bodyB(objectB.body);
1150
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1151
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1152
+ jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
1153
+ jointDef.set_collideConnected(collide);
1154
+ super(jointDef);
1155
+ }
1156
+
1157
+ /** Get the local anchor point relative to objectA's origin
1158
+ * @return {Vector2} */
1159
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1160
+
1161
+ /** Get the local anchor point relative to objectB's origin
1162
+ * @return {Vector2} */
1163
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1164
+
1165
+ /** Get the reference angle
1166
+ * @return {number} */
1167
+ getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
1168
+
1169
+ /** Set the frequency in Hertz
1170
+ * @param {number} hz */
1171
+ setFrequency(hz) { return this.box2dJoint.SetFrequency(hz); }
1172
+
1173
+ /** Get the frequency in Hertz
1174
+ * @return {number} */
1175
+ getFrequency() { return this.box2dJoint.GetFrequency(); }
1176
+
1177
+ /** Set the damping ratio
1178
+ * @param {number} ratio */
1179
+ setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
1180
+
1181
+ /** Get the damping ratio
1182
+ * @return {number} */
1183
+ getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
772
1184
  }
773
1185
 
774
- function box2dDrawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context)
1186
+ ///////////////////////////////////////////////////////////////////////////////
1187
+ /**
1188
+ * Box2D Friction Joint
1189
+ * - Used to apply top-down friction
1190
+ * - Provides 2D translational friction and angular friction
1191
+ * @extends Box2dJoint
1192
+ */
1193
+ class Box2dFrictionJoint extends Box2dJoint
775
1194
  {
776
- drawCanvas2D(pos, vec2(1), angle, 0, context=>
1195
+ /** Create a friction joint
1196
+ * @param {Box2dObject} objectA
1197
+ * @param {Box2dObject} objectB
1198
+ * @param {Vector2} anchor
1199
+ * @param {boolean} [collide] */
1200
+ constructor(objectA, objectB, anchor, collide=false)
777
1201
  {
778
- context.beginPath();
779
- context.lineTo(posA.x, posA.y);
780
- context.lineTo(posB.x, posB.y);
781
- box2dDrawFillStroke(context, 0, color, lineWidth);
782
- }, 0, context);
1202
+ anchor ||= box2d.vec2From(objectB.body.GetPosition());
1203
+ const localAnchorA = objectA.worldToLocal(anchor);
1204
+ const localAnchorB = objectB.worldToLocal(anchor);
1205
+ const jointDef = new box2d.instance.b2FrictionJointDef();
1206
+ jointDef.set_bodyA(objectA.body);
1207
+ jointDef.set_bodyB(objectB.body);
1208
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1209
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1210
+ jointDef.set_collideConnected(collide);
1211
+ super(jointDef);
1212
+ }
1213
+
1214
+ /** Get the local anchor point relative to objectA's origin
1215
+ * @return {Vector2} */
1216
+ getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
1217
+
1218
+ /** Get the local anchor point relative to objectB's origin
1219
+ * @return {Vector2} */
1220
+ getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
1221
+
1222
+ /** Set the maximum friction force
1223
+ * @param {number} force */
1224
+ setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
1225
+
1226
+ /** Get the maximum friction force
1227
+ * @return {number} */
1228
+ getMaxForce() { return this.box2dJoint.GetMaxForce(); }
1229
+
1230
+ /** Set the maximum friction torque
1231
+ * @param {number} torque */
1232
+ setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
1233
+
1234
+ /** Get the maximum friction torque
1235
+ * @return {number} */
1236
+ getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
783
1237
  }
784
1238
 
785
- function box2dDrawFillStroke(context, color, outlineColor, lineWidth)
1239
+ ///////////////////////////////////////////////////////////////////////////////
1240
+ /**
1241
+ * Box2D Pulley Joint
1242
+ * - Connects to two objects and two fixed ground points
1243
+ * - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
1244
+ * - The force transmitted is scaled by the ratio
1245
+ * @extends Box2dJoint
1246
+ */
1247
+ class Box2dPulleyJoint extends Box2dJoint
786
1248
  {
787
- if (color)
1249
+ /** Create a pulley joint
1250
+ * @param {Box2dObject} objectA
1251
+ * @param {Box2dObject} objectB
1252
+ * @param {Vector2} groundAnchorA
1253
+ * @param {Vector2} groundAnchorB
1254
+ * @param {Vector2} anchorA
1255
+ * @param {Vector2} anchorB
1256
+ * @param {number} [ratio]
1257
+ * @param {boolean} [collide] */
1258
+ constructor(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
788
1259
  {
789
- context.fillStyle = color.toString();
790
- context.fill();
791
- }
792
- if (outlineColor && lineWidth)
793
- {
794
- context.lineWidth = lineWidth;
795
- context.lineJoin = context.lineCap = 'round';
796
- context.strokeStyle = outlineColor.toString();
797
- context.stroke();
1260
+ anchorA ||= box2d.vec2From(objectA.body.GetPosition());
1261
+ anchorB ||= box2d.vec2From(objectB.body.GetPosition());
1262
+ const localAnchorA = objectA.worldToLocal(anchorA);
1263
+ const localAnchorB = objectB.worldToLocal(anchorB);
1264
+ const jointDef = new box2d.instance.b2PulleyJointDef();
1265
+ jointDef.set_bodyA(objectA.body);
1266
+ jointDef.set_bodyB(objectB.body);
1267
+ jointDef.set_groundAnchorA(box2d.vec2dTo(groundAnchorA));
1268
+ jointDef.set_groundAnchorB(box2d.vec2dTo(groundAnchorB));
1269
+ jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
1270
+ jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
1271
+ jointDef.set_ratio(ratio);
1272
+ jointDef.set_lengthA(groundAnchorA.distance(anchorA));
1273
+ jointDef.set_lengthB(groundAnchorB.distance(anchorB));
1274
+ jointDef.set_collideConnected(collide);
1275
+ super(jointDef);
798
1276
  }
1277
+
1278
+ /** Get the first ground anchor
1279
+ * @return {Vector2} */
1280
+ getGroundAnchorA() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorA()); }
1281
+
1282
+ /** Get the second ground anchor
1283
+ * @return {Vector2} */
1284
+ getGroundAnchorB() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorB()); }
1285
+
1286
+ /** Get the current length of the segment attached to objectA
1287
+ * @return {number} */
1288
+ getLengthA() { return this.box2dJoint.GetLengthA(); }
1289
+
1290
+ /** Get the current length of the segment attached to objectB
1291
+ * @return {number} */
1292
+ getLengthB(){ return this.box2dJoint.GetLengthB(); }
1293
+
1294
+ /** Get the pulley ratio
1295
+ * @return {number} */
1296
+ getRatio() { return this.box2dJoint.GetRatio(); }
1297
+
1298
+ /** Get the current length of the segment attached to objectA
1299
+ * @return {number} */
1300
+ getCurrentLengthA() { return this.box2dJoint.GetCurrentLengthA(); }
1301
+
1302
+ /** Get the current length of the segment attached to objectB
1303
+ * @return {number} */
1304
+ getCurrentLengthB() { return this.box2dJoint.GetCurrentLengthB(); }
799
1305
  }
800
1306
 
801
1307
  ///////////////////////////////////////////////////////////////////////////////
802
- // Box2D Setup
803
-
804
- function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources)
1308
+ /**
1309
+ * Box2D Motor Joint
1310
+ * - Controls the relative motion between two objects
1311
+ * - Typical usage is to control the movement of a object with respect to the ground
1312
+ * @extends Box2dJoint
1313
+ */
1314
+ class Box2dMotorJoint extends Box2dJoint
805
1315
  {
806
- Box2D().then(_box2d=>
1316
+ /** Create a motor joint
1317
+ * @param {Box2dObject} objectA
1318
+ * @param {Box2dObject} objectB */
1319
+ constructor(objectA, objectB)
807
1320
  {
808
- // setup box2d
809
- box2d = _box2d;
810
- box2dWorld = new box2d.b2World();
1321
+ const linearOffset = objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));
1322
+ const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
1323
+ const jointDef = new box2d.instance.b2MotorJointDef();
1324
+ jointDef.set_bodyA(objectA.body);
1325
+ jointDef.set_bodyB(objectB.body);
1326
+ jointDef.set_linearOffset(box2d.vec2dTo(linearOffset));
1327
+ jointDef.set_angularOffset(angularOffset);
1328
+ super(jointDef);
1329
+ }
811
1330
 
812
- // override functions for box2d
813
- setGravity = function(newGravity)
814
- {
815
- box2dWorld.SetGravity(vec2(0,newGravity).getBox2d());
816
- gravity = newGravity*timeDelta*timeDelta; // engine gravity
817
- }
1331
+ /** Set the target linear offset, in frame A, in meters.
1332
+ * @param {Vector2} offset */
1333
+ setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2d.vec2dTo(offset)); }
818
1334
 
819
- // allow passing box2d vectors to vec2
820
- const defaultVec2 = vec2;
821
- vec2 = function(x, y)
822
- {
823
- return (x instanceof box2d.b2Vec2) ?
824
- new Vector2(x.get_x(), x.get_y()) : defaultVec2(x, y);
825
- }
1335
+ /** Get the target linear offset, in frame A, in meters.
1336
+ * @return {Vector2} */
1337
+ getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
1338
+
1339
+ /** Set the target angular offset
1340
+ * @param {number} offset */
1341
+ setAngularOffset(offset) { this.box2dJoint.SetAngularOffset(offset); }
1342
+
1343
+ /** Get the target angular offset
1344
+ * @return {number} */
1345
+ getAngularOffset() { return this.box2dJoint.GetAngularOffset(); }
1346
+
1347
+ /** Set the maximum friction force
1348
+ * @param {number} force */
1349
+ setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
1350
+
1351
+ /** Get the maximum friction force
1352
+ * @return {number} */
1353
+ getMaxForce() { return this.box2dJoint.GetMaxForce(); }
826
1354
 
827
- // functions to convert between vec2 and box2d vectors
828
- Vector2.prototype.setBox2d = function(p) { return this.set(p.get_x(), p.get_y()); }
829
- Vector2.prototype.getBox2d = function() { return new box2d.b2Vec2(this.x, this.y); }
830
- Vector2.prototype.setBox2dPointer = function(p)
831
- { return this.setBox2d(box2d.wrapPointer(p, box2d.b2Vec2)); }
1355
+ /** Set the maximum torque
1356
+ * @param {number} torque */
1357
+ setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
832
1358
 
833
- // functions to convert between color and box2d colors
834
- Color.prototype.setBox2d = function(c)
835
- { return this.set(c.get_r(), c.get_g(), c.get_b()); }
836
- Color.prototype.setBox2dPointer = function(c)
837
- { return this.setBox2d(box2d.wrapPointer(c, box2d.b2Color)); }
1359
+ /** Get the maximum torque
1360
+ * @return {number} */
1361
+ getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
1362
+
1363
+ /** Set the position correction factor in the range [0,1]
1364
+ * @param {number} factor */
1365
+ setCorrectionFactor(factor) { this.box2dJoint.SetCorrectionFactor(factor); }
1366
+
1367
+ /** Get the position correction factor in the range [0,1]
1368
+ * @return {number} */
1369
+ getCorrectionFactor() { return this.box2dJoint.GetCorrectionFactor(); }
1370
+ }
1371
+
1372
+ ///////////////////////////////////////////////////////////////////////////////
1373
+ /**
1374
+ * Box2D Global Object
1375
+ * - Wraps Box2d world and provides global functions
1376
+ */
1377
+ class Box2dPlugin
1378
+ {
1379
+ /** Create the global UI system object
1380
+ * @param {Object} instance */
1381
+ constructor(instance)
1382
+ {
1383
+ ASSERT(!box2d, 'Box2D already initialized');
1384
+ box2d = this;
1385
+ this.instance = instance;
1386
+ this.world = new box2d.instance.b2World();
1387
+
1388
+ /** @property {number} - Velocity iterations per update*/
1389
+ this.velocityIterations = 8;
1390
+ /** @property {number} - Position iterations per update*/
1391
+ this.positionIterations = 3;
1392
+ /** @property {number} - Static, zero mass, zero velocity, may be manually moved */
1393
+ this.bodyTypeStatic = instance.b2_staticBody;
1394
+ /** @property {number} - Kinematic, zero mass, non-zero velocity set by user, moved by solver */
1395
+ this.bodyTypeKinematic = instance.b2_kinematicBody;
1396
+ /** @property {number} - Dynamic, positive mass, non-zero velocity determined by forces, moved by solver */
1397
+ this.bodyTypeDynamic = instance.b2_dynamicBody;
838
1398
 
839
1399
  // setup contact listener
840
- const listener = new box2d.JSContactListener();
1400
+ const listener = new box2d.instance.JSContactListener();
841
1401
  listener.BeginContact = function(contactPtr)
842
1402
  {
843
- const contact = box2d.wrapPointer(contactPtr, box2d.b2Contact);
1403
+ const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
844
1404
  const fixtureA = contact.GetFixtureA();
845
1405
  const fixtureB = contact.GetFixtureB();
846
1406
  const objectA = fixtureA.GetBody().object;
847
1407
  const objectB = fixtureB.GetBody().object;
848
- objectA.beginContact(objectB, contact);
849
- objectB.beginContact(objectA, contact);
1408
+ objectA.beginContact(objectB);
1409
+ objectB.beginContact(objectA);
850
1410
  }
851
1411
  listener.EndContact = function(contactPtr)
852
1412
  {
853
- const contact = box2d.wrapPointer(contactPtr, box2d.b2Contact);
1413
+ const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
854
1414
  const fixtureA = contact.GetFixtureA();
855
1415
  const fixtureB = contact.GetFixtureB();
856
1416
  const objectA = fixtureA.GetBody().object;
857
1417
  const objectB = fixtureB.GetBody().object;
858
- objectA.endContact(objectB, contact);
859
- objectB.endContact(objectA, contact);
1418
+ objectA.endContact(objectB);
1419
+ objectB.endContact(objectA);
860
1420
  };
861
1421
  listener.PreSolve = function() {};
862
1422
  listener.PostSolve = function() {};
863
- box2dWorld.SetContactListener(listener);
1423
+ box2d.world.SetContactListener(listener);
1424
+ }
864
1425
 
865
- // setup debug draw
866
- box2dDebugDraw = box2dGetDebugDraw();
867
- box2dDebugDraw.AppendFlags(box2d.b2Draw.e_shapeBit);
868
- box2dDebugDraw.AppendFlags(box2d.b2Draw.e_jointBit);
869
- //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_aabbBit);
870
- //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_pairBit);
871
- //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_centerOfMassBit);
872
- box2dWorld.SetDebugDraw(box2dDebugDraw);
873
-
874
- // hook up box2d plugin to update and render
875
- engineAddPlugin(box2dUpdate, box2dRender);
876
- function box2dUpdate()
1426
+ /** Step the physics world simulation
1427
+ * @param {number} [frames] */
1428
+ step(frames=1)
1429
+ {
1430
+ box2d.world.SetGravity(box2d.vec2dTo(vec2(0,gravity)));
1431
+ for (let i=frames; i--;)
1432
+ box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
1433
+ }
1434
+
1435
+ ///////////////////////////////////////////////////////////////////////////////
1436
+ // raycasting and querying
1437
+
1438
+ /** raycast and return a list of all the results
1439
+ * @param {Vector2} start
1440
+ * @param {Vector2} end */
1441
+ raycastAll(start, end)
1442
+ {
1443
+ const raycastCallback = new box2d.instance.JSRayCastCallback();
1444
+ raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
877
1445
  {
878
- if (!paused)
879
- box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations);
880
- }
881
- function box2dRender()
1446
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1447
+ point = box2d.vec2FromPointer(point);
1448
+ normal = box2d.vec2FromPointer(normal);
1449
+ raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
1450
+ return 1; // continue getting results
1451
+ };
1452
+
1453
+ const raycastResults = [];
1454
+ box2d.world.RayCast(raycastCallback, box2d.vec2dTo(start), box2d.vec2dTo(end));
1455
+ debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02);
1456
+ return raycastResults;
1457
+ }
1458
+
1459
+ /** raycast and return the first result
1460
+ * @param {Vector2} start
1461
+ * @param {Vector2} end */
1462
+ raycast(start, end)
1463
+ {
1464
+ const raycastResults = box2d.raycastAll(start, end);
1465
+ if (!raycastResults.length)
1466
+ return undefined;
1467
+ return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b);
1468
+ }
1469
+
1470
+ /** box aabb cast and return all the objects
1471
+ * @param {Vector2} pos
1472
+ * @param {Vector2} size */
1473
+ boxCastAll(pos, size)
1474
+ {
1475
+ const queryCallback = new box2d.instance.JSQueryCallback();
1476
+ queryCallback.ReportFixture = function(fixturePointer)
882
1477
  {
883
- if (box2dDebug || debugPhysics && debugOverlay)
884
- box2dWorld.DrawDebugData();
885
- }
1478
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1479
+ const o = fixture.GetBody().object;
1480
+ if (!queryObjects.includes(o))
1481
+ queryObjects.push(o); // add if not already in list
1482
+ return true; // continue getting results
1483
+ };
886
1484
 
887
- // start littlejs
888
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources);
889
-
890
- // box2d debug drawing implementation
891
- function box2dGetDebugDraw()
1485
+ const aabb = new box2d.instance.b2AABB();
1486
+ aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
1487
+ aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
1488
+
1489
+ let queryObjects = [];
1490
+ box2d.world.QueryAABB(queryCallback, aabb);
1491
+ debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', .02);
1492
+ return queryObjects;
1493
+ }
1494
+
1495
+ /** box aabb cast and return the first object
1496
+ * @param {Vector2} pos
1497
+ * @param {Vector2} size */
1498
+ boxCast(pos, size)
1499
+ {
1500
+ const queryCallback = new box2d.instance.JSQueryCallback();
1501
+ queryCallback.ReportFixture = function(fixturePointer)
892
1502
  {
893
- const debugDraw = new box2d.JSDraw();
894
- debugDraw.DrawSegment = function(point1, point2, color)
895
- {
896
- color = getDebugColor(color);
897
- point1 = vec2().setBox2dPointer(point1);
898
- point2 = vec2().setBox2dPointer(point2);
899
- box2dDrawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
900
- };
901
- debugDraw.DrawPolygon = function(vertices, vertexCount, color)
902
- {
903
- color = getDebugColor(color);
904
- const points = getPointsList(vertices, vertexCount);
905
- box2dDrawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
906
- };
907
- debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
908
- {
909
- color = getDebugColor(color);
910
- const points = getPointsList(vertices, vertexCount);
911
- box2dDrawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
912
- };
913
- debugDraw.box2dDrawCircle = function(center, radius, color)
914
- {
915
- color = getDebugColor(color);
916
- center = vec2().setBox2dPointer(center);
917
- box2dDrawCircle(center, radius, undefined, color, undefined, overlayContext);
918
- };
919
- debugDraw.DrawSolidCircle = function(center, radius, axis, color)
1503
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1504
+ queryObject = fixture.GetBody().object;
1505
+ return false; // stop getting results
1506
+ };
1507
+
1508
+ const aabb = new box2d.instance.b2AABB();
1509
+ aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));
1510
+ aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));
1511
+
1512
+ let queryObject;
1513
+ box2d.world.QueryAABB(queryCallback, aabb);
1514
+ debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', .02);
1515
+ return queryObject;
1516
+ }
1517
+
1518
+ /** circle cast and return all the objects
1519
+ * @param {Vector2} pos
1520
+ * @param {number} diameter */
1521
+ circleCastAll(pos, diameter)
1522
+ {
1523
+ const radius2 = (diameter/2)**2;
1524
+ const results = box2d.boxCastAll(pos, vec2(diameter));
1525
+ return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
1526
+ }
1527
+
1528
+ /** circle cast and return the first object
1529
+ * @param {Vector2} pos
1530
+ * @param {number} diameter */
1531
+ circleCast(pos, diameter)
1532
+ {
1533
+ const radius2 = (diameter/2)**2;
1534
+ let results = box2d.boxCastAll(pos, vec2(diameter));
1535
+
1536
+ let bestResult, bestDistance2;
1537
+ for (const result of results)
1538
+ {
1539
+ const distance2 = result.pos.distanceSquared(pos);
1540
+ if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2))
920
1541
  {
921
- color = getDebugColor(color);
922
- center = vec2().setBox2dPointer(center);
923
- axis = vec2().setBox2dPointer(axis).scale(radius);
924
- box2dDrawCircle(center, radius, color, color, undefined, overlayContext);
925
- box2dDrawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
926
- };
927
- debugDraw.DrawTransform = function(transform)
1542
+ bestResult = result;
1543
+ bestDistance2 = distance2;
1544
+ }
1545
+ }
1546
+ return bestResult;
1547
+ }
1548
+
1549
+ /** point cast and return the first object
1550
+ * @param {Vector2} pos
1551
+ * @param {boolean} dynamicOnly */
1552
+ pointCast(pos, dynamicOnly=true)
1553
+ {
1554
+ const queryCallback = new box2d.instance.JSQueryCallback();
1555
+ queryCallback.ReportFixture = function(fixturePointer)
1556
+ {
1557
+ const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
1558
+ if (dynamicOnly && fixture.GetBody().GetType() != box2d.instance.b2_dynamicBody)
1559
+ return true; // continue getting results
1560
+ if (!fixture.TestPoint(box2d.vec2dTo(pos)))
1561
+ return true; // continue getting results
1562
+ queryObject = fixture.GetBody().object;
1563
+ return false; // stop getting results
1564
+ };
1565
+
1566
+ const aabb = new box2d.instance.b2AABB();
1567
+ aabb.set_lowerBound(box2d.vec2dTo(pos));
1568
+ aabb.set_upperBound(box2d.vec2dTo(pos));
1569
+
1570
+ let queryObject;
1571
+ debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02);
1572
+ box2d.world.QueryAABB(queryCallback, aabb);
1573
+ return queryObject;
1574
+ }
1575
+
1576
+ ///////////////////////////////////////////////////////////////////////////////
1577
+ // drawing
1578
+
1579
+ /** draws a fixture
1580
+ * @param {Object} fixture
1581
+ * @param {Vector2} pos
1582
+ * @param {number} angle
1583
+ * @param {Color} [color]
1584
+ * @param {Color} [outlineColor]
1585
+ * @param {number} [lineWidth]
1586
+ * @param {CanvasRenderingContext2D} [context] */
1587
+ drawFixture(fixture, pos, angle, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
1588
+ {
1589
+ const shape = box2d.castObjectType(fixture.GetShape());
1590
+ switch (shape.GetType())
1591
+ {
1592
+ case box2d.instance.b2Shape.e_polygon:
928
1593
  {
929
- transform = box2d.wrapPointer(transform, box2d.b2Transform);
930
- const pos = vec2(transform.get_p());
931
- const angle = -transform.get_q().GetAngle();
932
- const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8)
933
- const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
934
- box2dDrawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
935
- box2dDrawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
1594
+ let points = [];
1595
+ for (let i=shape.GetVertexCount(); i--;)
1596
+ points.push(box2d.vec2From(shape.GetVertex(i)));
1597
+ box2d.drawPoly(pos, angle, points, color, outlineColor, lineWidth, context);
1598
+ break;
936
1599
  }
937
- function getDebugColor(color)
1600
+ case box2d.instance.b2Shape.e_circle:
938
1601
  {
939
- color = rgb().setBox2dPointer(color);
940
- color.a = .8;
941
- return color;
1602
+ const radius = shape.get_m_radius();
1603
+ box2d.drawCircle(pos, radius, color, outlineColor, lineWidth, context);
1604
+ break;
942
1605
  }
943
- function getPointsList(vertices, vertexCount)
1606
+ case box2d.instance.b2Shape.e_edge:
944
1607
  {
945
- const points = [];
946
- for (let i=vertexCount; i--;)
947
- points.push(vec2().setBox2dPointer(vertices+i*8));
948
- return points
1608
+ const v1 = box2d.vec2From(shape.get_m_vertex1());
1609
+ const v2 = box2d.vec2From(shape.get_m_vertex2());
1610
+ box2d.drawLine(pos, angle, v1, v2, color, lineWidth, context);
1611
+ break;
949
1612
  }
950
- return debugDraw;
951
1613
  }
1614
+ }
1615
+
1616
+ /** draws a circle
1617
+ * @param {Vector2} pos
1618
+ * @param {number} radius
1619
+ * @param {Color} [color]
1620
+ * @param {Color} [outlineColor]
1621
+ * @param {number} [lineWidth]
1622
+ * @param {CanvasRenderingContext2D} [context] */
1623
+ drawCircle(pos, radius, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
1624
+ {
1625
+ drawCanvas2D(pos, vec2(1), 0, 0, context=>
1626
+ {
1627
+ context.beginPath();
1628
+ context.arc(0, 0, radius, 0, 9);
1629
+ box2d.drawFillStroke(color, outlineColor, lineWidth, context);
1630
+ }, 0, context);
1631
+ }
1632
+
1633
+ /** draws a polygon
1634
+ * @param {Vector2} pos
1635
+ * @param {number} angle
1636
+ * @param {Array<Vector2>} points
1637
+ * @param {Color} [color]
1638
+ * @param {Color} [outlineColor]
1639
+ * @param {number} [lineWidth]
1640
+ * @param {CanvasRenderingContext2D} [context] */
1641
+ drawPoly(pos, angle, points, color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
1642
+ {
1643
+ drawCanvas2D(pos, vec2(1), angle, 0, context=>
1644
+ {
1645
+ context.beginPath();
1646
+ points.forEach(p=>context.lineTo(p.x, p.y));
1647
+ context.closePath();
1648
+ box2d.drawFillStroke(color, outlineColor, lineWidth, context);
1649
+ }, 0, context);
1650
+ }
1651
+
1652
+ /** draws a line
1653
+ * @param {Vector2} pos
1654
+ * @param {number} angle
1655
+ * @param {Vector2} posA
1656
+ * @param {Vector2} posB
1657
+ * @param {Color} [color]
1658
+ * @param {number} [lineWidth]
1659
+ * @param {CanvasRenderingContext2D} [context] */
1660
+ drawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context=mainContext)
1661
+ {
1662
+ drawCanvas2D(pos, vec2(1), angle, 0, context=>
1663
+ {
1664
+ context.beginPath();
1665
+ context.lineTo(posA.x, posA.y);
1666
+ context.lineTo(posB.x, posB.y);
1667
+ box2d.drawFillStroke(0, color, lineWidth, context);
1668
+ }, 0, context);
1669
+ }
1670
+
1671
+ /** performs a fill or stroke as a helper to the other draw functions
1672
+ * @param {Color} [color]
1673
+ * @param {Color} [outlineColor]
1674
+ * @param {number} [lineWidth]
1675
+ * @param {CanvasRenderingContext2D} [context] */
1676
+ drawFillStroke(color=WHITE, outlineColor=BLACK, lineWidth=.1, context=mainContext)
1677
+ {
1678
+ if (color)
1679
+ {
1680
+ context.fillStyle = color.toString();
1681
+ context.fill();
1682
+ }
1683
+ if (outlineColor && lineWidth)
1684
+ {
1685
+ context.lineWidth = lineWidth;
1686
+ context.lineJoin = context.lineCap = 'round';
1687
+ context.strokeStyle = outlineColor.toString();
1688
+ context.stroke();
1689
+ }
1690
+ }
1691
+
1692
+ ///////////////////////////////////////////////////////////////////////////////
1693
+ // helper functions
1694
+
1695
+ /** converts a box2d vec2 to a Vector2
1696
+ * @param {Object} v */
1697
+ vec2From(v)
1698
+ {
1699
+ ASSERT(v instanceof box2d.instance.b2Vec2);
1700
+ return new Vector2(v.get_x(), v.get_y());
1701
+ }
1702
+
1703
+ /** converts a box2d vec2 pointer to a Vector2
1704
+ * @param {Object} v */
1705
+ vec2FromPointer(v)
1706
+ {
1707
+ return box2d.vec2From(box2d.instance.wrapPointer(v, box2d.instance.b2Vec2));
1708
+ }
1709
+
1710
+ /** converts a Vector2 to a box2 vec2
1711
+ * @param {Vector2} v */
1712
+ vec2dTo(v)
1713
+ {
1714
+ ASSERT(v instanceof Vector2);
1715
+ return new box2d.instance.b2Vec2(v.x, v.y);
1716
+ }
1717
+
1718
+ /** checks if a box2d object is null
1719
+ * @param {Object} o */
1720
+ isNull(o) { return !box2d.instance.getPointer(o); }
1721
+
1722
+ /** casts a box2d object to its correct type
1723
+ * @param {Object} o */
1724
+ castObjectType(o)
1725
+ {
1726
+ switch (o.GetType())
1727
+ {
1728
+ case box2d.instance.b2Shape.e_circle:
1729
+ return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
1730
+ case box2d.instance.b2Shape.e_edge:
1731
+ return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
1732
+ case box2d.instance.b2Shape.e_polygon:
1733
+ return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
1734
+ case box2d.instance.b2Shape.e_chain:
1735
+ return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
1736
+ case box2d.instance.e_revoluteJoint:
1737
+ return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
1738
+ case box2d.instance.e_prismaticJoint:
1739
+ return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
1740
+ case box2d.instance.e_distanceJoint:
1741
+ return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
1742
+ case box2d.instance.e_pulleyJoint:
1743
+ return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
1744
+ case box2d.instance.e_mouseJoint:
1745
+ return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
1746
+ case box2d.instance.e_gearJoint:
1747
+ return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
1748
+ case box2d.instance.e_wheelJoint:
1749
+ return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
1750
+ case box2d.instance.e_weldJoint:
1751
+ return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
1752
+ case box2d.instance.e_frictionJoint:
1753
+ return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
1754
+ case box2d.instance.e_ropeJoint:
1755
+ return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
1756
+ case box2d.instance.e_motorJoint:
1757
+ return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
1758
+ }
1759
+
1760
+ ASSERT(false, 'Unknown box2d object type');
1761
+ }
1762
+ }
1763
+
1764
+ ///////////////////////////////////////////////////////////////////////////////
1765
+ /** Box2d Init - Startup LittleJS engine with your callback functions
1766
+ * @param {Function|function():Promise} gameInit - Called once after the engine starts up
1767
+ * @param {Function} gameUpdate - Called every frame before objects are updated
1768
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
1769
+ * @param {Function} gameRender - Called before objects are rendered, for drawing the background
1770
+ * @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
1771
+ * @param {Array<string>} [imageSources=[]] - List of images to load
1772
+ * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default */
1773
+ function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources, rootElement)
1774
+ {
1775
+ Box2D().then(box2dInstance=>
1776
+ {
1777
+ // create box2d object
1778
+ new Box2dPlugin(box2dInstance);
1779
+ setupDebugDraw();
1780
+
1781
+ // start littlejs
1782
+ engineAddPlugin(box2dUpdate, box2dRender);
1783
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources, rootElement);
952
1784
  });
953
- }
1785
+
1786
+ // hook up box2d plugin to update and render
1787
+ function box2dUpdate()
1788
+ {
1789
+ if (!paused)
1790
+ box2d.step();
1791
+ }
1792
+ function box2dRender()
1793
+ {
1794
+ if (box2dDebug || debugPhysics && debugOverlay)
1795
+ box2d.world.DrawDebugData();
1796
+ }
1797
+
1798
+ // box2d debug drawing
1799
+ function setupDebugDraw()
1800
+ {
1801
+ // setup debug draw
1802
+ const debugDraw = new box2d.instance.JSDraw();
1803
+ const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
1804
+ const box2dColorPointer = (c)=>
1805
+ box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
1806
+ const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
1807
+ const getPointsList = (vertices, vertexCount) =>
1808
+ {
1809
+ const points = [];
1810
+ for (let i=vertexCount; i--;)
1811
+ points.push(box2d.vec2FromPointer(vertices+i*8));
1812
+ return points;
1813
+ }
1814
+ debugDraw.DrawSegment = function(point1, point2, color)
1815
+ {
1816
+ color = getDebugColor(color);
1817
+ point1 = box2d.vec2FromPointer(point1);
1818
+ point2 = box2d.vec2FromPointer(point2);
1819
+ box2d.drawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
1820
+ };
1821
+ debugDraw.DrawPolygon = function(vertices, vertexCount, color)
1822
+ {
1823
+ color = getDebugColor(color);
1824
+ const points = getPointsList(vertices, vertexCount);
1825
+ box2d.drawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
1826
+ };
1827
+ debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
1828
+ {
1829
+ color = getDebugColor(color);
1830
+ const points = getPointsList(vertices, vertexCount);
1831
+ box2d.drawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
1832
+ };
1833
+ debugDraw.DrawCircle = function(center, radius, color)
1834
+ {
1835
+ color = getDebugColor(color);
1836
+ center = box2d.vec2FromPointer(center);
1837
+ box2d.drawCircle(center, radius, undefined, color, undefined, overlayContext);
1838
+ };
1839
+ debugDraw.DrawSolidCircle = function(center, radius, axis, color)
1840
+ {
1841
+ color = getDebugColor(color);
1842
+ center = box2d.vec2FromPointer(center);
1843
+ axis = box2d.vec2FromPointer(axis).scale(radius);
1844
+ box2d.drawCircle(center, radius, color, color, undefined, overlayContext);
1845
+ box2d.drawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
1846
+ };
1847
+ debugDraw.DrawTransform = function(transform)
1848
+ {
1849
+ transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
1850
+ const pos = vec2(transform.get_p());
1851
+ const angle = -transform.get_q().GetAngle();
1852
+ const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
1853
+ const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
1854
+ box2d.drawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
1855
+ box2d.drawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
1856
+ }
1857
+
1858
+ debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
1859
+ debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
1860
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
1861
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
1862
+ //debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
1863
+ box2d.world.SetDebugDraw(debugDraw);
1864
+ }
1865
+ }