/* Copyright (c) 2007 Scott Lembcke
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/// Check that a set of vertexes is convex and has a clockwise winding.
/**
* Check that a set of vertexes is convex and has a clockwise winding.
*
* @function
* @param {number[]} verts
* @return {boolean}
*/
var polyValidate = function(verts)
{
var len = verts.length;
for(var i=0; i<len; i+=2){
var ax = verts[i];
var ay = verts[i+1];
var bx = verts[(i+2)%len];
var by = verts[(i+3)%len];
var cx = verts[(i+4)%len];
var cy = verts[(i+5)%len];
//if(vcross(vsub(b, a), vsub(c, b)) > 0){
if(vcross2(bx - ax, by - ay, cx - bx, cy - by) > 0){
return false;
}
}
return true;
};
/// Initialize a polygon shape.
/// The vertexes must be convex and have a clockwise winding.
/**
* Initialize a polygon shape.
* The vertexes must be convex and have a clockwise winding.
*
* @class
* @extends cp.Shape
* @memberof cp
* @param {cp.Body} body
* @param {number[]} verts
* @param {cp.Vect} offset
*/
var PolyShape = cp.PolyShape = function(body, verts, offset)
{
this.setVerts(verts, offset);
this.type = 'poly';
Shape.call(this, body);
};
PolyShape.prototype = Object.create(Shape.prototype);
/**
* @function
* @param {cp.Vect} n
* @param {number} d
*/
var SplittingPlane = function(n, d)
{
this.n = n;
this.d = d;
};
/**
* @function
* @param {cp.Vect} v
* @return {number}
*/
SplittingPlane.prototype.compare = function(v)
{
return vdot(this.n, v) - this.d;
};
/**
* @function
* @param {number[]} verts
* @param {cp.Vect} offset
*/
PolyShape.prototype.setVerts = function(verts, offset)
{
assert(verts.length >= 4, "Polygons require some verts");
assert(typeof(verts[0]) === 'number',
'Polygon verticies should be specified in a flattened list (eg [x1,y1,x2,y2,x3,y3,...])');
// Fail if the user attempts to pass a concave poly, or a bad winding.
assert(polyValidate(verts), "Polygon is concave or has a reversed winding. Consider using cpConvexHull()");
var len = verts.length;
var numVerts = len >> 1;
// This a pretty bad way to do this in javascript. As a first pass, I want to keep
// the code similar to the C.
this.verts = new Array(len);
this.tVerts = new Array(len);
this.planes = new Array(numVerts);
this.tPlanes = new Array(numVerts);
for(var i=0; i<len; i+=2){
//var a = vadd(offset, verts[i]);
//var b = vadd(offset, verts[(i+1)%numVerts]);
var ax = verts[i] + offset.x;
var ay = verts[i+1] + offset.y;
var bx = verts[(i+2)%len] + offset.x;
var by = verts[(i+3)%len] + offset.y;
// Inefficient, but only called during object initialization.
var n = vnormalize(vperp(new Vect(bx-ax, by-ay)));
this.verts[i ] = ax;
this.verts[i+1] = ay;
this.planes[i>>1] = new SplittingPlane(n, vdot2(n.x, n.y, ax, ay));
this.tPlanes[i>>1] = new SplittingPlane(new Vect(0,0), 0);
}
};
/// Initialize a box shaped polygon shape.
/**
* Initialize a box shaped polygon shape.
*
* @class
* @memberof cp
* @param {cp.Body} body Body of the box shape.
* @param {number} width Shape width.
* @param {number} height Shape height.
* @return {cp.PolyShape}
*/
var BoxShape = cp.BoxShape = function(body, width, height)
{
var hw = width/2;
var hh = height/2;
return BoxShape2(body, new BB(-hw, -hh, hw, hh));
};
/// Initialize an offset box shaped polygon shape.
/**
* Initialize an offset box shaped polygon shape.
*
* @function
* @memberof cp
* @param {cp.Body} body
* @param {cp.BB} box
* @return {cp.PolyShape}
*/
var BoxShape2 = cp.BoxShape2 = function(body, box)
{
var verts = [
box.l, box.b,
box.l, box.t,
box.r, box.t,
box.r, box.b,
];
return new PolyShape(body, verts, vzero);
};
/**
* @function
* @param {cp.Vect} p
* @param {cp.Vect} rot
*/
PolyShape.prototype.transformVerts = function(p, rot)
{
var src = this.verts;
var dst = this.tVerts;
var l = Infinity, r = -Infinity;
var b = Infinity, t = -Infinity;
for(var i=0; i<src.length; i+=2){
//var v = vadd(p, vrotate(src[i], rot));
var x = src[i];
var y = src[i+1];
var vx = p.x + x*rot.x - y*rot.y;
var vy = p.y + x*rot.y + y*rot.x;
//console.log('(' + x + ',' + y + ') -> (' + vx + ',' + vy + ')');
dst[i] = vx;
dst[i+1] = vy;
l = min(l, vx);
r = max(r, vx);
b = min(b, vy);
t = max(t, vy);
}
this.bb_l = l;
this.bb_b = b;
this.bb_r = r;
this.bb_t = t;
};
/**
* @function
* @param {cp.Vect} p
* @param {cp.Vect} rot
*/
PolyShape.prototype.transformAxes = function(p, rot)
{
var src = this.planes;
var dst = this.tPlanes;
for(var i=0; i<src.length; i++){
var n = vrotate(src[i].n, rot);
dst[i].n = n;
dst[i].d = vdot(p, n) + src[i].d;
}
};
/**
* @function
* @param {cp.Vect} p
* @param {cp.Vect} rot
*/
PolyShape.prototype.cacheData = function(p, rot)
{
this.transformAxes(p, rot);
this.transformVerts(p, rot);
};
/**
* @function
* @param {cp.Vect} p
* @return {NearestPointQueryInfo}
*/
PolyShape.prototype.nearestPointQuery = function(p)
{
var planes = this.tPlanes;
var verts = this.tVerts;
var v0x = verts[verts.length - 2];
var v0y = verts[verts.length - 1];
var minDist = Infinity;
var closestPoint = vzero;
var outside = false;
for(var i=0; i<planes.length; i++){
if(planes[i].compare(p) > 0) outside = true;
var v1x = verts[i*2];
var v1y = verts[i*2 + 1];
var closest = closestPointOnSegment2(p.x, p.y, v0x, v0y, v1x, v1y);
var dist = vdist(p, closest);
if(dist < minDist){
minDist = dist;
closestPoint = closest;
}
v0x = v1x;
v0y = v1y;
}
return new NearestPointQueryInfo(this, closestPoint, (outside ? minDist : -minDist));
};
/**
* @function
* @param {cp.Vect} a
* @param {cp.Vect} b
* @return {SegmentQueryInfo}
*/
PolyShape.prototype.segmentQuery = function(a, b)
{
var axes = this.tPlanes;
var verts = this.tVerts;
var numVerts = axes.length;
var len = numVerts * 2;
for(var i=0; i<numVerts; i++){
var n = axes[i].n;
var an = vdot(a, n);
if(axes[i].d > an) continue;
var bn = vdot(b, n);
var t = (axes[i].d - an)/(bn - an);
if(t < 0 || 1 < t) continue;
var point = vlerp(a, b, t);
var dt = -vcross(n, point);
var dtMin = -vcross2(n.x, n.y, verts[i*2], verts[i*2+1]);
var dtMax = -vcross2(n.x, n.y, verts[(i*2+2)%len], verts[(i*2+3)%len]);
if(dtMin <= dt && dt <= dtMax){
// josephg: In the original C code, this function keeps
// looping through axes after finding a match. I *think*
// this code is equivalent...
return new SegmentQueryInfo(this, t, n);
}
}
};
/**
* @function
* @param {cp.Vect} n
* @param {number} d
*/
PolyShape.prototype.valueOnAxis = function(n, d)
{
var verts = this.tVerts;
var m = vdot2(n.x, n.y, verts[0], verts[1]);
for(var i=2; i<verts.length; i+=2){
m = min(m, vdot2(n.x, n.y, verts[i], verts[i+1]));
}
return m - d;
};
/**
* @function
* @param {number} vx
* @param {number} vy
* @return {boolean}
*/
PolyShape.prototype.containsVert = function(vx, vy)
{
var planes = this.tPlanes;
for(var i=0; i<planes.length; i++){
var n = planes[i].n;
var dist = vdot2(n.x, n.y, vx, vy) - planes[i].d;
if(dist > 0) return false;
}
return true;
};
/**
* @function
* @param {number} vx
* @param {number} vy
* @param {cp.Vect} n
* @return {boolean}
*/
PolyShape.prototype.containsVertPartial = function(vx, vy, n)
{
var planes = this.tPlanes;
for(var i=0; i<planes.length; i++){
var n2 = planes[i].n;
if(vdot(n2, n) < 0) continue;
var dist = vdot2(n2.x, n2.y, vx, vy) - planes[i].d;
if(dist > 0) return false;
}
return true;
};
// These methods are provided for API compatibility with Chipmunk. I recommend against using
// them - just access the poly.verts list directly.
/**
* @function
* @return {number}
*/
PolyShape.prototype.getNumVerts = function() { return this.verts.length / 2; };
/**
* @function
* @param {number} i
* @return {cp.Vect}
*/
PolyShape.prototype.getVert = function(i)
{
return new Vect(this.verts[i * 2], this.verts[i * 2 + 1]);
};