littlejsengine 1.9.7 → 1.9.8

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.
@@ -0,0 +1,873 @@
1
+ /**
2
+ * LittleJS Box2D Plugin
3
+ * - Box2dObject extends EngineObject with Box2D physics
4
+ * - Uses box2d.js super fast web assembly port of Box2D
5
+ * - More info: https://github.com/kripken/box2d.js
6
+ * - Functions to create polygon, circle, and edge shapes
7
+ * - Raycasting and querying
8
+ * - Joint creation
9
+ * - Contact begin and end callbacks
10
+ * - Debug physics drawing
11
+ * - Call box2dEngineInit to start
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ let box2d;
17
+ let box2dWorld;
18
+ let box2dDebugDraw;
19
+ let box2dDebug = false;
20
+ let box2dStepIterations = 3;
21
+ const box2dBodyTypeStatic = 0;
22
+ const box2dBodyTypeKinematic = 1;
23
+ const box2dBodyTypeDynamic = 2;
24
+
25
+ ///////////////////////////////////////////////////////////////////////////////
26
+ // Box2D Object - extend with your own custom physics objects
27
+
28
+ class Box2dObject extends EngineObject
29
+ {
30
+ constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2dBodyTypeDynamic, renderOrder=0)
31
+ {
32
+ super(pos, size, tileInfo, angle, color, renderOrder);
33
+
34
+ // create physics body
35
+ const bodyDef = new box2d.b2BodyDef();
36
+ bodyDef.set_type(bodyType);
37
+ bodyDef.set_position(pos.getBox2d());
38
+ bodyDef.set_angle(-angle);
39
+ this.body = box2dWorld.CreateBody(bodyDef);
40
+ this.body.object = this;
41
+ this.outlineColor = BLACK;
42
+ }
43
+
44
+ destroy()
45
+ {
46
+ // destroy physics body and fixtures
47
+ box2dWorld.DestroyBody(this.body);
48
+ super.destroy();
49
+ }
50
+
51
+ update()
52
+ {
53
+ // use box2d physics update
54
+ this.pos.setBox2d(this.body.GetPosition());
55
+ this.angle = -this.body.GetAngle();
56
+ }
57
+
58
+ render()
59
+ {
60
+ // use default render or draw fixtures
61
+ if (this.tileInfo)
62
+ super.render();
63
+ else
64
+ this.box2dDrawFixtures(this.color, this.outlineColor, this.lineWidth);
65
+ }
66
+
67
+ renderDebugInfo()
68
+ {
69
+ const isAsleep = !this.getIsAwake();
70
+ const isStatic = this.getIsStatic();
71
+ const color = rgb(isAsleep,isAsleep,isStatic,.5);
72
+ this.box2dDrawFixtures(color);
73
+ }
74
+
75
+ box2dDrawFixtures(fillColor=WHITE, outlineColor, lineWidth=.1)
76
+ {
77
+ this.getFixtureList().forEach(fixture=>
78
+ box2dDrawFixture(fixture, this.pos, this.angle, fillColor, outlineColor, lineWidth));
79
+ }
80
+
81
+ ///////////////////////////////////////////////////////////////////////////////
82
+ // physics contact callbacks
83
+
84
+ beginContact(otherObject, contact) {}
85
+ endContact(otherObject, contact) {}
86
+
87
+ ///////////////////////////////////////////////////////////////////////////////
88
+ // physics fixtures and shapes
89
+
90
+ addFixture(fixtureDef) { return this.body.CreateFixture(fixtureDef); }
91
+ addShape(shape, density, friction, restitution, isSensor)
92
+ {
93
+ const fd = box2dCreateFixtureDef(shape, density, friction, restitution, isSensor);
94
+ this.addFixture(fd);
95
+ }
96
+
97
+ addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
98
+ {
99
+ const shape = new box2d.b2PolygonShape();
100
+ shape.SetAsBox(size.x/2, size.y/2, offset.getBox2d(), angle);
101
+ this.addShape(shape, density, friction, restitution, isSensor);
102
+ }
103
+
104
+ addPoly(points, density, friction, restitution, isSensor)
105
+ {
106
+ const shape = box2dCreatePolygonShape(points);
107
+ this.addShape(shape, density, friction, restitution, isSensor);
108
+ }
109
+
110
+ addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
111
+ {
112
+ const points = [];
113
+ const radius = diameter/2;
114
+ for (let i=sides; i--;)
115
+ points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
116
+ this.addPoly(points, density, friction, restitution, isSensor);
117
+ }
118
+
119
+ addRandomPoly(diameter=1, density, friction, restitution, isSensor)
120
+ {
121
+ const sides = randInt(3, 9);
122
+ const points = [];
123
+ const radius = diameter/2;
124
+ for (let i=sides; i--;)
125
+ points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
126
+ this.addPoly(points, density, friction, restitution, isSensor);
127
+ }
128
+
129
+ addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
130
+ {
131
+ const shape = new box2d.b2CircleShape();
132
+ shape.set_m_p(offset.getBox2d());
133
+ shape.set_m_radius(diameter/2);
134
+ this.addShape(shape, density, friction, restitution, isSensor);
135
+ }
136
+
137
+ addEdge(point1, point2, density, friction, restitution, isSensor)
138
+ {
139
+ const shape = new box2d.b2EdgeShape();
140
+ shape.Set(point1.getBox2d(), point2.getBox2d());
141
+ this.addShape(shape, density, friction, restitution, isSensor);
142
+ }
143
+
144
+ addEdgeLoop(points, density, friction, restitution, isSensor)
145
+ {
146
+ const getPoint = i=> points[mod(i,points.length)];
147
+ for (let i=0; i<points.length; ++i)
148
+ {
149
+ const shape = new box2d.b2EdgeShape();
150
+ shape.set_m_vertex0(getPoint(i-1).getBox2d());
151
+ shape.set_m_vertex1(getPoint(i+0).getBox2d());
152
+ shape.set_m_vertex2(getPoint(i+1).getBox2d());
153
+ shape.set_m_vertex3(getPoint(i+2).getBox2d());
154
+ this.addShape(shape, density, friction, restitution, isSensor);
155
+ }
156
+ }
157
+
158
+ addEdgeList(points, density, friction, restitution, isSensor)
159
+ {
160
+ for (let i=0; i<points.length-1; ++i)
161
+ {
162
+ const shape = new box2d.b2EdgeShape();
163
+ points[i-1] && shape.set_m_vertex0(points[i-1].getBox2d());
164
+ points[i+0] && shape.set_m_vertex1(points[i+0].getBox2d());
165
+ points[i+1] && shape.set_m_vertex2(points[i+1].getBox2d());
166
+ points[i+2] && shape.set_m_vertex3(points[i+2].getBox2d());
167
+ this.addShape(shape, density, friction, restitution, isSensor);
168
+ }
169
+ }
170
+
171
+ getFixtureList()
172
+ {
173
+ const fixtureList = [];
174
+ for (let fixture=this.body.GetFixtureList(); !box2dIsNull(fixture); )
175
+ {
176
+ fixtureList.push(fixture);
177
+ fixture = fixture.GetNext();
178
+ }
179
+ return fixtureList;
180
+ }
181
+
182
+ ///////////////////////////////////////////////////////////////////////////////
183
+ // physics get functions
184
+
185
+ getCenterOfMass() { return vec2(this.body.GetWorldCenter()); }
186
+ getLinearVelocity() { return vec2(this.body.GetLinearVelocity()); }
187
+ getAngularVelocity() { return this.body.GetAngularVelocity(); }
188
+ getMass() { return this.body.GetMass(); }
189
+ getInertia() { return this.body.GetInertia(); }
190
+ getIsAwake() { return this.body.IsAwake(); }
191
+ getBodyType() { return this.body.GetType(); }
192
+ getIsStatic() { return this.getBodyType() == box2dBodyTypeStatic; }
193
+ getIsKinematic() { return this.getBodyType() == box2dBodyTypeStatic; }
194
+ getIsDynamic() { return this.getBodyType() == box2dBodyTypeDynamic; }
195
+
196
+ ///////////////////////////////////////////////////////////////////////////////
197
+ // physics set functions
198
+
199
+ setTransform(position, angle)
200
+ {
201
+ this.pos = position;
202
+ this.angle = angle;
203
+ this.body.SetTransform(position.getBox2d(), angle);
204
+ }
205
+ setPosition(position) { this.setTransform(position, this.body.GetAngle()); }
206
+ setAngle(angle) { this.setTransform(vec2(this.body.GetPosition()), -angle); }
207
+ setLinearVelocity(velocity) { this.body.SetLinearVelocity(velocity.getBox2d()); }
208
+ setAngularVelocity(angularVelocity) { this.body.SetAngularVelocity(angularVelocity); }
209
+ setLinearDamping(damping) { this.body.SetLinearDamping(damping); }
210
+ setAngularDamping(damping) { this.body.SetAngularDamping(damping); }
211
+ setGravityScale(scale=1) { this.body.SetGravityScale(this.gravityScale = scale); }
212
+ setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
213
+ setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
214
+ setBodyType(type) { this.body.SetType(type); }
215
+ setSleepingAllowed(isAllowed=true) { this.body.SetSleepingAllowed(isAllowed); }
216
+ setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); }
217
+ setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0)
218
+ {
219
+ this.getFixtureList().forEach(fixture=>
220
+ {
221
+ const filter = fixture.GetFilterData();
222
+ filter.set_categoryBits(categoryBits);
223
+ filter.set_maskBits(0xffff & ~ignoreCategoryBits);
224
+ filter.set_groupIndex(groupIndex);
225
+ });
226
+ }
227
+ setSensor(isSensor=true)
228
+ { this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
229
+
230
+ ///////////////////////////////////////////////////////////////////////////////
231
+ // physics kinematics
232
+
233
+ applyForce(force, pos)
234
+ {
235
+ pos = pos || this.getCenterOfMass();
236
+ this.setAwake();
237
+ this.body.ApplyForce(force.getBox2d(), pos.getBox2d());
238
+ }
239
+ applyAcceleration(acceleration, pos)
240
+ {
241
+ pos = pos || this.getCenterOfMass();
242
+ this.setAwake();
243
+ this.body.ApplyLinearImpulse(acceleration.getBox2d(), pos.getBox2d());
244
+ }
245
+ applyTorque(torque)
246
+ {
247
+ this.setAwake();
248
+ this.body.ApplyTorque(torque);
249
+ }
250
+ applyAngularAcceleration(acceleration)
251
+ {
252
+ this.setAwake();
253
+ this.ApplyAngularImpulse(acceleration);
254
+ }
255
+ }
256
+
257
+ ///////////////////////////////////////////////////////////////////////////////
258
+ // Box2D Raycasting and Querying
259
+
260
+ // result info for raycasts
261
+ class Box2dRaycastResult
262
+ {
263
+ constructor(fixture, point, normal, fraction)
264
+ {
265
+ this.fixture = fixture;
266
+ this.point = point;
267
+ this.normal = normal;
268
+ this.fraction = fraction;
269
+ this.object = fixture.GetBody().object;
270
+ }
271
+ }
272
+
273
+ // raycast and return a list of all results
274
+ function box2dRaycastAll(start, end)
275
+ {
276
+ const raycastCallback = new box2d.JSRayCastCallback();
277
+ raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
278
+ {
279
+ const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
280
+ point = vec2().setBox2dPointer(point);
281
+ normal = vec2().setBox2dPointer(normal);
282
+ raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
283
+ return 1; // continue getting results
284
+ };
285
+
286
+ const raycastResults = [];
287
+ box2dWorld.RayCast(raycastCallback, start.getBox2d(), end.getBox2d());
288
+ debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02);
289
+ return raycastResults;
290
+ }
291
+
292
+ // raycast and return the first result
293
+ function box2dRaycast(start, end)
294
+ {
295
+ const raycastResults = box2dRaycastAll(start, end);
296
+ return raycastResults.length ?
297
+ raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b) : undefined;
298
+ }
299
+
300
+ // box aabb cast and return the all results
301
+ function box2dBoxCastAll(pos, size)
302
+ {
303
+ const queryCallback = new box2d.JSQueryCallback();
304
+ queryCallback.ReportFixture = function(fixturePointer)
305
+ {
306
+ const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
307
+ const o = fixture.GetBody().object;
308
+ if (!queryObjects.includes(o))
309
+ queryObjects.push(o); // add if not already in list
310
+ return true; // continue getting results
311
+ };
312
+
313
+ const aabb = new box2d.b2AABB();
314
+ aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d());
315
+ aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d());
316
+
317
+ let queryObjects = [];
318
+ box2dWorld.QueryAABB(queryCallback, aabb);
319
+ debugRaycast && debugRect(pos, size, raycstResult ? '#f00' : '#00f', .02);
320
+ return queryObjects;
321
+ }
322
+
323
+ // box aabb cast and return the first result
324
+ function box2dBoxCast(pos, size)
325
+ {
326
+ const queryCallback = new box2d.JSQueryCallback();
327
+ queryCallback.ReportFixture = function(fixturePointer)
328
+ {
329
+ const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
330
+ queryObject = fixture.GetBody().object;
331
+ return false; // stop getting results
332
+ };
333
+
334
+ const aabb = new box2d.b2AABB();
335
+ aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d());
336
+ aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d());
337
+
338
+ let queryObject;
339
+ box2dWorld.QueryAABB(queryCallback, aabb);
340
+ debugRaycast && debugRect(pos, size, raycstResult ? '#f00' : '#00f', .02);
341
+ return queryObject;
342
+ }
343
+
344
+ // circle cast and return the all results
345
+ function box2dCircleCastAll(pos, diameter)
346
+ {
347
+ const radius2 = (diameter/2)**2;
348
+ const results = box2dBoxCastAll(pos, vec2(diameter));
349
+ return results.filter(o=>o.pos.distanceSquared(pos) < radius2);
350
+ }
351
+
352
+ // circle cast and return the first result
353
+ function box2dCircleCast(pos, diameter)
354
+ {
355
+ const radius2 = (diameter/2)**2;
356
+ let results = box2dBoxCastAll(pos, vec2(diameter));
357
+ results = results.filter(o=>o.pos.distanceSquared(pos) < radius2);
358
+ results = results.sort((a,b)=>a.pos.distanceSquared(pos)-b.pos.distanceSquared(pos));
359
+ return results[0];
360
+ }
361
+
362
+ // point cast and return the first result
363
+ function box2dPointCast(pos, dynamicOnly=true)
364
+ {
365
+ const queryCallback = new box2d.JSQueryCallback();
366
+ queryCallback.ReportFixture = function(fixturePointer)
367
+ {
368
+ const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture);
369
+ if (dynamicOnly && fixture.GetBody().GetType() != box2d.b2_dynamicBody)
370
+ return true; // continue getting results
371
+ if (!fixture.TestPoint(pos.getBox2d()))
372
+ return true; // continue getting results
373
+ queryResult = fixture.GetBody().object;
374
+ return false; // stop getting results
375
+ };
376
+
377
+ const aabb = new box2d.b2AABB();
378
+ aabb.set_lowerBound(pos.getBox2d());
379
+ aabb.set_upperBound(pos.getBox2d());
380
+
381
+ let queryResult;
382
+ debugRaycast && debugRect(pos, vec2(), queryResult ? '#f00' : '#00f', .02);
383
+ box2dWorld.QueryAABB(queryCallback, aabb);
384
+ return queryResult;
385
+ }
386
+
387
+ ///////////////////////////////////////////////////////////////////////////////
388
+ // Box2D Joints
389
+
390
+ function box2dCreateMouseJoint(object, fixedObject, worldPos)
391
+ {
392
+ object.setAwake();
393
+ const jointDef = new box2d.b2MouseJointDef();
394
+ jointDef.set_bodyA(fixedObject.body);
395
+ jointDef.set_bodyB(object.body);
396
+ jointDef.set_target(worldPos.getBox2d());
397
+ jointDef.set_maxForce(1e3 * object.getMass());
398
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
399
+ }
400
+
401
+ function box2dCreateDistanceJoint(objectA, objectB, anchorA, anchorB, collide=false)
402
+ {
403
+ anchorA = anchorA || vec2(objectA.body.GetPosition());
404
+ anchorB = anchorB || vec2(objectB.body.GetPosition());
405
+ const localAnchorA = objectA.worldToLocal(anchorA);
406
+ const localAnchorB = objectB.worldToLocal(anchorB);
407
+ const jointDef = new box2d.b2DistanceJointDef();
408
+ jointDef.set_bodyA(objectA.body);
409
+ jointDef.set_bodyB(objectB.body);
410
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
411
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
412
+ jointDef.set_length(anchorA.distance(anchorB));
413
+ jointDef.set_collideConnected(collide);
414
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
415
+ }
416
+
417
+ function box2dCreateRevoluteJoint(objectA, objectB, anchor, collide=false)
418
+ {
419
+ anchor = anchor || vec2(objectB.body.GetPosition());
420
+ const localAnchorA = objectA.worldToLocal(anchor);
421
+ const localAnchorB = objectB.worldToLocal(anchor);
422
+ const jointDef = new box2d.b2RevoluteJointDef();
423
+ jointDef.set_bodyA(objectA.body);
424
+ jointDef.set_bodyB(objectB.body);
425
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
426
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
427
+ jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
428
+ jointDef.set_collideConnected(collide);
429
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
430
+ }
431
+
432
+ function box2dCreatePrismaticJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
433
+ {
434
+ anchor = anchor || vec2(objectB.body.GetPosition());
435
+ const localAnchorA = objectA.worldToLocal(anchor);
436
+ const localAnchorB = objectB.worldToLocal(anchor);
437
+ const localAxisA = objectB.worldToLocalVector(worldAxis);
438
+ const jointDef = new box2d.b2PrismaticJointDef();
439
+ jointDef.set_bodyA(objectA.body);
440
+ jointDef.set_bodyB(objectB.body);
441
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
442
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
443
+ jointDef.set_localAxisA(localAxisA.getBox2d());
444
+ jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
445
+ jointDef.set_collideConnected(collide);
446
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
447
+ }
448
+
449
+ function box2dCreateWheelJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
450
+ {
451
+ anchor = anchor || vec2(objectB.body.GetPosition());
452
+ const localAnchorA = objectA.worldToLocal(anchor);
453
+ const localAnchorB = objectB.worldToLocal(anchor);
454
+ const localAxisA = objectB.worldToLocalVector(worldAxis);
455
+ const jointDef = new box2d.b2WheelJointDef();
456
+ jointDef.set_bodyA(objectA.body);
457
+ jointDef.set_bodyB(objectB.body);
458
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
459
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
460
+ jointDef.set_localAxisA(localAxisA.getBox2d());
461
+ jointDef.set_collideConnected(collide);
462
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
463
+ }
464
+
465
+ function box2dCreateWeldJoint(objectA, objectB, anchor, collide=false)
466
+ {
467
+ anchor = anchor || vec2(objectB.body.GetPosition());
468
+ const localAnchorA = objectA.worldToLocal(anchor);
469
+ const localAnchorB = objectB.worldToLocal(anchor);
470
+ const jointDef = new box2d.b2WeldJointDef();
471
+ jointDef.set_bodyA(objectA.body);
472
+ jointDef.set_bodyB(objectB.body);
473
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
474
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
475
+ jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
476
+ jointDef.set_collideConnected(collide);
477
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
478
+ }
479
+
480
+ function box2dCreateFrictionJoint(objectA, objectB, anchor, collide=false)
481
+ {
482
+ anchor = anchor || vec2(objectB.body.GetPosition());
483
+ const localAnchorA = objectA.worldToLocal(anchor);
484
+ const localAnchorB = objectB.worldToLocal(anchor);
485
+ const jointDef = new box2d.b2FrictionJointDef();
486
+ jointDef.set_bodyA(objectA.body);
487
+ jointDef.set_bodyB(objectB.body);
488
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
489
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
490
+ jointDef.set_collideConnected(collide);
491
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
492
+ }
493
+
494
+ function box2dCreateRopeJoint(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
495
+ {
496
+ anchorA = anchorA || vec2(objectA.body.GetPosition());
497
+ anchorB = anchorB || vec2(objectB.body.GetPosition());
498
+ const localAnchorA = objectA.worldToLocal(anchorA);
499
+ const localAnchorB = objectB.worldToLocal(anchorB);
500
+ const jointDef = new box2d.b2RopeJointDef();
501
+ jointDef.set_bodyA(objectA.body);
502
+ jointDef.set_bodyB(objectB.body);
503
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
504
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
505
+ jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
506
+ jointDef.set_collideConnected(collide);
507
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
508
+ }
509
+
510
+ function box2dCreatePulleyJoint(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
511
+ {
512
+ anchorA = anchorA || vec2(objectA.body.GetPosition());
513
+ anchorB = anchorB || vec2(objectB.body.GetPosition());
514
+ const localAnchorA = objectA.worldToLocal(anchorA);
515
+ const localAnchorB = objectB.worldToLocal(anchorB);
516
+ const jointDef = new box2d.b2PulleyJointDef();
517
+ jointDef.set_bodyA(objectA.body);
518
+ jointDef.set_bodyB(objectB.body);
519
+ jointDef.set_groundAnchorA(groundAnchorA.getBox2d());
520
+ jointDef.set_groundAnchorB(groundAnchorB.getBox2d());
521
+ jointDef.set_localAnchorA(localAnchorA.getBox2d());
522
+ jointDef.set_localAnchorB(localAnchorB.getBox2d());
523
+ jointDef.set_ratio(ratio);
524
+ jointDef.set_lengthA(groundAnchorA.distance(anchorA));
525
+ jointDef.set_lengthB(groundAnchorB.distance(anchorB));
526
+ jointDef.set_collideConnected(collide);
527
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
528
+ }
529
+
530
+ function box2dCreateMotorJoint(objectA, objectB)
531
+ {
532
+ const linearOffset = objectA.worldToLocal(vec2(objectB.body.GetPosition()));
533
+ const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
534
+ const jointDef = new box2d.b2MotorJointDef();
535
+ jointDef.set_bodyA(objectA.body);
536
+ jointDef.set_bodyB(objectB.body);
537
+ jointDef.set_linearOffset(linearOffset.getBox2d());
538
+ jointDef.set_angularOffset(angularOffset);
539
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
540
+ }
541
+
542
+ function box2dCreateGearJoint(objectA, objectB, joint1, joint2, ratio=1)
543
+ {
544
+ const jointDef = new box2d.b2GearJointDef();
545
+ jointDef.set_bodyA(objectA.body);
546
+ jointDef.set_bodyB(objectB.body);
547
+ jointDef.set_joint1(joint1);
548
+ jointDef.set_joint2(joint2);
549
+ jointDef.set_ratio(ratio);
550
+ return box2dCastObject(box2dWorld.CreateJoint(jointDef));
551
+ }
552
+
553
+ function box2dDestroyJoint(joint) { box2dWorld.DestroyJoint(joint); }
554
+
555
+ ///////////////////////////////////////////////////////////////////////////////
556
+ // Box2D Helper Functions
557
+
558
+ function box2dIsNull(object) { return !box2d.getPointer(object); }
559
+
560
+ function box2dCreateFixtureDef(shape, density=1, friction=.2, restitution=0, isSensor=false)
561
+ {
562
+ const fd = new box2d.b2FixtureDef();
563
+ fd.set_shape(shape);
564
+ fd.set_density(density);
565
+ fd.set_friction(friction);
566
+ fd.set_restitution(restitution);
567
+ fd.set_isSensor(isSensor);
568
+ return fd;
569
+ }
570
+
571
+ function box2dCreatePointList(points)
572
+ {
573
+ const buffer = box2d._malloc(points.length * 8);
574
+ for (let i=0, offset=0; i<points.length; ++i)
575
+ {
576
+ box2d.HEAPF32[buffer + offset >> 2] = points[i].x;
577
+ offset += 4;
578
+ box2d.HEAPF32[buffer + offset >> 2] = points[i].y;
579
+ offset += 4;
580
+ }
581
+ return box2d.wrapPointer(buffer, box2d.b2Vec2);
582
+ }
583
+
584
+ function box2dCreatePolygonShape(points)
585
+ {
586
+ ASSERT(3 <= points.length && points.length <= 8);
587
+ const shape = new box2d.b2PolygonShape();
588
+ const box2dPoints = box2dCreatePointList(points);
589
+ shape.Set(box2dPoints, points.length);
590
+ return shape;
591
+ }
592
+
593
+ function box2dCastObject(object)
594
+ {
595
+ if (object instanceof box2d.b2Shape)
596
+ {
597
+ switch (object.GetType())
598
+ {
599
+ case box2d.b2Shape.e_circle:
600
+ return box2d.castObject(object, box2d.b2CircleShape);
601
+ case box2d.b2Shape.e_edge:
602
+ return box2d.castObject(object, box2d.b2EdgeShape);
603
+ case box2d.b2Shape.e_polygon:
604
+ return box2d.castObject(object, box2d.b2PolygonShape);
605
+ case box2d.b2Shape.e_chain:
606
+ return box2d.castObject(object, box2d.b2ChainShape);
607
+ }
608
+ }
609
+ else if (object instanceof box2d.b2Joint)
610
+ {
611
+ switch (object.GetType())
612
+ {
613
+ case box2d.e_revoluteJoint:
614
+ return box2d.castObject(object, box2d.b2RevoluteJoint);
615
+ case box2d.e_prismaticJoint:
616
+ return box2d.castObject(object, box2d.b2PrismaticJoint);
617
+ case box2d.e_distanceJoint:
618
+ return box2d.castObject(object, box2d.b2DistanceJoint);
619
+ case box2d.e_pulleyJoint:
620
+ return box2d.castObject(object, box2d.b2PulleyJoint);
621
+ case box2d.e_mouseJoint:
622
+ return box2d.castObject(object, box2d.b2MouseJoint);
623
+ case box2d.e_gearJoint:
624
+ return box2d.castObject(object, box2d.b2GearJoint);
625
+ case box2d.e_wheelJoint:
626
+ return box2d.castObject(object, box2d.b2WheelJoint);
627
+ case box2d.e_weldJoint:
628
+ return box2d.castObject(object, box2d.b2WeldJoint);
629
+ case box2d.e_frictionJoint:
630
+ return box2d.castObject(object, box2d.b2FrictionJoint);
631
+ case box2d.e_ropeJoint:
632
+ return box2d.castObject(object, box2d.b2RopeJoint);
633
+ case box2d.e_motorJoint:
634
+ return box2d.castObject(object, box2d.b2MotorJoint);
635
+ }
636
+ }
637
+
638
+ ASSERT(false, 'Unknown object type');
639
+ }
640
+
641
+ ///////////////////////////////////////////////////////////////////////////////
642
+ // Box2D Drawing
643
+
644
+ function box2dDrawFixture(fixture, pos, angle, fillColor, outlineColor, lineWidth)
645
+ {
646
+ const shape = box2dCastObject(fixture.GetShape());
647
+ switch (shape.GetType())
648
+ {
649
+ case box2d.b2Shape.e_polygon:
650
+ {
651
+ let points = [];
652
+ for (let i=shape.GetVertexCount(); i--;)
653
+ points.push(vec2(shape.GetVertex(i)));
654
+ box2dDrawPoly(pos, angle, points, fillColor, outlineColor, lineWidth);
655
+ break;
656
+ }
657
+ case box2d.b2Shape.e_circle:
658
+ {
659
+ const radius = shape.get_m_radius();
660
+ box2dDrawCircle(pos, radius, fillColor, outlineColor, lineWidth);
661
+ break;
662
+ }
663
+ case box2d.b2Shape.e_edge:
664
+ {
665
+ const v1 = vec2(shape.get_m_vertex1());
666
+ const v2 = vec2(shape.get_m_vertex2());
667
+ box2dDrawLine(pos, angle, v1, v2, fillColor, lineWidth);
668
+ break;
669
+ }
670
+ }
671
+ }
672
+
673
+ function box2dDrawCircle(pos, radius, color=WHITE, outlineColor, lineWidth=.1, context)
674
+ {
675
+ drawCanvas2D(pos, vec2(1), 0, 0, context=>
676
+ {
677
+ context.beginPath();
678
+ context.arc(0, 0, radius, 0, 9);
679
+ box2dDrawFillStroke(context, color, outlineColor, lineWidth);
680
+ }, 0, context);
681
+ }
682
+
683
+ function box2dDrawPoly(pos, angle, points, color=WHITE, outlineColor, lineWidth=.1, context)
684
+ {
685
+ drawCanvas2D(pos, vec2(1), angle, 0, context=>
686
+ {
687
+ context.beginPath();
688
+ points.forEach(p=>context.lineTo(p.x, p.y));
689
+ context.closePath();
690
+ box2dDrawFillStroke(context, color, outlineColor, lineWidth);
691
+ }, 0, context);
692
+ }
693
+
694
+ function box2dDrawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context)
695
+ {
696
+ drawCanvas2D(pos, vec2(1), angle, 0, context=>
697
+ {
698
+ context.beginPath();
699
+ context.lineTo(posA.x, posA.y);
700
+ context.lineTo(posB.x, posB.y);
701
+ box2dDrawFillStroke(context, 0, color, lineWidth);
702
+ }, 0, context);
703
+ }
704
+
705
+ function box2dDrawFillStroke(context, color, outlineColor, lineWidth)
706
+ {
707
+ if (color)
708
+ {
709
+ context.fillStyle = color.toString();
710
+ context.fill();
711
+ }
712
+ if (outlineColor && lineWidth)
713
+ {
714
+ context.lineWidth = lineWidth;
715
+ context.lineJoin = context.lineCap = 'round';
716
+ context.strokeStyle = outlineColor.toString();
717
+ context.stroke();
718
+ }
719
+ }
720
+
721
+ ///////////////////////////////////////////////////////////////////////////////
722
+ // Box2D Setup
723
+
724
+ function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources)
725
+ {
726
+ Box2D().then(_box2d=>
727
+ {
728
+ // setup box2d
729
+ box2d = _box2d;
730
+ box2dWorld = new box2d.b2World();
731
+
732
+ // override functions for box2d
733
+ setGravity = function(newGravity)
734
+ {
735
+ box2dWorld.SetGravity(vec2(0,newGravity).getBox2d());
736
+ gravity = newGravity*timeDelta*timeDelta; // engine gravity
737
+ }
738
+
739
+ // allow passing box2d vectors to vec2
740
+ const defaultVec2 = vec2;
741
+ vec2 = function(x, y)
742
+ {
743
+ return (x instanceof box2d.b2Vec2) ?
744
+ new Vector2(x.get_x(), x.get_y()) : defaultVec2(x, y);
745
+ }
746
+
747
+ // functions to convert between vec2 and box2d vectors
748
+ Vector2.prototype.setBox2d = function(p) { return this.set(p.get_x(), p.get_y()); }
749
+ Vector2.prototype.getBox2d = function() { return new box2d.b2Vec2(this.x, this.y); }
750
+ Vector2.prototype.setBox2dPointer = function(p)
751
+ { return this.setBox2d(box2d.wrapPointer(p, box2d.b2Vec2)); }
752
+
753
+ // functions to convert between color and box2d colors
754
+ Color.prototype.setBox2d = function(c)
755
+ { return this.set(c.get_r(), c.get_g(), c.get_b()); }
756
+ Color.prototype.setBox2dPointer = function(c)
757
+ { return this.setBox2d(box2d.wrapPointer(c, box2d.b2Color)); }
758
+
759
+ // setup contact listener
760
+ const listener = new box2d.JSContactListener();
761
+ listener.BeginContact = function(contactPtr)
762
+ {
763
+ const contact = box2d.wrapPointer(contactPtr, box2d.b2Contact);
764
+ const fixtureA = contact.GetFixtureA();
765
+ const fixtureB = contact.GetFixtureB();
766
+ const objectA = fixtureA.GetBody().object;
767
+ const objectB = fixtureB.GetBody().object;
768
+ objectA.beginContact(objectB, contact);
769
+ objectB.beginContact(objectA, contact);
770
+ }
771
+ listener.EndContact = function(contactPtr)
772
+ {
773
+ const contact = box2d.wrapPointer(contactPtr, box2d.b2Contact);
774
+ const fixtureA = contact.GetFixtureA();
775
+ const fixtureB = contact.GetFixtureB();
776
+ const objectA = fixtureA.GetBody().object;
777
+ const objectB = fixtureB.GetBody().object;
778
+ objectA.endContact(objectB, contact);
779
+ objectB.endContact(objectA, contact);
780
+ };
781
+ listener.PreSolve = function() {};
782
+ listener.PostSolve = function() {};
783
+ box2dWorld.SetContactListener(listener);
784
+
785
+ // setup debug draw
786
+ box2dDebugDraw = box2dGetDebugDraw();
787
+ box2dDebugDraw.AppendFlags(box2d.b2Draw.e_shapeBit);
788
+ box2dDebugDraw.AppendFlags(box2d.b2Draw.e_jointBit);
789
+ //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_aabbBit);
790
+ //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_pairBit);
791
+ //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_centerOfMassBit);
792
+ box2dWorld.SetDebugDraw(box2dDebugDraw);
793
+
794
+ // hook up box2d plugin to update and render
795
+ addPluginUpdate(function()
796
+ {
797
+ const velocityIterations = box2dStepIterations;
798
+ const positionIterations = box2dStepIterations;
799
+ box2dWorld.Step(timeDelta, velocityIterations, positionIterations);
800
+ });
801
+ addPluginRender(function()
802
+ {
803
+ if (box2dDebug || debugPhysics && debugOverlay)
804
+ box2dWorld.DrawDebugData();
805
+ });
806
+
807
+ // start littlejs
808
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources);
809
+
810
+ // box2d debug drawing implementation
811
+ function box2dGetDebugDraw()
812
+ {
813
+ const debugDraw = new box2d.JSDraw();
814
+ debugDraw.DrawSegment = function(point1, point2, color)
815
+ {
816
+ color = getDebugColor(color);
817
+ point1 = vec2().setBox2dPointer(point1);
818
+ point2 = vec2().setBox2dPointer(point2);
819
+ box2dDrawLine(vec2(), 0, point1, point2, color, undefined, overlayContext);
820
+ };
821
+ debugDraw.DrawPolygon = function(vertices, vertexCount, color)
822
+ {
823
+ color = getDebugColor(color);
824
+ const points = getPointsList(vertices, vertexCount);
825
+ box2dDrawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext);
826
+ };
827
+ debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
828
+ {
829
+ color = getDebugColor(color);
830
+ const points = getPointsList(vertices, vertexCount);
831
+ box2dDrawPoly(vec2(), 0, points, color, color, undefined, overlayContext);
832
+ };
833
+ debugDraw.box2dDrawCircle = function(center, radius, color)
834
+ {
835
+ color = getDebugColor(color);
836
+ center = vec2().setBox2dPointer(center);
837
+ box2dDrawCircle(center, radius, undefined, color, undefined, overlayContext);
838
+ };
839
+ debugDraw.DrawSolidCircle = function(center, radius, axis, color)
840
+ {
841
+ color = getDebugColor(color);
842
+ center = vec2().setBox2dPointer(center);
843
+ axis = vec2().setBox2dPointer(axis).scale(radius);
844
+ box2dDrawCircle(center, radius, color, color, undefined, overlayContext);
845
+ box2dDrawLine(center, 0, vec2(), axis, color, undefined, overlayContext);
846
+ };
847
+ debugDraw.DrawTransform = function(transform)
848
+ {
849
+ transform = box2d.wrapPointer(transform, box2d.b2Transform);
850
+ const pos = vec2(transform.get_p());
851
+ const angle = -transform.get_q().GetAngle();
852
+ const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8)
853
+ const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
854
+ box2dDrawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext);
855
+ box2dDrawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext);
856
+ }
857
+ function getDebugColor(color)
858
+ {
859
+ color = rgb().setBox2dPointer(color);
860
+ color.a = .8;
861
+ return color;
862
+ }
863
+ function getPointsList(vertices, vertexCount)
864
+ {
865
+ const points = [];
866
+ for (let i=vertexCount; i--;)
867
+ points.push(vec2().setBox2dPointer(vertices+i*8));
868
+ return points
869
+ }
870
+ return debugDraw;
871
+ }
872
+ });
873
+ }