public void MeshValidation_IsSplitFace_DetectsDisconnectedTriangles() { var cube = TestUtility.CreateCubeWithNonContiguousMergedFace(); Assume.That(cube.item1.faceCount, Is.EqualTo(5)); Assert.That(MeshValidation.ContainsNonContiguousTriangles(cube.item1, cube.item2), Is.True); }
public void MeshValidation_IsSplitFace_ConfirmsValidFaces([ValueSource("k_IndexCounts")] int indexCount) { var points = new Vector3[indexCount]; var indices = new int[(indexCount - 1) * 3]; for (int i = 0; i < indexCount; i++) { float travel = ((i + 1) / (float)indexCount) * Mathf.PI * 2f; points[i] = new Vector3(Mathf.Cos(travel), 0f, Mathf.Sin(travel)); } for (int i = 1; i < indexCount - 1; i++) { indices[(i - 1) * 3 + 0] = 0; indices[(i - 1) * 3 + 1] = i; indices[(i - 1) * 3 + 2] = (i + 1) % indexCount; } var shape = ProBuilderMesh.Create(points, new Face[] { new Face(indices) }); Assume.That(shape, Is.Not.Null); var face = shape.faces.First(); Assume.That(face, Is.Not.Null); Assume.That(face.edgesInternal, Has.Length.EqualTo(indexCount)); Assert.That(MeshValidation.ContainsNonContiguousTriangles(shape, face), Is.False); }
/// <summary> /// Condense co-incident vertex positions per-face. vertices must already be marked as shared in the sharedIndexes /// array to be considered. This method is really only useful after merging faces. /// </summary> /// <param name="mesh"></param> /// <param name="faces"></param> internal static void CollapseCoincidentVertices(ProBuilderMesh mesh, IEnumerable <Face> faces) { Dictionary <int, int> lookup = new Dictionary <int, int>(); SharedVertex.GetSharedVertexLookup(mesh.sharedVertices, lookup); Dictionary <int, int> matches = new Dictionary <int, int>(); foreach (Face face in faces) { matches.Clear(); int[] indexes = face.indexes.ToArray(); for (int i = 0; i < indexes.Length; i++) { int common = lookup[face.indexes[i]]; if (matches.ContainsKey(common)) { indexes[i] = matches[common]; } else { matches.Add(common, indexes[i]); } } face.SetIndexes(indexes); face.Reverse(); face.Reverse(); } MeshValidation.RemoveUnusedVertices(mesh); //mesh.RemoveUnusedVertices(); }
void DoSceneViewOverlay() { Handles.BeginGUI(); GUILayout.Space(4); GUILayout.BeginHorizontal(); GUILayout.Space(4); GUILayout.BeginVertical(EditorStyles.helpBox); if (m_Meshes.Length > 0) { for (int i = 0, c = m_Meshes.Length; i < c; ++i) { GUILayout.BeginHorizontal(); if (m_Invalid[i].Count > 0 && GUILayout.Button("Fix")) { MeshValidation.EnsureMeshIsValid(m_Meshes[i], out var removedVertexCount); Debug.Log($"Successfully repaired {m_Meshes[i].name}. Removed {removedVertexCount} problem vertices."); } GUILayout.Label($"Mesh \"{m_Meshes[i].name}\" found {m_Invalid[i].Count} problems"); GUILayout.EndHorizontal(); } } else { GUILayout.Label("Select a ProBuilder Mesh"); } GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); Handles.EndGUI(); }
public static void MenuRemoveDegenerateTriangles() { int count = 0; foreach (ProBuilderMesh pb in InternalUtility.GetComponents <ProBuilderMesh>(Selection.transforms)) { int removedVertexCount; if (!MeshValidation.EnsureMeshIsValid(pb, out removedVertexCount)) { pb.Rebuild(); pb.Optimize(); count += removedVertexCount; } } EditorUtility.ShowNotification("Removed " + count + " vertices \nbelonging to degenerate triangles."); }
protected override ActionResult PerformActionImplementation() { if (MeshSelection.selectedObjectCount < 1) { return(ActionResult.NoSelection); } ActionResult res = ActionResult.NoSelection; UndoUtility.RecordSelection("Weld Vertices"); int weldCount = 0; foreach (ProBuilderMesh mesh in MeshSelection.topInternal) { weldCount += mesh.sharedVerticesInternal.Length; if (mesh.selectedIndexesInternal.Length > 1) { mesh.ToMesh(); var selectedVertices = mesh.GetCoincidentVertices(mesh.selectedVertices); int[] welds = mesh.WeldVertices(mesh.selectedIndexesInternal, m_WeldDistance); res = welds != null ? new ActionResult(ActionResult.Status.Success, "Weld Vertices") : new ActionResult(ActionResult.Status.Failure, "Failed Weld Vertices"); if (res) { var newSelection = welds ?? new int[0] { }; if (MeshValidation.ContainsDegenerateTriangles(mesh)) { List <int> removedIndices = new List <int>(); var vertexCount = mesh.vertexCount; if (MeshValidation.RemoveDegenerateTriangles(mesh, removedIndices)) { if (removedIndices.Count < vertexCount) { var newlySelectedVertices = new List <int>(); selectedVertices.Sort(); removedIndices.Sort(); int count = 0; for (int i = 0; i < selectedVertices.Count; i++) { if (count >= removedIndices.Count || selectedVertices[i] != removedIndices[count]) { newlySelectedVertices.Add(selectedVertices[i] - UnityEngine.ProBuilder.ArrayUtility.NearestIndexPriorToValue(removedIndices, selectedVertices[i]) - 1); } else { ++count; } } newSelection = newlySelectedVertices.ToArray(); } else { newSelection = new int[0]; } } mesh.ToMesh(); } mesh.SetSelectedVertices(newSelection); } mesh.Refresh(); mesh.Optimize(); } weldCount -= mesh.sharedVerticesInternal.Length; } ProBuilderEditor.Refresh(); if (res && weldCount > 0) { return(new ActionResult(ActionResult.Status.Success, "Weld " + weldCount + (weldCount > 1 ? " Vertices" : " Vertex"))); } return(new ActionResult(ActionResult.Status.Failure, "Nothing to Weld")); }
public virtual ValidationStatus Validate() { ValidationStatus loopStatus = MeshValidation.IsBoundaryLoop(Mesh, Loop); return(loopStatus); }
public void Close_Flat() { double minlen, maxlen, avglen; MeshQueries.EdgeLengthStats(Mesh, out minlen, out maxlen, out avglen, 1000); double target_edge_len = (TargetEdgeLen <= 0) ? avglen : TargetEdgeLen; // massage around boundary loop cleanup_boundary(Mesh, InitialBorderLoop, avglen, 3); // find new border loop // [TODO] this just assumes there is only one!! MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh); EdgeLoop fill_loop = loops.Loops[0]; int extrude_group = (ExtrudeGroup == -1) ? Mesh.AllocateTriangleGroup() : ExtrudeGroup; int fill_group = (FillGroup == -1) ? Mesh.AllocateTriangleGroup() : FillGroup; // decide on projection plane //AxisAlignedBox3d loopbox = fill_loop.GetBounds(); //Vector3d topPt = loopbox.Center; //if ( bIsUpper ) { // topPt.y = loopbox.Max.y + 0.25 * dims.y; //} else { // topPt.y = loopbox.Min.y - 0.25 * dims.y; //} //Frame3f plane = new Frame3f((Vector3f)topPt); // extrude loop to this plane MeshExtrusion extrude = new MeshExtrusion(Mesh, fill_loop); extrude.PositionF = (v, n, i) => { return(FlatClosePlane.ProjectToPlane((Vector3F)v, 1)); }; extrude.Extrude(extrude_group); MeshValidation.IsBoundaryLoop(Mesh, extrude.NewLoop); Debug.Assert(Mesh.CheckValidity()); // smooth the extrude loop MeshLoopSmooth loop_smooth = new MeshLoopSmooth(Mesh, extrude.NewLoop); loop_smooth.ProjectF = (v, i) => { return(FlatClosePlane.ProjectToPlane((Vector3F)v, 1)); }; loop_smooth.Alpha = 0.5f; loop_smooth.Rounds = 100; loop_smooth.Smooth(); Debug.Assert(Mesh.CheckValidity()); // fill result SimpleHoleFiller filler = new SimpleHoleFiller(Mesh, extrude.NewLoop); filler.Fill(fill_group); Debug.Assert(Mesh.CheckValidity()); // make selection for remesh region MeshFaceSelection remesh_roi = new MeshFaceSelection(Mesh); remesh_roi.Select(extrude.NewTriangles); remesh_roi.Select(filler.NewTriangles); remesh_roi.ExpandToOneRingNeighbours(); remesh_roi.ExpandToOneRingNeighbours(); remesh_roi.LocalOptimize(true, true); int[] new_roi = remesh_roi.ToArray(); // get rid of extrude group FaceGroupUtil.SetGroupToGroup(Mesh, extrude_group, 0); /* clean up via remesh * - constrain loop we filled to itself */ RegionRemesher r = new RegionRemesher(Mesh, new_roi); DCurve3 top_curve = mesh.MeshUtil.ExtractLoopV(Mesh, extrude.NewLoop.Vertices); DCurveProjectionTarget curve_target = new DCurveProjectionTarget(top_curve); int[] top_loop = (int[])extrude.NewLoop.Vertices.Clone(); r.Region.MapVerticesToSubmesh(top_loop); MeshConstraintUtil.ConstrainVtxLoopTo(r.Constraints, r.Mesh, top_loop, curve_target); DMeshAABBTree3 spatial = new DMeshAABBTree3(Mesh); spatial.Build(); MeshProjectionTarget target = new MeshProjectionTarget(Mesh, spatial); r.SetProjectionTarget(target); bool bRemesh = true; if (bRemesh) { r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = target_edge_len; r.MaxEdgeLength = 2 * target_edge_len; r.EnableSmoothing = true; r.SmoothSpeedT = 1.0f; for (int k = 0; k < 40; ++k) { r.BasicRemeshPass(); } r.SetProjectionTarget(null); r.SmoothSpeedT = 0.25f; for (int k = 0; k < 10; ++k) { r.BasicRemeshPass(); } Debug.Assert(Mesh.CheckValidity()); r.BackPropropagate(); } // smooth around the join region to clean up ugliness smooth_region(Mesh, r.Region.BaseBorderV, 3); }