littlejsengine
Version:
LittleJS - Tiny and Fast HTML5 Game Engine
1,358 lines (1,172 loc) • 72.8 kB
JavaScript
/**
* LittleJS Box2D Physics Plugin
* - Box2dObject extends EngineObject with Box2D physics
* - Call box2dInit() to enable
* - You will also need to include box2d.wasm.js
* - Uses a super fast web assembly port of Box2D v2.3.1
* - More info: https://github.com/kripken/box2d.js
* - Functions to create polygon, circle, and edge shapes
* - Contact begin and end callbacks
* - Wraps b2Vec2 type to/from Vector2
* - Raycasting and querying
* - Every type of joint
* - Debug physics drawing
* @namespace Box2D
*/
'use strict';
/** Global Box2d Plugin object
* @type {Box2dPlugin}
* @memberof Box2D */
let box2d;
/** Enable Box2D debug drawing
* @type {boolean}
* @default
* @memberof Box2D */
let box2dDebug = false;
/** Enable Box2D debug drawing
* @param {boolean} enable
* @memberof Box2D */
function box2dSetDebug(enable) { box2dDebug = enable; }
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Object - extend with your own custom physics objects
* - A LittleJS object with Box2D physics, dynamic by default
* - Provides interface for Box2D body and fixture functions
* - Each object can have multiple fixtures and joints
* @extends EngineObject
* @memberof Box2D
*/
class Box2dObject extends EngineObject
{
/** Create a LittleJS object with Box2d physics
* @param {Vector2} [pos]
* @param {Vector2} [size]
* @param {TileInfo} [tileInfo]
* @param {number} [angle]
* @param {Color} [color]
* @param {number} [bodyType]
* @param {number} [renderOrder] */
constructor(pos=vec2(), size=vec2(), tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
{
super(pos, size, tileInfo, angle, color, renderOrder);
// create physics body
const bodyDef = new box2d.instance.b2BodyDef();
bodyDef.set_type(bodyType);
bodyDef.set_position(box2d.vec2dTo(pos));
bodyDef.set_angle(-angle);
this.body = box2d.world.CreateBody(bodyDef);
this.body.object = this;
this.lineColor = BLACK;
box2d.objects.push(this);
// edge lists and loops for drawing
this.edgeLists = [];
this.edgeLoops = [];
}
/** Destroy this object and its physics body */
destroy()
{
if (this.destroyed)
return;
// destroy physics body, fixtures, and joints
ASSERT(this.body, 'Box2dObject has no body to destroy');
box2d.world.DestroyBody(this.body);
super.destroy();
}
/** Box2d objects updated with Box2d world step */
updatePhysics() {}
/** Render the object, uses box2d drawing if no tile info exists */
render()
{
// use default render or draw fixtures
if (this.tileInfo)
super.render();
else
this.drawFixtures(this.color, this.lineColor, this.lineWidth);
}
/** Render debug info */
renderDebugInfo()
{
const isAsleep = !this.getIsAwake();
const isStatic = this.getBodyType() === box2d.bodyTypeStatic;
const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
this.drawFixtures(color);
}
/** Draws all this object's fixtures
* @param {Color} [color]
* @param {Color} [lineColor]
* @param {number} [lineWidth]
* @param {CanvasRenderingContext2D} [context] */
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
{
// draw non-edge fixtures
this.getFixtureList().forEach((fixture)=>
{
const shape = box2d.castObjectType(fixture.GetShape());
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
{
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
}
});
// draw edges using a single draw line for better connections
this.edgeLists.forEach(points=>
drawLineList(points, lineWidth, lineColor, false, this.pos, this.angle));
this.edgeLoops.forEach(points=>
drawLineList(points, lineWidth, lineColor, true, this.pos, this.angle));
}
///////////////////////////////////////////////////////////////////////////////
// physics contact callbacks
/** Called when a contact begins
* @param {Box2dObject} otherObject */
beginContact(otherObject) {}
/** Called when a contact ends
* @param {Box2dObject} otherObject */
endContact(otherObject) {}
///////////////////////////////////////////////////////////////////////////////
// physics fixtures and shapes
/** Add a shape fixture to the body
* @param {Object} shape
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
{
ASSERT(isNumber(density), 'density must be a number');
ASSERT(isNumber(friction), 'friction must be a number');
ASSERT(isNumber(restitution), 'restitution must be a number');
const fd = new box2d.instance.b2FixtureDef();
fd.set_shape(shape);
fd.set_density(density);
fd.set_friction(friction);
fd.set_restitution(restitution);
fd.set_isSensor(isSensor);
return this.body.CreateFixture(fd);
}
/** Add a box shape to the body
* @param {Vector2} [size]
* @param {Vector2} [offset]
* @param {number} [angle]
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
{
ASSERT(isVector2(size), 'size must be a Vector2');
ASSERT(size.x > 0 && size.y > 0, 'size must be positive');
ASSERT(isVector2(offset), 'offset must be a Vector2');
ASSERT(isNumber(angle), 'angle must be a number');
const shape = new box2d.instance.b2PolygonShape();
shape.SetAsBox(size.x/2, size.y/2, box2d.vec2dTo(offset), angle);
return this.addShape(shape, density, friction, restitution, isSensor);
}
/** Add a polygon shape to the body
* @param {Array<Vector2>} points
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addPoly(points, density, friction, restitution, isSensor)
{
ASSERT(isArray(points), 'points must be an array');
function box2dCreatePolygonShape(points)
{
function box2dCreatePointList(points)
{
const buffer = box2d.instance._malloc(points.length * 8);
for (let i=0, offset=0; i<points.length; ++i)
{
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
offset += 4;
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
offset += 4;
}
return box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
}
ASSERT(3 <= points.length && points.length <= 8);
const shape = new box2d.instance.b2PolygonShape();
const box2dPoints = box2dCreatePointList(points);
shape.Set(box2dPoints, points.length);
return shape;
}
const shape = box2dCreatePolygonShape(points);
return this.addShape(shape, density, friction, restitution, isSensor);
}
/** Add a regular polygon shape to the body
* @param {number} [diameter]
* @param {number} [sides]
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
{
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
ASSERT(isNumber(sides) && sides>2, 'sides must be a positive number greater than 2');
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);
}
/** Add a random polygon shape to the body
* @param {number} [diameter]
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
{
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
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);
}
/** Add a circle shape to the body
* @param {number} [diameter]
* @param {Vector2} [offset]
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
{
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
ASSERT(isVector2(offset), 'offset must be a Vector2');
const shape = new box2d.instance.b2CircleShape();
shape.set_m_p(box2d.vec2dTo(offset));
shape.set_m_radius(diameter/2);
return this.addShape(shape, density, friction, restitution, isSensor);
}
/** Add an edge shape to the body
* @param {Vector2} point1
* @param {Vector2} point2
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addEdge(point1, point2, density, friction, restitution, isSensor)
{
ASSERT(isVector2(point1), 'point1 must be a Vector2');
ASSERT(isVector2(point2), 'point2 must be a Vector2');
const shape = new box2d.instance.b2EdgeShape();
shape.Set(box2d.vec2dTo(point1), box2d.vec2dTo(point2));
return this.addShape(shape, density, friction, restitution, isSensor);
}
/** Add an edge list to the body
* @param {Array<Vector2>} points
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addEdgeList(points, density, friction, restitution, isSensor)
{
ASSERT(isArray(points), 'points must be an array');
const fixtures = [], edgePoints = [];
for (let i=0; i<points.length-1; ++i)
{
const shape = new box2d.instance.b2EdgeShape();
points[i-1] && shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));
points[i+0] && shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));
points[i+1] && shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));
points[i+2] && shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));
const f = this.addShape(shape, density, friction, restitution, isSensor);
fixtures.push(f);
edgePoints.push(points[i].copy());
}
edgePoints.push(points[points.length-1].copy());
this.edgeLists.push(edgePoints);
return fixtures;
}
/** Add an edge loop to the body, an edge loop connects the end points
* @param {Array<Vector2>} points
* @param {number} [density]
* @param {number} [friction]
* @param {number} [restitution]
* @param {boolean} [isSensor] */
addEdgeLoop(points, density, friction, restitution, isSensor)
{
ASSERT(isArray(points), 'points must be an array');
const fixtures = [], edgePoints = [];
const getPoint = i=> points[mod(i,points.length)];
for (let i=0; i<points.length; ++i)
{
const shape = new box2d.instance.b2EdgeShape();
shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));
shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));
shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));
shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));
const f = this.addShape(shape, density, friction, restitution, isSensor);
fixtures.push(f);
i < points.length && edgePoints.push(points[i].copy());
}
this.edgeLoops.push(edgePoints);
return fixtures;
}
///////////////////////////////////////////////////////////////////////////////
// physics get functions
/** Gets the center of mass
* @return {Vector2} */
getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
/** Gets the linear velocity
* @return {Vector2} */
getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
/** Gets the angular velocity
* @return {Vector2} */
getAngularVelocity() { return this.body.GetAngularVelocity(); }
/** Gets the mass
* @return {number} */
getMass() { return this.body.GetMass(); }
/** Gets the rotational inertia
* @return {number} */
getInertia() { return this.body.GetInertia(); }
/** Check if this object is awake
* @return {boolean} */
getIsAwake() { return this.body.IsAwake(); }
/** Gets the physics body type
* @return {number} */
getBodyType() { return this.body.GetType(); }
/** Get the speed of this object
* @return {number} */
getSpeed() { return this.getLinearVelocity().length(); }
///////////////////////////////////////////////////////////////////////////////
// physics set functions
/** Sets the position and angle
* @param {Vector2} pos
* @param {number} angle */
setTransform(pos, angle)
{
this.pos = pos;
this.angle = angle;
this.body.SetTransform(box2d.vec2dTo(pos), angle);
}
/** Sets the position
* @param {Vector2} pos */
setPosition(pos)
{ this.setTransform(pos, this.body.GetAngle()); }
/** Sets the angle
* @param {number} angle */
setAngle(angle)
{ this.setTransform(box2d.vec2From(this.body.GetPosition()), -angle); }
/** Sets the linear velocity
* @param {Vector2} velocity */
setLinearVelocity(velocity)
{ this.body.SetLinearVelocity(box2d.vec2dTo(velocity)); }
/** Sets the angular velocity
* @param {number} angularVelocity */
setAngularVelocity(angularVelocity)
{ this.body.SetAngularVelocity(angularVelocity); }
/** Sets the linear damping
* @param {number} damping */
setLinearDamping(damping)
{ this.body.SetLinearDamping(damping); }
/** Sets the angular damping
* @param {number} damping */
setAngularDamping(damping)
{ this.body.SetAngularDamping(damping); }
/** Sets the gravity scale
* @param {number} [scale] */
setGravityScale(scale=1)
{ this.body.SetGravityScale(this.gravityScale = scale); }
/** Should be like a bullet for continuous collision detection?
* @param {boolean} [isBullet] */
setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
/** Set the sleep state of the body
* @param {boolean} [isAwake] */
setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
/** Set the physics body type
* @param {number} type */
setBodyType(type) { this.body.SetType(type); }
/** Set whether the body is allowed to sleep
* @param {boolean} [isAllowed] */
setSleepingAllowed(isAllowed=true)
{ this.body.SetSleepingAllowed(isAllowed); }
/** Set whether the body can rotate
* @param {boolean} [isFixed] */
setFixedRotation(isFixed=true)
{ this.body.SetFixedRotation(isFixed); }
/** Set the center of mass of the body
* @param {Vector2} center */
setCenterOfMass(center) { this.setMassData(center) }
/** Set the mass of the body
* @param {number} mass */
setMass(mass) { this.setMassData(undefined, mass) }
/** Set the moment of inertia of the body
* @param {number} momentOfInertia */
setMomentOfInertia(momentOfInertia)
{ this.setMassData(undefined, undefined, momentOfInertia) }
/** Reset the mass, center of mass, and moment */
resetMassData() { this.body.ResetMassData(); }
/** Set the mass data of the body
* @param {Vector2} [localCenter]
* @param {number} [mass]
* @param {number} [momentOfInertia] */
setMassData(localCenter, mass, momentOfInertia)
{
const data = new box2d.instance.b2MassData();
this.body.GetMassData(data);
localCenter && data.set_center(box2d.vec2dTo(localCenter));
mass && data.set_mass(mass);
momentOfInertia && data.set_I(momentOfInertia);
this.body.SetMassData(data);
}
/** Set the collision filter data for this body
* @param {number} [categoryBits]
* @param {number} [ignoreCategoryBits]
* @param {number} [groupIndex] */
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);
});
}
/** Set if this body is a sensor
* @param {boolean} [isSensor] */
setSensor(isSensor=true)
{ this.getFixtureList().forEach(f=>f.SetSensor(isSensor)); }
///////////////////////////////////////////////////////////////////////////////
// physics force and torque functions
/** Apply force to this object
* @param {Vector2} force
* @param {Vector2} [pos] */
applyForce(force, pos)
{
pos ||= this.getCenterOfMass();
this.setAwake();
this.body.ApplyForce(box2d.vec2dTo(force), box2d.vec2dTo(pos));
}
/** Apply acceleration to this object
* @param {Vector2} acceleration
* @param {Vector2} [pos] */
applyAcceleration(acceleration, pos)
{
pos ||= this.getCenterOfMass();
this.setAwake();
this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration), box2d.vec2dTo(pos));
}
/** Apply torque to this object
* @param {number} torque */
applyTorque(torque)
{
this.setAwake();
this.body.ApplyTorque(torque);
}
/** Apply angular acceleration to this object
* @param {number} acceleration */
applyAngularAcceleration(acceleration)
{
this.setAwake();
this.body.ApplyAngularImpulse(acceleration);
}
///////////////////////////////////////////////////////////////////////////////
// lists of fixtures and joints
/** Check if this object has any fixtures
* @return {boolean} */
hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
/** Get list of fixtures for this object
* @return {Array<Object>} */
getFixtureList()
{
const fixtures = [];
for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
{
fixtures.push(fixture);
fixture = fixture.GetNext();
}
return fixtures;
}
/** Check if this object has any joints
* @return {boolean} */
hasJoints() { return !box2d.isNull(this.body.GetJointList()); }
/** Get list of joints for this object
* @return {Array<Object>} */
getJointList()
{
const joints = [];
for (let joint=this.body.GetJointList(); !box2d.isNull(joint); )
{
joints.push(joint);
joint = joint.get_next();
}
return joints;
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Raycast Result
* - Holds results from a box2d raycast queries
* - Automatically created by box2d raycast functions
*/
class Box2dRaycastResult
{
/** Create a raycast result
* @param {Object} fixture
* @param {Vector2} point
* @param {Vector2} normal
* @param {number} fraction */
constructor(fixture, point, normal, fraction)
{
/** @property {Box2dObject} - The box2d object */
this.object = fixture.GetBody().object;
/** @property {Object} - The fixture that was hit */
this.fixture = fixture;
/** @property {Vector2} - The hit point */
this.point = point;
/** @property {Vector2} - The hit normal */
this.normal = normal;
/** @property {number} - Distance fraction at the point of intersection */
this.fraction = fraction;
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Joint
* - Base class for Box2D joints
* - A joint is used to connect objects together
* @memberof Box2D
*/
class Box2dJoint
{
/** Create a box2d joint, the base class is not intended to be used directly
* @param {Object} jointDef */
constructor(jointDef)
{
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
}
/** Destroy this joint */
destroy() { box2d.world.DestroyJoint(this.box2dJoint); this.box2dJoint = 0; }
/** Get the first object attached to this joint
* @return {Box2dObject} */
getObjectA() { return this.box2dJoint.GetBodyA().object; }
/** Get the second object attached to this joint
* @return {Box2dObject} */
getObjectB() { return this.box2dJoint.GetBodyB().object; }
/** Get the first anchor for this joint in world coordinates
* @return {Vector2} */
getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
/** Get the second anchor for this joint in world coordinates
* @return {Vector2} */
getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
/** Get the reaction force on bodyB at the joint anchor given a time step
* @param {number} time
* @return {Vector2} */
getReactionForce(time) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
/** Get the reaction torque on bodyB in N*m given a time step
* @param {number} time
* @return {number} */
getReactionTorque(time) { return this.box2dJoint.GetReactionTorque(1/time);}
/** Check if the connected bodies should collide
* @return {boolean} */
getCollideConnected() { return this.box2dJoint.getCollideConnected();}
/** Check if either connected body is active
* @return {boolean} */
isActive() { return this.box2dJoint.IsActive();}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Target Joint, also known as a mouse joint
* - Used to make a point on a object track a specific world point target
* - This a soft constraint with a max force
* - This allows the constraint to stretch and without applying huge forces
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dTargetJoint extends Box2dJoint
{
/** Create a target joint
* @param {Box2dObject} object
* @param {Box2dObject} fixedObject
* @param {Vector2} worldPos */
constructor(object, fixedObject, worldPos)
{
object.setAwake();
const jointDef = new box2d.instance.b2MouseJointDef();
jointDef.set_bodyA(fixedObject.body);
jointDef.set_bodyB(object.body);
jointDef.set_target(box2d.vec2dTo(worldPos));
jointDef.set_maxForce(2e3 * object.getMass());
super(jointDef);
}
/** Set the target point in world coordinates
* @param {Vector2} pos */
setTarget(pos) { this.box2dJoint.SetTarget(box2d.vec2dTo(pos)); }
/** Get the target point in world coordinates
* @return {Vector2} */
getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
/** Sets the maximum force in Newtons
* @param {number} force */
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
/** Gets the maximum force in Newtons
* @return {number} */
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
/** Sets the joint frequency in Hertz
* @param {number} hz */
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
/** Gets the joint frequency in Hertz
* @return {number} */
getFrequency() { return this.box2dJoint.GetFrequency(); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Distance Joint
* - Constrains two points on two objects to remain at a fixed distance
* - You can view this as a massless, rigid rod
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dDistanceJoint extends Box2dJoint
{
/** Create a distance joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchorA
* @param {Vector2} anchorB
* @param {boolean} [collide] */
constructor(objectA, objectB, anchorA, anchorB, collide=false)
{
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchorA);
const localAnchorB = objectB.worldToLocal(anchorB);
const jointDef = new box2d.instance.b2DistanceJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_length(anchorA.distance(anchorB));
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Set the length of the joint
* @param {number} length */
setLength(length) { this.box2dJoint.SetLength(length); }
/** Get the length of the joint
* @return {number} */
getLength() { return this.box2dJoint.GetLength(); }
/** Set the frequency in Hertz
* @param {number} hz */
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); }
/** Get the frequency in Hertz
* @return {number} */
getFrequency() { return this.box2dJoint.GetFrequency(); }
/** Set the damping ratio
* @param {number} ratio */
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); }
/** Get the damping ratio
* @return {number} */
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Pin Joint
* - Pins two objects together at a point
* @extends Box2dDistanceJoint
* @memberof Box2D
*/
class Box2dPinJoint extends Box2dDistanceJoint
{
/** Create a pin joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} [pos]
* @param {boolean} [collide] */
constructor(objectA, objectB, pos=objectA.pos, collide=false)
{
super(objectA, objectB, undefined, pos, collide);
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Rope Joint
* - Enforces a maximum distance between two points on two objects
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dRopeJoint extends Box2dJoint
{
/** Create a rope joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchorA
* @param {Vector2} anchorB
* @param {number} extraLength
* @param {boolean} [collide] */
constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
{
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchorA);
const localAnchorB = objectB.worldToLocal(anchorB);
const jointDef = new box2d.instance.b2RopeJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Set the max length of the joint
* @param {number} length */
setMaxLength(length) { this.box2dJoint.SetMaxLength(length); }
/** Get the max length of the joint
* @return {number} */
getMaxLength() { return this.box2dJoint.GetMaxLength(); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Revolute Joint
* - Constrains two objects to share a point while they are free to rotate around the point
* - The relative rotation about the shared point is the joint angle
* - You can limit the relative rotation with a joint limit
* - You can use a motor to drive the relative rotation about the shared point
* - A maximum motor torque is provided so that infinite forces are not generated
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dRevoluteJoint extends Box2dJoint
{
/** Create a revolute joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchor
* @param {boolean} [collide] */
constructor(objectA, objectB, anchor, collide=false)
{
anchor ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchor);
const localAnchorB = objectB.worldToLocal(anchor);
const jointDef = new box2d.instance.b2RevoluteJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Get the reference angle, objectB angle minus objectA angle in the reference state
* @return {number} */
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
/** Get the current joint angle
* @return {number} */
getJointAngle() { return this.box2dJoint.GetJointAngle(); }
/** Get the current joint angle speed in radians per second
* @return {number} */
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
/** Is the joint limit enabled?
* @return {boolean} */
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
/** Enable/disable the joint limit
* @param {boolean} [enable] */
enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
/** Get the lower joint limit
* @return {number} */
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
/** Get the upper joint limit
* @return {number} */
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
/** Set the joint limits
* @param {number} min
* @param {number} max */
setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
/** Is the joint motor enabled?
* @return {boolean} */
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
/** Enable/disable the joint motor
* @param {boolean} [enable] */
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
/** Set the motor speed
* @param {number} speed */
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
/** Get the motor speed
* @return {number} */
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
/** Set the motor torque
* @param {number} torque */
setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
/** Get the max motor torque
* @return {number} */
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
/** Get the motor torque given a time step
* @param {number} time
* @return {number} */
getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Gear Joint
* - A gear joint is used to connect two joints together
* - Either joint can be a revolute or prismatic joint
* - You specify a gear ratio to bind the motions together
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dGearJoint extends Box2dJoint
{
/** Create a gear joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Box2dJoint} joint1
* @param {Box2dJoint} joint2
* @param {ratio} [ratio] */
constructor(objectA, objectB, joint1, joint2, ratio=1)
{
const jointDef = new box2d.instance.b2GearJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_joint1(joint1.box2dJoint);
jointDef.set_joint2(joint2.box2dJoint);
jointDef.set_ratio(ratio);
super(jointDef);
this.joint1 = joint1;
this.joint2 = joint2;
}
/** Get the first joint
* @return {Box2dJoint} */
getJoint1() { return this.joint1; }
/** Get the second joint
* @return {Box2dJoint} */
getJoint2() { return this.joint2; }
/** Set the gear ratio
* @param {number} ratio */
setRatio(ratio) { return this.box2dJoint.SetRatio(ratio); }
/** Get the gear ratio
* @return {number} */
getRatio() { return this.box2dJoint.GetRatio(); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Prismatic Joint
* - Provides one degree of freedom: translation along an axis fixed in objectA
* - Relative rotation is prevented
* - You can use a joint limit to restrict the range of motion
* - You can use a joint motor to drive the motion or to model joint friction
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dPrismaticJoint extends Box2dJoint
{
/** Create a prismatic joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchor
* @param {Vector2} worldAxis
* @param {boolean} [collide] */
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
{
anchor ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchor);
const localAnchorB = objectB.worldToLocal(anchor);
const localAxisA = objectB.worldToLocalVector(worldAxis);
const jointDef = new box2d.instance.b2PrismaticJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Get the local joint axis relative to bodyA
* @return {Vector2} */
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
/** Get the reference angle
* @return {number} */
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
/** Get the current joint translation
* @return {number} */
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
/** Get the current joint translation speed
* @return {number} */
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
/** Is the joint limit enabled?
* @return {boolean} */
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
/** Enable/disable the joint limit
* @param {boolean} [enable] */
enableLimit(enable=true) { return this.box2dJoint.enableLimit(enable); }
/** Get the lower joint limit
* @return {number} */
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
/** Get the upper joint limit
* @return {number} */
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
/** Set the joint limits
* @param {number} min
* @param {number} max */
setLimits(min, max) { return this.box2dJoint.SetLimits(min, max); }
/** Is the motor enabled?
* @return {boolean} */
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
/** Enable/disable the joint motor
* @param {boolean} [enable] */
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
/** Set the motor speed
* @param {number} speed */
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
/** Get the motor speed
* @return {number} */
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
/** Set the maximum motor force
* @param {number} force */
setMaxMotorForce(force) { return this.box2dJoint.SetMaxMotorForce(force); }
/** Get the maximum motor force
* @return {number} */
getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
/** Get the motor force given a time step
* @param {number} time
* @return {number} */
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Static Object - Box2d with a static physics body
* @extends Box2dObject
* @memberof Box2D
*/
class Box2dStaticObject extends Box2dObject
{
/** Create a LittleJS object with Box2d physics
* @param {Vector2} [pos]
* @param {Vector2} [size]
* @param {TileInfo} [tileInfo]
* @param {number} [angle]
* @param {Color} [color]
* @param {number} [renderOrder] */
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
{
const bodyType = box2d.bodyTypeStatic;
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Kiematic Object - Box2d with a kinematic physics body
* @extends Box2dObject
* @memberof Box2D
*/
class Box2dKiematicObject extends Box2dObject
{
/** Create a LittleJS object with Box2d physics
* @param {Vector2} [pos]
* @param {Vector2} [size]
* @param {TileInfo} [tileInfo]
* @param {number} [angle]
* @param {Color} [color]
* @param {number} [renderOrder] */
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
{
const bodyType = box2d.bodyTypeKinematic;
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Wheel Joint
* - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
* - You can use a joint limit to restrict the range of motion
* - You can use a joint motor to drive the motion or to model joint friction
* - This joint is designed for vehicle suspensions
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dWheelJoint extends Box2dJoint
{
/** Create a wheel joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchor
* @param {Vector2} worldAxis
* @param {boolean} [collide] */
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
{
anchor ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchor);
const localAnchorB = objectB.worldToLocal(anchor);
const localAxisA = objectB.worldToLocalVector(worldAxis);
const jointDef = new box2d.instance.b2WheelJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Get the local joint axis relative to bodyA
* @return {Vector2} */
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
/** Get the current joint translation
* @return {number} */
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
/** Get the current joint translation speed
* @return {number} */
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
/** Is the joint motor enabled?
* @return {boolean} */
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
/** Enable/disable the joint motor
* @param {boolean} [enable] */
enableMotor(enable=true) { return this.box2dJoint.EnableMotor(enable); }
/** Set the motor speed
* @param {number} speed */
setMotorSpeed(speed) { return this.box2dJoint.SetMotorSpeed(speed); }
/** Get the motor speed
* @return {number} */
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
/** Set the maximum motor torque
* @param {number} torque */
setMaxMotorTorque(torque) { return this.box2dJoint.SetMaxMotorTorque(torque); }
/** Get the max motor torque
* @return {number} */
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
/** Get the motor torque for a time step
* @return {number} */
getMotorTorque(time) { return this.box2dJoint.GetMotorTorque(1/time); }
/** Set the spring frequency in Hertz
* @param {number} hz */
setSpringFrequencyHz(hz) { return this.box2dJoint.SetSpringFrequencyHz(hz); }
/** Get the spring frequency in Hertz
* @return {number} */
getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
/** Set the spring damping ratio
* @param {number} ratio */
setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
/** Get the spring damping ratio
* @return {number} */
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Weld Joint
* - Glues two objects together
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dWeldJoint extends Box2dJoint
{
/** Create a weld joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchor
* @param {boolean} [collide] */
constructor(objectA, objectB, anchor, collide=false)
{
anchor ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchor);
const localAnchorB = objectB.worldToLocal(anchor);
const jointDef = new box2d.instance.b2WeldJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_referenceAngle(objectA.body.GetAngle() - objectB.body.GetAngle());
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Get the reference angle
* @return {number} */
getReferenceAngle() { return this.box2dJoint.GetReferenceAngle(); }
/** Set the frequency in Hertz
* @param {number} hz */
setFrequency(hz) { return this.box2dJoint.SetFrequency(hz); }
/** Get the frequency in Hertz
* @return {number} */
getFrequency() { return this.box2dJoint.GetFrequency(); }
/** Set the damping ratio
* @param {number} ratio */
setSpringDampingRatio(ratio) { return this.box2dJoint.SetSpringDampingRatio(ratio); }
/** Get the damping ratio
* @return {number} */
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
}
///////////////////////////////////////////////////////////////////////////////
/**
* Box2D Friction Joint
* - Used to apply top-down friction
* - Provides 2D translational friction and angular friction
* @extends Box2dJoint
* @memberof Box2D
*/
class Box2dFrictionJoint extends Box2dJoint
{
/** Create a friction joint
* @param {Box2dObject} objectA
* @param {Box2dObject} objectB
* @param {Vector2} anchor
* @param {boolean} [collide] */
constructor(objectA, objectB, anchor, collide=false)
{
anchor ||= box2d.vec2From(objectB.body.GetPosition());
const localAnchorA = objectA.worldToLocal(anchor);
const localAnchorB = objectB.worldToLocal(anchor);
const jointDef = new box2d.instance.b2FrictionJointDef();
jointDef.set_bodyA(objectA.body);
jointDef.set_bodyB(objectB.body);
jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));
jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));
jointDef.set_collideConnected(collide);
super(jointDef);
}
/** Get the local anchor point relative to objectA's origin
* @return {Vector2} */
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
/** Get the local anchor point relative to objectB's origin
* @return {Vector2} */
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
/** Set the maximum friction force
* @param {number} force */
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); }
/** Get the maximum friction force
* @return {number} */
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
/** Set the maximum friction torque
* @param {number} torque */
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(torque); }
/** Get the maximum friction torque
*