// Bullet Continuous Collision Detection and Physics Library // Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ // // // Hull.cpp // // Copyright (c) 2006 Simon Hobbs // // 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. #ifdef WIN32 #if _MSC_VER >= 1310 #include "hull.h" #include "Geometry.h" #include #include "HullContactCollector.h" Hull::Hull() { m_numVerts = 0; m_numFaces = 0; m_numEdges = 0; m_pVerts = 0; m_pFaces = 0; m_pEdges = 0; m_pPlanes = 0; } Hull::~Hull() { delete[] m_pVerts; delete[] m_pFaces; delete[] m_pEdges; delete[] m_pPlanes; } Point3 Hull::GetFaceCentroid(short face) const { short edge; edge = GetFaceFirstEdge(face); Vector3 c = Vector3(getVertex(GetEdgeVertex0(face, edge))); for (edge = GetFaceNextEdge(face, edge); edge >= 0; edge = GetFaceNextEdge(face, edge)) c += Vector3(getVertex(GetEdgeVertex0(face, edge))); c /= Scalar(GetFace(face).m_numEdges); return Point3(c); } // helper stuff for MakeHull short Hull::s_firstFreeTmpFace = 0; short Hull::s_firstUsedTmpFace = 0; Hull::TmpFace* Hull::s_pTmpFaces = 0; short Hull::s_firstFreeTmpEdge = 0; short Hull::s_firstUsedTmpEdge = 0; Hull::TmpEdge* Hull::s_pTmpEdges = 0; const Point3* Hull::s_pPoints = 0; short Hull::AllocTmpFace() { short face = s_firstFreeTmpFace; assert(face != -1); TmpFace* pFace = GetTmpFace(face); s_firstFreeTmpFace = pFace->m_next; pFace->m_next = s_firstUsedTmpFace; s_firstUsedTmpFace = face; for (int i = 0; i < kTmpFaceMaxVerts; i++) pFace->m_verts[i] = pFace->m_edges[i] = -1; pFace->m_plane = Maths::Zero; return face; } void Hull::FreeTmpFace(short face) { assert(face >= 0); TmpFace* pFace = GetTmpFace(face); if (face == s_firstUsedTmpFace) s_firstUsedTmpFace = pFace->m_next; else { TmpFace* pPrev; for (pPrev = GetTmpFace(s_firstUsedTmpFace); pPrev->m_next != face; pPrev = GetTmpFace(pPrev->m_next)) { } pPrev->m_next = pFace->m_next; } pFace->m_next = s_firstFreeTmpFace; s_firstFreeTmpFace = face; } short Hull::AllocTmpEdge() { short edge = s_firstFreeTmpEdge; assert(edge != -1); TmpEdge* pEdge = GetTmpEdge(edge); s_firstFreeTmpEdge = pEdge->m_next; pEdge->m_next = s_firstUsedTmpEdge; s_firstUsedTmpEdge = edge; pEdge->m_verts[0] = pEdge->m_verts[1] = -1; pEdge->m_faces[0] = pEdge->m_faces[1] = -1; return edge; } void Hull::FreeTmpEdge(short edge) { assert(edge >= 0); TmpEdge* pEdge = GetTmpEdge(edge); if (edge == s_firstUsedTmpEdge) s_firstUsedTmpEdge = pEdge->m_next; else { TmpEdge* pPrev; for ( pPrev= GetTmpEdge(s_firstUsedTmpEdge); pPrev->m_next != edge; pPrev = GetTmpEdge(pPrev->m_next)) { } pPrev->m_next = pEdge->m_next; } pEdge->m_next = s_firstFreeTmpEdge; s_firstFreeTmpEdge = edge; } short Hull::MatchOrAddEdge(short vert0, short vert1, short face) { assert(vert0 >= 0); assert(vert1 >= 0); assert(vert0 != vert1); TmpEdge* pEdge; // see if edge already exists for (pEdge = GetTmpEdge(s_firstUsedTmpEdge); pEdge; pEdge = GetTmpEdge(pEdge->m_next)) { if (pEdge->m_verts[0] == vert0 && pEdge->m_verts[1] == vert1) { assert(pEdge->m_faces[0] == -1); pEdge->m_faces[0] = face; return pEdge->m_index; } else if (pEdge->m_verts[0] == vert1 && pEdge->m_verts[1] == vert0) { assert(pEdge->m_faces[1] == -1); pEdge->m_faces[1] = face; return pEdge->m_index; } } // doesn't exist so add new face short edge = AllocTmpEdge(); assert(edge >= 0); pEdge = GetTmpEdge(edge); pEdge->m_verts[0] = vert0; pEdge->m_verts[1] = vert1; pEdge->m_faces[0] = face; return edge; } void Hull::UnmatchOrRemoveEdge(short edge, short face) { assert(edge >= 0); TmpEdge* pEdge = GetTmpEdge(edge); if (pEdge->m_faces[0] == face) { pEdge->m_faces[0] = -1; } else { assert(pEdge->m_faces[1] == face); pEdge->m_faces[1] = -1; } // if edge is now redundant then free it if (pEdge->m_faces[0] == -1 && pEdge->m_faces[1] == -1) FreeTmpEdge(edge); } short Hull::AddTmpFace(short vert0, short vert1, short vert2) { short verts[3] = {vert0, vert1, vert2}; return AddTmpFace(3, verts); } short Hull::AddTmpFace(short vert0, short numOtherVerts, short* pVerts) { short verts[256]; verts[0] = vert0; for (short i = 0; i < numOtherVerts; i++) verts[i + 1] = pVerts[i]; return AddTmpFace(numOtherVerts + 1, verts); } short Hull::AddTmpFace(short numVerts, short* pVerts) { assert(numVerts >= 3); assert(numVerts <= kTmpFaceMaxVerts); assert(pVerts); short face = AllocTmpFace(); assert(face >= 0); TmpFace* pFace = GetTmpFace(face); pFace->m_numVerts = numVerts; for (short i = 0; i < numVerts; i++) { pFace->m_verts[i] = pVerts[i]; pFace->m_edges[i] = MatchOrAddEdge(pVerts[i], pVerts[(i + 1) % numVerts], face); } pFace->m_plane = Plane(s_pPoints[pVerts[0]], s_pPoints[pVerts[1]], s_pPoints[pVerts[2]]); return face; } void Hull::RemoveTmpFace(short face) { assert(face >= 0); TmpFace* pFace = GetTmpFace(face); for (short i = 0; i < pFace->m_numVerts; i++) UnmatchOrRemoveEdge(pFace->m_edges[i], face); FreeTmpFace(face); } bool Hull::TmpFaceAddPoint(short point, short face) { assert(face >= 0); TmpFace* pFace = GetTmpFace(face); // vertex limit reached? if (pFace->m_numVerts == kTmpFaceMaxVerts) return false; // in same plane? if (Abs(Dot(pFace->m_plane, s_pPoints[point])) > 0.001f) return false; // remove last edge UnmatchOrRemoveEdge(pFace->m_edges[pFace->m_numVerts - 1], face); // add 2 new edges MatchOrAddEdge(pFace->m_verts[pFace->m_numVerts - 1], point, face); MatchOrAddEdge(point, pFace->m_verts[0], face); // add new vertex pFace->m_verts[pFace->m_numVerts++] = point; return true; } // The resulting hull will not contain interior points // Note that this is a cheap and cheerful implementation that can only handle // reasonably small point sets. However, except for the final hull it doesn't do any dynamic // memory allocation - all the work happens on the stack. Hull* Hull::MakeHull(int numPoints, const Point3* pPoints) { assert(numPoints >= 4); assert(pPoints); // check first 4 points are disjoint // TODO: make it search points so it can cope better with this assert(Length(pPoints[1] - pPoints[0]) > 0.01f); assert(Length(pPoints[2] - pPoints[0]) > 0.01f); assert(Length(pPoints[3] - pPoints[0]) > 0.01f); assert(Length(pPoints[2] - pPoints[1]) > 0.01f); assert(Length(pPoints[3] - pPoints[1]) > 0.01f); assert(Length(pPoints[3] - pPoints[2]) > 0.01f); s_pPoints = pPoints; // put all temp faces on a free list TmpFace tmpFaces[kMaxFaces]; s_firstFreeTmpFace = 0; s_firstUsedTmpFace = -1; s_pTmpFaces = tmpFaces; for (short i = 0; i < kMaxEdges; i++) { tmpFaces[i].m_index = i; tmpFaces[i].m_next = i + 1; } tmpFaces[kMaxFaces - 1].m_next = -1; // put all temp edges on a free list TmpEdge tmpEdges[kMaxEdges]; s_firstFreeTmpEdge = 0; s_firstUsedTmpEdge = -1; s_pTmpEdges = tmpEdges; for (short i = 0; i < kMaxEdges; i++) { tmpEdges[i].m_index = i; tmpEdges[i].m_next = i + 1; } tmpEdges[kMaxEdges - 1].m_next = -1; // make initial tetrahedron Plane plane = Plane(pPoints[0], pPoints[1], pPoints[2]); Scalar dot = Dot(plane, pPoints[3]); assert(Abs(dot) > 0.01f); // first 4 points are co-planar if (IsNegative(dot)) { AddTmpFace(0, 1, 2); AddTmpFace(1, 0, 3); AddTmpFace(0, 2, 3); AddTmpFace(2, 1, 3); } else { AddTmpFace(2, 1, (short)0); AddTmpFace(1, 2, 3); AddTmpFace(2, 0, 3); AddTmpFace(0, 1, 3); } // merge all remaining points for (int i = 4; i < numPoints; i++) if (RemoveVisibleFaces(pPoints[i]) > 0) FillHole(i); return MakeHullFromTemp(); } int Hull::RemoveVisibleFaces(const Point3& point) { // test point for containment in current hull int numRemoved = 0; TmpFace* pNextFace; for (TmpFace* pFace = GetTmpFace(s_firstUsedTmpFace); pFace; pFace = pNextFace) { pNextFace = GetTmpFace(pFace->m_next); if (Dot(pFace->m_plane, point) > -0.001f) { RemoveTmpFace(pFace->m_index); numRemoved++; } } return numRemoved; } void Hull::FillHole(short newVertex) { // gather unmatched edges (they form the silhouette of the hole) short edgeVert0[kMaxEdges]; short edgeVert1[kMaxEdges]; int numEdges = 0; for (TmpEdge* pEdge = GetTmpEdge(s_firstUsedTmpEdge); pEdge; pEdge = GetTmpEdge(pEdge->m_next)) { if (pEdge->m_faces[0] == -1) { assert(numEdges < kMaxEdges); edgeVert0[numEdges] = pEdge->m_verts[0]; edgeVert1[numEdges] = pEdge->m_verts[1]; numEdges++; } else if (pEdge->m_faces[1] == -1) { assert(numEdges < kMaxEdges); edgeVert0[numEdges] = pEdge->m_verts[1]; edgeVert1[numEdges] = pEdge->m_verts[0]; numEdges++; } } // extract vertex winding by sorting edges short verts[kMaxEdges + 1]; // +1 for repeat of first vertex verts[0] = edgeVert0[0]; verts[1] = edgeVert1[0]; short numVerts = 2; while (numVerts < (numEdges + 1)) { for (int i = 0; i < numEdges; i++) { // if leading vertex of edge matches our last vertex if (edgeVert0[i] == verts[numVerts - 1]) { // then add trailing vertex of edge to our vertex list verts[numVerts++] = edgeVert1[i]; } } } // fill the hole int i = 1; while (i < numVerts) { // make plane for first 3 vertices Plane plane = Plane(s_pPoints[newVertex], s_pPoints[verts[i - 1]], s_pPoints[verts[i]]); // any subequent vertices that lie in the same plane are also added int numFaceVerts = 2; for (int j = i + 1; j < numVerts; j++) { if (Abs(Dot(plane, s_pPoints[verts[j]])) > 0.001f) break; numFaceVerts++; } // add the polygon AddTmpFace(newVertex, numFaceVerts, verts + i - 1); // skip to next i += numFaceVerts - 1; } } // make hull from temporary working structures Hull* Hull::MakeHullFromTemp() { Hull* pHull = new Hull(); // count and index faces short index = 0; for (TmpFace* pFace = GetTmpFace(s_firstUsedTmpFace); pFace; pFace = GetTmpFace(pFace->m_next)) { pFace->m_index = index++; pHull->m_numFaces++; } // count and index edges, also count and index used vertices short vertIndexes[kMaxVerts]; for (int i = 0; i < kMaxVerts; i++) vertIndexes[i] = -1; index = 0; for (TmpEdge* pEdge = GetTmpEdge(s_firstUsedTmpEdge); pEdge; pEdge = GetTmpEdge(pEdge->m_next)) { pEdge->m_index = index++; pHull->m_numEdges++; if (vertIndexes[pEdge->m_verts[0]] == -1) vertIndexes[pEdge->m_verts[0]] = pHull->m_numVerts++; if (vertIndexes[pEdge->m_verts[1]] == -1) vertIndexes[pEdge->m_verts[1]] = pHull->m_numVerts++; } // allocate hull arrays pHull->m_pVerts = new Point3[pHull->m_numVerts]; pHull->m_pEdges = new Hull::Edge[pHull->m_numEdges]; pHull->m_pFaces = new Hull::Face[pHull->m_numFaces]; pHull->m_pPlanes = new Plane[pHull->m_numFaces]; // fill in hull arrays // verts: for (int i = 0; i < kMaxVerts; i++) if (vertIndexes[i] != -1) pHull->m_pVerts[vertIndexes[i]] = s_pPoints[i]; // edges: index = 0; for (TmpEdge* pEdge = GetTmpEdge(s_firstUsedTmpEdge); pEdge; pEdge = GetTmpEdge(pEdge->m_next)) { pHull->m_pEdges[index].m_faces[0] = GetTmpFace(pEdge->m_faces[0])->m_index; pHull->m_pEdges[index].m_faces[1] = GetTmpFace(pEdge->m_faces[1])->m_index; pHull->m_pEdges[index].m_verts[0] = vertIndexes[pEdge->m_verts[0]]; pHull->m_pEdges[index].m_verts[1] = vertIndexes[pEdge->m_verts[1]]; index++; } // faces: index = 0; for (TmpFace* pFace = GetTmpFace(s_firstUsedTmpFace); pFace; pFace = GetTmpFace(pFace->m_next)) { // set edge count pHull->m_pFaces[index].m_numEdges = pFace->m_numVerts; // make linked list of face edges short prevEdge = GetTmpEdge(pFace->m_edges[0])->m_index; pHull->m_pFaces[index].m_firstEdge = prevEdge; for (int i = 1; i < pFace->m_numVerts; i++) { Edge& e = pHull->m_pEdges[prevEdge]; prevEdge = GetTmpEdge(pFace->m_edges[i])->m_index; e.m_nextEdge[pFace->m_index == e.m_faces[1]] = prevEdge; } Edge& e = pHull->m_pEdges[prevEdge]; e.m_nextEdge[pFace->m_index == e.m_faces[1]] = -1; // set plane pHull->m_pPlanes[index] = pFace->m_plane; index++; } return pHull; } void Hull::ProcessHullHull(btSeparation& sep,const Hull& shapeA,const Hull& shapeB,const Transform& trA,const Transform& trB,HullContactCollector* collector) { Point3 vertsA[Hull::kMaxVerts]; Point3 vertsB[Hull::kMaxVerts]; // const Hull& shapeA((const Hull&)*sep.m_pBodyA->GetShape()); // const Hull& shapeB((const Hull&)*sep.m_pBodyB->GetShape()); // Transform trA(sep.m_pBodyA->GetTransform()); // Transform trB(sep.m_pBodyB->GetTransform()); // transform verts of A to world space Point3* pVertsA = vertsA; for (short v = 0; v < shapeA.m_numVerts; v++) pVertsA[v] = shapeA.m_pVerts[v] * trA; // transform verts of B to world space Point3* pVertsB = vertsB; for (short v = 0; v < shapeB.m_numVerts; v++) pVertsB[v] = shapeB.m_pVerts[v] * trB; #ifdef SHAPE_COLLIDER_USE_CACHING // update cached pair if (sep.m_separator != btSeparation::kFeatureNone) { // re-use the separation if (UpdateSeparationHullHull(sep, pVertsA, pVertsB, trA, trB) == true) return; // also re-use penetration if only slight if (sep.m_dist > -0.02f) { // except if no contacts were generated, in which case we continue through to full test if (AddContactsHullHull(sep, pVertsA, pVertsB, trA, trB,shapeA,shapeB) > 0) return; } } #endif if (GetSeparationHullHull(sep, pVertsA, pVertsB, trA, trB,shapeA,shapeB) == false) { AddContactsHullHull(sep, pVertsA, pVertsB, trA, trB,shapeA,shapeB,collector); } } void Hull::ComputeInertia(const Transform& transform, Point3& centerOfMass, Matrix33& inertia, float totalMass) const { assert(totalMass > 0.0f); // order: 1, x, y, z, x^2, y^2, z^2, xy, yz, zx float integral[10] = {0,0,0,0,0,0,0,0,0,0}; // for each triangle for (short face = 0; face < m_numFaces; face++) { short edge = GetFaceFirstEdge(face); Point3 v0 = m_pVerts[ GetEdgeVertex0(face, edge) ] * transform; edge = GetFaceNextEdge(face, edge); Point3 v1 = m_pVerts[ GetEdgeVertex0(face, edge) ] * transform; for (edge = GetFaceNextEdge(face, edge); edge != -1; edge = GetFaceNextEdge(face, edge)) { Point3 v2 = m_pVerts[ GetEdgeVertex0(face, edge) ] * transform; // get cross product of triangle edges Vector3 d = Cross(v2 - v0, v1 - v0); // compute integral terms Vector3 w0 = Vector3(v0); Vector3 w1 = Vector3(v1); Vector3 w2 = Vector3(v2); Vector3 temp0 = w0 + w1; Vector3 f1 = temp0 + w2; Vector3 temp1 = w0 * w0; Vector3 temp2 = temp1 + w1 * temp0; Vector3 f2 = temp2 + w2 * f1; Vector3 f3 = w0 * temp1 + w1 * temp2 + w2 * f2; Vector3 g0 = f2 + w0 * (f1 + w0); Vector3 g1 = f2 + w1 * (f1 + w1); Vector3 g2 = f2 + w2 * (f1 + w2); // update integrals integral[0] += d[0] * f1[0]; integral[1] += d[0] * f2[0]; integral[2] += d[1] * f2[1]; integral[3] += d[2] * f2[2]; integral[4] += d[0] * f3[0]; integral[5] += d[1] * f3[1]; integral[6] += d[2] * f3[2]; integral[7] += d[0] * (v0[1] * g0[0] + v1[1] * g1[0] + v2[1] * g2[0]); integral[8] += d[1] * (v0[2] * g0[1] + v1[2] * g1[1] + v2[2] * g2[1]); integral[9] += d[2] * (v0[0] * g0[2] + v1[0] * g1[2] + v2[0] * g2[2]); // next edge v1 = v2; } } integral[0] *= 1.0f / 6.0f; integral[1] *= 1.0f / 24.0f; integral[2] *= 1.0f / 24.0f; integral[3] *= 1.0f / 24.0f; integral[4] *= 1.0f / 60.0f; integral[5] *= 1.0f / 60.0f; integral[6] *= 1.0f / 60.0f; integral[7] *= 1.0f / 120.0f; integral[8] *= 1.0f / 120.0f; integral[9] *= 1.0f / 120.0f; // scale all integrals to get desired total mass assert(integral[0] > 0.0f); float invMassRatio = totalMass / integral[0]; for (int i = 0; i < 10; i++) integral[i] *= invMassRatio; // center of mass centerOfMass = Point3(integral[1] / totalMass, integral[2] / totalMass, integral[3] / totalMass); // inertia relative to world inertia[0][0] = integral[5] + integral[6]; inertia[0][1] = -integral[7]; inertia[0][2] = -integral[9]; inertia[1][0] = -integral[7]; inertia[1][1] = integral[4] + integral[6]; inertia[1][2] = -integral[8]; inertia[2][0] = -integral[9]; inertia[2][1] = -integral[8]; inertia[2][2] = integral[5] + integral[5]; // inertia relative to center of mass inertia[0][0] -= totalMass * (centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2]); inertia[0][1] += totalMass * centerOfMass[0] * centerOfMass[1]; inertia[0][2] += totalMass * centerOfMass[2] * centerOfMass[0]; inertia[1][0] += totalMass * centerOfMass[0] * centerOfMass[1]; inertia[1][1] -= totalMass * (centerOfMass[2] * centerOfMass[2] + centerOfMass[0] * centerOfMass[0]); inertia[1][2] += totalMass * centerOfMass[1] * centerOfMass[2]; inertia[2][0] += totalMass * centerOfMass[2] * centerOfMass[0]; inertia[2][1] += totalMass * centerOfMass[1] * centerOfMass[2]; inertia[2][2] -= totalMass * (centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1]); } Bounds3 Hull::ComputeBounds(const Transform& transform) const { Bounds3 b(m_pVerts[0] * transform); for (int i = 1; i < m_numVerts; i++) b += m_pVerts[i] * transform; return b; } // Clips a face to the back of a plane int Hull::ClipFace(int numVerts, Point3** ppVtxIn, Point3** ppVtxOut, const Plane& plane) { int ve, numVertsOut; Point3 *pVtxOut, *pVtxS, *pVtxE; Scalar ds, de; if (numVerts == 0) return 0; pVtxE = *ppVtxIn; pVtxS = pVtxE + numVerts - 1; pVtxOut = *ppVtxOut; Scalar zero(0.0f); ds = Dot(plane, *pVtxS); for (ve = 0; ve < numVerts; ve++, pVtxE++) { de = Dot(plane, *pVtxE); if (ds <= zero) { *pVtxOut++ = *pVtxS; if (de > zero) *pVtxOut++ = Lerp(*pVtxS, *pVtxE, ds * RcpNr(ds - de)); } else if (de <= zero) *pVtxOut++ = Lerp(*pVtxS, *pVtxE, ds * RcpNr(ds - de)); if (ve == 0) pVtxS = *ppVtxIn; else pVtxS++; ds = de; } numVertsOut = pVtxOut - *ppVtxOut; // swap in and out arrays ready for next time pVtxOut = *ppVtxIn; *ppVtxIn = *ppVtxOut; *ppVtxOut = pVtxOut; return numVertsOut; } int Hull::AddContactsHullHull(btSeparation& sep, const Point3* pVertsA, const Point3* pVertsB, const Transform& trA, const Transform& trB,const Hull& hullA,const Hull& hullB, HullContactCollector* hullContactCollector) { const int maxContacts = hullContactCollector->GetMaxNumContacts(); Vector3 normalWorld = sep.m_axis; // edge->edge contact is always a single point if (sep.m_separator == btSeparation::kFeatureBoth) { const Hull::Edge& edgeA = hullA.getEdge(sep.m_featureA); const Hull::Edge& edgeB = hullB.getEdge(sep.m_featureB); float ta, tb; Line la(pVertsA[edgeA.m_verts[0]], pVertsA[edgeA.m_verts[1]]); Line lb(pVertsB[edgeB.m_verts[0]], pVertsB[edgeB.m_verts[1]]); Intersect(la, lb, ta, tb); #ifdef VALIDATE_CONTACT_POINTS AssertPointInsideHull(contact.m_points[0].m_pos, trA, hullA); AssertPointInsideHull(contact.m_points[0].m_pos, trB, hullB); #endif Point3 posWorld = Lerp(la.m_start, la.m_end, ta); float depth = -sep.m_dist; Vector3 tangent = Normalize(pVertsA[edgeA.m_verts[1]] - pVertsA[edgeA.m_verts[0]]); sep.m_contact = hullContactCollector->BatchAddContactGroup(sep,1,normalWorld,tangent,&posWorld,&depth); } // face->face contact is polygon else { short faceA = sep.m_featureA; short faceB = sep.m_featureB; Vector3 tangent; // find face of hull A that is most opposite contact axis // TODO: avoid having to transform planes here if (sep.m_separator == btSeparation::kFeatureB) { const Hull::Edge& edgeB = hullB.getEdge(hullB.GetFaceFirstEdge(faceB)); tangent = Normalize(pVertsB[edgeB.m_verts[1]] - pVertsB[edgeB.m_verts[0]]); Scalar dmin = Scalar::Consts::MaxValue; for (short face = 0; face < hullA.m_numFaces; face++) { Vector3 normal = hullA.getPlane(face).GetNormal() * trA; Scalar d = Dot(normal, sep.m_axis); if (d < dmin) { dmin = d; faceA = face; } } } else { const Hull::Edge& edgeA = hullA.getEdge(hullA.GetFaceFirstEdge(faceA)); tangent = Normalize(pVertsA[edgeA.m_verts[1]] - pVertsA[edgeA.m_verts[0]]); Scalar dmin = Scalar::Consts::MaxValue; for (short face = 0; face < hullB.m_numFaces; face++) { Vector3 normal = hullB.getPlane(face).GetNormal() * trB; Scalar d = Dot(normal, -sep.m_axis); if (d < dmin) { dmin = d; faceB = face; } } } Point3 workspace[2][Hull::kMaxVerts]; // setup initial clip face (minimizing face from hull B) int numContacts = 0; for (short edge = hullB.GetFaceFirstEdge(faceB); edge != -1; edge = hullB.GetFaceNextEdge(faceB, edge)) workspace[0][numContacts++] = pVertsB[ hullB.GetEdgeVertex0(faceB, edge) ]; // clip polygon to back of planes of all faces of hull A that are adjacent to witness face Point3* pVtxIn = workspace[0]; Point3* pVtxOut = workspace[1]; #if 0 for (short edge = hullA.GetFaceFirstEdge(faceA); edge != -1; edge = hullA.GetFaceNextEdge(faceA, edge)) { Plane planeA = hullA.getPlane( hullA.GetEdgeOtherFace(edge, faceA) ) * trA; numContacts = ClipFace(numContacts, &pVtxIn, &pVtxOut, planeA); } #else for (short f = 0; f < hullA.GetNumFaces(); f++) { Plane planeA = hullA.getPlane(f) * trA; numContacts = ClipFace(numContacts, &pVtxIn, &pVtxOut, planeA); } #endif // only keep points that are behind the witness face Plane planeA = hullA.getPlane(faceA) * trA; float depths[Hull::kMaxVerts]; int numPoints = 0; for (int i = 0; i < numContacts; i++) { Scalar d = Dot(planeA, pVtxIn[i]); if (IsNegative(d)) { depths[numPoints] = (float)-d; pVtxIn[numPoints] = pVtxIn[i]; #ifdef VALIDATE_CONTACT_POINTS AssertPointInsideHull(pVtxIn[numPoints], trA, hullA); AssertPointInsideHull(pVtxIn[numPoints], trB, hullB); #endif numPoints++; } } //we can also use a persistentManifold/reducer class // keep maxContacts points at most if (numPoints > 0) { if (numPoints > maxContacts) { int step = (numPoints << 8) / maxContacts; numPoints = maxContacts; for (int i = 0; i < numPoints; i++) { int nth = (step * i) >> 8; depths[i] = depths[nth]; pVtxIn[i] = pVtxIn[nth]; #ifdef VALIDATE_CONTACT_POINTS AssertPointInsideHull(contact.m_points[i].m_pos, trA, hullA); AssertPointInsideHull(contact.m_points[i].m_pos, trB, hullB); #endif } } sep.m_contact = hullContactCollector->BatchAddContactGroup(sep,numPoints,normalWorld,tangent,pVtxIn,depths); } return numPoints; } // shut up compiler return 0; } // returns true if a separating axis was found // if no separating axis was found then details of least penetrating axis are returned // resulting axis always points away from hullB // either transform can be null in which case it is treated as identity (this avoids a bunch of work) bool Hull::GetSeparationHullHull(btSeparation& sep, const Point3* pVertsA, const Point3* pVertsB, const Transform& trA, const Transform& trB, const Hull& hullA, const Hull& hullB ) { //const Hull& hullA((const Hull&)*sep.m_pShapeA->GetShape()); //const Hull& hullB((const Hull&)*sep.m_pShapeB->GetShape()); sep.m_separator = btSeparation::kFeatureNone; sep.m_dist = MinValueF; sep.m_featureA = sep.m_featureB = -1; sep.m_contact = -1; // test verts of A to planes of B Scalar minDistB = Scalar::Consts::MinValue; for (short p = 0; p < hullB.m_numFaces; p++) { Plane planeWorld = hullB.m_pPlanes[p] * trB; Scalar minDist = Dot(planeWorld, pVertsA[0]); for (short v = 1; v < hullA.m_numVerts; v++) minDist = Min(minDist, Dot(planeWorld, pVertsA[v])); // keep min overlap if (minDist > minDistB) { minDistB = minDist; sep.m_featureB = p; sep.m_dist = minDist; sep.m_axis = planeWorld.GetNormal(); sep.m_separator = btSeparation::kFeatureB; } // got a separating plane? if (!IsNegative(minDist)) return true; } // test verts of B to planes of A Scalar minDistA = Scalar::Consts::MinValue; for (short p = 0; p < hullA.m_numFaces; p++) { // get plane in world space Plane planeWorld = hullA.m_pPlanes[p] * trA; // get min dist Scalar minDist = Dot(planeWorld, pVertsB[0]); for (short v = 1; v < hullB.m_numVerts; v++) minDist = Min(minDist, Dot(planeWorld, pVertsB[v])); // keep min overlap if (minDist > minDistA) { minDistA = minDist; sep.m_featureA = p; if ((float)minDist > sep.m_dist) { sep.m_dist = (float)minDist; sep.m_axis = -planeWorld.GetNormal(); sep.m_separator = btSeparation::kFeatureA; } } // got a separating plane? if (!IsNegative(minDist)) return true; } // test edge pairs on two minimizing faces short faceA( sep.m_featureA ); short faceB( sep.m_featureB ); Vector3 faceBnormal = Vector3(hullB.m_pPlanes[sep.m_featureB]) * trB; // Plane bestPlane; // Point3 bestPlanePos; for (short ea = hullA.GetFaceFirstEdge(faceA); ea != -1; ea = hullA.GetFaceNextEdge(faceA, ea)) { const Hull::Edge& edgeA = hullA.getEdge(ea); for (short eb = hullB.GetFaceFirstEdge(faceB); eb != -1; eb = hullB.GetFaceNextEdge(faceB, eb)) { const Hull::Edge& edgeB = hullB.getEdge(eb); Vector3 va = pVertsA[edgeA.m_verts[1]] - pVertsA[edgeA.m_verts[0]]; Vector3 vb = pVertsB[edgeB.m_verts[1]] - pVertsB[edgeB.m_verts[0]]; Vector3 axis = Cross(va, vb); Scalar rcpLen = LengthRcp(axis); // if the two edges have nearly equal or opposite directions then try the next pair if (IsNan(rcpLen) || rcpLen > 10000.0f) continue; axis *= rcpLen; // if axis is very close to current best axis then don't use it if (Abs(Dot(sep.m_axis, axis)) > 0.99f) continue; // ensure the axis points away from hullB if (Dot(faceBnormal, axis) < Scalar::Consts::Zero) axis = -axis; Plane plane(axis, pVertsB[edgeB.m_verts[0]]); // hull B must be entirely behind plane Scalar dmaxB = Dot(plane, pVertsB[0]); for (short v = 1; v < hullB.m_numVerts; v++) dmaxB = Max(dmaxB, Dot(plane, pVertsB[v])); if (dmaxB > 0.001f) continue; // get hull A distance from plane Scalar dminA = Dot(plane, pVertsA[0]); for (short v = 1; v < hullA.m_numVerts; v++) dminA = Min(dminA, Dot(plane, pVertsA[v])); // got a separating plane? if (!IsNegative(dminA)) { sep.m_featureA = ea; sep.m_featureB = eb; sep.m_dist = dminA; sep.m_axis = axis; sep.m_separator = btSeparation::kFeatureBoth; return true; } // keep min overlap if (dminA > sep.m_dist) { // if edge of A is in front of the plane, then we haven't identified the correct edge pair if (IsNegative(Dot(plane, pVertsA[edgeA.m_verts[0]]))) { // bestPlane = plane; // bestPlanePos = Lerp(pVertsB[edgeB.m_verts[0]], pVertsB[edgeB.m_verts[1]], 0.5f); // sep.m_featureA = ea; // sep.m_featureB = eb; sep.m_dist = dminA; sep.m_axis = axis; // sep.m_separator = btSeparation::kFeatureBoth; } } } // RenderPlane(bestPlane, bestPlanePos); } return false; } #endif //MSC_VER >= 1310 #endif //WIN32