public static void test_planar_fill() { //DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_hollow.obj"); DMesh3 mesh = TestUtil.LoadTestInputMesh("local\\__LAST_FAILED_CUT_MESH.obj"); double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out maxe, out avge); AxisAlignedBox3d bounds = mesh.CachedBounds; Vector3d origin = bounds.Center; Vector3d axis = Vector3d.AxisY; double targetLen = avge; origin = new Vector3d(-7.53203300, -39.50826000, -4.22260500); axis = new Vector3d(0.00000007, 0.00000007, -0.99999960); targetLen = 3.0; MeshPlaneCut cut = new MeshPlaneCut(mesh, origin, axis); cut.Cut(); PlanarHoleFiller fill = new PlanarHoleFiller(cut) { FillTargetEdgeLen = targetLen }; if (fill.Fill() == false) { System.Console.WriteLine("test_meshEdits.test_planar_fill: Fill() failed!"); } TestUtil.WriteTestOutputMesh(mesh, "planar_filled.obj"); }
InteractiveRemesher make_remesher(DMesh3 mesh) { var m = new InteractiveRemesher(mesh); m.PreventNormalFlips = true; double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out avge, out maxe); m.SetTargetEdgeLength(avge * EdgeLengthMultiplier); m.SmoothSpeedT = SmoothSpeed; if (Reproject) { m.SetProjectionTarget(MeshProjectionTarget.Auto(mesh)); } if (RemeshBoundary) { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); int k = 1; foreach (var loop in loops) { MeshConstraintUtil.ConstrainVtxLoopTo(m, loop.Vertices, new DCurveProjectionTarget(loop.ToCurve()), k++); } } else if (PreserveBoundary) { MeshConstraintUtil.FixAllBoundaryEdges(m); } return(m); }
public static void test_basic_closed_reduce() { //DMesh3 mesh = TestUtil.MakeCappedCylinder(false); //DMesh3 mesh = TestUtil.LoadTestInputMesh("sphere_bowtie_groups.obj"); DMesh3 mesh = TestUtil.LoadTestInputMesh("bunny_solid.obj"); //MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1,2,1)); //DMesh3 mesh = TestUtil.MakeOpenCylinder(false); mesh.CheckValidity(); if (WriteDebugMeshes) { TestUtil.WriteTestOutputMesh(mesh, "basic_closed_reduce_before.obj"); } Reducer r = new Reducer(mesh); DMeshAABBTree3 tree = new DMeshAABBTree3(new DMesh3(mesh)); tree.Build(); //r.SetProjectionTarget(new MeshProjectionTarget() { Mesh = tree.Mesh, Spatial = tree }); r.ReduceToTriangleCount(3000); //r.ReduceToEdgeLength(2.0); double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out maxe, out avge); System.Console.WriteLine("Edge length stats: {0} {1} {2}", mine, maxe, avge); if (WriteDebugMeshes) { TestUtil.WriteTestOutputMesh(mesh, "basic_closed_reduce_after.obj"); } }
virtual public void CutMesh() { Frame3f frameL = SceneTransforms.SceneToObject(target, cut_plane); Action <DMesh3> editF = (mesh) => { MeshPlaneCut cut = new MeshPlaneCut(mesh, frameL.Origin, frameL.Y); cut.Cut(); PlaneProjectionTarget planeTarget = new PlaneProjectionTarget() { Origin = frameL.Origin, Normal = frameL.Y }; if (GenerateFillSurface) { double min, max, avg; MeshQueries.EdgeLengthStats(mesh, out min, out max, out avg); cut.FillHoles(); MeshFaceSelection selection = new MeshFaceSelection(mesh); foreach (var tris in cut.LoopFillTriangles) { selection.Select(tris); } RegionRemesher.QuickRemesh(mesh, selection.ToArray(), 2 * avg, 1.0f, 25, planeTarget); MeshNormals normals = new MeshNormals(mesh); normals.Compute(); normals.CopyTo(mesh); } }; target.EditAndUpdateMesh(editF, GeometryEditTypes.ArbitraryEdit); }
DMesh3 GenerateRemesh(DMesh3 mesh) { DMesh3 remeshed = new DMesh3(mesh); DMeshAABBTree3 project = new DMeshAABBTree3(mesh); project.Build(); MeshProjectionTarget Target = new MeshProjectionTarget(project.Mesh, project); double minlen, maxlen, avglen; MeshQueries.EdgeLengthStats(mesh, out minlen, out maxlen, out avglen); double edge_len = (TargetEdgeLength == 0) ? Loop.AverageEdgeLength : avglen; Remesher r = new Remesher(remeshed); r.SetTargetEdgeLength(edge_len); r.SetProjectionTarget(Target); MeshConstraintUtil.FixAllBoundaryEdges(r); for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); } return(remeshed); }
public static void test_auto_fill() { DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_cases.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_tempcase1.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_planarcase.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_box_edge_strip.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_box_edge_strip_2.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("crazyhole.obj"); double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out maxe, out avge); int ROUNDS = 1; for (int k = 0; k < ROUNDS; ++k) { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); foreach (EdgeLoop loop in loops) { AutoHoleFill fill = new AutoHoleFill(mesh, loop); fill.TargetEdgeLength = avge; fill.Apply(); } if (k == ROUNDS - 1) { continue; } RemesherPro r = new RemesherPro(mesh); r.SetTargetEdgeLength(avge); for (int j = 0; j < 10; ++j) { r.FastSplitIteration(); } MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh); merge.Apply(); } TestUtil.WriteTestOutputMesh(mesh, "autofill_result.obj"); //DMesh3 mesh = new DMesh3(); //SphericalFibonacciPointSet ps = new SphericalFibonacciPointSet(); //for (int k = 0; k < ps.N; ++k) { // MeshEditor.AppendBox(mesh, ps[k], ps[k], 0.05f); //} //Random r = new Random(31337); //Vector3d[] pts = TestUtil.RandomPoints3(10000, r, Vector3d.Zero); //foreach ( Vector3d pt in pts ) { // pt.Normalize(); // int nearest = ps.NearestPoint(pt, true); // Vector3d p = ps[nearest]; // MeshEditor.AppendLine(mesh, new Segment3d(p, p + 0.25f * pt), 0.01f); //} //TestUtil.WriteTestOutputMesh(mesh, "fibonacci.obj"); }
public virtual void Update() { base.begin_update(); int start_timestamp = this.CurrentInputTimestamp; if (MeshSource == null) { throw new Exception("GenerateClosedMeshOp: must set valid MeshSource to compute!"); } try { ResultMesh = null; DMesh3 meshIn = MeshSource.GetDMeshUnsafe(); input_spatial = MeshSource.GetSpatial() as DMeshAABBTree3; if (meshIn.ShapeTimestamp != input_mesh_cache_timestamp) { cached_is_closed = meshIn.IsClosed(); MeshQueries.EdgeLengthStats(meshIn, out input_mesh_edge_stats.x, out input_mesh_edge_stats.y, out input_mesh_edge_stats.z); if (input_spatial == null) { input_spatial = new DMeshAABBTree3(meshIn, false); } input_mesh_cache_timestamp = meshIn.ShapeTimestamp; } if (closing_type == ClosingTypes.LevelSet) { update_level_set(); } else if (closing_type == ClosingTypes.WindingNumberGrid) { if (cached_is_closed) { update_winding(); } else { update_winding_fast(); } } else { update_winding_exact(); } base.complete_update(); } catch (Exception e) { PostOnOperatorException(e); ResultMesh = base.make_failure_output(MeshSource.GetDMeshUnsafe()); base.complete_update(); } }
protected override void SolveInstance(IGH_DataAccess DA) { DMesh3_goo goo = null; DA.GetData(0, ref goo); DMesh3 mesh = new DMesh3(goo.Value); MeshQueries.EdgeLengthStats(mesh, out double min, out double max, out double avg); DA.SetData(0, max); DA.SetData(1, min); DA.SetData(2, avg); }
void remove_old_vertices(int[] MapV, DMesh3 mesh) { HashSet <int> keepV = new HashSet <int>(); for (int k = 0; k < MapV.Length; ++k) { if (MapV[k] != DMesh3.InvalidID) { keepV.Add(MapV[k]); } } Remesher r = new Remesher(mesh); //r.EnableCollapses = false; //r.EnableSplits = false; //r.EnableFlips = false; r.SmoothSpeedT = 1.0; //r.EnableSmoothing = false; r.PreventNormalFlips = true; r.SetTargetEdgeLength(1.0); //r.EnableSmoothing = false; MeshConstraints c = new MeshConstraints(); foreach (int vid in keepV) { c.SetOrUpdateVertexConstraint(vid, VertexConstraint.Pinned); } r.SetExternalConstraints(c); double minE, maxE, avgE; MeshQueries.EdgeLengthStats(mesh, out minE, out maxE, out avgE); r.SetTargetEdgeLength(avgE * .3); for (int k = 0; k < 10; ++k) { r.BasicRemeshPass(); } //int iter = 0; //while (iter++ < 10) { // r.SetTargetEdgeLength(iter * 1.0); // for (int k = 0; k < 10; ++k) // r.BasicRemeshPass(); //} }
public virtual void Update() { base.begin_update(); int start_timestamp = this.CurrentInputTimestamp; if (MeshSource == null) { throw new Exception("MeshShellOp: must set valid MeshSource to compute!"); } try { ResultMesh = null; DMesh3 meshIn = MeshSource.GetDMeshUnsafe(); input_spatial = MeshSource.GetSpatial() as DMeshAABBTree3; if (meshIn.ShapeTimestamp != input_mesh_cache_timestamp) { cached_is_closed = meshIn.IsClosed(); MeshQueries.EdgeLengthStats(meshIn, out input_mesh_edge_stats.x, out input_mesh_edge_stats.y, out input_mesh_edge_stats.z); if (input_spatial == null) { input_spatial = new DMeshAABBTree3(meshIn, false); } input_mesh_cache_timestamp = meshIn.ShapeTimestamp; } if (shell_type == ShellTypes.DistanceField) { compute_shell_distancefield(); } else { compute_shell_extrude(); } if (ResultMesh.TriangleCount == 0) { ResultMesh = base.make_failure_output(null); } base.complete_update(); } catch (Exception e) { PostOnOperatorException(e); ResultMesh = base.make_failure_output(MeshSource.GetDMeshUnsafe()); base.complete_update(); } }
public virtual void Update() { base.begin_update(); int start_timestamp = this.CurrentInputTimestamp; if (mesh_sources == null) { throw new Exception("MeshVoxelBooleanOp: must set valid MeshSource to compute!"); } try { ResultMesh = null; if (source_edge_stats == null) { source_edge_stats = new List <Vector3d>(); source_bounds = AxisAlignedBox3d.Empty; foreach (DMeshSourceOp op in mesh_sources) { Vector3d einfo = new Vector3d(); MeshQueries.EdgeLengthStats(op.GetDMeshUnsafe(), out einfo.x, out einfo.y, out einfo.z); source_edge_stats.Add(einfo); source_bounds.Contain(op.GetDMeshUnsafe().CachedBounds); } } //ResultMesh = compute_blend(); //ResultMesh = compute_blend_bounded(); ResultMesh = compute_blend_analytic(); if (ResultMesh.TriangleCount == 0) { ResultMesh = base.make_failure_output(null); } base.complete_update(); } catch (Exception e) { PostOnOperatorException(e); ResultMesh = base.make_failure_output(mesh_sources[0].GetDMeshUnsafe()); base.complete_update(); } }
public static void test_smooth_fill() { //DMesh3 mesh = TestUtil.LoadTestInputMesh("n_holed_bunny.obj"); DMesh3 mesh = TestUtil.LoadTestInputMesh("crazyhole.obj"); double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out maxe, out avge); MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); foreach (EdgeLoop loop in loops) { SmoothedHoleFill fill = new SmoothedHoleFill(mesh, loop); fill.TargetEdgeLength = avge; fill.SmoothAlpha = 1.0f; fill.InitialRemeshPasses = 50; fill.EnableLaplacianSmooth = true; fill.ConstrainToHoleInterior = true; fill.Apply(); } TestUtil.WriteTestOutputMesh(mesh, "smooth_fill_result.obj"); }
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 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 override Result RunCommand(RhinoDoc doc, RunMode mode) { const ObjectType geometryFilter = ObjectType.Mesh; OptionDouble minEdgeLengthOption = new OptionDouble(minEdgeLength, 0.001, 200); OptionInteger maxTriangleCountOption = new OptionInteger(maxTriangleCount, 0, 100000000); GetObject go = new GetObject(); go.SetCommandPrompt("Select meshes to reduce"); go.GeometryFilter = geometryFilter; go.AddOptionDouble("MinEdge", ref minEdgeLengthOption); go.AddOptionInteger("MaxTriangle", ref maxTriangleCountOption); go.GroupSelect = true; go.SubObjectSelect = false; go.EnableClearObjectsOnEntry(false); go.EnableUnselectObjectsOnExit(false); go.DeselectAllBeforePostSelect = false; bool bHavePreselectedObjects = false; for (;;) { GetResult res = go.GetMultiple(1, 0); if (res == GetResult.Option) { go.EnablePreSelect(false, true); continue; } else if (go.CommandResult() != Result.Success) return go.CommandResult(); if (go.ObjectsWerePreselected) { bHavePreselectedObjects = true; go.EnablePreSelect(false, true); continue; } break; } maxTriangleCount = maxTriangleCountOption.CurrentValue; minEdgeLength = minEdgeLengthOption.CurrentValue; if (bHavePreselectedObjects) { // Normally, pre-selected objects will remain selected, when a // command finishes, and post-selected objects will be unselected. // This this way of picking, it is possible to have a combination // of pre-selected and post-selected. So, to make sure everything // "looks the same", lets unselect everything before finishing // the command. for (int i = 0; i < go.ObjectCount; i++) { RhinoObject rhinoObject = go.Object(i).Object(); if (null != rhinoObject) rhinoObject.Select(false); } doc.Views.Redraw(); } bool result = false; foreach (var obj in go.Objects()) { var rhinoMesh = obj.Mesh(); if (rhinoMesh == null || !rhinoMesh.IsValid) continue; var mesh = GopherUtil.ConvertToD3Mesh(obj.Mesh()); GopherUtil.ReduceMesh(mesh, (float)minEdgeLength, maxTriangleCount); double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out maxe, out avge); RhinoApp.WriteLine("Edge length stats: {0} {1} {2}", mine, maxe, avge); var newRhinoMesh = GopherUtil.ConvertToRhinoMesh(mesh); if (newRhinoMesh != null && newRhinoMesh.IsValid) { doc.Objects.Replace(obj, newRhinoMesh); } if (newRhinoMesh != null && newRhinoMesh.IsValid) { result |= doc.Objects.Replace(obj, newRhinoMesh); } } doc.Views.Redraw(); return Result.Success; }