/* 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.
*/
/// Segment query info struct.
/* These are created using literals where needed.
typedef struct cpSegmentQueryInfo {
/// The shape that was hit, null if no collision occured.
cpShape *shape;
/// The normalized distance along the query segment in the range [0, 1].
cpFloat t;
/// The normal of the surface hit.
cpVect n;
} cpSegmentQueryInfo;
*/
/**
* @var {number}
* @default 0
*/
var shapeIDCounter = 0;
/**
* @memberof cp
* @constant
*/
var CP_NO_GROUP = cp.NO_GROUP = 0;
/**
* @memberof cp
* @constant
*/
var CP_ALL_LAYERS = cp.ALL_LAYERS = ~0;
/**
* @function
* @memberof cp
*/
cp.resetShapeIdCounter = function()
{
shapeIDCounter = 0;
};
/// The cpShape struct defines the shape of a rigid body.
//
/// Opaque collision shape struct. Do not create directly - instead use
/// PolyShape, CircleShape and SegmentShape.
/**
* The cpShape struct defines the shape of a rigid body.
*
* Opaque collision shape struct. Do not create directly - instead use
* PolyShape, CircleShape and SegmentShape.
*
* @class
* @memberof cp
* @param {cp.Body} body
*/
var Shape = cp.Shape = function(body) {
/// The rigid body this collision shape is attached to.
this.body = body;
/// The current bounding box of the shape.
this.bb_l = this.bb_b = this.bb_r = this.bb_t = 0;
this.hashid = shapeIDCounter++;
/// Sensor flag.
/// Sensor shapes call collision callbacks but don't produce collisions.
this.sensor = false;
/// Coefficient of restitution. (elasticity)
this.e = 0;
/// Coefficient of friction.
this.u = 0;
/// Surface velocity used when solving for friction.
this.surface_v = vzero;
/// Collision type of this shape used when picking collision handlers.
this.collision_type = 0;
/// Group of this shape. Shapes in the same group don't collide.
this.group = 0;
// Layer bitmask for this shape. Shapes only collide if the bitwise and of their layers is non-zero.
this.layers = CP_ALL_LAYERS;
this.space = null;
// Copy the collision code from the prototype into the actual object. This makes collision
// function lookups slightly faster.
this.collisionCode = this.collisionCode;
};
/**
* @function
* @param {number} e How bouncy this shape is.
*/
Shape.prototype.setElasticity = function(e) { this.e = e; };
/**
* @function
* @param {number} u How much friction this shape has.
*/
Shape.prototype.setFriction = function(u) { this.body.activate(); this.u = u; };
/**
* @function
* @param {number} layers
*/
Shape.prototype.setLayers = function(layers) { this.body.activate(); this.layers = layers; };
/**
* @function
* @param {boolean} sensor
*/
Shape.prototype.setSensor = function(sensor) { this.body.activate(); this.sensor = sensor; };
/**
* @function
* @param {number} collision_type
*/
Shape.prototype.setCollisionType = function(collision_type) { this.body.activate(); this.collision_type = collision_type; };
/**
* @function
* @return {cp.Body}
*/
Shape.prototype.getBody = function() { return this.body; };
/**
* @function
* @return {boolean}
*/
Shape.prototype.active = function()
{
// return shape->prev || (shape->body && shape->body->shapeList == shape);
return this.body && this.body.shapeList.indexOf(this) !== -1;
};
/**
* @function
* @param {cp.Body} body
*/
Shape.prototype.setBody = function(body)
{
assert(!this.active(), "You cannot change the body on an active shape. You must remove the shape from the space before changing the body.");
this.body = body;
};
/**
* @function
* @return {}
*/
Shape.prototype.cacheBB = function()
{
return this.update(this.body.p, this.body.rot);
};
/**
* @function
* @param {cp.Vect} pos
* @param {cp.Vect} rot
*/
Shape.prototype.update = function(pos, rot)
{
assert(!isNaN(rot.x), 'Rotation is NaN');
assert(!isNaN(pos.x), 'Position is NaN');
this.cacheData(pos, rot);
};
/**
* @function
* @param {cp.Vect} p
* @return {NearestPointQueryInfo}
*/
Shape.prototype.pointQuery = function(p)
{
var info = this.nearestPointQuery(p);
if (info.d < 0) return info;
};
/**
* @function
* @return {cp.BB}
*/
Shape.prototype.getBB = function()
{
return new BB(this.bb_l, this.bb_b, this.bb_r, this.bb_t);
};
/* Not implemented - all these getters and setters. Just edit the object directly.
CP_DefineShapeStructGetter(cpBody*, body, Body);
void cpShapeSetBody(cpShape *shape, cpBody *body);
CP_DefineShapeStructGetter(cpBB, bb, BB);
CP_DefineShapeStructProperty(cpBool, sensor, Sensor, cpTrue);
CP_DefineShapeStructProperty(cpFloat, e, Elasticity, cpFalse);
CP_DefineShapeStructProperty(cpFloat, u, Friction, cpTrue);
CP_DefineShapeStructProperty(cpVect, surface_v, SurfaceVelocity, cpTrue);
CP_DefineShapeStructProperty(cpDataPointer, data, UserData, cpFalse);
CP_DefineShapeStructProperty(cpCollisionType, collision_type, CollisionType, cpTrue);
CP_DefineShapeStructProperty(cpGroup, group, Group, cpTrue);
CP_DefineShapeStructProperty(cpLayers, layers, Layers, cpTrue);
*/
/// Extended point query info struct. Returned from calling pointQuery on a shape.
/**
* Extended point query info struct. Returned from calling pointQuery on a shape.
*
* @class
* @param {cp.Shape} shape
*/
var PointQueryExtendedInfo = function(shape)
{
/// Shape that was hit, NULL if no collision occurred.
this.shape = shape;
/// Depth of the point inside the shape.
this.d = Infinity;
/// Direction of minimum norm to the shape's surface.
this.n = vzero;
};
/**
* @class
* @param {cp.Shape} shape
* @param {cp.Vect} p
* @param {cp.Vect} d
*/
var NearestPointQueryInfo = function(shape, p, d)
{
/// The nearest shape, NULL if no shape was within range.
this.shape = shape;
/// The closest point on the shape's surface. (in world space coordinates)
this.p = p;
/// The distance to the point. The distance is negative if the point is inside the shape.
this.d = d;
};
/**
* @class
* @param {cp.Shape} shape
* @param {number} t
* @param {cp.Vect} n
*/
var SegmentQueryInfo = function(shape, t, n)
{
/// The shape that was hit, NULL if no collision occured.
this.shape = shape;
/// The normalized distance along the query segment in the range [0, 1].
this.t = t;
/// The normal of the surface hit.
this.n = n;
};
/// Get the hit point for a segment query.
/**
* Get the hit point for a segment query.
*
* @function
* @param {cp.Vect} start
* @param {cp.Vect} end
* @return {cp.Vect}
*/
SegmentQueryInfo.prototype.hitPoint = function(start, end)
{
return vlerp(start, end, this.t);
};
/// Get the hit distance for a segment query.
/**
* Get the hit distance for a segment query.
*
* @function
* @param {cp.Vect} start
* @param {cp.Vect} end
* @return {number}
*/
SegmentQueryInfo.prototype.hitDist = function(start, end)
{
return vdist(start, end) * this.t;
};
// Circles.
/**
* @class
* @extends cp.Shape
* @memberof cp
* @param {cp.Body} body
* @param {number} radius
* @param {cp.Vect} offset
*/
var CircleShape = cp.CircleShape = function(body, radius, offset)
{
this.c = this.tc = offset;
this.r = radius;
this.type = 'circle';
Shape.call(this, body);
};
CircleShape.prototype = Object.create(Shape.prototype);
/**
* @function
* @param {cp.Vect} p
* @param {cp.Vect} rot
*/
CircleShape.prototype.cacheData = function(p, rot)
{
//var c = this.tc = vadd(p, vrotate(this.c, rot));
var c = this.tc = vrotate(this.c, rot).add(p);
//this.bb = bbNewForCircle(c, this.r);
var r = this.r;
this.bb_l = c.x - r;
this.bb_b = c.y - r;
this.bb_r = c.x + r;
this.bb_t = c.y + r;
};
/// Test if a point lies within a shape.
/*CircleShape.prototype.pointQuery = function(p)
{
var delta = vsub(p, this.tc);
var distsq = vlengthsq(delta);
var r = this.r;
if(distsq < r*r){
var info = new PointQueryExtendedInfo(this);
var dist = Math.sqrt(distsq);
info.d = r - dist;
info.n = vmult(delta, 1/dist);
return info;
}
};*/
/**
* @function
* @param {cp.Vect} p
* @return {NearestPointQueryInfo}
*/
CircleShape.prototype.nearestPointQuery = function(p)
{
var deltax = p.x - this.tc.x;
var deltay = p.y - this.tc.y;
var d = vlength2(deltax, deltay);
var r = this.r;
var nearestp = new Vect(this.tc.x + deltax * r/d, this.tc.y + deltay * r/d);
return new NearestPointQueryInfo(this, nearestp, d - r);
};
/**
* @class
* @param {cp.Shape} shape
* @param {cp.Vect} center
* @param {cp.Vect} r
* @param {cp.Vect} a
* @param {cp.Vect} b
* @param {} info
* @return {SegmentQueryInfo}
*/
var circleSegmentQuery = function(shape, center, r, a, b, info)
{
// offset the line to be relative to the circle
a = vsub(a, center);
b = vsub(b, center);
var qa = vdot(a, a) - 2*vdot(a, b) + vdot(b, b);
var qb = -2*vdot(a, a) + 2*vdot(a, b);
var qc = vdot(a, a) - r*r;
var det = qb*qb - 4*qa*qc;
if(det >= 0)
{
var t = (-qb - Math.sqrt(det))/(2*qa);
if(0 <= t && t <= 1){
return new SegmentQueryInfo(shape, t, vnormalize(vlerp(a, b, t)));
}
}
};
/**
* @function
* @param {cp.Vect} a
* @param {cp.Vect} b
* @return {SegmentQueryInfo}
*/
CircleShape.prototype.segmentQuery = function(a, b)
{
return circleSegmentQuery(this, this.tc, this.r, a, b);
};
// The C API has these, and also getters. Its not idiomatic to
// write getters and setters in JS.
/*
CircleShape.prototype.setRadius = function(radius)
{
this.r = radius;
}
CircleShape.prototype.setOffset = function(offset)
{
this.c = offset;
}*/
// Segment shape
/**
* A beveled (rounded) segment shape.
*
* @class
* @extends cp.Shape
* @memberof cp
* @param {cp.Body} body
* @param {cp.Vect} a The start of the segment shape.
* @param {cp.Vect} b The end of the segment shape.
* @param {number} r The beveling radius of the segment shape.
*/
var SegmentShape = cp.SegmentShape = function(body, a, b, r)
{
this.a = a;
this.b = b;
this.n = vperp(vnormalize(vsub(b, a)));
this.ta = this.tb = this.tn = null;
this.r = r;
this.a_tangent = vzero;
this.b_tangent = vzero;
this.type = 'segment';
Shape.call(this, body);
};
SegmentShape.prototype = Object.create(Shape.prototype);
/**
* @function
* @param {cp.Vect} p
* @param {cp.Vect} rot
*/
SegmentShape.prototype.cacheData = function(p, rot)
{
this.ta = vadd(p, vrotate(this.a, rot));
this.tb = vadd(p, vrotate(this.b, rot));
this.tn = vrotate(this.n, rot);
var l,r,b,t;
if(this.ta.x < this.tb.x){
l = this.ta.x;
r = this.tb.x;
} else {
l = this.tb.x;
r = this.ta.x;
}
if(this.ta.y < this.tb.y){
b = this.ta.y;
t = this.tb.y;
} else{
b = this.tb.y;
t = this.ta.y;
}
var rad = this.r;
this.bb_l = l - rad;
this.bb_b = b - rad;
this.bb_r = r + rad;
this.bb_t = t + rad;
};
/**
* @function
* @param {cp.Vect} p
* @return {NearestPointQueryInfo}
*/
SegmentShape.prototype.nearestPointQuery = function(p)
{
var closest = closestPointOnSegment(p, this.ta, this.tb);
var deltax = p.x - closest.x;
var deltay = p.y - closest.y;
var d = vlength2(deltax, deltay);
var r = this.r;
var nearestp = (d ? vadd(closest, vmult(new Vect(deltax, deltay), r/d)) : closest);
return new NearestPointQueryInfo(this, nearestp, d - r);
};
/**
* @function
* @param {cp.Vect} a
* @param {cp.Vect} b
* @return {SegmentQueryInfo}
*/
SegmentShape.prototype.segmentQuery = function(a, b)
{
var n = this.tn;
var d = vdot(vsub(this.ta, a), n);
var r = this.r;
var flipped_n = (d > 0 ? vneg(n) : n);
var n_offset = vsub(vmult(flipped_n, r), a);
var seg_a = vadd(this.ta, n_offset);
var seg_b = vadd(this.tb, n_offset);
var delta = vsub(b, a);
if(vcross(delta, seg_a)*vcross(delta, seg_b) <= 0){
var d_offset = d + (d > 0 ? -r : r);
var ad = -d_offset;
var bd = vdot(delta, n) - d_offset;
if(ad*bd < 0){
return new SegmentQueryInfo(this, ad/(ad - bd), flipped_n);
}
} else if(r !== 0){
var info1 = circleSegmentQuery(this, this.ta, this.r, a, b);
var info2 = circleSegmentQuery(this, this.tb, this.r, a, b);
if (info1){
return info2 && info2.t < info1.t ? info2 : info1;
} else {
return info2;
}
}
};
/**
* @function
* @param {cp.Vect} prev
* @param {cp.Vect} next
*/
SegmentShape.prototype.setNeighbors = function(prev, next)
{
this.a_tangent = vsub(prev, this.a);
this.b_tangent = vsub(next, this.b);
};
/**
* @function
* @param {cp.Vect} a
* @param {cp.Vect} b
*/
SegmentShape.prototype.setEndpoints = function(a, b)
{
this.a = a;
this.b = b;
this.n = vperp(vnormalize(vsub(b, a)));
};
/*
cpSegmentShapeSetRadius(cpShape *shape, cpFloat radius)
{
this.r = radius;
}*/
/*
CP_DeclareShapeGetter(cpSegmentShape, cpVect, A);
CP_DeclareShapeGetter(cpSegmentShape, cpVect, B);
CP_DeclareShapeGetter(cpSegmentShape, cpVect, Normal);
CP_DeclareShapeGetter(cpSegmentShape, cpFloat, Radius);
*/