Boprs/Region.cs
2024-09-04 13:59:00 +02:00

271 lines
6.5 KiB
C#

#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;
#endif
using System;
using System.Collections.Generic;
namespace Boprs
{
/// <summary>
/// Geometric region used for boolean operations calculations.
/// </summary>
public class Region
{
/// <summary>
/// Edges belonging to this polygon.
/// </summary>
private List<SweepEdge> Edges { get; set; }
private List<Contour> Contours { get; set; }
/// <summary>
/// Subdivide my edges, set their inOut tags and validate their non-colinearity.
/// </summary>
private void SetEdges(List<SweepVertex> vertices)
{
// Lexico-graphically sort my vertices.
vertices.Sort();
// Sweepline to keep track of edges.
SweepLine sweepLine = new SweepLine();
// Processed edges will be moved here.
List<SweepEdge> processed = new List<SweepEdge>();
// Go through all the sorted vertices, test for intersections when appropriate and,
// if needed, subdivide the edges.
while (vertices.Count != 0)
{
SweepVertex acVx = vertices[0];
vertices.RemoveAt(0);
if (acVx.IsLeft())
{
sweepLine.Add(acVx.Edge, true);
SweepEdge prevEdge = sweepLine.PrevEdge(acVx.Edge);
SweepEdge nextEdge = sweepLine.NextEdge(acVx.Edge);
acVx.Edge.SetTransitionInside(prevEdge);
if (prevEdge != null)
{
List<SweepVertex> newVertices = acVx.Edge.TrySubdivideBy(prevEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(vertices, vertex);
}
}
if (nextEdge != null)
{
List<SweepVertex> newVertices = acVx.Edge.TrySubdivideBy(nextEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(vertices, vertex);
}
}
}
else
{
SweepEdge prevEdge = sweepLine.PrevEdge(acVx.Edge);
SweepEdge nextEdge = sweepLine.NextEdge(acVx.Edge);
sweepLine.Remove(acVx.Edge);
processed.Add(acVx.Edge);
if (prevEdge != null && nextEdge != null)
{
List<SweepVertex> newVertices = prevEdge.TrySubdivideBy(nextEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(vertices, vertex);
}
}
}
}
Edges = processed;
}
/// <summary>
/// Find the index of an un-processed vertex with the same position as the one on specified index.
/// </summary>
/// <param name="pos">Index of the current vertex.</param>
/// <param name="vertices">List of vertices.</param>
/// <param name="processed">Mask for processed vertices.</param>
/// <returns>Index of an unprocessed neighbour with the same position.</returns>
private int NextPos(int pos, List<SweepVertex> vertices, bool[] processed)
{
int newPos = pos + 1;
while(newPos < vertices.Count && Utils.DoubleEquals(vertices[pos].Point.GetDistanceTo(vertices[newPos].Point), 0))
{
if (!processed[newPos])
{
return newPos;
}
else
{
newPos++;
}
}
newPos = pos - 1;
while (newPos >= 0 && Utils.DoubleEquals(vertices[pos].Point.GetDistanceTo(vertices[newPos].Point), 0))
{
if (!processed[newPos])
{
return newPos;
}
else
{
newPos--;
}
}
throw new Exception("Hit contour dead end (non-connected edge).");
}
/// <summary>
/// Compute my closed contours.
/// </summary>
private void ComputeContours()
{
// Gather all vertices of the region.
List<SweepVertex> vertices = new List<SweepVertex>();
foreach (SweepEdge edge in Edges)
{
vertices.Add(edge.LeftVertex);
vertices.Add(edge.RightVertex);
}
// Lexico-graphically sort the vertices.
vertices.Sort();
// Sweepline to keep track of edges.
SweepLine sweepLine = new SweepLine();
bool[] processed = new bool[vertices.Count];
List<Contour> contours = new List<Contour>();
for(int i = 0; i < vertices.Count; i++)
{
SweepVertex acVx = vertices[i];
if (acVx.IsLeft())
{
sweepLine.Add(acVx.Edge, false);
}
else
{
sweepLine.Remove(acVx.Edge);
}
if (processed[i])
{
continue;
}
Contour contour = new Contour();
contours.Add(contour);
SweepEdge prev = sweepLine.PrevEdge(acVx.Edge);
bool shouldBeCW = prev == null ? false : prev.TransitionInside;
Point2d target = acVx.Point;
int pos = i;
processed[i] = true;
contour.Vertices.Add(vertices[pos].Point);
acVx.Edge.ParentContour = contour;
while (!Utils.DoubleEquals(vertices[pos].OtherVertex().Point.GetDistanceTo(target), 0))
{
int otherPos = vertices.IndexOf(vertices[pos].OtherVertex());
pos = NextPos(otherPos, vertices, processed);
processed[pos] = true;
processed[otherPos] = true;
vertices[pos].Edge.ParentContour = contour;
contour.Vertices.Add(vertices[pos].Point);
}
processed[vertices.IndexOf(vertices[pos].OtherVertex())] = true;
if (shouldBeCW != contour.IsClockwise())
{
contour.Reverse();
}
}
Contours = contours;
}
/// <summary>
/// Get a copy of the regions edges list.
/// </summary>
/// <returns>Copy of the regions edges list.</returns>
internal List<SweepEdge> GetEdgeCopies()
{
List<SweepEdge> copies = new List<SweepEdge>();
foreach (SweepEdge edge in Edges)
{
copies.Add((SweepEdge)edge.Clone());
}
return copies;
}
/// <summary>
/// Get a copy of the contours of the polygon.
/// </summary>
/// <returns>List of copies of the regions contours.</returns>
public List<Contour> GetContourCopies()
{
throw new NotImplementedException();
}
/// <summary>
/// Instantiate a region with edges defined by a list of given line segments.
/// </summary>
/// <param name="segments"></param>
/// <returns></returns>
public static Region FromSegments(List<LineSegment2d> segments)
{
Region region = new Region();
List<SweepVertex> vertices = new List<SweepVertex>();
foreach(LineSegment2d segment in segments)
{
SweepEdge newEdge = new SweepEdge(segment.StartPoint, segment.EndPoint);
vertices.Add(newEdge.LeftVertex);
vertices.Add(newEdge.RightVertex);
newEdge.ParentRegion = region;
}
region.SetEdges(vertices);
region.ComputeContours();
return region;
}
#if DEBUG
internal void Draw()
{
foreach (Contour contour in Contours)
{
contour.Draw();
}
}
#endif
}
}