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