planck-js
Version:
2D JavaScript physics engine for cross-platform HTML5 game development
267 lines (224 loc) • 7.98 kB
JavaScript
/*
* Planck.js
* The MIT License
* Copyright (c) 2021 Erin Catto, Ali Shakiba
*
* 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.
*/
var _DEBUG = typeof DEBUG === 'undefined' ? false : DEBUG;
var _ASSERT = typeof ASSERT === 'undefined' ? false : ASSERT;
var common = require('../util/common');
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 Settings = require('../Settings');
var Manifold = require('../Manifold');
var Contact = require('../Contact');
var Shape = require('../Shape');
var PolygonShape = require('./PolygonShape');
module.exports = CollidePolygons;
Contact.addType(PolygonShape.TYPE, PolygonShape.TYPE, PolygonContact);
function PolygonContact(manifold, xfA, fixtureA, indexA, xfB, fixtureB, indexB) {
_ASSERT && common.assert(fixtureA.getType() == PolygonShape.TYPE);
_ASSERT && common.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.mulTXf(xf2, xf1);
var bestIndex = 0;
var maxSeparation = -Infinity;
for (var i = 0; i < count1; ++i) {
// Get poly1 normal in frame2.
var n = Rot.mulVec2(xf.q, n1s[i]);
var v1 = Transform.mulVec2(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 && common.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.mulVec2(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.mulVec2(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.mulVec2(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.combine(0.5, v11, 0.5, v12);
var tangent = Rot.mulVec2(xf1.q, localTangent);
var normal = Vec2.cross(tangent, 1.0);
v11 = Transform.mulVec2(xf1, v11);
v12 = Transform.mulVec2(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.mulTVec2(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;
}