Example #1
0
        //Given an edge and a list of points, find the point furthest away from the edge
        private static Vector3 FindPointFurthestFromEdge(Edge3 edge, HashSet <Vector3> pointsHashSet)
        {
            List <Vector3> points = new List <Vector3>(pointsHashSet);

            //Init with the first point
            Vector3 pointFurthestAway = points[0];

            // Vector3 closestPointOnLine = _Geometry.GetClosestPointOnLine(edge, pointFurthestAway, withinSegment: false);
            //
            // float maxDistSqr = Vector3.SqrDistance(pointFurthestAway, closestPointOnLine);
            //
            // //Try to find a better point
            // for (int i = 1; i < points.Count; i++)
            // {
            //     Vector3 thisPoint = points[i];
            //
            //     //TODO make sure that thisPoint is NOT colinear with the edge because then we wont be able to build a triangle
            //
            //     closestPointOnLine = _Geometry.GetClosestPointOnLine(edge, thisPoint, withinSegment: false);
            //
            //     float distSqr = Vector3.SqrDistance(thisPoint, closestPointOnLine);
            //
            //     if (distSqr > maxDistSqr)
            //     {
            //         maxDistSqr = distSqr;
            //
            //         pointFurthestAway = thisPoint;
            //     }
            // }


            return(pointFurthestAway);
        }
        //3d
        //Same math as in 2d case
        public static MyVector3 GetClosestPointOnLine(Edge3 e, MyVector3 p, bool withinSegment)
        {
            MyVector3 a = e.p1;
            MyVector3 b = e.p2;

            //Assume the line goes from a to b
            MyVector3 ab = b - a;
            //Vector from start of the line to the point outside of line
            MyVector3 ap = p - a;

            //The normalized "distance" from a to the closest point, so [0,1] if we are within the line segment
            float distance = MyVector3.Dot(ap, ab) / MyVector3.SqrMagnitude(ab);


            ///This point may not be on the line segment, if so return one of the end points
            float epsilon = MathUtility.EPSILON;

            if (withinSegment && distance < 0f - epsilon)
            {
                return(a);
            }
            else if (withinSegment && distance > 1f + epsilon)
            {
                return(b);
            }
            else
            {
                //This works because a_b is not normalized and distance is [0,1] if distance is within ab
                return(a + ab * distance);
            }
        }
        //If we know that the vertex positions were created in the same way (no floating point precision issues)
        //we can generate a lookup table of all edges which should make it faster to find an opposite edge for each edge
        //This method takes rough 0.1 seconds for the bunny, while the slow method takes 1.6 seconds
        public void ConnectAllEdgesFast()
        {
            //Create the lookup table
            //Important in this case that Edge3 is a struct
            Dictionary <Edge3, HalfEdge3> edgeLookup = new Dictionary <Edge3, HalfEdge3>();

            //We can also maybe create a list of all edges which are not connected, so we don't have to search through all edges again?
            //List<HalfEdge3> unconnectedEdges = new List<HalfEdge3>();

            foreach (HalfEdge3 e in edges)
            {
                //Dont add it if its opposite is not null
                //Sometimes we run this method if just a few edges are not connected
                //This means this edge is already connected, so it cant possibly be connected with the edges we want to connect
                if (e.oppositeEdge != null)
                {
                    continue;
                }

                //Each edge points TO a vertex
                Vector3 p2 = e.v.position;
                Vector3 p1 = e.prevEdge.v.position;

                edgeLookup.Add(new Edge3(p1, p2), e);
            }

            //Connect edges
            foreach (HalfEdge3 e in edges)
            {
                //This edge is already connected
                //Is faster to first do a null check
                if (e.oppositeEdge != null)
                //if (!(e.oppositeEdge is null)) //Is slightly slower
                {
                    continue;
                }

                //Each edge points TO a vertex, so the opposite edge goes in the opposite direction
                Vector3 p1 = e.v.position;
                Vector3 p2 = e.prevEdge.v.position;

                Edge3 edgeToLookup = new Edge3(p1, p2);

                //This is slightly faster than first edgeLookup.ContainsKey(edgeToLookup)
                HalfEdge3 eOther = null;

                edgeLookup.TryGetValue(edgeToLookup, out eOther);

                if (eOther != null)
                {
                    //Connect them with each other
                    e.oppositeEdge = eOther;

                    eOther.oppositeEdge = e;
                }

                //This edge doesnt exist so opposite edge must be null
            }
        }
Example #4
0
        //Get the positions where this edge starts and end
        public Edge3 GetEdgeEndPoints()
        {
            Vector3 p1 = this.halfEdge.prevEdge.v.position;
            Vector3 p2 = this.halfEdge.v.position;

            Edge3 e = new Edge3(p1, p2);

            return(e);
        }
Example #5
0
        //3d
        public static MyVector3 GetLinePlaneIntersectionPoint(Plane3 plane, Edge3 line)
        {
            MyVector3 lineDir = MyVector3.Normalize(line.p1 - line.p2);

            Ray3 ray = new Ray3(line.p1, lineDir);

            MyVector3 intersectionPoint = GetIntersectionCoordinate(plane, ray);

            return(intersectionPoint);
        }
        //Cut a triangle where two vertices are inside and the other vertex is outside
        //Make sure they are sorted clockwise: O1-O2-I1
        //F means that this vertex is outside the plane
        private static void CutTriangleTwoOutside(MyMeshVertex O1, MyMeshVertex O2, MyMeshVertex I1, HalfEdgeData3 newMeshO, HalfEdgeData3 newMeshI, HashSet <HalfEdge3> newEdgesI, HashSet <HalfEdge3> newEdgesO, Plane3 cutPlane)
        {
            //Cut the triangle by using edge-plane intersection
            //Triangles in Unity are ordered clockwise, so form edges that intersects with the plane:
            Edge3 e_O2I1 = new Edge3(O2.position, I1.position);
            //Edge3 e_F1F2 = new Edge3(F1, F2); //Not needed because never intersects with the plane
            Edge3 e_I1O1 = new Edge3(I1.position, O1.position);

            //The positions of the intersection vertices
            MyVector3 pos_O2I1 = _Intersections.GetLinePlaneIntersectionPoint(cutPlane, e_O2I1);
            MyVector3 pos_I1O1 = _Intersections.GetLinePlaneIntersectionPoint(cutPlane, e_I1O1);

            //The normals of the intersection vertices
            float percentageBetween_O2I1 = MyVector3.Distance(O2.position, pos_O2I1) / MyVector3.Distance(O2.position, I1.position);
            float percentageBetween_I1O1 = MyVector3.Distance(I1.position, pos_I1O1) / MyVector3.Distance(I1.position, O1.position);

            MyVector3 normal_O2I1 = _Interpolation.Lerp(O2.normal, I1.normal, percentageBetween_O2I1);
            MyVector3 normal_I1O1 = _Interpolation.Lerp(I1.normal, O1.normal, percentageBetween_I1O1);

            //MyVector3 normal_F2B1 = Vector3.Slerp(F2.normal.ToVector3(), B1.normal.ToVector3(), percentageBetween_F2B1).ToMyVector3();
            //MyVector3 normal_B1F1 = Vector3.Slerp(B1.normal.ToVector3(), F1.normal.ToVector3(), percentageBetween_B1F1).ToMyVector3();

            normal_O2I1 = MyVector3.Normalize(normal_O2I1);
            normal_I1O1 = MyVector3.Normalize(normal_I1O1);

            //The intersection vertices
            MyMeshVertex v_O2I1 = new MyMeshVertex(pos_O2I1, normal_O2I1);
            MyMeshVertex v_I1O1 = new MyMeshVertex(pos_I1O1, normal_I1O1);


            //Form 3 new triangles
            //Outside
            AddTriangleToMesh(v_O2I1, v_I1O1, O2, newMeshO, newEdgesO);
            AddTriangleToMesh(O2, v_I1O1, O1, newMeshO, null);
            //Inside
            AddTriangleToMesh(v_I1O1, v_O2I1, I1, newMeshI, newEdgesI);
        }
Example #7
0
        //From a list of points, find the two points that are furthest away from each other
        private static Edge3 FindEdgeFurthestApart(HashSet <Vector3> pointsHashSet)
        {
            List <Vector3> points = new List <Vector3>(pointsHashSet);


            //Instead of using all points, find the points on the AABB
            Vector3 maxX = points[0]; //Cant use default because default doesnt exist and might be a min point
            Vector3 minX = points[0];
            Vector3 maxY = points[0];
            Vector3 minY = points[0];
            Vector3 maxZ = points[0];
            Vector3 minZ = points[0];

            for (int i = 1; i < points.Count; i++)
            {
                Vector3 p = points[i];

                if (p.x > maxX.x)
                {
                    maxX = p;
                }
                if (p.x < minX.x)
                {
                    minX = p;
                }

                if (p.y > maxY.y)
                {
                    maxY = p;
                }
                if (p.y < minY.y)
                {
                    minY = p;
                }

                if (p.z > maxZ.z)
                {
                    maxZ = p;
                }
                if (p.z < minZ.z)
                {
                    minZ = p;
                }
            }

            //Some of these might be the same point (like minZ and minY)
            //But we have earlier check that the points have a width greater than 0, so we should get the points we need
            HashSet <Vector3> extremePointsHashSet = new HashSet <Vector3>();

            extremePointsHashSet.Add(maxX);
            extremePointsHashSet.Add(minX);
            extremePointsHashSet.Add(maxY);
            extremePointsHashSet.Add(minY);
            extremePointsHashSet.Add(maxZ);
            extremePointsHashSet.Add(minZ);

            points = new List <Vector3>(extremePointsHashSet);


            //Find all possible combinations of edges between all points
            List <Edge3> pointCombinations = new List <Edge3>();

            for (int i = 0; i < points.Count; i++)
            {
                Vector3 p1 = points[i];

                for (int j = i + 1; j < points.Count; j++)
                {
                    Vector3 p2 = points[j];

                    Edge3 e = new Edge3(p1, p2);

                    pointCombinations.Add(e);
                }
            }


            //Find the edge that is the furthest apart

            //Init by picking the first edge
            Edge3 eFurthestApart = pointCombinations[0];

            // float maxDistanceBetween = Vector3.SqrDistance(eFurthestApart.p1, eFurthestApart.p2);
            //
            //
            // //Try to find a better edge
            // for (int i = 1; i < pointCombinations.Count; i++)
            // {
            //     Edge3 e = pointCombinations[i];
            //
            //     float distanceBetween = Vector3.SqrDistance(e.p1, e.p2);
            //
            //     if (distanceBetween > maxDistanceBetween)
            //     {
            //         maxDistanceBetween = distanceBetween;
            //
            //         eFurthestApart = e;
            //     }
            // }

            return(eFurthestApart);
        }
Example #8
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);
             * }
             */
        }
Example #9
0
        //TODO:
        //- Calculate the optimal contraction target v and not just the average between two vertices or one of the two endpoints
        //- Sometimes at the end of a simplification process, the QEM is NaN because the normal of the triangle has length 0 because two vertices are at the same position. This has maybe to do with "mesh inversion." The reports says that you should compare the normal of each neighboring face before and after the contraction. If the normal flips, undo the contraction or penalize it. The temp solution to solve this problem is to set the matrix to zero matrix if the normal is NaN
        //- The algorithm can also join vertices that are within ||v1 - v2|| < distance, so test to add that. It should merge the hole in the bunny
        //- Maybe there's a faster (and simpler) way by using unique edges instead of double the calculations for an edge going in the opposite direction?
        //- A major bottleneck is finding edges going to a specific vertex. The problem is that if there are holes in the mesh, we can't just rotate around the vertex to find the edges - we have to search through ALL edges. In the regular mesh structure, we have a list of all vertices, so moving a vertex would be fast if we moved it in that list, so all edges should reference a vertex in a list?
        //- Is edgesToContract the correct way to stop the algorithm? Maybe it should be number of vertices in the final mesh?
        //- Visualize the error by using some color scale.
        //- Some times when we contract an edge we end up with invalid triangles, such as triangles with area 0. Are all these automatically removed if we weigh each error with triangle area? Or do we need to check that the triangulation is valid after contracting an edge?



        /// <summary>
        /// Merge edges to simplify a mesh
        /// Based on reports by Garland and Heckbert, "Surface simplification using quadric error metrics"
        /// Is called: "Iterative pair contraction with the Quadric Error Metric (QEM)"
        /// </summary>
        /// <param name="halfEdgeMeshData">Original mesh</param>
        /// <param name="maxEdgesToContract">How many edges do we want to merge (the algorithm stops if it can't merge more edges)</param>
        /// <param name="maxError">Stop merging edges if the error is bigger than the maxError, which will prevent the algorithm from changing the shape of the mesh</param>
        /// <param name="normalizeTriangles">Sometimes the quality improves if we take triangle area into account when calculating ther error</param>
        /// <param name="normalizer">Is only needed for debugging</param>
        /// <returns>The simplified mesh</returns>
        /// If you set edgesToContract to max value, then it will continue until it cant merge any more edges or the maxError is reached
        /// If you set maxError to max value, then it will continue to merge edges until it cant merge or max edgesToContract is reached
        public static HalfEdgeData3 Simplify(HalfEdgeData3 halfEdgeMeshData, int maxEdgesToContract, float maxError, bool normalizeTriangles = false, Normalizer3 normalizer = null)
        {
            System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();


            //
            // 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 <Vector3, Matrix4x4> qMatrices = new Dictionary <Vector3, 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 = 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)
            {
                Vector3 p1 = halfEdge.prevEdge.v.position;
                Vector3 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);
            }



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

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


            //Timers: 0.78 to generate the simplified bunny (2400 edge contractions) (normalizing triangles is 0.05 seconds slower)
            //Init:
            // - 0.1 to convert to half-edge data structure
            // - 0.14 to calculate a Q matrix for each unique vertex
            //Loop (total time):
            // - 0.04 to find smallest QEM error
            // - 0.25 to merge the edges (the bottleneck is where we have to find all edges pointing to a vertex)
            // - 0.02 to remove the data that was destroyed when we contracted an edge
            // - 0.13 to update QEM edges
            //Debug.Log($"It took {timer.ElapsedMilliseconds / 1000f} seconds to measure whatever we measured");


            return(halfEdgeMeshData);
        }