public b2EPAxis ComputePolygonSeparation() { b2EPAxis axis = new b2EPAxis(); axis.type = b2EPAxis.Type.e_unknown; axis.index = -1; axis.separation = -float.MaxValue; b2Vec2 perp = new b2Vec2(-m_normal.y, m_normal.x); for (int i = 0; i < m_polygonB.count; ++i) { b2Vec2 n = -m_polygonB.normals[i]; float s1 = Utils.b2Dot(n, m_polygonB.vertices[i] - m_v1); float s2 = Utils.b2Dot(n, m_polygonB.vertices[i] - m_v2); float s = Utils.b2Min(s1, s2); if (s > m_radius) { // No collision axis.type = b2EPAxis.Type.e_edgeB; axis.index = i; axis.separation = s; return(axis); } // Adjacency if (Utils.b2Dot(n, perp) >= 0.0f) { if (Utils.b2Dot(n - m_upperLimit, m_normal) < -Settings.b2_angularSlop) { continue; } } else { if (Utils.b2Dot(n - m_lowerLimit, m_normal) < -Settings.b2_angularSlop) { continue; } } if (s > axis.separation) { axis.type = b2EPAxis.Type.e_edgeB; axis.index = i; axis.separation = s; } } return(axis); }
public b2EPAxis ComputeEdgeSeparation() { b2EPAxis axis = new b2EPAxis(); axis.type = b2EPAxis.Type.e_edgeA; axis.index = m_front ? 0 : 1; axis.separation = float.MaxValue; for (int i = 0; i < m_polygonB.count; ++i) { float s = Utils.b2Dot(m_normal, m_polygonB.vertices[i] - m_v1); if (s < axis.separation) { axis.separation = s; } } return(axis); }
// Algorithm: // 1. Classify v1 and v2 // 2. Classify polygon centroid as front or back // 3. Flip normal if necessary // 4. Initialize normal range to [-pi, pi] about face normal // 5. Adjust normal range according to adjacent edges // 6. Visit each separating axes, only accept axes within the range // 7. Return if _any_ axis indicates separation // 8. Clip public void Collide(b2Manifold manifold, b2EdgeShape edgeA, b2Transform xfA, b2PolygonShape polygonB, b2Transform xfB) { m_xf = Utils.b2MulT(xfA, xfB); m_centroidB = Utils.b2Mul(m_xf, polygonB.m_centroid); m_v0 = edgeA.m_vertex0; m_v1 = edgeA.m_vertex1; m_v2 = edgeA.m_vertex2; m_v3 = edgeA.m_vertex3; bool hasVertex0 = edgeA.m_hasVertex0; bool hasVertex3 = edgeA.m_hasVertex3; b2Vec2 edge1 = m_v2 - m_v1; edge1.Normalize(); m_normal1.Set(edge1.y, -edge1.x); float offset1 = Utils.b2Dot(m_normal1, m_centroidB - m_v1); float offset0 = 0.0f; float offset2 = 0.0f; bool convex1 = false; bool convex2 = false; // Is there a preceding edge? if (hasVertex0) { b2Vec2 edge0 = m_v1 - m_v0; edge0.Normalize(); m_normal0.Set(edge0.y, -edge0.x); convex1 = Utils.b2Cross(edge0, edge1) >= 0.0f; offset0 = Utils.b2Dot(m_normal0, m_centroidB - m_v0); } // Is there a following edge? if (hasVertex3) { b2Vec2 edge2 = m_v3 - m_v2; edge2.Normalize(); m_normal2.Set(edge2.y, -edge2.x); convex2 = Utils.b2Cross(edge1, edge2) > 0.0f; offset2 = Utils.b2Dot(m_normal2, m_centroidB - 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 = m_normal1; m_lowerLimit = m_normal0; m_upperLimit = m_normal2; } else { m_normal = -m_normal1; m_lowerLimit = -m_normal1; m_upperLimit = -m_normal1; } } else if (convex1) { m_front = offset0 >= 0.0f || (offset1 >= 0.0f && offset2 >= 0.0f); if (m_front) { m_normal = m_normal1; m_lowerLimit = m_normal0; m_upperLimit = m_normal1; } else { m_normal = -m_normal1; m_lowerLimit = -m_normal2; m_upperLimit = -m_normal1; } } else if (convex2) { m_front = offset2 >= 0.0f || (offset0 >= 0.0f && offset1 >= 0.0f); if (m_front) { m_normal = m_normal1; m_lowerLimit = m_normal1; m_upperLimit = m_normal2; } else { m_normal = -m_normal1; m_lowerLimit = -m_normal1; m_upperLimit = -m_normal0; } } else { m_front = offset0 >= 0.0f && offset1 >= 0.0f && offset2 >= 0.0f; if (m_front) { m_normal = m_normal1; m_lowerLimit = m_normal1; m_upperLimit = m_normal1; } else { m_normal = -m_normal1; m_lowerLimit = -m_normal2; m_upperLimit = -m_normal0; } } } else if (hasVertex0) { if (convex1) { m_front = offset0 >= 0.0f || offset1 >= 0.0f; if (m_front) { m_normal = m_normal1; m_lowerLimit = m_normal0; m_upperLimit = -m_normal1; } else { m_normal = -m_normal1; m_lowerLimit = m_normal1; m_upperLimit = -m_normal1; } } else { m_front = offset0 >= 0.0f && offset1 >= 0.0f; if (m_front) { m_normal = m_normal1; m_lowerLimit = m_normal1; m_upperLimit = -m_normal1; } else { m_normal = -m_normal1; m_lowerLimit = m_normal1; m_upperLimit = -m_normal0; } } } else if (hasVertex3) { if (convex2) { m_front = offset1 >= 0.0f || offset2 >= 0.0f; if (m_front) { m_normal = m_normal1; m_lowerLimit = -m_normal1; m_upperLimit = m_normal2; } else { m_normal = -m_normal1; m_lowerLimit = -m_normal1; m_upperLimit = m_normal1; } } else { m_front = offset1 >= 0.0f && offset2 >= 0.0f; if (m_front) { m_normal = m_normal1; m_lowerLimit = -m_normal1; m_upperLimit = m_normal1; } else { m_normal = -m_normal1; m_lowerLimit = -m_normal2; m_upperLimit = m_normal1; } } } else { m_front = offset1 >= 0.0f; if (m_front) { m_normal = m_normal1; m_lowerLimit = -m_normal1; m_upperLimit = -m_normal1; } else { m_normal = -m_normal1; m_lowerLimit = m_normal1; m_upperLimit = m_normal1; } } // Get polygonB in frameA m_polygonB.count = polygonB.m_count; for (int i = 0; i < polygonB.m_count; ++i) { m_polygonB.vertices[i] = Utils.b2Mul(m_xf, polygonB.m_vertices[i]); m_polygonB.normals[i] = Utils.b2Mul(m_xf.q, polygonB.m_normals[i]); } m_radius = polygonB.m_radius + edgeA.m_radius; manifold.pointCount = 0; b2EPAxis edgeAxis = ComputeEdgeSeparation(); // If no valid normal can be found than this edge should not collide. if (edgeAxis.type == b2EPAxis.Type.e_unknown) { return; } if (edgeAxis.separation > m_radius) { return; } b2EPAxis polygonAxis = ComputePolygonSeparation(); if (polygonAxis.type != b2EPAxis.Type.e_unknown && polygonAxis.separation > m_radius) { return; } // Use hysteresis for jitter reduction. const float k_relativeTol = 0.98f; const float k_absoluteTol = 0.001f; b2EPAxis primaryAxis = new b2EPAxis(); if (polygonAxis.type == b2EPAxis.Type.e_unknown) { primaryAxis = edgeAxis; } else if (polygonAxis.separation > k_relativeTol * edgeAxis.separation + k_absoluteTol) { primaryAxis = polygonAxis; } else { primaryAxis = edgeAxis; } b2ClipVertex[] ie = Arrays.InitializeWithDefaultInstances <b2ClipVertex>(2); b2ReferenceFace rf = new b2ReferenceFace(); if (primaryAxis.type == b2EPAxis.Type.e_edgeA) { manifold.type = b2Manifold.Type.e_faceA; // Search for the polygon normal that is most anti-parallel to the edge normal. int bestIndex = 0; float bestValue = Utils.b2Dot(m_normal, m_polygonB.normals[0]); for (int i = 1; i < m_polygonB.count; ++i) { float value = Utils.b2Dot(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; ie[0].v = m_polygonB.vertices[i1]; ie[0].id.cf.indexA = 0; ie[0].id.cf.indexB = (byte)i1; ie[0].id.cf.typeA = (int)b2ContactFeature.Type.e_face; ie[0].id.cf.typeB = (int)b2ContactFeature.Type.e_vertex; ie[1].v = m_polygonB.vertices[i2]; ie[1].id.cf.indexA = 0; ie[1].id.cf.indexB = (byte)i2; ie[1].id.cf.typeA = (int)b2ContactFeature.Type.e_face; ie[1].id.cf.typeB = (int)b2ContactFeature.Type.e_vertex; if (m_front) { rf.i1 = 0; rf.i2 = 1; rf.v1 = m_v1; rf.v2 = m_v2; rf.normal = m_normal1; } else { rf.i1 = 1; rf.i2 = 0; rf.v1 = m_v2; rf.v2 = m_v1; rf.normal = -m_normal1; } } else { manifold.type = b2Manifold.Type.e_faceB; ie[0].v = m_v1; ie[0].id.cf.indexA = 0; ie[0].id.cf.indexB = (byte)primaryAxis.index; ie[0].id.cf.typeA = (int)b2ContactFeature.Type.e_vertex; ie[0].id.cf.typeB = (int)b2ContactFeature.Type.e_face; ie[1].v = m_v2; ie[1].id.cf.indexA = 0; ie[1].id.cf.indexB = (byte)primaryAxis.index; ie[1].id.cf.typeA = (int)b2ContactFeature.Type.e_vertex; ie[1].id.cf.typeB = (int)b2ContactFeature.Type.e_face; rf.i1 = primaryAxis.index; rf.i2 = rf.i1 + 1 < m_polygonB.count ? rf.i1 + 1 : 0; rf.v1 = m_polygonB.vertices[rf.i1]; rf.v2 = m_polygonB.vertices[rf.i2]; rf.normal = m_polygonB.normals[rf.i1]; } rf.sideNormal1.Set(rf.normal.y, -rf.normal.x); rf.sideNormal2 = -rf.sideNormal1; rf.sideOffset1 = Utils.b2Dot(rf.sideNormal1, rf.v1); rf.sideOffset2 = Utils.b2Dot(rf.sideNormal2, rf.v2); // Clip incident edge against extruded edge1 side edges. b2ClipVertex[] clipPoints1 = Arrays.InitializeWithDefaultInstances <b2ClipVertex>(2); b2ClipVertex[] clipPoints2 = Arrays.InitializeWithDefaultInstances <b2ClipVertex>(2); int np; // Clip to box side 1 np = Utils.b2ClipSegmentToLine(clipPoints1, ie, rf.sideNormal1, rf.sideOffset1, rf.i1); if (np < Settings.b2_maxManifoldPoints) { return; } // Clip to negative box side 1 np = Utils.b2ClipSegmentToLine(clipPoints2, clipPoints1, rf.sideNormal2, rf.sideOffset2, rf.i2); if (np < Settings.b2_maxManifoldPoints) { return; } // Now clipPoints2 contains the clipped points. if (primaryAxis.type == b2EPAxis.Type.e_edgeA) { manifold.localNormal = rf.normal; manifold.localPoint = rf.v1; } else { manifold.localNormal = polygonB.m_normals[rf.i1]; manifold.localPoint = polygonB.m_vertices[rf.i1]; } int pointCount = 0; for (int i = 0; i < Settings.b2_maxManifoldPoints; ++i) { float separation; separation = Utils.b2Dot(rf.normal, clipPoints2[i].v - rf.v1); if (separation <= m_radius) { b2ManifoldPoint cp = manifold.points[pointCount]; if (primaryAxis.type == b2EPAxis.Type.e_edgeA) { cp.localPoint = Utils.b2MulT(m_xf, clipPoints2[i].v); cp.id = clipPoints2[i].id; } else { cp.localPoint = clipPoints2[i].v; cp.id.cf.typeA = clipPoints2[i].id.cf.typeB; cp.id.cf.typeB = clipPoints2[i].id.cf.typeA; cp.id.cf.indexA = clipPoints2[i].id.cf.indexB; cp.id.cf.indexB = clipPoints2[i].id.cf.indexA; } ++pointCount; } } manifold.pointCount = pointCount; }