Boprs/SweepEdge.cs

413 lines
12 KiB
C#
Raw Normal View History

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>
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})");
}
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
}
}