//Help method to build a triangle and add it to a mesh
        //v1-v2-v3 should be sorted clock-wise
        //v1-v2 should be the cut edge (if we have a cut edge), and we know this triangle has a cut edge if newEdges != null
        private static void AddTriangleToMesh(MyMeshVertex v1, MyMeshVertex v2, MyMeshVertex v3, HalfEdgeData3 mesh, HashSet <HalfEdge3> newEdges)
        {
            //Create three new vertices
            HalfEdgeVertex3 half_v1 = new HalfEdgeVertex3(v1.position, v1.normal);
            HalfEdgeVertex3 half_v2 = new HalfEdgeVertex3(v2.position, v2.normal);
            HalfEdgeVertex3 half_v3 = new HalfEdgeVertex3(v3.position, v3.normal);

            //Create three new half-edges that points TO these vertices
            HalfEdge3 e_to_v1 = new HalfEdge3(half_v1);
            HalfEdge3 e_to_v2 = new HalfEdge3(half_v2);
            HalfEdge3 e_to_v3 = new HalfEdge3(half_v3);

            //Create the face (which is a triangle) which needs a reference to one of the edges
            HalfEdgeFace3 f = new HalfEdgeFace3(e_to_v1);


            //Connect the data:

            //Connect the edges clock-wise
            e_to_v1.nextEdge = e_to_v2;
            e_to_v2.nextEdge = e_to_v3;
            e_to_v3.nextEdge = e_to_v1;

            e_to_v1.prevEdge = e_to_v3;
            e_to_v2.prevEdge = e_to_v1;
            e_to_v3.prevEdge = e_to_v2;

            //Each vertex needs a reference to an edge going FROM that vertex
            half_v1.edge = e_to_v2;
            half_v2.edge = e_to_v3;
            half_v3.edge = e_to_v1;

            //Each edge needs a reference to the face
            e_to_v1.face = f;
            e_to_v2.face = f;
            e_to_v3.face = f;

            //Each edge needs an opposite edge
            //This is slow process but we need it to be able to split meshes which are not connected
            //You could do this afterwards when all triangles have been generate, but Im not sure which is the fastest...

            //Save the data
            mesh.verts.Add(half_v1);
            mesh.verts.Add(half_v2);
            mesh.verts.Add(half_v3);

            mesh.edges.Add(e_to_v1);
            mesh.edges.Add(e_to_v2);
            mesh.edges.Add(e_to_v3);

            mesh.faces.Add(f);


            //Save the new edge
            if (newEdges != null)
            {
                //We know the knew edge goes from v1 to v2, so we should save the half-edge that points to v2
                newEdges.Add(e_to_v2);
            }
        }
        //Help method to above
        private void RemoveTriangleAndConnectOppositeSides(HalfEdge3 e)
        {
            //AB is the edge we want to contract
            HalfEdge3 e_AB = e;
            HalfEdge3 e_BC = e.nextEdge;
            HalfEdge3 e_CA = e.nextEdge.nextEdge;

            //The triangle belonging to this edge
            HalfEdgeFace3 f_ABC = e.face;

            //Delete the triangle (which will also delete the vertices and edges belonging to that face)
            //We have to do it before we re-connect the edges because it sets all opposite edges to null, making a hole
            DeleteFace(f_ABC);

            //Connect the opposite edges of the edges which are not a part of the edge we want to delete
            //This will connect the opposite sides of the hole
            //We move vertices in another method
            if (e_BC.oppositeEdge != null)
            {
                //The edge on the opposite side of BC should have its opposite edge connected with the opposite edge of CA
                e_BC.oppositeEdge.oppositeEdge = e_CA.oppositeEdge;
            }
            if (e_CA.oppositeEdge != null)
            {
                e_CA.oppositeEdge.oppositeEdge = e_BC.oppositeEdge;
            }
        }
        //
        // Delete a face from this data structure
        //

        public void DeleteFace(HalfEdgeFace3 f)
        {
            //Get all edges belonging to this face
            //TODO: This creates garbage because we create a list for each face, so maybe better to move the loop to here...
            List <HalfEdge3> edgesToRemove = f.GetEdges();

            if (edgesToRemove == null)
            {
                Debug.LogWarning("This face can't be deleted because the edges are not fully connected");

                return;
            }

            foreach (HalfEdge3 edgeToRemove in edgesToRemove)
            {
                //The opposite edge to this edge is referencing this edges, so remove that connection
                if (edgeToRemove.oppositeEdge != null)
                {
                    edgeToRemove.oppositeEdge.oppositeEdge = null;
                }

                //Remove the edge and the vertex the edge points to from the list of all vertices and edges
                this.edges.Remove(edgeToRemove);
                this.verts.Remove(edgeToRemove.v);

                //Set face reference to null, which is needed for some other methods
                edgeToRemove.face = null;
            }

            //Remove the face from the list of all faces
            this.faces.Remove(f);
        }
        //
        // Add a triangle to this mesh
        //

        //We dont have a normal so we have to calculate it, so make sure v1-v2-v3 is clock-wise
        public HalfEdgeFace3 AddTriangle(Vector3 p1, Vector3 p2, Vector3 p3, bool findOppositeEdge = false)
        {
            Vector3 normal = Vector3.Normalize(Vector3.Cross(p3 - p2, p1 - p2));

            MyMeshVertex v1 = new MyMeshVertex(p1, normal);
            MyMeshVertex v2 = new MyMeshVertex(p2, normal);
            MyMeshVertex v3 = new MyMeshVertex(p3, normal);

            HalfEdgeFace3 f = AddTriangle(v1, v2, v3);

            return(f);
        }
Beispiel #5
0
        //Find a visible triangle from a point
        private static HalfEdgeFace3 FindVisibleTriangleFromPoint(Vector3 p, HashSet <HalfEdgeFace3> triangles)
        {
            HalfEdgeFace3 visibleTriangle = null;

            foreach (HalfEdgeFace3 triangle in triangles)
            {
                //A triangle is visible from a point the point is outside of a plane formed with the triangles position and normal
                Plane3 plane = new Plane3(triangle.edge.v.position, triangle.edge.v.normal);

                bool isPointOutsidePlane = _Geometry.IsPointOutsidePlane(p, plane);

                //We have found a triangle which is visible from the point and should be removed
                if (isPointOutsidePlane)
                {
                    visibleTriangle = triangle;

                    break;
                }
            }

            return(visibleTriangle);
        }
        //Separate a mesh by its islands (if it has islands)
        private static HashSet <HalfEdgeData3> SeparateMeshIslands(HalfEdgeData3 meshData)
        {
            HashSet <HalfEdgeData3> meshIslands = new HashSet <HalfEdgeData3>();

            HashSet <HalfEdgeFace3> allFaces = meshData.faces;


            //Separate by flood-filling

            //Faces belonging to a separate island
            HashSet <HalfEdgeFace3> facesOnThisIsland = new HashSet <HalfEdgeFace3>();

            //Faces we havent flodded from yet
            Queue <HalfEdgeFace3> facesToFloodFrom = new Queue <HalfEdgeFace3>();

            //Add a first face to the queue
            HalfEdgeFace3 firstFace = allFaces.FakePop();

            facesToFloodFrom.Enqueue(firstFace);

            int numberOfIslands = 0;

            List <HalfEdge3> edges = new List <HalfEdge3>();

            int safety = 0;

            while (true)
            {
                //If the queue is empty, it means we have flooded this island
                if (facesToFloodFrom.Count == 0)
                {
                    numberOfIslands += 1;

                    //Generate the new half-edge data structure from the faces that belong to this island
                    HalfEdgeData3 meshIsland = HalfEdgeData3.GenerateHalfEdgeDataFromFaces(facesOnThisIsland);

                    meshIslands.Add(meshIsland);

                    //We still have faces to visit, so they must be on a new island
                    if (allFaces.Count > 0)
                    {
                        facesOnThisIsland = new HashSet <HalfEdgeFace3>();

                        //Add a first face to the queue
                        firstFace = allFaces.FakePop();

                        facesToFloodFrom.Enqueue(firstFace);
                    }
                    else
                    {
                        Debug.Log($"This mesh has {numberOfIslands} islands");

                        break;
                    }
                }

                HalfEdgeFace3 f = facesToFloodFrom.Dequeue();

                facesOnThisIsland.Add(f);

                //Remove from the original mesh so we can identify if we need to start at a new island
                allFaces.Remove(f);

                //Find neighboring faces
                edges.Clear();

                edges.Add(f.edge);
                edges.Add(f.edge.nextEdge);
                edges.Add(f.edge.nextEdge.nextEdge);

                foreach (HalfEdge3 e in edges)
                {
                    if (e.oppositeEdge != null)
                    {
                        HalfEdgeFace3 fNeighbor = e.oppositeEdge.face;

                        //If we haven't seen this face before
                        if (!facesOnThisIsland.Contains(fNeighbor) && !facesToFloodFrom.Contains(fNeighbor))
                        {
                            facesToFloodFrom.Enqueue(fNeighbor);
                        }
                    }

                    //Here we could mabe save all edges with no opposite, meaning its an edge at the hole
                }


                safety += 1;

                if (safety > 50000)
                {
                    Debug.Log("Stuck in infinite loop when generating mesh islands");

                    break;
                }
            }

            return(meshIslands);
        }
Beispiel #7
0
        //Initialize by making 2 triangles by using three points, so its a flat triangle with a face on each side
        //We could use the ideas from Quickhull to make the start triangle as big as possible
        //Then find a point which is the furthest away as possible from these triangles
        //Add that point and you have a tetrahedron (triangular pyramid)
        public static void BuildFirstTetrahedron(HashSet <Vector3> points, HalfEdgeData3 convexHull)
        {
            //Of all points, find the two points that are furthes away from each other
            Edge3 eFurthestApart = FindEdgeFurthestApart(points);

            //Remove the two points we found
            points.Remove(eFurthestApart.p1);
            points.Remove(eFurthestApart.p2);


            //Find a point which is the furthest away from this edge
            //TODO: Is this point also on the AABB? So we don't have to search all remaining points...
            Vector3 pointFurthestAway = FindPointFurthestFromEdge(eFurthestApart, points);

            //Remove the point
            points.Remove(pointFurthestAway);


            //Display the triangle
            //Debug.DrawLine(eFurthestApart.p1.ToVector3(), eFurthestApart.p2.ToVector3(), Color.white, 1f);
            //Debug.DrawLine(eFurthestApart.p1.ToVector3(), pointFurthestAway.ToVector3(), Color.blue, 1f);
            //Debug.DrawLine(eFurthestApart.p2.ToVector3(), pointFurthestAway.ToVector3(), Color.blue, 1f);


            //Now we can build two triangles
            //It doesnt matter how we build these triangles as long as they are opposite
            //But the normal matters, so make sure it is calculated so the triangles are ordered clock-wise while the normal is pointing out
            Vector3 p1 = eFurthestApart.p1;
            Vector3 p2 = eFurthestApart.p2;
            Vector3 p3 = pointFurthestAway;

            convexHull.AddTriangle(p1, p2, p3);
            convexHull.AddTriangle(p1, p3, p2);

            //Debug.Log(convexHull.faces.Count);

            /*
             * foreach (HalfEdgeFace3 f in convexHull.faces)
             * {
             *  TestAlgorithmsHelpMethods.DebugDrawTriangle(f, Color.white, Color.red);
             * }
             */

            //Find the point which is furthest away from the triangle (this point cant be co-planar)
            List <HalfEdgeFace3> triangles = new List <HalfEdgeFace3>(convexHull.faces);

            //Just pick one of the triangles
            HalfEdgeFace3 triangle = triangles[0];

            //Build a plane
            Plane3 plane = new Plane3(triangle.edge.v.position, triangle.edge.v.normal);

            //Find the point furthest away from the plane
            Vector3 p4 = FindPointFurthestAwayFromPlane(points, plane);

            //Remove the point
            points.Remove(p4);

            //Debug.DrawLine(p1.ToVector3(), p4.ToVector3(), Color.green, 1f);
            //Debug.DrawLine(p2.ToVector3(), p4.ToVector3(), Color.green, 1f);
            //Debug.DrawLine(p3.ToVector3(), p4.ToVector3(), Color.green, 1f);

            //Now we have to remove one of the triangles == the triangle the point is outside of
            HalfEdgeFace3 triangleToRemove = triangles[0];
            HalfEdgeFace3 triangleToKeep   = triangles[1];

            //This means the point is inside the triangle-plane, so we have to switch
            //We used triangle #0 to generate the plane
            if (_Geometry.GetSignedDistanceFromPointToPlane(p4, plane) < 0f)
            {
                triangleToRemove = triangles[1];
                triangleToKeep   = triangles[0];
            }

            //Delete the triangle
            convexHull.DeleteFace(triangleToRemove);

            //Build three new triangles

            //The triangle we keep is ordered clock-wise:
            Vector3 p1_opposite = triangleToKeep.edge.v.position;
            Vector3 p2_opposite = triangleToKeep.edge.nextEdge.v.position;
            Vector3 p3_opposite = triangleToKeep.edge.nextEdge.nextEdge.v.position;

            //But we are looking at it from the back-side,
            //so we add those vertices counter-clock-wise to make the new triangles clock-wise
            convexHull.AddTriangle(p1_opposite, p3_opposite, p4);
            convexHull.AddTriangle(p3_opposite, p2_opposite, p4);
            convexHull.AddTriangle(p2_opposite, p1_opposite, p4);

            //Make sure all opposite edges are connected
            convexHull.ConnectAllEdgesSlow();

            //Debug.Log(convexHull.faces.Count);

            //Display what weve got so far
            //foreach (HalfEdgeFace3 f in convexHull.faces)
            //{
            //    TestAlgorithmsHelpMethods.DebugDrawTriangle(f, Color.white, Color.red);
            //}

            /*
             * //Now we might as well remove all the points that are within the tetrahedron because they are not on the hull
             * //But this is slow if we have many points and none of them are inside
             * HashSet<MyVector3> pointsToRemove = new HashSet<MyVector3>();
             *
             * foreach (MyVector3 p in points)
             * {
             *  bool isWithinConvexHull = _Intersections.PointWithinConvexHull(p, convexHull);
             *
             *  if (isWithinConvexHull)
             *  {
             *      pointsToRemove.Add(p);
             *  }
             * }
             *
             * Debug.Log($"Removed {pointsToRemove.Count} points because they were within the tetrahedron");
             *
             * foreach (MyVector3 p in pointsToRemove)
             * {
             *  points.Remove(p);
             * }
             */
        }
Beispiel #8
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="points">The points from which we want to build the convex hull</param>
        /// <param name="removeUnwantedTriangles">At the end of the algorithm, try to remove triangles from the hull that we dont want,
        //such as needles where one edge is much shorter than the other edges in the triangle</param>
        /// <param name="normalizer">Is only needed for debugging</param>
        /// <returns></returns>
        public static HalfEdgeData3 GenerateConvexHull(HashSet <Vector3> points, bool removeUnwantedTriangles, Normalizer3 normalizer = null)
        {
            HalfEdgeData3 convexHull = new HalfEdgeData3();

            //Step 1. Init by making a tetrahedron (triangular pyramid) and remove all points within the tetrahedron
            System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();

            //timer.Start();

            BuildFirstTetrahedron(points, convexHull);

            //timer.Stop();

            //Debug.Log($"Testrahedron {timer.ElapsedMilliseconds/1000f}");

            //Debug.Log(convexHull.faces.Count);

            //return convexHull;

            //Step 2. For each other point:
            // -If the point is within the hull constrcuted so far, remove it
            // - Otherwise, see which triangles are visible to the point and remove them
            //   Then build new triangles from the edges that have no neighbor to the point

            List <Vector3> pointsToAdd = new List <Vector3>(points);

            int removedPointsCounter = 0;

            //int debugCounter = 0;

            foreach (Vector3 p in pointsToAdd)
            {
                //Is this point within the tetrahedron
                bool isWithinHull = _Intersections.PointWithinConvexHull(p, convexHull);

                if (isWithinHull)
                {
                    points.Remove(p);

                    removedPointsCounter += 1;

                    continue;
                }


                //Find visible triangles and edges on the border between the visible and invisible triangles
                HashSet <HalfEdgeFace3> visibleTriangles = null;
                HashSet <HalfEdge3>     borderEdges      = null;

                FindVisibleTrianglesAndBorderEdgesFromPoint(p, convexHull, out visibleTriangles, out borderEdges);


                //Remove all visible triangles
                foreach (HalfEdgeFace3 triangle in visibleTriangles)
                {
                    convexHull.DeleteFace(triangle);
                }


                //Make new triangle by connecting all edges on the border with the point
                //Debug.Log($"Number of border edges: {borderEdges.Count}");
                //int debugStop = 11;

                //Save all ned edges so we can connect them with an opposite edge
                //To make it faster you can use the ideas in the Valve paper to get a sorted list of newEdges
                HashSet <HalfEdge3> newEdges = new HashSet <HalfEdge3>();

                foreach (HalfEdge3 borderEdge in borderEdges)
                {
                    //Each edge is point TO a vertex
                    Vector3 p1 = borderEdge.prevEdge.v.position;
                    Vector3 p2 = borderEdge.v.position;

                    /*
                     * if (debugCounter > debugStop)
                     * {
                     *  Debug.DrawLine(normalizer.UnNormalize(p1).ToVector3(), normalizer.UnNormalize(p2).ToVector3(), Color.white, 2f);
                     *
                     *  Debug.DrawLine(normalizer.UnNormalize(p1).ToVector3(), normalizer.UnNormalize(p).ToVector3(), Color.gray, 2f);
                     *  Debug.DrawLine(normalizer.UnNormalize(p2).ToVector3(), normalizer.UnNormalize(p).ToVector3(), Color.gray, 2f);
                     *
                     *  convexHull.AddTriangle(p2, p1, p);
                     * }
                     * else
                     * {
                     *  //Debug.Log(borderEdge.face);
                     *
                     *  convexHull.AddTriangle(p2, p1, p);
                     * }
                     */

                    //The border edge belongs to a triangle which is invisible
                    //Because triangles are oriented clockwise, we have to add the vertices in the other direction
                    //to build a new triangle with the point
                    HalfEdgeFace3 newTriangle = convexHull.AddTriangle(p2, p1, p);

                    //Connect the new triangle with the opposite edge on the border
                    //When we create the face we give it a reference edge which goes to p2
                    //So the edge we want to connect is the next edge
                    HalfEdge3 edgeToConnect = newTriangle.edge.nextEdge;

                    edgeToConnect.oppositeEdge = borderEdge;
                    borderEdge.oppositeEdge    = edgeToConnect;

                    //Two edges are still not connected, so save those
                    HalfEdge3 e1 = newTriangle.edge;
                    //HalfEdge3 e2 = newTriangle.edge.nextEdge;
                    HalfEdge3 e3 = newTriangle.edge.nextEdge.nextEdge;

                    newEdges.Add(e1);
                    //newEdges.Add(e2);
                    newEdges.Add(e3);
                }

                //timer.Start();
                //Two edges in each triangle are still not connected with an opposite edge
                foreach (HalfEdge3 e in newEdges)
                {
                    if (e.oppositeEdge != null)
                    {
                        continue;
                    }

                    convexHull.TryFindOppositeEdge(e, newEdges);
                }
                //timer.Stop();


                //Connect all new triangles and the triangles on the border,
                //so each edge has an opposite edge or flood filling will be impossible
                //timer.Start();
                //convexHull.ConnectAllEdges();
                //timer.Stop();


                //if (debugCounter > debugStop)
                //{
                //    break;
                //}

                //debugCounter += 1;
            }

            //Debug.Log($"Connect half-edges took {timer.ElapsedMilliseconds/1000f} seconds");

            Debug.Log($"Removed {removedPointsCounter} points during the construction of the hull because they were inside the hull");


            //
            // Clean up
            //

            //Merge concave edges according to the paper


            //Remove unwanted triangles, such as slivers and needles
            //Which is maybe not needed because when you add a Unity convex mesh collider to the result of this algorithm, there are still slivers
            //Unity's mesh collider is also using quads and not just triangles
            //But if you add enough points, so you end up with many points on the hull you can see that Unitys convex mesh collider is not capturing all points, so they must be using some simplification algorithm

            //Run the hull through the mesh simplification algorithm
            if (removeUnwantedTriangles)
            {
                convexHull = MeshSimplification_QEM.Simplify(convexHull, maxEdgesToContract: int.MaxValue, maxError: 0.0001f, normalizeTriangles: true);
            }


            return(convexHull);
        }
Beispiel #9
0
        //Find all visible triangles from a point
        //Also find edges on the border between invisible and visible triangles
        public static void FindVisibleTrianglesAndBorderEdgesFromPoint(Vector3 p, HalfEdgeData3 convexHull, out HashSet <HalfEdgeFace3> visibleTriangles, out HashSet <HalfEdge3> borderEdges)
        {
            //Flood-fill from the visible triangle to find all other visible triangles
            //When you cross an edge from a visible triangle to an invisible triangle,
            //save the edge because thhose edge should be used to build triangles with the point
            //These edges should belong to the triangle which is not visible
            borderEdges = new HashSet <HalfEdge3>();

            //Store all visible triangles here so we can't visit triangles multiple times
            visibleTriangles = new HashSet <HalfEdgeFace3>();


            //Start the flood-fill by finding a triangle which is visible from the point
            //A triangle is visible if the point is outside the plane formed at the triangles
            //Another sources is using the signed volume of a tetrahedron formed by the triangle and the point
            HalfEdgeFace3 visibleTriangle = FindVisibleTriangleFromPoint(p, convexHull.faces);

            //If we didn't find a visible triangle, we have some kind of edge case and should move on for now
            if (visibleTriangle == null)
            {
                Debug.LogWarning("Couldn't find a visible triangle so will ignore the point");

                return;
            }


            //The queue which we will use when flood-filling
            Queue <HalfEdgeFace3> trianglesToFloodFrom = new Queue <HalfEdgeFace3>();

            //Add the first triangle to init the flood-fill
            trianglesToFloodFrom.Enqueue(visibleTriangle);

            List <HalfEdge3> edgesToCross = new List <HalfEdge3>();

            int safety = 0;

            while (true)
            {
                //We have visited all visible triangles
                if (trianglesToFloodFrom.Count == 0)
                {
                    break;
                }

                HalfEdgeFace3 triangleToFloodFrom = trianglesToFloodFrom.Dequeue();

                //This triangle is always visible and should be deleted
                visibleTriangles.Add(triangleToFloodFrom);

                //Investigate bordering triangles
                edgesToCross.Clear();

                edgesToCross.Add(triangleToFloodFrom.edge);
                edgesToCross.Add(triangleToFloodFrom.edge.nextEdge);
                edgesToCross.Add(triangleToFloodFrom.edge.nextEdge.nextEdge);

                //Jump from this triangle to a bordering triangle
                foreach (HalfEdge3 edgeToCross in edgesToCross)
                {
                    HalfEdge3 oppositeEdge = edgeToCross.oppositeEdge;

                    if (oppositeEdge == null)
                    {
                        Debug.LogWarning("Found an opposite edge which is null");

                        break;
                    }

                    HalfEdgeFace3 oppositeTriangle = oppositeEdge.face;

                    //Have we visited this triangle before (only test visible triangles)?
                    if (trianglesToFloodFrom.Contains(oppositeTriangle) || visibleTriangles.Contains(oppositeTriangle))
                    {
                        continue;
                    }

                    //Check if this triangle is visible
                    //A triangle is visible from a point the point is outside of a plane formed with the triangles position and normal
                    Plane3 plane = new Plane3(oppositeTriangle.edge.v.position, oppositeTriangle.edge.v.normal);

                    bool isPointOutsidePlane = _Geometry.IsPointOutsidePlane(p, plane);

                    //This triangle is visible so save it so we can flood from it
                    if (isPointOutsidePlane)
                    {
                        trianglesToFloodFrom.Enqueue(oppositeTriangle);
                    }
                    //This triangle is invisible. Since we only flood from visible triangles,
                    //it means we crossed from a visible triangle to an invisible triangle, so save the crossing edge
                    else
                    {
                        borderEdges.Add(oppositeEdge);
                    }
                }


                safety += 1;

                if (safety > 50000)
                {
                    Debug.Log("Stuck in infinite loop when flood-filling visible triangles");

                    break;
                }
            }
        }
        //Remove flat tetrahedrons (a vertex in a triangle)
        private static bool RemoveFlatTetrahedrons(HalfEdgeData3 meshData, Normalizer3 normalizer = null)
        {
            HashSet <HalfEdgeVertex3> vertices = meshData.verts;

            bool foundFlatTetrahedron = false;

            foreach (HalfEdgeVertex3 vertex in vertices)
            {
                HashSet <HalfEdge3> edgesGoingToVertex = vertex.GetEdgesPointingToVertex(meshData);

                if (edgesGoingToVertex.Count == 3)
                {
                    //Find the vertices of the triangle covering this vertex clock-wise
                    HalfEdgeVertex3 v1 = vertex.edge.v;
                    HalfEdgeVertex3 v2 = vertex.edge.prevEdge.oppositeEdge.v;
                    HalfEdgeVertex3 v3 = vertex.edge.oppositeEdge.nextEdge.v;

                    //Build a plane
                    MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(v3.position - v2.position, v1.position - v2.position));

                    Plane3 plane = new Plane3(v1.position, normal);

                    //Find the distance from the vertex to the plane
                    float distance = _Geometry.GetSignedDistanceFromPointToPlane(vertex.position, plane);

                    distance = Mathf.Abs(distance);

                    if (distance < FLAT_TETRAHEDRON_DISTANCE)
                    {
                        //Debug.Log("Found flat tetrahedron");

                        Vector3 p1 = normalizer.UnNormalize(v1.position).ToVector3();
                        Vector3 p2 = normalizer.UnNormalize(v2.position).ToVector3();
                        Vector3 p3 = normalizer.UnNormalize(v3.position).ToVector3();

                        TestAlgorithmsHelpMethods.DebugDrawTriangle(p1, p2, p3, normal.ToVector3(), Color.blue, Color.red);

                        foundFlatTetrahedron = true;

                        //Save the opposite edges
                        HashSet <HalfEdge3> oppositeEdges = new HashSet <HalfEdge3>();

                        oppositeEdges.Add(v1.edge.oppositeEdge);
                        oppositeEdges.Add(v2.edge.oppositeEdge);
                        oppositeEdges.Add(v3.edge.oppositeEdge);

                        //Remove the three triangles
                        foreach (HalfEdge3 e in edgesGoingToVertex)
                        {
                            meshData.DeleteFace(e.face);
                        }

                        //Add the new triangle (could maybe connect it ourselves)
                        HalfEdgeFace3 newTriangle = meshData.AddTriangle(v1.position, v2.position, v3.position, findOppositeEdge: false);

                        meshData.TryFindOppositeEdge(newTriangle.edge, oppositeEdges);
                        meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge, oppositeEdges);
                        meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge.nextEdge, oppositeEdges);

                        break;
                    }
                }
            }

            return(foundFlatTetrahedron);
        }
Beispiel #11
0
        //Generate a Voronoi diagram in 3d space given a Delaunay triangulation in 3d space
        public static HashSet <VoronoiCell3> GenerateVoronoiDiagram(HalfEdgeData3 delaunayTriangulation)
        {
            //If we dont need the voronoi sitePos, which is the center of the voronoi cell, we can use the half-edge data structure
            //If not we have the create a child class for voronoi
            HashSet <VoronoiCell3> voronoiDiagram = new HashSet <VoronoiCell3>();


            //Step 1. Generate a center of circle for each triangle because this process is slow in 3d space
            Dictionary <HalfEdgeFace3, Vector3> circleCenterLookup = new Dictionary <HalfEdgeFace3, Vector3>();

            HashSet <HalfEdgeFace3> delaunayTriangles = delaunayTriangulation.faces;

            foreach (HalfEdgeFace3 triangle in delaunayTriangles)
            {
                Vector3 p1 = triangle.edge.v.position;
                Vector3 p2 = triangle.edge.nextEdge.v.position;
                Vector3 p3 = triangle.edge.nextEdge.nextEdge.v.position;

                Vector3 circleCenter = _Geometry.CalculateCircleCenter(p1, p2, p3);

                //https://www.redblobgames.com/x/1842-delaunay-voronoi-sphere/ suggested circleCenter should be moved to get a better surface
                //But it generates a bad result
                //float d = Mathf.Sqrt(circleCenter.x * circleCenter.x + circleCenter.y * circleCenter.y + circleCenter.z * circleCenter.z);

                //MyVector3 circleCenterMove = new MyVector3(circleCenter.x / d, circleCenter.y / d, circleCenter.z / d);

                //circleCenter = circleCenterMove;

                circleCenterLookup.Add(triangle, circleCenter);
            }


            //Step 2. Generate the voronoi cells
            HashSet <HalfEdgeVertex3> delaunayVertices = delaunayTriangulation.verts;

            //In the half-edge data structure we have multiple vertices at the same position,
            //so we have to track which vertex positions have been added
            HashSet <Vector3> addedSites = new HashSet <Vector3>();


            foreach (HalfEdgeVertex3 v in delaunayVertices)
            {
                //Has this site already been added?
                if (addedSites.Contains(v.position))
                {
                    continue;
                }


                addedSites.Add(v.position);

                //This vertex is a cite pos in the voronoi diagram
                VoronoiCell3 cell = new VoronoiCell3(v.position);

                voronoiDiagram.Add(cell);

                //All triangles are fully connected so no null opposite edges should exist
                //So to generate the voronoi cell, we just rotate clock-wise around each vertex in the delaunay triangulation

                HalfEdge3 currentEdge = v.edge;

                int safety = 0;

                while (true)
                {
                    //Build an edge going from the opposite face to this face
                    //Each vertex has an edge going FROM it
                    HalfEdgeFace3 oppositeTriangle = currentEdge.oppositeEdge.face;

                    HalfEdgeFace3 thisTriangle = currentEdge.face;

                    Vector3 oppositeCircleCenter = circleCenterLookup[oppositeTriangle];

                    Vector3 thisCircleCenter = circleCenterLookup[thisTriangle];

                    VoronoiEdge3 edge = new VoronoiEdge3(oppositeCircleCenter, thisCircleCenter, v.position);

                    cell.edges.Add(edge);

                    //Jump to the next triangle
                    //Each vertex has an edge going FROM it
                    //And we want to rotate around a vertex clockwise
                    //So the edge we should jump over is:
                    HalfEdge3 jumpEdge = currentEdge.nextEdge.nextEdge;

                    HalfEdge3 oppositeEdge = jumpEdge.oppositeEdge;

                    //Are we back where we started?
                    if (oppositeEdge == v.edge)
                    {
                        break;
                    }

                    currentEdge = oppositeEdge;


                    safety += 1;

                    if (safety > 10000)
                    {
                        Debug.Log("Stuck in infinite loop when generating voronoi cells");

                        break;
                    }
                }
            }

            return(voronoiDiagram);
        }
        //v1-v2-v3 should be clock-wise which is Unity standard
        public HalfEdgeFace3 AddTriangle(MyMeshVertex v1, MyMeshVertex v2, MyMeshVertex v3, bool findOppositeEdge = false)
        {
            //Create three new vertices
            HalfEdgeVertex3 half_v1 = new HalfEdgeVertex3(v1.position, v1.normal);
            HalfEdgeVertex3 half_v2 = new HalfEdgeVertex3(v2.position, v2.normal);
            HalfEdgeVertex3 half_v3 = new HalfEdgeVertex3(v3.position, v3.normal);

            //Create three new half-edges that points TO these vertices
            HalfEdge3 e_to_v1 = new HalfEdge3(half_v1);
            HalfEdge3 e_to_v2 = new HalfEdge3(half_v2);
            HalfEdge3 e_to_v3 = new HalfEdge3(half_v3);

            //Create the face (which is a triangle) which needs a reference to one of the edges
            HalfEdgeFace3 f = new HalfEdgeFace3(e_to_v1);


            //Connect the data:

            //Connect the edges clock-wise
            e_to_v1.nextEdge = e_to_v2;
            e_to_v2.nextEdge = e_to_v3;
            e_to_v3.nextEdge = e_to_v1;

            e_to_v1.prevEdge = e_to_v3;
            e_to_v2.prevEdge = e_to_v1;
            e_to_v3.prevEdge = e_to_v2;

            //Each vertex needs a reference to an edge going FROM that vertex
            half_v1.edge = e_to_v2;
            half_v2.edge = e_to_v3;
            half_v3.edge = e_to_v1;

            //Each edge needs a reference to the face
            e_to_v1.face = f;
            e_to_v2.face = f;
            e_to_v3.face = f;

            //Each edge needs an opposite edge
            //This is slow process
            //You could do this afterwards when all triangles have been generate
            //Doing it in this method takes 2.7 seconds for the bunny
            //Doing it afterwards takes 0.1 seconds by using the fast method and 1.6 seconds for the slow method
            //The reason is that we keep searching the list for an opposite which doesnt exist yet, so we get more searches even though
            //the list is shorter as we build up the mesh
            //But you could maybe do it here if you just add a new triangle?
            if (findOppositeEdge)
            {
                TryFindOppositeEdge(e_to_v1);
                TryFindOppositeEdge(e_to_v2);
                TryFindOppositeEdge(e_to_v3);
            }


            //Save the data
            this.verts.Add(half_v1);
            this.verts.Add(half_v2);
            this.verts.Add(half_v3);

            this.edges.Add(e_to_v1);
            this.edges.Add(e_to_v2);
            this.edges.Add(e_to_v3);

            this.faces.Add(f);

            return(f);
        }