void smooth_and_remesh(MeshFaceSelection tris) { if (EnableLaplacianSmooth) { LaplacianMeshSmoother.RegionSmooth(Mesh, tris, 2, 2, false); } if (RemeshAfterSmooth) { tris.ExpandToOneRingNeighbours(2); tris.LocalOptimize(true, true); MeshProjectionTarget target = MeshProjectionTarget.Auto(Mesh, tris, 5); RegionRemesher remesh2 = new RegionRemesher(Mesh, tris); remesh2.SetTargetEdgeLength(TargetEdgeLength); remesh2.SmoothSpeedT = 1.0; remesh2.SetProjectionTarget(target); if (ConfigureRemesherF != null) { ConfigureRemesherF(remesh2, false); } for (int k = 0; k < 10; ++k) { remesh2.BasicRemeshPass(); } remesh2.BackPropropagate(); FillTriangles = remesh2.CurrentBaseTriangles; } else { FillTriangles = tris.ToArray(); } }
// This function does local remeshing around a boundary loop within a fixed # of // rings, to try to 'massage' it into a cleaner shape/topology // [TODO] use geodesic distance instead of fixed # of rings? public static void cleanup_boundary(NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, EdgeLoop loop, double target_edge_len, int nRings = 3) { Debug.Assert(loop.IsBoundaryLoop()); MeshFaceSelection roi = new MeshFaceSelection(mesh); roi.SelectVertexOneRings(loop.Vertices); for (int i = 0; i < nRings; ++i) { roi.ExpandToOneRingNeighbours(); } roi.LocalOptimize(true, true); RegionRemesher r = new RegionRemesher(mesh, roi.ToArray()); 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 = 0.1f; for (int k = 0; k < nRings * 3; ++k) { r.BasicRemeshPass(); } Debug.Assert(mesh.CheckValidity()); r.BackPropropagate(); }
// smooths embedded loop in mesh, by first smoothing edge loop and then // smoothing vertex neighbourhood // [TODO] geodesic nbrhoold instead of # of rings // [TODO] reprojection? public static void smooth_loop(NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, EdgeLoop loop, int nRings) { MeshFaceSelection roi_t = new MeshFaceSelection(mesh); roi_t.SelectVertexOneRings(loop.Vertices); for (int i = 0; i < nRings; ++i) { roi_t.ExpandToOneRingNeighbours(); } roi_t.LocalOptimize(true, true); MeshVertexSelection roi_v = new MeshVertexSelection(mesh); roi_v.SelectTriangleVertices(roi_t.ToArray()); roi_v.Deselect(loop.Vertices); MeshLoopSmooth loop_smooth = new MeshLoopSmooth(mesh, loop); loop_smooth.Rounds = 1; MeshIterativeSmooth mesh_smooth = new MeshIterativeSmooth(mesh, roi_v.ToArray(), true); mesh_smooth.Rounds = 1; for (int i = 0; i < 10; ++i) { loop_smooth.Smooth(); mesh_smooth.Smooth(); } }
public void EndStroke() { if (CurrentStroke.Count >= 2) { DMesh3 mesh = Target.Mesh; TransformSequence toScene = SceneTransforms.ObjectToSceneXForm(Target); List <int> tris1 = new List <int>(), tris2 = new List <int>(); Ray3f first = CurrentStroke[0], last = CurrentStroke[CurrentStroke.Count - 1]; Vector3f v0 = PlaneFrameS.RayPlaneIntersection(first.Origin, first.Direction, 2); Vector3f v1 = PlaneFrameS.RayPlaneIntersection(last.Origin, last.Direction, 2); Vector3f planeN = Vector3f.Cross(first.Direction, last.Direction); Frame3f planeF = new Frame3f((v0 + v1) / 2, planeN); foreach (int tid in mesh.TriangleIndices()) { Vector3f c = (Vector3f)mesh.GetTriCentroid(tid); c = toScene.TransformP(c); if (planeF.DistanceToPlaneSigned(c, 2) < 0) { tris1.Add(tid); } else { tris2.Add(tid); } } double area1 = MeshMeasurements.AreaT(mesh, tris1); double area2 = MeshMeasurements.AreaT(mesh, tris2); lastSelection = new MeshFaceSelection(mesh); lastSelection.Select((area1 > area2) ? tris2 : tris1); lastSelection.LocalOptimize(); if (OnStrokeCompletedF != null) { OnStrokeCompletedF(Target, lastSelection); } } CurrentStroke.Clear(); }
// local mesh smooth applied to all vertices in N-rings around input list public static void smooth_region(NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, IEnumerable <int> vertices, int nRings) { MeshFaceSelection roi_t = new MeshFaceSelection(mesh); roi_t.SelectVertexOneRings(vertices); for (int i = 0; i < nRings; ++i) { roi_t.ExpandToOneRingNeighbours(); } roi_t.LocalOptimize(true, true); MeshVertexSelection roi_v = new MeshVertexSelection(mesh); roi_v.SelectTriangleVertices(roi_t.ToArray()); MeshIterativeSmooth mesh_smooth = new MeshIterativeSmooth(mesh, roi_v.ToArray(), true); mesh_smooth.Alpha = 0.2f; mesh_smooth.Rounds = 10; mesh_smooth.Smooth(); }
public bool Apply() { EdgeLoop useLoop = null; if (FillLoop == null) { MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh, true); if (loops.Count == 0) { return(false); } if (BorderHintTris != null) { useLoop = select_loop_tris_hint(loops); } if (useLoop == null && loops.MaxVerticesLoopIndex >= 0) { useLoop = loops[loops.MaxVerticesLoopIndex]; } } else { useLoop = FillLoop; } if (useLoop == null) { return(false); } // step 1: do stupid hole fill SimpleHoleFiller filler = new SimpleHoleFiller(Mesh, useLoop); if (filler.Fill() == false) { return(false); } if (useLoop.Vertices.Length <= 3) { FillTriangles = filler.NewTriangles; FillVertices = new int[0]; return(true); } MeshFaceSelection tris = new MeshFaceSelection(Mesh); tris.Select(filler.NewTriangles); // extrude initial fill surface (this is used in socketgen for example) if (OffsetDistance > 0) { MeshExtrudeFaces extrude = new MeshExtrudeFaces(Mesh, tris); extrude.ExtrudedPositionF = (v, n, vid) => { return(v + OffsetDistance * OffsetDirection); }; if (!extrude.Extrude()) { return(false); } tris.Select(extrude.JoinTriangles); } // if we aren't trying to stay inside hole, expand out a bit, // which allows us to clean up ugly edges if (ConstrainToHoleInterior == false) { tris.ExpandToOneRingNeighbours(2); tris.LocalOptimize(true, true); } // remesh the initial coarse fill region if (RemeshBeforeSmooth) { RegionRemesher remesh = new RegionRemesher(Mesh, tris); remesh.SetTargetEdgeLength(TargetEdgeLength); remesh.EnableSmoothing = (SmoothAlpha > 0); remesh.SmoothSpeedT = SmoothAlpha; if (ConfigureRemesherF != null) { ConfigureRemesherF(remesh, true); } for (int k = 0; k < InitialRemeshPasses; ++k) { remesh.BasicRemeshPass(); } remesh.BackPropropagate(); tris = new MeshFaceSelection(Mesh); tris.Select(remesh.CurrentBaseTriangles); if (ConstrainToHoleInterior == false) { tris.LocalOptimize(true, true); } } if (ConstrainToHoleInterior) { for (int k = 0; k < SmoothSolveIterations; ++k) { smooth_and_remesh_preserve(tris, k == SmoothSolveIterations - 1); tris = new MeshFaceSelection(Mesh); tris.Select(FillTriangles); } } else { smooth_and_remesh(tris); tris = new MeshFaceSelection(Mesh); tris.Select(FillTriangles); } MeshVertexSelection fill_verts = new MeshVertexSelection(Mesh); fill_verts.SelectInteriorVertices(tris); FillVertices = fill_verts.ToArray(); return(true); }
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); }
protected virtual DMesh3 compute_bounded_distance() { DMesh3 sourceMesh = MeshSource.GetDMeshUnsafe(); ISpatial inputSpatial = MeshSource.GetSpatial(); DMesh3 targetMesh = TargetSource.GetDMeshUnsafe(); ISpatial targetSpatial = TargetSource.GetSpatial(); double max_dist = (TargetMaxDistance == double.MaxValue) ? double.MaxValue : TargetMaxDistance; DMesh3 meshIn = new DMesh3(sourceMesh); bool target_closed = targetMesh.IsClosed(); MeshVertexSelection roiV = new MeshVertexSelection(meshIn); SpinLock roi_lock = new SpinLock(); gParallel.ForEach(meshIn.VertexIndices(), (vid) => { Vector3d pos = meshIn.GetVertex(vid); Vector3d posTarget = TransformToTarget.TransformP(pos); double dist = MeshQueries.NearestPointDistance(targetMesh, targetSpatial, posTarget, max_dist); bool inside = (target_closed && targetSpatial.IsInside(posTarget)); if (dist < max_dist || inside) { bool taken = false; roi_lock.Enter(ref taken); roiV.Select(vid); roi_lock.Exit(); } }); if (is_invalidated()) { return(null); } MeshFaceSelection roi_faces = new MeshFaceSelection(meshIn, roiV, 1); roi_faces.ExpandToOneRingNeighbours(3); roi_faces.LocalOptimize(); if (is_invalidated()) { return(null); } RegionOperator op = new RegionOperator(meshIn, roi_faces); DMesh3 meshROI = op.Region.SubMesh; if (is_invalidated()) { return(null); } RemesherPro remesher = new RemesherPro(meshROI); remesher.SetTargetEdgeLength(TargetEdgeLength); remesher.PreventNormalFlips = this.PreventNormalFlips; remesher.EnableFlips = this.EnableFlips; remesher.EnableSplits = this.EnableSplits; remesher.EnableCollapses = this.EnableCollapses; remesher.EnableSmoothing = this.EnableSmoothing; remesher.SmoothSpeedT = this.SmoothingSpeed; BoundedProjectionTarget target = new BoundedProjectionTarget() { Source = sourceMesh, SourceSpatial = inputSpatial, Target = targetMesh, TargetSpatial = targetSpatial, SourceToTargetXForm = source_to_target, TargetToSourceXForm = target_to_source, MaxDistance = max_dist, Smoothness = transition_smoothness }; remesher.SetProjectionTarget(target); if (remesher.Constraints == null) { remesher.SetExternalConstraints(new MeshConstraints()); } MeshConstraintUtil.FixAllBoundaryEdges(remesher.Constraints, meshROI); if (is_invalidated()) { return(null); } remesher.Progress = new ProgressCancel(is_invalidated); remesher.FastestRemesh(RemeshRounds); if (is_invalidated()) { return(null); } op.BackPropropagate(); return(meshIn); }
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 }