void insert_triangle(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c, bool threadsafe = true) { bool lockTaken = false; while (threadsafe == true && lockTaken == false) { spinlock.Enter(ref lockTaken); } // [TODO] actually want to conservatively rasterize triangles here, not just // store in every cell in bbox! AxisAlignedBox2d bounds = BoundsUtil.Bounds(ref a, ref b, ref c); Vector2i imin = indexer.ToGrid(bounds.Min); Vector2i imax = indexer.ToGrid(bounds.Max); for (int yi = imin.y; yi <= imax.y; ++yi) { for (int xi = imin.x; xi <= imax.x; ++xi) { // check if triangle overlaps this grid cell... int bin_i = yi * bins_x + xi; bins_list.Insert(bin_i, triangle_id); } } if (lockTaken) { spinlock.Exit(); } }
public Box2d(AxisAlignedBox2d aaBox) { Extent = 0.5 * aaBox.Diagonal; Center = aaBox.Min + Extent; AxisX = Vector2d.AxisX; AxisY = Vector2d.AxisY; }
/// <summary> /// find all triangles that overlap range /// </summary> public void FindTrianglesInRange(AxisAlignedBox2d range, HashSet <int> triangles) { Vector2i grid_min = indexer.ToGrid(range.Min); if (grid_bounds.Contains(grid_min) == false) { throw new Exception("TriangleBinsGrid2d.FindTrianglesInRange: range.Min is out of bounds"); } Vector2i grid_max = indexer.ToGrid(range.Max); if (grid_bounds.Contains(grid_max) == false) { throw new Exception("TriangleBinsGrid2d.FindTrianglesInRange: range.Max is out of bounds"); } for (int yi = grid_min.y; yi <= grid_max.y; ++yi) { for (int xi = grid_min.x; xi <= grid_max.x; ++xi) { int bin_i = yi * bins_x + xi; foreach (int tid in bins_list.ValueItr(bin_i)) { triangles.Add(tid); } } } }
public List <Vector2d> FindIntersections(Segment2d oseg) { List <Vector2d> v = new List <Vector2d>(); var osegBounds = new AxisAlignedBox2d(); osegBounds.Contain(oseg.P0); osegBounds.Contain(oseg.P1); if (!this.GetBounds().Intersects(osegBounds)) { return(v); } foreach (Segment2d seg in SegmentItr()) { // this computes test twice for intersections, but seg.intersects doesn't // create any new objects so it should be much faster for majority of segments (should profile!) if (seg.Intersects(oseg)) { IntrSegment2Segment2 intr = new IntrSegment2Segment2(seg, oseg); if (intr.Find()) { v.Add(intr.Point0); if (intr.Quantity == 2) { v.Add(intr.Point1); } } } } return(v); }
public void Contain(AxisAlignedBox2d box) { Min.x = Math.Min(Min.x, box.Min.x); Min.y = Math.Min(Min.y, box.Min.y); Max.x = Math.Max(Max.x, box.Max.x); Max.y = Math.Max(Max.y, box.Max.y); }
public void PrintStats(string label = "") { System.Console.WriteLine("PlanarComplex Stats {0}", label); var Loops = new List <SmoothLoopElement>(LoopsItr()); var Curves = new List <SmoothCurveElement>(CurvesItr()); AxisAlignedBox2d bounds = Bounds(); System.Console.WriteLine(" Bounding Box w: {0} h: {1} range {2} ", bounds.Width, bounds.Height, bounds); var vEndpoints = new List <ComplexEndpoint2d>(EndpointsItr()); System.Console.WriteLine(" Closed Loops {0} Open Curves {1} Open Endpoints {2}", Loops.Count, Curves.Count, vEndpoints.Count); int nSegments = CountType(typeof(Segment2d)); int nArcs = CountType(typeof(Arc2d)); int nCircles = CountType(typeof(Circle2d)); int nNURBS = CountType(typeof(NURBSCurve2)); int nEllipses = CountType(typeof(Ellipse2d)); int nEllipseArcs = CountType(typeof(EllipseArc2d)); int nSeqs = CountType(typeof(ParametricCurveSequence2)); System.Console.WriteLine(" [Type Counts] // {0} multi-curves", nSeqs); System.Console.WriteLine(" segments {0,4} arcs {1,4} circles {2,4}", nSegments, nArcs, nCircles); System.Console.WriteLine(" nurbs {0,4} ellipses {1,4} ellipse-arcs {2,4}", nNURBS, nEllipses, nEllipseArcs); }
public void PrintStats(string label = "") { System.Console.WriteLine("PlanarComplex Stats {0}", label); List <SmoothLoopElement> Loops = new List <SmoothLoopElement>(LoopsItr()); List <SmoothCurveElement> Curves = new List <SmoothCurveElement>(CurvesItr()); AxisAlignedBox2d bounds = Bounds(); System.Console.WriteLine(" Bounding Box: " + bounds); System.Console.WriteLine(" Closed Loops: " + Loops.Count.ToString()); System.Console.WriteLine(" Open Curves: " + Curves.Count.ToString()); List <ComplexEndpoint2d> vEndpoints = new List <ComplexEndpoint2d>(EndpointsItr()); System.Console.WriteLine(" Open Endpoints: " + vEndpoints.Count.ToString()); int nSegments = CountType(typeof(Segment2d)); int nArcs = CountType(typeof(Arc2d)); int nCircles = CountType(typeof(Circle2d)); int nNURBS = CountType(typeof(NURBSCurve2)); int nEllipses = CountType(typeof(Ellipse2d)); int nEllipseArcs = CountType(typeof(EllipseArc2d)); int nSeqs = CountType(typeof(ParametricCurveSequence2)); System.Console.WriteLine(" Type Counts: "); System.Console.WriteLine(" segments {0,4} arcs {1,4} circles {2,4}", nSegments, nArcs, nCircles); System.Console.WriteLine(" nurbs {0,4} ellipses {1,4} ellipse-arcs {2,4}", nNURBS, nEllipses, nEllipseArcs); System.Console.WriteLine(" multi {0,4} ", nSeqs); }
void remove_triangle(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c, bool threadsafe = true) { bool lockTaken = false; while (threadsafe == true && lockTaken == false) { spinlock.Enter(ref lockTaken); } AxisAlignedBox2d bounds = BoundsUtil.Bounds(ref a, ref b, ref c); Vector2i imin = indexer.ToGrid(bounds.Min); Vector2i imax = indexer.ToGrid(bounds.Max); for (int yi = imin.y; yi <= imax.y; ++yi) { for (int xi = imin.x; xi <= imax.x; ++xi) { int bin_i = yi * bins_x + xi; bins_list.Remove(bin_i, triangle_id); } } if (lockTaken) { spinlock.Exit(); } }
public AxisAlignedBox2d GetBounds() { AxisAlignedBox2d box = AxisAlignedBox2d.Empty; box.Contain(vertices); return(box); }
public Arrangement2d(AxisAlignedBox2d boundsHint) { Graph = new DGraph2(); double cellSize = boundsHint.MaxDim / 64; PointHash = new PointHashGrid2d <int>(cellSize, -1); }
public SVGWriter() { Objects = new List <object>(); Bounds = AxisAlignedBox2d.Empty; DefaultPolygonStyle = Style.Outline("black", 1); DefaultPolylineStyle = Style.Outline("black", 1); DefaultCircleStyle = Style.Filled("green", "black", 1); }
public AxisAlignedBox2d Bounds() { AxisAlignedBox2d box = AxisAlignedBox2d.Empty; foreach (Element e in vElements) { box.Contain(e.Bounds()); } return(box); }
public void AddBox(AxisAlignedBox2d box, Style style) { Polygon2d poly = new Polygon2d(); for (int k = 0; k < 4; ++k) { poly.AppendVertex(box.GetCorner(k)); } AddPolygon(poly, style); }
public static AxisAlignedBox2d Bounds(IEnumerable <Vector2d> positions) { AxisAlignedBox2d box = AxisAlignedBox2d.Empty; foreach (Vector2d v in positions) { box.Contain(v); } return(box); }
public SVGWriter() { Objects = new List <object>(); Bounds = AxisAlignedBox2d.Empty; DefaultPolygonStyle = Style.Outline("grey", 1); DefaultPolylineStyle = Style.Outline("cyan", 1); DefaultCircleStyle = Style.Filled("green", "black", 1); DefaultArcStyle = Style.Outline("magenta", 1); DefaultLineStyle = Style.Outline("black", 1); DefaultDGraphStyle = Style.Outline("blue", 1); }
/// <summary> /// Actually computes the insertion. In some cases we would like more info /// coming back than we get by using Generate() api. Note that resulting /// mesh is *not* compacted. /// </summary> public DMesh3 ComputeResult(out MeshInsertPolygon insertion) { AxisAlignedBox2d bounds = Polygon.Bounds; double padding = 0.1 * bounds.DiagonalLength; bounds.Expand(padding); TrivialRectGenerator rectgen = (Subdivisions == 1) ? new TrivialRectGenerator() : new GriddedRectGenerator() { EdgeVertices = Subdivisions }; rectgen.Width = (float)bounds.Width; rectgen.Height = (float)bounds.Height; rectgen.IndicesMap = new Index2i(1, 2); rectgen.UVMode = UVMode; rectgen.Clockwise = true; // MeshPolygonInserter assumes mesh faces are CW? (except code says CCW...) rectgen.Generate(); var base_mesh = new DMesh3(); rectgen.MakeMesh(base_mesh); var shiftPolygon = new GeneralPolygon2d(Polygon); Vector2d shift = bounds.Center; shiftPolygon.Translate(-shift); var insert = new MeshInsertPolygon() { Mesh = base_mesh, Polygon = shiftPolygon }; bool bOK = insert.Insert(); if (!bOK) { throw new Exception("TriangulatedPolygonGenerator: failed to Insert()"); } MeshFaceSelection selected = insert.InteriorTriangles; var editor = new MeshEditor(base_mesh); editor.RemoveTriangles((tid) => { return(selected.IsSelected(tid) == false); }, true); var shift3 = new Vector3d(shift.x, shift.y, 0); MeshTransforms.Translate(base_mesh, shift3); insertion = insert; return(base_mesh); }
void compute_polygon() { SpansPoly = new Polygon2d(); for (int i = 0; i < FillSpans.Count; ++i) { foreach (int vid in FillSpans[i].Vertices) { Vector2d v = to2D(Mesh.GetVertex(vid)); SpansPoly.AppendVertex(v); } } Bounds = SpansPoly.Bounds; }
/// <summary> /// "invalid" value will be returned by queries if no valid result is found (eg bounded-distance query) /// </summary> public TriangleBinsGrid2d(AxisAlignedBox2d bounds, int numCells) { this.bounds = bounds; double cellsize = bounds.MaxDim / (double)numCells; Vector2d origin = bounds.Min - cellsize * 0.5 * Vector2d.One; indexer = new ShiftGridIndexer2(origin, cellsize); bins_x = (int)(bounds.Width / cellsize) + 2; bins_y = (int)(bounds.Height / cellsize) + 2; grid_bounds = new AxisAlignedBox2i(0, 0, bins_x - 1, bins_y - 1); bins_list = new SmallListSet(); bins_list.Resize(bins_x * bins_y); }
public AxisAlignedBox2d GetBounds() { if (vertices.Count == 0) { return(AxisAlignedBox2d.Empty); } AxisAlignedBox2d box = new AxisAlignedBox2d(vertices[0]); for (int i = 1; i < vertices.Count; ++i) { box.Contain(vertices[i]); } return(box); }
public AxisAlignedBox2d Intersect(AxisAlignedBox2d box) { AxisAlignedBox2d intersect = new AxisAlignedBox2d( Math.Max(Min.x, box.Min.x), Math.Max(Min.y, box.Min.y), Math.Min(Max.x, box.Max.x), Math.Min(Max.y, box.Max.y)); if (intersect.Height <= 0 || intersect.Width <= 0) { return(AxisAlignedBox2d.Empty); } else { return(intersect); } }
public SVGWriter() { CurrentLayer = new Layer("default"); Layers = new List <Layer>() { CurrentLayer }; Bounds = AxisAlignedBox2d.Empty; DefaultPolygonStyle = Style.Outline("grey", 1); DefaultPolylineStyle = Style.Outline("cyan", 1); DefaultCircleStyle = Style.Filled("green", "black", 1); DefaultArcStyle = Style.Outline("magenta", 1); DefaultLineStyle = Style.Outline("black", 1); DefaultDGraphStyle = Style.Outline("blue", 1); }
void compute_polygons() { Bounds = AxisAlignedBox2d.Empty; for (int i = 0; i < Loops.Count; ++i) { EdgeLoop loop = Loops[i].edgeLoop; Polygon2d poly = new Polygon2d(); foreach (int vid in loop.Vertices) { Vector2d v = to2D(Mesh.GetVertex(vid)); poly.AppendVertex(v); } Loops[i].poly = poly; Bounds.Contain(poly.Bounds); } }
public void Contain(ref AxisAlignedBox2d box) { if (box.Min.x < Min.x) { Min.x = box.Min.x; } if (box.Max.x > Max.x) { Max.x = box.Max.x; } if (box.Min.y < Min.y) { Min.y = box.Min.y; } if (box.Max.y > Max.y) { Max.y = box.Max.y; } }
public bool Equals(AxisAlignedBox2d other, double tolerance = MathUtil.Epsilon) { if (!MathUtil.EpsilonEqual(Min.x, other.Min.x, tolerance)) { return(false); } if (!MathUtil.EpsilonEqual(Min.y, other.Min.y, tolerance)) { return(false); } if (!MathUtil.EpsilonEqual(Max.x, other.Max.x, tolerance)) { return(false); } if (!MathUtil.EpsilonEqual(Max.y, other.Max.y, tolerance)) { return(false); } return(true); }
/// <summary> /// Regular-grid tiling of element inside bounds, with spacing between elements /// Returns list of translations to element. /// Always allows at least one row and column, even if element overflows bounds in that dimension. /// </summary> public static List <Vector2d> BoundedRegularTiling2(AxisAlignedBox2d element, AxisAlignedBox2d bounds, double spacing) { Vector2d oshift = -element.Min; double w = element.Width; double h = element.Height; int nx = Math.Max(1, (int)(bounds.Width / w)); double spacew = (nx - 1) * spacing; while (nx > 1 && bounds.Width - (w * nx + spacew) < 0) { nx--; } int ny = Math.Max(1, (int)(bounds.Height / h)); double spaceh = (ny - 1) * spacing; while (ny > 1 && bounds.Height - (h * ny + spaceh) < 0) { ny--; } var translations = new List <Vector2d>(); for (int yi = 0; yi < ny; ++yi) { double dy = yi * h + yi * spacing; for (int xi = 0; xi < nx; ++xi) { double dx = xi * w + xi * spacing; translations.Add(new Vector2d(dx, dy) + oshift + bounds.Min); } } return(translations); }
// (sequentially) find each triangle that path point lies in, and insert a vertex for // that point into mesh. void insert_corners(HashSet <int> MeshVertsOnCurve) { PrimalQuery2d query = new PrimalQuery2d(PointF); if (UseTriSpatial) { int count = Mesh.TriangleCount + Curve.VertexCount; int bins = 32; if (count < 25) { bins = 8; } else if (count < 100) { bins = 16; } AxisAlignedBox3d bounds3 = Mesh.CachedBounds; AxisAlignedBox2d bounds2 = new AxisAlignedBox2d(bounds3.Min.xy, bounds3.Max.xy); triSpatial = new TriangleBinsGrid2d(bounds2, bins); foreach (int tid in Mesh.TriangleIndices()) { spatial_add_triangle(tid); } } Func <int, Vector2d, bool> inTriangleF = (tid, pos) => { Index3i tv = Mesh.GetTriangle(tid); int query_result = query.ToTriangleUnsigned(pos, tv.a, tv.b, tv.c); return(query_result == -1 || query_result == 0); }; CurveVertices = new int[Curve.VertexCount]; for (int i = 0; i < Curve.VertexCount; ++i) { Vector2d vInsert = Curve[i]; bool inserted = false; int contain_tid = DMesh3.InvalidID; if (triSpatial != null) { contain_tid = triSpatial.FindContainingTriangle(vInsert, inTriangleF); } else { foreach (int tid in Mesh.TriangleIndices()) { Index3i tv = Mesh.GetTriangle(tid); // [RMS] using unsigned query here because we do not need to care about tri CW/CCW orientation // (right? otherwise we have to explicitly invert mesh. Nothing else we do depends on tri orientation) //int query_result = query.ToTriangle(vInsert, tv.a, tv.b, tv.c); int query_result = query.ToTriangleUnsigned(vInsert, tv.a, tv.b, tv.c); if (query_result == -1 || query_result == 0) { contain_tid = tid; break; } } } if (contain_tid != DMesh3.InvalidID) { Index3i tv = Mesh.GetTriangle(contain_tid); Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c)); // SpatialEpsilon is our zero-tolerance, so merge if we are closer than that bool is_existing_v; int vid = insert_corner_from_bary(i, contain_tid, bary, 0.01, 100 * SpatialEpsilon, out is_existing_v); if (vid > 0) // this should be always happening.. { CurveVertices[i] = vid; if (is_existing_v) { MeshVertsOnCurve.Add(vid); } inserted = true; } else { throw new Exception("MeshInsertUVPolyCurve.insert_corners: failed to insert vertex " + i.ToString()); } } if (inserted == false) { throw new Exception("MeshInsertUVPolyCurve.insert_corners: curve vertex " + i.ToString() + " is not inside or on any mesh triangle!"); } } }
public void AddBox(AxisAlignedBox2d box) { AddBox(box, DefaultPolygonStyle); }
// 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 }); }
public virtual bool Apply() { HashSet <int> OnCurveVerts = new HashSet <int>(); // original vertices that were epsilon-coincident w/ curve vertices insert_corners(OnCurveVerts); // [RMS] not using this? //HashSet<int> corner_v = new HashSet<int>(CurveVertices); // not sure we need to track all of these HashSet <int> ZeroEdges = new HashSet <int>(); HashSet <int> ZeroVertices = new HashSet <int>(); OnCutEdges = new HashSet <int>(); HashSet <int> NewEdges = new HashSet <int>(); HashSet <int> NewCutVertices = new HashSet <int>(); sbyte[] signs = new sbyte[2 * Mesh.MaxVertexID + 2 * Curve.VertexCount]; HashSet <int> segTriangles = new HashSet <int>(); HashSet <int> segVertices = new HashSet <int>(); HashSet <int> segEdges = new HashSet <int>(); // loop over segments, insert each one in sequence int N = (IsLoop) ? Curve.VertexCount : Curve.VertexCount - 1; for (int si = 0; si < N; ++si) { int i0 = si; int i1 = (si + 1) % Curve.VertexCount; Segment2d seg = new Segment2d(Curve[i0], Curve[i1]); int i0_vid = CurveVertices[i0]; int i1_vid = CurveVertices[i1]; // If these vertices are already connected by an edge, we can just continue. int existing_edge = Mesh.FindEdge(i0_vid, i1_vid); if (existing_edge != DMesh3.InvalidID) { add_cut_edge(existing_edge); continue; } if (triSpatial != null) { segTriangles.Clear(); segVertices.Clear(); segEdges.Clear(); AxisAlignedBox2d segBounds = new AxisAlignedBox2d(seg.P0); segBounds.Contain(seg.P1); segBounds.Expand(MathUtil.ZeroTolerancef * 10); triSpatial.FindTrianglesInRange(segBounds, segTriangles); IndexUtil.TrianglesToVertices(Mesh, segTriangles, segVertices); IndexUtil.TrianglesToEdges(Mesh, segTriangles, segEdges); } int MaxVID = Mesh.MaxVertexID; IEnumerable <int> vertices = Interval1i.Range(MaxVID); if (triSpatial != null) { vertices = segVertices; } // compute edge-crossing signs // [TODO] could walk along mesh from a to b, rather than computing for entire mesh? if (signs.Length < MaxVID) { signs = new sbyte[2 * MaxVID]; } gParallel.ForEach(vertices, (vid) => { if (Mesh.IsVertex(vid)) { if (vid == i0_vid || vid == i1_vid) { signs[vid] = 0; } else { Vector2d v2 = PointF(vid); // tolerance defines band in which we will consider values to be zero signs[vid] = (sbyte)seg.WhichSide(v2, SpatialEpsilon); } } else { signs[vid] = sbyte.MaxValue; } }); // have to skip processing of new edges. If edge id // is > max at start, is new. Otherwise if in NewEdges list, also new. // (need both in case we re-use an old edge index) int MaxEID = Mesh.MaxEdgeID; NewEdges.Clear(); NewCutVertices.Clear(); NewCutVertices.Add(i0_vid); NewCutVertices.Add(i1_vid); // cut existing edges with segment IEnumerable <int> edges = Interval1i.Range(MaxEID); if (triSpatial != null) { edges = segEdges; } foreach (int eid in edges) { if (Mesh.IsEdge(eid) == false) { continue; } if (eid >= MaxEID || NewEdges.Contains(eid)) { continue; } // cannot cut boundary edges? if (Mesh.IsBoundaryEdge(eid)) { continue; } Index2i ev = Mesh.GetEdgeV(eid); int eva_sign = signs[ev.a]; int evb_sign = signs[ev.b]; // [RMS] should we be using larger epsilon here? If we don't track OnCurveVerts explicitly, we // need to at least use same epsilon we passed to insert_corner_from_bary...do we still also // need that to catch the edges we split in the poke? bool eva_in_segment = false; if (eva_sign == 0) { eva_in_segment = OnCurveVerts.Contains(ev.a) || Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + SpatialEpsilon); } bool evb_in_segment = false; if (evb_sign == 0) { evb_in_segment = OnCurveVerts.Contains(ev.b) || Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + SpatialEpsilon); } // If one or both vertices are on-segment, we have special case. // If just one vertex is on the segment, we can skip this edge. // If both vertices are on segment, then we can just re-use this edge. if (eva_in_segment || evb_in_segment) { if (eva_in_segment && evb_in_segment) { ZeroEdges.Add(eid); add_cut_edge(eid); NewCutVertices.Add(ev.a); NewCutVertices.Add(ev.b); } else { int zvid = eva_in_segment ? ev.a : ev.b; ZeroVertices.Add(zvid); NewCutVertices.Add(zvid); } continue; } // no crossing if (eva_sign * evb_sign > 0) { continue; } // compute segment/segment intersection Vector2d va = PointF(ev.a); Vector2d vb = PointF(ev.b); Segment2d edge_seg = new Segment2d(va, vb); IntrSegment2Segment2 intr = new IntrSegment2Segment2(seg, edge_seg); intr.Compute(); if (intr.Type == IntersectionType.Segment) { // [RMS] we should have already caught this above, so if it happens here it is probably spurious? // we should have caught this case above, but numerics are different so it might occur again ZeroEdges.Add(eid); NewCutVertices.Add(ev.a); NewCutVertices.Add(ev.b); add_cut_edge(eid); continue; } else if (intr.Type != IntersectionType.Point) { continue; // no intersection } Vector2d x = intr.Point0; double t = Math.Sqrt(x.DistanceSquared(va) / va.DistanceSquared(vb)); // this case happens if we aren't "on-segment" but after we do the test the intersection pt // is within epsilon of one end of the edge. This is a spurious t-intersection and we // can ignore it. Some other edge should exist that picks up this vertex as part of it. // [TODO] what about if this edge is degenerate? bool x_in_segment = Math.Abs(edge_seg.Project(x)) < (edge_seg.Extent - SpatialEpsilon); if (!x_in_segment) { continue; } Index2i et = Mesh.GetEdgeT(eid); spatial_remove_triangles(et.a, et.b); // split edge at this segment DMesh3.EdgeSplitInfo splitInfo; MeshResult result = Mesh.SplitEdge(eid, out splitInfo, t); if (result != MeshResult.Ok) { throw new Exception("MeshInsertUVSegment.Apply: SplitEdge failed - " + result.ToString()); //return false; } // move split point to intersection position SetPointF(splitInfo.vNew, x); NewCutVertices.Add(splitInfo.vNew); NewEdges.Add(splitInfo.eNewBN); NewEdges.Add(splitInfo.eNewCN); spatial_add_triangles(et.a, et.b); spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3); // some splits - but not all - result in new 'other' edges that are on // the polypath. We want to keep track of these edges so we can extract loop later. Index2i ecn = Mesh.GetEdgeV(splitInfo.eNewCN); if (NewCutVertices.Contains(ecn.a) && NewCutVertices.Contains(ecn.b)) { add_cut_edge(splitInfo.eNewCN); } // since we don't handle bdry edges this should never be false, but maybe we will handle bdry later... if (splitInfo.eNewDN != DMesh3.InvalidID) { NewEdges.Add(splitInfo.eNewDN); Index2i edn = Mesh.GetEdgeV(splitInfo.eNewDN); if (NewCutVertices.Contains(edn.a) && NewCutVertices.Contains(edn.b)) { add_cut_edge(splitInfo.eNewDN); } } } } // extract the cut paths if (EnableCutSpansAndLoops) { find_cut_paths(OnCutEdges); } return(true); } // Apply()
// 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 }); }