public static unsafe void ValidateEdge(this NativeHull hull, NativeHalfEdge *edge) { Debug.Assert(hull.faceCount > 0); Debug.Assert(hull.edgeCount > 0); Debug.Assert(edge->twin != -1); NativeHalfEdge *curTwin = hull.edges + edge->twin; int edgeIndex = (int)(edge - hull.edges); Debug.Assert(curTwin->twin == edgeIndex, "The twin of the edge twin must be the edge itself"); Debug.Assert(math.abs(edge->twin - edgeIndex) == 1, "The two edges must be close by one index."); Debug.Assert(hull.edges[edge->prev].next == edgeIndex, "The twin of the edge twin must be the edge"); Debug.Assert(edge->origin != curTwin->origin, "Edges and their twin must point to each others' origin vertex"); int count = 0; NativeHalfEdge *start = edge; do { NativeHalfEdge *next = hull.edges + edge->next; NativeHalfEdge *twin = hull.edges + next->twin; edge = twin; Debug.Assert(edge->face != -1, "All edges must have a face index"); bool infiniteLoop = count > hull.edgeCount; if (count > hull.edgeCount) { Debug.Assert(true, "Possible infinite Edge Loop"); break; } ++count; }while (edge != start); }
public static unsafe void ValidateFace(this NativeHull hull, NativeFace *face) { Debug.Assert(hull.faceCount > 0); Debug.Assert(hull.edgeCount > 0); Debug.Assert(face->edge != -1); ValidateEdge(hull, hull.edges + face->edge); }
public static void DrawBasicHull(NativeHull hull1, RigidTransform t) { foreach (var edge in hull1.GetEdges()) { var a = math.transform(t, edge.GetOrigin(hull1)); var b = math.transform(t, edge.GetTwinOrigin(hull1)); Debug.DrawLine(a, b, Color.black); } }
public static CollisionInfo GetDebugCollisionInfo(RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { CollisionInfo result = default; QueryFaceDistance(out result.Face1, transform1, hull1, transform2, hull2); QueryFaceDistance(out result.Face2, transform2, hull2, transform1, hull1); QueryEdgeDistance(out result.Edge, transform1, hull1, transform2, hull2); result.IsColliding = result.Face1.Distance < 0 && result.Face2.Distance < 0 && result.Edge.Distance < 0; return(result); }
public static unsafe void Validate(this NativeHull hull) { Debug.Assert(hull.faceCount > 0); Debug.Assert(hull.edgeCount > 0); for (int i = 0; i < hull.faceCount; ++i) { ValidateFace(hull, hull.faces + i); } }
public static void DrawFaceWithOutline(int faceIndex, RigidTransform t, NativeHull hull, Color fillColor, Color outlineColor) { var verts = hull.GetVertices(faceIndex).Select(cp => (Vector3)cp).ToArray(); var tVerts = new List <Vector3>(); for (int i = 0; i < verts.Length; i++) { var v = math.transform(t, verts[i]); tVerts.Add(v); var nextIndex = i + 1 < verts.Length ? i + 1 : 0; var next = math.transform(t, verts[nextIndex]); Debug.DrawLine(v, next, outlineColor); } DebugDrawer.DrawAAConvexPolygon(tVerts.ToArray(), fillColor); }
public static unsafe int GetSupportIndex(this NativeHull hull, float3 direction) { int index = 0; float max = dot(direction, hull.vertices[index]); for (int i = 1; i < hull.vertexCount; ++i) { float dot = math.dot(direction, hull.vertices[i]); if (dot > max) { index = i; max = dot; } } return(index); }
public static bool IsCollision(RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { FaceQueryResult faceQuery; QueryFaceDistance(out faceQuery, transform1, hull1, transform2, hull2); if (faceQuery.Distance > 0) { return(false); } QueryFaceDistance(out faceQuery, transform2, hull2, transform1, hull1); if (faceQuery.Distance > 0) { return(false); } QueryEdgeDistance(out EdgeQueryResult edgeQuery, transform1, hull1, transform2, hull2); if (edgeQuery.Distance > 0) { return(false); } return(true); }
public static int CreateFaceContact(ref NativeManifold output, int referenceFaceIndex, RigidTransform transform1, NativeHull hull1, int incidentFaceIndex, RigidTransform transform2, NativeHull hull2) { Debug.Assert(output.IsCreated); var refPlane = hull1.GetPlane(referenceFaceIndex); NativePlane referencePlane = transform1 * refPlane; NativeList <ClipPlane> clippingPlanes = new NativeList <ClipPlane>((int)hull1.FaceCount, Allocator.Temp); GetClippingPlanes(ref clippingPlanes, referencePlane, referenceFaceIndex, transform1, hull1); // Create face polygon. NativeList <ClipVertex> incidentPolygon = new NativeList <ClipVertex>((int)hull1.VertexCount, Allocator.Temp); ComputeFaceClippingPolygon(ref incidentPolygon, incidentFaceIndex, transform2, hull2); // Clip face polygon against the clipping planes. for (int i = 0; i < clippingPlanes.Length; ++i) { NativeList <ClipVertex> outputPolygon = new NativeList <ClipVertex>((int)hull1.VertexCount, Allocator.Temp); Clip(clippingPlanes[i], ref incidentPolygon, ref outputPolygon); if (outputPolygon.Length == 0) { return(-1); } incidentPolygon.Dispose(); incidentPolygon = outputPolygon; } for (int i = 0; i < incidentPolygon.Length; ++i) { ClipVertex vertex = incidentPolygon[i]; float distance = referencePlane.Distance(vertex.position); output.Add(vertex.position, distance, new ContactID { FeaturePair = vertex.featurePair }); } clippingPlanes.Dispose(); incidentPolygon.Dispose(); return(incidentFaceIndex); }
public static int CreateFaceContact2(ref NativeManifold output, int referenceFaceIndex, RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { // todo, clean this up, i'm reversing the args here so that closest face is the one clipped instead of incident. Debug.Assert(output.IsCreated); NativePlane referencePlane = transform1 * hull1.GetPlane(referenceFaceIndex); var incidentFaceIndex = ComputeIncidentFaceIndex(referencePlane, transform2, hull2); return(CreateFaceContact(ref output, incidentFaceIndex, transform2, hull2, referenceFaceIndex, transform1, hull1)); }
public static bool NativeHullHullContact(out NativeManifold output, RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { output = new NativeManifold(Allocator.Persistent); // todo: collect faces and put them in combined manifold for (int i = 0; i < hull2.FaceCount; i++) { var tmp = new NativeManifold(Allocator.Temp); CreateFaceContact2(ref tmp, i, transform2, hull2, transform1, hull1); HullDrawingUtility.DebugDrawManifold(tmp); tmp.Dispose(); } for (int i = 0; i < hull1.FaceCount; i++) { var tmp = new NativeManifold(Allocator.Temp); CreateFaceContact2(ref tmp, i, transform1, hull1, transform2, hull2); HullDrawingUtility.DebugDrawManifold(tmp); tmp.Dispose(); } return(true); }
public static unsafe void b3CreateEdgeContact(ref NativeManifold output, EdgeQueryResult input, RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { Debug.Assert(output.IsCreated); ContactPoint cp = default; if (input.Index1 < 0 || input.Index2 < 0) { return; } NativeHalfEdge *edge1 = hull1.GetEdgePtr(input.Index1); NativeHalfEdge *twin1 = hull1.GetEdgePtr(edge1->Twin); float3 P1 = math.transform(transform1, hull1.GetVertex(edge1->Origin)); float3 Q1 = math.transform(transform1, hull1.GetVertex(twin1->Origin)); float3 E1 = Q1 - P1; NativeHalfEdge *edge2 = hull2.GetEdgePtr(input.Index2); NativeHalfEdge *twin2 = hull2.GetEdgePtr(edge2->Twin); float3 P2 = math.transform(transform1, hull2.GetVertex(edge2->Origin)); float3 Q2 = math.transform(transform1, hull2.GetVertex(twin2->Origin)); float3 E2 = Q2 - P2; float3 normal = math.normalize(math.cross(Q1 - P1, Q2 - P2)); float3 C2C1 = transform2.pos - transform1.pos; if (math.dot(normal, C2C1) < 0) { // Flip output.Normal = -normal; cp.Id.FeaturePair.InEdge1 = (sbyte)input.Index2; cp.Id.FeaturePair.OutEdge1 = (sbyte)(input.Index2 + 1); cp.Id.FeaturePair.InEdge2 = (sbyte)(input.Index1 + 1); cp.Id.FeaturePair.OutEdge2 = (sbyte)input.Index1; } else { output.Normal = normal; cp.Id.FeaturePair.InEdge1 = (sbyte)input.Index1; cp.Id.FeaturePair.OutEdge1 = (sbyte)(input.Index1 + 1); cp.Id.FeaturePair.InEdge2 = (sbyte)(input.Index2 + 1); cp.Id.FeaturePair.OutEdge2 = (sbyte)input.Index2; } // Compute the closest points between the two edges (center point of penetration) ClosestPointsSegmentSegment(P1, Q1, P2, Q2, out float3 C1, out float3 C2); float3 position = 0.5f * (C1 + C2); //// the closest points on each hull //cp.positionOnTarget = Math3d.ProjectPointOnLineSegment(P2, Q2, C2); //cp.positionOnSource = Math3d.ProjectPointOnLineSegment(P1, Q1, C1); cp.Penetration = C1 - C2; cp.Position = position; cp.Distance = input.Distance; output.Add(cp); }
public static unsafe NativeHull CreateFromMesh(Mesh mesh) { var faces = new List <DetailedFaceDef>(); var verts = mesh.vertices.Select(RoundVertex).ToArray(); var uniqueVerts = verts.Distinct().ToList(); var indices = mesh.triangles; // Create faces from Triangles and collapse multiple vertices with same position into shared vertices. for (int i = 0; i < mesh.triangles.Length; i = i + 3) { var idx1 = i; var idx2 = i + 1; var idx3 = i + 2; Vector3 p1 = verts[indices[idx1]]; Vector3 p2 = verts[indices[idx2]]; Vector3 p3 = verts[indices[idx3]]; var normal = normalize(cross(p3 - p2, p1 - p2)); // Round normal so that faces with only slight variances can be grouped properly together. var roundedNormal = RoundVertex(normal); faces.Add(new DetailedFaceDef { Center = ((p1 + p2 + p3) / 3), Normal = roundedNormal, Verts = new List <float3> { p1, p2, p3 }, Indices = new List <int> { uniqueVerts.IndexOf(p1), uniqueVerts.IndexOf(p2), uniqueVerts.IndexOf(p3) } }); } var faceDefs = new List <NativeFaceDef>(); var orphanIndices = new HashSet <int>(); // Merge all faces with the same normal and shared vertex var mergedFaces = GroupBySharedVertex(GroupByNormal(faces)); foreach (var faceGroup in mergedFaces) { var indicesFromMergedFaces = faceGroup.SelectMany(face => face.Indices).ToArray(); // Collapse points inside the new combined face by using only the border vertices. var border = PolygonPerimeter.CalculatePerimeter(indicesFromMergedFaces, ref uniqueVerts); var borderIndices = border.Select(b => b.EndIndex).ToArray(); foreach (var idx in indicesFromMergedFaces.Except(borderIndices)) { orphanIndices.Add(idx); } var v = stackalloc int[borderIndices.Length]; int max = 0; for (int i = 0; i < borderIndices.Length; i++) { var idx = borderIndices[i]; if (idx > max) { max = idx; } v[i] = idx; } faceDefs.Add(new NativeFaceDef { highestIndex = max, vertexCount = borderIndices.Length, vertices = v, }); } // Remove vertices with no edges connected to them and fix all impacted face vertex references. foreach (var orphanIdx in orphanIndices.OrderByDescending(i => i)) { uniqueVerts.RemoveAt(orphanIdx); foreach (var face in faceDefs.Where(f => f.highestIndex >= orphanIdx)) { for (int i = 0; i < face.vertexCount; i++) { var faceVertIdx = face.vertices[i]; if (faceVertIdx >= orphanIdx) { face.vertices[i] = --faceVertIdx; } } } } var result = new NativeHull(); using (var faceNative = new NativeArray <NativeFaceDef>(faceDefs.ToArray(), Allocator.Temp)) using (var vertsNative = new NativeArray <float3>(uniqueVerts.ToArray(), Allocator.Temp)) { NativeHullDef hullDef; hullDef.vertexCount = vertsNative.Length; hullDef.verticesNative = vertsNative; hullDef.faceCount = faceNative.Length; hullDef.facesNative = faceNative; SetFromFaces(ref result, hullDef); } result._isCreated = 1; return(result); }
public ref NativeHalfEdge GetTwin(NativeHull hull) => ref hull.GetEdgeRef(Twin);
public ref NativeHalfEdge GetPrev(NativeHull hull) => ref hull.GetEdgeRef(Prev);
public ref float3 GetNextOrigin(NativeHull hull) => ref GetNext(hull).GetOrigin(hull);
public static IEnumerable <float3> GetVertices(this NativeHull hull) { return(new HullAllEdgesEnumerator(hull).Select(v => v.GetOrigin(hull))); }
public static IEnumerable <float3> GetVertices(this NativeHull hull, int faceIndex) { return(new HullFaceEdgesEnumerator(hull, faceIndex).Select(v => v.GetOrigin(hull))); }
public static unsafe void QueryFaceDistance(out FaceQueryResult result, RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { // Perform computations in the local space of the second hull. RigidTransform transform = math.mul(math.inverse(transform2), transform1); result.Distance = -float.MaxValue; result.Index = -1; for (int i = 0; i < hull1.FaceCount; ++i) { NativePlane plane = transform * hull1.GetPlane(i); float3 support = hull2.GetSupport(-plane.Normal); float distance = plane.Distance(support); if (distance > result.Distance) { result.Distance = distance; result.Index = i; } } }
public static unsafe NativeHull CreateBox(float3 scale) { float3[] cubeVertices = { new float3(1, 1, -1), new float3(-1, 1, -1), new float3(-1, -1, -1), new float3(1, -1, -1), new float3(1, 1, 1), new float3(-1, 1, 1), new float3(-1, -1, 1), new float3(1, -1, 1), }; for (int i = 0; i < 8; ++i) { cubeVertices[i].x *= scale.x; cubeVertices[i].y *= scale.y; cubeVertices[i].z *= scale.z; } int *left = stackalloc int[] { 1, 2, 6, 5 }; int *right = stackalloc int[] { 4, 7, 3, 0 }; int *down = stackalloc int[] { 3, 7, 6, 2 }; int *up = stackalloc int[] { 0, 1, 5, 4 }; int *back = stackalloc int[] { 4, 5, 6, 7 }; int *front = stackalloc int[] { 0, 3, 2, 1 }; NativeFaceDef[] boxFaces = { new NativeFaceDef { vertexCount = 4, vertices = left }, new NativeFaceDef { vertexCount = 4, vertices = right }, new NativeFaceDef { vertexCount = 4, vertices = down }, new NativeFaceDef { vertexCount = 4, vertices = up }, new NativeFaceDef { vertexCount = 4, vertices = back }, new NativeFaceDef { vertexCount = 4, vertices = front }, }; var result = new NativeHull(); using (var boxFacesNative = new NativeArray <NativeFaceDef>(boxFaces, Allocator.Temp)) using (var cubeVertsNative = new NativeArray <float3>(cubeVertices, Allocator.Temp)) { NativeHullDef hullDef; hullDef.vertexCount = 8; hullDef.verticesNative = cubeVertsNative; hullDef.faceCount = 6; hullDef.facesNative = boxFacesNative; SetFromFaces(ref result, hullDef); } result._isCreated = 1; return(result); }
public static unsafe void QueryEdgeDistance(out EdgeQueryResult result, RigidTransform transform1, NativeHull hull1, RigidTransform transform2, NativeHull hull2) { // Perform computations in the local space of the second hull. RigidTransform transform = math.mul(math.inverse(transform2), transform1); float3 C1 = transform.pos; result.Distance = -float.MaxValue; result.Index1 = -1; result.Index2 = -1; for (int i = 0; i < hull1.EdgeCount; i += 2) { NativeHalfEdge *edge1 = hull1.GetEdgePtr(i); NativeHalfEdge *twin1 = hull1.GetEdgePtr(i + 1); Debug.Assert(edge1->Twin == i + 1 && twin1->Twin == i); float3 P1 = math.transform(transform, hull1.GetVertex(edge1->Origin)); float3 Q1 = math.transform(transform, hull1.GetVertex(twin1->Origin)); float3 E1 = Q1 - P1; float3 U1 = math.rotate(transform, hull1.GetPlane(edge1->Face).Normal); float3 V1 = math.rotate(transform, hull1.GetPlane(twin1->Face).Normal); for (int j = 0; j < hull2.EdgeCount; j += 2) { NativeHalfEdge *edge2 = hull2.GetEdgePtr(j); NativeHalfEdge *twin2 = hull2.GetEdgePtr(j + 1); Debug.Assert(edge2->Twin == j + 1 && twin2->Twin == j); float3 P2 = hull2.GetVertex(edge2->Origin); float3 Q2 = hull2.GetVertex(twin2->Origin); float3 E2 = Q2 - P2; float3 U2 = hull2.GetPlane(edge2->Face).Normal; float3 V2 = hull2.GetPlane(twin2->Face).Normal; if (IsMinkowskiFace(U1, V1, -E1, -U2, -V2, -E2)) { float distance = Project(P1, E1, P2, E2, C1); if (distance > result.Distance) { result.Index1 = i; result.Index2 = j; result.Distance = distance; } } } } }
public ref NativeFace GetFace(NativeHull hull) => ref hull.GetFaceRef(Face);
public ref NativeHalfEdge GetNext(NativeHull hull) => ref hull.GetEdgeRef(Next);
/// <summary> /// Populates a list with transformed face planes /// </summary> public static unsafe void GetClippingPlanes(ref NativeList <ClipPlane> output, NativePlane facePlane, int faceIndex, RigidTransform transform, NativeHull hull) { Debug.Assert(output.IsCreated); for (int i = 0; i < hull.FaceCount; i++) { var p = hull.GetPlane(i); output.Add(new ClipPlane { plane = transform * p, }); } }
public ref float3 GetOrigin(NativeHull hull) => ref hull.GetVertexRef(Origin);
/// <summary> /// Populates a list with transformed face vertices. /// </summary> public static unsafe void ComputeFaceClippingPolygon(ref NativeList <ClipVertex> output, int faceIndex, RigidTransform t, NativeHull hull) { Debug.Assert(output.IsCreated); NativeFace * face = hull.GetFacePtr(faceIndex); NativePlane plane = hull.GetPlane(faceIndex); NativeHalfEdge *start = hull.GetEdgePtr(face->Edge); NativeHalfEdge *current = start; do { NativeHalfEdge *twin = hull.GetEdgePtr(current->Twin); float3 vertex = hull.GetVertex(current->Origin); float3 P = math.transform(t, vertex); ClipVertex clipVertex; clipVertex.featurePair.InEdge1 = -1; clipVertex.featurePair.OutEdge1 = -1; clipVertex.featurePair.InEdge2 = (sbyte)current->Next; clipVertex.featurePair.OutEdge2 = (sbyte)twin->Twin; clipVertex.position = P; clipVertex.hull2local = vertex; clipVertex.plane = plane; output.Add(clipVertex); current = hull.GetEdgePtr(current->Next); } while (current != start); }
public ref float3 GetTwinOrigin(NativeHull hull) => ref GetTwin(hull).GetOrigin(hull);
public unsafe static void SetFromFaces(ref NativeHull hull, NativeHullDef def) { Debug.Assert(def.faceCount > 0); Debug.Assert(def.vertexCount > 0); hull.vertexCount = def.vertexCount; var arr = def.verticesNative.ToArray(); hull.verticesNative = new NativeArrayNoLeakDetection <float3>(arr, Allocator.Persistent); hull.vertices = (float3 *)hull.verticesNative.GetUnsafePtr(); hull.faceCount = def.faceCount; hull.facesNative = new NativeArrayNoLeakDetection <NativeFace>(hull.faceCount, Allocator.Persistent); hull.faces = (NativeFace *)hull.facesNative.GetUnsafePtr(); // Initialize all faces by assigning -1 to each edge reference. for (int k = 0; k < def.faceCount; ++k) { NativeFace *f = hull.faces + k; f->edge = -1; } CreateFacesPlanes(ref hull, ref def); var edgeMap = new Dictionary <(int v1, int v2), int>(); var edgesList = new NativeHalfEdge[10000]; // todo lol // Loop through all faces. for (int i = 0; i < def.faceCount; ++i) { NativeFaceDef face = def.facesNative[i]; int vertCount = face.vertexCount; Debug.Assert(vertCount >= 3); int *vertices = face.vertices; var faceHalfEdges = new List <int>(); // Loop through all face edges. for (int j = 0; j < vertCount; ++j) { int v1 = vertices[j]; int v2 = j + 1 < vertCount ? vertices[j + 1] : vertices[0]; bool edgeFound12 = edgeMap.TryGetValue((v1, v2), out int iter12); bool edgeFound21 = edgeMap.ContainsKey((v2, v1)); Debug.Assert(edgeFound12 == edgeFound21); if (edgeFound12) { // The edge is shared by two faces. int e12 = iter12; // Link adjacent face to edge. if (edgesList[e12].face == -1) { edgesList[e12].face = i; } else { throw new Exception("Two shared edges can't have the same vertices in the same order"); } if (hull.faces[i].edge == -1) { hull.faces[i].edge = e12; } faceHalfEdges.Add(e12); } else { // The next edge of the current half edge in the array is the twin edge. int e12 = hull.edgeCount++; int e21 = hull.edgeCount++; if (hull.faces[i].edge == -1) { hull.faces[i].edge = e12; } faceHalfEdges.Add(e12); edgesList[e12].prev = -1; edgesList[e12].next = -1; edgesList[e12].twin = e21; edgesList[e12].face = i; edgesList[e12].origin = v1; edgesList[e21].prev = -1; edgesList[e21].next = -1; edgesList[e21].twin = e12; edgesList[e21].face = -1; edgesList[e21].origin = v2; // Add edges to map. edgeMap[(v1, v2)] = e12;
public ref NativeHalfEdge AsRef(NativeHull hull) => ref GetTwin(hull).GetTwin(hull);
/// <summary> /// Finds the index to the least parallel face on the other hull. /// </summary> /// <param name="facePlane"></param> /// <param name="transform"></param> /// <param name="hull"></param> /// <returns></returns> public static unsafe int ComputeIncidentFaceIndex(NativePlane facePlane, RigidTransform transform, NativeHull hull) { int faceIndex = 0; float min = math.dot(facePlane.Normal, (transform * hull.GetPlane(faceIndex)).Normal); for (int i = 1; i < hull.FaceCount; ++i) { float dot = math.dot(facePlane.Normal, (transform * hull.GetPlane(i)).Normal); if (dot < min) { min = dot; faceIndex = i; } } return(faceIndex); }