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 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"))); } }
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); }
// 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 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); }
/// <summary> /// This function tries to remove vertices from loop to hit TargetVtxCount. We call this /// when polygon-insertion returns more vertices than polygon. Strategy is to try to find /// co-linear vertices, ie that can be removed without changing shape. If that fails, /// we remove vertices that result in smallest length change (probably should do proper simplification /// here instead). /// /// Basically this is to handle failures in MeshInsertUVPolyCurve.Simplify(), which sometimes /// fails to remove extra vertices because it would case triangle flips. Here we don't /// care about triangle flips. /// /// Note that if the input polygon had splits on edges, this function would remove those /// vertices. Which is not ideal. /// </summary> protected EdgeLoop simplify_loop(DMesh3 mesh, EdgeLoop loop, int TargetVtxCount) { while (loop.VertexCount > TargetVtxCount) { DCurve3 curve = loop.ToCurve(); DMesh3.EdgeCollapseInfo cinfo; int NV = loop.VertexCount; for (int k = 1; k <= NV; ++k) { int prevv = k - 1; int curv = k % NV; int nextv = (k + 1) % NV; //if (curve[prevv].Distance(curve[curv]) < 0.0001 || // curve[nextv].Distance(curve[curv]) < 0.0001) // f3.DebugUtil.Log("DEGENERATE!!"); double angle = curve.OpeningAngleDeg(curv); if (angle > 179) { MeshResult r = mesh.CollapseEdge(loop.Vertices[prevv], loop.Vertices[curv], out cinfo); mesh.SetVertex(loop.Vertices[prevv], curve[prevv]); if (r == MeshResult.Ok) { goto done_this_iter; } else { f3.DebugUtil.Log("collinear collapse failed!"); } } } f3.DebugUtil.Log("Did not find collinear vert..."); int i_shortest = -1; double shortest_len = double.MaxValue; for (int k = 1; k <= NV; ++k) { int prevv = k - 1; int curv = k % NV; int nextv = (k + 1) % NV; Vector3d pc = curve[curv] - curve[prevv]; Vector3d pn = curve[nextv] - curve[curv]; double len_sum = pc.Length + pn.Length; if (len_sum < shortest_len) { i_shortest = curv; shortest_len = len_sum; } } if (i_shortest != -1) { int curv = i_shortest; int prevv = (curv == 0) ? NV - 1 : curv - 1; int nextv = (curv + 1) % NV; Vector3d pc = curve[curv] - curve[prevv]; Vector3d pn = curve[nextv] - curve[curv]; int iWhich = (pc.Length < pn.Length) ? prevv : nextv; MeshResult r = mesh.CollapseEdge(loop.Vertices[iWhich], loop.Vertices[curv], out cinfo); if (r == MeshResult.Ok) { goto done_this_iter; } else { f3.DebugUtil.Log("shortest failed!"); } } f3.DebugUtil.Log("Did not find shortest vert..."); // if we didn't find a vert to remove yet, just arbitrarily remove first one int v0 = loop.Vertices[0], v1 = loop.Vertices[1]; MeshResult result = mesh.CollapseEdge(v1, v0, out cinfo); done_this_iter: List <int> new_verts = new List <int>(); for (int k = 0; k < loop.Vertices.Count(); ++k) { if (mesh.IsVertex(loop.Vertices[k])) { new_verts.Add(loop.Vertices[k]); } } loop = EdgeLoop.FromVertices(mesh, new_verts); } return(loop); }
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 }