Exemplo n.º 1
0
    private void StartMeshSimplification()
    {
        Mesh meshToSimplify = meshInput.sharedMesh;

        meshInput.transform.gameObject.SetActive(false);


        //
        // Change data structure and normalize
        //

        //Mesh -> MyMesh
        MyMesh myMeshToSimplify = new MyMesh(meshToSimplify);

        //From local to global space
        myMeshToSimplify.vertices = myMeshToSimplify.vertices.Select(x => meshInput.transform.TransformPoint(x.ToVector3()).ToMyVector3()).ToList();

        //Normalize to 0-1
        //this.normalizer = new Normalizer3(myMeshToSimplify.vertices);

        //We only need to normalize the vertices
        //myMeshToSimplify.vertices = normalizer.Normalize(myMeshToSimplify.vertices);

        HalfEdgeData3 myMeshToSimplify_HalfEdge = new HalfEdgeData3(myMeshToSimplify, HalfEdgeData3.ConnectOppositeEdges.Fast);


        //Start
        VisualizeMergeEdgesQEM visualizeThisAlgorithm = GetComponent <VisualizeMergeEdgesQEM>();

        visualizeThisAlgorithm.StartVisualizer(myMeshToSimplify_HalfEdge, maxEdgesToContract: 2450, maxError: Mathf.Infinity);
    }
Exemplo n.º 2
0
    public void DisplayMeshMain(HalfEdgeData2 meshData, Normalizer2 normalizer)
    {
        //UnNormalize and to 3d
        HalfEdgeData3 meshDataUnNormalized_3d = new HalfEdgeData3();


        //We dont want to modify the original data
        //HalfEdgeData2 meshDataUnNormalized = normalizer.UnNormalize(meshData);

        HashSet <HalfEdgeFace2> faces_2d = meshData.faces;

        foreach (HalfEdgeFace2 f in faces_2d)
        {
            MyVector2 p1 = f.edge.v.position;
            MyVector2 p2 = f.edge.nextEdge.v.position;
            MyVector2 p3 = f.edge.nextEdge.nextEdge.v.position;

            p1 = normalizer.UnNormalize(p1);
            p2 = normalizer.UnNormalize(p2);
            p3 = normalizer.UnNormalize(p3);

            meshDataUnNormalized_3d.AddTriangle(p1.ToMyVector3_Yis3D(), p2.ToMyVector3_Yis3D(), p3.ToMyVector3_Yis3D());
        }

        this.meshData = meshDataUnNormalized_3d.faces;

        DisplayMesh(meshDataUnNormalized_3d.faces, displayMeshHere);

        //Normalize again
        //meshData = normalizer.Normalize(meshDataUnNormalized);
    }
Exemplo n.º 3
0
    //Display a mesh, which is called from the coroutine when a mesh has changed
    public void DisplayMesh(HashSet <HalfEdgeFace3> meshDataUnNormalized, MeshFilter mf)
    {
        //Generate a mesh
        MyMesh myMesh = HalfEdgeData3.ConvertToMyMesh("Main visualization mesh", meshDataUnNormalized, MyMesh.MeshStyle.HardEdges);

        Mesh mesh = myMesh.ConvertToUnityMesh(generateNormals: true);

        mf.mesh = mesh;

        //Debug.Log(mesh.triangles.Length);
    }
    //These points should be normalized
    public void StartVisualizer(HashSet <MyVector3> points)
    {
        controller = GetComponent <VisualizerController3D>();

        HalfEdgeData3 convexHull = new HalfEdgeData3();

        //Generate the first tertahedron
        IterativeHullAlgorithm3D.BuildFirstTetrahedron(points, convexHull);


        //Main visualization algorithm
        StartCoroutine(GenerateHull(points, convexHull));
    }
    private IEnumerator GenerateHull(HashSet <MyVector3> points, HalfEdgeData3 convexHull)
    {
        //PAUSE FOR VISUALIZATION
        //Display what we have so far
        controller.DisplayMeshMain(convexHull.faces);
        controller.HideAllVisiblePoints(convexHull.verts);

        yield return(new WaitForSeconds(10f));


        //Add all other points one-by-one
        List <MyVector3> pointsToAdd = new List <MyVector3>(points);

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

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

                controller.HideVisiblePoint(p);

                continue;
            }


            //PAUSE FOR VISUALIZATION
            //Display active point
            controller.DisplayActivePoint(p);

            //Rotate camera to this point
            //Important to turn this vector to 2d
            Vector3 unity_pos = controller.normalizer.UnNormalize(p).ToVector3();

            controller.cameraScript.SetWantedHeight(unity_pos.y);

            unity_pos.y = 0f;

            controller.cameraScript.SetWantedDirection((Vector3.zero - unity_pos).normalized);

            yield return(new WaitForSeconds(2f));


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

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

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


            //PAUSE FOR VISUALIZATION
            //For visualization purposes we now need to create two meshes and then remove the triangles again
            controller.DisplayMeshMain(convexHull.faces);
            controller.DisplayMeshOther(visibleTriangles);
            controller.HideAllVisiblePoints(convexHull.verts);

            yield return(new WaitForSeconds(2f));


            //PAUSE FOR VISUALIZATION
            //Remove all now visible triangles that forms the hole
            List <HalfEdgeFace3> visibleTrianglesList = new List <HalfEdgeFace3>(visibleTriangles);

            for (int i = 0; i < visibleTrianglesList.Count; i++)
            {
                visibleTriangles.Remove(visibleTrianglesList[i]);

                controller.DisplayMeshOther(visibleTriangles);

                yield return(new WaitForSeconds(0.5f));
            }


            //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
                MyVector3 p1 = borderEdge.prevEdge.v.position;
                MyVector3 p2 = borderEdge.v.position;

                //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);


                //PAUSE FOR VISUALIZATION
                controller.DisplayMeshMain(convexHull.faces);

                yield return(new WaitForSeconds(0.5f));


                //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);
            }


            //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);
            }


            //PAUSE FOR VISUALIZATION
            //controller.DisplayMeshMain(convexHull.faces);

            //yield return new WaitForSeconds(2f);
            controller.HideVisiblePoint(p);
        }


        controller.HideActivePoint();

        controller.cameraScript.SetWantedDirection(Vector3.zero);
        controller.cameraScript.SetWantedHeight(0f);

        //controller.DisplayMeshMain(convexHull.faces);

        //yield return new WaitForSeconds(5f);


        yield return(null);
    }
    void Start()
    {
        vertices_Unity = new HashSet <UnityEngine.Vector3>();

        for (int i = 0; i < pointCount; i++)
        {
            UnityEngine.Vector3 point = srcMesh.mesh.GetRandomPointInsideNonConvex(srcMesh.GetComponent <Renderer>().bounds.center);
            vertices_Unity.Add(point);
        }



        HashSet <Vector3> points            = new HashSet <Vector3>(vertices_Unity.Select(x => x.ToMyVector3()));
        Normalizer3       normalizer        = new Normalizer3(new List <Vector3>(points));
        HashSet <Vector3> points_normalized = normalizer.Normalize(points);

        //Convex Hull


        HalfEdgeData3 convexHull_normalized = _ConvexHull.Iterative_3D(points_normalized, removeUnwantedTriangles: false, normalizer);
        //HalfEdgeData3 convexHull_unnormalized = _ConvexHull.Iterative_3D(points, removeUnwantedTriangles: false, normalizer);

        var tmp = convexHull_normalized.ConvertToMyMesh("Triangulated Points", MyMesh.MeshStyle.HardEdges);

        delaunayMesh = tmp.ConvertToUnityMesh(true);

        // if (convexHull_normalized == null)
        // {
        //     Debug.LogError("ConvexHull is null");
        //     return;
        // }
        //
        HashSet <VoronoiCell3> voronoiCells_normalized = _Voronoi.Delaunay3DToVoronoi(convexHull_normalized);

        HalfEdgeData3 convexHull = normalizer.UnNormalize(convexHull_normalized);

        MyMesh myMesh = convexHull.ConvertToMyMesh("convex hull aka delaunay triangulation", MyMesh.MeshStyle.HardEdges);

        delaunayMesh = myMesh.ConvertToUnityMesh(generateNormals: false);


        //Voronoi
        HashSet <VoronoiCell3> voronoiCells = normalizer.UnNormalize(voronoiCells_normalized);

        //Generate a mesh for each separate cell
        voronoiCellsMeshes = GenerateVoronoiCellsMeshes(voronoiCells);

        foreach (var mesh in voronoiCellsMeshes)
        {
            GameObject go         = new GameObject();
            var        meshfilter = go.AddComponent <MeshFilter>();
            var        renderer   = go.AddComponent <MeshRenderer>();
            renderer.sharedMaterial = material;
            meshfilter.mesh         = mesh;
            go.transform.parent     = gameObject.transform;
        }


        //Generate a single mesh for all cells where each vertex has a color belonging to that cell
        //Now we can display the mesh with an unlit shader where each vertex is associated with a color belonging to that cell
        //The problem is that the voronoi cell is not a flat surface on the mesh
        //But it looks flat if we are using an unlit shader
        // Mesh oneMesh = GenerateAndDisplaySingleMesh(voronoiCellsMeshes);
        //
        // if (meshFilter != null)
        // {
        //     meshFilter.mesh = oneMesh;
        // }
        // else
        // {
        //     Debug.LogError("Meshfilter is null");
        // }
    }
Exemplo n.º 7
0
    //
    // Add the constraints to the delaunay triangulation
    //

    //timer is for debugging
    private IEnumerator AddConstraints(HalfEdgeData2 triangleData, List <MyVector2> constraints, bool shouldRemoveTriangles, Normalizer2 normalizer, System.Diagnostics.Stopwatch timer = null)
    {
        //Validate the data
        if (constraints == null)
        {
            yield return(null);
        }



        //
        // PAUSE AND VISUALIZE
        //

        //Show the constraint with a line mesh
        HashSet <Triangle2> lineTriangles = _GenerateMesh.ConnectedLineSegments(constraints, width: 0.01f, isConnected: true);

        //UnNormalized and to half-edge 3 (also move each vertex up a little or will intersect with the underlying mesh)
        HalfEdgeData3 lineData = new HalfEdgeData3();

        foreach (Triangle2 t in lineTriangles)
        {
            MyVector2 p1 = t.p1;
            MyVector2 p2 = t.p2;
            MyVector2 p3 = t.p3;

            p1 = normalizer.UnNormalize(p1);
            p2 = normalizer.UnNormalize(p2);
            p3 = normalizer.UnNormalize(p3);

            lineData.AddTriangle(p1.ToMyVector3_Yis3D(0.1f), p2.ToMyVector3_Yis3D(0.1f), p3.ToMyVector3_Yis3D(0.1f));
        }

        visualizeController.DisplayMeshOtherUnNormalized(lineData.faces);

        yield return(new WaitForSeconds(2f));



        //Get a list with all edges
        //This is faster than first searching for unique edges
        //The report suggest we should do a triangle walk, but it will not work if the mesh has holes
        //The mesh has holes because we remove triangles while adding constraints one-by-one
        //so maybe better to remove triangles after we added all constraints...
        HashSet <HalfEdge2> edges = triangleData.edges;


        //The steps numbering is from the report
        //Step 1. Loop over each constrained edge. For each of these edges, do steps 2-4
        for (int i = 0; i < constraints.Count; i++)
        {
            //Let each constrained edge be defined by the vertices:
            MyVector2 c_p1 = constraints[i];
            MyVector2 c_p2 = constraints[MathUtility.ClampListIndex(i + 1, constraints.Count)];

            //Check if this constraint already exists in the triangulation,
            //if so we are happy and dont need to worry about this edge
            //timer.Start();
            if (IsEdgeInListOfEdges(edges, c_p1, c_p2))
            {
                continue;
            }
            //timer.Stop();

            //Step 2. Find all edges in the current triangulation that intersects with this constraint
            //Is returning unique edges only, so not one edge going in the opposite direction
            //timer.Start();
            Queue <HalfEdge2> intersectingEdges = FindIntersectingEdges_BruteForce(edges, c_p1, c_p2);
            //timer.Stop();

            //Debug.Log("Intersecting edges: " + intersectingEdges.Count);

            //Step 3. Remove intersecting edges by flipping triangles
            //This takes 0 seconds so is not bottleneck
            //timer.Start();
            List <HalfEdge2> newEdges = new List <HalfEdge2>();

            yield return(StartCoroutine(RemoveIntersectingEdges(c_p1, c_p2, intersectingEdges, newEdges, triangleData, normalizer)));

            //timer.Stop();

            //Step 4. Try to restore delaunay triangulation
            //Because we have constraints we will never get a delaunay triangulation
            //This takes 0 seconds so is not bottleneck
            //timer.Start();
            yield return(StartCoroutine(RestoreDelaunayTriangulation(c_p1, c_p2, newEdges, triangleData, normalizer)));
            //timer.Stop();
        }

        //Step 5. Remove superfluous triangles, such as the triangles "inside" the constraints
        if (shouldRemoveTriangles)
        {
            //timer.Start();
            yield return(StartCoroutine(RemoveSuperfluousTriangles(triangleData, constraints, normalizer)));
            //timer.Stop();
        }

        //return triangleData;



        //
        // PAUSE AND VISUALIZE
        //

        visualizeController.HideMeshOther();

        yield return(new WaitForSeconds(2f));
    }
Exemplo n.º 8
0
    //Generates points, delaunay triangulation, and voronoi diagram
    public void Generate()
    {
        if (radius <= 0f)
        {
            radius = 0.01f;
        }
        if (numberOfPoints < 4)
        {
            numberOfPoints = 4;
        }

        //Get random points in 3d space
        points_Unity = TestAlgorithmsHelpMethods.GenerateRandomPointsOnSphere(seed, radius, numberOfPoints);

        //To MyVector3
        HashSet <MyVector3> points = new HashSet <MyVector3>(points_Unity.Select(x => x.ToMyVector3()));

        //Normalize
        Normalizer3 normalizer = new Normalizer3(new List <MyVector3>(points));

        HashSet <MyVector3> points_normalized = normalizer.Normalize(points);


        System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();


        //
        // Generate the convex hull, which is the same as the Delaunay triangulation of points on the sphere
        //

        //Iterative algorithm
        timer.Start();

        HalfEdgeData3 convexHull_normalized = _ConvexHull.Iterative_3D(points_normalized, removeUnwantedTriangles: false, normalizer);

        timer.Stop();

        Debug.Log($"Generated a 3d convex hull in {timer.ElapsedMilliseconds / 1000f} seconds");

        if (convexHull_normalized == null)
        {
            return;
        }



        //
        // Generate the voronoi diagram from the delaunay triangulation
        //
        timer.Restart();

        HashSet <VoronoiCell3> voronoiCells_normalized = _Voronoi.Delaunay3DToVoronoi(convexHull_normalized);

        timer.Stop();

        Debug.Log($"Generated a 3d voronoi diagram in {timer.ElapsedMilliseconds / 1000f} seconds");

        if (voronoiCells_normalized == null)
        {
            return;
        }


        //
        // Display
        //

        //Delaunay
        HalfEdgeData3 convexHull = normalizer.UnNormalize(convexHull_normalized);

        MyMesh myMesh = convexHull.ConvertToMyMesh("convex hull aka delaunay triangulation", MyMesh.MeshStyle.HardEdges);

        delaunayMesh = myMesh.ConvertToUnityMesh(generateNormals: false);


        //Voronoi
        HashSet <VoronoiCell3> voronoiCells = normalizer.UnNormalize(voronoiCells_normalized);

        //Generate a mesh for each separate cell
        voronoiCellsMeshes = GenerateVoronoiCellsMeshes(voronoiCells);

        //Generate a single mesh for all cells where each vertex has a color belonging to that cell
        //Now we can display the mesh with an unlit shader where each vertex is associated with a color belonging to that cell
        //The problem is that the voronoi cell is not a flat surface on the mesh
        //But it looks flat if we are using an unlit shader
        Mesh oneMesh = GenerateAndDisplaySingleMesh(voronoiCellsMeshes);

        if (meshFilter != null)
        {
            meshFilter.mesh = oneMesh;
        }
    }
    public void SimplifyMesh()
    {
        //Has to be sharedMesh if we are using Editor tools
        Mesh meshToSimplify = meshFilterToSimplify.sharedMesh;


        //
        // Change data structure and normalize
        //

        //Mesh -> MyMesh
        MyMesh myMeshToSimplify = new MyMesh(meshToSimplify);

        //Normalize to 0-1
        Normalizer3 normalizer = new Normalizer3(myMeshToSimplify.vertices);

        //We only need to normalize the vertices
        myMeshToSimplify.vertices = normalizer.Normalize(myMeshToSimplify.vertices);

        HalfEdgeData3 myMeshToSimplify_HalfEdge = new HalfEdgeData3(myMeshToSimplify, HalfEdgeData3.ConnectOppositeEdges.Fast);



        //
        // Simplify
        //

        System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();

        timer.Start();

        HalfEdgeData3 mySimplifiedMesh_HalfEdge = MeshSimplification_QEM.Simplify(myMeshToSimplify_HalfEdge, maxEdgesToContract: 2400, maxError: Mathf.Infinity, normalizeTriangles: true);

        timer.Stop();

        Debug.Log($"It took {timer.ElapsedMilliseconds / 1000f} seconds to simplify the mesh");



        //
        // Change data structure and un-normalize
        //

        timer.Reset();
        timer.Start();

        //From half-edge to mesh
        MyMesh mySimplifiedMesh = mySimplifiedMesh_HalfEdge.ConvertToMyMesh("Simplified mesh", MyMesh.MeshStyle.HardEdges);

        //Un-Normalize
        mySimplifiedMesh.vertices = normalizer.UnNormalize(mySimplifiedMesh.vertices);

        //Convert to global space
        Transform trans = meshFilterToSimplify.transform;

        mySimplifiedMesh.vertices = mySimplifiedMesh.vertices.Select(x => trans.TransformPoint(x.ToVector3()).ToMyVector3()).ToList();

        //Convert to mesh
        Mesh unitySimplifiedMesh = mySimplifiedMesh.ConvertToUnityMesh(generateNormals: true, meshName: "simplified mesh");

        //Attach to new game object
        meshFilterToShowSimplifiedMesh.mesh = unitySimplifiedMesh;

        timer.Stop();

        Debug.Log($"It took {timer.ElapsedMilliseconds / 1000f} seconds to finalize the mesh after simplifying");
    }
    public void StartVisualizer(HalfEdgeData3 halfEdgeMeshData, int maxEdgesToContract, float maxError, bool normalizeTriangles = false)
    {
        controller = GetComponent <VisualizerController3D>();


        //
        // Compute the Q matrices for all the initial vertices
        //

        //Put the result in a lookup dictionary
        //This assumes we have no floating point precision issues, so vertices at the same position have to be at the same position
        Dictionary <MyVector3, Matrix4x4> qMatrices = new Dictionary <MyVector3, Matrix4x4>();

        HashSet <HalfEdgeVertex3> vertices = halfEdgeMeshData.verts;

        //timer.Start();

        //0.142 seconds for the bunny (0.012 for dictionary lookup, 0.024 to calculate the Q matrices, 0.087 to find edges going to vertex)
        foreach (HalfEdgeVertex3 v in vertices)
        {
            //Have we already calculated a Q matrix for this vertex?
            //Remember that we have multiple vertices at the same position in the half-edge data structure
            //timer.Start();
            if (qMatrices.ContainsKey(v.position))
            {
                continue;
            }
            //timer.Stop();

            //Calculate the Q matrix for this vertex

            //timer.Start();
            //Find all edges meeting at this vertex
            HashSet <HalfEdge3> edgesPointingToThisVertex = v.GetEdgesPointingToVertex(halfEdgeMeshData);
            //timer.Stop();

            //timer.Start();
            Matrix4x4 Q = MeshSimplification_QEM.CalculateQMatrix(edgesPointingToThisVertex, normalizeTriangles);
            //timer.Stop();

            qMatrices.Add(v.position, Q);
        }

        //timer.Stop();



        //
        // Select all valid pairs that can be contracted
        //

        List <HalfEdge3> validPairs = new List <HalfEdge3>(halfEdgeMeshData.edges);



        //
        // Compute the cost of contraction for each pair
        //

        HashSet <QEM_Edge> QEM_edges = new HashSet <QEM_Edge>();

        //We need a lookup table to faster remove and update QEM_edges
        Dictionary <HalfEdge3, QEM_Edge> halfEdge_QEM_Lookup = new Dictionary <HalfEdge3, QEM_Edge>();

        foreach (HalfEdge3 halfEdge in validPairs)
        {
            MyVector3 p1 = halfEdge.prevEdge.v.position;
            MyVector3 p2 = halfEdge.v.position;

            Matrix4x4 Q1 = qMatrices[p1];
            Matrix4x4 Q2 = qMatrices[p2];

            QEM_Edge QEM_edge = new QEM_Edge(halfEdge, Q1, Q2);

            QEM_edges.Add(QEM_edge);

            halfEdge_QEM_Lookup.Add(halfEdge, QEM_edge);
        }



        //
        // Sort all pairs, with the minimum cost pair at the top
        //

        //The fastest way to keep the data sorted is to use a heap
        Heap <QEM_Edge> sorted_QEM_edges = new Heap <QEM_Edge>(QEM_edges.Count);

        foreach (QEM_Edge e in QEM_edges)
        {
            sorted_QEM_edges.Add(e);
        }


        //Main visualization algorithm coroutine
        StartCoroutine(QEMLoop(halfEdgeMeshData, sorted_QEM_edges, qMatrices, halfEdge_QEM_Lookup, maxEdgesToContract, maxError, normalizeTriangles));
    }
    private IEnumerator QEMLoop(HalfEdgeData3 halfEdgeMeshData, Heap <QEM_Edge> sorted_QEM_edges, Dictionary <MyVector3, Matrix4x4> qMatrices, Dictionary <HalfEdge3, QEM_Edge> halfEdge_QEM_Lookup, int maxEdgesToContract, float maxError, bool normalizeTriangles = false)
    {
        //PAUSE FOR VISUALIZATION
        //Display what we have so far
        controller.DisplayMeshMain(halfEdgeMeshData.faces);

        controller.displayStuffUI.text = "Triangles: " + halfEdgeMeshData.faces.Count.ToString();

        yield return(new WaitForSeconds(5f));


        //
        // Start contracting edges
        //

        //For each edge we want to remove
        for (int i = 0; i < maxEdgesToContract; i++)
        {
            //Check that we can simplify the mesh
            //The smallest mesh we can have is a tetrahedron with 4 faces, itherwise we get a flat triangle
            if (halfEdgeMeshData.faces.Count <= 4)
            {
                Debug.Log($"Cant contract more than {i} edges");

                break;
            }


            //
            // Remove the pair (v1,v2) of the least cost and contract the pair
            //

            //timer.Start();

            QEM_Edge smallestErrorEdge = sorted_QEM_edges.RemoveFirst();

            //This means an edge in this face has already been contracted
            //We are never removing edges from the heap after contracting and edges,
            //so we do it this way for now, which is maybe better?
            if (smallestErrorEdge.halfEdge.face == null)
            {
                //This edge wasn't contracted so don't add it to iteration
                i -= 1;

                continue;
            }

            if (smallestErrorEdge.qem > maxError)
            {
                Debug.Log($"Cant contract more than {i} edges because reached max error");

                break;
            }

            //timer.Stop();


            //timer.Start();

            //Get the half-edge we want to contract
            HalfEdge3 edgeToContract = smallestErrorEdge.halfEdge;

            //Need to save the endpoints so we can remove the old Q matrices from the pos-matrix lookup table
            Edge3 contractedEdgeEndpoints = new Edge3(edgeToContract.prevEdge.v.position, edgeToContract.v.position);

            //Contract edge
            HashSet <HalfEdge3> edgesPointingToNewVertex = halfEdgeMeshData.ContractTriangleHalfEdge(edgeToContract, smallestErrorEdge.mergePosition);

            //timer.Stop();



            //
            // Remove all QEM_edges that belonged to the faces we contracted
            //

            //This is not needed if we check if an edge in the triangle has already been contracted

            /*
             * //timer.Start();
             *
             * //This edge doesnt exist anymore, so remove it from the lookup
             * halfEdge_QEM_Lookup.Remove(edgeToContract);
             *
             * //Remove the two edges that were a part of the triangle of the edge we contracted
             * RemoveHalfEdgeFromQEMEdges(edgeToContract.nextEdge, QEM_edges, halfEdge_QEM_Lookup);
             * RemoveHalfEdgeFromQEMEdges(edgeToContract.nextEdge.nextEdge, QEM_edges, halfEdge_QEM_Lookup);
             *
             * //Remove the three edges belonging to the triangle on the opposite side of the edge we contracted
             * //If there was an opposite side...
             * if (edgeToContract.oppositeEdge != null)
             * {
             *  HalfEdge3 oppositeEdge = edgeToContract.oppositeEdge;
             *
             *  RemoveHalfEdgeFromQEMEdges(oppositeEdge, QEM_edges, halfEdge_QEM_Lookup);
             *  RemoveHalfEdgeFromQEMEdges(oppositeEdge.nextEdge, QEM_edges, halfEdge_QEM_Lookup);
             *  RemoveHalfEdgeFromQEMEdges(oppositeEdge.nextEdge.nextEdge, QEM_edges, halfEdge_QEM_Lookup);
             * }
             * //timer.Stop();
             */

            //Remove the edges start and end vertices from the pos-matrix lookup table
            qMatrices.Remove(contractedEdgeEndpoints.p1);
            qMatrices.Remove(contractedEdgeEndpoints.p2);
            //timer.Stop();



            //
            // Update all QEM_edges that is now connected with the new contracted vertex because their errors have changed
            //

            //The contracted position has a new Q matrix
            Matrix4x4 QNew = MeshSimplification_QEM.CalculateQMatrix(edgesPointingToNewVertex, normalizeTriangles);

            //Add the Q matrix to the pos-matrix lookup table
            qMatrices.Add(smallestErrorEdge.mergePosition, QNew);


            //Update the error of the QEM_edges of the edges that pointed to and from one of the two old Q matrices
            //Those edges are the same edges that points to the new vertex and goes from the new vertex
            //timer.Start();
            foreach (HalfEdge3 edgeToV in edgesPointingToNewVertex)
            {
                //The edge going from the new vertex is the next edge of the edge going to the vertex
                HalfEdge3 edgeFromV = edgeToV.nextEdge;


                //To
                QEM_Edge QEM_edgeToV = halfEdge_QEM_Lookup[edgeToV];

                Edge3 edgeToV_endPoints = QEM_edgeToV.GetEdgeEndPoints();

                Matrix4x4 Q1_edgeToV = qMatrices[edgeToV_endPoints.p1];
                Matrix4x4 Q2_edgeToV = QNew;

                QEM_edgeToV.UpdateEdge(edgeToV, Q1_edgeToV, Q2_edgeToV);

                sorted_QEM_edges.UpdateItem(QEM_edgeToV);


                //From
                QEM_Edge QEM_edgeFromV = halfEdge_QEM_Lookup[edgeFromV];

                Edge3 edgeFromV_endPoints = QEM_edgeFromV.GetEdgeEndPoints();

                Matrix4x4 Q1_edgeFromV = QNew;
                Matrix4x4 Q2_edgeFromV = qMatrices[edgeFromV_endPoints.p2];

                QEM_edgeFromV.UpdateEdge(edgeFromV, Q1_edgeFromV, Q2_edgeFromV);

                sorted_QEM_edges.UpdateItem(QEM_edgeFromV);
            }
            //timer.Stop();



            //PAUSE FOR VISUALIZATION
            //Display what we have so far
            controller.DisplayMeshMain(halfEdgeMeshData.faces);

            controller.displayStuffUI.text = "Triangles: " + halfEdgeMeshData.faces.Count.ToString();

            yield return(new WaitForSeconds(0.02f));
        }
    }
    //Called from editor script
    public void GenerateHull()
    {
        //Get random points in 3d space
        HashSet <Vector3> points_Unity = TestAlgorithmsHelpMethods.GenerateRandomPoints3D(seed, halfMapSize, numberOfPoints);

        //HashSet<Vector3> points_Unity = GetCubeTestPoints();

        //Points from a mesh

        /*
         * Transform meshTrans = constructHullFromThisMesh.transform;
         *
         * List<Vector3> vertices = new List<Vector3>(constructHullFromThisMesh.sharedMesh.vertices);
         *
         * //Local to global space
         * List<Vector3> verticesGlobal = vertices.Select(x => meshTrans.TransformPoint(x)).ToList();
         *
         * HashSet<Vector3> points_Unity = new HashSet<Vector3>(verticesGlobal);
         */

        //To stress-test these algorithms, generate points on a sphere because all of those should be on the hull
        //HashSet<Vector3> points_Unity = TestAlgorithmsHelpMethods.GenerateRandomPointsOnSphere(seed, radius: 1f, numberOfPoints);

        //To MyVector3
        HashSet <MyVector3> points = new HashSet <MyVector3>(points_Unity.Select(x => x.ToMyVector3()));

        //Normalize
        Normalizer3 normalizer = new Normalizer3(new List <MyVector3>(points));

        HashSet <MyVector3> points_normalized = normalizer.Normalize(points);



        //
        // Generate the convex hull
        //

        //Algorithm 1. Iterative algorithm

        System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();

        timer.Start();

        HalfEdgeData3 convexHull_normalized = _ConvexHull.Iterative_3D(points_normalized, removeUnwantedTriangles, normalizer);

        timer.Stop();

        Debug.Log($"Generated a 3d convex hull in {timer.ElapsedMilliseconds / 1000f} seconds with {convexHull_normalized.faces.Count} triangles");



        //
        // Display
        //

        //Points
        //TestAlgorithmsHelpMethods.DisplayPoints(points_Unity, 0.01f, Color.black);


        //Hull mesh
        if (convexHull_normalized != null)
        {
            HalfEdgeData3 convexHull = normalizer.UnNormalize(convexHull_normalized);

            MyMesh myMesh = convexHull.ConvertToMyMesh("convex hull", MyMesh.MeshStyle.HardEdges);

            //To unity mesh
            Mesh convexHullMesh = myMesh.ConvertToUnityMesh(generateNormals: false, myMesh.meshName);

            //Using gizmos to display mesh in 3d space gives a bad result
            //TestAlgorithmsHelpMethods.DisplayMeshWithRandomColors(convexHullMesh, 0);

            //Better to add it to a gameobject
            //Use Shaded Wireframe to see the triangles
            meshFilter.mesh = convexHullMesh;

            //Points on the hull
            //These are shining thorugh the mesh
            //TestAlgorithmsHelpMethods.DisplayMeshCorners(convexHullMesh, 0.01f, Color.black);
        }
    }