public void AddHole(Polygon2d hole, bool bCheck = true) { if (outer == null) { throw new Exception("GeneralPolygon2d.AddHole: outer polygon not set!"); } if (bCheck) { if (outer.Contains(hole) == false) { throw new Exception("GeneralPolygon2d.AddHole: outer does not contain hole!"); } // [RMS] segment/segment intersection broken? foreach (var hole2 in holes) { if (hole.Intersects(hole2)) { throw new Exception("GeneralPolygon2D.AddHole: new hole intersects existing hole!"); } } } if ((bOuterIsCW && hole.IsClockwise) || (bOuterIsCW == false && hole.IsClockwise == false)) { throw new Exception("GeneralPolygon2D.AddHole: new hole has same orientation as outer polygon!"); } holes.Add(hole); }
// Finds set of "solid" regions - eg boundary loops with interior holes. // Result has outer loops being clockwise, and holes counter-clockwise public SolidRegionInfo FindSolidRegions(double fSimplifyDeviationTol = 0.1, bool bWantCurveSolids = true) { List <SmoothLoopElement> valid = new List <SmoothLoopElement>(LoopsItr()); int N = valid.Count; // precompute bounding boxes int maxid = 0; foreach (var v in valid) { maxid = Math.Max(maxid, v.ID + 1); } AxisAlignedBox2d[] bounds = new AxisAlignedBox2d[maxid]; foreach (var v in valid) { bounds[v.ID] = v.Bounds(); } // copy polygons, simplify if desired double fClusterTol = 0.0; // don't do simple clustering, can lose corners double fDeviationTol = fSimplifyDeviationTol; Polygon2d[] polygons = new Polygon2d[maxid]; foreach (var v in valid) { Polygon2d p = new Polygon2d(v.polygon); if (fClusterTol > 0 || fDeviationTol > 0) { p.Simplify(fClusterTol, fDeviationTol); } polygons[v.ID] = p; } // sort by bbox containment to speed up testing (does it??) valid.Sort((x, y) => { return(bounds[x.ID].Contains(bounds[y.ID]) ? -1 : 1); }); // containment sets bool[] bIsContained = new bool[N]; Dictionary <int, List <int> > ContainSets = new Dictionary <int, List <int> >(); // construct containment sets for (int i = 0; i < N; ++i) { SmoothLoopElement loopi = valid[i]; Polygon2d polyi = polygons[loopi.ID]; for (int j = 0; j < N; ++j) { if (i == j) { continue; } SmoothLoopElement loopj = valid[j]; Polygon2d polyj = polygons[loopj.ID]; // cannot be contained if bounds are not contained if (bounds[loopi.ID].Contains(bounds[loopj.ID]) == false) { continue; } // any other early-outs?? if (polyi.Contains(polyj)) { if (ContainSets.ContainsKey(i) == false) { ContainSets.Add(i, new List <int>()); } ContainSets[i].Add(j); bIsContained[j] = true; } } } List <GeneralPolygon2d> polysolids = new List <GeneralPolygon2d>(); List <PlanarSolid2d> solids = new List <PlanarSolid2d>(); List <SmoothLoopElement> used = new List <SmoothLoopElement>(); // extract solids from containment relationships foreach (var i in ContainSets.Keys) { SmoothLoopElement outer_element = valid[i]; used.Add(outer_element); if (bIsContained[i]) { throw new Exception("PlanarComplex.FindSolidRegions: multiply-nested regions not supported!"); } Polygon2d outer_poly = polygons[outer_element.ID]; IParametricCurve2d outer_loop = (bWantCurveSolids) ? outer_element.source.Clone() : null; if (outer_poly.IsClockwise == false) { outer_poly.Reverse(); if (bWantCurveSolids) { outer_loop.Reverse(); } } GeneralPolygon2d g = new GeneralPolygon2d(); g.Outer = outer_poly; PlanarSolid2d s = new PlanarSolid2d(); if (bWantCurveSolids) { s.SetOuter(outer_loop, true); } foreach (int hi in ContainSets[i]) { SmoothLoopElement he = valid[hi]; used.Add(he); Polygon2d hole_poly = polygons[he.ID]; IParametricCurve2d hole_loop = (bWantCurveSolids) ? he.source.Clone() : null; if (hole_poly.IsClockwise) { hole_poly.Reverse(); if (bWantCurveSolids) { hole_loop.Reverse(); } } try { g.AddHole(hole_poly); if (hole_loop != null) { s.AddHole(hole_loop); } } catch { // don't add this hole - must intersect or something // We should have caught this earlier! } } polysolids.Add(g); if (bWantCurveSolids) { solids.Add(s); } } for (int i = 0; i < N; ++i) { SmoothLoopElement loopi = valid[i]; if (used.Contains(loopi)) { continue; } Polygon2d outer_poly = polygons[loopi.ID]; IParametricCurve2d outer_loop = (bWantCurveSolids) ? loopi.source.Clone() : null; if (outer_poly.IsClockwise == false) { outer_poly.Reverse(); if (bWantCurveSolids) { outer_loop.Reverse(); } } GeneralPolygon2d g = new GeneralPolygon2d(); g.Outer = outer_poly; PlanarSolid2d s = new PlanarSolid2d(); if (bWantCurveSolids) { s.SetOuter(outer_loop, true); } polysolids.Add(g); if (bWantCurveSolids) { solids.Add(s); } } return(new SolidRegionInfo() { Polygons = polysolids, Solids = (bWantCurveSolids) ? solids : null }); }
// Finds set of "solid" regions - eg boundary loops with interior holes. // Result has outer loops being clockwise, and holes counter-clockwise public SolidRegionInfo FindSolidRegions(FindSolidsOptions options) { var validLoops = new List <SmoothLoopElement>(LoopsItr()); int N = validLoops.Count; // precompute bounding boxes int maxid = 0; foreach (var v in validLoops) { maxid = Math.Max(maxid, v.ID + 1); } var bounds = new AxisAlignedBox2d[maxid]; foreach (var v in validLoops) { bounds[v.ID] = v.Bounds(); } // copy polygons, simplify if desired double fClusterTol = 0.0; // don't do simple clustering, can lose corners double fDeviationTol = options.SimplifyDeviationTolerance; var polygons = new Polygon2d[maxid]; foreach (var v in validLoops) { var p = new Polygon2d(v.polygon); if (fClusterTol > 0 || fDeviationTol > 0) { p.Simplify(fClusterTol, fDeviationTol); } polygons[v.ID] = p; } // sort by bbox containment to speed up testing (does it??) validLoops.Sort((x, y) => { return(bounds[x.ID].Contains(bounds[y.ID]) ? -1 : 1); }); // containment sets bool[] bIsContained = new bool[N]; var ContainSets = new Dictionary <int, List <int> >(); var ContainedParents = new Dictionary <int, List <int> >(); bool bUseOrient = options.TrustOrientations; bool bWantCurveSolids = options.WantCurveSolids; bool bCheckHoles = !options.AllowOverlappingHoles; // construct containment sets for (int i = 0; i < N; ++i) { SmoothLoopElement loopi = validLoops[i]; Polygon2d polyi = polygons[loopi.ID]; for (int j = 0; j < N; ++j) { if (i == j) { continue; } SmoothLoopElement loopj = validLoops[j]; Polygon2d polyj = polygons[loopj.ID]; // if we are preserving orientations, holes cannot contain holes and // outers cannot contain outers! if (bUseOrient && loopj.polygon.IsClockwise == loopi.polygon.IsClockwise) { continue; } // cannot be contained if bounds are not contained if (bounds[loopi.ID].Contains(bounds[loopj.ID]) == false) { continue; } // any other early-outs?? if (polyi.Contains(polyj)) { if (ContainSets.ContainsKey(i) == false) { ContainSets.Add(i, new List <int>()); } ContainSets[i].Add(j); bIsContained[j] = true; if (ContainedParents.ContainsKey(j) == false) { ContainedParents.Add(j, new List <int>()); } ContainedParents[j].Add(i); } } } var polysolids = new List <GeneralPolygon2d>(); var polySolidsInfo = new List <GeneralSolid>(); var solids = new List <PlanarSolid2d>(); var used = new HashSet <SmoothLoopElement>(); var LoopToOuterIndex = new Dictionary <SmoothLoopElement, int>(); var ParentsToProcess = new List <int>(); // The following is a lot of code but it is very similar, just not clear how // to refactor out the common functionality // 1) we find all the top-level uncontained polys and add them to the final polys list // 2a) for any poly contained in those parent-polys, that is not also contained in anything else, // add as hole to that poly // 2b) remove all those used parents & holes from consideration // 2c) now find all the "new" top-level polys // 3) repeat 2a-c until done all polys // 4) any remaining polys must be interior solids w/ no holes // **or** weird leftovers like intersecting polys... // add all top-level uncontained polys for (int i = 0; i < N; ++i) { SmoothLoopElement loopi = validLoops[i]; if (bIsContained[i]) { continue; } Polygon2d outer_poly = polygons[loopi.ID]; IParametricCurve2d outer_loop = (bWantCurveSolids) ? loopi.source.Clone() : null; if (outer_poly.IsClockwise == false) { outer_poly.Reverse(); if (bWantCurveSolids) { outer_loop.Reverse(); } } var g = new GeneralPolygon2d(); g.Outer = outer_poly; var s = new PlanarSolid2d(); if (bWantCurveSolids) { s.SetOuter(outer_loop, true); } int idx = polysolids.Count; LoopToOuterIndex[loopi] = idx; used.Add(loopi); if (ContainSets.ContainsKey(i)) { ParentsToProcess.Add(i); } polysolids.Add(g); polySolidsInfo.Add(new GeneralSolid() { Outer = loopi }); if (bWantCurveSolids) { solids.Add(s); } } // keep iterating until we processed all parent loops while (ParentsToProcess.Count > 0) { var ContainersToRemove = new List <int>(); // now for all top-level polys that contain children, add those children // as long as they do not have multiple contain-parents foreach (int i in ParentsToProcess) { SmoothLoopElement parentloop = validLoops[i]; int outer_idx = LoopToOuterIndex[parentloop]; List <int> children = ContainSets[i]; foreach (int childj in children) { SmoothLoopElement childLoop = validLoops[childj]; Debug.Assert(used.Contains(childLoop) == false); // skip multiply-contained children List <int> parents = ContainedParents[childj]; if (parents.Count > 1) { continue; } Polygon2d hole_poly = polygons[childLoop.ID]; IParametricCurve2d hole_loop = (bWantCurveSolids) ? childLoop.source.Clone() : null; if (hole_poly.IsClockwise) { hole_poly.Reverse(); if (bWantCurveSolids) { hole_loop.Reverse(); } } try { polysolids[outer_idx].AddHole(hole_poly, bCheckHoles); polySolidsInfo[outer_idx].Holes.Add(childLoop); if (hole_loop != null) { solids[outer_idx].AddHole(hole_loop); } } catch { // don't add this hole - must intersect or something? // We should have caught this earlier! } used.Add(childLoop); if (ContainSets.ContainsKey(childj)) { ContainersToRemove.Add(childj); } } ContainersToRemove.Add(i); } // remove all containers that are no longer valid foreach (int ci in ContainersToRemove) { ContainSets.Remove(ci); // have to remove from each ContainedParents list var keys = new List <int>(ContainedParents.Keys); foreach (int j in keys) { if (ContainedParents[j].Contains(ci)) { ContainedParents[j].Remove(ci); } } } ParentsToProcess.Clear(); // ok now find next-level uncontained parents... for (int i = 0; i < N; ++i) { SmoothLoopElement loopi = validLoops[i]; if (used.Contains(loopi)) { continue; } if (ContainSets.ContainsKey(i) == false) { continue; } List <int> parents = ContainedParents[i]; if (parents.Count > 0) { continue; } Polygon2d outer_poly = polygons[loopi.ID]; IParametricCurve2d outer_loop = (bWantCurveSolids) ? loopi.source.Clone() : null; if (outer_poly.IsClockwise == false) { outer_poly.Reverse(); if (bWantCurveSolids) { outer_loop.Reverse(); } } var g = new GeneralPolygon2d(); g.Outer = outer_poly; var s = new PlanarSolid2d(); if (bWantCurveSolids) { s.SetOuter(outer_loop, true); } int idx = polysolids.Count; LoopToOuterIndex[loopi] = idx; used.Add(loopi); if (ContainSets.ContainsKey(i)) { ParentsToProcess.Add(i); } polysolids.Add(g); polySolidsInfo.Add(new GeneralSolid() { Outer = loopi }); if (bWantCurveSolids) { solids.Add(s); } } } // any remaining loops must be top-level for (int i = 0; i < N; ++i) { SmoothLoopElement loopi = validLoops[i]; if (used.Contains(loopi)) { continue; } Polygon2d outer_poly = polygons[loopi.ID]; IParametricCurve2d outer_loop = (bWantCurveSolids) ? loopi.source.Clone() : null; if (outer_poly.IsClockwise == false) { outer_poly.Reverse(); if (bWantCurveSolids) { outer_loop.Reverse(); } } var g = new GeneralPolygon2d(); g.Outer = outer_poly; var s = new PlanarSolid2d(); if (bWantCurveSolids) { s.SetOuter(outer_loop, true); } polysolids.Add(g); polySolidsInfo.Add(new GeneralSolid() { Outer = loopi }); if (bWantCurveSolids) { solids.Add(s); } } return(new SolidRegionInfo() { Polygons = polysolids, PolygonsSources = polySolidsInfo, Solids = (bWantCurveSolids) ? solids : null }); }
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); }
/// <summary> /// Constrained laplacian smoothing of input polygon, alpha X iterations. /// vertices are only allowed to move at most max_dist from constraint /// if bAllowShrink == false, vertices are kept outside input polygon /// if bAllowGrow == false, vertices are kept inside input polygon /// /// max_dist is measured from vertex[i] to original_vertex[i], unless /// you set bPerVertexDistances = false, then distance to original polygon /// is used (which is much more expensive) /// /// [TODO] this is pretty hacky...could be better in lots of ways... /// /// </summary> public static void LaplacianSmoothConstrained(Polygon2d poly, double alpha, int iterations, double max_dist, bool bAllowShrink, bool bAllowGrow, bool bPerVertexDistances = true) { Polygon2d origPoly = new Polygon2d(poly); int N = poly.VertexCount; Vector2d[] newV = new Vector2d[poly.VertexCount]; double max_dist_sqr = max_dist * max_dist; double beta = 1.0 - alpha; for (int ii = 0; ii < iterations; ++ii) { for (int i = 0; i < N; ++i) { Vector2d curpos = poly[i]; Vector2d smoothpos = (poly[(i + N - 1) % N] + poly[(i + 1) % N]) * 0.5; bool do_smooth = true; if (bAllowShrink == false || bAllowGrow == false) { bool is_inside = origPoly.Contains(smoothpos); if (is_inside == true) { do_smooth = bAllowShrink; } else { do_smooth = bAllowGrow; } } // [RMS] this is old code...I think not correct? //bool contained = true; //if (bAllowShrink == false || bAllowGrow == false) // contained = origPoly.Contains(smoothpos); //bool do_smooth = true; //if (bAllowShrink && contained == false) // do_smooth = false; //if (bAllowGrow && contained == true) // do_smooth = false; if (do_smooth) { Vector2d newpos = beta * curpos + alpha * smoothpos; if (bPerVertexDistances) { while (origPoly[i].DistanceSquared(newpos) > max_dist_sqr) { newpos = (newpos + curpos) * 0.5; } } else { while (origPoly.DistanceSquared(newpos) > max_dist_sqr) { newpos = (newpos + curpos) * 0.5; } } newV[i] = newpos; } else { newV[i] = curpos; } } for (int i = 0; i < N; ++i) { poly[i] = newV[i]; } } }