/// <summary> /// shortcut to construct graph for mesh triangles /// </summary> public static DijkstraGraphDistance MeshTriangles(DMesh3 mesh, bool bSparse = false) { Func <int, int, float> tri_dist_f = (a, b) => { return((float)mesh.GetTriCentroid(a).Distance(mesh.GetTriCentroid(b))); }; return((bSparse) ? new DijkstraGraphDistance(mesh.MaxTriangleID, true, (id) => { return mesh.IsTriangle(id); }, tri_dist_f, mesh.TriTrianglesItr, null) : new DijkstraGraphDistance(mesh.MaxTriangleID, false, (id) => { return true; }, tri_dist_f, mesh.TriTrianglesItr, null)); }
public void RemoveContained() { DMeshAABBTree3 spatial = new DMeshAABBTree3(CutMesh, true); spatial.WindingNumber(Vector3d.Zero); SafeListBuilder <int> removeT = new SafeListBuilder <int>(); gParallel.ForEach(Target.TriangleIndices(), (tid) => { Vector3d v = Target.GetTriCentroid(tid); if (spatial.WindingNumber(v) > 0.9) { removeT.SafeAdd(tid); } }); MeshEditor.RemoveTriangles(Target, removeT.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); } } }
int[] get_tri_order_by_axis_sort() { int i = 0; int[] tri_order = new int[mesh.TriangleCount]; int nT = mesh.MaxTriangleID; for (int ti = 0; ti < nT; ++ti) { if (mesh.IsTriangle(ti)) { tri_order[i++] = ti; } } // precompute triangle centroids - wildly expensive to // do it inline in sort (!) I guess O(N) vs O(N log N) Vector3d[] centroids = new Vector3d[mesh.MaxTriangleID]; gParallel.ForEach(mesh.TriangleIndices(), (ti) => { if (mesh.IsTriangle(ti)) { centroids[ti] = mesh.GetTriCentroid(ti); } }); Array.Sort(tri_order, (t0, t1) => { double f0 = centroids[t0].x; double f1 = centroids[t1].x; return((f0 == f1) ? 0 : (f0 < f1) ? -1 : 1); }); return(tri_order); }
void build_top_down(bool bSorted) { int i = 0; int[] triangles = new int[mesh.TriangleCount]; Vector3d[] centers = new Vector3d[mesh.TriangleCount]; foreach (int ti in mesh.TriangleIndices()) { triangles[i] = ti; centers[i++] = mesh.GetTriCentroid(ti); } boxes_set tris = new boxes_set(); boxes_set nodes = new boxes_set(); AxisAlignedBox3f rootBox; int rootnode = (bSorted) ? split_tri_set_sorted(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox) : split_tri_set_midpoint(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox); box_to_index = tris.box_to_index; box_centers = tris.box_centers; box_extents = tris.box_extents; index_list = tris.index_list; triangles_end = tris.iIndicesCur; int iIndexShift = triangles_end; int iBoxShift = tris.iBoxCur; // ok now append internal node boxes & index ptrs for (i = 0; i < nodes.iBoxCur; ++i) { box_centers.insert(nodes.box_centers[i], iBoxShift + i); box_extents.insert(nodes.box_extents[i], iBoxShift + i); // internal node indices are shifted box_to_index.insert(iIndexShift + nodes.box_to_index[i], iBoxShift + i); } // now append index list for (i = 0; i < nodes.iIndicesCur; ++i) { int child_box = nodes.index_list[i]; if (child_box < 0) // this is a triangles box { child_box = (-child_box) - 1; } else { child_box += iBoxShift; } child_box = child_box + 1; index_list.insert(child_box, iIndexShift + i); } root_index = rootnode + iBoxShift; }
int[] get_tri_order_by_axis_sort() { int i = 0; int[] tri_order = new int[mesh.TriangleCount]; int nT = mesh.MaxTriangleID; for (int ti = 0; ti < nT; ++ti) { if (mesh.IsTriangle(ti)) { tri_order[i++] = ti; } } Array.Sort(tri_order, (t0, t1) => { double f0 = mesh.GetTriCentroid(t0).x; double f1 = mesh.GetTriCentroid(t1).x; return((f0 == f1) ? 0 : (f0 < f1) ? -1 : 1); }); return(tri_order); }
// Modes: 0: centroids, 1: any vertex, 2: 2 vertices, 3: all vertices // ContainF should return true if 3D position is in set (eg inside box, etc) // If mode = 0, will be called with (centroid, tri_idx) // If mode = 1/2/3, will be called with (vtx_pos, vtx_idx) // AddF is called with triangle IDs that are in set public static void TrianglesContained(DMesh3 mesh, Func <Vector3d, int, bool> ContainF, Action <int> AddF, int nMode = 0) { BitArray inV = null; if (nMode != 0) { inV = new BitArray(mesh.MaxVertexID); foreach (int vid in mesh.VertexIndices()) { if (ContainF(mesh.GetVertex(vid), vid)) { inV[vid] = true; } } } foreach (int tid in mesh.TriangleIndices()) { Index3i tri = mesh.GetTriangle(tid); bool bIn = false; if (nMode == 0) { if (ContainF(mesh.GetTriCentroid(tid), tid)) { bIn = true; } } else { int countIn = (inV[tri.a] ? 1 : 0) + (inV[tri.b] ? 1 : 0) + (inV[tri.c] ? 1 : 0); bIn = (countIn >= nMode); } if (bIn) { AddF(tid); } } }
void build_top_down(bool bSorted) { // build list of valid triangles & centers. We skip any // triangles that have infinite/garbage vertices... int i = 0; int[] triangles = new int[mesh.TriangleCount]; Vector3d[] centers = new Vector3d[mesh.TriangleCount]; foreach (int ti in mesh.TriangleIndices()) { Vector3d centroid = mesh.GetTriCentroid(ti); double d2 = centroid.LengthSquared; bool bInvalid = double.IsNaN(d2) || double.IsInfinity(d2); Debug.Assert(bInvalid == false); if (bInvalid == false) { triangles[i] = ti; centers[i] = mesh.GetTriCentroid(ti); i++; } // otherwise skip this tri } boxes_set tris = new boxes_set(); boxes_set nodes = new boxes_set(); AxisAlignedBox3f rootBox; int rootnode = (bSorted) ? split_tri_set_sorted(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox) : split_tri_set_midpoint(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox); box_to_index = tris.box_to_index; box_centers = tris.box_centers; box_extents = tris.box_extents; index_list = tris.index_list; triangles_end = tris.iIndicesCur; int iIndexShift = triangles_end; int iBoxShift = tris.iBoxCur; // ok now append internal node boxes & index ptrs for (i = 0; i < nodes.iBoxCur; ++i) { box_centers.insert(nodes.box_centers[i], iBoxShift + i); box_extents.insert(nodes.box_extents[i], iBoxShift + i); // internal node indices are shifted box_to_index.insert(iIndexShift + nodes.box_to_index[i], iBoxShift + i); } // now append index list for (i = 0; i < nodes.iIndicesCur; ++i) { int child_box = nodes.index_list[i]; if (child_box < 0) // this is a triangles box { child_box = (-child_box) - 1; } else { child_box += iBoxShift; } child_box = child_box + 1; index_list.insert(child_box, iIndexShift + i); } root_index = rootnode + iBoxShift; }
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); var 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 var 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... var 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 var 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 var 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); }
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 = (insert.Loops.Count != 1) || (insert.Loops[0].VertexCount != polys[pi].VertexCount); } } 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 bool Insert() { OuterInsert = new MeshInsertUVPolyCurve(Mesh, Polygon.Outer); Util.gDevAssert(OuterInsert.Validate() == ValidationStatus.Ok); bool outerApplyOK = OuterInsert.Apply(); if (outerApplyOK == false || OuterInsert.Loops.Count == 0) { return(false); } if (SimplifyInsertion) { OuterInsert.Simplify(); } HoleInserts = new List <MeshInsertUVPolyCurve>(Polygon.Holes.Count); for (int hi = 0; hi < Polygon.Holes.Count; ++hi) { var insert = new MeshInsertUVPolyCurve(Mesh, Polygon.Holes[hi]); Util.gDevAssert(insert.Validate() == ValidationStatus.Ok); insert.Apply(); if (SimplifyInsertion) { insert.Simplify(); } HoleInserts.Add(insert); } // find a triangle connected to loop that is inside the polygon // [TODO] maybe we could be a bit more robust about this? at least // check if triangle is too degenerate... int seed_tri = -1; EdgeLoop outer_loop = OuterInsert.Loops[0]; for (int i = 0; i < outer_loop.EdgeCount; ++i) { if (!Mesh.IsEdge(outer_loop.Edges[i])) { continue; } Index2i et = Mesh.GetEdgeT(outer_loop.Edges[i]); Vector3d ca = Mesh.GetTriCentroid(et.a); bool in_a = Polygon.Outer.Contains(ca.xy); Vector3d cb = Mesh.GetTriCentroid(et.b); bool in_b = Polygon.Outer.Contains(cb.xy); if (in_a && in_b == false) { seed_tri = et.a; break; } else if (in_b && in_a == false) { seed_tri = et.b; break; } } if (seed_tri == -1) { throw new Exception("MeshPolygonsInserter: could not find seed triangle!"); } // make list of all outer & hole edges InsertedPolygonEdges = new HashSet <int>(outer_loop.Edges); foreach (var insertion in HoleInserts) { foreach (int eid in insertion.Loops[0].Edges) { InsertedPolygonEdges.Add(eid); } } // flood-fill inside loop from seed triangle InteriorTriangles = new MeshFaceSelection(Mesh); InteriorTriangles.FloodFill(seed_tri, null, (eid) => { return(InsertedPolygonEdges.Contains(eid) == false); }); 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); } } }