Compare commits

...

10 commits

Author SHA1 Message Date
527abd16f4 release 1.1.0 2024-09-22 00:07:36 +02:00
37f4065919 fix typo 2024-09-22 00:04:53 +02:00
b99de74993 compare points without getting distance 2024-09-21 23:58:14 +02:00
22cd0105b7 add Contour.GetVertexCopies() 2024-09-21 23:27:42 +02:00
2e57f41125 Add Region.Area 2024-09-21 23:13:05 +02:00
borovec zdenek
dff02947c8 remove debug only using 2024-09-04 15:12:48 +02:00
borovec zdenek
75d12d6a5a Implement Region.GetContourCopies 2024-09-04 14:36:34 +02:00
borovec zdenek
6744f5e967 Optimize the contour creation. 2024-09-04 14:29:30 +02:00
borovec zdenek
47d3f96b20 Contours orientate themselves properly. 2024-09-04 13:59:00 +02:00
borovec zdenek
1a91a64b2d Compute closed contours. 2024-09-04 09:04:07 +02:00
9 changed files with 343 additions and 32 deletions

View file

@ -18,7 +18,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;ACAD24</DefineConstants> <DefineConstants>TRACE;DEBUG;BCAD</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
@ -41,6 +41,12 @@
<Reference Include="acmgd"> <Reference Include="acmgd">
<HintPath>..\..\..\..\..\Program Files\Autodesk\AutoCAD 2024\acmgd.dll</HintPath> <HintPath>..\..\..\..\..\Program Files\Autodesk\AutoCAD 2024\acmgd.dll</HintPath>
</Reference> </Reference>
<Reference Include="BricscadExtensions">
<HintPath>References\BricscadExtensions.dll</HintPath>
</Reference>
<Reference Include="BrxMgd">
<HintPath>..\..\..\..\..\Program Files\Bricsys\BricsCAD V24 en_US\BrxMgd.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -49,10 +55,14 @@
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="TD_Mgd">
<HintPath>..\..\..\..\..\Program Files\Bricsys\BricsCAD V24 en_US\TD_Mgd.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="BooleanOperation.cs" /> <Compile Include="BooleanOperation.cs" />
<Compile Include="Commands.cs" /> <Compile Include="Commands.cs" />
<Compile Include="Contour.cs" />
<Compile Include="Region.cs" /> <Compile Include="Region.cs" />
<Compile Include="SweepEdge.cs" /> <Compile Include="SweepEdge.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -55,6 +55,8 @@ namespace Boprs
segs.Add(new LineSegment2d(asLine.StartPoint.To2d(), asLine.EndPoint.To2d())); segs.Add(new LineSegment2d(asLine.StartPoint.To2d(), asLine.EndPoint.To2d()));
} }
} }
asPoly.Erase();
} }
acTrans.Commit(); acTrans.Commit();
} }
@ -109,7 +111,14 @@ namespace Boprs
BooleanOperation bopr = new BooleanOperation(subject, clip, type); BooleanOperation bopr = new BooleanOperation(subject, clip, type);
bopr.Result.Draw(); List <Contour> contours = bopr.Result.GetContourCopies();
foreach (Contour contour in contours)
{
contour.Draw();
}
acDoc.Editor.WriteMessage($"\nResult area: {bopr.Result.Area}");
} }
} }
} }

124
Contour.cs Normal file
View file

@ -0,0 +1,124 @@
#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
{
/// <summary>
/// Simple closed polygon.
/// </summary>
public class Contour : ICloneable
{
public object Clone()
{
return new Contour(new List<Point2d>(Vertices));
}
/// <summary>
/// Vertices of the contour.
/// </summary>
private List<Point2d> Vertices { get; }
/// <summary>
/// Signed area of the contour.
/// </summary>
public double SignedArea { get; private set; }
/// <summary>
/// Are the vertices of the contour arranged in clockwise manner?
/// </summary>
/// <returns><c>true</c> if the contour is clockwise, <c>false</c> if counter-clockwise.</returns>
public bool IsClockwise { get => SignedArea < 0; }
/// <summary>
/// Get a copy of the list of the vertices of this contour.
/// </summary>
/// <returns>List of vertices defining this region.</returns>
public List<Point2d> GetVertexCopies()
{
return new List<Point2d>(Vertices);
}
/// <summary>
/// Compute the signed area of the contour
/// </summary>
private void ComputeArea()
{
double areaSum = 0;
// Go through all the vertices.
for (int i = 0; i < Vertices.Count; i++)
{
// Get current vertex
Point2d p0 = Vertices[i];
// Get the next vertex
Point2d p1 = Vertices[(i + 1) % Vertices.Count];
// Compute the area under the trapezoid made by the current segment.
// Note that this is actually double the proper value.
areaSum += (p1.X - p0.X) * (p1.Y + p0.Y);
}
// The above algorithm deems clockwise area to be positive,
// however we consider ccw contours to be positive, so we flip the value.
SignedArea = - areaSum / 2;
}
/// <summary>
/// Reverse the orientation of the contour.
/// </summary>
internal void Reverse()
{
Vertices.Reverse();
SignedArea *= -1;
}
/// <summary>
/// Constructor.
/// </summary>
public Contour(List<Point2d> vertices)
{
Vertices = vertices;
ComputeArea();
}
#if DEBUG
internal void Draw(Color color = null)
{
AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument;
Database acDb = acDoc.Database;
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);
for(int i = 0; i < Vertices.Count; i++)
{
Line asLine = new Line(Vertices[i].To3d(), Vertices[(i + 1) % Vertices.Count].To3d());
asLine.Color = IsClockwise ? Color.FromRgb(255, 32, 32) : Color.FromRgb(32, 255, 32);
acBlkTblRec.AppendEntity(asLine);
acTrans.AddNewlyCreatedDBObject(asLine, true);
}
acTrans.Commit();
}
}
#endif
}
}

View file

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")] [assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyFileVersion("1.1.0.0")]

165
Region.cs
View file

@ -25,6 +25,16 @@ namespace Boprs
/// </summary> /// </summary>
private List<SweepEdge> Edges { get; set; } private List<SweepEdge> Edges { get; set; }
/// <summary>
/// Contours making up the region.
/// </summary>
private List<Contour> Contours { get; set; }
/// <summary>
/// Area of the positive part of the region (holes subtracted).
/// </summary>
public double Area { get; private set; }
/// <summary> /// <summary>
/// Subdivide my edges, set their inOut tags and validate their non-colinearity. /// Subdivide my edges, set their inOut tags and validate their non-colinearity.
/// </summary> /// </summary>
@ -97,6 +107,124 @@ namespace Boprs
Edges = processed; 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.PointEquals(vertices[pos].Point, vertices[newPos].Point))
{
if (!processed[newPos])
{
return newPos;
}
else
{
newPos++;
}
}
newPos = pos - 1;
while (newPos >= 0 && Utils.PointEquals(vertices[pos].Point, vertices[newPos].Point))
{
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();
bool[] processed = new bool[vertices.Count];
List<Contour> contours = new List<Contour>();
for(int i = 0; i < vertices.Count; i++)
{
// If we already traversed this vertex, skip it.
if (processed[i])
{
continue;
}
// Otherwise this is an edge on an indiscovered contour, create it.
// acVx should be guaranteed to be the lowest and leftest vertex of the new polygon.
// Thus it cannot be vertical.
// if it is transition inside, it is a positive contour and should wind counter-clockwise,
// if it is transition outside, it is a negative contour and should wind clockwise.
SweepVertex acVx = vertices[i];
bool shouldBeCW = !acVx.Edge.TransitionInside;
// Traverse connected edges, and mark taversed vertices as processed,
// until we loop back to the first discovered vertex on the contour.
int pos = i;
processed[i] = true;
Point2d target = acVx.Point;
List<Point2d> contourVertices = new List<Point2d>() { vertices[pos].Point };
while (!Utils.PointEquals(vertices[pos].OtherVertex().Point, target))
{
int otherPos = vertices.IndexOf(vertices[pos].OtherVertex());
pos = NextPos(otherPos, vertices, processed);
processed[pos] = true;
processed[otherPos] = true;
contourVertices.Add(vertices[pos].Point);
}
processed[vertices.IndexOf(vertices[pos].OtherVertex())] = true;
// Instantiate the contour object from the found vertices.
Contour contour = new Contour(contourVertices);
contours.Add(contour);
// If the contour is a hole and winds ccw, or positive and winds cw, flip it.
if (shouldBeCW != contour.IsClockwise)
{
contour.Reverse();
}
}
Contours = contours;
}
/// <summary>
/// Compute the area of the region.
/// </summary>
/// <remarks>
/// Requires contours to be computed.
/// </remarks>
private void ComputeArea()
{
Area = 0;
foreach(Contour contour in Contours)
{
Area += contour.SignedArea;
}
}
/// <summary> /// <summary>
/// Get a copy of the regions edges list. /// Get a copy of the regions edges list.
/// </summary> /// </summary>
@ -113,6 +241,22 @@ namespace Boprs
return copies; 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()
{
List<Contour> copies = new List<Contour>();
foreach (Contour contour in Contours)
{
copies.Add((Contour)contour.Clone());
}
return copies;
}
/// <summary> /// <summary>
/// Instantiate a region with edges defined by a list of given line segments. /// Instantiate a region with edges defined by a list of given line segments.
/// </summary> /// </summary>
@ -133,27 +277,10 @@ namespace Boprs
} }
region.SetEdges(vertices); region.SetEdges(vertices);
region.ComputeContours();
region.ComputeArea();
return region; return region;
} }
#if DEBUG
internal void Draw()
{
Erase();
foreach (SweepEdge edge in Edges)
{
edge.Draw();
}
}
internal void Erase()
{
foreach (SweepEdge edge in Edges)
{
edge.Erase();
}
}
#endif
} }
} }

View file

@ -235,13 +235,13 @@ namespace Boprs
Point2d intPt = intersector.GetIntersectionPoint(0); Point2d intPt = intersector.GetIntersectionPoint(0);
// If the point is inside of either edge (not on its end point), subdivide the edge. // If the point is inside of either edge (not on its end point), subdivide the edge.
if (!(Utils.DoubleEquals(intPt.GetDistanceTo(LeftVertex.Point), 0) || if (!(Utils.PointEquals(intPt, LeftVertex.Point) ||
Utils.DoubleEquals(intPt.GetDistanceTo(RightVertex.Point), 0))) Utils.PointEquals(intPt, RightVertex.Point)))
{ {
newVertices.AddRange(SubdivideAt(intPt)); newVertices.AddRange(SubdivideAt(intPt));
} }
if (!(Utils.DoubleEquals(intPt.GetDistanceTo(otherEdge.LeftVertex.Point), 0) || if (!(Utils.PointEquals(intPt, otherEdge.LeftVertex.Point) ||
Utils.DoubleEquals(intPt.GetDistanceTo(otherEdge.RightVertex.Point), 0))) Utils.PointEquals(intPt, otherEdge.RightVertex.Point)))
{ {
newVertices.AddRange(otherEdge.SubdivideAt(intPt)); newVertices.AddRange(otherEdge.SubdivideAt(intPt));
} }
@ -256,14 +256,14 @@ namespace Boprs
List<SweepVertex> newVertices = new List<SweepVertex>(); List<SweepVertex> newVertices = new List<SweepVertex>();
// The edges share the left vertex // The edges share the left vertex
if (Utils.DoubleEquals(LeftVertex.Point.GetDistanceTo(otherEdge.LeftVertex.Point), 0)) if (Utils.PointEquals(LeftVertex.Point, otherEdge.LeftVertex.Point))
{ {
EdgeType et = (TransitionInside == otherEdge.TransitionInside) ? EdgeType et = (TransitionInside == otherEdge.TransitionInside) ?
EdgeType.OVERLAP_SAME : EdgeType.OVERLAP_DIFFERENT; EdgeType.OVERLAP_SAME : EdgeType.OVERLAP_DIFFERENT;
// The edges share the right vertex as well, set the edge type for both this and the other // The edges share the right vertex as well, set the edge type for both this and the other
// edge and return an empty list. // edge and return an empty list.
if (Utils.DoubleEquals(RightVertex.Point.GetDistanceTo(otherEdge.RightVertex.Point), 0)) if (Utils.PointEquals(RightVertex.Point, otherEdge.RightVertex.Point))
{ {
Type = et; Type = et;
otherEdge.Type = EdgeType.OVERLAP_SILENT; otherEdge.Type = EdgeType.OVERLAP_SILENT;
@ -309,7 +309,7 @@ namespace Boprs
/// <param name="pt2">EndPoint 2.</param> /// <param name="pt2">EndPoint 2.</param>
internal SweepEdge(Point2d pt1, Point2d pt2) internal SweepEdge(Point2d pt1, Point2d pt2)
{ {
if (Utils.DoubleEquals(pt1.GetDistanceTo(pt2), 0)) if (Utils.PointEquals(pt1, pt2))
{ {
throw new ArgumentException("Edge end points cannot be the same."); throw new ArgumentException("Edge end points cannot be the same.");
} }

View file

@ -77,6 +77,27 @@ namespace Boprs
IntersectingEdges.Remove(edge); IntersectingEdges.Remove(edge);
} }
/// <summary>
/// Get all edges on the sweepline below a given edge.
/// </summary>
/// <param name="curr">Current edge.</param>
/// <returns>From lowest to highest.</returns>
internal List<SweepEdge> AllBefore(SweepEdge curr)
{
List<SweepEdge> foundEdges = new List<SweepEdge>();
// Get the index of the current edge
int currIdx = IntersectingEdges.IndexOf(curr);
// Add all the previous edges to the list
for(int i = 0; i < currIdx; i++)
{
foundEdges.Add(IntersectingEdges[i]);
}
return foundEdges;
}
/// <summary> /// <summary>
/// Returns the edge below curr, or null if curr is the lowest. /// Returns the edge below curr, or null if curr is the lowest.
/// </summary> /// </summary>

View file

@ -30,7 +30,7 @@ namespace Boprs
public object Clone() public object Clone()
{ {
return new SweepVertex(Point) { Edge = Edge}; return new SweepVertex(Point) { Edge = Edge };
} }
/// <summary> /// <summary>
@ -119,6 +119,15 @@ namespace Boprs
return this == Edge.LeftVertex; return this == Edge.LeftVertex;
} }
/// <summary>
/// Returns the other vertex associated with my edge.
/// </summary>
/// <returns>Other vertex of my edge.</returns>
internal SweepVertex OtherVertex()
{
return IsLeft() ? Edge.RightVertex : Edge.LeftVertex;
}
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>

View file

@ -25,7 +25,7 @@ namespace Boprs
/// Largest difference between two doubles before they are considered equal. /// Largest difference between two doubles before they are considered equal.
/// </summary> /// </summary>
internal const double DOUBLEPREC = 1e-8; internal const double DOUBLEPREC = 1e-8;
/// <summary> /// <summary>
/// Signed area of the triangle (pt1, pt2, pt3). /// Signed area of the triangle (pt1, pt2, pt3).
/// </summary> /// </summary>
@ -38,6 +38,17 @@ namespace Boprs
return (pt1.X - pt3.X) * (pt2.Y - pt3.Y) - (pt2.X - pt3.X) * (pt1.Y - pt3.Y); return (pt1.X - pt3.X) * (pt2.Y - pt3.Y) - (pt2.X - pt3.X) * (pt1.Y - pt3.Y);
} }
/// <summary>
/// Are the points at roughly the same position?
/// </summary>
/// <param name="p1">First point to compare.</param>
/// <param name="p2">Second point to compare.</param>
/// <returns><c>true</c> if the points represent the same position, <c>false</c> if not.</returns>
internal static bool PointEquals(Point2d p1, Point2d p2)
{
return DoubleEquals(p1.X, p2.X) && DoubleEquals(p1.Y, p2.Y);
}
/// <summary> /// <summary>
/// Are the two doubles close enough to be almost equal? /// Are the two doubles close enough to be almost equal?
/// </summary> /// </summary>