void remove_remaining_interior_verts() { HashSet <int> interiorv = new HashSet <int>(MeshIterators.InteriorVertices(fillmesh)); int prev_count = 0; while (interiorv.Count > 0 && interiorv.Count != prev_count) { prev_count = interiorv.Count; int[] curv = interiorv.ToArray(); foreach (int vid in curv) { foreach (int e in fillmesh.VtxEdgesItr(vid)) { Index2i ev = fillmesh.GetEdgeV(e); int otherv = (ev.a == vid) ? ev.b : ev.a; DMesh3.EdgeCollapseInfo info; MeshResult result = fillmesh.CollapseEdge(otherv, vid, out info); if (result == MeshResult.Ok) { break; } } if (fillmesh.IsVertex(vid) == false) { interiorv.Remove(vid); } } } if (interiorv.Count > 0) { Util.gBreakToDebugger(); } }
public DMesh3 remesh_constraints_fixedverts(int iterations, DMesh3 mesh, double min, double max, double angle) { AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > angle) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.Precompute(); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = min; r.MaxEdgeLength = max; r.EnableSmoothing = true; r.SmoothSpeedT = 1; for (int k = 0; k < iterations; ++k) { r.BasicRemeshPass(); mesh.CheckValidity(); } return(mesh); }
public static void collapse_tests(bool bTestBoundary, int N = 100) { bool write_debug_meshes = false; DMesh3 mesh = TestUtil.MakeCappedCylinder(bTestBoundary); mesh.CheckValidity(); System.Console.WriteLine(string.Format("DMesh3:collapse_tests() starting - test bdry {2}, verts {0} tris {1}", mesh.VertexCount, mesh.TriangleCount, bTestBoundary)); if (write_debug_meshes) { TestUtil.WriteDebugMesh(mesh, string.Format("before_collapse_{0}.obj", ((bTestBoundary)?"boundary":"noboundary"))); } Random r = new Random(31377); for (int k = 0; k < N; ++k) { int eid = r.Next() % mesh.EdgeCount; if (!mesh.IsEdge(eid)) { continue; } //bool bBoundary = mesh.IsBoundaryEdge(eid); //if (bTestBoundary && bBoundary == false) // continue; Index2i ev = mesh.GetEdgeV(eid); DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = mesh.CollapseEdge(ev[0], ev[1], out collapseInfo); Debug.Assert( result != MeshResult.Failed_NotAnEdge && result != MeshResult.Failed_FoundDuplicateTriangle); mesh.CheckValidity(); } System.Console.WriteLine(string.Format("random collapses ok - verts {0} tris {1}", mesh.VertexCount, mesh.TriangleCount)); collapse_to_convergence(mesh); System.Console.WriteLine(string.Format("all possible collapses ok - verts {0} tris {1}", mesh.VertexCount, mesh.TriangleCount)); if (write_debug_meshes) { TestUtil.WriteDebugMesh(mesh, string.Format("after_collapse_{0}.obj", ((bTestBoundary)?"boundary":"noboundary"))); } }
static int find_pair_edge(DMesh3 mesh, int eid, List <int> candidates) { Index2i ev = mesh.GetEdgeV(eid); Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b); double eps = 100 * MathUtil.Epsilonf; foreach (int eother in candidates) { if (eother == eid) { continue; } Index2i ov = mesh.GetEdgeV(eother); Vector3d c = mesh.GetVertex(ov.a), d = mesh.GetVertex(ov.b); if ((a.EpsilonEqual(c, eps) && b.EpsilonEqual(d, eps)) || (b.EpsilonEqual(c, eps) && a.EpsilonEqual(d, eps))) { return(eother); } } ; return(DMesh3.InvalidID); }
public static DMesh3 MakeRemeshedCappedCylinder(double fResFactor = 1.0) { DMesh3 mesh = MakeCappedCylinder(false, 128); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new g3.Vector3f(1, 2, 1)); // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; MeshConstraints cons = new MeshConstraints(); EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > 1) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > 1) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.1f * fResFactor; r.MaxEdgeLength = 0.2f * fResFactor; r.EnableSmoothing = true; r.SmoothSpeedT = 0.5f; for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); } return(mesh); }
bool collapse_degenerate_edges( double minLength, bool bBoundaryOnly, out int collapseCount) { collapseCount = 0; // don't iterate sequentially because there may be pathological cases foreach (int eid in MathUtil.ModuloIteration(Mesh.MaxEdgeID)) { if (Cancelled()) { break; } if (Mesh.IsEdge(eid) == false) { continue; } bool is_boundary_edge = Mesh.IsBoundaryEdge(eid); if (bBoundaryOnly && is_boundary_edge == false) { continue; } Index2i ev = Mesh.GetEdgeV(eid); Vector3d a = Mesh.GetVertex(ev.a), b = Mesh.GetVertex(ev.b); if (a.Distance(b) < minLength) { int keep = Mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b; int discard = (keep == ev.a) ? ev.b : ev.a; DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = Mesh.CollapseEdge(keep, discard, out collapseInfo); if (result == MeshResult.Ok) { ++collapseCount; if (Mesh.IsBoundaryVertex(keep) == false || is_boundary_edge) { Mesh.SetVertex(keep, (a + b) * 0.5); } } } } return(true); }
private bool CollapseDegenerateEdges(DMesh3 mesh, CancellationToken cancellationToken, double minLength, bool bBoundaryOnly, out int collapseCount) { collapseCount = 0; // don't iterate sequentially because there may be pathological cases foreach (int eid in MathUtil.ModuloIteration(mesh.MaxEdgeID)) { cancellationToken.ThrowIfCancellationRequested(); if (mesh.IsEdge(eid) == false) { continue; } bool isBoundaryEdge = mesh.IsBoundaryEdge(eid); if (bBoundaryOnly && isBoundaryEdge == false) { continue; } Index2i ev = mesh.GetEdgeV(eid); Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b); if (a.Distance(b) < minLength) { int keep = mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b; int discard = (keep == ev.a) ? ev.b : ev.a; MeshResult result = mesh.CollapseEdge(keep, discard, out DMesh3.EdgeCollapseInfo collapseInfo); if (result == MeshResult.Ok) { ++collapseCount; if (mesh.IsBoundaryVertex(keep) == false || isBoundaryEdge) { mesh.SetVertex(keep, (a + b) * 0.5); } } } } return(true); }
int find_shortest_internal_edge(DMesh3 mesh) { double shortSqr = double.MaxValue; int short_eid = -1; Vector3d va, vb; va = vb = Vector3d.Zero; foreach (int eid in mesh.VertexIndices()) { if (mesh.IsBoundaryEdge(eid)) { continue; } mesh.GetEdgeV(eid, ref va, ref vb); double lenSqr = va.DistanceSquared(ref vb); if (lenSqr < shortSqr) { shortSqr = lenSqr; short_eid = eid; } } return(short_eid); }
// this function collapses edges until it can't anymore static void collapse_to_convergence(DMesh3 mesh) { bool bContinue = true; while (bContinue) { bContinue = false; for (int eid = 0; eid < mesh.MaxEdgeID; ++eid) { if (!mesh.IsEdge(eid)) { continue; } Index2i ev = mesh.GetEdgeV(eid); DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = mesh.CollapseEdge(ev[0], ev[1], out collapseInfo); if (result == MeshResult.Ok) { bContinue = true; break; } } } }
public static Mesh RemeshTest(Mesh inMesh, double fResScale = 1.0, int iterations = 50) { inMesh.Faces.ConvertQuadsToTriangles(); DMesh3 mesh = inMesh.ToDMesh3(); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.Precompute(); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.5 * fResScale; r.MaxEdgeLength = 1.0 * fResScale; r.EnableSmoothing = true; r.SmoothSpeedT = 0.5; try { for (int k = 0; k < iterations; ++k) { r.BasicRemeshPass(); // mesh.CheckValidity(); } } catch { // ignore } return(mesh.ToRhinoMesh()); }
public bool Apply() { // do a simple fill SimpleHoleFiller simplefill = new SimpleHoleFiller(Mesh, FillLoop); int fill_gid = Mesh.AllocateTriangleGroup(); bool bOK = simplefill.Fill(fill_gid); if (bOK == false) { return(false); } if (FillLoop.Vertices.Length <= 3) { FillTriangles = simplefill.NewTriangles; FillVertices = new int[0]; return(true); } // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later HashSet <int> intial_fill_tris = new HashSet <int>(simplefill.NewTriangles); regionop = new RegionOperator(Mesh, simplefill.NewTriangles, (submesh) => { submesh.ComputeTriMaps = true; }); fillmesh = regionop.Region.SubMesh; // for each boundary vertex, compute the exterior angle sum // we will use this to compute gaussian curvature later boundaryv = new HashSet <int>(MeshIterators.BoundaryEdgeVertices(fillmesh)); exterior_angle_sums = new Dictionary <int, double>(); if (IgnoreBoundaryTriangles == false) { foreach (int sub_vid in boundaryv) { double angle_sum = 0; int base_vid = regionop.Region.MapVertexToBaseMesh(sub_vid); foreach (int tid in regionop.BaseMesh.VtxTrianglesItr(base_vid)) { if (intial_fill_tris.Contains(tid) == false) { Index3i et = regionop.BaseMesh.GetTriangle(tid); int idx = IndexUtil.find_tri_index(base_vid, ref et); angle_sum += regionop.BaseMesh.GetTriInternalAngleR(tid, idx); } } exterior_angle_sums[sub_vid] = angle_sum; } } // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass double loop_mine, loop_maxe, loop_avge, fill_mine, fill_maxe, fill_avge; MeshQueries.EdgeLengthStatsFromEdges(Mesh, FillLoop.Edges, out loop_mine, out loop_maxe, out loop_avge); MeshQueries.EdgeLengthStats(fillmesh, out fill_mine, out fill_maxe, out fill_avge); double remesh_target_len = loop_avge; if (fill_maxe / remesh_target_len > 10) { remesh_target_len = fill_maxe / 10; } //double remesh_target_len = Math.Min(loop_avge, fill_avge / 4); // remesh up to target edge length, ideally gives us some triangles to work with RemesherPro remesh1 = new RemesherPro(fillmesh); remesh1.SmoothSpeedT = 1.0; MeshConstraintUtil.FixAllBoundaryEdges(remesh1); //remesh1.SetTargetEdgeLength(remesh_target_len / 2); // would this speed things up? on large regions? //remesh1.FastestRemesh(); remesh1.SetTargetEdgeLength(remesh_target_len); remesh1.FastestRemesh(); /* * first round: collapse to minimal mesh, while flipping to try to * get to ballpark minimal mesh. We stop these passes as soon as * we have done two rounds where we couldn't do another collapse * * This is the most unstable part of the algorithm because there * are strong ordering effects. maybe we could sort the edges somehow?? */ int zero_collapse_passes = 0; int collapse_passes = 0; while (collapse_passes++ < 20 && zero_collapse_passes < 2) { // collapse pass int NE = fillmesh.MaxEdgeID; int collapses = 0; for (int ei = 0; ei < NE; ++ei) { if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); bool a_bdry = boundaryv.Contains(ev.a), b_bdry = boundaryv.Contains(ev.b); if (a_bdry && b_bdry) { continue; } int keepv = (a_bdry) ? ev.a : ev.b; int otherv = (keepv == ev.a) ? ev.b : ev.a; Vector3d newv = fillmesh.GetVertex(keepv); if (MeshUtil.CheckIfCollapseCreatesFlip(fillmesh, ei, newv)) { continue; } DMesh3.EdgeCollapseInfo info; MeshResult result = fillmesh.CollapseEdge(keepv, otherv, out info); if (result == MeshResult.Ok) { collapses++; } } if (collapses == 0) { zero_collapse_passes++; } else { zero_collapse_passes = 0; } // flip pass. we flip in these cases: // 1) if angle between current triangles is too small (slightly more than 90 degrees, currently) // 2) if angle between flipped triangles is smaller than between current triangles // 3) if flipped edge length is shorter *and* such a flip won't flip the normal NE = fillmesh.MaxEdgeID; Vector3d n1, n2, on1, on2; for (int ei = 0; ei < NE; ++ei) { if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) { continue; } bool do_flip = false; Index2i ev = fillmesh.GetEdgeV(ei); MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); double dot_cur = n1.Dot(n2); double dot_flip = on1.Dot(on2); if (n1.Dot(n2) < 0.1 || dot_flip > dot_cur + MathUtil.Epsilonf) { do_flip = true; } if (do_flip == false) { Index2i otherv = fillmesh.GetEdgeOpposingV(ei); double len_e = fillmesh.GetVertex(ev.a).Distance(fillmesh.GetVertex(ev.b)); double len_flip = fillmesh.GetVertex(otherv.a).Distance(fillmesh.GetVertex(otherv.b)); if (len_flip < len_e) { if (MeshUtil.CheckIfEdgeFlipCreatesFlip(fillmesh, ei) == false) { do_flip = true; } } } if (do_flip) { DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); } } } // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?) // Try to force removal of such vertices, even if it makes ugly mesh remove_remaining_interior_verts(); // enable/disable passes. bool DO_FLATTER_PASS = true; bool DO_CURVATURE_PASS = OptimizeDevelopability && true; bool DO_AREA_PASS = OptimizeDevelopability && OptimizeTriangles && true; /* * In this pass we repeat the flipping iterations from the previous pass. * * Note that because of the always-flip-if-dot-is-small case (commented), * this pass will frequently not converge, as some number of edges will * be able to flip back and forth (because neither has large enough dot). * This is not ideal, but also, if we remove this behavior, then we * generally get worse fills. This case basically introduces a sort of * randomization factor that lets us escape local minima... * */ HashSet <int> remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); HashSet <int> updated_edges = new HashSet <int>(); int flatter_passes = 0; int zero_flips_passes = 0; while (flatter_passes++ < 40 && zero_flips_passes < 2 && remaining_edges.Count() > 0 && DO_FLATTER_PASS) { zero_flips_passes++; foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } bool do_flip = false; Index2i ev = fillmesh.GetEdgeV(ei); Vector3d n1, n2, on1, on2; MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); double dot_cur = n1.Dot(n2); double dot_flip = on1.Dot(on2); if (flatter_passes < 20 && dot_cur < 0.1) // this check causes oscillatory behavior { do_flip = true; } if (dot_flip > dot_cur + MathUtil.Epsilonf) { do_flip = true; } if (do_flip) { DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result == MeshResult.Ok) { zero_flips_passes = 0; add_all_edges(ei, updated_edges); } } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } int curvature_passes = 0; if (DO_CURVATURE_PASS) { curvatures = new double[fillmesh.MaxVertexID]; foreach (int vid in fillmesh.VertexIndices()) { update_curvature(vid); } remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); updated_edges = new HashSet <int>(); /* * In this pass we try to minimize gaussian curvature at all the vertices. * This will recover sharp edges, etc, and do lots of good stuff. * However, this pass will not make much progress if we are not already * relatively close to a minimal mesh, so it really relies on the previous * passes getting us in the ballpark. */ while (curvature_passes++ < 40 && remaining_edges.Count() > 0 && DO_CURVATURE_PASS) { foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); Index2i ov = fillmesh.GetEdgeOpposingV(ei); int find_other = fillmesh.FindEdge(ov.a, ov.b); if (find_other != DMesh3.InvalidID) { continue; } double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); if (total_curv_cur < MathUtil.ZeroTolerancef) { continue; } DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result != MeshResult.Ok) { continue; } double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); bool keep_flip = total_curv_flip < total_curv_cur - MathUtil.ZeroTolerancef; if (keep_flip == false) { result = fillmesh.FlipEdge(ei, out info); } else { update_curvature(ev.a); update_curvature(ev.b); update_curvature(ov.a); update_curvature(ov.b); add_all_edges(ei, updated_edges); } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } } //System.Console.WriteLine("collapse {0} flatter {1} curvature {2}", collapse_passes, flatter_passes, curvature_passes); /* * In this final pass, we try to improve triangle quality. We flip if * the flipped triangles have better total aspect ratio, and the * curvature doesn't change **too** much. The .DevelopabilityTolerance * parameter determines what is "too much" curvature change. */ if (DO_AREA_PASS) { remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); updated_edges = new HashSet <int>(); int area_passes = 0; while (remaining_edges.Count() > 0 && area_passes < 20) { area_passes++; foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); Index2i ov = fillmesh.GetEdgeOpposingV(ei); int find_other = fillmesh.FindEdge(ov.a, ov.b); if (find_other != DMesh3.InvalidID) { continue; } double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); double a = aspect_metric(ei); if (a > 1) { continue; } DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result != MeshResult.Ok) { continue; } double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); bool keep_flip = Math.Abs(total_curv_cur - total_curv_flip) < DevelopabilityTolerance; if (keep_flip == false) { result = fillmesh.FlipEdge(ei, out info); } else { update_curvature(ev.a); update_curvature(ev.b); update_curvature(ov.a); update_curvature(ov.b); add_all_edges(ei, updated_edges); } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } } regionop.BackPropropagate(); FillTriangles = regionop.CurrentBaseTriangles; FillVertices = regionop.CurrentBaseInteriorVertices().ToArray(); return(true); }
public DMesh3 remesh_constraints_vertcurves(int iterations, DMesh3 mesh, double min, double max, double angle) { mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget mesh_target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; // cylinder projection target CylinderProjectionTarget cyl_target = new CylinderProjectionTarget() { Cylinder = new Cylinder3d(new Vector3D(0, 1, 0), Vector3D.AxisY, 1, 2) }; //IProjectionTarget target = mesh_target; IProjectionTarget target = cyl_target; // construct projection target circles CircleProjectionTarget bottomCons = new CircleProjectionTarget() { Circle = new Circle3d(bounds.Center, 1.0) }; bottomCons.Circle.Center.y = bounds.Min.y; CircleProjectionTarget topCons = new CircleProjectionTarget() { Circle = new Circle3d(bounds.Center, 1.0) }; topCons.Circle.Center.y = bounds.Max.y; // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; bool bConstrainVertices = true; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { Index2i ev = mesh.GetEdgeV(eid); Vector3D ev0 = mesh.GetVertex(ev[0]); Vector3D ev1 = mesh.GetVertex(ev[1]); CircleProjectionTarget loopTarget = null; if (ev0.y > bounds.Center.y && ev1.y > bounds.Center.y) { loopTarget = topCons; } else if (ev0.y < bounds.Center.y && ev1.y < bounds.Center.y) { loopTarget = bottomCons; } cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags, loopTarget)); if (bConstrainVertices && loopTarget != null) { cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(loopTarget)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(loopTarget)); } } } Remesher r = new Remesher(mesh); //r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.Precompute(); r.ENABLE_PROFILING = true; r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = min; r.MaxEdgeLength = max; r.EnableSmoothing = true; r.SmoothSpeedT = 1.0f; for (int k = 0; k < iterations; ++k) { r.BasicRemeshPass(); mesh.CheckValidity(); } return(mesh); }
protected void merge_loops(DMesh3 mesh, EdgeLoop cutLoop, EdgeLoop connectorLoop, bool is_outer) { /* * To join the loops, we are going to first make a circle, then snap both * open loops to that circle. Then we sample a set of vertices on the circle * and remesh the loops while also snapping them to the circle vertices. * The result is two perfectly-matching edge loops. */ //AxisAlignedBox3d cutLoopBounds = // BoundsUtil.Bounds(cutLoop.Vertices, (vid) => { return mesh.GetVertex(vid); }); AxisAlignedBox3d cutLoopBounds = cutLoop.GetBounds(); AxisAlignedBox3d connectorLoopBounds = connectorLoop.GetBounds(); Vector3d midPt = (cutLoopBounds.Center + connectorLoopBounds.Center) * 0.5; double midY = midPt.y; // this mess construcst the circle and the sampled version Frame3f circFrame = new Frame3f(midPt); Circle3d circ = new Circle3d(circFrame, connectorLoopBounds.Width * 0.5, 1); DistPoint3Circle3 dist = new DistPoint3Circle3(Vector3d.Zero, circ); DCurve3 sampled = new DCurve3(); double target_edge_len = TargetMeshEdgeLength; int N = (int)(circ.ArcLength / target_edge_len); for (int k = 0; k < N; ++k) { sampled.AppendVertex(circ.SampleT((double)k / (double)N)); } MergeProjectionTarget circleTarget = new MergeProjectionTarget() { Mesh = mesh, CircleDist = dist, CircleLoop = sampled }; EdgeLoop[] loops = new EdgeLoop[2] { cutLoop, connectorLoop }; EdgeLoop[] outputLoops = new EdgeLoop[2]; // loops after this remeshing/etc (but might be missing some verts/edges!) for (int li = 0; li < 2; ++li) { EdgeLoop loop = loops[li]; // snap the loop verts onto the analytic circle foreach (int vid in loop.Vertices) { Vector3d v = mesh.GetVertex(vid); dist.Point = new Vector3d(v.x, midY, v.z); mesh.SetVertex(vid, dist.Compute().CircleClosest); } if (DebugStep <= 5) { continue; } // remesh around the edge loop while we snap it to the sampled circle verts EdgeLoopRemesher loopRemesh = new EdgeLoopRemesher(mesh, loop) { LocalSmoothingRings = 3 }; loopRemesh.EnableParallelProjection = false; loopRemesh.SetProjectionTarget(circleTarget); loopRemesh.SetTargetEdgeLength(TargetMeshEdgeLength); loopRemesh.SmoothSpeedT = 0.5f; for (int k = 0; k < 5; ++k) { loopRemesh.BasicRemeshPass(); } loopRemesh.SmoothSpeedT = 0; for (int k = 0; k < 2; ++k) { loopRemesh.BasicRemeshPass(); } EdgeLoop newLoop = loopRemesh.OutputLoop; outputLoops[li] = newLoop; if (DebugStep <= 6) { continue; } // hard snap the loop vertices to the sampled circle verts foreach (int vid in newLoop.Vertices) { Vector3d v = mesh.GetVertex(vid); v = circleTarget.Project(v, vid); mesh.SetVertex(vid, v); } // [TODO] we could re-order newLoop verts/edges to match the sampled verts order, // then the pair of loops would be consistently ordered (currently no guarantee) if (DebugStep <= 7) { continue; } // collapse any degenerate edges on loop (just in case) // DANGER: if this actually happens, then outputLoops[li] has some invalid verts/edges! foreach (int eid in newLoop.Edges) { if (mesh.IsEdge(eid)) { Index2i ev = mesh.GetEdgeV(eid); Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b); if (a.Distance(b) < TargetMeshEdgeLength * 0.001) { DMesh3.EdgeCollapseInfo collapse; mesh.CollapseEdge(ev.a, ev.b, out collapse); } } } } if (DebugStep <= 7) { return; } /* * Ok now we want to merge the loops and make them nice */ // would be more efficient to find loops and stitch them... MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh); merge.Apply(); // fill any fail-holes?? // remesh merge region MeshVertexSelection remesh_roi_v = new MeshVertexSelection(mesh); remesh_roi_v.Select(outputLoops[0].Vertices); remesh_roi_v.Select(outputLoops[1].Vertices); remesh_roi_v.ExpandToOneRingNeighbours(5); MeshFaceSelection remesh_roi = new MeshFaceSelection(mesh, remesh_roi_v, 1); remesh_roi.LocalOptimize(true, true); IProjectionTarget projTarget = null; if (is_outer) { projTarget = new NoPenetrationProjectionTarget() { Spatial = this.OuterOffsetMeshSpatial }; } else { projTarget = new NoPenetrationProjectionTarget() { Spatial = this.InnerOffsetMeshSpatial }; } RegionRemesher join_remesh = RegionRemesher.QuickRemesh(mesh, remesh_roi.ToArray(), TargetMeshEdgeLength, 0.5, 5, projTarget); if (DebugStep <= 8) { return; } if (false && is_outer) { Func <int, bool> filterF = (tid) => { return(mesh.GetTriCentroid(tid).y > connectorLoopBounds.Max.y); }; MeshFaceSelection tris = new MeshFaceSelection(mesh); foreach (int tid in join_remesh.CurrentBaseTriangles) { if (filterF(tid)) { tris.Select(tid); } } tris.ExpandToOneRingNeighbours(5, filterF); MeshVertexSelection verts = new MeshVertexSelection(mesh, tris); MeshIterativeSmooth smooth = new MeshIterativeSmooth(mesh, verts.ToArray(), true); smooth.Alpha = 1.0f; smooth.Rounds = 25; smooth.ProjectF = (v, n, vid) => { return(projTarget.Project(v)); }; smooth.Smooth(); } // [RMS] this smooths too far. we basically only want to smooth 'up' from top of socket... //LaplacianMeshSmoother.RegionSmooth(mesh, join_remesh.CurrentBaseTriangles, 1, 10); // need to post-enforce thickness, which we aren't doing above - we could though }
public static void test_remesh_constraints_fixedverts() { int Slices = 128; DMesh3 mesh = TestUtil.MakeCappedCylinder(false, Slices); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_fixed_constraints_test_before.obj"); } // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.Precompute(); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); var stopwatch = Stopwatch.StartNew(); //double fResScale = 1.0f; double fResScale = 0.5f; r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.1f * fResScale; r.MaxEdgeLength = 0.2f * fResScale; r.EnableSmoothing = true; r.SmoothSpeedT = 0.5f; try { for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); mesh.CheckValidity(); } } catch { // ignore } stopwatch.Stop(); System.Console.WriteLine("Second Pass Timing: " + stopwatch.Elapsed); if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_fixed_constraints_test_after.obj"); } }
public static void test_reduce_constraints_fixedverts() { int Slices = 128; DMesh3 mesh = TestUtil.MakeCappedCylinder(false, Slices); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; if (WriteDebugMeshes) { TestUtil.WriteTestOutputMesh(mesh, "reduce_fixed_constraints_test_before.obj"); } // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.PreserveTopology; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags) { TrackingSetID = 1 }); Index2i ev = mesh.GetEdgeV(eid); int nSetID0 = (mesh.GetVertex(ev[0]).y > bounds.Center.y) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > bounds.Center.y) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Reducer r = new Reducer(mesh); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.ReduceToTriangleCount(50); mesh.CheckValidity(); if (WriteDebugMeshes) { TestUtil.WriteTestOutputMesh(mesh, "reduce_fixed_constraints_test_after.obj"); } }
public static void test_remesh_constraints_vertcurves() { int Slices = 16; DMesh3 mesh = TestUtil.MakeCappedCylinder(false, Slices); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); //DMesh3 mesh = TestUtil.MakeRemeshedCappedCylinder(0.25); //DMesh3 mesh = TestUtil.MakeRemeshedCappedCylinder(1.0); mesh.CheckValidity(); AxisAlignedBox3d bounds = mesh.CachedBounds; // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); meshCopy.CheckValidity(); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget mesh_target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; // cylinder projection target CylinderProjectionTarget cyl_target = new CylinderProjectionTarget() { Cylinder = new Cylinder3d(new Vector3d(0, 1, 0), Vector3d.AxisY, 1, 2) }; //IProjectionTarget target = mesh_target; IProjectionTarget target = cyl_target; // construct projection target circles CircleProjectionTarget bottomCons = new CircleProjectionTarget() { Circle = new Circle3d(bounds.Center, 1.0) }; bottomCons.Circle.Center.y = bounds.Min.y; CircleProjectionTarget topCons = new CircleProjectionTarget() { Circle = new Circle3d(bounds.Center, 1.0) }; topCons.Circle.Center.y = bounds.Max.y; if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_analytic_constraints_test_before.obj"); } // construct constraint set MeshConstraints cons = new MeshConstraints(); //EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse; EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; bool bConstrainVertices = true; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > 30.0f) { Index2i ev = mesh.GetEdgeV(eid); Vector3d ev0 = mesh.GetVertex(ev[0]); Vector3d ev1 = mesh.GetVertex(ev[1]); CircleProjectionTarget loopTarget = null; if (ev0.y > bounds.Center.y && ev1.y > bounds.Center.y) { loopTarget = topCons; } else if (ev0.y < bounds.Center.y && ev1.y < bounds.Center.y) { loopTarget = bottomCons; } cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags, loopTarget)); if (bConstrainVertices && loopTarget != null) { cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(loopTarget)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(loopTarget)); } } } Remesher r = new Remesher(mesh); //r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.Precompute(); r.ENABLE_PROFILING = true; var stopwatch = Stopwatch.StartNew(); //double fResScale = 1.0f; double fResScale = 0.5f; r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.1f * fResScale; r.MaxEdgeLength = 0.2f * fResScale; r.EnableSmoothing = true; r.SmoothSpeedT = 1.0f; try { for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); mesh.CheckValidity(); } } catch { // continue; } stopwatch.Stop(); System.Console.WriteLine("Second Pass Timing: " + stopwatch.Elapsed); if (WriteDebugMeshes) { TestUtil.WriteDebugMesh(mesh, "remesh_analytic_constraints_test_after.obj"); } }
void extract_topology() { var graph = new DGraph3(); // add vertices to graph, and store mappings int[] mapV = new int[Mesh.MaxVertexID]; int[] mapVFrom = new int[AllVertices.Count]; foreach (int vid in AllVertices) { int new_vid = graph.AppendVertex(Mesh.GetVertex(vid)); mapV[vid] = new_vid; mapVFrom[new_vid] = vid; } // add edges to graph. graph-to-mesh eid mapping is stored via graph edge-group-id int[] mapE = new int[Mesh.MaxEdgeID]; foreach (int eid in AllEdges) { Index2i ev = Mesh.GetEdgeV(eid); int new_a = mapV[ev.a]; int new_b = mapV[ev.b]; int new_eid = graph.AppendEdge(new_a, new_b, eid); mapE[eid] = new_eid; } // extract the graph topology DGraph3Util.Curves curves = DGraph3Util.ExtractCurves(graph, true); // reconstruct mesh spans / curves / junctions from graph topology int NP = curves.PathEdges.Count; Spans = new EdgeSpan[NP]; for (int pi = 0; pi < NP; ++pi) { List <int> pathE = curves.PathEdges[pi]; for (int k = 0; k < pathE.Count; ++k) { pathE[k] = graph.GetEdgeGroup(pathE[k]); } Spans[pi] = EdgeSpan.FromEdges(Mesh, pathE); } int NL = curves.LoopEdges.Count; Loops = new EdgeLoop[NL]; for (int li = 0; li < NL; ++li) { List <int> loopE = curves.LoopEdges[li]; for (int k = 0; k < loopE.Count; ++k) { loopE[k] = graph.GetEdgeGroup(loopE[k]); } Loops[li] = EdgeLoop.FromEdges(Mesh, loopE); } JunctionVertices = new HashSet <int>(); foreach (int gvid in curves.JunctionV) { JunctionVertices.Add(mapVFrom[gvid]); } }