public static void Append(DMesh3 appendTo, DMesh3 append) { MeshEditor editor = new MeshEditor(appendTo); editor.AppendMesh(append, appendTo.AllocateTriangleGroup()); }
public static void AppendBox(DMesh3 mesh, Vector3d pos, float size) { MeshEditor editor = new MeshEditor(mesh); editor.AppendBox(new Frame3f(pos), size); }
public static int RemoveSmallComponents(DMesh3 mesh, double min_volume, double min_area) { var e = new MeshEditor(mesh); return(e.RemoveSmallComponents(min_volume, min_area)); }
public static bool RemoveTriangles(DMesh3 Mesh, IEnumerable <int> triangles, bool bRemoveIsolatedVerts = true) { MeshEditor editor = new MeshEditor(Mesh); return(editor.RemoveTriangles(triangles, bRemoveIsolatedVerts)); }
public static void AppendBox(DMesh3 mesh, Frame3f frame, Vector3f size, Colorf color) { var editor = new MeshEditor(mesh); editor.AppendBox(frame, size, color); }
public static int RemoveUnusedVertices(DMesh3 mesh) { var e = new MeshEditor(mesh); return(e.RemoveUnusedVertices()); }
public bool Fill() { compute_polygons(); // translate/scale fill loops to unit box. This will improve // accuracy in the calcs below... Vector2d shiftOrigin = Bounds.Center; double scale = 1.0 / Bounds.MaxDim; foreach (var floop in Loops) { floop.poly.Translate(-shiftOrigin); floop.poly.Scale(scale * Vector2d.One, Vector2d.Zero); } Dictionary <PlanarComplex.Element, int> ElemToLoopMap = new Dictionary <PlanarComplex.Element, int>(); // [TODO] if we have multiple components in input mesh, we could do this per-component. // This also helps avoid nested shells creating holes. // *However*, we shouldn't *have* to because FindSolidRegions will do the right thing if // the polygons have the same orientation // add all loops to planar complex PlanarComplex complex = new PlanarComplex(); for (int i = 0; i < Loops.Count; ++i) { var elem = complex.Add(Loops[i].poly); ElemToLoopMap[elem] = i; } // sort into separate 2d solids PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(PlanarComplex.FindSolidsOptions.SortPolygons); // fill each 2d solid List <Index2i> failed_inserts = new List <Index2i>(); List <Index2i> failed_merges = new List <Index2i>(); for (int fi = 0; fi < solids.Polygons.Count; ++fi) { var gpoly = solids.Polygons[fi]; PlanarComplex.GeneralSolid gsolid = solids.PolygonsSources[fi]; // [TODO] could do scale/translate here, per-polygon would be more precise // generate planar mesh that we will insert polygons into MeshGenerator meshgen; float planeW = 1.5f; int nDivisions = 0; if (FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0) { int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1; nDivisions = (n <= 1) ? 0 : n; } if (nDivisions == 0) { meshgen = new TrivialRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, }; } else { meshgen = new GriddedRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, EdgeVertices = nDivisions }; } DMesh3 FillMesh = meshgen.Generate().MakeDMesh(); FillMesh.ReverseOrientation(); // why?!? // convenient list List <Polygon2d> polys = new List <Polygon2d>() { gpoly.Outer }; polys.AddRange(gpoly.Holes); // for each poly, we track the set of vertices inserted into mesh int[][] polyVertices = new int[polys.Count][]; // insert each poly for (int pi = 0; pi < polys.Count; ++pi) { MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(FillMesh, polys[pi]); ValidationStatus status = insert.Validate(MathUtil.ZeroTolerancef * scale); bool failed = true; if (status == ValidationStatus.Ok) { if (insert.Apply()) { insert.Simplify(); polyVertices[pi] = insert.CurveVertices; failed = false; } } if (failed) { failed_inserts.Add(new Index2i(fi, pi)); } } // remove any triangles not contained in gpoly // [TODO] degenerate triangle handling? may be 'on' edge of gpoly... List <int> removeT = new List <int>(); foreach (int tid in FillMesh.TriangleIndices()) { Vector3d v = FillMesh.GetTriCentroid(tid); if (gpoly.Contains(v.xy) == false) { removeT.Add(tid); } } foreach (int tid in removeT) { FillMesh.RemoveTriangle(tid, true, false); } //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj"); // transform fill mesh back to 3d MeshTransforms.PerVertexTransform(FillMesh, (v) => { Vector2d v2 = v.xy; v2 /= scale; v2 += shiftOrigin; return(to3D(v2)); }); //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj"); //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj"); // figure out map between new mesh and original edge loops // [TODO] if # of verts is different, we can still find correspondence, it is just harder // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh // if not, can try to delete nbr tris to repair IndexMap mergeMapV = new IndexMap(true); if (MergeFillBoundary) { for (int pi = 0; pi < polys.Count; ++pi) { if (polyVertices[pi] == null) { continue; } int[] fillLoopVerts = polyVertices[pi]; int NV = fillLoopVerts.Length; PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; int loopi = ElemToLoopMap[sourceElem]; EdgeLoop sourceLoop = Loops[loopi].edgeLoop; if (sourceLoop.VertexCount != NV) { failed_merges.Add(new Index2i(fi, pi)); continue; } for (int k = 0; k < NV; ++k) { Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) { mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; } } } } // append this fill to input mesh MeshEditor editor = new MeshEditor(Mesh); int[] mapV; editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup()); // [TODO] should verify that we actually merged the loops... } if (failed_inserts.Count > 0 || failed_merges.Count > 0) { return(false); } return(true); }
public static void AppendBox(DMesh3 mesh, Vector3d pos, Vector3d normal, float size, Colorf color) { var editor = new MeshEditor(mesh); editor.AppendBox(new Frame3f(pos, normal), size * Vector3f.One, color); }
public bool Compute(boolOperation op = boolOperation.Union) { if (!Target.IsClosed()) { Debug.WriteLine("Target mesh is not closed;"); } if (!Tool.IsClosed()) { Debug.WriteLine("Tool mesh is not closed;"); } Util.gDevAssert(Target.IsClosed() && Tool.IsClosed()); // Alternate strategy: // - don't do RemoveContained // - match embedded vertices, split where possible // - find min-cut path through shared edges // - remove contiguous patches that are inside both/etc (use MWN) // ** no good for coplanar regions... cutTargetOp = new MeshMeshCut() { Target = new DMesh3(Target), CutMesh = Tool, VertexSnapTol = VertexSnapTol, AttemptPlanarRemoval = AttemptPlanarRemoval }; cutTargetOp.Compute(); if (op == boolOperation.Union || op == boolOperation.Subtraction) { cutTargetOp.RemoveContained(); } else if (op == boolOperation.Intersection) { cutTargetOp.RemoveExternal(); } cutTargetMesh = cutTargetOp.Target; cutToolOp = new MeshMeshCut() { Target = new DMesh3(Tool), CutMesh = Target, VertexSnapTol = VertexSnapTol, AttemptPlanarRemoval = AttemptPlanarRemoval }; cutToolOp.Compute(); if (op == boolOperation.Union || op == boolOperation.Intersection) { cutToolOp.RemoveContained(); } else if (op == boolOperation.Subtraction) { cutToolOp.RemoveExternal(); } cutToolMesh = cutToolOp.Target; resolve_vtx_pairs(); Result = cutToolMesh; MeshEditor.Append(Result, cutTargetMesh); return(true); }
public virtual bool Trim() { if (Spatial == null) { Spatial = new DMeshAABBTree3(new DMesh3(Mesh, false, MeshComponents.None)); Spatial.Build(); } if (seed_tri == -1) { seed_tri = Spatial.FindNearestTriangle(seed_pt); } MeshFacesFromLoop loop = new MeshFacesFromLoop(Mesh, TrimLine, Spatial, seed_tri); MeshFaceSelection selection = loop.ToSelection(); selection.LocalOptimize(true, true); MeshEditor editor = new MeshEditor(Mesh); editor.RemoveTriangles(selection, true); MeshConnectedComponents components = new MeshConnectedComponents(Mesh); components.FindConnectedT(); if (components.Count > 1) { int keep = components.LargestByCount; for (int i = 0; i < components.Count; ++i) { if (i != keep) { editor.RemoveTriangles(components[i].Indices, true); } } } editor.RemoveAllBowtieVertices(true); MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh); bool loopsOK = false; try { loopsOK = loops.Compute(); } catch (Exception) { return(false); } if (!loopsOK) { return(false); } // [TODO] to support trimming mesh w/ existing holes, we need to figure out which // loop we created in RemoveTriangles above! if (loops.Count > 1) { return(false); } int[] loopVerts = loops[0].Vertices; MeshFaceSelection borderTris = new MeshFaceSelection(Mesh); borderTris.SelectVertexOneRings(loopVerts); borderTris.ExpandToOneRingNeighbours(RemeshBorderRings); RegionRemesher remesh = new RegionRemesher(Mesh, borderTris.ToArray()); remesh.Region.MapVerticesToSubmesh(loopVerts); double target_len = TargetEdgeLength; if (target_len <= 0) { double mine, maxe, avge; MeshQueries.EdgeLengthStatsFromEdges(Mesh, loops[0].Edges, out mine, out maxe, out avge); target_len = avge; } MeshProjectionTarget meshTarget = new MeshProjectionTarget(Spatial.Mesh, Spatial); remesh.SetProjectionTarget(meshTarget); remesh.SetTargetEdgeLength(target_len); remesh.SmoothSpeedT = SmoothingAlpha; DCurveProjectionTarget curveTarget = new DCurveProjectionTarget(TrimLine); SequentialProjectionTarget multiTarget = new SequentialProjectionTarget(curveTarget, meshTarget); int set_id = 3; MeshConstraintUtil.ConstrainVtxLoopTo(remesh, loopVerts, multiTarget, set_id); for (int i = 0; i < RemeshRounds; ++i) { remesh.BasicRemeshPass(); } remesh.BackPropropagate(); // [TODO] output loop somehow...use MeshConstraints.FindConstrainedEdgesBySetID(set_id)... return(true); } // Trim()
public bool Fill() { compute_polygon(); // translate/scale fill loops to unit box. This will improve // accuracy in the calcs below... Vector2d shiftOrigin = Bounds.Center; double scale = 1.0 / Bounds.MaxDim; SpansPoly.Translate(-shiftOrigin); SpansPoly.Scale(scale * Vector2d.One, Vector2d.Zero); Dictionary <PlanarComplex.Element, int> ElemToLoopMap = new Dictionary <PlanarComplex.Element, int>(); // generate planar mesh that we will insert polygons into MeshGenerator meshgen; float planeW = 1.5f; int nDivisions = 0; if (FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0) { int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1; nDivisions = (n <= 1) ? 0 : n; } if (nDivisions == 0) { meshgen = new TrivialRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, }; } else { meshgen = new GriddedRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, EdgeVertices = nDivisions }; } DMesh3 FillMesh = meshgen.Generate().MakeDMesh(); FillMesh.ReverseOrientation(); // why?!? int[] polyVertices = null; // insert each poly MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(FillMesh, SpansPoly); ValidationStatus status = insert.Validate(MathUtil.ZeroTolerancef * scale); bool failed = true; if (status == ValidationStatus.Ok) { if (insert.Apply()) { insert.Simplify(); polyVertices = insert.CurveVertices; failed = false; } } if (failed) { return(false); } // remove any triangles not contained in gpoly // [TODO] degenerate triangle handling? may be 'on' edge of gpoly... List <int> removeT = new List <int>(); foreach (int tid in FillMesh.TriangleIndices()) { Vector3d v = FillMesh.GetTriCentroid(tid); if (SpansPoly.Contains(v.xy) == false) { removeT.Add(tid); } } foreach (int tid in removeT) { FillMesh.RemoveTriangle(tid, true, false); } //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj"); // transform fill mesh back to 3d MeshTransforms.PerVertexTransform(FillMesh, (v) => { Vector2d v2 = v.xy; v2 /= scale; v2 += shiftOrigin; return(to3D(v2)); }); //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj"); //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj"); // figure out map between new mesh and original edge loops // [TODO] if # of verts is different, we can still find correspondence, it is just harder // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh // if not, can try to delete nbr tris to repair IndexMap mergeMapV = new IndexMap(true); if (MergeFillBoundary && polyVertices != null) { throw new NotImplementedException("PlanarSpansFiller: merge fill boundary not implemented!"); //int[] fillLoopVerts = polyVertices; //int NV = fillLoopVerts.Length; //PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; //int loopi = ElemToLoopMap[sourceElem]; //EdgeLoop sourceLoop = Loops[loopi].edgeLoop; //for (int k = 0; k < NV; ++k) { // Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); // Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); // if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) // mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; //} } // append this fill to input mesh MeshEditor editor = new MeshEditor(Mesh); int[] mapV; editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup()); // [TODO] should verify that we actually merged the loops... return(true); }
private void Remove(TriangleRemoval rem = TriangleRemoval.contained) { #if ACAD var lastColor = 0; #endif DMeshAABBTree3 spatial = new DMeshAABBTree3(CutMesh, true); spatial.WindingNumber(Vector3d.Zero); SafeListBuilder <int> containedT = new SafeListBuilder <int>(); SafeListBuilder <int> removeAnywayT = new SafeListBuilder <int>(); // if the windinging number for the centroid point candidate triangles // is one or more (or close for safety), then it's inside the volume of cutMesh // gParallel.ForEach(Target.TriangleIndices(), (tid) => { if (Target.GetTriArea(tid) < VertexSnapTol) { removeAnywayT.SafeAdd(tid); return; // parallel: equivalent to continue. } Vector3d v = Target.GetTriCentroid(tid); if (AttemptPlanarRemoval) { // slightly offset the point to be evaluated. // var nrm = Target.GetTriNormal(tid); v -= nrm * 5 * VertexSnapTol; } var winding = spatial.WindingNumber(v); bool IsInternal = winding > 0.9; #if ACAD // temporarily here for debug purposes var wantColor = IsInternal ? 1 : 2; if (lastColor != wantColor) { Debug.WriteLine($"-LAYER set L{wantColor}"); Debug.WriteLine($""); lastColor = wantColor; } Triangle3d tri = new Triangle3d(); Target.GetTriVertices(tid, ref tri.V0, ref tri.V1, ref tri.V2); Debug.WriteLine($"3DPOLY {tri.V0.CommaDelimited} {tri.V1.CommaDelimited} {tri.V2.CommaDelimited} {tri.V0.CommaDelimited} {v.CommaDelimited} "); #endif if (IsInternal) { containedT.SafeAdd(tid); } }); if (rem == TriangleRemoval.contained) { MeshEditor.RemoveTriangles(Target, containedT.Result); } else if (rem == TriangleRemoval.external) { var ext = Target.TriangleIndices().Except(containedT.Result); MeshEditor.RemoveTriangles(Target, ext); } MeshEditor.RemoveTriangles(Target, removeAnywayT.Result); // [RMS] construct set of on-cut vertices? This is not // necessarily all boundary vertices... CutVertices = new List <int>(); foreach (int vid in SegmentInsertVertices) { if (Target.IsVertex(vid)) { CutVertices.Add(vid); } } }
public virtual bool Extrude() { MeshNormals normals = null; bool bHaveNormals = Mesh.HasVertexNormals; if (!bHaveNormals) { normals = new MeshNormals(Mesh); normals.Compute(); } InitialLoops = new MeshBoundaryLoops(Mesh); InitialTriangles = Mesh.TriangleIndices().ToArray(); InitialVertices = Mesh.VertexIndices().ToArray(); // duplicate triangles of mesh InitialToOffsetMapV = new IndexMap(Mesh.MaxVertexID, Mesh.MaxVertexID); OffsetGroupID = OffsetGroup.GetGroupID(Mesh); var editor = new MeshEditor(Mesh); OffsetTriangles = editor.DuplicateTriangles(InitialTriangles, ref InitialToOffsetMapV, OffsetGroupID); // set vertices to new positions foreach (int vid in InitialVertices) { int newvid = InitialToOffsetMapV[vid]; if (!Mesh.IsVertex(newvid)) { continue; } Vector3d v = Mesh.GetVertex(vid); Vector3f n = (bHaveNormals) ? Mesh.GetVertexNormal(vid) : (Vector3f)normals.Normals[vid]; Vector3d newv = ExtrudedPositionF(v, n, vid); Mesh.SetVertex(newvid, newv); } // we need to reverse one side if (IsPositiveOffset) { editor.ReverseTriangles(InitialTriangles); } else { editor.ReverseTriangles(OffsetTriangles); } // stitch each loop NewLoops = new EdgeLoop[InitialLoops.Count]; StitchTriangles = new int[InitialLoops.Count][]; StitchGroupIDs = new int[InitialLoops.Count]; int li = 0; foreach (var loop in InitialLoops) { int[] loop2 = new int[loop.VertexCount]; for (int k = 0; k < loop2.Length; ++k) { loop2[k] = InitialToOffsetMapV[loop.Vertices[k]]; } StitchGroupIDs[li] = StitchGroups.GetGroupID(Mesh); if (IsPositiveOffset) { StitchTriangles[li] = editor.StitchLoop(loop2, loop.Vertices, StitchGroupIDs[li]); } else { StitchTriangles[li] = editor.StitchLoop(loop.Vertices, loop2, StitchGroupIDs[li]); } NewLoops[li] = EdgeLoop.FromVertices(Mesh, loop2); li++; } return(true); }