var Shape = require('./Shape')
, vec2 = require('../math/vec2')
, polyk = require('../math/polyk')
, decomp = require('poly-decomp');
module.exports = Convex;
/**
* Convex shape class.
* @class Convex
* @constructor
* @extends Shape
* @param {Array} vertices An array of vertices that span this shape. Vertices are given in counter-clockwise (CCW) direction.
* @param {Array} axes An array of unit length vectors
*/
function Convex(vertices, axes){
/**
* Vertices defined in the local frame.
* @property vertices
* @type {Array}
*/
this.vertices = [];
/**
* Axes defined in the local frame.
* @property axes
* @type {Array}
*/
this.axes = [];
// Copy the verts
for(var i=0; i<vertices.length; i++){
var v = vec2.create();
vec2.copy(v,vertices[i]);
this.vertices.push(v);
}
if(axes){
// Copy the axes
for(var i=0; i < axes.length; i++){
var axis = vec2.create();
vec2.copy(axis, axes[i]);
this.axes.push(axis);
}
} else {
// Construct axes from the vertex data
for(var i = 0; i < vertices.length; i++){
// Get the world edge
var worldPoint0 = vertices[i];
var worldPoint1 = vertices[(i+1) % vertices.length];
var normal = vec2.create();
vec2.sub(normal, worldPoint1, worldPoint0);
// Get normal - just rotate 90 degrees since vertices are given in CCW
vec2.rotate90cw(normal, normal);
vec2.normalize(normal, normal);
this.axes.push(normal);
}
}
/**
* The center of mass of the Convex
* @property centerOfMass
* @type {Array}
*/
this.centerOfMass = vec2.fromValues(0,0);
/**
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices.
* @property triangles
* @type {Array}
*/
this.triangles = [];
if(this.vertices.length){
this.updateTriangles();
this.updateCenterOfMass();
}
/**
* The bounding radius of the convex
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
Shape.call(this, Shape.CONVEX);
this.updateBoundingRadius();
this.updateArea();
if(this.area < 0){
throw new Error("Convex vertices must be given in conter-clockwise winding.");
}
}
Convex.prototype = new Shape();
var tmpVec1 = vec2.create();
var tmpVec2 = vec2.create();
/**
* Project a Convex onto a world-oriented axis
* @method projectOntoAxis
* @static
* @param {Array} offset
* @param {Array} localAxis
* @param {Array} result
*/
Convex.prototype.projectOntoLocalAxis = function(localAxis, result){
var max=null,
min=null,
v,
value,
localAxis = tmpVec1;
// Get projected position of all vertices
for(var i=0; i<this.vertices.length; i++){
v = this.vertices[i];
value = vec2.dot(v, localAxis);
if(max === null || value > max){
max = value;
}
if(min === null || value < min){
min = value;
}
}
if(min > max){
var t = min;
min = max;
max = t;
}
vec2.set(result, min, max);
};
Convex.prototype.projectOntoWorldAxis = function(localAxis, shapeOffset, shapeAngle, result){
var worldAxis = tmpVec2;
this.projectOntoLocalAxis(localAxis, result);
// Project the position of the body onto the axis - need to add this to the result
if(shapeAngle !== 0){
vec2.rotate(worldAxis, localAxis, shapeAngle);
} else {
worldAxis = localAxis;
}
var offset = vec2.dot(shapeOffset, worldAxis);
vec2.set(result, result[0] + offset, result[1] + offset);
};
/**
* Update the .triangles property
* @method updateTriangles
*/
Convex.prototype.updateTriangles = function(){
this.triangles.length = 0;
// Rewrite on polyk notation, array of numbers
var polykVerts = [];
for(var i=0; i<this.vertices.length; i++){
var v = this.vertices[i];
polykVerts.push(v[0],v[1]);
}
// Triangulate
var triangles = polyk.Triangulate(polykVerts);
// Loop over all triangles, add their inertia contributions to I
for(var i=0; i<triangles.length; i+=3){
var id1 = triangles[i],
id2 = triangles[i+1],
id3 = triangles[i+2];
// Add to triangles
this.triangles.push([id1,id2,id3]);
}
};
var updateCenterOfMass_centroid = vec2.create(),
updateCenterOfMass_centroid_times_mass = vec2.create(),
updateCenterOfMass_a = vec2.create(),
updateCenterOfMass_b = vec2.create(),
updateCenterOfMass_c = vec2.create(),
updateCenterOfMass_ac = vec2.create(),
updateCenterOfMass_ca = vec2.create(),
updateCenterOfMass_cb = vec2.create(),
updateCenterOfMass_n = vec2.create();
/**
* Update the .centerOfMass property.
* @method updateCenterOfMass
*/
Convex.prototype.updateCenterOfMass = function(){
var triangles = this.triangles,
verts = this.vertices,
cm = this.centerOfMass,
centroid = updateCenterOfMass_centroid,
n = updateCenterOfMass_n,
a = updateCenterOfMass_a,
b = updateCenterOfMass_b,
c = updateCenterOfMass_c,
ac = updateCenterOfMass_ac,
ca = updateCenterOfMass_ca,
cb = updateCenterOfMass_cb,
centroid_times_mass = updateCenterOfMass_centroid_times_mass;
vec2.set(cm,0,0);
var totalArea = 0;
for(var i=0; i!==triangles.length; i++){
var t = triangles[i],
a = verts[t[0]],
b = verts[t[1]],
c = verts[t[2]];
vec2.centroid(centroid,a,b,c);
// Get mass for the triangle (density=1 in this case)
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
var m = Convex.triangleArea(a,b,c);
totalArea += m;
// Add to center of mass
vec2.scale(centroid_times_mass, centroid, m);
vec2.add(cm, cm, centroid_times_mass);
}
vec2.scale(cm,cm,1/totalArea);
};
/**
* Compute the mass moment of inertia of the Convex.
* @method computeMomentOfInertia
* @param {Number} mass
* @return {Number}
* @see http://www.gamedev.net/topic/342822-moment-of-inertia-of-a-polygon-2d/
*/
Convex.prototype.computeMomentOfInertia = function(mass){
var denom = 0.0,
numer = 0.0,
N = this.vertices.length;
for(var j = N-1, i = 0; i < N; j = i, i ++){
var p0 = this.vertices[j];
var p1 = this.vertices[i];
var a = Math.abs(vec2.crossLength(p0,p1));
var b = vec2.dot(p1,p1) + vec2.dot(p1,p0) + vec2.dot(p0,p0);
denom += a * b;
numer += a;
}
return (mass / 6.0) * (denom / numer);
};
/**
* Updates the .boundingRadius property
* @method updateBoundingRadius
*/
Convex.prototype.updateBoundingRadius = function(){
var verts = this.vertices,
r2 = 0;
for(var i=0; i!==verts.length; i++){
var l2 = vec2.squaredLength(verts[i]);
if(l2 > r2){
r2 = l2;
}
}
this.boundingRadius = Math.sqrt(r2);
};
/**
* Get the area of the triangle spanned by the three points a, b, c. The area is positive if the points are given in counter-clockwise order, otherwise negative.
* @static
* @method triangleArea
* @param {Array} a
* @param {Array} b
* @param {Array} c
* @return {Number}
*/
Convex.triangleArea = function(a,b,c){
return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))) * 0.5;
};
/**
* Update the .area
* @method updateArea
*/
Convex.prototype.updateArea = function(){
this.updateTriangles();
this.area = 0;
var triangles = this.triangles,
verts = this.vertices;
for(var i=0; i!==triangles.length; i++){
var t = triangles[i],
a = verts[t[0]],
b = verts[t[1]],
c = verts[t[2]];
// Get mass for the triangle (density=1 in this case)
var m = Convex.triangleArea(a,b,c);
this.area += m;
}
};
/**
* @method computeAABB
* @param {AABB} out
* @param {Array} position
* @param {Number} angle
*/
Convex.prototype.computeAABB = function(out, position, angle){
out.setFromPoints(this.vertices, position, angle, 0);
};