UNPKG

littlejsengine

Version:

LittleJS - Tiny and Fast HTML5 Game Engine

953 lines (863 loc) 36.8 kB
/** * LittleJS Box2D Plugin * - Box2dObject extends EngineObject with Box2D physics * - Uses box2d.js super fast web assembly port of Box2D * - More info: https://github.com/kripken/box2d.js * - Functions to create polygon, circle, and edge shapes * - Raycasting and querying * - Joint creation * - Contact begin and end callbacks * - Debug physics drawing * - Call box2dEngineInit to start */ 'use strict'; let box2d; let box2dWorld; let box2dDebugDraw; let box2dDebug = false; let box2dStepIterations = 3; const box2dBodyTypeStatic = 0; const box2dBodyTypeKinematic = 1; const box2dBodyTypeDynamic = 2; /////////////////////////////////////////////////////////////////////////////// // Box2D Object - extend with your own custom physics objects class Box2dObject extends EngineObject { constructor(pos=vec2(), size, tileInfo, angle=0, color, bodyType=box2dBodyTypeDynamic, renderOrder=0) { super(pos, size, tileInfo, angle, color, renderOrder); // create physics body const bodyDef = new box2d.b2BodyDef(); bodyDef.set_type(bodyType); bodyDef.set_position(pos.getBox2d()); bodyDef.set_angle(-angle); this.body = box2dWorld.CreateBody(bodyDef); this.body.object = this; this.outlineColor = BLACK; } destroy() { // destroy physics body, fixtures, and joints this.body && box2dWorld.DestroyBody(this.body); this.body = 0; super.destroy(); } update() { // use box2d physics update this.pos.setBox2d(this.body.GetPosition()); this.angle = -this.body.GetAngle(); } render() { // use default render or draw fixtures if (this.tileInfo) super.render(); else this.box2dDrawFixtures(this.color, this.outlineColor, this.lineWidth); } renderDebugInfo() { const isAsleep = !this.getIsAwake(); const isStatic = this.getIsStatic(); const color = rgb(isAsleep?1:0 ,isAsleep?1:0, isStatic?1:0, .5); this.box2dDrawFixtures(color); } box2dDrawFixtures(fillColor=WHITE, outlineColor, lineWidth=.1) { this.getFixtureList().forEach(fixture=> box2dDrawFixture(fixture, this.pos, this.angle, fillColor, outlineColor, lineWidth)); } /////////////////////////////////////////////////////////////////////////////// // physics contact callbacks beginContact(otherObject, contact) {} endContact(otherObject, contact) {} /////////////////////////////////////////////////////////////////////////////// // physics fixtures and shapes addFixture(fixtureDef) { return this.body.CreateFixture(fixtureDef); } addShape(shape, density, friction, restitution, isSensor) { const fd = box2dCreateFixtureDef(shape, density, friction, restitution, isSensor); return this.addFixture(fd); } addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor) { const shape = new box2d.b2PolygonShape(); shape.SetAsBox(size.x/2, size.y/2, offset.getBox2d(), angle); return this.addShape(shape, density, friction, restitution, isSensor); } addPoly(points, density, friction, restitution, isSensor) { const shape = box2dCreatePolygonShape(points); return this.addShape(shape, density, friction, restitution, isSensor); } addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor) { const points = []; const radius = diameter/2; for (let i=sides; i--;) points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2)); return this.addPoly(points, density, friction, restitution, isSensor); } addRandomPoly(diameter=1, density, friction, restitution, isSensor) { const sides = randInt(3, 9); const points = []; const radius = diameter/2; for (let i=sides; i--;) points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2)); return this.addPoly(points, density, friction, restitution, isSensor); } addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor) { const shape = new box2d.b2CircleShape(); shape.set_m_p(offset.getBox2d()); shape.set_m_radius(diameter/2); return this.addShape(shape, density, friction, restitution, isSensor); } addEdge(point1, point2, density, friction, restitution, isSensor) { const shape = new box2d.b2EdgeShape(); shape.Set(point1.getBox2d(), point2.getBox2d()); return this.addShape(shape, density, friction, restitution, isSensor); } addEdgeLoop(points, density, friction, restitution, isSensor) { const fixtures = []; const getPoint = i=> points[mod(i,points.length)]; for (let i=0; i<points.length; ++i) { const shape = new box2d.b2EdgeShape(); shape.set_m_vertex0(getPoint(i-1).getBox2d()); shape.set_m_vertex1(getPoint(i+0).getBox2d()); shape.set_m_vertex2(getPoint(i+1).getBox2d()); shape.set_m_vertex3(getPoint(i+2).getBox2d()); const f = this.addShape(shape, density, friction, restitution, isSensor); fixtures.push(f); } return fixtures; } addEdgeList(points, density, friction, restitution, isSensor) { const fixtures = []; for (let i=0; i<points.length-1; ++i) { const shape = new box2d.b2EdgeShape(); points[i-1] && shape.set_m_vertex0(points[i-1].getBox2d()); points[i+0] && shape.set_m_vertex1(points[i+0].getBox2d()); points[i+1] && shape.set_m_vertex2(points[i+1].getBox2d()); points[i+2] && shape.set_m_vertex3(points[i+2].getBox2d()); const f = this.addShape(shape, density, friction, restitution, isSensor); fixtures.push(f); } return fixtures; } /////////////////////////////////////////////////////////////////////////////// // lists of fixtures and joints hasFixtures() { return !box2dIsNull(this.body.GetFixtureList()); } getFixtureList() { const fixtures = []; for (let fixture=this.body.GetFixtureList(); !box2dIsNull(fixture); ) { fixtures.push(fixture); fixture = fixture.GetNext(); } return fixtures; } hasJoints() { return !box2dIsNull(this.body.GetJointList()); } getJointList() { const joints = []; for (let joint=this.body.GetJointList(); !box2dIsNull(joint); ) { joints.push(joint); joint = joint.get_next(); } return joints; } /////////////////////////////////////////////////////////////////////////////// // physics get functions getCenterOfMass() { return vec2(this.body.GetWorldCenter()); } getLinearVelocity() { return vec2(this.body.GetLinearVelocity()); } getAngularVelocity() { return this.body.GetAngularVelocity(); } getMass() { return this.body.GetMass(); } getInertia() { return this.body.GetInertia(); } getIsAwake() { return this.body.IsAwake(); } getBodyType() { return this.body.GetType(); } getIsStatic() { return this.getBodyType() == box2dBodyTypeStatic; } getIsKinematic() { return this.getBodyType() == box2dBodyTypeStatic; } getIsDynamic() { return this.getBodyType() == box2dBodyTypeDynamic; } /////////////////////////////////////////////////////////////////////////////// // physics set functions setTransform(position, angle) { this.pos = position; this.angle = angle; this.body.SetTransform(position.getBox2d(), angle); } setPosition(position) { this.setTransform(position, this.body.GetAngle()); } setAngle(angle) { this.setTransform(vec2(this.body.GetPosition()), -angle); } setLinearVelocity(velocity) { this.body.SetLinearVelocity(velocity.getBox2d()); } setAngularVelocity(angularVelocity) { this.body.SetAngularVelocity(angularVelocity); } setLinearDamping(damping) { this.body.SetLinearDamping(damping); } setAngularDamping(damping) { this.body.SetAngularDamping(damping); } setGravityScale(scale=1) { this.body.SetGravityScale(this.gravityScale = scale); } setBullet(isBullet=true) { this.body.SetBullet(isBullet); } setAwake(isAwake=true) { this.body.SetAwake(isAwake); } setBodyType(type) { this.body.SetType(type); } setSleepingAllowed(isAllowed=true) { this.body.SetSleepingAllowed(isAllowed); } setFixedRotation(isFixed=true) { this.body.SetFixedRotation(isFixed); } setCenterOfMass(center) { this.setMassData(center) } setMass(mass) { this.setMassData(undefined, mass) } setMomentOfInertia(I) { this.setMassData(undefined, undefined, I) } resetMassData() { this.body.ResetMassData(); } setMassData(localCenter, mass, momentOfInertia) { const data = new box2d.b2MassData(); this.body.GetMassData(data); localCenter && data.set_center(localCenter.getBox2d()); mass && data.set_mass(mass); momentOfInertia && data.set_I(momentOfInertia); this.body.SetMassData(data); } setFilterData(categoryBits=0, ignoreCategoryBits=0, groupIndex=0) { this.getFixtureList().forEach(fixture=> { const filter = fixture.GetFilterData(); filter.set_categoryBits(categoryBits); filter.set_maskBits(0xffff & ~ignoreCategoryBits); filter.set_groupIndex(groupIndex); }); } setSensor(isSensor=true) { this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); } /////////////////////////////////////////////////////////////////////////////// // physics force and torque functions applyForce(force, pos) { pos ||= this.getCenterOfMass(); this.setAwake(); this.body.ApplyForce(force.getBox2d(), pos.getBox2d()); } applyAcceleration(acceleration, pos) { pos ||= this.getCenterOfMass(); this.setAwake(); this.body.ApplyLinearImpulse(acceleration.getBox2d(), pos.getBox2d()); } applyTorque(torque) { this.setAwake(); this.body.ApplyTorque(torque); } applyAngularAcceleration(acceleration) { this.setAwake(); this.ApplyAngularImpulse(acceleration); } } /////////////////////////////////////////////////////////////////////////////// // Box2D Raycasting and Querying // result info for raycasts class Box2dRaycastResult { constructor(fixture, point, normal, fraction) { this.fixture = fixture; this.point = point; this.normal = normal; this.fraction = fraction; this.object = fixture.GetBody().object; } } // raycast and return a list of all the results function box2dRaycastAll(start, end) { const raycastCallback = new box2d.JSRayCastCallback(); raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction) { const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture); point = vec2().setBox2dPointer(point); normal = vec2().setBox2dPointer(normal); raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction)); return 1; // continue getting results }; const raycastResults = []; box2dWorld.RayCast(raycastCallback, start.getBox2d(), end.getBox2d()); debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02); return raycastResults; } // raycast and return the first result function box2dRaycast(start, end) { const raycastResults = box2dRaycastAll(start, end); if (!raycastResults.length) return undefined; return raycastResults.reduce((a,b)=>a.fraction < b.fraction ? a : b); } // box aabb cast and return all the objects function box2dBoxCastAll(pos, size) { const queryCallback = new box2d.JSQueryCallback(); queryCallback.ReportFixture = function(fixturePointer) { const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture); const o = fixture.GetBody().object; if (!queryObjects.includes(o)) queryObjects.push(o); // add if not already in list return true; // continue getting results }; const aabb = new box2d.b2AABB(); aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d()); aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d()); let queryObjects = []; box2dWorld.QueryAABB(queryCallback, aabb); debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', .02); return queryObjects; } // box aabb cast and return the first object function box2dBoxCast(pos, size) { const queryCallback = new box2d.JSQueryCallback(); queryCallback.ReportFixture = function(fixturePointer) { const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture); queryObject = fixture.GetBody().object; return false; // stop getting results }; const aabb = new box2d.b2AABB(); aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d()); aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d()); let queryObject; box2dWorld.QueryAABB(queryCallback, aabb); debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', .02); return queryObject; } // circle cast and return all the objects function box2dCircleCastAll(pos, diameter) { const radius2 = (diameter/2)**2; const results = box2dBoxCastAll(pos, vec2(diameter)); return results.filter(o=>o.pos.distanceSquared(pos) < radius2); } // circle cast and return the first object function box2dCircleCast(pos, diameter) { const radius2 = (diameter/2)**2; let results = box2dBoxCastAll(pos, vec2(diameter)); let bestResult, bestDistance2; for (const result of results) { const distance2 = result.pos.distanceSquared(pos); if (distance2 < radius2 && (!bestResult || distance2 < bestDistance2)) { bestResult = result; bestDistance2 = distance2; } } return bestResult; } // point cast and return the first object function box2dPointCast(pos, dynamicOnly=true) { const queryCallback = new box2d.JSQueryCallback(); queryCallback.ReportFixture = function(fixturePointer) { const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture); if (dynamicOnly && fixture.GetBody().GetType() != box2d.b2_dynamicBody) return true; // continue getting results if (!fixture.TestPoint(pos.getBox2d())) return true; // continue getting results queryObject = fixture.GetBody().object; return false; // stop getting results }; const aabb = new box2d.b2AABB(); aabb.set_lowerBound(pos.getBox2d()); aabb.set_upperBound(pos.getBox2d()); let queryObject; debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', .02); box2dWorld.QueryAABB(queryCallback, aabb); return queryObject; } // box aabb cast and return all the fixtures function box2dBoxCastAllFixtures(pos, size) { const queryCallback = new box2d.JSQueryCallback(); queryCallback.ReportFixture = function(fixturePointer) { const fixture = box2d.wrapPointer(fixturePointer, box2d.b2Fixture); if (!queryFixtures.includes(fixture)) queryFixtures.push(fixture); // add if not already in list return true; // continue getting results }; const aabb = new box2d.b2AABB(); aabb.set_lowerBound(pos.subtract(size.scale(.5)).getBox2d()); aabb.set_upperBound(pos.add(size.scale(.5)).getBox2d()); let queryFixtures = []; box2dWorld.QueryAABB(queryCallback, aabb); debugRaycast && debugRect(pos, size, queryFixtures.length ? '#f00' : '#00f', .02); return queryFixtures; } /////////////////////////////////////////////////////////////////////////////// // Box2D Joints function box2dCreateMouseJoint(object, fixedObject, worldPos) { object.setAwake(); const jointDef = new box2d.b2MouseJointDef(); jointDef.set_bodyA(fixedObject.body); jointDef.set_bodyB(object.body); jointDef.set_target(worldPos.getBox2d()); jointDef.set_maxForce(2e3 * object.getMass()); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreatePinJoint(objectA, objectB, collide=false) { return box2dCreateDistanceJoint(objectA, objectB, objectB.pos, undefined, collide); } function box2dCreateDistanceJoint(objectA, objectB, anchorA, anchorB, collide=false) { anchorA ||= vec2(objectA.body.GetPosition()); anchorB ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchorA); const localAnchorB = objectB.worldToLocal(anchorB); const jointDef = new box2d.b2DistanceJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_length(anchorA.distance(anchorB)); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateRevoluteJoint(objectA, objectB, anchor, collide=false) { anchor ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchor); const localAnchorB = objectB.worldToLocal(anchor); const jointDef = new box2d.b2RevoluteJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle()); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreatePrismaticJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false) { anchor ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchor); const localAnchorB = objectB.worldToLocal(anchor); const localAxisA = objectB.worldToLocalVector(worldAxis); const jointDef = new box2d.b2PrismaticJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_localAxisA(localAxisA.getBox2d()); jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle()); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateWheelJoint(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false) { anchor ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchor); const localAnchorB = objectB.worldToLocal(anchor); const localAxisA = objectB.worldToLocalVector(worldAxis); const jointDef = new box2d.b2WheelJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_localAxisA(localAxisA.getBox2d()); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateWeldJoint(objectA, objectB, anchor, collide=false) { anchor ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchor); const localAnchorB = objectB.worldToLocal(anchor); const jointDef = new box2d.b2WeldJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle()); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateFrictionJoint(objectA, objectB, anchor, collide=false) { anchor ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchor); const localAnchorB = objectB.worldToLocal(anchor); const jointDef = new box2d.b2FrictionJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateRopeJoint(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false) { anchorA ||= vec2(objectA.body.GetPosition()); anchorB ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchorA); const localAnchorB = objectB.worldToLocal(anchorB); const jointDef = new box2d.b2RopeJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreatePulleyJoint(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false) { anchorA ||= vec2(objectA.body.GetPosition()); anchorB ||= vec2(objectB.body.GetPosition()); const localAnchorA = objectA.worldToLocal(anchorA); const localAnchorB = objectB.worldToLocal(anchorB); const jointDef = new box2d.b2PulleyJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_groundAnchorA(groundAnchorA.getBox2d()); jointDef.set_groundAnchorB(groundAnchorB.getBox2d()); jointDef.set_localAnchorA(localAnchorA.getBox2d()); jointDef.set_localAnchorB(localAnchorB.getBox2d()); jointDef.set_ratio(ratio); jointDef.set_lengthA(groundAnchorA.distance(anchorA)); jointDef.set_lengthB(groundAnchorB.distance(anchorB)); jointDef.set_collideConnected(collide); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateMotorJoint(objectA, objectB) { const linearOffset = objectA.worldToLocal(vec2(objectB.body.GetPosition())); const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle(); const jointDef = new box2d.b2MotorJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_linearOffset(linearOffset.getBox2d()); jointDef.set_angularOffset(angularOffset); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dCreateGearJoint(objectA, objectB, joint1, joint2, ratio=1) { const jointDef = new box2d.b2GearJointDef(); jointDef.set_bodyA(objectA.body); jointDef.set_bodyB(objectB.body); jointDef.set_joint1(joint1); jointDef.set_joint2(joint2); jointDef.set_ratio(ratio); return box2dCastObject(box2dWorld.CreateJoint(jointDef)); } function box2dDestroyJoint(joint) { box2dWorld.DestroyJoint(joint); } /////////////////////////////////////////////////////////////////////////////// // Box2D Helper Functions function box2dIsNull(object) { return !box2d.getPointer(object); } function box2dCreateFixtureDef(shape, density=1, friction=.2, restitution=0, isSensor=false) { const fd = new box2d.b2FixtureDef(); fd.set_shape(shape); fd.set_density(density); fd.set_friction(friction); fd.set_restitution(restitution); fd.set_isSensor(isSensor); return fd; } function box2dCreatePointList(points) { const buffer = box2d._malloc(points.length * 8); for (let i=0, offset=0; i<points.length; ++i) { box2d.HEAPF32[buffer + offset >> 2] = points[i].x; offset += 4; box2d.HEAPF32[buffer + offset >> 2] = points[i].y; offset += 4; } return box2d.wrapPointer(buffer, box2d.b2Vec2); } function box2dCreatePolygonShape(points) { ASSERT(3 <= points.length && points.length <= 8); const shape = new box2d.b2PolygonShape(); const box2dPoints = box2dCreatePointList(points); shape.Set(box2dPoints, points.length); return shape; } function box2dCastObject(object) { if (object instanceof box2d.b2Shape) { switch (object.GetType()) { case box2d.b2Shape.e_circle: return box2d.castObject(object, box2d.b2CircleShape); case box2d.b2Shape.e_edge: return box2d.castObject(object, box2d.b2EdgeShape); case box2d.b2Shape.e_polygon: return box2d.castObject(object, box2d.b2PolygonShape); case box2d.b2Shape.e_chain: return box2d.castObject(object, box2d.b2ChainShape); } } else if (object instanceof box2d.b2Joint) { switch (object.GetType()) { case box2d.e_revoluteJoint: return box2d.castObject(object, box2d.b2RevoluteJoint); case box2d.e_prismaticJoint: return box2d.castObject(object, box2d.b2PrismaticJoint); case box2d.e_distanceJoint: return box2d.castObject(object, box2d.b2DistanceJoint); case box2d.e_pulleyJoint: return box2d.castObject(object, box2d.b2PulleyJoint); case box2d.e_mouseJoint: return box2d.castObject(object, box2d.b2MouseJoint); case box2d.e_gearJoint: return box2d.castObject(object, box2d.b2GearJoint); case box2d.e_wheelJoint: return box2d.castObject(object, box2d.b2WheelJoint); case box2d.e_weldJoint: return box2d.castObject(object, box2d.b2WeldJoint); case box2d.e_frictionJoint: return box2d.castObject(object, box2d.b2FrictionJoint); case box2d.e_ropeJoint: return box2d.castObject(object, box2d.b2RopeJoint); case box2d.e_motorJoint: return box2d.castObject(object, box2d.b2MotorJoint); } } ASSERT(false, 'Unknown object type'); } function box2dWarmup(frames=100) { // run the sim for a few frames to let objects settle for (let i=frames; i--;) box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations); } /////////////////////////////////////////////////////////////////////////////// // Box2D Drawing function box2dDrawFixture(fixture, pos, angle, fillColor, outlineColor, lineWidth) { const shape = box2dCastObject(fixture.GetShape()); switch (shape.GetType()) { case box2d.b2Shape.e_polygon: { let points = []; for (let i=shape.GetVertexCount(); i--;) points.push(vec2(shape.GetVertex(i))); box2dDrawPoly(pos, angle, points, fillColor, outlineColor, lineWidth); break; } case box2d.b2Shape.e_circle: { const radius = shape.get_m_radius(); box2dDrawCircle(pos, radius, fillColor, outlineColor, lineWidth); break; } case box2d.b2Shape.e_edge: { const v1 = vec2(shape.get_m_vertex1()); const v2 = vec2(shape.get_m_vertex2()); box2dDrawLine(pos, angle, v1, v2, fillColor, lineWidth); break; } } } function box2dDrawCircle(pos, radius, color=WHITE, outlineColor, lineWidth=.1, context) { drawCanvas2D(pos, vec2(1), 0, 0, context=> { context.beginPath(); context.arc(0, 0, radius, 0, 9); box2dDrawFillStroke(context, color, outlineColor, lineWidth); }, 0, context); } function box2dDrawPoly(pos, angle, points, color=WHITE, outlineColor, lineWidth=.1, context) { drawCanvas2D(pos, vec2(1), angle, 0, context=> { context.beginPath(); points.forEach(p=>context.lineTo(p.x, p.y)); context.closePath(); box2dDrawFillStroke(context, color, outlineColor, lineWidth); }, 0, context); } function box2dDrawLine(pos, angle, posA, posB, color=WHITE, lineWidth=.1, context) { drawCanvas2D(pos, vec2(1), angle, 0, context=> { context.beginPath(); context.lineTo(posA.x, posA.y); context.lineTo(posB.x, posB.y); box2dDrawFillStroke(context, 0, color, lineWidth); }, 0, context); } function box2dDrawFillStroke(context, color, outlineColor, lineWidth) { if (color) { context.fillStyle = color.toString(); context.fill(); } if (outlineColor && lineWidth) { context.lineWidth = lineWidth; context.lineJoin = context.lineCap = 'round'; context.strokeStyle = outlineColor.toString(); context.stroke(); } } /////////////////////////////////////////////////////////////////////////////// // Box2D Setup function box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources) { Box2D().then(_box2d=> { // setup box2d box2d = _box2d; box2dWorld = new box2d.b2World(); // override functions for box2d setGravity = function(newGravity) { box2dWorld.SetGravity(vec2(0,newGravity).getBox2d()); gravity = newGravity*timeDelta*timeDelta; // engine gravity } // allow passing box2d vectors to vec2 const defaultVec2 = vec2; vec2 = function(x, y) { return (x instanceof box2d.b2Vec2) ? new Vector2(x.get_x(), x.get_y()) : defaultVec2(x, y); } // functions to convert between vec2 and box2d vectors Vector2.prototype.setBox2d = function(p) { return this.set(p.get_x(), p.get_y()); } Vector2.prototype.getBox2d = function() { return new box2d.b2Vec2(this.x, this.y); } Vector2.prototype.setBox2dPointer = function(p) { return this.setBox2d(box2d.wrapPointer(p, box2d.b2Vec2)); } // functions to convert between color and box2d colors Color.prototype.setBox2d = function(c) { return this.set(c.get_r(), c.get_g(), c.get_b()); } Color.prototype.setBox2dPointer = function(c) { return this.setBox2d(box2d.wrapPointer(c, box2d.b2Color)); } // setup contact listener const listener = new box2d.JSContactListener(); listener.BeginContact = function(contactPtr) { const contact = box2d.wrapPointer(contactPtr, box2d.b2Contact); const fixtureA = contact.GetFixtureA(); const fixtureB = contact.GetFixtureB(); const objectA = fixtureA.GetBody().object; const objectB = fixtureB.GetBody().object; objectA.beginContact(objectB, contact); objectB.beginContact(objectA, contact); } listener.EndContact = function(contactPtr) { const contact = box2d.wrapPointer(contactPtr, box2d.b2Contact); const fixtureA = contact.GetFixtureA(); const fixtureB = contact.GetFixtureB(); const objectA = fixtureA.GetBody().object; const objectB = fixtureB.GetBody().object; objectA.endContact(objectB, contact); objectB.endContact(objectA, contact); }; listener.PreSolve = function() {}; listener.PostSolve = function() {}; box2dWorld.SetContactListener(listener); // setup debug draw box2dDebugDraw = box2dGetDebugDraw(); box2dDebugDraw.AppendFlags(box2d.b2Draw.e_shapeBit); box2dDebugDraw.AppendFlags(box2d.b2Draw.e_jointBit); //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_aabbBit); //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_pairBit); //box2dDebugDraw.AppendFlags(box2d.b2Draw.e_centerOfMassBit); box2dWorld.SetDebugDraw(box2dDebugDraw); // hook up box2d plugin to update and render engineAddPlugin(box2dUpdate, box2dRender); function box2dUpdate() { if (!paused) box2dWorld.Step(timeDelta, box2dStepIterations, box2dStepIterations); } function box2dRender() { if (box2dDebug || debugPhysics && debugOverlay) box2dWorld.DrawDebugData(); } // start littlejs engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources); // box2d debug drawing implementation function box2dGetDebugDraw() { const debugDraw = new box2d.JSDraw(); debugDraw.DrawSegment = function(point1, point2, color) { color = getDebugColor(color); point1 = vec2().setBox2dPointer(point1); point2 = vec2().setBox2dPointer(point2); box2dDrawLine(vec2(), 0, point1, point2, color, undefined, overlayContext); }; debugDraw.DrawPolygon = function(vertices, vertexCount, color) { color = getDebugColor(color); const points = getPointsList(vertices, vertexCount); box2dDrawPoly(vec2(), 0, points, undefined, color, undefined, overlayContext); }; debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color) { color = getDebugColor(color); const points = getPointsList(vertices, vertexCount); box2dDrawPoly(vec2(), 0, points, color, color, undefined, overlayContext); }; debugDraw.box2dDrawCircle = function(center, radius, color) { color = getDebugColor(color); center = vec2().setBox2dPointer(center); box2dDrawCircle(center, radius, undefined, color, undefined, overlayContext); }; debugDraw.DrawSolidCircle = function(center, radius, axis, color) { color = getDebugColor(color); center = vec2().setBox2dPointer(center); axis = vec2().setBox2dPointer(axis).scale(radius); box2dDrawCircle(center, radius, color, color, undefined, overlayContext); box2dDrawLine(center, 0, vec2(), axis, color, undefined, overlayContext); }; debugDraw.DrawTransform = function(transform) { transform = box2d.wrapPointer(transform, box2d.b2Transform); const pos = vec2(transform.get_p()); const angle = -transform.get_q().GetAngle(); const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8) const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8); box2dDrawLine(pos, angle, vec2(), p1, c1, undefined, overlayContext); box2dDrawLine(pos, angle, vec2(), p2, c2, undefined, overlayContext); } function getDebugColor(color) { color = rgb().setBox2dPointer(color); color.a = .8; return color; } function getPointsList(vertices, vertexCount) { const points = []; for (let i=vertexCount; i--;) points.push(vec2().setBox2dPointer(vertices+i*8)); return points } return debugDraw; } }); }