// // Reference implementations of queries using simple brute-force methods // static unsafe float RefConvexConvexDistance(ref ConvexHull a, ref ConvexHull b, MTransform aFromB) { bool success = false; if (a.NumVertices + b.NumVertices < 64) // too slow without burst { // Build the minkowski difference in a-space int maxNumVertices = a.NumVertices * b.NumVertices; Aabb aabb = Aabb.Empty; for (int iB = 0; iB < b.NumVertices; iB++) { float3 vertexB = Math.Mul(aFromB, b.Vertices[iB]); for (int iA = 0; iA < a.NumVertices; iA++) { float3 vertexA = a.Vertices[iA]; aabb.Include(vertexA - vertexB); } } ConvexHullBuilderStorage diffStorage = new ConvexHullBuilderStorage(maxNumVertices, Allocator.Temp, aabb, 0.0f, ConvexHullBuilder.IntResolution.Low); ref ConvexHullBuilder diff = ref diffStorage.Builder; success = true; for (int iB = 0; iB < b.NumVertices; iB++) { float3 vertexB = Math.Mul(aFromB, b.Vertices[iB]); for (int iA = 0; iA < a.NumVertices; iA++) { float3 vertexA = a.Vertices[iA]; diff.AddPoint(vertexA - vertexB, (uint)(iA | iB << 16)); } } float distance = 0.0f; if (success && diff.Dimension == 3) { // Find the closest triangle to the origin distance = float.MaxValue; bool penetrating = true; foreach (int t in diff.Triangles.Indices) { ConvexHullBuilder.Triangle triangle = diff.Triangles[t]; float3 v0 = diff.Vertices[triangle.GetVertex(0)].Position; float3 v1 = diff.Vertices[triangle.GetVertex(1)].Position; float3 v2 = diff.Vertices[triangle.GetVertex(2)].Position; float3 n = diff.ComputePlane(t).Normal; DistanceQueries.Result result = DistanceQueries.TriangleSphere(v0, v1, v2, n, float3.zero, 0.0f, MTransform.Identity); if (result.Distance < distance) { distance = result.Distance; } penetrating = penetrating & (math.dot(n, -result.NormalInA) < 0.0f); // only penetrating if inside of all planes } if (penetrating) { distance = -distance; } distance -= a.ConvexRadius + b.ConvexRadius; } else { success = false; } diffStorage.Dispose(); if (success) { return(distance); } }
// // Reference implementations of queries using simple brute-force methods // static unsafe float RefConvexConvexDistance(ref ConvexHull a, ref ConvexHull b, MTransform aFromB) { // Build the minkowski difference in a-space int maxNumVertices = a.NumVertices * b.NumVertices; ConvexHullBuilder diff = new ConvexHullBuilder(maxNumVertices, 2 * maxNumVertices, Allocator.Temp); bool success = true; Aabb aabb = Aabb.Empty; for (int iB = 0; iB < b.NumVertices; iB++) { float3 vertexB = Math.Mul(aFromB, b.Vertices[iB]); for (int iA = 0; iA < a.NumVertices; iA++) { float3 vertexA = a.Vertices[iA]; aabb.Include(vertexA - vertexB); } } diff.IntegerSpaceAabb = aabb; for (int iB = 0; iB < b.NumVertices; iB++) { float3 vertexB = Math.Mul(aFromB, b.Vertices[iB]); for (int iA = 0; iA < a.NumVertices; iA++) { float3 vertexA = a.Vertices[iA]; if (!diff.AddPoint(vertexA - vertexB, (uint)(iA | iB << 16))) { // TODO - coplanar vertices are tripping up ConvexHullBuilder, we should fix it but for now fall back to DistanceQueries.ConvexConvex() success = false; } } } float distance; if (!success || diff.Triangles.GetFirstIndex() == -1) { // No triangles unless the difference is 3D, fall back to GJK // Most of the time this happens for cases like sphere-sphere, capsule-capsule, etc. which have special implementations, // so comparing those to GJK still validates the results of different API queries against each other. distance = DistanceQueries.ConvexConvex(ref a, ref b, aFromB).Distance; } else { // Find the closest triangle to the origin distance = float.MaxValue; bool penetrating = true; for (int t = diff.Triangles.GetFirstIndex(); t != -1; t = diff.Triangles.GetNextIndex(t)) { ConvexHullBuilder.Triangle triangle = diff.Triangles[t]; float3 v0 = diff.Vertices[triangle.GetVertex(0)].Position; float3 v1 = diff.Vertices[triangle.GetVertex(1)].Position; float3 v2 = diff.Vertices[triangle.GetVertex(2)].Position; float3 n = diff.ComputePlane(t).Normal; DistanceQueries.Result result = DistanceQueries.TriangleSphere(v0, v1, v2, n, float3.zero, 0.0f, MTransform.Identity); if (result.Distance < distance) { distance = result.Distance; } penetrating = penetrating & (math.dot(n, -result.NormalInA) < 0.0f); // only penetrating if inside of all planes } if (penetrating) { distance = -distance; } distance -= a.ConvexRadius + b.ConvexRadius; } diff.Dispose(); return(distance); }
private void UpdateMeshNow() { MassProperties = Hull.ComputeMassProperties(); Hull.CompactVertices(); Hull.BuildFaceIndices((float)(MaxFaceAngle * System.Math.PI / 180)); { HullData.Faces = new Face[Hull.NumFaces]; HullData.FaceVertices = new int[Hull.NumFaceVertices]; int nextVertex = 0; for (var faceEdge = Hull.GetFirstFace(); faceEdge.IsValid; faceEdge = Hull.GetNextFace(faceEdge)) { var triangleIndex = ((ConvexHullBuilder.Edge)faceEdge).TriangleIndex; Face newFace = new Face { Plane = Hull.ComputePlane(triangleIndex), FirstVertex = nextVertex, NumVertices = 0 }; for (var edge = faceEdge; edge.IsValid; edge = Hull.GetNextFaceEdge(edge)) { HullData.FaceVertices[nextVertex++] = Hull.StartVertex(edge); } newFace.NumVertices = nextVertex - newFace.FirstVertex; HullData.Faces[Hull.Triangles[triangleIndex].FaceIndex] = newFace; } var indices = new List <int>(); var set = new List <int>(); HullData.VertexFaces = new VertexFaces[Hull.Vertices.PeakCount]; for (int i = 0; i < Hull.Vertices.PeakCount; ++i) { var cardinality = Hull.Vertices[i].Cardinality; var edge = Hull.GetVertexEdge(i); for (int j = Hull.Vertices[i].Cardinality; j > 0; --j) { int faceIndex = Hull.Triangles[edge.TriangleIndex].FaceIndex; if (set.IndexOf(faceIndex) == -1) { set.Add(faceIndex); } edge = Hull.GetLinkedEdge(edge).Next; } set.Sort(); HullData.VertexFaces[i] = new VertexFaces { FirstFace = indices.Count, NumFaces = set.Count }; indices.AddRange(set); set.Clear(); } HullData.FaceIndices = indices.ToArray(); } Vector3[] vertices = null; int[] triangles = null; switch (Hull.Dimension) { case 2: vertices = new Vector3[Hull.Vertices.PeakCount]; triangles = new int[(Hull.Vertices.PeakCount - 2) * 2 * 3]; for (int i = 0; i < Hull.Vertices.PeakCount; ++i) { vertices[i] = Hull.Vertices[i].Position; } for (int i = 2; i < Hull.Vertices.PeakCount; ++i) { int j = (i - 2) * 6; triangles[j + 0] = 0; triangles[j + 1] = i - 1; triangles[j + 2] = i; triangles[j + 3] = 0; triangles[j + 4] = i; triangles[j + 5] = i - 1; } break; case 3: vertices = new Vector3[Hull.Triangles.PeakCount * 3]; triangles = new int[Hull.Triangles.PeakCount * 3]; for (int i = 0; i < Hull.Triangles.PeakCount; ++i) { if (Hull.Triangles[i].IsAllocated) { vertices[i * 3 + 0] = Hull.Vertices[Hull.Triangles[i].Vertex0].Position; vertices[i * 3 + 1] = Hull.Vertices[Hull.Triangles[i].Vertex1].Position; vertices[i * 3 + 2] = Hull.Vertices[Hull.Triangles[i].Vertex2].Position; triangles[i * 3 + 0] = i * 3 + 0; triangles[i * 3 + 1] = i * 3 + 1; triangles[i * 3 + 2] = i * 3 + 2; } else { triangles[i * 3 + 0] = 0; triangles[i * 3 + 1] = 0; triangles[i * 3 + 2] = 0; } } break; } var mesh = GetComponent <MeshFilter>().sharedMesh; mesh.Clear(); mesh.vertices = vertices; mesh.triangles = triangles; mesh.RecalculateBounds(); mesh.RecalculateNormals(); mesh.RecalculateTangents(); }
/// <summary> /// Generalized convex-convex distance. /// </summary> /// <param name="verticesA">Vertices of the first collider in local space</param> /// <param name="verticesB">Vertices of the second collider in local space</param> /// <param name="aFromB">Transform from the local space of B to the local space of A</param> /// <param name="penetrationHandling">How to compute penetration.</param> /// <returns></returns> public static unsafe Result ConvexConvex( float3 *verticesA, int numVerticesA, float3 *verticesB, int numVerticesB, MTransform aFromB, PenetrationHandling penetrationHandling) { const float epsTerminationSq = 1e-8f; // Main loop quits when it cannot find a point that improves the simplex by at least this much const float epsPenetrationSq = 1e-9f; // Epsilon used to check for penetration. Should be smaller than shape cast ConvexConvex keepDistance^2. // Initialize simplex. Simplex simplex = new Simplex(); simplex.NumVertices = 1; simplex.A = GetSupportingVertex(new float3(1, 0, 0), verticesA, numVerticesA, verticesB, numVerticesB, aFromB); simplex.Direction = simplex.A.Xyz; simplex.ScaledDistance = math.lengthsq(simplex.A.Xyz); float scaleSq = simplex.ScaledDistance; // Iterate. int iteration = 0; bool penetration = false; const int maxIterations = 64; for (; iteration < maxIterations; ++iteration) { // Find a new support vertex SupportVertex newSv = GetSupportingVertex(-simplex.Direction, verticesA, numVerticesA, verticesB, numVerticesB, aFromB); // If the new vertex is not significantly closer to the origin, quit float scaledImprovement = math.dot(simplex.A.Xyz - newSv.Xyz, simplex.Direction); if (scaledImprovement * math.abs(scaledImprovement) < epsTerminationSq * scaleSq) { break; } // Add the new vertex and reduce the simplex switch (simplex.NumVertices++) { case 1: simplex.B = newSv; break; case 2: simplex.C = newSv; break; default: simplex.D = newSv; break; } simplex.SolveDistance(); // Check for penetration scaleSq = math.lengthsq(simplex.Direction); float scaledDistanceSq = simplex.ScaledDistance * simplex.ScaledDistance; if (simplex.NumVertices == 4 || scaledDistanceSq <= epsPenetrationSq * scaleSq) { penetration = true; break; } } // Finalize result. var ret = new Result { Iterations = iteration + 1 }; // Handle penetration. if (penetration && penetrationHandling != PenetrationHandling.DotNotCompute) { // Allocate a hull for EPA int verticesCapacity = 64; int triangleCapacity = 2 * verticesCapacity; ConvexHullBuilder.Vertex * vertices = stackalloc ConvexHullBuilder.Vertex[verticesCapacity]; ConvexHullBuilder.Triangle *triangles = stackalloc ConvexHullBuilder.Triangle[triangleCapacity]; var hull = new ConvexHullBuilder(vertices, verticesCapacity, triangles, triangleCapacity); // Initialize int space // TODO - either the hull should be robust when int space changes after points are already added, or the ability to do so should be removed, probably the latter // Currently for example a valid triangle can collapse to a line segment when the bounds grow hull.IntegerSpaceAabb = GetSupportingAabb(verticesA, numVerticesA, verticesB, numVerticesB, aFromB); // Add simplex vertices to the hull, remove any vertices from the simplex that do not increase the hull dimension hull.AddPoint(simplex.A.Xyz, simplex.A.Id); if (simplex.NumVertices > 1) { hull.AddPoint(simplex.B.Xyz, simplex.B.Id); if (simplex.NumVertices > 2) { int dimension = hull.Dimension; hull.AddPoint(simplex.C.Xyz, simplex.C.Id); if (dimension == 0 && hull.Dimension == 1) { simplex.B = simplex.C; } if (simplex.NumVertices > 3) { dimension = hull.Dimension; hull.AddPoint(simplex.D.Xyz, simplex.D.Id); if (dimension > hull.Dimension) { if (dimension == 0) { simplex.B = simplex.D; } else if (dimension == 1) { simplex.C = simplex.D; } } } } } simplex.NumVertices = (hull.Dimension + 1); // If the simplex is not 3D, try expanding the hull in all directions while (hull.Dimension < 3) { // Choose expansion directions float3 support0, support1, support2; switch (simplex.NumVertices) { case 1: support0 = new float3(1, 0, 0); support1 = new float3(0, 1, 0); support2 = new float3(0, 0, 1); break; case 2: Math.CalculatePerpendicularNormalized(math.normalize(simplex.B.Xyz - simplex.A.Xyz), out support0, out support1); support2 = float3.zero; break; default: UnityEngine.Assertions.Assert.IsTrue(simplex.NumVertices == 3); support0 = math.cross(simplex.B.Xyz - simplex.A.Xyz, simplex.C.Xyz - simplex.A.Xyz); support1 = float3.zero; support2 = float3.zero; break; } // Try each one int numSupports = 4 - simplex.NumVertices; bool success = false; for (int i = 0; i < numSupports; i++) { for (int j = 0; j < 2; j++) // +/- each direction { SupportVertex vertex = GetSupportingVertex(support0, verticesA, numVerticesA, verticesB, numVerticesB, aFromB); hull.AddPoint(vertex.Xyz, vertex.Id); if (hull.Dimension == simplex.NumVertices) { switch (simplex.NumVertices) { case 1: simplex.B = vertex; break; case 2: simplex.C = vertex; break; default: simplex.D = vertex; break; } // Next dimension success = true; simplex.NumVertices++; i = numSupports; break; } support0 = -support0; } support0 = support1; support1 = support2; } if (!success) { break; } } // We can still fail to build a tetrahedron if the minkowski difference is really flat. // In those cases just find the closest point to the origin on the infinite extension of the simplex (point / line / plane) if (hull.Dimension != 3) { switch (simplex.NumVertices) { case 1: { ret.ClosestPoints.Distance = math.length(simplex.A.Xyz); ret.ClosestPoints.NormalInA = -math.normalizesafe(simplex.A.Xyz, new float3(1, 0, 0)); break; } case 2: { float3 edge = math.normalize(simplex.B.Xyz - simplex.A.Xyz); float3 direction = math.cross(math.cross(edge, simplex.A.Xyz), edge); Math.CalculatePerpendicularNormalized(edge, out float3 safeNormal, out float3 unused); // backup, take any direction perpendicular to the edge float3 normal = math.normalizesafe(direction, safeNormal); ret.ClosestPoints.Distance = math.dot(normal, simplex.A.Xyz); ret.ClosestPoints.NormalInA = -normal; break; } default: { UnityEngine.Assertions.Assert.IsTrue(simplex.NumVertices == 3); float3 normal = math.normalize(math.cross(simplex.B.Xyz - simplex.A.Xyz, simplex.C.Xyz - simplex.A.Xyz)); float dot = math.dot(normal, simplex.A.Xyz); ret.ClosestPoints.Distance = math.abs(dot); ret.ClosestPoints.NormalInA = math.select(-normal, normal, dot < 0); break; } } } else { int closestTriangleIndex; Plane closestPlane = new Plane(); float stopThreshold = 1e-4f; uint *uidsCache = stackalloc uint[triangleCapacity]; for (int i = 0; i < triangleCapacity; i++) { uidsCache[i] = 0; } float *distancesCache = stackalloc float[triangleCapacity]; do { // Select closest triangle. closestTriangleIndex = -1; foreach (int triangleIndex in hull.Triangles.Indices) { if (hull.Triangles[triangleIndex].Uid != uidsCache[triangleIndex]) { uidsCache[triangleIndex] = hull.Triangles[triangleIndex].Uid; distancesCache[triangleIndex] = hull.ComputePlane(triangleIndex).Distance; } if (closestTriangleIndex == -1 || distancesCache[closestTriangleIndex] < distancesCache[triangleIndex]) { closestTriangleIndex = triangleIndex; } } closestPlane = hull.ComputePlane(closestTriangleIndex); // Add supporting vertex or exit. SupportVertex sv = GetSupportingVertex(closestPlane.Normal, verticesA, numVerticesA, verticesB, numVerticesB, aFromB); float d2P = math.dot(closestPlane.Normal, sv.Xyz) + closestPlane.Distance; if (math.abs(d2P) > stopThreshold && hull.AddPoint(sv.Xyz, sv.Id)) { stopThreshold *= 1.3f; } else { break; } } while (++iteration < maxIterations); // Generate simplex. ConvexHullBuilder.Triangle triangle = hull.Triangles[closestTriangleIndex]; simplex.NumVertices = 3; simplex.A.Xyz = hull.Vertices[triangle.Vertex0].Position; simplex.A.Id = hull.Vertices[triangle.Vertex0].UserData; simplex.B.Xyz = hull.Vertices[triangle.Vertex1].Position; simplex.B.Id = hull.Vertices[triangle.Vertex1].UserData; simplex.C.Xyz = hull.Vertices[triangle.Vertex2].Position; simplex.C.Id = hull.Vertices[triangle.Vertex2].UserData; simplex.Direction = -closestPlane.Normal; simplex.ScaledDistance = closestPlane.Distance; // Set normal and distance. ret.ClosestPoints.NormalInA = -closestPlane.Normal; ret.ClosestPoints.Distance = closestPlane.Distance; } } else { // Compute distance and normal. float lengthSq = math.lengthsq(simplex.Direction); float invLength = math.rsqrt(lengthSq); bool smallLength = lengthSq < 1e-10f; ret.ClosestPoints.Distance = math.select(simplex.ScaledDistance * invLength, 0.0f, smallLength); ret.ClosestPoints.NormalInA = math.select(simplex.Direction * invLength, new float3(1, 0, 0), smallLength); // Make sure the normal is always valid. if (!math.all(math.isfinite(ret.ClosestPoints.NormalInA))) { ret.ClosestPoints.NormalInA = new float3(1, 0, 0); } } // Compute position. float3 closestPoint = ret.ClosestPoints.NormalInA * ret.ClosestPoints.Distance; float4 coordinates = simplex.ComputeBarycentricCoordinates(closestPoint); ret.ClosestPoints.PositionOnAinA = verticesA[simplex.A.IdA] * coordinates.x + verticesA[simplex.B.IdA] * coordinates.y + verticesA[simplex.C.IdA] * coordinates.z + verticesA[simplex.D.IdA] * coordinates.w; // Encode simplex. ret.Simplex.x = simplex.A.Id; ret.Simplex.y = simplex.NumVertices >= 2 ? simplex.B.Id : Result.InvalidSimplexVertex; ret.Simplex.z = simplex.NumVertices >= 3 ? simplex.C.Id : Result.InvalidSimplexVertex; // Done. UnityEngine.Assertions.Assert.IsTrue(math.isfinite(ret.ClosestPoints.Distance)); UnityEngine.Assertions.Assert.IsTrue(math.abs(math.lengthsq(ret.ClosestPoints.NormalInA) - 1.0f) < 1e-5f); return(ret); }