2024-09-01 13:10:19 +02:00
|
|
|
|
#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;
|
2024-09-01 13:27:00 +02:00
|
|
|
|
using AcAp = Bricscad.ApplicationServices;
|
2024-09-01 13:10:19 +02:00
|
|
|
|
#endif
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
namespace Boprs
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Type the edge can be.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal enum EdgeType
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Normal edge, nothing special.
|
|
|
|
|
/// </summary>
|
|
|
|
|
NORMAL,
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Edge overlaps with an edge on another region,
|
|
|
|
|
/// both represent the same transition (in or out) of their respective regions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
OVERLAP_SAME,
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Edge overlaps with an edge on another region,
|
|
|
|
|
/// both represent a different transition (in or out) of their respective regions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
OVERLAP_DIFFERENT,
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Edge overlaps with an edge on another region, it will handle the situation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
OVERLAP_SILENT,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Line segment (edge) used while a sweep line is processing a boolean operation.
|
|
|
|
|
/// </summary>
|
2024-09-02 10:08:52 +02:00
|
|
|
|
internal class SweepEdge : ICloneable
|
2024-09-01 13:10:19 +02:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// String representation of the edge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
return ($"({LeftVertex} -- {RightVertex})");
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-02 10:08:52 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-01 13:10:19 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Comparison of edges.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="other">Other edge to compare.</param>
|
|
|
|
|
/// <param name="xParam">X param at which to make the comparison.</param>
|
|
|
|
|
/// <returns><c>-1</c> if this is below obj, <c>1</c> if this is above obj, <c>0</c> if collinear.</returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Left (or bottom) vertex of the edge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal SweepVertex LeftVertex { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Right (or top) vertex of the edge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal SweepVertex RightVertex { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Does this edge signify a transition from the outside to the inside of its region?
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal bool TransitionInside { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Is this edge inside the other polygon?
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal bool InsideOther { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Type of the edge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal EdgeType Type { get; private set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Region I belong to.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Region ParentRegion { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Is this edge vertical?
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns><c>true</c> if this vedge is vertical, <c>false</c> if not.</returns>
|
|
|
|
|
internal bool IsVertical()
|
|
|
|
|
{
|
|
|
|
|
return Utils.DoubleEquals(LeftVertex.Point.X, RightVertex.Point.X);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set the TransitionInside flag for this edge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="prevEdge">Edge preceeding this one in the region construction sweep line.</param>
|
|
|
|
|
internal void SetTransitionInside(SweepEdge prevEdge)
|
|
|
|
|
{
|
|
|
|
|
if(prevEdge == null)
|
|
|
|
|
{
|
|
|
|
|
TransitionInside = true;
|
|
|
|
|
}
|
|
|
|
|
else if(IsVertical())
|
|
|
|
|
{
|
|
|
|
|
TransitionInside = prevEdge.TransitionInside;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TransitionInside = !prevEdge.TransitionInside;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Set the InsideOther flag for this edge.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="prevEdge">Edge preceeding this one in the bopr processing sweep line.</param>
|
|
|
|
|
internal void SetInsideOther(SweepEdge prevEdge)
|
|
|
|
|
{
|
|
|
|
|
if (prevEdge == null)
|
|
|
|
|
{
|
|
|
|
|
InsideOther = false;
|
|
|
|
|
}
|
|
|
|
|
else if (ParentRegion == prevEdge.ParentRegion)
|
|
|
|
|
{
|
|
|
|
|
InsideOther = prevEdge.InsideOther;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
InsideOther = prevEdge.TransitionInside;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Create new edge going from pt to my right vertex and make pt my new right vertex.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pt">Point at which to subdivide.</param>
|
|
|
|
|
/// <returns>List of the two newly created vertices.</returns>
|
|
|
|
|
internal List<SweepVertex> 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<SweepVertex>() { newLeft, newRight };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Intersect this edge with the other, if there is an intersection, subdivide them.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="otherEdge">Edge to try intersecting with.</param>
|
|
|
|
|
/// <returns>Unsorted list of newly created verticies.</returns>
|
|
|
|
|
internal List<SweepVertex> 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<SweepVertex> newVertices = new List<SweepVertex>();
|
|
|
|
|
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<SweepVertex> newVertices = new List<SweepVertex>();
|
|
|
|
|
|
|
|
|
|
// 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<SweepVertex>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pt1">Endpoint 1.</param>
|
|
|
|
|
/// <param name="pt2">EndPoint 2.</param>
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|