core functionality

This commit is contained in:
Zdenek Borovec 2024-09-01 13:10:19 +02:00
commit 9049499525
13 changed files with 1859 additions and 0 deletions

400
.gitignore vendored Normal file
View file

@ -0,0 +1,400 @@
# ---> VisualStudio
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

216
BooleanOperation.cs Normal file
View file

@ -0,0 +1,216 @@
#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>
/// Type of the boolean operation.
/// </summary>
public enum BoprType
{
/// <summary>
/// Union.
/// </summary>
UNITE,
/// <summary>
/// Intersection.
/// </summary>
INTERSECT,
/// <summary>
/// Difference.
/// </summary>
SUBTRACT,
/// <summary>
/// Exclusive or.
/// </summary>
EXCLUSIVE,
}
/// <summary>
/// Boolean operation on two regions.
/// </summary>
public class BooleanOperation
{
/// <summary>
/// Resulting region from the operation.
/// </summary>
public Region Result { get; private set; }
/// <summary>
/// Subdivide any intersecting edges such that there are no more edge intersections and tag their Type property.
/// </summary>
/// <remarks>
/// <para>
/// This method will empty the sortedVertices list.
/// </para>
/// </remarks>
/// <param name="sortedVertices">Lexico-graphically sorted vertices of the edges to subdivide.</param>
/// <returns>Processed edges.</returns>
private List<SweepEdge> ProcessEdges(List<SweepVertex> sortedVertices)
{
// 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 (sortedVertices.Count != 0)
{
SweepVertex acVx = sortedVertices[0];
sortedVertices.RemoveAt(0);
if (acVx.IsLeft())
{
sweepLine.Add(acVx.Edge, true);
SweepEdge prevEdge = sweepLine.PrevEdge(acVx.Edge);
SweepEdge nextEdge = sweepLine.NextEdge(acVx.Edge);
acVx.Edge.SetInsideOther(prevEdge);
if (prevEdge != null)
{
List<SweepVertex> newVertices = acVx.Edge.TrySubdivideBy(prevEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(sortedVertices, vertex);
}
}
if (nextEdge != null)
{
List<SweepVertex> newVertices = acVx.Edge.TrySubdivideBy(nextEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(sortedVertices, 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(sortedVertices, vertex);
}
}
}
}
return processed;
}
/// <summary>
/// Should a given edge be included in the result polygon?
/// </summary>
/// <param name="edge">Edge to evaluate.</param>
/// <param name="subject">Subject region of this operation.</param>
/// <param name="boprType">Type of the boolean operation.</param>
/// <returns><c>true</c> if the edge should be included in the result, <c>false</c> if not.</returns>
private bool IsInResult(SweepEdge edge, Region subject, BoprType boprType)
{
switch (edge.Type)
{
case EdgeType.NORMAL:
switch (boprType)
{
case BoprType.UNITE:
return !edge.InsideOther;
case BoprType.INTERSECT:
return edge.InsideOther;
case BoprType.SUBTRACT:
return (edge.ParentRegion == subject && !edge.InsideOther) ||
(edge.ParentRegion != subject && edge.InsideOther);
case BoprType.EXCLUSIVE:
return true;
}
break;
case EdgeType.OVERLAP_SAME:
return boprType == BoprType.INTERSECT || boprType == BoprType.UNITE;
case EdgeType.OVERLAP_DIFFERENT:
return boprType == BoprType.SUBTRACT;
case EdgeType.OVERLAP_SILENT:
return false;
}
return false; // Just to avoid compiler warning.
}
/// <summary>
/// Build the result polygon from processed edges.
/// </summary>
/// <param name="edges">Input edges.</param>
/// <param name="subject">Subject region of this operation.</param>
/// <param name="boprType">Type of boolean operation.</param>
/// <returns>Built result region.</returns>
private Region BuildResult(List<SweepEdge> edges, Region subject, BoprType boprType)
{
List<LineSegment2d> chosenSegments = new List<LineSegment2d>();
foreach(SweepEdge edge in edges)
{
if(IsInResult(edge, subject, boprType))
{
chosenSegments.Add(new LineSegment2d(edge.LeftVertex.Point, edge.RightVertex.Point));
}
}
return Region.FromSegments(chosenSegments);
}
/// <summary>
/// Compute a boolean operation on two regions.
/// </summary>
/// <param name="subject">Subject region.</param>
/// <param name="clip">Clip region.</param>
/// <param name="type">Boolean operation type.</param>
public BooleanOperation(Region subject, Region clip, BoprType type)
{
List<SweepVertex> sortedVerts = new List<SweepVertex>();
foreach (LineSegment2d seg in subject.GetSegments())
{
SweepEdge edge = new SweepEdge(seg.StartPoint, seg.EndPoint);
sortedVerts.Add(edge.LeftVertex);
sortedVerts.Add(edge.RightVertex);
}
foreach (LineSegment2d seg in clip.GetSegments())
{
SweepEdge edge = new SweepEdge(seg.StartPoint, seg.EndPoint);
sortedVerts.Add(edge.LeftVertex);
sortedVerts.Add(edge.RightVertex);
}
sortedVerts.Sort();
List<SweepEdge> processed = ProcessEdges(sortedVerts);
Result = BuildResult(processed, subject, type);
}
}
}

63
Boprs.csproj Normal file
View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{12366D09-2514-4EF3-9918-253B46077A4D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Boprs</RootNamespace>
<AssemblyName>Boprs</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;ACAD24</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="accoremgd">
<HintPath>..\..\..\..\..\Program Files\Autodesk\AutoCAD 2024\accoremgd.dll</HintPath>
</Reference>
<Reference Include="acdbmgd">
<HintPath>..\..\..\..\..\Program Files\Autodesk\AutoCAD 2024\acdbmgd.dll</HintPath>
</Reference>
<Reference Include="acmgd">
<HintPath>..\..\..\..\..\Program Files\Autodesk\AutoCAD 2024\acmgd.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BooleanOperation.cs" />
<Compile Include="Commands.cs" />
<Compile Include="Region.cs" />
<Compile Include="SweepEdge.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SweepLine.cs" />
<Compile Include="SweepVertex.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

25
Boprs.sln Normal file
View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boprs", "Boprs.csproj", "{12366D09-2514-4EF3-9918-253B46077A4D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{12366D09-2514-4EF3-9918-253B46077A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12366D09-2514-4EF3-9918-253B46077A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12366D09-2514-4EF3-9918-253B46077A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12366D09-2514-4EF3-9918-253B46077A4D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {36F3AEF0-3759-4E30-8DA3-A47688241BCE}
EndGlobalSection
EndGlobal

115
Commands.cs Normal file
View file

@ -0,0 +1,115 @@
#if ACAD24
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Colors;
using AcAp = Autodesk.AutoCAD.ApplicationServices;
#elif BCAD
using Teigha.Runtime;
using Bricscad.EditorInput;
using Teigha.Geometry;
using Teigha.DatabaseServices;
using Teigha.Colors;
#endif
using System;
using System.Collections.Generic;
using Boprs;
#if DEBUG
[assembly: CommandClass(typeof(Commands))]
namespace Boprs
{
internal class Commands
{
private Region UserCreateRegion(string msg)
{
AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument;
Database acDb = acDoc.Database;
acDoc.Editor.WriteMessage("\n" + msg);
PromptSelectionResult res = acDoc.Editor.GetSelection();
if (res.Status != PromptStatus.OK || res.Value.Count == 0)
{
return null;
}
List<LineSegment2d> segs = new List<LineSegment2d>();
using (Transaction acTrans = acDb.TransactionManager.StartTransaction())
{
foreach (ObjectId asObjId in res.Value.GetObjectIds())
{
Polyline asPoly = (Polyline)acTrans.GetObject(asObjId, OpenMode.ForWrite);
using (DBObjectCollection exploded = new DBObjectCollection())
{
asPoly.Explode(exploded);
foreach(DBObject dBObject in exploded)
{
Line asLine = (Line)dBObject;
segs.Add(new LineSegment2d(asLine.StartPoint.To2d(), asLine.EndPoint.To2d()));
}
}
}
acTrans.Commit();
}
return Region.FromSegments(segs);
}
private BoprType UserChooseBoprType()
{
AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument;
PromptKeywordOptions keyPrompt = new PromptKeywordOptions("\nBoolean operation type: ");
keyPrompt.Keywords.Add("Unite");
keyPrompt.Keywords.Add("Intersect");
keyPrompt.Keywords.Add("Subtract");
keyPrompt.Keywords.Add("eXclusive");
keyPrompt.AllowNone = false;
PromptResult keywRes = acDoc.Editor.GetKeywords(keyPrompt);
switch (keywRes.StringResult.ToLower())
{
case "unite":
return BoprType.UNITE;
case "intersect":
return BoprType.INTERSECT;
case "subtract":
return BoprType.SUBTRACT;
case "exclusive":
return BoprType.EXCLUSIVE;
default:
return BoprType.INTERSECT;
}
}
[CommandMethod("boprtest")]
public void Subdivide()
{
AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument;
Region subject = UserCreateRegion("Enter polylines for first polygon:");
Region clip = UserCreateRegion("Enter polylines for second polygon:");
if(subject == null || clip == null)
{
acDoc.Editor.WriteMessage("\nOne of the polyline selections was empty.");
return;
}
BoprType type = UserChooseBoprType();
BooleanOperation bopr = new BooleanOperation(subject, clip, type);
bopr.Result.Draw();
}
}
}
#endif

37
LICENSE Normal file
View file

@ -0,0 +1,37 @@
3-Clause BSD NON-AI License
Copyright 2024 Zdenek Borovec
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
4. The source code and the binary form, and any modifications made to them
may not be used for the purpose of training or improving machine learning
algorithms, including but not limited to artificial intelligence,
natural language processing, or data mining. This condition applies
to any derivatives, modifications, or updates based on the Software code.
Any usage of the source code or the binary form in an AI-training dataset
is considered a breach of this License.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Boprs")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("DEK")]
[assembly: AssemblyProduct("Boprs")]
[assembly: AssemblyCopyright("Copyright © DEK 2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("12366d09-2514-4ef3-9918-253b46077a4d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Boprs
Boolean operation library for .NET framework 4.8, written in C#. Default bindings for AutoCAD (pre-v25) and BricsCAD.
Implements the algorithm described in the "A simple algorithm for Boolean operations on polygons" paper by F. Martínez et al.

175
Region.cs Normal file
View file

@ -0,0 +1,175 @@
#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>
/// Vertices belonging to this polygon.
/// </summary>
private List<SweepVertex> Vertices { get; }
/// <summary>
/// Subdivide my edges, set their inOut tags and validate their non-colinearity.
/// </summary>
private void ValidateEdges()
{
// Lexico-graphically sort my vertices.
Vertices.Sort();
// Sweepline to keep track of edges.
SweepLine sweepLine = new SweepLine();
// Current number of vertices in the list.
// Will be increased if subdivision occurs.
int numVertices = Vertices.Count;
// Go through all the sorted vertices, test for intersections when appropriate and,
// if needed, subdivide the edges.
for (int i = 0; i < numVertices; i++)
{
SweepVertex acVx = Vertices[i];
if (acVx.IsLeft())
{
sweepLine.Add(acVx.Edge, false);
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);
numVertices++;
}
}
if (nextEdge != null)
{
List<SweepVertex> newVertices = acVx.Edge.TrySubdivideBy(nextEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(Vertices, vertex);
numVertices++;
}
}
}
else
{
SweepEdge prevEdge = sweepLine.PrevEdge(acVx.Edge);
SweepEdge nextEdge = sweepLine.NextEdge(acVx.Edge);
sweepLine.Remove(acVx.Edge);
if (prevEdge != null && nextEdge != null)
{
List<SweepVertex> newVertices = prevEdge.TrySubdivideBy(nextEdge);
foreach (SweepVertex vertex in newVertices)
{
Utils.InsertVertexSorted(Vertices, vertex);
numVertices++;
}
}
}
}
}
/// <summary>
/// Get a copy of the regions vertex list.
/// </summary>
/// <returns>Copy of the regions vertex list.</returns>
internal List<LineSegment2d> GetSegments()
{
List <LineSegment2d> segments = new List<LineSegment2d>();
foreach(SweepVertex sweepVertex in Vertices)
{
if (sweepVertex.IsLeft())
{
segments.Add(
new LineSegment2d(sweepVertex.Edge.LeftVertex.Point, sweepVertex.Edge.RightVertex.Point));
}
}
return segments;
}
/// <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();
foreach(LineSegment2d segment in segments)
{
SweepEdge newEdge = new SweepEdge(segment.StartPoint, segment.EndPoint);
region.Vertices.Add(newEdge.LeftVertex);
region.Vertices.Add(newEdge.RightVertex);
newEdge.ParentRegion = region;
}
region.ValidateEdges();
return region;
}
/// <summary>
/// Constructor.
/// </summary>
private Region()
{
Vertices = new List<SweepVertex>();
}
#if DEBUG
internal void Draw()
{
Erase();
foreach (SweepVertex vert in Vertices)
{
if (vert.IsLeft())
{
vert.Edge.Draw();
}
}
}
internal void Erase()
{
foreach (SweepVertex vert in Vertices)
{
if (vert.IsLeft())
{
vert.Edge.Erase();
}
}
}
#endif
}
}

399
SweepEdge.cs Normal file
View file

@ -0,0 +1,399 @@
#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>
/// 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
{
/// <summary>
/// String representation of the edge.
/// </summary>
public override string ToString()
{
return ($"({LeftVertex} -- {RightVertex})");
}
/// <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)
{
AcAp.Document acDoc = AcAp.Application.DocumentManager.MdiActiveDocument;
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
}
}

128
SweepLine.cs Normal file
View file

@ -0,0 +1,128 @@
#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>
/// Line to sweep through sorted vertices, keeps track of currently intersecting edges.
/// </summary>
internal class SweepLine
{
/// <summary>
/// Edges the line is currently intersecting.
/// </summary>
/// TODO: Change this into a BST when optimizing.
private List<SweepEdge> IntersectingEdges { get; }
/// <summary>
/// Start tracking an edge identified by its left vertex.
/// </summary>
/// <param name="edge">Edge to start tracking.</param>
/// <param name="allowCollinearity">
/// If <c>false</c> and the edge is collinear with one of the edges in the sweep line,
/// an exception will get thrown.
/// </param>
internal void Add(SweepEdge edge, bool allowCollinearity)
{
int i = 0;
if (IntersectingEdges.Contains(edge))
{
throw new Exception("inserting contained edge");
}
// Go through all the edges, find my index for insertion.
for(; i < IntersectingEdges.Count; i++)
{
int comparison = edge.CompareTo(IntersectingEdges[i], edge.LeftVertex.Point.X);
// If we are below the current edge, that is the correct index.
if (comparison < 0)
{
break;
}
// If edges are collinear and it is not allowed, throw an exception
else if(!allowCollinearity && comparison == 0)
{
throw new Exception($"Collinear edges detected. [{edge} & {IntersectingEdges[i]}]");
}
// If we are above the last edge, set inserttion index after it.
else if (i == IntersectingEdges.Count)
{
i++;
}
}
// Insert the edge to the correct place.
IntersectingEdges.Insert(i, edge);
}
/// <summary>
/// Remove an edge from being tracked by the sweep line.
/// </summary>
/// <param name="edge">Edge to remove.</param>
internal void Remove(SweepEdge edge)
{
IntersectingEdges.Remove(edge);
}
/// <summary>
/// Returns the edge below curr, or null if curr is the lowest.
/// </summary>
/// <param name="curr">Current edge.</param>
/// <returns>Edge below curr, or null.</returns>
internal SweepEdge PrevEdge(SweepEdge curr)
{
// Get the index of the current edge
int currIdx = IntersectingEdges.IndexOf(curr);
// If the current edge isn't the lowest, return the edge below it.
if(currIdx != 0)
{
return IntersectingEdges[currIdx - 1];
}
// Otherwise return null.
return null;
}
/// <summary>
/// Returns the edge above curr, or null if curr is the highest.
/// </summary>
/// <param name="curr">Current edge.</param>
/// <returns>Edge above curr, or null.</returns>
internal SweepEdge NextEdge(SweepEdge curr)
{
// Get the index of the current edge
int currIdx = IntersectingEdges.IndexOf(curr);
// If the current edge isn't the highest, return the edge above it.
if (currIdx != IntersectingEdges.Count - 1)
{
return IntersectingEdges[currIdx + 1];
}
// Otherwise return null.
return null;
}
/// <summary>
/// Constructor.
/// </summary>
internal SweepLine()
{
IntersectingEdges = new List<SweepEdge>();
}
}
}

126
SweepVertex.cs Normal file
View file

@ -0,0 +1,126 @@
#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>
/// End point of an edge used while sweep line is processing a boolean operation.
/// </summary>
internal class SweepVertex : IComparable
{
/// <summary>
/// String representation of the vertex.
/// </summary>
public override string ToString()
{
return ($"({Point.X}, {Point.Y})");
}
/// <summary>
/// Comparison of vertices.
/// </summary>
/// <param name="obj">Vertex to compare.</param>
/// <returns>
/// <c>-1</c> if this is to the left of obj, <c>1</c> if this is to the right of obj, <c>0</c> if same.
/// </returns>
public int CompareTo(object obj)
{
if (obj is SweepVertex other)
{
// Different x coordinates
if (Point.X > other.Point.X)
{
return 1;
}
// Different x coordinates
if (Point.X < other.Point.X)
{
return -1;
}
// Different points, but same x-coordinate. The event with lower y-coordinate is processed first.
if (Point.Y != other.Point.Y)
{
return Point.Y > other.Point.Y ? 1 : -1;
}
// Same point, but one is a left endpoint and the other a right endpoint.
// The right endpoint is processed first.
if (IsLeft() != other.IsLeft())
{
return IsLeft() ? 1 : -1;
}
// One of our edges is vertical, the non-vertical one gets processed first.
if (Edge.IsVertical() != other.Edge.IsVertical())
{
return Edge.IsVertical() ? 1 : -1;
}
// Left vertex common, the downward leading edge gets processed first.
if (IsLeft())
{
if (Utils.SignedArea(Point, Edge.RightVertex.Point, other.Edge.RightVertex.Point) < 0)
{
return 1;
}
if (Utils.SignedArea(Point, Edge.RightVertex.Point, other.Edge.RightVertex.Point) > 0)
{
return -1;
}
}
// Right vertex is common, the upward leading edge gets processed first.
if (!IsLeft())
{
if (Utils.SignedArea(Point, Edge.RightVertex.Point, other.Edge.RightVertex.Point) < 0)
{
return -1;
}
if (Utils.SignedArea(Point, Edge.RightVertex.Point, other.Edge.RightVertex.Point) > 0)
{
return 1;
}
}
// Colinear edges
return 0;
}
throw new ArgumentException("obj is not SweepEvent.");
}
/// <summary>
/// Position of the vertex, use
/// </summary>
internal Point2d Point { get; }
/// <summary>
/// Edge this vertex belongs to.
/// </summary>
internal SweepEdge Edge { get; set; }
/// <summary>
/// Is this the left vertex of my parent edge?
/// </summary>
/// <returns><c>true</c> if this is the left(or bottom) edge of my parent, <c>false</c> if not.</returns>
internal bool IsLeft()
{
return this == Edge.LeftVertex;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="point">Position of the vertex.</param>
internal SweepVertex (Point2d point)
{
Point = point;
}
}
}

134
Utils.cs Normal file
View file

@ -0,0 +1,134 @@
#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;
using System.Runtime.CompilerServices;
namespace Boprs
{
/// <summary>
/// Non=specific utility functions.
/// </summary>
internal static class Utils
{
/// <summary>
/// Largest difference between two doubles before they are considered equal.
/// </summary>
internal const double DOUBLEPREC = 1e-8;
/// <summary>
/// Signed area of the triangle (pt1, pt2, pt3).
/// </summary>
/// <param name="pt1">Point 1</param>
/// <param name="pt2">Point 2</param>
/// <param name="pt3">Point 3</param>
/// <returns>Signed area of the triangle (pt1, pt2, pt3).</returns>
internal static double SignedArea(Point2d pt1, Point2d pt2, Point2d pt3)
{
return (pt1.X - pt3.X) * (pt2.Y - pt3.Y) - (pt2.X - pt3.X) * (pt1.Y - pt3.Y);
}
/// <summary>
/// Are the two doubles close enough to be almost equal?
/// </summary>
/// <param name="a">First double to compare.</param>
/// <param name="b">Second double to compare.</param>
/// <returns><c>true</c> if doubles are almost equal, <c>false</c> if doubles are not equal.</returns>
internal static bool DoubleEquals(double a, double b)
{
return Math.Abs(a - b) < DOUBLEPREC;
}
/// <summary>
/// Get the value Y at a given coordinate X for line going through stPt and endPt.
/// </summary>
/// <param name="stPt">Start point of the line.</param>
/// <param name="endPt">End point of the line.</param>
/// <param name="x">X param.</param>
/// <returns>Y value at X param for line going through the 2 points.</returns>
internal static double YatX(Point2d stPt, Point2d endPt, double x)
{
double m = (endPt.Y - stPt.Y) / (endPt.X - stPt.X);
double b = stPt.Y - (m * stPt.X);
return m * x + b;
}
/// <summary>
/// Insert a new vertex inbto a sorted list of vertices such that it remains sorted.
/// </summary>
/// <param name="vertices">Sorted list of vertices.</param>
/// <param name="newElement">Vertex to insert.</param>
internal static void InsertVertexSorted(List<SweepVertex> vertices, SweepVertex newElement)
{
int i = 0;
// Go through all the vertices, find my index for insertion.
for (; i < vertices.Count; i++)
{
// If we are left of the current edge, that is the correct index.
if (newElement.CompareTo(vertices[i]) < 0)
{
break;
}
// If we are to the right of the last vertex, set inserttion index after it.
else if (i == vertices.Count - 1)
{
i++;
}
}
// Insert the vertex to the correct place.
vertices.Insert(i, newElement);
}
internal static bool Overlaps(this LineSegment2d me, LineSegment2d other)
{
if (!me.IsColinearTo(other))
{
return false;
}
Line2d commonLine = me.GetLine();
double a = commonLine.GetParameterOf(me.StartPoint);
double b = commonLine.GetParameterOf(me.EndPoint);
double c = commonLine.GetParameterOf(other.StartPoint);
double d = commonLine.GetParameterOf(other.EndPoint);
if(DoubleEquals(b, c) || DoubleEquals(a, d))
{
return false;
}
if (b < c || d < a)
{
return false;
}
return true;
}
/// <summary>
/// Convert a 3d point to 2d.
/// </summary>
internal static Point2d To2d(this Point3d pt)
{
return new Point2d(pt.X, pt.Y);
}
/// <summary>
/// Convert a 2d point to 3d.
/// </summary>
internal static Point3d To3d(this Point2d pt)
{
return new Point3d(pt.X, pt.Y, 0);
}
}
}