UNPKG

planck-js

Version:

2D physics engine for JavaScript/HTML5 game development

324 lines (290 loc) 10.1 kB
/* * 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; }