org.jbox2d.collision.Collision Maven / Gradle / Ivy
The newest version!
/*
* The MIT License (MIT)
*
* FXGL - JavaFX Game Library
*
* Copyright (c) 2015-2017 AlmasB ([email protected])
*
* 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.
*/
package org.jbox2d.collision;
import com.almasb.fxgl.core.math.Vec2;
import org.jbox2d.collision.Distance.SimplexCache;
import org.jbox2d.collision.Manifold.ManifoldType;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.EdgeShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.JBoxSettings;
import org.jbox2d.common.JBoxUtils;
import org.jbox2d.common.Rotation;
import org.jbox2d.common.Transform;
import org.jbox2d.pooling.IWorldPool;
/**
* Functions used for computing contact points, distance queries, and TOI queries. Collision methods
* are non-static for pooling speed, retrieve a collision object from the {@link SingletonPool}.
* Should not be finalructed.
*
* @author Daniel Murphy
*/
public class Collision {
public static final int NULL_FEATURE = Integer.MAX_VALUE;
private final IWorldPool pool;
public Collision(IWorldPool argPool) {
incidentEdge[0] = new ClipVertex();
incidentEdge[1] = new ClipVertex();
clipPoints1[0] = new ClipVertex();
clipPoints1[1] = new ClipVertex();
clipPoints2[0] = new ClipVertex();
clipPoints2[1] = new ClipVertex();
pool = argPool;
}
private final DistanceInput input = new DistanceInput();
private final SimplexCache cache = new SimplexCache();
private final DistanceOutput output = new DistanceOutput();
/**
* Determine if two generic shapes overlap.
*
* @param shapeA
* @param shapeB
* @param xfA
* @param xfB
* @return
*/
public final boolean testOverlap(Shape shapeA, int indexA, Shape shapeB, int indexB,
Transform xfA, Transform xfB) {
input.proxyA.set(shapeA, indexA);
input.proxyB.set(shapeB, indexB);
input.transformA.set(xfA);
input.transformB.set(xfB);
input.useRadii = true;
cache.count = 0;
pool.getDistance().distance(output, cache, input);
// djm note: anything significant about 10.0f?
return output.distance < 10.0f * JBoxSettings.EPSILON;
}
/**
* 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 state1
* @param state2
* @param manifold1
* @param manifold2
*/
public static final void getPointStates(final PointState[] state1, final PointState[] state2,
final Manifold manifold1, final Manifold manifold2) {
for (int i = 0; i < JBoxSettings.maxManifoldPoints; i++) {
state1[i] = PointState.NULL_STATE;
state2[i] = PointState.NULL_STATE;
}
// Detect persists and removes.
for (int i = 0; i < manifold1.pointCount; i++) {
ContactID id = manifold1.points[i].id;
state1[i] = PointState.REMOVE_STATE;
for (int j = 0; j < manifold2.pointCount; j++) {
if (manifold2.points[j].id.isEqual(id)) {
state1[i] = PointState.PERSIST_STATE;
break;
}
}
}
// Detect persists and adds
for (int i = 0; i < manifold2.pointCount; i++) {
ContactID id = manifold2.points[i].id;
state2[i] = PointState.ADD_STATE;
for (int j = 0; j < manifold1.pointCount; j++) {
if (manifold1.points[j].id.isEqual(id)) {
state2[i] = PointState.PERSIST_STATE;
break;
}
}
}
}
/**
* Clipping for contact manifolds. Sutherland-Hodgman clipping.
*
* @param vOut
* @param vIn
* @param normal
* @param offset
* @return
*/
public static final int clipSegmentToLine(final ClipVertex[] vOut, final ClipVertex[] vIn,
final Vec2 normal, float offset, int vertexIndexA) {
// Start with no output points
int numOut = 0;
final ClipVertex vIn0 = vIn[0];
final ClipVertex vIn1 = vIn[1];
final Vec2 vIn0v = vIn0.v;
final Vec2 vIn1v = vIn1.v;
// Calculate the distance of end points to the line
float distance0 = Vec2.dot(normal, vIn0v) - offset;
float distance1 = Vec2.dot(normal, vIn1v) - offset;
// If the points are behind the plane
if (distance0 <= 0.0f) {
vOut[numOut++].set(vIn0);
}
if (distance1 <= 0.0f) {
vOut[numOut++].set(vIn1);
}
// If the points are on different sides of the plane
if (distance0 * distance1 < 0.0f) {
// Find intersection point of edge and plane
float interp = distance0 / (distance0 - distance1);
ClipVertex vOutNO = vOut[numOut];
// vOut[numOut].v = vIn[0].v + interp * (vIn[1].v - vIn[0].v);
vOutNO.v.x = vIn0v.x + interp * (vIn1v.x - vIn0v.x);
vOutNO.v.y = vIn0v.y + interp * (vIn1v.y - vIn0v.y);
// VertexA is hitting edgeB.
vOutNO.id.indexA = (byte) vertexIndexA;
vOutNO.id.indexB = vIn0.id.indexB;
vOutNO.id.typeA = (byte) ContactID.Type.VERTEX.ordinal();
vOutNO.id.typeB = (byte) ContactID.Type.FACE.ordinal();
++numOut;
}
return numOut;
}
// #### COLLISION STUFF (not from collision.h or collision.cpp) ####
// djm pooling
private static Vec2 d = new Vec2();
/**
* Compute the collision manifold between two circles.
*
* @param manifold
* @param circle1
* @param xfA
* @param circle2
* @param xfB
*/
public final void collideCircles(Manifold manifold, final CircleShape circle1,
final Transform xfA, final CircleShape circle2, final Transform xfB) {
manifold.pointCount = 0;
// before inline:
// Transform.mulToOut(xfA, circle1.m_p, pA);
// Transform.mulToOut(xfB, circle2.m_p, pB);
// d.set(pB).subLocal(pA);
// float distSqr = d.x * d.x + d.y * d.y;
// after inline:
Vec2 circle1p = circle1.m_p;
Vec2 circle2p = circle2.m_p;
float pAx = (xfA.q.c * circle1p.x - xfA.q.s * circle1p.y) + xfA.p.x;
float pAy = (xfA.q.s * circle1p.x + xfA.q.c * circle1p.y) + xfA.p.y;
float pBx = (xfB.q.c * circle2p.x - xfB.q.s * circle2p.y) + xfB.p.x;
float pBy = (xfB.q.s * circle2p.x + xfB.q.c * circle2p.y) + xfB.p.y;
float dx = pBx - pAx;
float dy = pBy - pAy;
float distSqr = dx * dx + dy * dy;
// end inline
final float radius = circle1.getRadius() + circle2.getRadius();
if (distSqr > radius * radius) {
return;
}
manifold.type = ManifoldType.CIRCLES;
manifold.localPoint.set(circle1p);
manifold.localNormal.setZero();
manifold.pointCount = 1;
manifold.points[0].localPoint.set(circle2p);
manifold.points[0].id.zero();
}
// djm pooling, and from above
/**
* Compute the collision manifold between a polygon and a circle.
*
* @param manifold
* @param polygon
* @param xfA
* @param circle
* @param xfB
*/
public final void collidePolygonAndCircle(Manifold manifold, final PolygonShape polygon,
final Transform xfA, final CircleShape circle, final Transform xfB) {
manifold.pointCount = 0;
// Vec2 v = circle.m_p;
// Compute circle position in the frame of the polygon.
// before inline:
// Transform.mulToOutUnsafe(xfB, circle.m_p, c);
// Transform.mulTransToOut(xfA, c, cLocal);
// final float cLocalx = cLocal.x;
// final float cLocaly = cLocal.y;
// after inline:
final Vec2 circlep = circle.m_p;
final Rotation xfBq = xfB.q;
final Rotation xfAq = xfA.q;
final float cx = (xfBq.c * circlep.x - xfBq.s * circlep.y) + xfB.p.x;
final float cy = (xfBq.s * circlep.x + xfBq.c * circlep.y) + xfB.p.y;
final float px = cx - xfA.p.x;
final float py = cy - xfA.p.y;
final float cLocalx = (xfAq.c * px + xfAq.s * py);
final float cLocaly = (-xfAq.s * px + xfAq.c * py);
// end inline
// Find the min separating edge.
int normalIndex = 0;
float separation = -Float.MAX_VALUE;
final float radius = polygon.getRadius() + circle.getRadius();
final int vertexCount = polygon.getVertexCount();
float s;
final Vec2[] vertices = polygon.m_vertices;
final Vec2[] normals = polygon.m_normals;
for (int i = 0; i < vertexCount; i++) {
// before inline
// temp.set(cLocal).subLocal(vertices[i]);
// float s = Vec2.dot(normals[i], temp);
// after inline
final Vec2 vertex = vertices[i];
final float tempx = cLocalx - vertex.x;
final float tempy = cLocaly - vertex.y;
s = normals[i].x * tempx + normals[i].y * tempy;
if (s > radius) {
// early out
return;
}
if (s > separation) {
separation = s;
normalIndex = i;
}
}
// Vertices that subtend the incident face.
final int vertIndex1 = normalIndex;
final int vertIndex2 = vertIndex1 + 1 < vertexCount ? vertIndex1 + 1 : 0;
final Vec2 v1 = vertices[vertIndex1];
final Vec2 v2 = vertices[vertIndex2];
// If the center is inside the polygon ...
if (separation < JBoxSettings.EPSILON) {
manifold.pointCount = 1;
manifold.type = ManifoldType.FACE_A;
// before inline:
// manifold.localNormal.set(normals[normalIndex]);
// manifold.localPoint.set(v1).addLocal(v2).mulLocal(.5f);
// manifold.points[0].localPoint.set(circle.m_p);
// after inline:
final Vec2 normal = normals[normalIndex];
manifold.localNormal.x = normal.x;
manifold.localNormal.y = normal.y;
manifold.localPoint.x = (v1.x + v2.x) * .5f;
manifold.localPoint.y = (v1.y + v2.y) * .5f;
final ManifoldPoint mpoint = manifold.points[0];
mpoint.localPoint.x = circlep.x;
mpoint.localPoint.y = circlep.y;
mpoint.id.zero();
// end inline
return;
}
// Compute barycentric coordinates
// before inline:
// temp.set(cLocal).subLocal(v1);
// temp2.set(v2).subLocal(v1);
// float u1 = Vec2.dot(temp, temp2);
// temp.set(cLocal).subLocal(v2);
// temp2.set(v1).subLocal(v2);
// float u2 = Vec2.dot(temp, temp2);
// after inline:
final float tempX = cLocalx - v1.x;
final float tempY = cLocaly - v1.y;
final float temp2X = v2.x - v1.x;
final float temp2Y = v2.y - v1.y;
final float u1 = tempX * temp2X + tempY * temp2Y;
final float temp3X = cLocalx - v2.x;
final float temp3Y = cLocaly - v2.y;
final float temp4X = v1.x - v2.x;
final float temp4Y = v1.y - v2.y;
final float u2 = temp3X * temp4X + temp3Y * temp4Y;
// end inline
if (u1 <= 0f) {
// inlined
final float dx = cLocalx - v1.x;
final float dy = cLocaly - v1.y;
if (dx * dx + dy * dy > radius * radius) {
return;
}
manifold.pointCount = 1;
manifold.type = ManifoldType.FACE_A;
// before inline:
// manifold.localNormal.set(cLocal).subLocal(v1);
// after inline:
manifold.localNormal.x = cLocalx - v1.x;
manifold.localNormal.y = cLocaly - v1.y;
// end inline
manifold.localNormal.normalize();
manifold.localPoint.set(v1);
manifold.points[0].localPoint.set(circlep);
manifold.points[0].id.zero();
} else if (u2 <= 0.0f) {
// inlined
final float dx = cLocalx - v2.x;
final float dy = cLocaly - v2.y;
if (dx * dx + dy * dy > radius * radius) {
return;
}
manifold.pointCount = 1;
manifold.type = ManifoldType.FACE_A;
// before inline:
// manifold.localNormal.set(cLocal).subLocal(v2);
// after inline:
manifold.localNormal.x = cLocalx - v2.x;
manifold.localNormal.y = cLocaly - v2.y;
// end inline
manifold.localNormal.normalize();
manifold.localPoint.set(v2);
manifold.points[0].localPoint.set(circlep);
manifold.points[0].id.zero();
} else {
// Vec2 faceCenter = 0.5f * (v1 + v2);
// (temp is faceCenter)
// before inline:
// temp.set(v1).addLocal(v2).mulLocal(.5f);
//
// temp2.set(cLocal).subLocal(temp);
// separation = Vec2.dot(temp2, normals[vertIndex1]);
// if (separation > radius) {
// return;
// }
// after inline:
final float fcx = (v1.x + v2.x) * .5f;
final float fcy = (v1.y + v2.y) * .5f;
final float tx = cLocalx - fcx;
final float ty = cLocaly - fcy;
final Vec2 normal = normals[vertIndex1];
separation = tx * normal.x + ty * normal.y;
if (separation > radius) {
return;
}
// end inline
manifold.pointCount = 1;
manifold.type = ManifoldType.FACE_A;
manifold.localNormal.set(normals[vertIndex1]);
manifold.localPoint.x = fcx; // (faceCenter)
manifold.localPoint.y = fcy;
manifold.points[0].localPoint.set(circlep);
manifold.points[0].id.zero();
}
}
// djm pooling, and from above
private final Vec2 temp = new Vec2();
private final Transform xf = new Transform();
private final Vec2 n = new Vec2();
private final Vec2 v1 = new Vec2();
/**
* Find the max separation between poly1 and poly2 using edge normals from poly1.
*
* @param edgeIndex
* @param poly1
* @param xf1
* @param poly2
* @param xf2
* @return
*/
public final void findMaxSeparation(EdgeResults results, final PolygonShape poly1,
final Transform xf1, final PolygonShape poly2, final Transform xf2) {
int count1 = poly1.getVertexCount();
int count2 = poly2.getVertexCount();
Vec2[] n1s = poly1.m_normals;
Vec2[] v1s = poly1.m_vertices;
Vec2[] v2s = poly2.m_vertices;
Transform.mulTransToOutUnsafe(xf2, xf1, xf);
final Rotation xfq = xf.q;
int bestIndex = 0;
float maxSeparation = -Float.MAX_VALUE;
for (int i = 0; i < count1; i++) {
// Get poly1 normal in frame2.
Rotation.mulToOutUnsafe(xfq, n1s[i], n);
Transform.mulToOutUnsafe(xf, v1s[i], v1);
// Find deepest point for normal i.
float si = Float.MAX_VALUE;
for (int j = 0; j < count2; ++j) {
Vec2 v2sj = v2s[j];
float sij = n.x * (v2sj.x - v1.x) + n.y * (v2sj.y - v1.y);
if (sij < si) {
si = sij;
}
}
if (si > maxSeparation) {
maxSeparation = si;
bestIndex = i;
}
}
results.edgeIndex = bestIndex;
results.separation = maxSeparation;
}
public final void findIncidentEdge(final ClipVertex[] c, final PolygonShape poly1,
final Transform xf1, int edge1, final PolygonShape poly2, final Transform xf2) {
int count1 = poly1.getVertexCount();
final Vec2[] normals1 = poly1.m_normals;
int count2 = poly2.getVertexCount();
final Vec2[] vertices2 = poly2.m_vertices;
final Vec2[] normals2 = poly2.m_normals;
assert (0 <= edge1 && edge1 < count1);
final ClipVertex c0 = c[0];
final ClipVertex c1 = c[1];
final Rotation xf1q = xf1.q;
final Rotation xf2q = xf2.q;
// Get the normal of the reference edge in poly2's frame.
// Vec2 normal1 = MulT(xf2.R, Mul(xf1.R, normals1[edge1]));
// before inline:
// Rot.mulToOutUnsafe(xf1.q, normals1[edge1], normal1); // temporary
// Rot.mulTrans(xf2.q, normal1, normal1);
// after inline:
final Vec2 v = normals1[edge1];
final float tempx = xf1q.c * v.x - xf1q.s * v.y;
final float tempy = xf1q.s * v.x + xf1q.c * v.y;
final float normal1x = xf2q.c * tempx + xf2q.s * tempy;
final float normal1y = -xf2q.s * tempx + xf2q.c * tempy;
// end inline
// Find the incident edge on poly2.
int index = 0;
float minDot = Float.MAX_VALUE;
for (int i = 0; i < count2; ++i) {
Vec2 b = normals2[i];
float dot = normal1x * b.x + normal1y * b.y;
if (dot < minDot) {
minDot = dot;
index = i;
}
}
// Build the clip vertices for the incident edge.
int i1 = index;
int i2 = i1 + 1 < count2 ? i1 + 1 : 0;
// c0.v = Mul(xf2, vertices2[i1]);
Vec2 v1 = vertices2[i1];
Vec2 out = c0.v;
out.x = (xf2q.c * v1.x - xf2q.s * v1.y) + xf2.p.x;
out.y = (xf2q.s * v1.x + xf2q.c * v1.y) + xf2.p.y;
c0.id.indexA = (byte) edge1;
c0.id.indexB = (byte) i1;
c0.id.typeA = (byte) ContactID.Type.FACE.ordinal();
c0.id.typeB = (byte) ContactID.Type.VERTEX.ordinal();
// c1.v = Mul(xf2, vertices2[i2]);
Vec2 v2 = vertices2[i2];
Vec2 out1 = c1.v;
out1.x = (xf2q.c * v2.x - xf2q.s * v2.y) + xf2.p.x;
out1.y = (xf2q.s * v2.x + xf2q.c * v2.y) + xf2.p.y;
c1.id.indexA = (byte) edge1;
c1.id.indexB = (byte) i2;
c1.id.typeA = (byte) ContactID.Type.FACE.ordinal();
c1.id.typeB = (byte) ContactID.Type.VERTEX.ordinal();
}
private final EdgeResults results1 = new EdgeResults();
private final EdgeResults results2 = new EdgeResults();
private final ClipVertex[] incidentEdge = new ClipVertex[2];
private final Vec2 localTangent = new Vec2();
private final Vec2 localNormal = new Vec2();
private final Vec2 planePoint = new Vec2();
private final Vec2 tangent = new Vec2();
private final Vec2 v11 = new Vec2();
private final Vec2 v12 = new Vec2();
private final ClipVertex[] clipPoints1 = new ClipVertex[2];
private final ClipVertex[] clipPoints2 = new ClipVertex[2];
/**
* Compute the collision manifold between two polygons.
*
* @param manifold
* @param polygon1
* @param xf1
* @param polygon2
* @param xf2
*/
public final void collidePolygons(Manifold manifold, final PolygonShape polyA,
final Transform xfA, final PolygonShape polyB, final Transform xfB) {
// Find edge normal of max separation on A - return if separating axis is found
// Find edge normal of max separation on B - return if separation axis is found
// Choose reference edge as min(minA, minB)
// Find incident edge
// Clip
// The normal points from 1 to 2
manifold.pointCount = 0;
float totalRadius = polyA.getRadius() + polyB.getRadius();
findMaxSeparation(results1, polyA, xfA, polyB, xfB);
if (results1.separation > totalRadius) {
return;
}
findMaxSeparation(results2, polyB, xfB, polyA, xfA);
if (results2.separation > totalRadius) {
return;
}
final PolygonShape poly1; // reference polygon
final PolygonShape poly2; // incident polygon
Transform xf1, xf2;
int edge1; // reference edge
boolean flip;
final float k_tol = 0.1f * JBoxSettings.linearSlop;
if (results2.separation > results1.separation + k_tol) {
poly1 = polyB;
poly2 = polyA;
xf1 = xfB;
xf2 = xfA;
edge1 = results2.edgeIndex;
manifold.type = ManifoldType.FACE_B;
flip = true;
} else {
poly1 = polyA;
poly2 = polyB;
xf1 = xfA;
xf2 = xfB;
edge1 = results1.edgeIndex;
manifold.type = ManifoldType.FACE_A;
flip = false;
}
final Rotation xf1q = xf1.q;
findIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2);
int count1 = poly1.getVertexCount();
final Vec2[] vertices1 = poly1.m_vertices;
final int iv1 = edge1;
final int iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0;
v11.set(vertices1[iv1]);
v12.set(vertices1[iv2]);
localTangent.x = v12.x - v11.x;
localTangent.y = v12.y - v11.y;
localTangent.normalize();
// Vec2 localNormal = Vec2.cross(dv, 1.0f);
localNormal.x = 1f * localTangent.y;
localNormal.y = -1f * localTangent.x;
// Vec2 planePoint = 0.5f * (v11+ v12);
planePoint.x = (v11.x + v12.x) * .5f;
planePoint.y = (v11.y + v12.y) * .5f;
// Rot.mulToOutUnsafe(xf1.q, localTangent, tangent);
tangent.x = xf1q.c * localTangent.x - xf1q.s * localTangent.y;
tangent.y = xf1q.s * localTangent.x + xf1q.c * localTangent.y;
// Vec2.crossToOutUnsafe(tangent, 1f, normal);
final float normalx = 1f * tangent.y;
final float normaly = -1f * tangent.x;
Transform.mulToOut(xf1, v11, v11);
Transform.mulToOut(xf1, v12, v12);
// v11 = Mul(xf1, v11);
// v12 = Mul(xf1, v12);
// Face offset
// float frontOffset = Vec2.dot(normal, v11);
float frontOffset = normalx * v11.x + normaly * v11.y;
// Side offsets, extended by polytope skin thickness.
// float sideOffset1 = -Vec2.dot(tangent, v11) + totalRadius;
// float sideOffset2 = Vec2.dot(tangent, v12) + totalRadius;
float sideOffset1 = -(tangent.x * v11.x + tangent.y * v11.y) + totalRadius;
float sideOffset2 = tangent.x * v12.x + tangent.y * v12.y + totalRadius;
// Clip incident edge against extruded edge1 side edges.
// ClipVertex clipPoints1[2];
// ClipVertex clipPoints2[2];
int np;
// Clip to box side 1
// np = ClipSegmentToLine(clipPoints1, incidentEdge, -sideNormal, sideOffset1);
tangent.negateLocal();
np = clipSegmentToLine(clipPoints1, incidentEdge, tangent, sideOffset1, iv1);
tangent.negateLocal();
if (np < 2) {
return;
}
// Clip to negative box side 1
np = clipSegmentToLine(clipPoints2, clipPoints1, tangent, sideOffset2, iv2);
if (np < 2) {
return;
}
// Now clipPoints2 contains the clipped points.
manifold.localNormal.set(localNormal);
manifold.localPoint.set(planePoint);
int pointCount = 0;
for (int i = 0; i < JBoxSettings.maxManifoldPoints; ++i) {
// float separation = Vec2.dot(normal, clipPoints2[i].v) - frontOffset;
float separation = normalx * clipPoints2[i].v.x + normaly * clipPoints2[i].v.y - frontOffset;
if (separation <= totalRadius) {
ManifoldPoint cp = manifold.points[pointCount];
// cp.m_localPoint = MulT(xf2, clipPoints2[i].v);
Vec2 out = cp.localPoint;
final float px = clipPoints2[i].v.x - xf2.p.x;
final float py = clipPoints2[i].v.y - xf2.p.y;
out.x = (xf2.q.c * px + xf2.q.s * py);
out.y = (-xf2.q.s * px + xf2.q.c * py);
cp.id.set(clipPoints2[i].id);
if (flip) {
// Swap features
cp.id.flip();
}
++pointCount;
}
}
manifold.pointCount = pointCount;
}
private final Vec2 Q = new Vec2();
private final Vec2 e = new Vec2();
private final ContactID cf = new ContactID();
private final Vec2 e1 = new Vec2();
private final Vec2 P = new Vec2();
// Compute contact points for edge versus circle.
// This accounts for edge connectivity.
public void collideEdgeAndCircle(Manifold manifold, final EdgeShape edgeA, final Transform xfA,
final CircleShape circleB, final Transform xfB) {
manifold.pointCount = 0;
// Compute circle in frame of edge
// Vec2 Q = MulT(xfA, Mul(xfB, circleB.m_p));
Transform.mulToOutUnsafe(xfB, circleB.m_p, temp);
Transform.mulTransToOutUnsafe(xfA, temp, Q);
final Vec2 A = edgeA.m_vertex1;
final Vec2 B = edgeA.m_vertex2;
e.set(B).subLocal(A);
// Barycentric coordinates
float u = Vec2.dot(e, temp.set(B).subLocal(Q));
float v = Vec2.dot(e, temp.set(Q).subLocal(A));
float radius = edgeA.getRadius() + circleB.getRadius();
// ContactFeature cf;
cf.indexB = 0;
cf.typeB = (byte) ContactID.Type.VERTEX.ordinal();
// Region A
if (v <= 0.0f) {
final Vec2 P = A;
d.set(Q).subLocal(P);
float dd = Vec2.dot(d, d);
if (dd > radius * radius) {
return;
}
// Is there an edge connected to A?
if (edgeA.m_hasVertex0) {
final Vec2 A1 = edgeA.m_vertex0;
final Vec2 B1 = A;
e1.set(B1).subLocal(A1);
float u1 = Vec2.dot(e1, temp.set(B1).subLocal(Q));
// Is the circle in Region AB of the previous edge?
if (u1 > 0.0f) {
return;
}
}
cf.indexA = 0;
cf.typeA = (byte) ContactID.Type.VERTEX.ordinal();
manifold.pointCount = 1;
manifold.type = Manifold.ManifoldType.CIRCLES;
manifold.localNormal.setZero();
manifold.localPoint.set(P);
// manifold.points[0].id.key = 0;
manifold.points[0].id.set(cf);
manifold.points[0].localPoint.set(circleB.m_p);
return;
}
// Region B
if (u <= 0.0f) {
Vec2 P = B;
d.set(Q).subLocal(P);
float dd = Vec2.dot(d, d);
if (dd > radius * radius) {
return;
}
// Is there an edge connected to B?
if (edgeA.m_hasVertex3) {
final Vec2 B2 = edgeA.m_vertex3;
final Vec2 A2 = B;
final Vec2 e2 = e1;
e2.set(B2).subLocal(A2);
float v2 = Vec2.dot(e2, temp.set(Q).subLocal(A2));
// Is the circle in Region AB of the next edge?
if (v2 > 0.0f) {
return;
}
}
cf.indexA = 1;
cf.typeA = (byte) ContactID.Type.VERTEX.ordinal();
manifold.pointCount = 1;
manifold.type = Manifold.ManifoldType.CIRCLES;
manifold.localNormal.setZero();
manifold.localPoint.set(P);
// manifold.points[0].id.key = 0;
manifold.points[0].id.set(cf);
manifold.points[0].localPoint.set(circleB.m_p);
return;
}
// Region AB
float den = Vec2.dot(e, e);
assert (den > 0.0f);
// Vec2 P = (1.0f / den) * (u * A + v * B);
P.set(A).mulLocal(u).addLocal(temp.set(B).mulLocal(v));
P.mulLocal(1.0f / den);
d.set(Q).subLocal(P);
float dd = Vec2.dot(d, d);
if (dd > radius * radius) {
return;
}
n.x = -e.y;
n.y = e.x;
if (Vec2.dot(n, temp.set(Q).subLocal(A)) < 0.0f) {
n.set(-n.x, -n.y);
}
n.normalize();
cf.indexA = 0;
cf.typeA = (byte) ContactID.Type.FACE.ordinal();
manifold.pointCount = 1;
manifold.type = Manifold.ManifoldType.FACE_A;
manifold.localNormal.set(n);
manifold.localPoint.set(A);
// manifold.points[0].id.key = 0;
manifold.points[0].id.set(cf);
manifold.points[0].localPoint.set(circleB.m_p);
}
private final EPCollider collider = new EPCollider();
public void collideEdgeAndPolygon(Manifold manifold, final EdgeShape edgeA, final Transform xfA,
final PolygonShape polygonB, final Transform xfB) {
collider.collide(manifold, edgeA, xfA, polygonB, xfB);
}
/**
* Java-specific class for returning edge results
*/
private static class EdgeResults {
public float separation;
public int edgeIndex;
}
/**
* Used for computing contact manifolds.
*/
public static class ClipVertex {
public final Vec2 v;
public final ContactID id;
public ClipVertex() {
v = new Vec2();
id = new ContactID();
}
public void set(final ClipVertex cv) {
Vec2 v1 = cv.v;
v.x = v1.x;
v.y = v1.y;
ContactID c = cv.id;
id.indexA = c.indexA;
id.indexB = c.indexB;
id.typeA = c.typeA;
id.typeB = c.typeB;
}
}
/**
* This is used for determining the state of contact points.
*
* @author Daniel Murphy
*/
public static enum PointState {
/**
* point does not exist
*/
NULL_STATE,
/**
* point was added in the update
*/
ADD_STATE,
/**
* point persisted across the update
*/
PERSIST_STATE,
/**
* point was removed in the update
*/
REMOVE_STATE
}
/**
* This structure is used to keep track of the best separating axis.
*/
static class EPAxis {
enum Type {
UNKNOWN, EDGE_A, EDGE_B
}
Type type;
int index;
float separation;
}
/**
* This holds polygon B expressed in frame A.
*/
static class TempPolygon {
final Vec2[] vertices = new Vec2[JBoxSettings.maxPolygonVertices];
final Vec2[] normals = new Vec2[JBoxSettings.maxPolygonVertices];
int count;
public TempPolygon() {
for (int i = 0; i < vertices.length; i++) {
vertices[i] = new Vec2();
normals[i] = new Vec2();
}
}
}
/**
* Reference face used for clipping
*/
static class ReferenceFace {
int i1, i2;
final Vec2 v1 = new Vec2();
final Vec2 v2 = new Vec2();
final Vec2 normal = new Vec2();
final Vec2 sideNormal1 = new Vec2();
float sideOffset1;
final Vec2 sideNormal2 = new Vec2();
float sideOffset2;
}
/**
* This class collides and edge and a polygon, taking into account edge adjacency.
*/
static class EPCollider {
enum VertexType {
ISOLATED, CONCAVE, CONVEX
}
final TempPolygon m_polygonB = new TempPolygon();
final Transform m_xf = new Transform();
final Vec2 m_centroidB = new Vec2();
Vec2 m_v0 = new Vec2();
Vec2 m_v1 = new Vec2();
Vec2 m_v2 = new Vec2();
Vec2 m_v3 = new Vec2();
final Vec2 m_normal0 = new Vec2();
final Vec2 m_normal1 = new Vec2();
final Vec2 m_normal2 = new Vec2();
final Vec2 m_normal = new Vec2();
VertexType m_type1, m_type2;
final Vec2 m_lowerLimit = new Vec2();
final Vec2 m_upperLimit = new Vec2();
float m_radius;
boolean m_front;
public EPCollider() {
for (int i = 0; i < 2; i++) {
ie[i] = new ClipVertex();
clipPoints1[i] = new ClipVertex();
clipPoints2[i] = new ClipVertex();
}
}
private final Vec2 edge1 = new Vec2();
private final Vec2 temp = new Vec2();
private final Vec2 edge0 = new Vec2();
private final Vec2 edge2 = new Vec2();
private final ClipVertex[] ie = new ClipVertex[2];
private final ClipVertex[] clipPoints1 = new ClipVertex[2];
private final ClipVertex[] clipPoints2 = new ClipVertex[2];
private final ReferenceFace rf = new ReferenceFace();
private final EPAxis edgeAxis = new EPAxis();
private final EPAxis polygonAxis = new EPAxis();
public void collide(Manifold manifold, final EdgeShape edgeA, final Transform xfA,
final PolygonShape polygonB, final Transform xfB) {
Transform.mulTransToOutUnsafe(xfA, xfB, m_xf);
Transform.mulToOutUnsafe(m_xf, polygonB.m_centroid, m_centroidB);
m_v0 = edgeA.m_vertex0;
m_v1 = edgeA.m_vertex1;
m_v2 = edgeA.m_vertex2;
m_v3 = edgeA.m_vertex3;
boolean hasVertex0 = edgeA.m_hasVertex0;
boolean hasVertex3 = edgeA.m_hasVertex3;
edge1.set(m_v2).subLocal(m_v1);
edge1.normalize();
m_normal1.set(edge1.y, -edge1.x);
float offset1 = Vec2.dot(m_normal1, temp.set(m_centroidB).subLocal(m_v1));
float offset0 = 0.0f, offset2 = 0.0f;
boolean convex1 = false, convex2 = false;
// Is there a preceding edge?
if (hasVertex0) {
edge0.set(m_v1).subLocal(m_v0);
edge0.normalize();
m_normal0.set(edge0.y, -edge0.x);
convex1 = Vec2.cross(edge0, edge1) >= 0.0f;
offset0 = Vec2.dot(m_normal0, temp.set(m_centroidB).subLocal(m_v0));
}
// Is there a following edge?
if (hasVertex3) {
edge2.set(m_v3).subLocal(m_v2);
edge2.normalize();
m_normal2.set(edge2.y, -edge2.x);
convex2 = Vec2.cross(edge1, edge2) > 0.0f;
offset2 = Vec2.dot(m_normal2, temp.set(m_centroidB).subLocal(m_v2));
}
// Determine front or back collision. Determine collision normal limits.
if (hasVertex0 && hasVertex3) {
if (convex1 && convex2) {
m_front = offset0 >= 0.0f || offset1 >= 0.0f || offset2 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = m_normal0.x;
m_lowerLimit.y = m_normal0.y;
m_upperLimit.x = m_normal2.x;
m_upperLimit.y = m_normal2.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = -m_normal1.x;
m_lowerLimit.y = -m_normal1.y;
m_upperLimit.x = -m_normal1.x;
m_upperLimit.y = -m_normal1.y;
}
} else if (convex1) {
m_front = offset0 >= 0.0f || (offset1 >= 0.0f && offset2 >= 0.0f);
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = m_normal0.x;
m_lowerLimit.y = m_normal0.y;
m_upperLimit.x = m_normal1.x;
m_upperLimit.y = m_normal1.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = -m_normal2.x;
m_lowerLimit.y = -m_normal2.y;
m_upperLimit.x = -m_normal1.x;
m_upperLimit.y = -m_normal1.y;
}
} else if (convex2) {
m_front = offset2 >= 0.0f || (offset0 >= 0.0f && offset1 >= 0.0f);
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = m_normal1.x;
m_lowerLimit.y = m_normal1.y;
m_upperLimit.x = m_normal2.x;
m_upperLimit.y = m_normal2.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = -m_normal1.x;
m_lowerLimit.y = -m_normal1.y;
m_upperLimit.x = -m_normal0.x;
m_upperLimit.y = -m_normal0.y;
}
} else {
m_front = offset0 >= 0.0f && offset1 >= 0.0f && offset2 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = m_normal1.x;
m_lowerLimit.y = m_normal1.y;
m_upperLimit.x = m_normal1.x;
m_upperLimit.y = m_normal1.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = -m_normal2.x;
m_lowerLimit.y = -m_normal2.y;
m_upperLimit.x = -m_normal0.x;
m_upperLimit.y = -m_normal0.y;
}
}
} else if (hasVertex0) {
if (convex1) {
m_front = offset0 >= 0.0f || offset1 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = m_normal0.x;
m_lowerLimit.y = m_normal0.y;
m_upperLimit.x = -m_normal1.x;
m_upperLimit.y = -m_normal1.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = m_normal1.x;
m_lowerLimit.y = m_normal1.y;
m_upperLimit.x = -m_normal1.x;
m_upperLimit.y = -m_normal1.y;
}
} else {
m_front = offset0 >= 0.0f && offset1 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = m_normal1.x;
m_lowerLimit.y = m_normal1.y;
m_upperLimit.x = -m_normal1.x;
m_upperLimit.y = -m_normal1.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = m_normal1.x;
m_lowerLimit.y = m_normal1.y;
m_upperLimit.x = -m_normal0.x;
m_upperLimit.y = -m_normal0.y;
}
}
} else if (hasVertex3) {
if (convex2) {
m_front = offset1 >= 0.0f || offset2 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = -m_normal1.x;
m_lowerLimit.y = -m_normal1.y;
m_upperLimit.x = m_normal2.x;
m_upperLimit.y = m_normal2.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = -m_normal1.x;
m_lowerLimit.y = -m_normal1.y;
m_upperLimit.x = m_normal1.x;
m_upperLimit.y = m_normal1.y;
}
} else {
m_front = offset1 >= 0.0f && offset2 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = -m_normal1.x;
m_lowerLimit.y = -m_normal1.y;
m_upperLimit.x = m_normal1.x;
m_upperLimit.y = m_normal1.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = -m_normal2.x;
m_lowerLimit.y = -m_normal2.y;
m_upperLimit.x = m_normal1.x;
m_upperLimit.y = m_normal1.y;
}
}
} else {
m_front = offset1 >= 0.0f;
if (m_front) {
m_normal.x = m_normal1.x;
m_normal.y = m_normal1.y;
m_lowerLimit.x = -m_normal1.x;
m_lowerLimit.y = -m_normal1.y;
m_upperLimit.x = -m_normal1.x;
m_upperLimit.y = -m_normal1.y;
} else {
m_normal.x = -m_normal1.x;
m_normal.y = -m_normal1.y;
m_lowerLimit.x = m_normal1.x;
m_lowerLimit.y = m_normal1.y;
m_upperLimit.x = m_normal1.x;
m_upperLimit.y = m_normal1.y;
}
}
// Get polygonB in frameA
m_polygonB.count = polygonB.getVertexCount();
for (int i = 0; i < polygonB.getVertexCount(); ++i) {
Transform.mulToOutUnsafe(m_xf, polygonB.m_vertices[i], m_polygonB.vertices[i]);
Rotation.mulToOutUnsafe(m_xf.q, polygonB.m_normals[i], m_polygonB.normals[i]);
}
m_radius = 2.0f * JBoxSettings.polygonRadius;
manifold.pointCount = 0;
computeEdgeSeparation(edgeAxis);
// If no valid normal can be found than this edge should not collide.
if (edgeAxis.type == EPAxis.Type.UNKNOWN) {
return;
}
if (edgeAxis.separation > m_radius) {
return;
}
computePolygonSeparation(polygonAxis);
if (polygonAxis.type != EPAxis.Type.UNKNOWN && polygonAxis.separation > m_radius) {
return;
}
// Use hysteresis for jitter reduction.
final float k_relativeTol = 0.98f;
final float k_absoluteTol = 0.001f;
EPAxis primaryAxis;
if (polygonAxis.type == EPAxis.Type.UNKNOWN) {
primaryAxis = edgeAxis;
} else if (polygonAxis.separation > k_relativeTol * edgeAxis.separation + k_absoluteTol) {
primaryAxis = polygonAxis;
} else {
primaryAxis = edgeAxis;
}
final ClipVertex ie0 = ie[0];
final ClipVertex ie1 = ie[1];
if (primaryAxis.type == EPAxis.Type.EDGE_A) {
manifold.type = Manifold.ManifoldType.FACE_A;
// Search for the polygon normal that is most anti-parallel to the edge normal.
int bestIndex = 0;
float bestValue = Vec2.dot(m_normal, m_polygonB.normals[0]);
for (int i = 1; i < m_polygonB.count; ++i) {
float value = Vec2.dot(m_normal, m_polygonB.normals[i]);
if (value < bestValue) {
bestValue = value;
bestIndex = i;
}
}
int i1 = bestIndex;
int i2 = i1 + 1 < m_polygonB.count ? i1 + 1 : 0;
ie0.v.set(m_polygonB.vertices[i1]);
ie0.id.indexA = 0;
ie0.id.indexB = (byte) i1;
ie0.id.typeA = (byte) ContactID.Type.FACE.ordinal();
ie0.id.typeB = (byte) ContactID.Type.VERTEX.ordinal();
ie1.v.set(m_polygonB.vertices[i2]);
ie1.id.indexA = 0;
ie1.id.indexB = (byte) i2;
ie1.id.typeA = (byte) ContactID.Type.FACE.ordinal();
ie1.id.typeB = (byte) ContactID.Type.VERTEX.ordinal();
if (m_front) {
rf.i1 = 0;
rf.i2 = 1;
rf.v1.set(m_v1);
rf.v2.set(m_v2);
rf.normal.set(m_normal1);
} else {
rf.i1 = 1;
rf.i2 = 0;
rf.v1.set(m_v2);
rf.v2.set(m_v1);
rf.normal.set(m_normal1).negateLocal();
}
} else {
manifold.type = Manifold.ManifoldType.FACE_B;
ie0.v.set(m_v1);
ie0.id.indexA = 0;
ie0.id.indexB = (byte) primaryAxis.index;
ie0.id.typeA = (byte) ContactID.Type.VERTEX.ordinal();
ie0.id.typeB = (byte) ContactID.Type.FACE.ordinal();
ie1.v.set(m_v2);
ie1.id.indexA = 0;
ie1.id.indexB = (byte) primaryAxis.index;
ie1.id.typeA = (byte) ContactID.Type.VERTEX.ordinal();
ie1.id.typeB = (byte) ContactID.Type.FACE.ordinal();
rf.i1 = primaryAxis.index;
rf.i2 = rf.i1 + 1 < m_polygonB.count ? rf.i1 + 1 : 0;
rf.v1.set(m_polygonB.vertices[rf.i1]);
rf.v2.set(m_polygonB.vertices[rf.i2]);
rf.normal.set(m_polygonB.normals[rf.i1]);
}
rf.sideNormal1.set(rf.normal.y, -rf.normal.x);
rf.sideNormal2.set(rf.sideNormal1).negateLocal();
rf.sideOffset1 = Vec2.dot(rf.sideNormal1, rf.v1);
rf.sideOffset2 = Vec2.dot(rf.sideNormal2, rf.v2);
// Clip incident edge against extruded edge1 side edges.
int np;
// Clip to box side 1
np = clipSegmentToLine(clipPoints1, ie, rf.sideNormal1, rf.sideOffset1, rf.i1);
if (np < JBoxSettings.maxManifoldPoints) {
return;
}
// Clip to negative box side 1
np = clipSegmentToLine(clipPoints2, clipPoints1, rf.sideNormal2, rf.sideOffset2, rf.i2);
if (np < JBoxSettings.maxManifoldPoints) {
return;
}
// Now clipPoints2 contains the clipped points.
if (primaryAxis.type == EPAxis.Type.EDGE_A) {
manifold.localNormal.set(rf.normal);
manifold.localPoint.set(rf.v1);
} else {
manifold.localNormal.set(polygonB.m_normals[rf.i1]);
manifold.localPoint.set(polygonB.m_vertices[rf.i1]);
}
int pointCount = 0;
for (int i = 0; i < JBoxSettings.maxManifoldPoints; ++i) {
float separation;
separation = Vec2.dot(rf.normal, temp.set(clipPoints2[i].v).subLocal(rf.v1));
if (separation <= m_radius) {
ManifoldPoint cp = manifold.points[pointCount];
if (primaryAxis.type == EPAxis.Type.EDGE_A) {
// cp.localPoint = MulT(m_xf, clipPoints2[i].v);
Transform.mulTransToOutUnsafe(m_xf, clipPoints2[i].v, cp.localPoint);
cp.id.set(clipPoints2[i].id);
} else {
cp.localPoint.set(clipPoints2[i].v);
cp.id.typeA = clipPoints2[i].id.typeB;
cp.id.typeB = clipPoints2[i].id.typeA;
cp.id.indexA = clipPoints2[i].id.indexB;
cp.id.indexB = clipPoints2[i].id.indexA;
}
++pointCount;
}
}
manifold.pointCount = pointCount;
}
public void computeEdgeSeparation(EPAxis axis) {
axis.type = EPAxis.Type.EDGE_A;
axis.index = m_front ? 0 : 1;
axis.separation = Float.MAX_VALUE;
float nx = m_normal.x;
float ny = m_normal.y;
for (int i = 0; i < m_polygonB.count; ++i) {
Vec2 v = m_polygonB.vertices[i];
float tempx = v.x - m_v1.x;
float tempy = v.y - m_v1.y;
float s = nx * tempx + ny * tempy;
if (s < axis.separation) {
axis.separation = s;
}
}
}
private final Vec2 perp = new Vec2();
private final Vec2 n = new Vec2();
public void computePolygonSeparation(EPAxis axis) {
axis.type = EPAxis.Type.UNKNOWN;
axis.index = -1;
axis.separation = -Float.MAX_VALUE;
perp.x = -m_normal.y;
perp.y = m_normal.x;
for (int i = 0; i < m_polygonB.count; ++i) {
Vec2 normalB = m_polygonB.normals[i];
Vec2 vB = m_polygonB.vertices[i];
n.x = -normalB.x;
n.y = -normalB.y;
// float s1 = Vec2.dot(n, temp.set(vB).subLocal(m_v1));
// float s2 = Vec2.dot(n, temp.set(vB).subLocal(m_v2));
float tempx = vB.x - m_v1.x;
float tempy = vB.y - m_v1.y;
float s1 = n.x * tempx + n.y * tempy;
tempx = vB.x - m_v2.x;
tempy = vB.y - m_v2.y;
float s2 = n.x * tempx + n.y * tempy;
float s = JBoxUtils.min(s1, s2);
if (s > m_radius) {
// No collision
axis.type = EPAxis.Type.EDGE_B;
axis.index = i;
axis.separation = s;
return;
}
// Adjacency
if (n.x * perp.x + n.y * perp.y >= 0.0f) {
if (Vec2.dot(temp.set(n).subLocal(m_upperLimit), m_normal) < -JBoxSettings.angularSlop) {
continue;
}
} else {
if (Vec2.dot(temp.set(n).subLocal(m_lowerLimit), m_normal) < -JBoxSettings.angularSlop) {
continue;
}
}
if (s > axis.separation) {
axis.type = EPAxis.Type.EDGE_B;
axis.index = i;
axis.separation = s;
}
}
}
}
}