#if ACAD24 using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Colors; using AcAp = Autodesk.AutoCAD.ApplicationServices; #elif BCAD using Bricscad.EditorInput; using Teigha.Geometry; using Teigha.DatabaseServices; using Teigha.Colors; using AcAp = Bricscad.ApplicationServices; #endif using System; using System.Collections.Generic; namespace Boprs { /// /// Type the edge can be. /// internal enum EdgeType { /// /// Normal edge, nothing special. /// NORMAL, /// /// Edge overlaps with an edge on another region, /// both represent the same transition (in or out) of their respective regions. /// OVERLAP_SAME, /// /// Edge overlaps with an edge on another region, /// both represent a different transition (in or out) of their respective regions. /// OVERLAP_DIFFERENT, /// /// Edge overlaps with an edge on another region, it will handle the situation. /// OVERLAP_SILENT, } /// /// Line segment (edge) used while a sweep line is processing a boolean operation. /// internal class SweepEdge : ICloneable { /// /// String representation of the edge. /// public override string ToString() { return ($"({LeftVertex} -- {RightVertex})"); } public object Clone() { SweepVertex left = (SweepVertex)LeftVertex.Clone(); SweepVertex right = (SweepVertex)RightVertex.Clone(); SweepEdge clone = new SweepEdge(LeftVertex, RightVertex) { TransitionInside = TransitionInside, InsideOther = InsideOther, Type = Type, ParentRegion = ParentRegion, }; return clone; } /// /// Comparison of edges. /// /// Other edge to compare. /// X param at which to make the comparison. /// -1 if this is below obj, 1 if this is above obj, 0 if collinear. public int CompareTo(SweepEdge other, double xParam) { // Get the Y values of the edges at the given X coordinate // If the edge is vertical, the Y value of its left (lower) endpoint is its Y value for this comparison. double myY = IsVertical() ? LeftVertex.Point.Y : Utils.YatX(LeftVertex.Point, RightVertex.Point, xParam); double otherY = other.IsVertical() ? other.LeftVertex.Point.Y : Utils.YatX(other.LeftVertex.Point, other.RightVertex.Point, xParam); // If the edges are not intersecting at this X, order them as expected. if (!Utils.DoubleEquals(myY, otherY)) { return myY > otherY ? 1 : -1; } // If the edges do intersect here, and one is vertical, the non-vertical one is "lower". if (IsVertical() != other.IsVertical()) { return IsVertical() ? 1 : -1; } // If they are not collinear, the one leading downwards from here is "lower". if (Utils.SignedArea(LeftVertex.Point, RightVertex.Point, other.RightVertex.Point) < 0) { return 1; } if (Utils.SignedArea(LeftVertex.Point, RightVertex.Point, other.RightVertex.Point) > 0) { return -1; } // If they are collinear, return 0; return 0; } /// /// Left (or bottom) vertex of the edge. /// internal SweepVertex LeftVertex { get; set; } /// /// Right (or top) vertex of the edge. /// internal SweepVertex RightVertex { get; set; } /// /// Does this edge signify a transition from the outside to the inside of its region? /// internal bool TransitionInside { get; private set; } /// /// Is this edge inside the other polygon? /// internal bool InsideOther { get; private set; } /// /// Type of the edge. /// internal EdgeType Type { get; private set; } /// /// Region I belong to. /// internal Region ParentRegion { get; set; } /// /// Is this edge vertical? /// /// true if this vedge is vertical, false if not. internal bool IsVertical() { return Utils.DoubleEquals(LeftVertex.Point.X, RightVertex.Point.X); } /// /// Set the TransitionInside flag for this edge. /// /// Edge preceeding this one in the region construction sweep line. internal void SetTransitionInside(SweepEdge prevEdge) { if(prevEdge == null) { TransitionInside = true; } else if(IsVertical()) { TransitionInside = prevEdge.TransitionInside; } else { TransitionInside = !prevEdge.TransitionInside; } } /// /// Set the InsideOther flag for this edge. /// /// Edge preceeding this one in the bopr processing sweep line. internal void SetInsideOther(SweepEdge prevEdge) { if (prevEdge == null) { InsideOther = false; } else if (ParentRegion == prevEdge.ParentRegion) { InsideOther = prevEdge.InsideOther; } else { InsideOther = prevEdge.TransitionInside; } } /// /// Create new edge going from pt to my right vertex and make pt my new right vertex. /// /// Point at which to subdivide. /// List of the two newly created vertices. internal List SubdivideAt(Point2d pt) { // Create the new vertices. SweepVertex newLeft = new SweepVertex(pt); SweepVertex newRight = new SweepVertex(pt); // Create new edge from the point to my right vertex and assign it as the edge of its vertices. SweepEdge newEdge = new SweepEdge(newLeft, RightVertex); newEdge.TransitionInside = TransitionInside; newEdge.ParentRegion = ParentRegion; newLeft.Edge = newEdge; RightVertex.Edge = newEdge; // Set the point as my new right vertex and me as its edge. RightVertex = newRight; newRight.Edge = this; // Return the newly created vertices. return new List() { newLeft, newRight }; } /// /// Intersect this edge with the other, if there is an intersection, subdivide them. /// /// Edge to try intersecting with. /// Unsorted list of newly created verticies. internal List TrySubdivideBy(SweepEdge otherEdge) { // Get the linesegment representation of this and the other edge. LineSegment2d myLine = new LineSegment2d(LeftVertex.Point, RightVertex.Point); LineSegment2d otherLine = new LineSegment2d(otherEdge.LeftVertex.Point, otherEdge.RightVertex.Point); // Get the potential intersection between the two edges. CurveCurveIntersector2d intersector = new CurveCurveIntersector2d(myLine, otherLine); // If the intersection was on the endpoints of both segments, return empty list. // Right vertices get processed before left ones, so connected left and right vertices // will not communicate. if (intersector.NumberOfIntersectionPoints == 1) { List newVertices = new List(); Point2d intPt = intersector.GetIntersectionPoint(0); // If the point is inside of either edge (not on its end point), subdivide the edge. if (!(Utils.DoubleEquals(intPt.GetDistanceTo(LeftVertex.Point), 0) || Utils.DoubleEquals(intPt.GetDistanceTo(RightVertex.Point), 0))) { newVertices.AddRange(SubdivideAt(intPt)); } if (!(Utils.DoubleEquals(intPt.GetDistanceTo(otherEdge.LeftVertex.Point), 0) || Utils.DoubleEquals(intPt.GetDistanceTo(otherEdge.RightVertex.Point), 0))) { newVertices.AddRange(otherEdge.SubdivideAt(intPt)); } // Return the newly created vertices. return newVertices; } // Overlap if (myLine.Overlaps(otherLine)) { List newVertices = new List(); // The edges share the left vertex if (Utils.DoubleEquals(LeftVertex.Point.GetDistanceTo(otherEdge.LeftVertex.Point), 0)) { EdgeType et = (TransitionInside == otherEdge.TransitionInside) ? EdgeType.OVERLAP_SAME : EdgeType.OVERLAP_DIFFERENT; // The edges share the right vertex as well, set the edge type for both this and the other // edge and return an empty list. if (Utils.DoubleEquals(RightVertex.Point.GetDistanceTo(otherEdge.RightVertex.Point), 0)) { Type = et; otherEdge.Type = EdgeType.OVERLAP_SILENT; return newVertices; } // One of the edges carries on farther, clip it to be fully overlapping, // set the edge type for this and other edge and create a new edge from the clipped off part. else { SweepEdge longer = RightVertex.CompareTo(otherEdge.RightVertex) > 0 ? this : otherEdge; SweepEdge shorter = (this == longer) ? otherEdge : this; newVertices.AddRange(longer.SubdivideAt(shorter.RightVertex.Point)); Type = et; otherEdge.Type = EdgeType.OVERLAP_SILENT; return newVertices; } } // One edge starts earlier than the other. Find it and create new edge from the point it // intersects the other edge, those two will negtiate how to handle the situation later. else { SweepEdge earlier = LeftVertex.CompareTo(otherEdge.LeftVertex) < 0 ? this : otherEdge; SweepEdge later = (this == earlier) ? otherEdge : this; newVertices.AddRange(earlier.SubdivideAt(later.LeftVertex.Point)); return newVertices; } } // If there was no intersection, return empty list return new List(); } /// /// Constructor. /// /// Endpoint 1. /// EndPoint 2. internal SweepEdge(Point2d pt1, Point2d pt2) { if (Utils.DoubleEquals(pt1.GetDistanceTo(pt2), 0)) { throw new ArgumentException("Edge end points cannot be the same."); } SweepVertex v1 = new SweepVertex(pt1); SweepVertex v2 = new SweepVertex(pt2); v1.Edge = this; v2.Edge = this; Type = EdgeType.NORMAL; if (v1.CompareTo(v2) < 0) { LeftVertex = v1; RightVertex = v2; } else { LeftVertex = v2; RightVertex = v1; } } internal SweepEdge(SweepVertex v1, SweepVertex v2) { Type = EdgeType.NORMAL; if (v1.CompareTo(v2) < 0) { LeftVertex = v1; RightVertex = v2; } else { LeftVertex = v2; RightVertex = v1; } } #if DEBUG internal ObjectId ObjectId { get; private set; } internal void Draw(Color color = null) { AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument; Database acDb = acDoc.Database; Erase(); Color lineColor = color; if (color == null) { if (Type == EdgeType.NORMAL) { lineColor = TransitionInside ? Color.FromRgb(32, 255, 32) : Color.FromRgb(255, 32, 32); } else { lineColor = Type == EdgeType.OVERLAP_SAME ? Color.FromRgb(32, 32, 255) : Color.FromRgb(255, 255, 32); } } using (Transaction acTrans = acDb.TransactionManager.StartTransaction()) { // Open the BlockTableRecord BlockTable acBlkTbl = (BlockTable)acTrans.GetObject(acDb.BlockTableId, OpenMode.ForRead); BlockTableRecord acBlkTblRec = (BlockTableRecord)acTrans.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite); Line asLine = new Line(LeftVertex.Point.To3d(), RightVertex.Point.To3d()); asLine.Color = lineColor; acBlkTblRec.AppendEntity(asLine); acTrans.AddNewlyCreatedDBObject(asLine, true); acTrans.Commit(); ObjectId = asLine.ObjectId; } } internal void Erase() { AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument; Database acDb = acDoc.Database; if (!ObjectId.IsNull && !ObjectId.IsEffectivelyErased) { using (Transaction acTrans = acDb.TransactionManager.StartTransaction()) { acTrans.GetObject(ObjectId, OpenMode.ForWrite).Erase(); acTrans.Commit(); } } } #endif } }