planck-js
Version:
2D physics engine for JavaScript/HTML5 game development
324 lines (290 loc) • 10.1 kB
JavaScript
/*
* Copyright (c) 2016 Ali Shakiba http://shakiba.me/planck.js
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
var Vec2 = require('./common/Vec2');
var Transform = require('./common/Transform');
var Math = require('./common/Math');
var Rot = require('./common/Rot');
module.exports = Manifold;
module.exports.ClipSegmentToLine = ClipSegmentToLine;
module.exports.ClipVertex = ClipVertex;
module.exports.GetPointStates = GetPointStates;
module.exports.PointState = PointState;
// Manifold Type
Manifold.e_circles = 0;
Manifold.e_faceA = 1;
Manifold.e_faceB = 2;
// ContactFeature Type
Manifold.e_vertex = 0;
Manifold.e_face = 1;
/**
* A manifold for two touching convex shapes. Manifolds are created in Evaluate
* method of Contact subclasses.
*
* Supported manifold types are e_faceA or e_faceB for clip point versus plane
* with radius and e_circles point versus point with radius.
*
* We store contacts in this way so that position correction can account for
* movement, which is critical for continuous physics. All contact scenarios
* must be expressed in one of these types. This structure is stored across time
* steps, so we keep it small.
*
* @prop type e_circle, e_faceA, e_faceB
* @prop localPoint Usage depends on manifold type:<br>
* e_circles: the local center of circleA <br>
* e_faceA: the center of faceA <br>
* e_faceB: the center of faceB
* @prop localNormal Usage depends on manifold type:<br>
* e_circles: not used <br>
* e_faceA: the normal on polygonA <br>
* e_faceB: the normal on polygonB
* @prop points The points of contact {ManifoldPoint[]}
* @prop pointCount The number of manifold points
*/
function Manifold() {
this.type;
this.localNormal = Vec2();
this.localPoint = Vec2();
this.points = [ new ManifoldPoint(), new ManifoldPoint() ];
this.pointCount = 0;
};
/**
* A manifold point is a contact point belonging to a contact manifold. It holds
* details related to the geometry and dynamics of the contact points.
*
* This structure is stored across time steps, so we keep it small.
*
* Note: impulses are used for internal caching and may not provide reliable
* contact forces, especially for high speed collisions.
*
* @prop {Vec2} localPoint Usage depends on manifold type:<br>
* e_circles: the local center of circleB<br>
* e_faceA: the local center of cirlceB or the clip point of polygonB<br>
* e_faceB: the clip point of polygonA.
* @prop normalImpulse The non-penetration impulse
* @prop tangentImpulse The friction impulse
* @prop {ContactID} id Uniquely identifies a contact point between two shapes
* to facilatate warm starting
*/
function ManifoldPoint() {
this.localPoint = Vec2();
this.normalImpulse = 0;
this.tangentImpulse = 0;
this.id = new ContactID();
};
/**
* Contact ids to facilitate warm starting.
*
* @prop {ContactFeature} cf
* @prop key Used to quickly compare contact ids.
*
*/
function ContactID() {
// TODO merge with ManifoldPoint?
this.cf = new ContactFeature();
this.key;
};
/**
* The features that intersect to form the contact point.
*
* @prop indexA Feature index on shapeA
* @prop indexB Feature index on shapeB
* @prop typeA The feature type on shapeA
* @prop typeB The feature type on shapeB
*/
function ContactFeature() {
// TODO merge with ContactID?
this.indexA;
this.indexB;
this.typeA;
this.typeB;
};
/**
* This is used to compute the current state of a contact manifold.
*
* @prop normal World vector pointing from A to B
* @prop points World contact point (point of intersection)
* @prop separations A negative value indicates overlap, in meters
*/
function WorldManifold() {
this.normal;
this.points = []; // [maxManifoldPoints]
this.separations = []; // float[maxManifoldPoints]
};
/**
* Evaluate the manifold with supplied transforms. This assumes modest motion
* from the original state. This does not change the point count, impulses, etc.
* The radii must come from the shapes that generated the manifold.
*
* @param {WorldManifold} [wm]
*/
Manifold.prototype.GetWorldManifold = function(wm, xfA, radiusA, xfB, radiusB) {
if (this.pointCount == 0) {
return;
}
wm = wm || new WorldManifold();
var normal = wm.normal;
var points = wm.points;
var separations = wm.separations;
// TODO: improve
switch (this.type) {
case Manifold.e_circles:
normal = Vec2(1.0, 0.0);
var pointA = Transform.Mul(xfA, this.localPoint);
var pointB = Transform.Mul(xfB, this.points[0].localPoint);
var dist = Vec2.Sub(pointB, pointA);
if (Vec2.LengthSquared(dist) > Math.EPSILON * Math.EPSILON) {
normal.Set(dist);
normal.Normalize();
}
points[0] = Vec2.Mid(pointA, pointB);
separations[0] = -radiusB - radiusA;
points.length = 1;
separations.length = 1;
break;
case Manifold.e_faceA:
normal = Rot.Mul(xfA.q, this.localNormal);
var planePoint = Transform.Mul(xfA, this.localPoint);
for (var i = 0; i < this.pointCount; ++i) {
var clipPoint = Transform.Mul(xfB, this.points[i].localPoint);
var cA = Vec2()
.WSet(1, clipPoint,
(radiusA - Vec2.Dot(Vec2.Sub(clipPoint, planePoint), normal)),
normal);
var cB = Vec2().WSet(1, clipPoint, -radiusB, normal);
points[i] = Vec2.Mid(cA, cB);
separations[i] = Vec2.Dot(Vec2.Sub(cB, cA), normal);
}
points.length = this.pointCount;
separations.length = this.pointCount;
break;
case Manifold.e_faceB:
normal = Rot.Mul(xfB.q, this.localNormal);
var planePoint = Transform.Mul(xfB, this.localPoint);
for (var i = 0; i < this.pointCount; ++i) {
var clipPoint = Transform.Mul(xfA, this.points[i].localPoint);
var cB = Vec2()
.WSet(1, clipPoint,
(radiusB - Vec2.Dot(Vec2.Sub(clipPoint, planePoint), normal)),
normal);
var cA = Vec2().WSet(1, clipPoint, -radiusA, normal);
points[i] = Vec2.Mid(cA, cB);
separations[i] = Vec2.Dot(Vec2.Sub(cA, cB), normal);
}
points.length = this.pointCount;
separations.length = this.pointCount;
// Ensure normal points from A to B.
normal.Mul(-1);
break;
}
wm.normal = normal;
wm.points = points;
wm.separations = separations;
return wm;
}
/**
* This is used for determining the state of contact points.
*
* @prop {0} nullState Point does not exist
* @prop {1} addState Point was added in the update
* @prop {2} persistState Point persisted across the update
* @prop {3} removeState Point was removed in the update
*/
var PointState = {
nullState : 0,
addState : 1,
persistState : 2,
removeState : 3
};
/**
* Compute the point states given two manifolds. The states pertain to the
* transition from manifold1 to manifold2. So state1 is either persist or remove
* while state2 is either add or persist.
*
* @param {PointState[Settings.maxManifoldPoints]} state1
* @param {PointState[Settings.maxManifoldPoints]} state2
*/
function GetPointStates(state1, state2, manifold1, manifold2) {
// for (var i = 0; i < Settings.maxManifoldPoints; ++i) {
// state1[i] = PointState.nullState;
// state2[i] = PointState.nullState;
// }
// Detect persists and removes.
for (var i = 0; i < manifold1.pointCount; ++i) {
var id = manifold1.points[i].id;// ContactID
state1[i] = PointState.removeState;
for (var j = 0; j < manifold2.pointCount; ++j) {
if (manifold2.points[j].id.key == id.key) {
state1[i] = PointState.persistState;
break;
}
}
}
// Detect persists and adds.
for (var i = 0; i < manifold2.pointCount; ++i) {
var id = manifold2.points[i].id;// ContactID
state2[i] = PointState.addState;
for (var j = 0; j < manifold1.pointCount; ++j) {
if (manifold1.points[j].id.key == id.key) {
state2[i] = PointState.persistState;
break;
}
}
}
}
/**
* Used for computing contact manifolds.
*
* @prop {Vec2} v
* @prop {ContactID} id
*/
function ClipVertex() {
this.v = Vec2();
this.id = new ContactID();
};
/**
* Clipping for contact manifolds. Sutherland-Hodgman clipping.
*
* @param {ClipVertex[2]} vOut
* @param {ClipVertex[2]} vIn
*/
function ClipSegmentToLine(vOut, vIn, normal, offset, vertexIndexA) {
// Start with no output points
var numOut = 0;
// Calculate the distance of end points to the line
var distance0 = Vec2.Dot(normal, vIn[0].v) - offset;
var distance1 = Vec2.Dot(normal, vIn[1].v) - offset;
// If the points are behind the plane
if (distance0 <= 0.0)
vOut[numOut++] = vIn[0];
if (distance1 <= 0.0)
vOut[numOut++] = vIn[1];
// If the points are on different sides of the plane
if (distance0 * distance1 < 0.0) {
// Find intersection point of edge and plane
var interp = distance0 / (distance0 - distance1);
vOut[numOut].v.WSet(1 - interp, vIn[0].v, interp, vIn[1].v);
// VertexA is hitting edgeB.
vOut[numOut].id.cf.indexA = vertexIndexA;
vOut[numOut].id.cf.indexB = vIn[0].id.cf.indexB;
vOut[numOut].id.cf.typeA = ContactFeature.e_vertex;
vOut[numOut].id.cf.typeB = ContactFeature.e_face;
++numOut;
}
return numOut;
}