var vec2 = require('../math/vec2')
, decomp = require('poly-decomp')
, Convex = require('../shapes/Convex')
module.exports = Body;
var zero = vec2.fromValues(0,0);
/**
* A rigid body. Has got a center of mass, position, velocity and a number of
* shapes that are used for collisions.
*
* @class Body
* @constructor
* @param {Object} [options]
* @param {Number} [options.mass=0] A number >= 0. If zero, the .motionState will be set to Body.STATIC.
* @param {Float32Array|Array} [options.position]
* @param {Float32Array|Array} [options.velocity]
* @param {Number} [options.angle=0]
* @param {Number} [options.angularVelocity=0]
* @param {Float32Array|Array} [options.force]
* @param {Number} [options.angularForce=0]
*
* @todo Should not take mass as argument to Body, but as density to each Shape
*/
function Body(options){
options = options || {};
/**
* The body identifyer
* @property id
* @type {Number}
*/
this.id = ++Body._idCounter;
/**
* The shapes of the body. The local transform of the shape in .shapes[i] is
* defined by .shapeOffsets[i] and .shapeAngles[i].
*
* @property shapes
* @type {Array}
*/
this.shapes = [];
/**
* The local shape offsets, relative to the body center of mass. This is an
* array of Float32Array.
* @property shapeOffsets
* @type {Array}
*/
this.shapeOffsets = [];
/**
* The body-local shape angle transforms. This is an array of numbers (angles).
* @property shapeAngles
* @type {Array}
*/
this.shapeAngles = [];
/**
* The mass of the body.
* @property mass
* @type {number}
*/
this.mass = options.mass || 0;
/**
* The inverse mass of the body.
* @property invMass
* @type {number}
*/
this.invMass = 0;
/**
* The inertia of the body around the Z axis.
* @property inertia
* @type {number}
*/
this.inertia = 0;
/**
* The inverse inertia of the body.
* @property invInertia
* @type {number}
*/
this.invInertia = 0;
this.updateMassProperties();
/**
* The position of the body
* @property position
* @type {Float32Array}
*/
this.position = vec2.fromValues(0,0);
if(options.position) vec2.copy(this.position, options.position);
/**
* The velocity of the body
* @property velocity
* @type {Float32Array}
*/
this.velocity = vec2.fromValues(0,0);
if(options.velocity) vec2.copy(this.velocity, options.velocity);
/**
* Constraint velocity that was added to the body during the last step.
* @property vlambda
* @type {Float32Array}
*/
this.vlambda = vec2.fromValues(0,0);
/**
* Angular constraint velocity that was added to the body during last step.
* @property wlambda
* @type {Float32Array}
*/
this.wlambda = 0;
/**
* The angle of the body
* @property angle
* @type {number}
*/
this.angle = options.angle || 0;
/**
* The angular velocity of the body
* @property angularVelocity
* @type {number}
*/
this.angularVelocity = options.angularVelocity || 0;
/**
* The force acting on the body
* @property force
* @type {Float32Array}
*/
this.force = vec2.create();
if(options.force) vec2.copy(this.force, options.force);
/**
* The angular force acting on the body
* @property angularForce
* @type {number}
*/
this.angularForce = options.angularForce || 0;
/**
* The type of motion this body has. Should be one of: Body.STATIC (the body
* does not move), Body.DYNAMIC (body can move and respond to collisions)
* and Body.KINEMATIC (only moves according to its .velocity).
*
* @property motionState
* @type {number}
*
* @example
* // This body will move and interact with other bodies
* var dynamicBody = new Body();
* dynamicBody.motionState = Body.DYNAMIC;
*
* @example
* // This body will not move at all
* var staticBody = new Body();
* staticBody.motionState = Body.STATIC;
*
* @example
* // This body will only move if you change its velocity
* var kinematicBody = new Body();
* kinematicBody.motionState = Body.KINEMATIC;
*/
this.motionState = this.mass == 0 ? Body.STATIC : Body.DYNAMIC;
/**
* Bounding circle radius
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
this.concavePath = null;
};
Body._idCounter = 0;
/**
* Update the bounding radius of the body. Should be done if any of the shapes
* are changed.
* @method updateBoundingRadius
*/
Body.prototype.updateBoundingRadius = function(){
var shapes = this.shapes,
shapeOffsets = this.shapeOffsets,
N = shapes.length,
radius = 0;
for(var i=0; i!==N; i++){
var shape = shapes[i],
offset = vec2.length(shapeOffsets[i] || zero),
r = shape.boundingRadius;
if(offset + r > radius)
radius = offset + r;
}
this.boundingRadius = radius;
};
/**
* Add a shape to the body. You can pass a local transform when adding a shape,
* so that the shape gets an offset and angle relative to the body center of mass.
* Will automatically update the mass properties and bounding radius.
*
* @method addShape
* @param {Shape} shape
* @param {Float32Array|Array} [offset] Local body offset of the shape.
* @param {Number} [angle] Local body angle.
*
* @example
* var body = new Body(),
* shape = new Circle();
*
* // Add the shape to the body, positioned in the center
* body.addShape(shape);
*
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local x-axis.
* body.addShape(shape,[1,0]);
*
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local y-axis, and rotated 90 degrees CCW.
* body.addShape(shape,[0,1],Math.PI/2);
*/
Body.prototype.addShape = function(shape,offset,angle){
angle = angle || 0.0;
// Copy the offset vector
if(offset){
offset = vec2.fromValues(offset[0],offset[1]);
} else {
offset = vec2.fromValues(0,0);
}
this.shapes .push(shape);
this.shapeOffsets.push(offset);
this.shapeAngles .push(angle);
this.updateMassProperties();
this.updateBoundingRadius();
};
/**
* Remove a shape
* @method removeShape
* @param {Shape} shape
* @return {Boolean} True if the shape was found and removed, else false.
*/
Body.prototype.removeShape = function(shape){
var idx = this.shapes.indexOf(shape);
if(idx != -1){
this.shapes.splice(idx,1);
this.shapeOffsets.splice(idx,1);
this.shapeAngles.splice(idx,1);
return true;
} else
return false;
};
/**
* Updates .inertia, .invMass, .invInertia for this Body. Should be called when
* changing the structure or mass of the Body.
*
* @method updateMassProperties
*
* @example
* body.mass += 1;
* body.updateMassProperties();
*/
Body.prototype.updateMassProperties = function(){
var shapes = this.shapes,
N = shapes.length,
m = this.mass / N,
I = 0;
for(var i=0; i<N; i++){
var shape = shapes[i],
r2 = vec2.squaredLength(this.shapeOffsets[i] || zero),
Icm = shape.computeMomentOfInertia(m);
I += Icm + m*r2;
}
this.inertia = I;
// Inverse mass properties are easy
this.invMass = this.mass > 0 ? 1/this.mass : 0;
this.invInertia = I>0 ? 1/I : 0;
};
var Body_applyForce_r = vec2.create();
/**
* Apply force to a world point. This could for example be a point on the RigidBody surface. Applying force this way will add to Body.force and Body.angularForce.
* @method applyForce
* @param {Float32Array} force The force to add.
* @param {Float32Array} worldPoint A world point to apply the force on.
*/
Body.prototype.applyForce = function(force,worldPoint){
// Compute point position relative to the body center
var r = Body_applyForce_r;
vec2.sub(r,worldPoint,this.position);
// Add linear force
vec2.add(this.force,this.force,force);
// Compute produced rotational force
var rotForce = vec2.crossLength(r,force);
// Add rotational force
this.angularForce += rotForce;
};
/**
* Transform a world point to local body frame.
* @method toLocalFrame
* @param {Float32Array|Array} out The vector to store the result in
* @param {Float32Array|Array} worldPoint The input world vector
*/
Body.prototype.toLocalFrame = function(out, worldPoint){
vec2.toLocalFrame(out, worldPoint, this.position, this.angle);
};
/**
* Transform a local point to world frame.
* @method toWorldFrame
* @param {Array} out The vector to store the result in
* @param {Array} localPoint The input local vector
*/
Body.prototype.toWorldFrame = function(out, localPoint){
vec2.toGlobalFrame(out, localPoint, this.position, this.angle);
};
/**
* Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points.
* @method fromPolygon
* @param {Array} path An array of 2d vectors, e.g. [[0,0],[0,1],...] that resembles a concave or convex polygon. The shape must be simple and without holes.
* @param {Object} [options]
* @param {Boolean} [options.optimalDecomp=false] Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
* @param {Boolean} [options.skipSimpleCheck=false] Set to true if you already know that the path is not intersecting itself.
* @param {Boolean|Number} [options.removeCollinearPoints=false] Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
* @return {Boolean} True on success, else false.
*/
Body.prototype.fromPolygon = function(path,options){
options = options || {};
// Remove all shapes
for(var i=this.shapes.length; i>=0; --i)
this.removeShape(this.shapes[i]);
var p = new decomp.Polygon();
p.vertices = path;
// Make it counter-clockwise
p.makeCCW();
if(typeof(options.removeCollinearPoints)=="number"){
p.removeCollinearPoints(options.removeCollinearPoints);
}
// Check if any line segment intersects the path itself
if(typeof(options.skipSimpleCheck) == "undefined"){
if(!p.isSimple()) return false;
}
// Save this path for later
this.concavePath = p.vertices.slice(0);
for(var i=0; i<this.concavePath.length; i++){
var v = [0,0];
vec2.copy(v,this.concavePath[i]);
this.concavePath[i] = v;
}
// Slow or fast decomp?
var convexes;
if(options.optimalDecomp) convexes = p.decomp();
else convexes = p.quickDecomp();
var cm = vec2.create();
// Add convexes
for(var i=0; i!==convexes.length; i++){
// Create convex
var c = new Convex(convexes[i].vertices);
// Move all vertices so its center of mass is in the local center of the convex
for(var j=0; j!==c.vertices.length; j++){
var v = c.vertices[j];
vec2.sub(v,v,c.centerOfMass);
}
vec2.scale(cm,c.centerOfMass,1);
c.updateTriangles();
c.updateCenterOfMass();
c.updateBoundingRadius();
// Add the shape
this.addShape(c,cm);
}
this.adjustCenterOfMass();
return true;
};
var adjustCenterOfMass_tmp1 = vec2.fromValues(0,0),
adjustCenterOfMass_tmp2 = vec2.fromValues(0,0),
adjustCenterOfMass_tmp3 = vec2.fromValues(0,0),
adjustCenterOfMass_tmp4 = vec2.fromValues(0,0);
/**
* Moves the shape offsets so their center of mass becomes the body center of mass.
* @method adjustCenterOfMass
*/
Body.prototype.adjustCenterOfMass = function(){
var zero = adjustCenterOfMass_tmp1,
offset_times_area = adjustCenterOfMass_tmp2,
sum = adjustCenterOfMass_tmp3,
cm = adjustCenterOfMass_tmp4,
totalArea = 0;
vec2.set(sum,0,0);
vec2.set(zero,0,0);
for(var i=0; i!==this.shapes.length; i++){
var s = this.shapes[i],
offset = this.shapeOffsets[i] || zero;
vec2.scale(offset_times_area,offset,s.area);
vec2.add(sum,sum,offset_times_area);
totalArea += s.area;
}
vec2.scale(cm,sum,1/totalArea);
// Now move all shapes
for(var i=0; i!==this.shapes.length; i++){
var s = this.shapes[i],
offset = this.shapeOffsets[i];
// Offset may be undefined. Fix that.
if(!offset){
offset = this.shapeOffsets[i] = vec2.create();
}
vec2.sub(offset,offset,cm);
}
// Move the body position too
vec2.add(this.position,this.position,cm);
// And concave path
for(var i=0; this.concavePath && i<this.concavePath.length; i++){
vec2.sub(this.concavePath[i], this.concavePath[i], cm);
}
this.updateMassProperties();
this.updateBoundingRadius();
};
/**
* Sets the force on the body to zero.
* @method setZeroForce
*/
Body.prototype.setZeroForce = function(){
vec2.set(this.force,0.0,0.0);
this.angularForce = 0.0;
};
Body.prototype.resetConstraintVelocity = function(){
var b = this,
vlambda = b.vlambda;
vec2.set(vlambda,0,0);
b.wlambda = 0;
};
Body.prototype.addConstraintVelocity = function(){
var b = this,
v = b.velocity;
vec2.add( v, v, b.vlambda);
b.angularVelocity += b.wlambda;
};
/**
* Dynamic body.
* @property DYNAMIC
* @type {Number}
* @static
*/
Body.DYNAMIC = 1;
/**
* Static body.
* @property STATIC
* @type {Number}
* @static
*/
Body.STATIC = 2;
/**
* Kinematic body.
* @property KINEMATIC
* @type {Number}
* @static
*/
Body.KINEMATIC = 4;