UNPKG

p2s

Version:

A JavaScript 2D physics engine.

390 lines (328 loc) 10.5 kB
var Shape = require('./Shape') , vec2 = require('../math/vec2') , dot = vec2.dot , polyk = require('../math/polyk') , shallowClone = require('../utils/Utils').shallowClone; module.exports = Convex; /** * Convex shape class. * @class Convex * @constructor * @extends Shape * @param {object} [options] (Note that this options object will be passed on to the {{#crossLink "Shape"}}{{/crossLink}} constructor.) * @param {Array} [options.vertices] An array of vertices that span this shape. Vertices are given in counter-clockwise (CCW) direction. * @example * var body = new Body({ mass: 1 }); * var vertices = [[-1,-1], [1,-1], [1,1], [-1,1]]; * var convexShape = new Convex({ * vertices: vertices * }); * body.addShape(convexShape); */ function Convex(options){ options = options ? shallowClone(options) : {}; /** * Vertices defined in the local frame. * @property vertices * @type {Array} */ this.vertices = []; // Copy the verts var vertices = options.vertices !== undefined ? options.vertices : []; for(var i=0; i < vertices.length; i++){ this.vertices.push(vec2.clone(vertices[i])); } /** * Edge normals defined in the local frame, pointing out of the shape. * @property normals * @type {Array} */ var normals = this.normals = []; for(var i=0; i < vertices.length; i++){ normals.push(vec2.create()); } this.updateNormals(); /** * The center of mass of the Convex * @property centerOfMass * @type {Array} */ this.centerOfMass = vec2.create(); /** * 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; options.type = options.type || Shape.CONVEX; Shape.call(this, options); this.updateBoundingRadius(); this.updateArea(); if(this.area < 0){ throw new Error("Convex vertices must be given in counter-clockwise winding."); } } Convex.prototype = new Shape(); Convex.prototype.constructor = Convex; var tmpVec1 = vec2.create(); var tmpVec2 = vec2.create(); Convex.prototype.updateNormals = function(){ var vertices = this.vertices; var normals = this.normals; for(var i = 0; i < vertices.length; i++){ var worldPoint0 = vertices[i]; var worldPoint1 = vertices[(i+1) % vertices.length]; var normal = normals[i]; vec2.subtract(normal, worldPoint1, worldPoint0); // Get normal - just rotate 90 degrees since vertices are given in CCW vec2.rotate90cw(normal, normal); vec2.normalize(normal, normal); } }; /** * 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 = 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 = 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(); /** * Update the .centerOfMass property. * @method updateCenterOfMass */ Convex.prototype.updateCenterOfMass = function(){ var triangles = this.triangles, verts = this.vertices, cm = this.centerOfMass, centroid = updateCenterOfMass_centroid, a = updateCenterOfMass_a, b = updateCenterOfMass_b, c = updateCenterOfMass_c, 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 = 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 moment of inertia of the Convex. * @method computeMomentOfInertia * @return {Number} * @see http://www.gamedev.net/topic/342822-moment-of-inertia-of-a-polygon-2d/ */ Convex.prototype.computeMomentOfInertia = function(){ 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 = dot(p1,p1) + dot(p1,p0) + dot(p0,p0); denom += a * b; numer += a; } return (1.0 / 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} * @deprecated */ Convex.triangleArea = triangleArea; function triangleArea(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 = triangleArea(a,b,c); this.area += m; } }; /** * @method computeAABB * @param {AABB} out * @param {Array} position * @param {Number} angle * @todo: approximate with a local AABB? */ Convex.prototype.computeAABB = function(out, position, angle){ out.setFromPoints(this.vertices, position, angle, 0); }; var intersectConvex_rayStart = vec2.create(); var intersectConvex_rayEnd = vec2.create(); var intersectConvex_normal = vec2.create(); /** * @method raycast * @param {RaycastResult} result * @param {Ray} ray * @param {array} position * @param {number} angle */ Convex.prototype.raycast = function(result, ray, position, angle){ var rayStart = intersectConvex_rayStart; var rayEnd = intersectConvex_rayEnd; var normal = intersectConvex_normal; var vertices = this.vertices; // Transform to local shape space vec2.toLocalFrame(rayStart, ray.from, position, angle); vec2.toLocalFrame(rayEnd, ray.to, position, angle); var n = vertices.length; for (var i = 0; i < n && !result.shouldStop(ray); i++) { var q1 = vertices[i]; var q2 = vertices[(i+1) % n]; var delta = vec2.getLineSegmentsIntersectionFraction(rayStart, rayEnd, q1, q2); if(delta >= 0){ vec2.subtract(normal, q2, q1); vec2.rotate(normal, normal, -Math.PI / 2 + angle); vec2.normalize(normal, normal); ray.reportIntersection(result, delta, normal, i); } } }; var pic_r0 = vec2.create(); var pic_r1 = vec2.create(); Convex.prototype.pointTest = function(localPoint){ var r0 = pic_r0, r1 = pic_r1, verts = this.vertices, lastCross = null, numVerts = verts.length; for(var i=0; i < numVerts + 1; i++){ var v0 = verts[i % numVerts], v1 = verts[(i + 1) % numVerts]; vec2.subtract(r0, v0, localPoint); vec2.subtract(r1, v1, localPoint); var cross = vec2.crossLength(r0,r1); if(lastCross === null){ lastCross = cross; } // If we got a different sign of the distance vector, the point is out of the polygon if(cross * lastCross < 0){ return false; } lastCross = cross; } return true; };