Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
We require a high quality engine for both collision detection and dynamics animation. In the current version we utilized the open source Bullet engine which is quite good. It should be straightfoward to plug in alternate engines, although we haven't done so. The input to the engine is the initial world transform of the body, its collision geometry, mass, friction, restitution, initial velocity, initial spin, initial orientation, initial position, and center of mass. For each body, the output of the engine per frame is a new world space transform, and a set of contact points indicating its collisions with other bodies. Our current system operates uniformly on 2d and 3d objects.
Dynamics Worlds, Colliders, and Collidables
In our system the Node class also implements the Collidable interface, which is defined as follows:
public mixin class Collidable {
public var collider: Collider;
public var dynamicsWorld: DynamicsWorld;
public abstract function setCollisionWorldTransform(mat:Mat4):Void;
public abstract function getCollisionWorldTransform():Mat4;
public abstract function getCollisionShape: CollisionShape;
}
DynamicsWorld encapsulates the physics engine. There can be multiple such per scene. CollisionShape's are subset of general geometry which can be managed efficiently by the physics engine: convex hulls, boxes, spheres, etc. Dependending on their type, various Node subclasses override the getCollisionShape() function to return such appropriately. The other two functions provide the callbacks for the physics engine to obtain the body's current world space transform, and to subsequently assign a new one if the body is dynamic. Collider objects encapsulate the above mentioned inputs, provide dynamics operations such as applying forces, optionally record collisions and contact points, and provide user callbacks as collisions are entered and exited. Colliders may be dynamic or static. If static they still apply forces to dynamic objects, but do not themselves respond to forces applied to them.
public class Collider {
public var mass: Number = 0;
public var dynamic: Boolean = false;
public var friction: Number = 0.5;
public var restitution: Number = 0;
public var tracksCollisions: Boolean = false;
public var initialVelocity: Vec3 = = Vec3.ZERO;
public var initialSpin: Vec3 = = Vec3.ZERO;
public var initialOrientation: Vec3 = Vec3.Z_AXIS;
public var initialPosition: Vec3 = Vec3.ZERO;
public var centerOfMass: Vec3 = Vec3.ZERO;
public var active: Boolean = true
public-read protected var collisions: Collision[];
public var onCollisionEnter: function(collision: Collision):Void;
public var onCollisionLeave: function(collision: Collision):Void;
public var collidable: Collidable;
public var shape: Collidable;
public function applyForce(v:Vec3):Void
public function applyForce(x:Number, y:Number, z:Number):Void
public function applyTarque(v:Vec3):Void
public function applyTorque(x:Number, y:Number, z:Number):Void
public function setAngularFactor(t:Number):Void
public function getLinearVelocity():Vec3
public function getAngularVelocity():Vec3
public function setLinearVelocity(v:Vec3):Void
public function setLinearVelocity(x:Number, y:Number, z:Number):Void
public function setAngularVelocity(v:Vec3):Void
public function setAngularVelocity(x:Number, y:Number, z:Number):Void
public function applyImpulse(v:Vec3):Void;
public function applyImpulse(x:Number, y:Number, z:Number):Void
public function applyTarqueImpulse(v:Vec3):Void
public function applyTorqueImpulse(x:Number, y:Number, z:Number):Void
}
The collision shape used by the Collider can be overridden by the user by assigning an alternate collidable to its shape variable.
Collision and ContactPoint are defined as follows:
public class ContactPoint {
public-init var point: Vec3;
public-init var normal: Vec3;
public-init var thisCollider: Collider;
public-init var otherCollider: Collider;
}
public class Collision {
public var collider: Collider;
public var contacts: ContactPoint[];
}
Force Fields and Constraints
In addition to gravity fields, we support the creation of arbitrary force fields, for example radial fields, vortex fields, turbulence fields, etc. Such fields extend the ForceField class and implement their behavior by overriding the applyForceField function and applying forces to the bodies passed in. The applyForceField function is called once per frame by the dynamics world.
public abstract class ForceField {
public var active: Boolean = true;
public var magnitude: Number;
public var attenuation: Number;
public var maxDistance: Number;
public var worldTransform: Mat4;
public var restrictTo: Collider[];
public function apply(world:DynamicsWorld):Void {...}
public abstract function applyForceField(bodies:Collider[]):Void;
}
The restrictTo variable allows the user to apply the field to only a subset of the bodies currently in the dynamics world.
For example, here's a possible implementation of a radial field:
public class RadialField extends ForceField {
override public function applyForceField(bodies:Collider[]):Void {
var fieldLocation = worldTransform.getTranslation();
for (body in bodies) {
var t = body.getCollisionWorldTransform();
var bodyLocation = t.getTranslation();
var dir = fieldLocation - bodyLocation;
var dist = dir.length();
if ((dist > 0 and dist <= maxDistance)) {
def force = dir * -magnitude;
body.applyForce(force);
}
}
}
}
Rigid bodies may be linked with constraints, for example hinges, sliders, or springs. Implementations of such constraints extend the RigidBodyConstraint class. Calling its apply() function makes the constraint active in the dynamics world associated with body1 and body2, calling its remove() function deactivates it. Activation/deactivation of constraints is typically asynchronous, as such the onApply and onRemove functions are provided as callbacks to the constraint implementation, when the constraint actually becomes active or inactive in the dynamics world.
public abstract class RigidBodyConstraint {
public-init var body1: Collidable;
public-init var body2: Collidable;
public function apply():Void { ... }
public function remove():Void;
protected abstract function onApply():Void;
protected abstract function onRemove():Void;
}