planck-js
Version:
2D physics engine for JavaScript/HTML5 game development
257 lines (215 loc) • 7.76 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 Settings = require('../Settings');
var Manifold = require('../Manifold');
var Contact = require('../Contact');
var Shape = require('../Shape');
var Math = require('../common/Math');
var Transform = require('../common/Transform');
var Rot = require('../common/Rot');
var Vec2 = require('../common/Vec2');
var AABB = require('../collision/AABB');
var PolygonShape = require('./PolygonShape');
Contact.AddType(PolygonShape.TYPE, PolygonShape.TYPE, PolygonContact);
function PolygonContact(manifold, xfA, fixtureA, indexA, xfB, fixtureB, indexB) {
Assert(fixtureA.GetType() == PolygonShape.TYPE);
Assert(fixtureB.GetType() == PolygonShape.TYPE);
CollidePolygons(manifold, fixtureA.GetShape(), xfA, fixtureB.GetShape(), xfB);
}
/**
* Find the max separation between poly1 and poly2 using edge normals from
* poly1.
*/
function FindMaxSeparation(poly1, xf1, poly2, xf2) {
var count1 = poly1.m_count;
var count2 = poly2.m_count;
var n1s = poly1.m_normals;
var v1s = poly1.m_vertices;
var v2s = poly2.m_vertices;
var xf = Transform.MulT(xf2, xf1);
var bestIndex = 0;
var maxSeparation = -Infinity;
for (var i = 0; i < count1; ++i) {
// Get poly1 normal in frame2.
var n = Rot.Mul(xf.q, n1s[i]);
var v1 = Transform.Mul(xf, v1s[i]);
// Find deepest point for normal i.
var si = Infinity;
for (var j = 0; j < count2; ++j) {
var sij = Vec2.Dot(n, v2s[j]) - Vec2.Dot(n, v1);
if (sij < si) {
si = sij;
}
}
if (si > maxSeparation) {
maxSeparation = si;
bestIndex = i;
}
}
// used to keep last FindMaxSeparation call values
FindMaxSeparation._maxSeparation = maxSeparation;
FindMaxSeparation._bestIndex = bestIndex;
}
/**
* @param {ClipVertex[2]} c
* @param {int} edge1
*/
function FindIncidentEdge(c, poly1, xf1, edge1, poly2, xf2) {
var normals1 = poly1.m_normals;
var count2 = poly2.m_count;
var vertices2 = poly2.m_vertices;
var normals2 = poly2.m_normals;
Assert(0 <= edge1 && edge1 < poly1.m_count);
// Get the normal of the reference edge in poly2's frame.
var normal1 = Rot.MulT(xf2.q, Rot.Mul(xf1.q, normals1[edge1]));
// Find the incident edge on poly2.
var index = 0;
var minDot = Infinity;
for (var i = 0; i < count2; ++i) {
var dot = Vec2.Dot(normal1, normals2[i]);
if (dot < minDot) {
minDot = dot;
index = i;
}
}
// Build the clip vertices for the incident edge.
var i1 = index;
var i2 = i1 + 1 < count2 ? i1 + 1 : 0;
c[0].v = Transform.Mul(xf2, vertices2[i1]);
c[0].id.cf.indexA = edge1;
c[0].id.cf.indexB = i1;
c[0].id.cf.typeA = Manifold.e_face;
c[0].id.cf.typeB = Manifold.e_vertex;
c[1].v = Transform.Mul(xf2, vertices2[i2]);
c[1].id.cf.indexA = edge1;
c[1].id.cf.indexB = i2;
c[1].id.cf.typeA = Manifold.e_face;
c[1].id.cf.typeB = Manifold.e_vertex;
}
/**
*
* Find edge normal of max separation on A - return if separating axis is found<br>
* Find edge normal of max separation on B - return if separation axis is found<br>
* Choose reference edge as min(minA, minB)<br>
* Find incident edge<br>
* Clip
*
* The normal points from 1 to 2
*/
function CollidePolygons(manifold, polyA, xfA, polyB, xfB) {
manifold.pointCount = 0;
var totalRadius = polyA.m_radius + polyB.m_radius;
FindMaxSeparation(polyA, xfA, polyB, xfB);
var edgeA = FindMaxSeparation._bestIndex;
var separationA = FindMaxSeparation._maxSeparation;
if (separationA > totalRadius)
return;
FindMaxSeparation(polyB, xfB, polyA, xfA);
var edgeB = FindMaxSeparation._bestIndex;
var separationB = FindMaxSeparation._maxSeparation;
if (separationB > totalRadius)
return;
var poly1; // reference polygon
var poly2; // incident polygon
var xf1;
var xf2;
var edge1; // reference edge
var flip;
var k_tol = 0.1 * Settings.linearSlop;
if (separationB > separationA + k_tol) {
poly1 = polyB;
poly2 = polyA;
xf1 = xfB;
xf2 = xfA;
edge1 = edgeB;
manifold.type = Manifold.e_faceB;
flip = 1;
} else {
poly1 = polyA;
poly2 = polyB;
xf1 = xfA;
xf2 = xfB;
edge1 = edgeA;
manifold.type = Manifold.e_faceA;
flip = 0;
}
var incidentEdge = [ new Manifold.ClipVertex(), new Manifold.ClipVertex() ];
FindIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2);
var count1 = poly1.m_count;
var vertices1 = poly1.m_vertices;
var iv1 = edge1;
var iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0;
var v11 = vertices1[iv1];
var v12 = vertices1[iv2];
var localTangent = Vec2.Sub(v12, v11);
localTangent.Normalize();
var localNormal = Vec2.Cross(localTangent, 1.0);
var planePoint = Vec2.WAdd(0.5, v11, 0.5, v12);
var tangent = Rot.Mul(xf1.q, localTangent);
var normal = Vec2.Cross(tangent, 1.0);
v11 = Transform.Mul(xf1, v11);
v12 = Transform.Mul(xf1, v12);
// Face offset.
var frontOffset = Vec2.Dot(normal, v11);
// Side offsets, extended by polytope skin thickness.
var sideOffset1 = -Vec2.Dot(tangent, v11) + totalRadius;
var sideOffset2 = Vec2.Dot(tangent, v12) + totalRadius;
// Clip incident edge against extruded edge1 side edges.
var clipPoints1 = [ new Manifold.ClipVertex(), new Manifold.ClipVertex() ];
var clipPoints2 = [ new Manifold.ClipVertex(), new Manifold.ClipVertex() ];
var np;
// Clip to box side 1
np = Manifold.ClipSegmentToLine(clipPoints1, incidentEdge, Vec2.Neg(tangent),
sideOffset1, iv1);
if (np < 2) {
return;
}
// Clip to negative box side 1
np = Manifold.ClipSegmentToLine(clipPoints2, clipPoints1, tangent,
sideOffset2, iv2);
if (np < 2) {
return;
}
// Now clipPoints2 contains the clipped points.
manifold.localNormal = localNormal;
manifold.localPoint = planePoint;
var pointCount = 0;
for (var i = 0; i < clipPoints2.length/* maxManifoldPoints */; ++i) {
var separation = Vec2.Dot(normal, clipPoints2[i].v) - frontOffset;
if (separation <= totalRadius) {
var cp = manifold.points[pointCount]; // ManifoldPoint
cp.localPoint.Set(Transform.MulT(xf2, clipPoints2[i].v));
cp.id = clipPoints2[i].id;
if (flip) {
// Swap features
var cf = cp.id.cf; // ContactFeature
var indexA = cf.indexA;
var indexB = cf.indexB;
var typeA = cf.typeA;
var typeB = cf.typeB;
cf.indexA = indexB;
cf.indexB = indexA;
cf.typeA = typeB;
cf.typeB = typeA;
}
++pointCount;
}
}
manifold.pointCount = pointCount;
}