From 0a04408382068358971ea133cef4b9699c60decb Mon Sep 17 00:00:00 2001 From: xenovacivus Date: Sun, 5 Jan 2014 13:12:59 -0800 Subject: [PATCH] Added PathCAM to the repo BUAHAHAHAhahaHAHAHAAA --- CNC_Machine.sln | 62 + Commands/Commands.csproj | 60 + Commands/ICommand.cs | 12 + Commands/MoveTool.cs | 31 + Commands/Properties/AssemblyInfo.cs | 36 + Commands/packages.config | 4 + GUI/App.config | 6 + GUI/COMPortForm.Designer.cs | 108 + GUI/COMPortForm.cs | 140 + GUI/COMPortForm.resx | 120 + GUI/Drawing3D.Designer.cs | 45 + GUI/Drawing3D.cs | 348 ++ GUI/Drawing3D.resx | 120 + GUI/GUI.csproj | 185 + GUI/IClickable3D.cs | 43 + GUI/Interfaces/IOpenGLDrawable.cs | 12 + GUI/PathCAM.cs | 466 ++ GUI/PathCAM.resx | 197 + GUI/Polyhedra.cs | 143 + GUI/Program.cs | 40 + GUI/Properties/AssemblyInfo.cs | 36 + GUI/Properties/Resources.Designer.cs | 71 + GUI/Properties/Resources.resx | 117 + GUI/Properties/Settings.Designer.cs | 30 + GUI/Properties/Settings.settings | 7 + GUI/RobotControl.Designer.cs | 163 + GUI/RobotControl.cs | 259 + GUI/RobotControl.resx | 120 + GUI/RobotGUI.cs | 125 + GUI/RouterGUI.cs | 134 + GUI/RouterUI.Designer.cs | 25 + GUI/TabsGUI.cs | 245 + GUI/TriangleMeshGUI.cs | 365 ++ GUI/Viewport3d.cs | 323 ++ GUI/icon.ico | Bin 0 -> 4286 bytes GUI/packages.config | 5 + Geometry/Geometry.csproj | 89 + Geometry/Intersect.cs | 216 + Geometry/LineSegment.cs | 150 + Geometry/LineStrip.cs | 123 + Geometry/Loaders/DAE_Loader.cs | 185 + Geometry/Loaders/OBJ_Loader.cs | 115 + Geometry/Loaders/STL_Loader.cs | 37 + Geometry/Plane.cs | 100 + Geometry/Polygon.cs | 134 + Geometry/Properties/AssemblyInfo.cs | 36 + Geometry/Ray.cs | 51 + Geometry/Slice.cs | 753 +++ Geometry/Triangle.cs | 126 + Geometry/TriangleMesh.cs | 304 ++ Geometry/packages.config | 6 + Robot/DataConverter.cs | 120 + Robot/IRobotCommand.cs | 33 + Robot/MoveCommand.cs | 55 + Robot/Properties/AssemblyInfo.cs | 36 + Robot/Robot.cs | 313 ++ Robot/Robot.csproj | 75 + Robot/SingleByteStatusCommands.cs | 70 + Robot/StatusCommand.cs | 109 + Robot/packages.config | 4 + Router/GCodeLoader.cs | 144 + Router/Paths/PathPlanner.cs | 710 +++ Router/Paths/Tabs.cs | 183 + Router/Paths/Tabs.cs.bak | 135 + Router/Properties/AssemblyInfo.cs | 36 + Router/Router.cs | 148 + Router/Router.csproj | 75 + Router/packages.config | 4 + Serial/Properties/AssemblyInfo.cs | 36 + Serial/Serial.csproj | 54 + Serial/SerialPacket.cs | 449 ++ Serial/SerialPortWrapper.cs | 220 + ThirdParty/Triangle/Algorithm/Dwyer.cs | 900 ++++ .../Triangle/Algorithm/ITriangulator.cs | 21 + ThirdParty/Triangle/Algorithm/Incremental.cs | 181 + ThirdParty/Triangle/Algorithm/SweepLine.cs | 804 +++ ThirdParty/Triangle/BadTriQueue.cs | 196 + ThirdParty/Triangle/Behavior.cs | 276 + ThirdParty/Triangle/Carver.cs | 421 ++ ThirdParty/Triangle/Data/BadSubseg.cs | 45 + ThirdParty/Triangle/Data/BadTriangle.cs | 42 + ThirdParty/Triangle/Data/Osub.cs | 255 + ThirdParty/Triangle/Data/Otri.cs | 484 ++ ThirdParty/Triangle/Data/Segment.cs | 108 + ThirdParty/Triangle/Data/Triangle.cs | 185 + ThirdParty/Triangle/Data/Vertex.cs | 114 + ThirdParty/Triangle/Enums.cs | 56 + ThirdParty/Triangle/Geometry/BoundingBox.cs | 129 + ThirdParty/Triangle/Geometry/Edge.cs | 64 + .../Triangle/Geometry/EdgeEnumerator.cs | 103 + ThirdParty/Triangle/Geometry/ISegment.cs | 47 + ThirdParty/Triangle/Geometry/ITriangle.cs | 83 + ThirdParty/Triangle/Geometry/InputGeometry.cs | 232 + ThirdParty/Triangle/Geometry/Point.cs | 165 + ThirdParty/Triangle/Geometry/RegionPointer.cs | 33 + ThirdParty/Triangle/IO/DataReader.cs | 322 ++ ThirdParty/Triangle/IO/DebugWriter.cs | 263 + ThirdParty/Triangle/IO/FileReader.cs | 711 +++ ThirdParty/Triangle/IO/FileWriter.cs | 551 ++ ThirdParty/Triangle/IO/IGeometryFormat.cs | 27 + ThirdParty/Triangle/IO/IMeshFormat.cs | 34 + ThirdParty/Triangle/IO/InputTriangle.cs | 118 + ThirdParty/Triangle/IO/TriangleFormat.cs | 67 + ThirdParty/Triangle/Log/ILog.cs | 37 + ThirdParty/Triangle/Log/ILogItem.cs | 24 + ThirdParty/Triangle/Log/SimpleLog.cs | 81 + ThirdParty/Triangle/Log/SimpleLogItem.cs | 56 + ThirdParty/Triangle/Mesh.cs | 2768 ++++++++++ ThirdParty/Triangle/NewLocation.cs | 4133 ++++++++++++++ ThirdParty/Triangle/Primitives.cs | 488 ++ .../Triangle/Properties/AssemblyInfo.cs | 36 + ThirdParty/Triangle/Quality.cs | 1000 ++++ ThirdParty/Triangle/Sampler.cs | 111 + ThirdParty/Triangle/Smoothing/ISmoother.cs | 21 + .../Triangle/Smoothing/SimpleSmoother.cs | 106 + ThirdParty/Triangle/Tools/AdjacencyMatrix.cs | 404 ++ ThirdParty/Triangle/Tools/BoundedVoronoi.cs | 656 +++ ThirdParty/Triangle/Tools/CuthillMcKee.cs | 659 +++ ThirdParty/Triangle/Tools/IVoronoi.cs | 27 + ThirdParty/Triangle/Tools/QuadTree.cs | 423 ++ ThirdParty/Triangle/Tools/QualityMeasure.cs | 544 ++ ThirdParty/Triangle/Tools/RegionIterator.cs | 139 + ThirdParty/Triangle/Tools/Statistic.cs | 518 ++ ThirdParty/Triangle/Tools/Voronoi.cs | 337 ++ ThirdParty/Triangle/Tools/VoronoiRegion.cs | 82 + ThirdParty/Triangle/Triangle.csproj | 108 + ThirdParty/Triangle/TriangleLocator.cs | 339 ++ .../Properties/AssemblyInfo.cs | 36 + ThirdParty/clipper_library/clipper.cs | 4782 +++++++++++++++++ .../clipper_library/clipper_library.csproj | 54 + 130 files changed, 34643 insertions(+) create mode 100644 CNC_Machine.sln create mode 100644 Commands/Commands.csproj create mode 100644 Commands/ICommand.cs create mode 100644 Commands/MoveTool.cs create mode 100644 Commands/Properties/AssemblyInfo.cs create mode 100644 Commands/packages.config create mode 100644 GUI/App.config create mode 100644 GUI/COMPortForm.Designer.cs create mode 100644 GUI/COMPortForm.cs create mode 100644 GUI/COMPortForm.resx create mode 100644 GUI/Drawing3D.Designer.cs create mode 100644 GUI/Drawing3D.cs create mode 100644 GUI/Drawing3D.resx create mode 100644 GUI/GUI.csproj create mode 100644 GUI/IClickable3D.cs create mode 100644 GUI/Interfaces/IOpenGLDrawable.cs create mode 100644 GUI/PathCAM.cs create mode 100644 GUI/PathCAM.resx create mode 100644 GUI/Polyhedra.cs create mode 100644 GUI/Program.cs create mode 100644 GUI/Properties/AssemblyInfo.cs create mode 100644 GUI/Properties/Resources.Designer.cs create mode 100644 GUI/Properties/Resources.resx create mode 100644 GUI/Properties/Settings.Designer.cs create mode 100644 GUI/Properties/Settings.settings create mode 100644 GUI/RobotControl.Designer.cs create mode 100644 GUI/RobotControl.cs create mode 100644 GUI/RobotControl.resx create mode 100644 GUI/RobotGUI.cs create mode 100644 GUI/RouterGUI.cs create mode 100644 GUI/RouterUI.Designer.cs create mode 100644 GUI/TabsGUI.cs create mode 100644 GUI/TriangleMeshGUI.cs create mode 100644 GUI/Viewport3d.cs create mode 100644 GUI/icon.ico create mode 100644 GUI/packages.config create mode 100644 Geometry/Geometry.csproj create mode 100644 Geometry/Intersect.cs create mode 100644 Geometry/LineSegment.cs create mode 100644 Geometry/LineStrip.cs create mode 100644 Geometry/Loaders/DAE_Loader.cs create mode 100644 Geometry/Loaders/OBJ_Loader.cs create mode 100644 Geometry/Loaders/STL_Loader.cs create mode 100644 Geometry/Plane.cs create mode 100644 Geometry/Polygon.cs create mode 100644 Geometry/Properties/AssemblyInfo.cs create mode 100644 Geometry/Ray.cs create mode 100644 Geometry/Slice.cs create mode 100644 Geometry/Triangle.cs create mode 100644 Geometry/TriangleMesh.cs create mode 100644 Geometry/packages.config create mode 100644 Robot/DataConverter.cs create mode 100644 Robot/IRobotCommand.cs create mode 100644 Robot/MoveCommand.cs create mode 100644 Robot/Properties/AssemblyInfo.cs create mode 100644 Robot/Robot.cs create mode 100644 Robot/Robot.csproj create mode 100644 Robot/SingleByteStatusCommands.cs create mode 100644 Robot/StatusCommand.cs create mode 100644 Robot/packages.config create mode 100644 Router/GCodeLoader.cs create mode 100644 Router/Paths/PathPlanner.cs create mode 100644 Router/Paths/Tabs.cs create mode 100644 Router/Paths/Tabs.cs.bak create mode 100644 Router/Properties/AssemblyInfo.cs create mode 100644 Router/Router.cs create mode 100644 Router/Router.csproj create mode 100644 Router/packages.config create mode 100644 Serial/Properties/AssemblyInfo.cs create mode 100644 Serial/Serial.csproj create mode 100644 Serial/SerialPacket.cs create mode 100644 Serial/SerialPortWrapper.cs create mode 100644 ThirdParty/Triangle/Algorithm/Dwyer.cs create mode 100644 ThirdParty/Triangle/Algorithm/ITriangulator.cs create mode 100644 ThirdParty/Triangle/Algorithm/Incremental.cs create mode 100644 ThirdParty/Triangle/Algorithm/SweepLine.cs create mode 100644 ThirdParty/Triangle/BadTriQueue.cs create mode 100644 ThirdParty/Triangle/Behavior.cs create mode 100644 ThirdParty/Triangle/Carver.cs create mode 100644 ThirdParty/Triangle/Data/BadSubseg.cs create mode 100644 ThirdParty/Triangle/Data/BadTriangle.cs create mode 100644 ThirdParty/Triangle/Data/Osub.cs create mode 100644 ThirdParty/Triangle/Data/Otri.cs create mode 100644 ThirdParty/Triangle/Data/Segment.cs create mode 100644 ThirdParty/Triangle/Data/Triangle.cs create mode 100644 ThirdParty/Triangle/Data/Vertex.cs create mode 100644 ThirdParty/Triangle/Enums.cs create mode 100644 ThirdParty/Triangle/Geometry/BoundingBox.cs create mode 100644 ThirdParty/Triangle/Geometry/Edge.cs create mode 100644 ThirdParty/Triangle/Geometry/EdgeEnumerator.cs create mode 100644 ThirdParty/Triangle/Geometry/ISegment.cs create mode 100644 ThirdParty/Triangle/Geometry/ITriangle.cs create mode 100644 ThirdParty/Triangle/Geometry/InputGeometry.cs create mode 100644 ThirdParty/Triangle/Geometry/Point.cs create mode 100644 ThirdParty/Triangle/Geometry/RegionPointer.cs create mode 100644 ThirdParty/Triangle/IO/DataReader.cs create mode 100644 ThirdParty/Triangle/IO/DebugWriter.cs create mode 100644 ThirdParty/Triangle/IO/FileReader.cs create mode 100644 ThirdParty/Triangle/IO/FileWriter.cs create mode 100644 ThirdParty/Triangle/IO/IGeometryFormat.cs create mode 100644 ThirdParty/Triangle/IO/IMeshFormat.cs create mode 100644 ThirdParty/Triangle/IO/InputTriangle.cs create mode 100644 ThirdParty/Triangle/IO/TriangleFormat.cs create mode 100644 ThirdParty/Triangle/Log/ILog.cs create mode 100644 ThirdParty/Triangle/Log/ILogItem.cs create mode 100644 ThirdParty/Triangle/Log/SimpleLog.cs create mode 100644 ThirdParty/Triangle/Log/SimpleLogItem.cs create mode 100644 ThirdParty/Triangle/Mesh.cs create mode 100644 ThirdParty/Triangle/NewLocation.cs create mode 100644 ThirdParty/Triangle/Primitives.cs create mode 100644 ThirdParty/Triangle/Properties/AssemblyInfo.cs create mode 100644 ThirdParty/Triangle/Quality.cs create mode 100644 ThirdParty/Triangle/Sampler.cs create mode 100644 ThirdParty/Triangle/Smoothing/ISmoother.cs create mode 100644 ThirdParty/Triangle/Smoothing/SimpleSmoother.cs create mode 100644 ThirdParty/Triangle/Tools/AdjacencyMatrix.cs create mode 100644 ThirdParty/Triangle/Tools/BoundedVoronoi.cs create mode 100644 ThirdParty/Triangle/Tools/CuthillMcKee.cs create mode 100644 ThirdParty/Triangle/Tools/IVoronoi.cs create mode 100644 ThirdParty/Triangle/Tools/QuadTree.cs create mode 100644 ThirdParty/Triangle/Tools/QualityMeasure.cs create mode 100644 ThirdParty/Triangle/Tools/RegionIterator.cs create mode 100644 ThirdParty/Triangle/Tools/Statistic.cs create mode 100644 ThirdParty/Triangle/Tools/Voronoi.cs create mode 100644 ThirdParty/Triangle/Tools/VoronoiRegion.cs create mode 100644 ThirdParty/Triangle/Triangle.csproj create mode 100644 ThirdParty/Triangle/TriangleLocator.cs create mode 100644 ThirdParty/clipper_library/Properties/AssemblyInfo.cs create mode 100644 ThirdParty/clipper_library/clipper.cs create mode 100644 ThirdParty/clipper_library/clipper_library.csproj diff --git a/CNC_Machine.sln b/CNC_Machine.sln new file mode 100644 index 0000000..d69383d --- /dev/null +++ b/CNC_Machine.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2012 for Windows Desktop +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robot", "Robot\Robot.csproj", "{C7BBABF0-0698-4C5F-A510-7E160C8771A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serial", "Serial\Serial.csproj", "{9477FD2C-FE8F-4653-AB25-BFA14338A932}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUI", "GUI\GUI.csproj", "{7BA42A6E-9E73-43E4-9461-B98C8D50AED1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Triangle", "ThirdParty\Triangle\Triangle.csproj", "{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Geometry", "Geometry\Geometry.csproj", "{29611CC3-E54F-45E4-9681-8DF653459CA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clipper_library", "ThirdParty\clipper_library\clipper_library.csproj", "{9B062971-A88E-4A3D-B3C9-12B78D15FA66}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Router", "Router\Router.csproj", "{6A2ED2BC-1FEA-46E3-8403-3C484A7BCCA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands", "Commands\Commands.csproj", "{0BE94AA8-662B-45EC-B95A-545180F54618}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C7BBABF0-0698-4C5F-A510-7E160C8771A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7BBABF0-0698-4C5F-A510-7E160C8771A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7BBABF0-0698-4C5F-A510-7E160C8771A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7BBABF0-0698-4C5F-A510-7E160C8771A5}.Release|Any CPU.Build.0 = Release|Any CPU + {9477FD2C-FE8F-4653-AB25-BFA14338A932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9477FD2C-FE8F-4653-AB25-BFA14338A932}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9477FD2C-FE8F-4653-AB25-BFA14338A932}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9477FD2C-FE8F-4653-AB25-BFA14338A932}.Release|Any CPU.Build.0 = Release|Any CPU + {7BA42A6E-9E73-43E4-9461-B98C8D50AED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BA42A6E-9E73-43E4-9461-B98C8D50AED1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BA42A6E-9E73-43E4-9461-B98C8D50AED1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BA42A6E-9E73-43E4-9461-B98C8D50AED1}.Release|Any CPU.Build.0 = Release|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.Build.0 = Release|Any CPU + {29611CC3-E54F-45E4-9681-8DF653459CA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29611CC3-E54F-45E4-9681-8DF653459CA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29611CC3-E54F-45E4-9681-8DF653459CA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29611CC3-E54F-45E4-9681-8DF653459CA2}.Release|Any CPU.Build.0 = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.Build.0 = Release|Any CPU + {6A2ED2BC-1FEA-46E3-8403-3C484A7BCCA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A2ED2BC-1FEA-46E3-8403-3C484A7BCCA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A2ED2BC-1FEA-46E3-8403-3C484A7BCCA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A2ED2BC-1FEA-46E3-8403-3C484A7BCCA5}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE94AA8-662B-45EC-B95A-545180F54618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE94AA8-662B-45EC-B95A-545180F54618}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE94AA8-662B-45EC-B95A-545180F54618}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE94AA8-662B-45EC-B95A-545180F54618}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Commands/Commands.csproj b/Commands/Commands.csproj new file mode 100644 index 0000000..430d5b0 --- /dev/null +++ b/Commands/Commands.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {0BE94AA8-662B-45EC-B95A-545180F54618} + Library + Properties + Commands + Commands + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\OpenTK.1.1.1456.5398\lib\NET40\OpenTK.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Commands/ICommand.cs b/Commands/ICommand.cs new file mode 100644 index 0000000..36d151b --- /dev/null +++ b/Commands/ICommand.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Commands +{ + public abstract class ICommand + { + } +} diff --git a/Commands/MoveTool.cs b/Commands/MoveTool.cs new file mode 100644 index 0000000..42f0ce3 --- /dev/null +++ b/Commands/MoveTool.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Commands +{ + public class MoveTool : ICommand + { + private Vector3 target; // Target location in inches + private float speed; // Moving speed in inches per minute + + public MoveTool(Vector3 target, float speed) + { + this.target = target; + this.speed = speed; + } + + public Vector3 Target + { + get { return target; } + } + + public float Speed + { + get { return speed; } + } + } +} diff --git a/Commands/Properties/AssemblyInfo.cs b/Commands/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..99cf9d0 --- /dev/null +++ b/Commands/Properties/AssemblyInfo.cs @@ -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("Commands")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Commands")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("8aea738d-0d61-414f-a11c-e73b7f1169c9")] + +// 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")] diff --git a/Commands/packages.config b/Commands/packages.config new file mode 100644 index 0000000..d0ea9c3 --- /dev/null +++ b/Commands/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/GUI/App.config b/GUI/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/GUI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GUI/COMPortForm.Designer.cs b/GUI/COMPortForm.Designer.cs new file mode 100644 index 0000000..330c1a6 --- /dev/null +++ b/GUI/COMPortForm.Designer.cs @@ -0,0 +1,108 @@ +namespace GUI +{ + partial class COMPortForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.baudBox = new System.Windows.Forms.TextBox(); + this.connect = new System.Windows.Forms.Button(); + this.listBox1 = new System.Windows.Forms.ListBox(); + this.SuspendLayout(); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(138, 9); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(58, 13); + this.label2.TabIndex = 12; + this.label2.Text = "Baud Rate"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(26, 13); + this.label1.TabIndex = 11; + this.label1.Text = "Port"; + // + // baudBox + // + this.baudBox.Location = new System.Drawing.Point(138, 25); + this.baudBox.Name = "baudBox"; + this.baudBox.Size = new System.Drawing.Size(120, 20); + this.baudBox.TabIndex = 10; + this.baudBox.Text = "9600"; + // + // connect + // + this.connect.Location = new System.Drawing.Point(138, 51); + this.connect.Name = "connect"; + this.connect.Size = new System.Drawing.Size(120, 23); + this.connect.TabIndex = 8; + this.connect.Text = "Connect"; + this.connect.UseVisualStyleBackColor = true; + this.connect.Click += new System.EventHandler(this.connect_Click); + // + // listBox1 + // + this.listBox1.FormattingEnabled = true; + this.listBox1.Location = new System.Drawing.Point(12, 25); + this.listBox1.Name = "listBox1"; + this.listBox1.Size = new System.Drawing.Size(120, 69); + this.listBox1.TabIndex = 14; + this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); + // + // COMPortForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(270, 102); + this.Controls.Add(this.listBox1); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.baudBox); + this.Controls.Add(this.connect); + this.Name = "COMPortForm"; + this.Text = "COMPort"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox baudBox; + private System.Windows.Forms.Button connect; + private System.Windows.Forms.ListBox listBox1; + } +} \ No newline at end of file diff --git a/GUI/COMPortForm.cs b/GUI/COMPortForm.cs new file mode 100644 index 0000000..f163985 --- /dev/null +++ b/GUI/COMPortForm.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.IO.Ports; +using Serial; +using Robot; +using Router; + +namespace GUI +{ + public partial class COMPortForm : Form + { + SerialPortWrapper port; + Timer t; + String[] portNames = new String [0]; + + + public COMPortForm(SerialPortWrapper p) + { + if (p == null) + { + throw new NullReferenceException("Serial Port Wrapper object cannot be null"); + } + + InitializeComponent(); + port = p; + + t_Tick(null, EventArgs.Empty); + baudBox.Text = port.BaudRate.ToString(); + SelectPortName(port.PortName); + if (port.IsOpen) + { + connect.Text = "Disconnect"; + } + else + { + if (listBox1.SelectedItem == null) + { + connect.Enabled = false; + } + } + + t = new Timer(); + t.Interval = 100; + t.Tick += new EventHandler(t_Tick); + t.Start(); + } + + void t_Tick(object sender, EventArgs e) + { + string [] s = port.PortNames; + bool matched = true; + if (s.Length != portNames.Length) + { + matched = false; + } + + if (!matched) + { + object selectedObject = listBox1.SelectedItem; + + portNames = s; + listBox1.Items.Clear(); + listBox1.Items.AddRange(portNames); + + if (selectedObject != null) + { + string str = selectedObject.ToString(); + SelectPortName(str); + } + if (listBox1.SelectedItem == null && !port.IsOpen) + { + connect.Enabled = false; + } + } + } + + private void SelectPortName(string name) + { + for (int i = 0; i < listBox1.Items.Count; i++) + { + if (listBox1.Items[i].ToString() == name) + { + listBox1.SelectedIndex = i; + } + } + } + + private void connect_Click(object sender, EventArgs e) + { + if (port.IsOpen) + { + port.Close(); + connect.Text = "Connect"; + } + else + { + try + { + int baudRate = Convert.ToInt32(this.baudBox.Text); + string portName = listBox1.SelectedItem.ToString(); + port.Open(portName, baudRate); + connect.Text = "Disconnect"; + } + catch (NullReferenceException ex) + { + MessageBox.Show("Error Opening Com Port: No Port Name Selected!"); + } + catch (FormatException ex) + { + MessageBox.Show("Error Opening Com Port: Invalid Baud Rate!"); + } + catch (Exception ex) + { + MessageBox.Show("Error Opening Com Port: " + ex.Message); + } + } + } + + private void listBox1_SelectedIndexChanged(object sender, EventArgs e) + { + if (!port.IsOpen) + { + if (listBox1.SelectedItem != null) + { + this.connect.Enabled = true; + } + else + { + this.connect.Enabled = false; + } + } + } + } +} diff --git a/GUI/COMPortForm.resx b/GUI/COMPortForm.resx new file mode 100644 index 0000000..d58980a --- /dev/null +++ b/GUI/COMPortForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GUI/Drawing3D.Designer.cs b/GUI/Drawing3D.Designer.cs new file mode 100644 index 0000000..856e6af --- /dev/null +++ b/GUI/Drawing3D.Designer.cs @@ -0,0 +1,45 @@ +namespace GUI +{ + partial class Drawing3D + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // UserControl1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Name = "UserControl1"; + this.Load += new System.EventHandler(this.UserControl1_Load); + this.ResumeLayout(false); + + } + + #endregion + } +} diff --git a/GUI/Drawing3D.cs b/GUI/Drawing3D.cs new file mode 100644 index 0000000..9d04691 --- /dev/null +++ b/GUI/Drawing3D.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Robot; +using Serial; +using Router; +using Geometry; + +namespace GUI +{ + public partial class Drawing3D : GLControl + { + Color clearColor; + Viewport3d viewport; + Vector2 lastMouseLocation = new Vector2(0, 0); + IClickable3D clickedObject = null; + private Router.Router router; + + Timer drawTimer; + + public Drawing3D() + { + this.InitializeComponent(); + + this.viewport = new Viewport3d(this); + this.Resize += new EventHandler(HandleControlResize); + this.MouseMove += new MouseEventHandler(HandleMouseMove); + this.MouseEnter += new EventHandler(HandleMouseEnter); + this.MouseLeave += new EventHandler(HandleMouseLeave); + this.MouseUp += new MouseEventHandler(HandleMouseUp); + this.MouseDown += new MouseEventHandler(HandleMouseDown); + this.MouseWheel += new MouseEventHandler(HandleMouseWheel); + + drawTimer = new Timer(); + drawTimer.Tick += new EventHandler(drawTimer_Tick); + drawTimer.Interval = 15; + drawTimer.Start(); + } + + void drawTimer_Tick(object sender, EventArgs e) + { + this.Invalidate(); + } + + #region Mouse Control + + void HandleMouseWheel(object sender, MouseEventArgs e) + { + Ray pointer = viewport.GetPointerRay(e.Location); + viewport.Zoom(pointer, -e.Delta / 120); + this.Invalidate(); + } + + private Point mouseDownLocation; + private bool mouseRDown = false; + void HandleMouseDown(object sender, MouseEventArgs e) + { + if (clickedObject != null) + { + // If something's already clicked, don't allow selecting other items. + // This will force a mouse up in the client area. + return; + } + + mouseDownLocation = e.Location; + + Ray pointer = viewport.GetPointerRay(e.Location); + clickedObject = GetClosestObject(pointer); + + if (e.Button == System.Windows.Forms.MouseButtons.Right) + { + mouseRDown = true; + viewport.BeginRotate(); + return; + } + else if (e.Button != System.Windows.Forms.MouseButtons.Left) + { + return; + } + + + + clickedObject.MouseDown(pointer); + + this.Invalidate(); + } + + public Ray GetPointerRay(Point mouseLocation) + { + return viewport.GetPointerRay(mouseLocation); + } + + private IClickable3D GetClosestObject(Ray pointer) + { + IClickable3D closestClickable = null; + float closest = float.PositiveInfinity; + foreach (var o in objects) + { + if (o is IClickable3D) + { + var clickable = o as IClickable3D; + float distance = clickable.DistanceToObject(pointer); + if (distance < closest) + { + closestClickable = clickable; + closest = distance; + } + } + } + + if (FilterSelectable(closestClickable)) + { + closestClickable.MouseHover(); // TODO: find a better way to handle hover indication... + return closestClickable; + } + return viewport as IClickable3D; + } + + private bool FilterSelectable(Object selecting) + { + if (selecting == null) + { + return false; + } + //if (selecting is TabsGUI) + //{ + // return true; + //} + //return false; + return true; + } + + void HandleMouseUp(object sender, MouseEventArgs e) + { + // Display a context menu? + if (mouseRDown) + { + if (mouseDownLocation == e.Location) + { + if (clickedObject != null && clickedObject is TriangleMeshGUI) + { + System.Windows.Forms.ContextMenu menu = new ContextMenu(); + var item = new MenuItem("Delete", new EventHandler(objectDeleteClicked)); + item.Tag = clickedObject; + menu.MenuItems.Add(item); + menu.Show(this, e.Location); + } + } + } + + mouseRDown = false; + if (clickedObject != null) + { + Ray pointer = viewport.GetPointerRay(e.Location); + clickedObject.MouseUp(pointer); + clickedObject = null; + } + } + + void objectDeleteClicked(object sender, EventArgs e) + { + var item = sender as MenuItem; + if (item != null) + { + IClickable3D toRemove = item.Tag as IClickable3D; + if (toRemove != null) + { + if (toRemove is TriangleMeshGUI) + { + foreach (var tab in (toRemove as TriangleMeshGUI).Tabs) + { + objects.RemoveAll(o => o == tab); + } + } + objects.RemoveAll(o => o == toRemove); + } + } + } + + void HandleMouseLeave(object sender, EventArgs e) + { + } + + void HandleMouseEnter(object sender, EventArgs e) + { + this.Focus(); + } + + IClickable3D hoveredObject = null; + void HandleMouseMove(object sender, MouseEventArgs e) + { + hoveredObject = null; + if (mouseRDown) + { + Point mouseDelta = new Point(e.Location.X - mouseDownLocation.X, e.Location.Y - mouseDownLocation.Y); + viewport.ViewportRotate(mouseDelta.X, mouseDelta.Y); + } + else + { + Ray pointer = viewport.GetPointerRay(e.Location); + if (clickedObject != null) + { + clickedObject.MouseMove(pointer); + } + else + { + IClickable3D hovered = GetClosestObject(pointer); + if (hovered != null) + { + hoveredObject = hovered; + } + } + } + this.Invalidate(); + } + + #endregion + + #region Drawing + + void HandleControlResize(object sender, EventArgs e) + { + // TODO: move viewport setup code here, only call when changing the viewport size. + } + + public Color ClearColor + { + get { return clearColor; } + set + { + clearColor = value; + + if (!this.DesignMode) + { + MakeCurrent(); + GL.ClearColor(clearColor); + } + } + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + if (!this.DesignMode) + { + MakeCurrent(); + GL.Viewport(ClientRectangle); + //float aspect = this.ClientSize.Width / (float)this.ClientSize.Height; + + GL.MatrixMode(MatrixMode.Projection); + GL.LoadIdentity(); + + // 3D Setup + Matrix4 projection = viewport.ProjectionMatrix; + GL.LoadMatrix(ref projection); + GL.MatrixMode(MatrixMode.Modelview); + + GL.Enable(EnableCap.Blend); // glEnable(GL_BLEND); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GL.ClearColor(Color.White); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + GL.LoadIdentity(); + + GL.Enable(EnableCap.Lighting); + GL.Enable(EnableCap.ColorMaterial); + GL.Light(LightName.Light0, LightParameter.Position, new Vector4(0, 0, 1, 0)); + GL.Light(LightName.Light0, LightParameter.Diffuse, new Vector4 (.8f, .8f, .8f, 1)); + GL.Light(LightName.Light0, LightParameter.Ambient, new Vector4(.3f, .3f, .3f, 1)); + //GL.Light(LightName.Light0, LightParameter.Specular, new Vector4(1.0f, 1.0f, 1.0f, 1)); + GL.Enable(EnableCap.Light0); + GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse); + //GL.ColorMaterial(MaterialFace.Front, ColorMaterialParameter.AmbientAndDiffuse); + GL.Enable(EnableCap.ColorMaterial); + + GL.LoadMatrix(ref viewport.viewMatrix); + GL.Normal3(new Vector3(0, 0, 1)); + GL.Enable(EnableCap.Normalize); + + GL.DepthMask(true); + GL.Enable(EnableCap.DepthTest); + GL.DepthFunc(DepthFunction.Lequal); + //GL.Enable(EnableCap.CullFace); + //GL.CullFace(CullFaceMode.Back); + GL.PolygonOffset(1.0f, 2.0f); + GL.Enable(EnableCap.PolygonOffsetFill); + + viewport.Draw(); + + //GL.PushMatrix(); + foreach (object o in objects) + { + if (o is IOpenGLDrawable) + { + var drawable = o as IOpenGLDrawable; + drawable.Draw(); + } + } + //GL.PopMatrix(); + + SwapBuffers(); + } + } + + private void UserControl1_Load(object sender, EventArgs e) + { + this.HandleControlResize(sender, e); + } + + #endregion + + List objects = new List(); + internal void AddObject(Object o) + { + if (o != null) + { + objects.Add(o); + if (o is Router.Router) + { + router = o as Router.Router; + } + } + } + + internal List GetObjects() + { + // TODO: return the viewport as an object? + return objects; + } + + internal void DeleteSelectedObjected() + { + if (clickedObject != null) + { + objects.RemoveAll(c => c == clickedObject); + } + clickedObject = null; + } + } +} diff --git a/GUI/Drawing3D.resx b/GUI/Drawing3D.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/GUI/Drawing3D.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GUI/GUI.csproj b/GUI/GUI.csproj new file mode 100644 index 0000000..59625c3 --- /dev/null +++ b/GUI/GUI.csproj @@ -0,0 +1,185 @@ + + + + + Debug + AnyCPU + {7BA42A6E-9E73-43E4-9461-B98C8D50AED1} + WinExe + Properties + GUI + GUI + v4.5 + 512 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\OpenTK.1.1.1456.5398\lib\NET40\OpenTK.dll + + + ..\packages\OpenTK.GLControl.1.1.1456.5398\lib\NET40\OpenTK.GLControl.dll + + + + + + + + + + + + + + + Form + + + COMPortForm.cs + + + UserControl + + + RobotControl.cs + + + + + + + + Form + + + PathCAM.cs + + + + + UserControl + + + Drawing3D.cs + + + + + + COMPortForm.cs + + + PathCAM.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + Drawing3D.cs + + + RobotControl.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {0be94aa8-662b-45ec-b95a-545180f54618} + Commands + + + {29611cc3-e54f-45e4-9681-8df653459ca2} + Geometry + + + {c7bbabf0-0698-4c5f-a510-7e160c8771a5} + Robot + + + {6a2ed2bc-1fea-46e3-8403-3c484a7bcca5} + Router + + + {9477fd2c-fe8f-4653-ab25-bfa14338a932} + Serial + + + + + False + Microsoft .NET Framework 4.5 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + + + \ No newline at end of file diff --git a/GUI/IClickable3D.cs b/GUI/IClickable3D.cs new file mode 100644 index 0000000..29a30c5 --- /dev/null +++ b/GUI/IClickable3D.cs @@ -0,0 +1,43 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Geometry; +using OpenTK; + +namespace GUI +{ + public interface IClickable3D + { + void MouseDown(Ray pointer); + void MouseUp(Ray pointer); + void MouseMove(Ray pointer); + + /// + /// Mouse is hovering on this object. + /// The ray will be the same as the last + /// call to DistanceToObject. + /// + void MouseHover(); + float DistanceToObject(Ray pointer); + } +} diff --git a/GUI/Interfaces/IOpenGLDrawable.cs b/GUI/Interfaces/IOpenGLDrawable.cs new file mode 100644 index 0000000..6a4fc15 --- /dev/null +++ b/GUI/Interfaces/IOpenGLDrawable.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace GUI +{ + public interface IOpenGLDrawable + { + void Draw(); + } +} diff --git a/GUI/PathCAM.cs b/GUI/PathCAM.cs new file mode 100644 index 0000000..6000cbe --- /dev/null +++ b/GUI/PathCAM.cs @@ -0,0 +1,466 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.IO; +using System.Drawing.Drawing2D; +using System.Text.RegularExpressions; +using System.Runtime.Serialization; +using Robot; +using Serial; +using Router; +using OpenTK; +using Geometry; +using Router.Paths; +using Commands; +using System.Reflection; + +namespace GUI +{ + public partial class PathCAM : Form, IOpenGLDrawable + { + private RouterGUI router; + private string dragDropFilename = null; + private Regex acceptedFileRegex = new Regex(@".*(\.dae|\.obj|\.stl|\.nc|\.gcode)", RegexOptions.IgnoreCase); + + public PathCAM() + { + InitializeComponent(); + + router = new RouterGUI(); + propertyGrid.SelectedObject = router; + drawing3D.AddObject(router); + robotControl.AssignRouter(router); + drawing3D.AddObject(robotControl); + drawing3D.AddObject(this); + drawing3D.DragDrop += this.Drawing3D_DragDrop; + drawing3D.DragOver += this.Drawing3D_DragOver; + drawing3D.DragEnter += this.Drawing3D_DragEnter; + drawing3D.DragLeave += this.Drawing3D_DragLeave; + } + + void Drawing3D_DragLeave(object sender, EventArgs e) + { + //dragDropFilename = null; + // TODO: cancel the background worker loading the mesh, completely remove the mesh from everywhere. + } + + Vector3 dragEnterLocation = Vector3.Zero; + void Drawing3D_DragOver(object sender, DragEventArgs e) + { + if (dragDropMesh != null) + { + var plane = new Plane(Vector3.UnitZ, new Vector3(0, 0, 0)); + var ray = drawing3D.GetPointerRay(drawing3D.PointToClient(MousePosition)); + dragDropMesh.Offset = ray.Start + ray.Direction * plane.Distance(ray); + } + } + + void Drawing3D_DragDrop(object sender, DragEventArgs e) + { + dragDropMesh = null; + } + + TriangleMeshGUI dragDropMesh = null; + void Drawing3D_DragEnter(object sender, DragEventArgs e) + { + this.Activate(); + if (dragDropMesh != null) + { + e.Effect = DragDropEffects.Copy; + return; + } + + e.Effect = DragDropEffects.None; + if (openFileButton.Enabled && e.Data.GetDataPresent(DataFormats.FileDrop)) + { + Array a = (Array)e.Data.GetData(DataFormats.FileDrop); + if (a != null && a.Length == 1) + { + string filename = a.GetValue(0).ToString(); + if (acceptedFileRegex.IsMatch(filename)) + { + dragDropFilename = filename; + dragDropMesh = AddFile(dragDropFilename, loadObjectScale); + var plane = new Plane(Vector3.UnitZ, new Vector3(0, 0, 0)); + var ray = drawing3D.GetPointerRay(new Point(e.X, e.Y)); + dragEnterLocation = ray.Start + ray.Direction * plane.Distance(ray); + e.Effect = DragDropEffects.Copy; + } + } + } + } + + private class LoadMeshData + { + public float scale; + public string filename; + public TriangleMeshGUI mesh; + } + + private List inProgressMeshes = new List(); + internal TriangleMeshGUI AddFile(string filename, float scale) + { + if (filename.EndsWith(".nc", StringComparison.OrdinalIgnoreCase) || filename.EndsWith(".gcode", StringComparison.OrdinalIgnoreCase)) + { + var commands = GCodeLoader.Load(filename); + foreach (ICommand command in commands) + { + router.AddCommand(command); + } + return null; + } + BackgroundWorker worker = new BackgroundWorker(); + worker.DoWork += worker_LoadMesh; + worker.RunWorkerCompleted += worker_LoadMeshCompleted; + var mesh = new TriangleMeshGUI(); + inProgressMeshes.Add(mesh); + worker.RunWorkerAsync(new LoadMeshData() { filename = filename, scale = loadObjectScale, mesh = mesh }); + return mesh; + } + + void worker_LoadMesh(object sender, DoWorkEventArgs e) + { + var data = e.Argument as LoadMeshData; + string filename = data.filename; + float scale = data.scale; + var triangleMesh = data.mesh; + if (filename.EndsWith(".dae", StringComparison.OrdinalIgnoreCase)) + { + DAE_Loader.Load(filename, triangleMesh, scale); + } + else if (filename.EndsWith(".obj", StringComparison.OrdinalIgnoreCase)) + { + OBJ_Loader.Load(filename, triangleMesh, scale); + } + else if (filename.EndsWith(".stl", StringComparison.OrdinalIgnoreCase)) + { + STL_Loader.Load(filename, triangleMesh, scale); + } + if (triangleMesh != null && triangleMesh.Triangles.Count() > 0) + { + triangleMesh.GenerateTabPaths(router.ToolDiameter / 2.0f); + triangleMesh.RefreshDisplayLists(); // The triangles will be static after this point - make sure they're correctly displayed. + } + e.Result = triangleMesh; + } + + + void IOpenGLDrawable.Draw() + { + try + { + foreach (var mesh in inProgressMeshes) + { + mesh.Draw(); + } + } + catch (Exception ex) + { + } + } + + void worker_LoadMeshCompleted(object sender, RunWorkerCompletedEventArgs e) + { + var mesh = e.Result as TriangleMeshGUI; + if (mesh != null && mesh.Triangles.Count() > 0) + { + drawing3D.AddObject(mesh); + foreach (var tab in mesh.Tabs) + { + drawing3D.AddObject(tab); + } + inProgressMeshes.RemoveAll(m => m == mesh); + } + } + + private void PermiterRoutsClick(object sender, EventArgs e) + { + foreach (Object o in drawing3D.GetObjects()) + { + if (o is TriangleMeshGUI) + { + var triangles = o as TriangleMeshGUI; + var routs = PathPlanner.PlanPaths(triangles, triangles.Tabs.ConvertAll(tab => tab as Tabs), router); + foreach (var rout in routs) + { + router.RoutPath(rout, false, triangles.Offset); + } + } + } + router.Complete(); + } + + private void boundaryCheckButton_Click(object sender, EventArgs e) + { + float xMin = -router.ToolDiameter; + float xMax = router.ToolDiameter; + float yMin = -router.ToolDiameter; + float yMax = router.ToolDiameter; + + foreach (Object o in drawing3D.GetObjects()) + { + if (o is TriangleMeshGUI) + { + var triangles = o as TriangleMeshGUI; + xMin = Math.Min(triangles.MinPoint.X - router.ToolDiameter + triangles.Offset.X, xMin); + xMax = Math.Max(triangles.MaxPoint.X + router.ToolDiameter + triangles.Offset.X, xMax); + yMin = Math.Min(triangles.MinPoint.Y - router.ToolDiameter + triangles.Offset.Y, yMin); + yMax = Math.Max(triangles.MaxPoint.Y + router.ToolDiameter + triangles.Offset.Y, yMax); + } + } + + LineStrip r = new LineStrip(); + r.Append(new Vector3(xMin, yMin, router.MoveHeight)); + r.Append(new Vector3(xMax, yMin, router.MoveHeight)); + r.Append(new Vector3(xMax, yMax, router.MoveHeight)); + r.Append(new Vector3(xMin, yMax, router.MoveHeight)); + r.Append(new Vector3(xMin, yMin, router.MoveHeight)); + router.RoutPath(r, false, Vector3.Zero); + router.Complete(); + } + + private void loadButton_Click(object sender, EventArgs e) + { + OpenFileDialog d = new OpenFileDialog(); + d.Filter = "3D Files |*.dae;*.obj;*.stl;*.nc;*.gcode"; + if (DialogResult.OK == d.ShowDialog()) + { + AddFile(d.FileName, loadObjectScale); + foreach (Object o in drawing3D.GetObjects()) + { + TriangleMesh mesh = o as TriangleMesh; + if (mesh != null) + { + router.MoveHeight = mesh.MaxPoint.Z + 0.025f; + this.propertyGrid.Refresh(); + } + } + } + } + + private void clearPathsButton_Click(object sender, EventArgs e) + { + router.ClearCommands(); + } + + private float loadObjectScale = 1.0f; + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + float targetScale = 0.0f; + float sourceScale = 0.0f; + + var match = new Regex(@"^(?\S+):(?\S+)").Match(comboBox1.Text); + + if (match.Success + && float.TryParse(match.Groups["source"].Value, out sourceScale) + && float.TryParse(match.Groups["target"].Value, out targetScale) + && targetScale != 0 && sourceScale != 0) + { + comboBox1.BackColor = SystemColors.Window; + openFileButton.Enabled = true; + loadObjectScale = targetScale / sourceScale; + } + else + { + comboBox1.BackColor = Color.LightPink; + openFileButton.Enabled = false; + } + } + + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PathCAM)); + this.button2 = new System.Windows.Forms.Button(); + this.propertyGrid = new System.Windows.Forms.PropertyGrid(); + this.saveGcodeButton = new System.Windows.Forms.Button(); + this.clearPathsButton = new System.Windows.Forms.Button(); + this.boundaryCheck = new System.Windows.Forms.Button(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.openFileButton = new System.Windows.Forms.Button(); + this.checkBox1 = new System.Windows.Forms.CheckBox(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.robotControl = new GUI.RobotControl(); + this.drawing3D = new GUI.Drawing3D(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // button2 + // + this.button2.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button2.Location = new System.Drawing.Point(12, 12); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(183, 23); + this.button2.TabIndex = 1; + this.button2.Text = "Add Perimeter Paths"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.PermiterRoutsClick); + // + // propertyGrid + // + this.propertyGrid.ImeMode = System.Windows.Forms.ImeMode.NoControl; + this.propertyGrid.Location = new System.Drawing.Point(12, 128); + this.propertyGrid.Name = "propertyGrid"; + this.propertyGrid.PropertySort = System.Windows.Forms.PropertySort.NoSort; + this.propertyGrid.Size = new System.Drawing.Size(183, 163); + this.propertyGrid.TabIndex = 5; + this.propertyGrid.ToolbarVisible = false; + // + // saveGcodeButton + // + this.saveGcodeButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.saveGcodeButton.Location = new System.Drawing.Point(12, 99); + this.saveGcodeButton.Name = "saveGcodeButton"; + this.saveGcodeButton.Size = new System.Drawing.Size(183, 23); + this.saveGcodeButton.TabIndex = 4; + this.saveGcodeButton.Text = "Save GCode"; + this.saveGcodeButton.UseVisualStyleBackColor = true; + this.saveGcodeButton.Click += new System.EventHandler(this.saveGcodeButton_Click); + // + // clearPathsButton + // + this.clearPathsButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.clearPathsButton.Location = new System.Drawing.Point(12, 70); + this.clearPathsButton.Name = "clearPathsButton"; + this.clearPathsButton.Size = new System.Drawing.Size(183, 23); + this.clearPathsButton.TabIndex = 3; + this.clearPathsButton.Text = "Clear Paths"; + this.clearPathsButton.UseVisualStyleBackColor = true; + this.clearPathsButton.Click += new System.EventHandler(this.clearPathsButton_Click); + // + // boundaryCheck + // + this.boundaryCheck.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.boundaryCheck.Location = new System.Drawing.Point(12, 41); + this.boundaryCheck.Name = "boundaryCheck"; + this.boundaryCheck.Size = new System.Drawing.Size(183, 23); + this.boundaryCheck.TabIndex = 2; + this.boundaryCheck.Text = "Boundary Check Paths"; + this.boundaryCheck.UseVisualStyleBackColor = true; + this.boundaryCheck.Click += new System.EventHandler(this.boundaryCheckButton_Click); + // + // comboBox1 + // + this.comboBox1.BackColor = System.Drawing.SystemColors.Window; + this.comboBox1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Items.AddRange(new object[] { + "1:1 (inches)", + "25.4:1 (millimeters)", + ".254:1 (meters)", + "1:12 (feet)"}); + this.comboBox1.Location = new System.Drawing.Point(87, 299); + this.comboBox1.Name = "comboBox1"; + this.comboBox1.Size = new System.Drawing.Size(107, 21); + this.comboBox1.TabIndex = 7; + this.comboBox1.Text = "1:1 (inches)"; + this.comboBox1.TextChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + // + // openFileButton + // + this.openFileButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.openFileButton.Location = new System.Drawing.Point(12, 297); + this.openFileButton.Name = "openFileButton"; + this.openFileButton.Size = new System.Drawing.Size(70, 23); + this.openFileButton.TabIndex = 6; + this.openFileButton.Text = "Open File"; + this.openFileButton.UseVisualStyleBackColor = true; + this.openFileButton.Click += new System.EventHandler(this.loadButton_Click); + // + // checkBox1 + // + this.checkBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.checkBox1.AutoSize = true; + this.checkBox1.Location = new System.Drawing.Point(0, 588); + this.checkBox1.Name = "checkBox1"; + this.checkBox1.Size = new System.Drawing.Size(15, 14); + this.checkBox1.TabIndex = 69; + this.checkBox1.UseVisualStyleBackColor = true; + this.checkBox1.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged); + // + // pictureBox1 + // + this.pictureBox1.BackColor = System.Drawing.SystemColors.ActiveBorder; + this.pictureBox1.Location = new System.Drawing.Point(86, 298); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(109, 23); + this.pictureBox1.TabIndex = 70; + this.pictureBox1.TabStop = false; + // + // robotControl + // + this.robotControl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.robotControl.BackColor = System.Drawing.Color.Transparent; + this.robotControl.Location = new System.Drawing.Point(0, 466); + this.robotControl.Name = "robotControl"; + this.robotControl.Size = new System.Drawing.Size(183, 136); + this.robotControl.TabIndex = 8; + this.robotControl.Visible = false; + // + // drawing3D + // + this.drawing3D.AllowDrop = true; + this.drawing3D.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.drawing3D.BackColor = System.Drawing.Color.Black; + this.drawing3D.ClearColor = System.Drawing.Color.Empty; + this.drawing3D.Location = new System.Drawing.Point(0, -1); + this.drawing3D.MinimumSize = new System.Drawing.Size(10, 10); + this.drawing3D.Name = "drawing3D"; + this.drawing3D.RightToLeft = System.Windows.Forms.RightToLeft.Yes; + this.drawing3D.Size = new System.Drawing.Size(986, 603); + this.drawing3D.TabIndex = 68; + this.drawing3D.VSync = false; + // + // PathCAM + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(986, 601); + this.Controls.Add(this.checkBox1); + this.Controls.Add(this.saveGcodeButton); + this.Controls.Add(this.button2); + this.Controls.Add(this.clearPathsButton); + this.Controls.Add(this.boundaryCheck); + this.Controls.Add(this.robotControl); + this.Controls.Add(this.openFileButton); + this.Controls.Add(this.propertyGrid); + this.Controls.Add(this.comboBox1); + this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.drawing3D); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(800, 600); + this.Name = "PathCAM"; + this.Text = "PathCAM - Toolpath generation software for CNC robots"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + private void saveGcodeButton_Click(object sender, EventArgs e) + { + var dialog = new SaveFileDialog(); + dialog.Filter = "GCode Files |*.nc;*.gcode"; + if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + string filename = dialog.FileName; + if (!filename.EndsWith(".nc", StringComparison.OrdinalIgnoreCase) && !filename.EndsWith(".gcode", StringComparison.OrdinalIgnoreCase)) + { + filename = filename + ".nc"; + } + GCodeLoader.ExportGCode(router.GetCommands(), filename); + } + } + + private void checkBox1_CheckedChanged(object sender, EventArgs e) + { + robotControl.Visible = checkBox1.Checked; + } + } +} diff --git a/GUI/PathCAM.resx b/GUI/PathCAM.resx new file mode 100644 index 0000000..35790c0 --- /dev/null +++ b/GUI/PathCAM.resx @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAICAAAAAAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAACAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP7+/v/8+/7/+/v9//f99//+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//v8 + /f/0+vf//v7+//7+/v/+/v7//v7+//3+/f/0+vb/+/v+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7/9v32//z8/v/7+/7//v7+//7+/v/+/v7/9v32//7+/v/8/P7/+/v+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//b99v/+/v7//v7+//z8/v/7+/7//v7+//b99v/+/v7//v7+//7+/v/8/P7/+/v+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/2/fb//v7+//7+/v/+/v7//v7+//z8/v/7+/7//f79//7+/v/+/v7//v7+//7+ + /v/8/P7/+/v+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7/9v32//7+/v/+/v7//v7+//7+/v/+/v7//v7+//z8/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/8/P7/+/r+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//f99//9/v3//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/9/P7/+/r+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/3/ff//f79//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/9/P7/+vr+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7/9/33//z+/P/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/9/f7/+vr+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//j9+P/8/vz//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/9/f7/+vn+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/4/fj//P78//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/9/f7/+vn+//7+/v/+/v7//v7+//7+/v/+/v7/+f35//v++//+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/9/f7/+vn+//7+/v/5+/z//v7+//n9+f/7/fv//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/9/f7/6/Hv/1+Bg/+9wdr/+/37//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+/+/57/9PnU//YWFj/0xM + hP+bm/7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/x+fH/Obk5/wyi + DP8+a1L/JibL/wEB/v+bm/7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7/9vv2/0O9 + Q/8EpwT/BagF/xqOTP8BAf7/DQz+/wEB/v+Njf7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//r9 + +v9Jv0n/A6cD/waoBv8isSL/5fXl/5mZ/v8BAf7/DQz+/wAA/v+Kiv7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/6/fr/VsNW/wKnAv8HqQf/Ha8d/9zy3P/+/v7//v7+/6Cg/v8EBP7/DAz+/wAA/v+Bgf7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//f79/2HHYf8CpwL/B6kH/xSsFP/Z8dn//v7+//7+/v/+/v7//v7+/6mp/v8DA/7/DAz+/wEB + /v94eP7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v9qymr/AaYB/wepB/8UrBT/0e7R//7+/v/+/v7//v7+//7+/v/+/v7//v7+/62t + /v8ICP7/Cwv+/wEB/v90dP7//f3+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7/eM94/wGmAf8Iqgj/DaoN/8bqxv/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+/7i4/v8ICP7/Cwv+/wIC/v9mZv7//f3+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+/4bUhv8BpgH/CKoI/wioCP/E6sT//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+/7q6/v8NDf7/Cgr+/wIC/v9mZv7//f3+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v+O147/AKUA/wmqCf8IqAj/uOa4//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+/8XF/v8NDf7/Cgr+/wMD/v9WVv7/+vr+//7+ + /v/+/v7//v7+//n9+f/6+f7//v7+//7+/v/+/v7/m9yb/wGmAf8Kqgr/A6cD/7DjsP/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+/8bG/v8UFP7/CQn+/wMD + /v9WVv7/+vr+//7+/v/5/fn/+/77//39/v/z9/f//v7+/6rhqv8BpgH/CaoJ/wOmA/+p4Kn//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+/9HR + /v8UFP7/CQn+/wQE/v9NTf7/yc35/7K0+//+/v7//v7+/3LNc/9lxWr/BKcE/wmqCf8BpgH/mduZ//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+/9HR/v8cHP7/CAj+/woN+v8GCfr/XFz+//7+/v/+/v7/Nbg1/wClAP8Xqxz/AKYB/5PZ + k//+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+/9vb/v8iJfr/DA/6/wQE/v8YGP7//Pz+//P68/8FpwX/CKoI/wGm + Af9BuEb//f3+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7/3uH6/xwf+v8AAP7/CAj+/wUF/v/Pz/7/vui+/wiq + CP8BpgH/Fa0V/1zGXP/d7+L//f3+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//r9+v/6/fr/1dX+/5WV/v9bW/7/JCP+/4+P + /v+v5K//gNKA/8nsyf/8/fz//v7+//7+/v/6+f7//f3+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/6/fr/+v36//7+/v/+/v7//v7+//7+ + /v/+/v7/8O/+//z+/P/+/v7//v7+//7+/v/+/v7//v7+//7+/v/7+v7//fz+//7+/v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v/+/v7/+v36//r9+v/+/v7//v7+//7+ + /v/+/v7//v7+//7+/v/+/v7//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + + + \ No newline at end of file diff --git a/GUI/Polyhedra.cs b/GUI/Polyhedra.cs new file mode 100644 index 0000000..5e0d95c --- /dev/null +++ b/GUI/Polyhedra.cs @@ -0,0 +1,143 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace GUI +{ + public class Polyhedra + { + private static Vector3 GetPerpendicular(Vector3 normal) + { + Vector3 c = new Vector3(0, 1, 0); + if (Math.Abs(normal.Y) > 0.5) + { + c = new Vector3(1, 0, 0); + } + return c; + } + + public static void DrawCylinderWireMesh(Vector3 from, Vector3 to, float radius, int sides = 24) + { + Vector3 direction = to - from; + direction.Normalize(); + Vector3 c = GetPerpendicular(direction); + Vector3 perp1 = Vector3.Cross(direction, c); + perp1.Normalize(); + Vector3 perp2 = Vector3.Cross(perp1, direction); + + GL.Disable(EnableCap.Lighting); + GL.Begin(PrimitiveType.Lines); + for (float theta = 0; theta < OpenTK.MathHelper.TwoPi; theta += OpenTK.MathHelper.TwoPi / sides) + { + float thetaNext = theta + OpenTK.MathHelper.TwoPi / sides; + Vector3 a = ((float)Math.Sin(theta) * perp1 + (float)Math.Cos(theta) * perp2) * radius; + Vector3 b = ((float)Math.Sin(thetaNext) * perp1 + (float)Math.Cos(thetaNext) * perp2) * radius; + + GL.Vertex3(to); + GL.Vertex3(to + a); + GL.Vertex3(from); + GL.Vertex3(from + a); + + GL.Vertex3(to + b); + GL.Vertex3(to + a); + //GL.Vertex3(to + b); + + GL.Vertex3(from + b); + GL.Vertex3(from + a); + //GL.Vertex3(from + b); + + GL.Vertex3(a + from); + GL.Vertex3(a + to); + } + GL.End(); + GL.Enable(EnableCap.Lighting); + } + + public static void DrawCylinder(Vector3 from, Vector3 to, float radius, int sides = 24) + { + Vector3 direction = to - from; + direction.Normalize(); + Vector3 c = GetPerpendicular(direction); + Vector3 perp1 = Vector3.Cross(direction, c); + perp1.Normalize(); + Vector3 perp2 = Vector3.Cross(perp1, direction); + + GL.Begin(PrimitiveType.Triangles); + for (float theta = 0; theta < OpenTK.MathHelper.TwoPi; theta += OpenTK.MathHelper.TwoPi / sides) + { + float thetaNext = theta + OpenTK.MathHelper.TwoPi / sides; + Vector3 a = ((float)Math.Sin(theta) * perp1 + (float)Math.Cos(theta) * perp2) * radius; + Vector3 b = ((float)Math.Sin(thetaNext) * perp1 + (float)Math.Cos(thetaNext) * perp2) * radius; + GL.Normal3(direction); + GL.Vertex3(to); + GL.Vertex3(to + a); + GL.Vertex3(to + b); + + GL.Normal3(-direction); + GL.Vertex3(from); + GL.Vertex3(from + a); + GL.Vertex3(from + b); + + GL.Normal3(a); + GL.Vertex3(a + from); + GL.Normal3(b); + GL.Vertex3(b + from); + GL.Vertex3(b + to); + + GL.Normal3(a); + GL.Vertex3(a + from); + GL.Vertex3(a + to); + GL.Normal3(b); + GL.Vertex3(b + to); + + } + GL.End(); + } + + public static void DrawCone(Vector3 from, Vector3 to, float radius, int sides = 24) + { + Vector3 direction = to - from; + direction.Normalize(); + Vector3 c = GetPerpendicular(direction); + Vector3 perp1 = Vector3.Cross(direction, c); + perp1.Normalize(); + Vector3 perp2 = Vector3.Cross(perp1, direction); + + + GL.Begin(PrimitiveType.TriangleFan); + GL.Normal3(direction); + GL.Vertex3(to); + for (float theta = 0; theta < OpenTK.MathHelper.TwoPi; theta += OpenTK.MathHelper.TwoPi/sides) + { + float x = (float)Math.Sin(theta); + float y = (float)Math.Cos(theta); + Vector3 tail = (perp1 * x + perp2 * y) * radius + from; + GL.Normal3(perp1 * x + perp2 * y); + GL.Vertex3(tail); + } + GL.End(); + } + } +} diff --git a/GUI/Program.cs b/GUI/Program.cs new file mode 100644 index 0000000..1106cec --- /dev/null +++ b/GUI/Program.cs @@ -0,0 +1,40 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace GUI +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new PathCAM()); + } + } +} diff --git a/GUI/Properties/AssemblyInfo.cs b/GUI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0f2847d --- /dev/null +++ b/GUI/Properties/AssemblyInfo.cs @@ -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("GUI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GUI")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("ad935126-6e14-4d00-9e21-e3c4745a49e2")] + +// 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")] diff --git a/GUI/Properties/Resources.Designer.cs b/GUI/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6e11490 --- /dev/null +++ b/GUI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18047 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GUI.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GUI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/GUI/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GUI/Properties/Settings.Designer.cs b/GUI/Properties/Settings.Designer.cs new file mode 100644 index 0000000..3f7c2df --- /dev/null +++ b/GUI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18047 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GUI.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/GUI/Properties/Settings.settings b/GUI/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/GUI/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/GUI/RobotControl.Designer.cs b/GUI/RobotControl.Designer.cs new file mode 100644 index 0000000..63dded5 --- /dev/null +++ b/GUI/RobotControl.Designer.cs @@ -0,0 +1,163 @@ +namespace GUI +{ + partial class RobotControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.steppersEnabledBox = new System.Windows.Forms.CheckBox(); + this.cancelButton = new System.Windows.Forms.Button(); + this.zbox = new System.Windows.Forms.TextBox(); + this.pause_resume_button = new System.Windows.Forms.Button(); + this.zGo = new System.Windows.Forms.Button(); + this.runButton = new System.Windows.Forms.Button(); + this.button4 = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // steppersEnabledBox + // + this.steppersEnabledBox.AutoCheck = false; + this.steppersEnabledBox.AutoSize = true; + this.steppersEnabledBox.Enabled = false; + this.steppersEnabledBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.steppersEnabledBox.Location = new System.Drawing.Point(6, 103); + this.steppersEnabledBox.Name = "steppersEnabledBox"; + this.steppersEnabledBox.Size = new System.Drawing.Size(107, 17); + this.steppersEnabledBox.TabIndex = 3; + this.steppersEnabledBox.Text = "Steppers Enabled"; + this.steppersEnabledBox.UseVisualStyleBackColor = true; + this.steppersEnabledBox.Click += new System.EventHandler(this.steppersEnabledBox_Click); + // + // cancelButton + // + this.cancelButton.Enabled = false; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.cancelButton.Location = new System.Drawing.Point(87, 45); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 2; + this.cancelButton.Text = "Clear"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // zbox + // + this.zbox.Enabled = false; + this.zbox.Location = new System.Drawing.Point(65, 76); + this.zbox.Name = "zbox"; + this.zbox.Size = new System.Drawing.Size(97, 20); + this.zbox.TabIndex = 78; + this.zbox.Text = "0"; + // + // pause_resume_button + // + this.pause_resume_button.Enabled = false; + this.pause_resume_button.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.pause_resume_button.Location = new System.Drawing.Point(6, 45); + this.pause_resume_button.Name = "pause_resume_button"; + this.pause_resume_button.Size = new System.Drawing.Size(75, 23); + this.pause_resume_button.TabIndex = 1; + this.pause_resume_button.Text = "Pause"; + this.pause_resume_button.UseVisualStyleBackColor = true; + this.pause_resume_button.Click += new System.EventHandler(this.pause_resume_button_Click); + // + // zGo + // + this.zGo.Enabled = false; + this.zGo.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.zGo.Location = new System.Drawing.Point(6, 74); + this.zGo.Name = "zGo"; + this.zGo.Size = new System.Drawing.Size(53, 23); + this.zGo.TabIndex = 77; + this.zGo.Text = "ZGo"; + this.zGo.UseVisualStyleBackColor = true; + this.zGo.Click += new System.EventHandler(this.zGo_Click); + // + // runButton + // + this.runButton.Enabled = false; + this.runButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.runButton.Location = new System.Drawing.Point(87, 16); + this.runButton.Name = "runButton"; + this.runButton.Size = new System.Drawing.Size(75, 23); + this.runButton.TabIndex = 0; + this.runButton.Text = "Run"; + this.runButton.UseVisualStyleBackColor = true; + this.runButton.Click += new System.EventHandler(this.runButton_Click); + // + // button4 + // + this.button4.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button4.Location = new System.Drawing.Point(6, 16); + this.button4.Name = "button4"; + this.button4.Size = new System.Drawing.Size(75, 23); + this.button4.TabIndex = 79; + this.button4.Text = "Com Port"; + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(96, 13); + this.label1.TabIndex = 80; + this.label1.Text = "--------ROBOT!--------"; + // + // RobotControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.label1); + this.Controls.Add(this.button4); + this.Controls.Add(this.runButton); + this.Controls.Add(this.zGo); + this.Controls.Add(this.steppersEnabledBox); + this.Controls.Add(this.pause_resume_button); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.zbox); + this.Name = "RobotControl"; + this.Size = new System.Drawing.Size(167, 124); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.CheckBox steppersEnabledBox; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.TextBox zbox; + private System.Windows.Forms.Button pause_resume_button; + private System.Windows.Forms.Button zGo; + private System.Windows.Forms.Button runButton; + private System.Windows.Forms.Button button4; + private System.Windows.Forms.Label label1; + + } +} diff --git a/GUI/RobotControl.cs b/GUI/RobotControl.cs new file mode 100644 index 0000000..3db0640 --- /dev/null +++ b/GUI/RobotControl.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Robot; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Serial; +using Router; +using Commands; + +namespace GUI +{ + public partial class RobotControl : UserControl, IOpenGLDrawable + { + private Router.Router router; + private Robot.Robot robot; + private SerialPortWrapper serial; + private COMPortForm comPortForm = null; + + public RobotControl() + { + InitializeComponent(); + serial = new SerialPortWrapper(); + } + + public void AssignRouter(Router.Router router) + { + this.router = router; + this.robot = new Robot.Robot(serial); + this.robot.onRobotStatusChange += new EventHandler(RobotStatusUpdate); + } + + void RobotStatusUpdate(object o, EventArgs e) + { + if (this.InvokeRequired && !this.Disposing) + { + try + { + this.Invoke(new EventHandler(RobotStatusUpdate), new object[] { o, e }); + } + catch (ObjectDisposedException) + { + } + } + else + { + StatusCommand status = o as StatusCommand; + if (status != null) + { + this.runButton.Enabled = true; + this.steppersEnabledBox.Enabled = true; + this.zbox.Enabled = true; + this.zGo.Enabled = true; + + this.steppersEnabledBox.Checked = status.SteppersEnabled; + if (status.Pausing) + { + pause_resume_button.Text = "Pausing..."; + pause_resume_button.Enabled = false; + } + else + { + if (status.Paused) + { + if (pause_resume_button.Text != "Resume") + { + cancelButton.Enabled = true; + pause_resume_button.Text = "Resume"; + } + } + else + { + if (pause_resume_button.Text != "Pause") + { + pause_resume_button.Text = "Pause"; + } + } + pause_resume_button.Enabled = true; + } + } + } + } + + void IOpenGLDrawable.Draw() + { + // Draw the tool location as a cone + Vector3 position = robot.GetPosition(); + GL.Color3(Color.Silver); + Polyhedra.DrawCone(position + new Vector3(0, 0, router.ToolDiameter), position, router.ToolDiameter / 2.0f); + + + + + //// Draw the past positions & velocity graph + //float lastTime = 0; + //Vector3 lastPos = new Vector3(0, 0, 0); + //float lastVel = 0; + //bool lastIsGood = false; + //GL.Disable(EnableCap.Lighting); + //lock (previousPoints) + //{ + // Vector3 lastpoint = new Vector3(0, 0, 0); + // for (int i = 0; i < previousPoints.Count(); i++) + // { + // PreviousPoint point = previousPoints[i]; + // float age_delta = point.createTime - lastTime; + // float time = age_delta / 1000.0f; // Age is microseconds, time is seconds + // float pos_delta = (point.location - lastPos).Length; + // float vel = pos_delta / time; // Inches per second + // Vector3 atpoint = new Vector3(point.location.X * 1000, point.location.Y * 1000, point.location.Z * 1000); + // if (lastIsGood) + // { + // GL.LineWidth(1); + // GL.Begin(PrimitiveType.Lines); + // GL.Color3(Color.LightGray); + // for (int j = 0; j < 5; j++) + // { + // GL.Vertex3(lastpoint + new Vector3(0, 0, j * 10)); + // GL.Vertex3(lastpoint + new Vector3(0, 0, j * 10 + 10)); + // + // GL.Vertex3(lastpoint + new Vector3(0, 0, j * 10)); + // GL.Vertex3(atpoint + new Vector3(0, 0, j * 10)); + // } + // GL.End(); + // GL.LineWidth(2); + // GL.Begin(PrimitiveType.Lines); + // GL.Color3(Color.Orange); + // GL.Vertex3(lastpoint + new Vector3(0, 0, lastVel * lastVel * 200)); + // GL.Vertex3(atpoint + new Vector3(0, 0, vel * vel * 200)); + // GL.End(); + // } + // lastVel = vel; + // lastpoint = atpoint; + // + // lastPos = point.location; + // lastTime = point.createTime; + // lastIsGood = true; + // } + //} + //GL.Enable(EnableCap.Lighting); + //GL.LineWidth(1); + + } + + private void pause_resume_button_Click(object sender, EventArgs e) + { + if (pause_resume_button.Text == "Pause") + { + robot.SendPauseCommand(); + } + else if (pause_resume_button.Text == "Resume") + { + robot.SendResumeCommand(); + cancelButton.Enabled = false; + } + pause_resume_button.Enabled = false; + } + + private void cancelButton_Click(object sender, EventArgs e) + { + robot.CancelPendingCommands(); + Vector3 position = robot.GetPosition(); + robot.AddCommand(new MoveTool(new Vector3(position.X, position.Y, router.MoveHeight), router.MoveSpeed)); + robot.AddCommand(new MoveTool(new Vector3(0, 0, router.MoveHeight), router.MoveSpeed)); + robot.SendResumeCommand(); + this.pause_resume_button.Enabled = false; + this.cancelButton.Enabled = false; + } + + private void steppersEnabledBox_Click(object sender, EventArgs e) + { + if (steppersEnabledBox.Checked) + { + robot.DisableMotors(); + } + else + { + robot.EnableMotors(); + } + } + + private void runButton_Click(object sender, EventArgs e) + { + foreach (ICommand command in router.GetCommands()) + { + robot.AddCommand(command); + } + } + + private void zGo_Click(object sender, EventArgs e) + { + float f = float.Parse(zbox.Text); + MoveTool move = new MoveTool(new Vector3(0, 0, f), router.MoveSpeed); + robot.AddCommand(move); + } + + private void button4_Click(object sender, EventArgs e) + { + if (comPortForm == null || comPortForm.IsDisposed) + { + comPortForm = new COMPortForm(serial); + } + + if (!comPortForm.Visible) + { + comPortForm.Show(null); + } + else + { + comPortForm.Focus(); + } + } + + //private List previousPoints = new List(); + //public class PreviousPoint + //{ + // public PreviousPoint(float time, Vector3 location) + // { + // this.createTime = time; + // this.location = location; + // } + // public float createTime; + // public Vector3 location; + //} + + + //void RouterPositionUpdate(object o, EventArgs e) + //{ + // StatusCommand status = o as StatusCommand; + // if (status != null) + // { + // Vector3 position = status.CurrentPosition; + // float time = status.time; + // float distance = (lastPosition - position).Length; + // + // if ((lastPosition - position).Length > 0.0001f) + // { + // lock (previousPoints) + // { + // //Console.WriteLine("{0},{1}", time, distance); + // while (previousPoints.Count > 1000) + // { + // previousPoints.RemoveAt(0); + // } + // previousPoints.Add(new PreviousPoint(time, position)); + // lastPosition = position; + // } + // } + // } + //} + + } +} diff --git a/GUI/RobotControl.resx b/GUI/RobotControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/GUI/RobotControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GUI/RobotGUI.cs b/GUI/RobotGUI.cs new file mode 100644 index 0000000..f0a0699 --- /dev/null +++ b/GUI/RobotGUI.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Robot; +using Geometry; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Drawing; +using Serial; + +namespace GUI +{ + public class RobotGUI : Robot.Robot, IOpenGLDrawable + { + private Router.Router router; + + public RobotGUI(SerialPortWrapper serial, Router.Router router) : base (serial) + { + this.router = router; + } + + void IOpenGLDrawable.Draw() + { + // Draw the tool location as a cone + Vector3 position = GetPosition(); + GL.Color3(Color.Silver); + Polyhedra.DrawCone(position + new Vector3(0, 0, router.ToolDiameter), position, router.ToolDiameter / 2.0f); + + + + + //// Draw the past positions & velocity graph + //float lastTime = 0; + //Vector3 lastPos = new Vector3(0, 0, 0); + //float lastVel = 0; + //bool lastIsGood = false; + //GL.Disable(EnableCap.Lighting); + //lock (previousPoints) + //{ + // Vector3 lastpoint = new Vector3(0, 0, 0); + // for (int i = 0; i < previousPoints.Count(); i++) + // { + // PreviousPoint point = previousPoints[i]; + // float age_delta = point.createTime - lastTime; + // float time = age_delta / 1000.0f; // Age is microseconds, time is seconds + // float pos_delta = (point.location - lastPos).Length; + // float vel = pos_delta / time; // Inches per second + // Vector3 atpoint = new Vector3(point.location.X * 1000, point.location.Y * 1000, point.location.Z * 1000); + // if (lastIsGood) + // { + // GL.LineWidth(1); + // GL.Begin(PrimitiveType.Lines); + // GL.Color3(Color.LightGray); + // for (int j = 0; j < 5; j++) + // { + // GL.Vertex3(lastpoint + new Vector3(0, 0, j * 10)); + // GL.Vertex3(lastpoint + new Vector3(0, 0, j * 10 + 10)); + // + // GL.Vertex3(lastpoint + new Vector3(0, 0, j * 10)); + // GL.Vertex3(atpoint + new Vector3(0, 0, j * 10)); + // } + // GL.End(); + // GL.LineWidth(2); + // GL.Begin(PrimitiveType.Lines); + // GL.Color3(Color.Orange); + // GL.Vertex3(lastpoint + new Vector3(0, 0, lastVel * lastVel * 200)); + // GL.Vertex3(atpoint + new Vector3(0, 0, vel * vel * 200)); + // GL.End(); + // } + // lastVel = vel; + // lastpoint = atpoint; + // + // lastPos = point.location; + // lastTime = point.createTime; + // lastIsGood = true; + // } + //} + //GL.Enable(EnableCap.Lighting); + //GL.LineWidth(1); + + } + + + //private List previousPoints = new List(); + //public class PreviousPoint + //{ + // public PreviousPoint(float time, Vector3 location) + // { + // this.createTime = time; + // this.location = location; + // } + // public float createTime; + // public Vector3 location; + //} + + + //void RouterPositionUpdate(object o, EventArgs e) + //{ + // StatusCommand status = o as StatusCommand; + // if (status != null) + // { + // Vector3 position = status.CurrentPosition; + // float time = status.time; + // float distance = (lastPosition - position).Length; + // + // if ((lastPosition - position).Length > 0.0001f) + // { + // lock (previousPoints) + // { + // //Console.WriteLine("{0},{1}", time, distance); + // while (previousPoints.Count > 1000) + // { + // previousPoints.RemoveAt(0); + // } + // previousPoints.Add(new PreviousPoint(time, position)); + // lastPosition = position; + // } + // } + // } + //} + + } +} diff --git a/GUI/RouterGUI.cs b/GUI/RouterGUI.cs new file mode 100644 index 0000000..cb3a5a1 --- /dev/null +++ b/GUI/RouterGUI.cs @@ -0,0 +1,134 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Router; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Drawing; +using Robot; +using Commands; +using System.ComponentModel; + + +namespace GUI +{ + public class RouterGUI : Router.Router, IOpenGLDrawable + { + + public RouterGUI() : base() + { + } + + /// + /// Accessors for the property grid + /// + [DisplayName("Last Pass Height")] + [Description("Height of the last pass in inches")] + new public float LastPassHeight + { + get { return base.LastPassHeight; } + set { base.LastPassHeight = value; } + } + + [DisplayName("Tool Diameter")] + [Description("Tool Diameter in inches")] + new public float ToolDiameter + { + get { return base.ToolDiameter; } + set { base.ToolDiameter = value; } + } + + [DisplayName("Routing Speed")] + [Description("Rout Speed (inches per minute)")] + new public float RoutSpeed + { + get { return base.RoutSpeed; } + set { base.RoutSpeed = value; } + } + + [DisplayName("Moving Speed")] + [Description("Moving speed (inches per minute)")] + new public float MoveSpeed + { + get { return base.MoveSpeed; } + set { base.MoveSpeed = value; } + } + + [DisplayName("Move Height")] + [Description("Safe travel height")] + new public float MoveHeight + { + get { return base.MoveHeight; } + set { base.MoveHeight = value; } + } + + [DisplayName("Max Cut Depth")] + [Description("Maximum Cut Depth")] + new public float MaxCutDepth + { + get { return base.MaxCutDepth; } + set { base.MaxCutDepth = value; } + } + + void IOpenGLDrawable.Draw() + { + try + { + var commands = base.GetCommands(); + // Draw cut paths + GL.Disable(EnableCap.Lighting); + GL.Color3(Color.Blue); + GL.Begin(PrimitiveType.LineStrip); + for (int i = 0; i < commands.Count(); i++) + { + if (commands[i] is MoveTool) + { + MoveTool m = commands[i] as MoveTool; + Vector3 finalPosition = m.Target; + GL.Vertex3(finalPosition); + } + } + GL.End(); + + GL.PointSize(2); + GL.Color3(Color.Red); + GL.Begin(PrimitiveType.Points); + for (int i = 0; i < commands.Count(); i++) + { + if (commands[i] is MoveTool) + { + MoveTool m = commands[i] as MoveTool; + Vector3 finalPosition = m.Target; + GL.Vertex3(finalPosition); + } + } + GL.End(); + GL.PointSize(1); + GL.Enable(EnableCap.Lighting); + } + catch (Exception ex) + { + } + } + } +} diff --git a/GUI/RouterUI.Designer.cs b/GUI/RouterUI.Designer.cs new file mode 100644 index 0000000..c7bee15 --- /dev/null +++ b/GUI/RouterUI.Designer.cs @@ -0,0 +1,25 @@ +namespace GUI +{ + partial class PathCAM + { + private System.Windows.Forms.Button button2; + + #region Windows Form Designer generated code + + + #endregion + + private Drawing3D drawing3D; + private System.Windows.Forms.PropertyGrid propertyGrid; + private System.Windows.Forms.Button boundaryCheck; + private System.Windows.Forms.Button openFileButton; + private RobotControl robotControl; + private System.Windows.Forms.Button clearPathsButton; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Button saveGcodeButton; + private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.PictureBox pictureBox1; + + } +} + diff --git a/GUI/TabsGUI.cs b/GUI/TabsGUI.cs new file mode 100644 index 0000000..b3dadfa --- /dev/null +++ b/GUI/TabsGUI.cs @@ -0,0 +1,245 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Geometry; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Drawing; +using Router.Paths; + +namespace GUI +{ + public class TabsGUI : Tabs, IOpenGLDrawable, IClickable3D + { + private Slice drawSlice; + protected Vector3 locationOffset = Vector3.Zero; + private bool hovered = false; + Vector3 hoveredPoint = Vector3.Zero; + int selectedTabIndex = -1; + int drawSliceDisplayList = -1; + int obliterateIndicateDisplayList = -1; + + public void Draw() + { + GL.PushMatrix(); + GL.Translate(locationOffset); + + if (mouseHovering) + { + var ticks = DateTime.Now.Ticks; + int alpha = (int)(100 * (Math.Sin(((double)(ticks / 10000))/300.0d) + 1)); + + Vector3 location = hoveredPoint; + GL.Color4(Color.FromArgb(alpha, Color.Green)); + if (selectedTabIndex >= 0) + { + location = tabLocations[selectedTabIndex]; + GL.Color3(Color.Blue); + } + + Polyhedra.DrawCylinderWireMesh(location, location + new Vector3(0, 0, tabHeight), this.tabRadius); + } + + GL.Color3(Color.Orange); + if (drawSliceDisplayList > 0) + { + GL.CallList(drawSliceDisplayList); + } + else + { + drawSliceDisplayList = GL.GenLists(1); + GL.NewList(drawSliceDisplayList, ListMode.CompileAndExecute); + GL.Begin(PrimitiveType.Triangles); + GL.Normal3(drawSlice.Plane.Normal); + foreach (var triangle in drawSlice.Triangles()) + { + foreach (var point in triangle.Vertices) + { + GL.Vertex3(point); + } + } + GL.End(); + GL.EndList(); + } + + if (tabLocations.Count == 0) + { + GL.Color3(Color.DarkRed); + if (obliterateIndicateDisplayList > 0) + { + GL.CallList(obliterateIndicateDisplayList); + } + else + { + Slice s = new Slice(Boundary); + s.Subtract(drawSlice); + obliterateIndicateDisplayList = GL.GenLists(1); + GL.NewList(obliterateIndicateDisplayList, ListMode.CompileAndExecute); + GL.Begin(PrimitiveType.Triangles); + GL.Normal3(s.Plane.Normal); + foreach (var triangle in s.Triangles()) + { + foreach (var point in triangle.Vertices) + { + GL.Vertex3(point); + } + } + GL.End(); + GL.EndList(); + } + } + + GL.Color3(Color.DarkOrange); + int i = 0; + foreach (var tab in tabLocations) + { + if (!selectedTabDraggedOff || i != selectedTabIndex) + { + Polyhedra.DrawCylinder(tab + new Vector3(0, 0, .001f), tab + new Vector3(0, 0, tabHeight), this.tabRadius); + } + i++; + } + GL.PopMatrix(); + } + + public TabsGUI(LineStrip boundary, float toolRadius, bool inside = false) : base(boundary, toolRadius, inside) + { + drawSlice = new Slice(this.TabPath, toolRadius * 2.0f, new Plane(Vector3.UnitZ, Vector3.Zero), true); + } + + public Vector3 Offset + { + get { return locationOffset; } + set { locationOffset = value; } + } + + #region IClickable3D + + void IClickable3D.MouseDown(Ray pointer) + { + float distance = drawSlice.Plane.Distance(pointer); + Vector3 mousePoint = pointer.Start + pointer.Direction * distance - locationOffset; + if (selectedTabIndex < 0) + { + this.tabLocations.Add(hoveredPoint); + selectedTabIndex = tabLocations.Count - 1; + } + mouseOffset = tabLocations[selectedTabIndex] - mousePoint; + mouseHovering = true; + } + + void IClickable3D.MouseUp(Ray pointer) + { + if (selectedTabDraggedOff && selectedTabIndex >= 0) + { + tabLocations.RemoveAt(selectedTabIndex); + } + selectedTabIndex = -1; + } + + bool mouseHovering = false; + float IClickable3D.DistanceToObject(Ray pointer) + { + mouseHovering = false; + float distance = drawSlice.Plane.Distance(pointer); + Vector3 mousePoint = pointer.Start + pointer.Direction * distance - locationOffset; + hovered = false; + float closestPointDistance = float.PositiveInfinity; + Vector3 closestPoint = Vector3.Zero; + if (distance > 0) + { + selectedTabIndex = -1; + float minDistanceToTab = tabRadius; + for (int i = 0; i < tabLocations.Count; i++) + { + float d = (mousePoint - tabLocations[i]).Length; + if (d < minDistanceToTab) + { + selectedTabIndex = i; + minDistanceToTab = d; + hovered = true; + } + } + if (selectedTabIndex < 0) + { + foreach (var segment in TabPath.Segments(LineStrip.Type.Closed)) + { + Vector3 test = segment.ClosestPoint(mousePoint); + float d = (mousePoint - test).Length; + if (d < closestPointDistance) + { + closestPoint = test; + closestPointDistance = d; + } + } + if (closestPointDistance < toolRadius) + { + hovered = true; + hoveredPoint = closestPoint; + } + } + } + + if (!hovered) + { + return float.PositiveInfinity; + } + return distance; + } + + + void IClickable3D.MouseHover() + { + mouseHovering = true; + selectedTabDraggedOff = false; + } + + bool selectedTabDraggedOff = false; + Vector3 mouseOffset = Vector3.Zero; + void IClickable3D.MouseMove(Ray pointer) + { + float distance = drawSlice.Plane.Distance(pointer); + Vector3 mousePoint = pointer.Start + pointer.Direction * distance + mouseOffset - locationOffset; + + selectedTabDraggedOff = true; + float closestPointDistance = tabRadius; + Vector3 closestPoint = mousePoint; + foreach (var segment in TabPath.Segments(LineStrip.Type.Closed)) + { + Vector3 test = segment.ClosestPoint(mousePoint); + float d = (mousePoint - test).Length; + if (d < closestPointDistance) + { + selectedTabDraggedOff = false; + closestPoint = test; + closestPointDistance = d; + } + } + + tabLocations[selectedTabIndex] = closestPoint; + } + + #endregion + + } +} diff --git a/GUI/TriangleMeshGUI.cs b/GUI/TriangleMeshGUI.cs new file mode 100644 index 0000000..29ac332 --- /dev/null +++ b/GUI/TriangleMeshGUI.cs @@ -0,0 +1,365 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Geometry; +using OpenTK.Graphics.OpenGL; +using OpenTK; +using System.Drawing; +using Router.Paths; + +namespace GUI +{ + class TriangleMeshGUI : TriangleMesh, IOpenGLDrawable, IClickable3D + { + private bool isPointedAt = false; + private Vector3 mouseHoverPoint = Vector3.Zero; + private List tabs = new List(); + private Vector3 offset = Vector3.Zero; + + public TriangleMeshGUI() : base() + { + } + + public List Tabs + { + get { return tabs; } + } + + public Vector3 Offset + { + get { return offset; } + set + { + Vector3 newOffset = value; + // Snap to 1/4 inch locations + //newOffset.X = (float)Math.Round(newOffset.X * 4.0f) / 4.0f; + //newOffset.Y = (float)Math.Round(newOffset.Y * 4.0f) / 4.0f; + //newOffset.Z = (float)Math.Round(newOffset.Z * 4.0f) / 4.0f; + offset = newOffset; + foreach (var tab in tabs) + { + tab.Offset = offset; + } + } + } + + public void GenerateTabPaths(float toolRadius) + { + tabs.Clear(); + try + { + Slice s = new Slice(this, new Plane(Vector3.UnitZ, new Vector3(0, 0, MinPoint.Z))); + foreach (var line in s.GetLines(Slice.LineType.Outside)) + { + tabs.Add(new TabsGUI(line, toolRadius, false)); + } + foreach (var line in s.GetLines(Slice.LineType.Hole)) + { + tabs.Add(new TabsGUI(line, toolRadius, true)); + } + } + catch (Exception ex) + { + } + this.Offset = offset; // Force the offset update in the tabs + } + + int lastNumTriangles = -1; + int triangleDisplayList = -1; + int lineDisplayList = -1; + int badLineDisplayList = -1; + + public void RefreshDisplayLists() + { + lastNumTriangles = -1; + } + + public bool UseDisplayLists = true; + + public void Draw() + { + GL.PushMatrix(); + GL.Translate(offset); + + Color triangleColor = Color.Green; + Color lineColor = Color.Green; + Color badLineColor = Color.White; + + if (hovered) + { + lineColor = Color.LightBlue; + } + + bool useDisplayLists = UseDisplayLists; + + // Draw the point pointed to by the mouse + //GL.Color3(Color.Blue); + //GL.PointSize(2); + //GL.Begin(PrimitiveType.Points); + //GL.Vertex3(mousePoint); + //GL.End(); + //GL.PointSize(1); + //GL.Color3(Color.LightGreen); + + // Draw the slice at the mouse point + //GL.Disable(EnableCap.Lighting); + //GL.Color3(Color.White); + //Slice s = new Slice(this, new Plane(Vector3.UnitZ, mousePoint)); + //foreach (var line in s.GetLines(Slice.LineType.All)) + //{ + // GL.Begin(PrimitiveType.LineLoop); + // foreach (var point in line.Vertices) + // { + // GL.Vertex3(point); + // } + // GL.End(); + //} + //GL.Enable(EnableCap.Lighting); + + // Draw the triangles & edges + if (TriangleCount != lastNumTriangles) + { + if (triangleDisplayList > 0) // triangleDisplayList is used as the sentinel + { + GL.DeleteLists(triangleDisplayList, 1); + GL.DeleteLists(lineDisplayList, 1); + GL.DeleteLists(badLineDisplayList, 1); + triangleDisplayList = -1; + } + lastNumTriangles = TriangleCount; + } + + if (triangleDisplayList >= 0) + { + GL.Color3(triangleColor); + GL.CallList(triangleDisplayList); + + GL.Disable(EnableCap.Lighting); + GL.Color3(lineColor); + GL.CallList(lineDisplayList); + + GL.Color3(badLineColor); + GL.CallList(badLineDisplayList); + GL.Enable(EnableCap.Lighting); + } + else + { + if (useDisplayLists) + { + triangleDisplayList = GL.GenLists(1); + lineDisplayList = GL.GenLists(1); + badLineDisplayList = GL.GenLists(1); + } + + // Triangles + GL.Color3(triangleColor); + if (useDisplayLists) + { + GL.NewList(triangleDisplayList, ListMode.CompileAndExecute); + } + GL.Begin(PrimitiveType.Triangles); + foreach (Triangle t in base.Triangles) + { + GL.Normal3(t.Plane.Normal); + foreach (Vector3 v in t.Vertices) + { + GL.Vertex3(v); + } + } + GL.End(); + if (useDisplayLists) + { + GL.EndList(); + } + + // Outside Edges + List edges = new List(); + List badEdges = new List(); + + foreach (Edge edge in this.Edges) + { + List triangles = new List(edge.Triangles); + if (triangles.Count == 2) + { + if (Vector3.Dot(triangles[0].Plane.Normal, triangles[1].Plane.Normal) < Math.Cos(Math.PI * 2.0f / 20.0f)) + { + edges.Add(edge.LineSegment); + } + } + else + { + badEdges.Add(edge.LineSegment); + } + } + + GL.Disable(EnableCap.Lighting); + GL.Color3(lineColor); + if (useDisplayLists) + { + GL.NewList(lineDisplayList, ListMode.CompileAndExecute); + } + GL.Begin(PrimitiveType.Lines); + foreach(var line in edges) + { + GL.Vertex3(line.A); + GL.Vertex3(line.B); + } + GL.End(); + if (useDisplayLists) + { + GL.EndList(); + } + + GL.Color3(badLineColor); + if (useDisplayLists) + { + GL.NewList(badLineDisplayList, ListMode.CompileAndExecute); + } + GL.Begin(PrimitiveType.Lines); + foreach(var line in badEdges) + { + GL.Vertex3(line.A); + GL.Vertex3(line.B); + } + GL.End(); + if (useDisplayLists) + { + GL.EndList(); + } + GL.Enable(EnableCap.Lighting); + } + + // Draw the lines connected to the closest line to the cursor (debugging) + //if (closestEdge != null) + //{ + // List triangles = new List(closestEdge.Triangles); + // if (triangles.Count == 2) + // { + // GL.Begin(PrimitiveType.Lines); + // GL.Color3(Color.Blue); + // foreach (var point in closestEdge.Vertices) + // { + // GL.Vertex3(point); + // } + // GL.End(); + // + // GL.Color3(Color.White); + // foreach (var t in triangles) + // { + // GL.Begin(PrimitiveType.LineLoop); + // foreach (var p in t.Vertices) + // { + // GL.Vertex3(p); + // } + // GL.End(); + // } + // } + //} + + + // Draw a perimeter around each triangle (debugging) + //GL.Disable(EnableCap.Lighting); + //foreach (Triangle t in Triangles) + //{ + // GL.Begin(PrimitiveType.LineLoop); + // foreach (Vector3 v in t.Vertices) + // { + // GL.Vertex3(v); + // } + // GL.End(); + //} + //GL.Enable(EnableCap.Lighting); + + GL.PopMatrix(); + } + + Vector3 mouseDownPoint = Vector3.Zero; + Vector3 mouseDownOffset = Vector3.Zero; + void IClickable3D.MouseDown(Ray pointer) + { + mouseDownPoint = mouseHoverPoint; + mouseDownOffset = offset; + Console.WriteLine("Mouse Down TriMeshGUI"); + } + + void IClickable3D.MouseUp(Ray pointer) + { + } + + private bool hovered = false; + //Edge closestEdge = null; + + float IClickable3D.DistanceToObject(Ray pointer) + { + Ray adjustedPointer = new Ray(pointer.Start - offset, pointer.Direction); + hovered = false; + float distance = float.PositiveInfinity; + foreach (Triangle t in base.Triangles) + { + TriangleRayIntersect i = new TriangleRayIntersect(t, adjustedPointer); + if (i.Intersects) + { + float d = (adjustedPointer.Start - i.Point).Length; + if (d < distance) + { + distance = d; + mouseHoverPoint = i.Point; + } + } + } + + + // Remember the closest edge - debugging + //float x = 0; + //closestEdge = null; + //foreach (Edge edge in this.Edges) + //{ + // List vertices = new List(edge.Vertices); + // LineSegment segment = new LineSegment(vertices[0], vertices[1]); + // float d = segment.Distance(mouseHoverPoint); + // if (d < x || closestEdge == null) + // { + // closestEdge = edge; + // x = d; + // } + //} + + isPointedAt = distance < float.PositiveInfinity; + return distance; + } + + void IClickable3D.MouseHover() + { + hovered = true; + } + + void IClickable3D.MouseMove(Ray pointer) + { + Ray adjustedPointer = new Ray(pointer.Start - mouseDownOffset, pointer.Direction); + // Move the object in the XY plane + Plane plane = new Plane(Vector3.UnitZ, mouseDownPoint); + Vector3 point = plane.Distance(adjustedPointer) * adjustedPointer.Direction + adjustedPointer.Start; + Offset = mouseDownOffset + point - mouseDownPoint; + } + } +} diff --git a/GUI/Viewport3d.cs b/GUI/Viewport3d.cs new file mode 100644 index 0000000..5bd4f29 --- /dev/null +++ b/GUI/Viewport3d.cs @@ -0,0 +1,323 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Router; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Geometry; + +namespace GUI +{ + public class Viewport3d : IClickable3D, IOpenGLDrawable + { + private Drawing3D parent; + private bool isMouseDown = false; + private Plane plane = new Plane(Vector3.UnitZ, Vector3.Zero); + private Matrix4 mouseDownMatrix = Matrix4.Identity; + private Ray mouseDownRay = new Ray(Vector3.Zero, -Vector3.UnitZ); + List objects = new List(); + public Matrix4 viewMatrix = Matrix4.CreateTranslation(0, 0, -5); + private Matrix4 projectionMatrix; + private Matrix4 inverseProjectionMatrix; + Vector3 mouseDownCameraPosition = Vector3.Zero; + + public Viewport3d(Drawing3D parent) + { + this.parent = parent; + parent.Resize += parent_Resize; + viewMatrix = Matrix4.Mult(Matrix4.CreateRotationX(-OpenTK.MathHelper.PiOver4), viewMatrix); + } + + void parent_Resize(object sender, EventArgs e) + { + Rectangle r = parent.ClientRectangle; + if (r.Height == 0) + { + r.Height = 1; + } + float aspect = r.Width / (float)r.Height; + projectionMatrix = OpenTK.Matrix4.CreatePerspectiveFieldOfView(OpenTK.MathHelper.PiOver4, aspect, 0.01f, 100.0f); + inverseProjectionMatrix = Matrix4.Invert(projectionMatrix); + } + + internal void Zoom(Ray pointer, int ticks) + { + Vector3 target = pointer.Start + (plane.Distance(pointer) * pointer.Direction); + viewMatrix = Matrix4.Mult(Matrix4.CreateTranslation((target - CameraPosition) * .10f * ticks), viewMatrix); + ClampMatrix(ref viewMatrix); + } + + internal void BeginRotate() + { + mouseDownMatrix = viewMatrix; + } + + public void ViewportRotate(float deltaX, float deltaY) + { + float pixelsPerRadian = 200.0f; // Larger factor means slower/more precise rotation. + + viewMatrix = mouseDownMatrix; + Ray pointer = GetPointerRay(new Point(parent.ClientRectangle.Width / 2, parent.ClientRectangle.Height / 2)); + Vector3 point = plane.Distance(pointer) * pointer.Direction + pointer.Start; + + viewMatrix = Matrix4.Mult(Matrix4.CreateTranslation(point), mouseDownMatrix); + viewMatrix = Matrix4.Mult(Matrix4.CreateRotationZ(deltaX / pixelsPerRadian), viewMatrix); + Matrix4 m = viewMatrix; + m.Invert(); + Vector3 left = new Vector3(m.Row0.X, m.Row0.Y, m.Row0.Z); + viewMatrix = Matrix4.Mult(Matrix4.CreateFromAxisAngle(left, deltaY / pixelsPerRadian), viewMatrix); + viewMatrix = Matrix4.Mult(Matrix4.CreateTranslation(-point), viewMatrix); + ClampMatrix(ref viewMatrix); + } + + public void MouseMove(Ray pointer) + { + float distance = plane.Distance(pointer); + Vector3 point = pointer.Start + pointer.Direction * distance; + + if (isMouseDown) + { + Vector3 downLocation = plane.Distance(mouseDownRay) * mouseDownRay.Direction + mouseDownRay.Start; + viewMatrix = Matrix4.Mult(Matrix4.CreateTranslation(point.X - downLocation.X, point.Y - downLocation.Y, 0), mouseDownMatrix); + ClampMatrix(ref viewMatrix); + } + } + + private void ClampMatrix(ref Matrix4 m) + { + float maxDistance = 50.0f; + Vector3 direction = m.Row3.Xyz; + float toZero = direction.Length; + if (toZero > maxDistance) + { + direction.Normalize(); + m.Row3 -= new Vector4(direction * (toZero - maxDistance), 0); + } + } + + public Matrix4 ProjectionMatrix + { + get { return this.projectionMatrix; } + } + + private Vector3 ComputePointerDirection(Vector2 screenLocation) + { + Matrix4 m = viewMatrix; + m.Row3 = new Vector4(0, 0, 0, 1); // Only keep the rotation part. + + Rectangle r = parent.ClientRectangle; + if (r.Height == 0) + { + r.Height = 1; + } + if (r.Width == 0) + { + r.Width = 1; + } + + // Scale the x & y positions such that the point (0, 0) is in the center and (1, 1) is in the upper right + float y = -(2.0f * screenLocation.Y / (float)r.Height) + 1.0f; + float x = (2.0f * screenLocation.X / (float)r.Width) - 1.0f; + + Vector3 test = Vector3.Transform(new Vector3(x, y, 0), inverseProjectionMatrix); + + test.Normalize(); + + return Vector3.TransformVector(test, Matrix4.Invert(m)); + } + + public Vector3 ComputeMouseTarget(Vector2 screen_location) + { + Matrix4 m = viewMatrix; + if (this.isMouseDown) + { + m = mouseDownMatrix; + } + + Rectangle r = parent.ClientRectangle; + + if (r.Height == 0) + { + r.Height = 1; + } + + // Scale the x & y positions such that the point (0, 0) is in the center and (0.5, 0.5) is in the upper right + float y = -screen_location.Y / (float)r.Height + 0.5f; + float x = screen_location.X / (float)r.Width - 0.5f; + + float a = (float)(0.5f / Math.Tan(Math.PI / 8.0d)); + float aspect = r.Width / (float)r.Height; + + Vector3 v = new Vector3(x * aspect / a, y / a, -1); + + Vector3 planeNormal = new Vector3(0, 0, 1); + Vector3 pointOnPlane = new Vector3(0, 0, 0); + + pointOnPlane = Vector3.Transform(pointOnPlane, m); + planeNormal = Vector3.Transform(planeNormal, m) - Vector3.Transform(new Vector3(0, 0, 0), m); + + Vector3 cameraPosition = new Vector3(m.Row3.X, m.Row3.Y, m.Row3.Z); + float distanceToPlane = Vector3.Dot(pointOnPlane, planeNormal); + v.Normalize(); + float distanceAlongLine = distanceToPlane / (Vector3.Dot(planeNormal, v)); + + v = v * distanceAlongLine; + + v = Vector3.Transform(v, Matrix4.Invert(m)); + return v; + } + + private void DrawAxis() + { + int min = -10; + int max = 10; + + // 1 inch spaced grid lines + GL.Disable(EnableCap.Lighting); + + GL.Begin(PrimitiveType.Lines); + for (float i = min; i <= max; i += 1.0f) + { + GL.Color3(Color.LightPink); + GL.Vertex3(min, i, 0); + GL.Vertex3(max, i, 0); + GL.Color3(Color.LightGreen); + GL.Vertex3(i, min, 0); + GL.Vertex3(i, max, 0); + } + GL.End(); + + GL.Enable(EnableCap.Lighting); + + // Axis arrows for X (red) and Y (green) + float width = 0.15f; + float length = 1.0f; + float z = -0.001f; + + GL.Normal3(Vector3.UnitZ); + GL.Color3(Color.Red); + GL.Begin(PrimitiveType.Quads); + GL.Vertex3(width/2, width/2, z); + GL.Vertex3(-width/2, -width/2, z); + GL.Vertex3(length, -width/2, z); + GL.Vertex3(length, width/2, z); + GL.End(); + + GL.Begin(PrimitiveType.Triangles); + GL.Vertex3(length, width, z); + GL.Vertex3(length, -width, z); + GL.Vertex3(length + width * Math.Sqrt(3), 0, z); + GL.End(); + + GL.Color3(Color.Green); + GL.Begin(PrimitiveType.Quads); + GL.Vertex3(-width / 2, -width / 2, z); + GL.Vertex3(width / 2, width / 2, z); + GL.Vertex3(width / 2, length, z); + GL.Vertex3(-width / 2, length, z); + + GL.End(); + + GL.Begin(PrimitiveType.Triangles); + GL.Vertex3(width, length, z); + GL.Vertex3(-width, length, z); + GL.Vertex3(0, length + width * Math.Sqrt(3), z); + GL.End(); + } + + public void Draw() + { + this.DrawAxis(); + } + + public void AddObject(object o) + { + objects.Add(o); + } + + internal List GetObjects() + { + return objects; + } + + internal Ray GetPointerRay(Point point) + { + Vector3 direction2 = ComputePointerDirection(new Vector2(point.X, point.Y)); + Vector3 location = mouseDownCameraPosition; + if (!isMouseDown) + { + location = CameraPosition; + } + return new Ray(location, direction2); + } + + private Vector3 CameraPosition + { + get + { + Matrix4 m = viewMatrix; + m.Invert(); + return new Vector3(m.Row3.X, m.Row3.Y, m.Row3.Z); + } + } + + private Vector3 CameraForward + { + get + { + Matrix4 m = viewMatrix; + m.Invert(); + return new Vector3(-m.Row2.X, -m.Row2.Y, -m.Row2.Z); + } + } + + #region IClickable3D + + void IClickable3D.MouseDown(Ray pointer) + { + mouseDownRay = pointer; + mouseDownMatrix = viewMatrix; + mouseDownCameraPosition = CameraPosition; + isMouseDown = true; + } + + void IClickable3D.MouseUp(Ray pointer) + { + isMouseDown = false; + } + + void IClickable3D.MouseHover() + { + } + + float IClickable3D.DistanceToObject(Ray pointer) + { + return plane.Distance(pointer); + } + + #endregion + } + +} diff --git a/GUI/icon.ico b/GUI/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0b76d5025618dc354e1dfb0b503fc6a9dde33ee0 GIT binary patch literal 4286 zcmd6r-%C?r7{||?(`|0l#ViRWv6B7)FT9CD2n0pw!W)Gckr#GXUU;b#UI^-icrivG zLC&p2i!PR+5$VE0#7M%l$jS(dmRZxCv-5sC&%563G@ZY8W}R`)^Zqz{Ki}tlpZCWm zNha-6S}JK*sWf<4l8$Jp(>y0hXEm$UYESJd<!5BkKVNetm>*_ivpRHSosrV^x zW~hwHBbtWnvB{^18bK^ZzW&Xja^4SH)EU(mf|1J6m#5ZcgDu(={Xl&$%7hhlBA4nQHa@5LM!1%-L%8*+L6x__7+t#rEqM zd;A*80kTO$I2uOX%{sW zC=pbRRUy}x3(KGd$GU4c+v#B3c6DX1A7oWV^`mN-eP-kh=ArCf8J2%7!{dpwNrNOY zbaooAf!Os=>XGBiLB2m9CBr3H{IQsrYyJHUxw#CEKN{NGjnhE;hx@5J!FJtsVSaT! z!4@&)_cNHyaSap?t*yprXnxiV(}0QlP3+lkvzypL!_W|e)yhy%5FeA476zKDqJFQo zb*7cGtpO{X0VhH$p&i=_29p|cau}ML_NSraWk-T-9kgO%WnxFR@NaaK!RchM+Zpzw zq5D-gq}vi~0UL-N{1n_ZTWFY^OlqLGsIT9=htj$&r|fCoMKAZ;93>7;Lt9Z0a?L{fgpaZ9RS_4=e>ZoBTEpnBjlt-#1%mn4L{(pgCGw%P=(^#r+4# z{p#xUHC&!-=Ia`!D|o5)?UIb*<1$~PX^pU0 z7-nV|#y^iI*o8xdXbd&tP58Z`8fg8ddv1=QuaBXjf#Ku{hMpdV;g7>;d(noeuTzMr t$uld>i%ixo;lE&uyxx8fYh$p@IhoG>bpF + + + + \ No newline at end of file diff --git a/Geometry/Geometry.csproj b/Geometry/Geometry.csproj new file mode 100644 index 0000000..1f5fc25 --- /dev/null +++ b/Geometry/Geometry.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {29611CC3-E54F-45E4-9681-8DF653459CA2} + Library + Properties + Geometry + Geometry + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\OpenTK.1.1.1456.5398\lib\NET40\OpenTK.dll + + + ..\packages\QuantumConcepts.Common.1.0.0.1\lib\net40\QuantumConcepts.Common.dll + + + False + ..\packages\QuantumConcepts.Formats.STL.1.1.0.0\lib\net40\QuantumConcepts.Formats.STL.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + {9b062971-a88e-4a3d-b3c9-12b78d15fa66} + clipper_library + + + {f7907a0a-b75f-400b-9e78-bfad00db4d6b} + Triangle + + + + + + + + \ No newline at end of file diff --git a/Geometry/Intersect.cs b/Geometry/Intersect.cs new file mode 100644 index 0000000..5c4cfc4 --- /dev/null +++ b/Geometry/Intersect.cs @@ -0,0 +1,216 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace Geometry +{ + public class LineSegmentCircleIntersect + { + LineSegment intersectSegment; + LineSegment beginNoIntersect; + LineSegment endNoIntersect; + private IntersectType intersectType = IntersectType.None; + public enum IntersectType + { + Segment, + None, + } + public LineSegmentCircleIntersect(LineSegment segment, Vector3 circleCenter, float circleRadius) + { + Vector3 a = segment.A; + Vector3 b = segment.B; + Vector3 c = circleCenter; + + // Compute a normal perpendicular to the line and pointing to the point + Vector3 up = Vector3.Cross(segment.A - circleCenter, segment.A - segment.B); // This points up from the line + Vector3 normal = Vector3.Cross(up, segment.A - segment.B); + float distanceToLine = 0.0f; + if (normal.Length > 0.0f) + { + distanceToLine = Math.Abs(new Plane(normal, segment.A).Distance(circleCenter)); + } + + if (distanceToLine >= circleRadius) + { + intersectType = IntersectType.None; + return; + } + if (distanceToLine == float.NaN) + { + } + + float along = (float)Math.Sqrt(Math.Pow(circleRadius, 2) - Math.Pow(distanceToLine, 2)); + + Plane plane = new Plane(b - a, a); + + float toA = 0.0f; + float toB = plane.Distance(b); + float toC = plane.Distance(c); + + float toP1 = toC - along; + float toP2 = toC + along; + + if (toP2 < toA || toP1 > toB) + { + intersectType = IntersectType.None; + return; + } + + if (toP1 > toA) + { + beginNoIntersect = new LineSegment(a, a + toA * plane.Normal); + } + if (toP2 < toB) + { + endNoIntersect = new LineSegment(a + toP2 * plane.Normal, a + toB * plane.Normal); + } + + toP1 = Math.Min(Math.Max(toP1, toA), toB); + toP2 = Math.Min(Math.Max(toP2, toA), toB); + + intersectType = IntersectType.Segment; + intersectSegment = new LineSegment(a + plane.Normal * toP1, a + plane.Normal * toP2); + } + + public LineSegment IntersectSegment + { + get + { + return intersectSegment; + } + } + public IntersectType type + { + get { return intersectType; } + } + } + + public class TrianglePlaneIntersect + { + private Vector3 pointA; + private Vector3 pointB; + private bool intersects = false; + public TrianglePlaneIntersect(Triangle triangle, Plane plane) + { + // Find the intersections between edges on the triangle and the plane. + List vertices = new List(3); + List distances = new List(3); + List onPlane = new List(); + + foreach (Vector3 v in triangle.Vertices) + { + vertices.Add(v); + distances.Add(plane.Distance(v)); + } + + for (int i = 0; i < 3; i++) + { + float d1 = distances[i]; + float d2 = distances[(i + 1) % 3]; + Vector3 v1 = vertices[i]; + Vector3 v2 = vertices[(i + 1) % 3]; + if (d1 * d2 < 0) + { + // Edge of the triangle crosses the plane, interpolate to get the intersection point + Vector3 intersect = new Vector3(v2 * d1 - v1 * d2) / (d1 - d2); + onPlane.Add(intersect); + } + } + + if (onPlane.Count == 2) + { + pointA = onPlane[0]; + pointB = onPlane[1]; + intersects = true; + } + + if (intersects) + { + // Check the vector direction - flip the vertices if necessary + if (Vector3.Dot(Vector3.Cross(pointB - pointA, plane.Normal), triangle.Plane.Normal) < 0) + { + pointA = onPlane[1]; + pointB = onPlane[0]; + } + } + } + + public bool Intersects + { + get { return intersects; } + } + + public Vector3 PointA + { + get { return pointA; } + } + + public Vector3 PointB + { + get { return pointB; } + } + } + + public class TriangleRayIntersect + { + private Vector3 point; + private bool intersects; + + public TriangleRayIntersect (Triangle triangle, Ray ray) + { + point = Vector3.Zero; + intersects = false; + + float distance = triangle.Plane.Distance(ray); + if (distance <= 0 || float.IsNaN(distance)) + { + // Plane is behind the ray, no intersection + return; + } + + point = ray.Direction * distance + ray.Start; + // Make sure the point is within the triangle + foreach (Plane p in triangle.EdgePlanes) + { + if (p.Distance(point) < 0) + { + // Outside of triangle, no intersection + return; + } + } + intersects = true; + } + + public Vector3 Point + { + get { return point; } + } + + public bool Intersects + { + get { return intersects; } + } + } +} diff --git a/Geometry/LineSegment.cs b/Geometry/LineSegment.cs new file mode 100644 index 0000000..fb1dc8a --- /dev/null +++ b/Geometry/LineSegment.cs @@ -0,0 +1,150 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Geometry +{ + public class LineSegment + { + private Vector3 a; + private Vector3 b; + + public LineSegment(Vector3 a, Vector3 b) + { + this.a = a; + this.b = b; + } + + public IEnumerable Points + { + get + { + yield return a; + yield return b; + } + } + + public Vector3 A + { + get { return a; } + } + public Vector3 B + { + get { return b; } + } + + public float Length + { + get { return (a - b).Length; } + } + + /// + /// Subtract another line segment from this one. + /// It's assumed that the other segment lies on the same line as this one. + /// + /// + public List Subtract(LineSegment other) + { + //Vector3 n = b - a; + //n.Normalize(); + + Plane plane = new Plane(b - a, a); + float toB = plane.Distance(b); + + float toOtherA = plane.Distance(other.a); + float toOtherB = plane.Distance(other.b); + + if (toOtherA > toOtherB) + { + float temp = toOtherA; + toOtherA = toOtherB; + toOtherB = temp; + } + else + { + } + + List remainingSegments = new List(); + + // Check for no overlap + if (toOtherA >= toB || toOtherB <= 0.0f) + { + remainingSegments.Add(new LineSegment(a, b)); + return remainingSegments; + } + + if (toOtherA > 0) + { + Vector3 p1 = a; + remainingSegments.Add(new LineSegment(a, a + plane.Normal * toOtherA)); + } + + if (toOtherB < toB) + { + remainingSegments.Add(new LineSegment(a + plane.Normal * toOtherB, b)); + } + + return remainingSegments; + } + + public float Distance(Vector3 point) + { + // Compute a normal perpendicular to the line and pointing to the point + Vector3 up = Vector3.Cross(a - point, a - b); // This points up from the line + Vector3 normal = Vector3.Cross(up, a - b); + float distanceToLine = 0.0f; + if (normal.Length > 0.0f) + { + distanceToLine = Math.Abs(new Plane(normal, a).Distance(point)); + } + + Plane plane = new Plane(b - a, a); + float toPoint = plane.Distance(point); + float toB = plane.Distance(b); + if (toPoint > 0.0f && toPoint < toB) + { + return distanceToLine; + } + return (float)Math.Min((b - point).Length, (a - point).Length); + } + + public Vector3 ClosestPoint(Vector3 point) + { + Plane plane = new Plane(b - a, a); + float toPoint = plane.Distance(point); + float toB = plane.Distance(b); + if (toPoint > 0.0f && toPoint < toB) + { + return plane.Normal * toPoint + a; + } + float toA = (a - point).Length; + toB = (b - point).Length; + if (toA < toB) + { + return a; + } + return b; + } + } +} diff --git a/Geometry/LineStrip.cs b/Geometry/LineStrip.cs new file mode 100644 index 0000000..b8ed6a9 --- /dev/null +++ b/Geometry/LineStrip.cs @@ -0,0 +1,123 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Geometry +{ + public class LineStrip + { + protected List vertices; + + public enum Type + { + Open, + Closed, + } + + public LineStrip() + { + vertices = new List(); + } + + public IEnumerable Segments(Type type) + { + bool lastKnown = false; + Vector3 last = Vector3.Zero; + foreach (var point in vertices) + { + if (lastKnown) + { + yield return new LineSegment(last, point); + } + last = point; + lastKnown = true; + } + + if (type == Type.Closed && lastKnown) + { + yield return new LineSegment(last, vertices[0]); + } + } + + public void Append(Vector3 vertex) + { + vertices.Add(vertex); + } + + public List Vertices + { + get { return vertices; } + } + + public IEnumerable PointsAlongLine(float distance, float toFirst, Type type = Type.Closed) + { + float toNext = toFirst; + float travelled = 0.0f; + foreach (var segment in Segments(type)) + { + travelled += segment.Length; + Vector3 normal = segment.A - segment.B; + normal.Normalize(); + while (travelled > toNext) + { + Vector3 point = segment.B + normal * (travelled - toNext); + yield return point; + toNext += distance; + } + } + } + + public float Length(Type type = Type.Closed) + { + bool lastKnown = false; + Vector3 lastVector = new Vector3(0, 0, 0); + float length = 0.0f; + foreach (Vector3 point in this.Vertices) + { + if (lastKnown) + { + length += (lastVector - point).Length; + } + else + { + lastKnown = true; + } + lastVector = point; + } + if (type == Type.Closed && lastKnown) + { + length += (lastVector - Vertices[0]).Length; + } + return length; + } + + public void AddRange(IEnumerable list) + { + foreach (var point in list) + { + Append(point); + } + } + } +} diff --git a/Geometry/Loaders/DAE_Loader.cs b/Geometry/Loaders/DAE_Loader.cs new file mode 100644 index 0000000..7f6b448 --- /dev/null +++ b/Geometry/Loaders/DAE_Loader.cs @@ -0,0 +1,185 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using OpenTK; + +namespace Geometry +{ + #region DAE File XML Description + + [Serializable()] + public class Source + { + [XmlElement("float_array")] + public string float_array { get; set; } + + [XmlAttribute("id")] + public string id { get; set; } + } + + [Serializable()] + public class Input + { + [XmlAttribute("semantic")] + public string semantic { get; set; } + + [XmlAttribute("source")] + public string source { get; set; } + + [XmlAttribute("offset")] + public string offset { get; set; } + } + + [Serializable()] + public class Vertices + { + [XmlElement("input")] + public Input[] input { get; set; } + } + + [Serializable()] + public class Triangles + { + [XmlElement("p")] + public string p { get; set; } + + [XmlElement("input")] + public Input[] input { get; set; } + } + + [Serializable()] + public class Mesh + { + [XmlElement("source")] + public Source[] sources { get; set; } + + [XmlElement("vertices")] + public Vertices vertices { get; set; } + + [XmlElement("triangles")] + public Triangles triangles { get; set; } + + public Input FindInputBySemantic(Input[] inputs, string semantic) + { + foreach (Input i in inputs) + { + if (i.semantic == semantic) + { + return i; + } + } + return null; + } + + public Source FindSourceById(Source[] sources, string id) + { + foreach (Source s in sources) + { + if (("#" + s.id) == id) + { + return s; + } + } + return null; + } + + public float[] PositionFloats() + { + Input i = FindInputBySemantic(vertices.input, "POSITION"); + Source s = FindSourceById(sources, i.source); + IEnumerable floats = s.float_array.Split(new char[] { ' ' }).Select(float_str => float.Parse(float_str)); + return floats.ToArray(); + } + + public int[] TrianglePositionIndices() + { + if (triangles == null) + { + return new int[] { }; + } + int num_inputs = triangles.input.Count(); + int index = int.Parse(FindInputBySemantic(triangles.input, "VERTEX").offset); + + int[] indices = triangles.p.Split(new char[] { ' ' }).Select(int_str => int.Parse(int_str)).ToArray(); + List position_indices = new List(); + for (int i = index; i < indices.Count(); i += num_inputs) + { + position_indices.Add(indices[i]); + } + return position_indices.ToArray(); + } + + } + + [Serializable()] + public class Geometry + { + [XmlElement("mesh")] + public Mesh mesh { get; set; } + } + + [XmlRootAttribute("COLLADA", Namespace = "http://www.collada.org/2005/11/COLLADASchema")] + [Serializable()] + public class Collada + { + [XmlArray("library_geometries")] + [XmlArrayItem("geometry")] + public Geometry[] library_geometries { get; set; } + } + + #endregion + + public class DAE_Loader + { + public static void Load(string filepath, TriangleMesh triMesh, float scale) + { + using (System.IO.TextReader reader = File.OpenText(filepath)) + { + XmlSerializer ser = new XmlSerializer(typeof(Collada)); + Collada c = (Collada)ser.Deserialize(reader); + foreach (Geometry g in c.library_geometries) + { + float[] floats = g.mesh.PositionFloats(); + int[] indices = g.mesh.TrianglePositionIndices(); + + List vertices = new List(); + + for (int i = 0; i < floats.Count(); i += 3) + { + Vector3 v = new Vector3(floats[i], floats[i + 1], floats[i + 2]); + vertices.Add(v * scale); + } + + for (int i = 0; i < indices.Count(); i += 3) + { + triMesh.AddTriangle(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]); + } + } + } + triMesh.Clean(); + } + } +} diff --git a/Geometry/Loaders/OBJ_Loader.cs b/Geometry/Loaders/OBJ_Loader.cs new file mode 100644 index 0000000..9e4c20e --- /dev/null +++ b/Geometry/Loaders/OBJ_Loader.cs @@ -0,0 +1,115 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; +using Geometry; +using System.Text.RegularExpressions; + +namespace Geometry +{ + public class OBJ_Loader + { + public static void Load(string filepath, TriangleMesh triMesh, float scale) + { + List vertices = new List(); + string[] strings = System.IO.File.ReadAllLines(filepath); + + // Lines starting with v are a vertex: + // "v 10.2426 4.5e-013 -31.7638" + Regex vertexRegex = new Regex(@"^v\s+(?\S+)\s+(?\S+)\s+(?\S+)", RegexOptions.IgnoreCase); + + // Lines starting with f are a face. The indices are //, where texture and normal are optional. + // "f 1/1/1 2/2/1 3/3/1 4/4/1 5/5/1" + Regex faceRegex = new Regex(@"^f(?\s+(?\d+)/?(?\d+)?/?(?\d+)?)+", RegexOptions.IgnoreCase); + + foreach (string s in strings) + { + if (vertexRegex.IsMatch(s)) + { + Match m = vertexRegex.Match(s); + float x = float.Parse(m.Groups["x"].Value); + float y = float.Parse(m.Groups["y"].Value); + float z = float.Parse(m.Groups["z"].Value); + // Rotate 90 degrees about the X axis - for some reason .obj files saved from sketchup have this issue... + Vector3 v = new Vector3(x, -z, y); + vertices.Add(v * scale); + } + else if (faceRegex.IsMatch(s)) + { + Match m = faceRegex.Match(s); + + //Console.WriteLine(m.Groups["face_data"].Captures.Count); + //Console.WriteLine(m.Groups["vertex"].Captures.Count); + //Console.WriteLine(m.Groups["texture_coordinate"].Captures.Count); + //Console.WriteLine(m.Groups["vertex_normal"].Captures.Count); + + //Face face = new Face(); + Polygon polygon = new Polygon(); + + CaptureCollection vert_captures = m.Groups["vertex"].Captures; + CaptureCollection texcoord_captures = m.Groups["texture_coordinate"].Captures; + CaptureCollection norm_captures = m.Groups["vertex_normal"].Captures; + + var vertexIndices = vert_captures.Cast().Select(capture => int.Parse(capture.Value) - 1); + + foreach (var vertexIndex in vertexIndices) + { + if (vertexIndex < 0 || vertexIndex > vertices.Count) + { + Console.WriteLine("Bad vertex index {0}, only {1} vertices loaded", vertexIndex, vertices.Count); + } + else + { + polygon.Add(vertices[vertexIndex]); + } + } + //for (int i = 0; i < vert_captures.Count; i++) + //{ + // int vert_index = int.Parse(vert_captures[i].Value) - 1; + // + //} + if (texcoord_captures.Count == vert_captures.Count) + { + // TODO: Add texture coordinates to the face + } + if (norm_captures.Count == vert_captures.Count) + { + // TODO: Add vertex normals to the face + } + + if (polygon.Vertices.Count() < 3) + { + Console.WriteLine("Bad face defined, less than 3 vertices"); + } + else + { + foreach (var triangle in polygon.ToTriangles()) + { + triMesh.AddTriangle(triangle); + } + } + } + } + } + } +} diff --git a/Geometry/Loaders/STL_Loader.cs b/Geometry/Loaders/STL_Loader.cs new file mode 100644 index 0000000..9400eea --- /dev/null +++ b/Geometry/Loaders/STL_Loader.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using QuantumConcepts.Common; +using System.IO; +using QuantumConcepts.Formats.StereoLithography; +using OpenTK; + +namespace Geometry +{ + public class STL_Loader + { + public static void Load(string filepath, TriangleMesh triMesh, float scale) + { + STLDocument stl = null; + + using (Stream filestream = File.OpenRead(filepath)) + { + stl = STLDocument.Read(filestream); + } + + foreach (var facet in stl.Facets) + { + List vertices = new List(); + foreach (var vertex in facet.Vertices) + { + Vector3 v = new Vector3((float)vertex.X, (float)vertex.Y, (float)vertex.Z); + vertices.Add(v * scale); + } + triMesh.AddTriangle(new Triangle(vertices[0], vertices[1], vertices[2])); + } + triMesh.Clean(); + } + } +} diff --git a/Geometry/Plane.cs b/Geometry/Plane.cs new file mode 100644 index 0000000..c1c77ed --- /dev/null +++ b/Geometry/Plane.cs @@ -0,0 +1,100 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Geometry +{ + public class Plane + { + private Vector3 normal; + private Vector3 point; + + public Plane(Vector3 normal, Vector3 point) + { + this.normal = normal; + this.point = point; + this.normal.Normalize(); // Make sure the normal is correct + } + + public Vector3 Normal + { + get { return normal; } + set { normal = value; } + } + + public Vector3 Point + { + get { return point; } + set { point = value; } + } + + /// + /// Compute the distance from the plane to a point. + /// + /// + /// Nearest distance from this plane to the specified point. The result is negative if the point is on the back side of the plane. + public float Distance(Vector3 point) + { + float scalar = Vector3.Dot(normal, this.point); + return Vector3.Dot(normal, point) - scalar; + } + + /// + /// Compute the distance along the ray to this plane. + /// + /// + /// Distance along the ray until the plane is reached. If the plane is behind the ray, the return value is negative. + public float Distance(Ray ray) + { + float fromRayStart = Distance(ray.Start); + + float alongRay = Math.Abs(fromRayStart / Vector3.Dot(ray.Direction, normal)); + Vector3 target = ray.Start + alongRay * ray.Direction; // This should be on the plane - if not, the distance has the wrong sign. + Vector3 target2 = ray.Start - alongRay * ray.Direction; // Rather than compare with zero, which is not deterministic, compare with the possible result if the plane were behind the ray. + + if (Math.Abs(Distance(target)) > Math.Abs(Distance(target2))) + { + alongRay = -alongRay; + } + return alongRay; + } + + /// + /// Create a matrix which aligns the z axis with plane normal. + /// + /// + /// + public Matrix4 CreateMatrix() + { + Vector3 up = new Vector3(0, 0, 1); + if (Math.Abs(Normal.Z) > 0.8) + { + up = new Vector3(1, 0, 0); + } + float scalar = Vector3.Dot(Point, Normal); + Matrix4 transform = Matrix4.LookAt(Normal * scalar, Normal * (scalar - 1), up); + return transform; + } + } +} diff --git a/Geometry/Polygon.cs b/Geometry/Polygon.cs new file mode 100644 index 0000000..2425f04 --- /dev/null +++ b/Geometry/Polygon.cs @@ -0,0 +1,134 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; +using Geometry; + +namespace Geometry +{ + public class Polygon + { + private List vertices; + + public Polygon() + { + vertices = new List(); + } + + public void Add(Vector3 vertex) + { + vertices.Add(vertex); + } + + public IEnumerable Vertices + { + get { return vertices; } + } + + public Plane Plane + { + get + { + // TODO: memoize, forget if vertices change + var normal = Vector3.Zero; + var center = Vector3.Zero; + var verts = vertices.Count; + // Compute the normal + for (int i = 0; i < verts; i++) + { + var i1 = i; + var i2 = (i + 1) % verts; + var v1 = vertices[i]; + var v2 = vertices[i2]; + normal.X += (v1.Y - v2.Y) * (v1.Z + v2.Z); + normal.Y += (v1.Z - v2.Z) * (v1.X + v2.X); + normal.Z += (v1.X - v2.X) * (v1.Y + v2.Y); + center += v1; + } + center = center / verts; + normal.Normalize(); + + return new Plane(normal, center); + } + } + + // Ear clipping algorithm to separate a polygon into triangles + // This should work on any list of triangles with no holes. + public IEnumerable ToTriangles() + { + Plane p = Plane; + + if (vertices.Count == 3) + { + } + + while (vertices.Count >= 3) + { + int verts = vertices.Count; + int i = 0; + // Find an ear on the face, remove it + for (i = 0; i < verts; i++) + { + Vector3 v1 = vertices[i]; + Vector3 v2 = vertices[(i + 1) % verts]; + Vector3 v3 = vertices[(i + 2) % verts]; + var tri = new Triangle(v1, v2, v3); + + bool anyPointInPolygon = false; + foreach (var otherPoint in vertices) + { + if (otherPoint != v1 && otherPoint != v2 && otherPoint != v3 && tri.IsPointInTriangle(otherPoint)) + { + anyPointInPolygon = true; + break; + } + } + + // First check: see if any point in the original polygon is inside the new triangle + if (anyPointInPolygon) + { + // Can't use this triangle, move onto the next one + } + else + { + // Make sure the triangle points the right way. + if (Vector3.Dot(tri.Plane.Normal, p.Normal) > 0.9f) + { + yield return tri; + this.vertices.RemoveAt((i + 1) % verts); + break; + } + else + { + } + } + } + if (i == verts) + { + // Got a bad face + break; + } + } + } + } +} diff --git a/Geometry/Properties/AssemblyInfo.cs b/Geometry/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c0460ee --- /dev/null +++ b/Geometry/Properties/AssemblyInfo.cs @@ -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("Geometry")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Geometry")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("4bcc6f36-6431-4a2a-a630-791d64979651")] + +// 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")] diff --git a/Geometry/Ray.cs b/Geometry/Ray.cs new file mode 100644 index 0000000..5555ab7 --- /dev/null +++ b/Geometry/Ray.cs @@ -0,0 +1,51 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Geometry +{ + public class Ray + { + private Vector3 start; + private Vector3 direction; + + public Ray(Vector3 start, Vector3 direction) + { + this.start = start; + this.direction = direction; + this.direction.Normalize(); + } + + public Vector3 Start + { + get { return start; } + set { start = value; } + } + public Vector3 Direction + { + get { return direction; } + set { direction = value; direction.Normalize(); } + } + } +} diff --git a/Geometry/Slice.cs b/Geometry/Slice.cs new file mode 100644 index 0000000..9abc2a1 --- /dev/null +++ b/Geometry/Slice.cs @@ -0,0 +1,753 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using ClipperLib; +using TriangleNet.Geometry; + + +namespace Geometry +{ + using Path = List; + using Paths = List>; + public class Slice + { + private Plane plane; + + // For converting to/from 2D polygons + private float scale = 1000000; + private Matrix4 transform; + private Matrix4 inverseTransform; + private PolyTree polyTree; + + public Slice(TriangleMesh mesh, Plane plane) + { + //GL.Disable(EnableCap.Lighting); + //GL.LineWidth(2); + //GL.Begin(PrimitiveType.Lines); + //float height = 0; + LineHandler lineHandler = new LineHandler(); + + // Slice at 3 levels and combine all segments - this obviates floating point comparison handling. + for (int i = -1; i <= 1; i++) + { + Vector3 offset = plane.Normal * 0.0001f * (float)i; + Plane testPlane = new Plane(plane.Normal, plane.Point + offset); + foreach (Triangle t in mesh.Triangles) + { + var intersect = new TrianglePlaneIntersect(t, testPlane); + if (intersect.Intersects) + { + lineHandler.AddSegment(intersect.PointA - offset, intersect.PointB - offset); + //GL.Color3(Color.Blue); + //GL.Vertex3(intersect.PointA + new Vector3(0, 0, height + .01f)); + //GL.Color3(Color.Red); + //GL.Vertex3(intersect.PointB + new Vector3(0, 0, height + .01f)); + } + } + } + //GL.End(); + //GL.Enable(EnableCap.Lighting); + //GL.LineWidth(1); + Init(lineHandler.GetOuterLoops(), plane); + } + + public Slice(Slice fromSlice) + { + this.plane = fromSlice.plane; + this.transform = fromSlice.transform; + this.inverseTransform = fromSlice.inverseTransform; + this.polyTree = fromSlice.polyTree; + } + + public Slice(IEnumerable fromLines, Plane plane) + { + Init(fromLines, plane); + } + + /// + /// Create a slice from an open path with the given width + /// + /// + /// + /// + public Slice (LineStrip path, float width, Plane plane, bool closed = false) + { + this.plane = plane; + transform = plane.CreateMatrix(); + transform = Matrix4.Mult(transform, Matrix4.CreateScale(scale)); + inverseTransform = Matrix4.Invert(transform); + polyTree = new PolyTree(); + + ClipperOffset co = new ClipperOffset(); + co.ArcTolerance = scale * 0.0001f; + if (closed) + { + co.AddPath(LineStripToPolygon(path), JoinType.jtRound, EndType.etClosedLine); + } + else + { + co.AddPath(LineStripToPolygon(path), JoinType.jtRound, EndType.etOpenRound); + } + co.Execute(ref this.polyTree, scale * width / 2.0f); + } + + private void Init(IEnumerable lines, Plane plane, PolyFillType pft = PolyFillType.pftEvenOdd) + { + transform = plane.CreateMatrix(); + transform = Matrix4.Mult(transform, Matrix4.CreateScale(scale)); + inverseTransform = Matrix4.Invert(transform); + this.plane = plane; + polyTree = GetPolyTree(lines, pft); + } + + public Plane Plane + { + get { return plane; } + } + + private Path LineStripToPolygon(LineStrip line) + { + Path polygon = new Path(); + foreach (var point in line.Vertices) + { + Vector2 result = Vector3.Transform(point, transform).Xy; + polygon.Add(new IntPoint((long)Math.Round(result.X), (long)Math.Round(result.Y))); + } + return polygon; + } + + private PolyTree GetPolyTree(IEnumerable lines, PolyFillType pft) + { + Paths polygons = new Paths(); + Clipper c = new Clipper(); + c.Clear(); + + foreach (var line in lines) + { + polygons.Add(LineStripToPolygon(line)); + } + + polygons = Clipper.SimplifyPolygons(polygons, pft); + c.AddPaths(polygons, PolyType.ptSubject, true); + PolyTree tree = new PolyTree(); + c.Execute(ClipType.ctUnion, tree); + return tree; + } + + public void Offset(float offset) + { + Paths polygons = Clipper.ClosedPathsFromPolyTree(polyTree); + ClipperOffset co = new ClipperOffset(); + co.ArcTolerance = scale * .0001f; + co.AddPaths(polygons, JoinType.jtRound, EndType.etClosedPolygon); + polyTree = new PolyTree(); + Paths offsetPaths = new Paths(); + co.Execute(ref offsetPaths, scale * offset); + offsetPaths = Clipper.CleanPolygons(offsetPaths, scale * .0001f); + polyTree = PolygonsToPolyTree(offsetPaths); + } + + public IEnumerable GetLines(LineType type) + { + PolyNode n = polyTree.GetFirst(); + while (null != n) + { + bool hole = n.IsHole; + if (type == LineType.All || (hole && type == LineType.Hole) || (!hole && type == LineType.Outside)) + { + yield return LineStripFromPolygon(n.Contour); + } + n = n.GetNext(); + } + } + + public enum LineType + { + Hole, + Outside, + All, + } + + private LineStrip LineStripFromPolygon(Path polygon) + { + LineStrip line = new LineStrip(); + foreach (IntPoint point in polygon) + { + line.Append(TransformTo3D(point)); + } + return line; + } + + public float Area() + { + float area = 0; + foreach (var poly in Clipper.PolyTreeToPaths(polyTree)) + { + area += (float)Clipper.Area(poly); + } + return area; + } + + public bool Contains(Slice other) + { + // To contain another slice: + // 1. Area of the union must be the same + // 2. Area of this - other must be less + + + + float thisArea = this.Area(); + + Paths otherPolygons = Clipper.PolyTreeToPaths(other.polyTree); + Paths thesePolygons = Clipper.PolyTreeToPaths(polyTree); + Slice s = new Slice(this); + Clipper c = new Clipper(); + c.Clear(); + c.AddPaths(thesePolygons, PolyType.ptSubject, true); + c.AddPaths(otherPolygons, PolyType.ptClip, true); + s.polyTree = new PolyTree(); + c.Execute(ClipType.ctUnion, s.polyTree); + float area_union = s.Area(); + if (area_union > thisArea) + { + return false; + } + return true; + //c.Clear(); + //c.AddPaths(thesePolygons, PolyType.ptSubject, true); + //c.AddPaths(otherPolygons, PolyType.ptClip, true); + //s.polyTree = new PolyTree(); + //c.Execute(ClipType.ctDifference, s.polyTree); + //float area_difference = s.Area(); + //if (area_difference < thisArea) + //{ + // return true; + //} + //return false; + } + + /// + /// Get a list of triangles which will fill the area described by the slice + /// + public IEnumerable Triangles() + { + + TriangleNet.Behavior behavior = new TriangleNet.Behavior(); + behavior.ConformingDelaunay = true; + + foreach (var poly in IndividualPolygons()) + { + PolyNode node = polyTree.GetFirst(); + InputGeometry geometry = new InputGeometry(); + while (node != null) + { + var offset = geometry.Points.Count(); + var index = 0; + foreach (IntPoint point in node.Contour) + { + geometry.AddPoint(point.X, point.Y); + if (index > 0) + { + geometry.AddSegment(index - 1 + offset, index + offset); + } + index++; + } + geometry.AddSegment(index - 1 + offset, offset); + + if (node.IsHole) + { + // To describe a hole, AddHole must be called with a location inside the hole. + IntPoint last = new IntPoint(0, 0); + bool lastKnown = false; + double longest = 0; + IntPoint longestAlong = new IntPoint(0, 0); + IntPoint from = new IntPoint(0, 0); + foreach (IntPoint point in node.Contour) + { + if (lastKnown) + { + IntPoint along = new IntPoint(point.X - last.X, point.Y - last.Y); + double length = Math.Sqrt(along.X * along.X + along.Y * along.Y); + if (length > longest) + { + longest = length; + longestAlong = along; + from = last; + } + } + last = point; + lastKnown = true; + } + if (longest > 0) + { + double perpendicularX = ((double)longestAlong.Y * (double)scale * 0.001d) / longest; + double perpendicularY = -((double)longestAlong.X * (double)scale * 0.001d) / longest; + geometry.AddHole(perpendicularX + from.X + longestAlong.X / 2.0d, + perpendicularY + from.Y + longestAlong.Y / 2.0d); + } + else + { + } + } + node = node.GetNext(); + } + + if (geometry.Points.Count() > 0) + { + var mesh = new TriangleNet.Mesh(behavior); + mesh.Triangulate(geometry); + mesh.Renumber(); + foreach (Triangle t in this.GetMeshTriangles(mesh)) + { + yield return t; + } + } + } + } + + private Vector3 TransformTo3D(IntPoint point) + { + return Vector3.Transform(new Vector3(point.X, point.Y, 0), inverseTransform); + } + + private IEnumerable GetMeshTriangles(TriangleNet.Mesh mesh) + { + List vertices = new List(); + foreach (var vertex in mesh.Vertices) + { + vertices.Add(TransformTo3D(new IntPoint((long)vertex.X, (long)vertex.Y))); + } + + foreach (var triangle in mesh.Triangles) + { + yield return new Triangle(vertices[triangle.P0], vertices[triangle.P1], vertices[triangle.P2]); + } + } + + public void RemoveHoles(float maxPerimiter) + { + Paths keep = new Paths(); + PolyNode node = polyTree.GetFirst(); + while (node != null) + { + if (node.IsHole && node.ChildCount == 0) + { + var line = LineStripFromPolygon(node.Contour); + float length = line.Length(LineStrip.Type.Closed); + if (length < maxPerimiter) + { + // Remove it + } + else + { + keep.Add(node.Contour); + } + } + else + { + keep.Add(node.Contour); + } + node = node.GetNext(); + } + Clipper c = new Clipper(); + c.Clear(); + c.AddPaths(keep, PolyType.ptSubject, true); + polyTree = new PolyTree(); + c.Execute(ClipType.ctUnion, polyTree); + } + + /// + /// Get all the polygons which contain holes + /// + /// + public Slice PolygonsWithHoles() + { + Slice s = new Slice(this); + PolyNode n = polyTree.GetFirst(); + Paths polygons = new Paths(); + while (null != n) + { + if (n.IsHole) + { + if (!polygons.Contains(n.Parent.Contour)) + { + polygons.Add(n.Parent.Contour); + } + polygons.Add(n.Contour); + } + n = n.GetNext(); + } + + s.polyTree = PolygonsToPolyTree(polygons); + return s; + } + + public Slice PolygonsWithoutHoles() + { + Slice s = new Slice(this); + PolyNode n = polyTree.GetFirst(); + Paths polygons = new Paths(); + while (null != n) + { + if (!n.IsHole && n.ChildCount == 0) + { + polygons.Add(n.Contour); + } + n = n.GetNext(); + } + + s.polyTree = PolygonsToPolyTree(polygons); + return s; + } + + public IEnumerable IndividualPolygons() + { + PolyNode n = polyTree.GetFirst(); + Paths polygons = new Paths(); + while (null != n) + { + if (!n.IsHole && polygons.Count > 0) + { + Slice s = new Slice(this); + s.polyTree = PolygonsToPolyTree(polygons); + yield return s; + polygons = new Paths(); + } + polygons.Add(n.Contour); + n = n.GetNext(); + } + if (polygons.Count > 0) + { + Slice s = new Slice(this); + s.polyTree = PolygonsToPolyTree(polygons); + yield return s; + } + } + + + private PolyTree PolygonsToPolyTree(Paths polygons) + { + var tree = new PolyTree(); + Clipper c = new Clipper(); + c.Clear(); + c.AddPaths(polygons, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, tree); + return tree; + } + + public void Subtract(Slice other) + { + Clipper c = new Clipper(); + c.Clear(); + c.AddPaths(Clipper.PolyTreeToPaths(polyTree), PolyType.ptSubject, true); + c.AddPaths(Clipper.PolyTreeToPaths(other.polyTree), PolyType.ptClip, true); + + polyTree = new PolyTree(); + c.Execute(ClipType.ctDifference, polyTree); + } + + public void SubtractFrom(Slice other) + { + Clipper c = new Clipper(); + c.Clear(); + c.AddPaths(PolyTreeToPolygons(other.polyTree), PolyType.ptSubject, true); + c.AddPaths(PolyTreeToPolygons(polyTree), PolyType.ptClip, true); + + polyTree = new PolyTree(); + c.Execute(ClipType.ctDifference, polyTree); + } + + private Paths PolyTreeToPolygons(PolyTree tree) + { + Paths p = new Paths(); + PolyNode n = tree.GetFirst(); + while (null != n) + { + p.Add(n.Contour); + n = n.GetNext(); + } + return p; + } + + #region LineHandler + + /// + /// Routines for dealing with a bunch of lines effeciently + /// + private class LineHandler + { + private class VectorWrapper + { + public Vector3 vector; + public List used_as_a = new List(); + public List used_as_b = new List(); + public VectorWrapper(Vector3 vector) + { + this.vector = vector; + } + public override string ToString() + { + return String.Format("{0}; count(a) = {1}, count(b)={2}", vector.ToString(), used_as_a.Count, used_as_b.Count); + } + } + private class Segment + { + internal VectorWrapper a; + internal VectorWrapper b; + bool normal_memoized = false; + private Vector3 normal; + public Vector3 Normal + { + get + { + if (!normal_memoized) + { + normal_memoized = true; + normal = b.vector - a.vector; + normal.Normalize(); + } + return normal; + } + } + public override string ToString() + { + return String.Format("{0}, {1}", a.vector.ToString(), b.vector.ToString()); + } + } + + // All units are in thousandths of an inch + // Floats have a precision of about 7 digits. + // An epsilion value of 0.001f will be valid for about +-100 inches + private const float epsilon = 0.01f; + private List vectors = new List(); + private List segments = new List(); + + public LineHandler() + { + } + + public void AddSegment (Vector3 a, Vector3 b) + { + var va = FindVectorWrapper(a); + var vb = FindVectorWrapper(b); + var s = new Segment(){a = va, b = vb}; + segments.Add(s); + va.used_as_a.Add(s); + vb.used_as_b.Add(s); + } + + private VectorWrapper FindVectorWrapper(Vector3 v) + { + int index = vectors.FindIndex(vectorwrapper => (vectorwrapper.vector - v).Length < epsilon); + if (index < 0) + { + vectors.Add(new VectorWrapper(v)); + index = vectors.Count-1; + } + return vectors[index]; + } + + /// + /// Convert the internal data to a list of the largest possible contiguous loops. + /// NOTE: This is destructive! + /// + /// + public List GetOuterLoops() + { + List loops = new List(); + + foreach (Segment segment in segments) + { + LineStrip loop = FindLargestLoop(segment); + if (loop != null) + { + loops.Add(loop); + } + } + return loops; + } + + /// + /// Measure the counter-clockwise angle around the normal in radians from one vector to another. + /// + /// + /// + /// + /// + private static float Angle(Vector3 one, Vector3 another, Vector3 normal) + { + float inv_cos = Vector3.Dot(one, another); + float inv_sin = Vector3.Dot(Vector3.Cross(one, another), normal); + + float angle = 0; + if (Math.Abs(inv_cos) > Math.Abs(inv_sin)) + { + if (inv_cos > 0) + { + // Between -Pi/2 and Pi/2 + angle = (float)Math.Asin(inv_sin); + } + else + { + angle = OpenTK.MathHelper.Pi - (float)Math.Asin(inv_sin); + } + } + else + { + // Determine the quadrant + if (inv_sin > 0) + { + // Angle is between 0 and Pi + angle = (float)Math.Acos(inv_cos); + } + else + { + // Angle is between Pi and 2*Pi + angle = (float)Math.Acos(-inv_cos) + OpenTK.MathHelper.Pi; + } + } + if (angle < 0) + { + angle += OpenTK.MathHelper.TwoPi; + } + return angle; + } + + + //private float height = .050f; + private LineStrip FindLargestLoop(Segment start) + { + //height += 0.050f; + Vector3 normal = new Vector3 (0, 0, 1); + + Segment next = start; + + List seen = new List(); + + while (!seen.Contains(next)) + { + //GL.Disable(EnableCap.Lighting); + //GL.LineWidth(3); + //GL.Begin(PrimitiveType.Lines); + //GL.Color3(Color.Blue); + //GL.Vertex3(next.a.vector.X, next.a.vector.Y, height); + //GL.Color3(Color.LightGreen); + //height += .010f; + //GL.Vertex3(next.b.vector.X, next.b.vector.Y, height); + //GL.End(); + //GL.LineWidth(1); + //GL.Enable(EnableCap.Lighting); + + seen.Add(next); + Segment best = null; + float largestAngle = 0; + foreach (Segment s in next.b.used_as_a) + { + float angle = Angle(-s.Normal, next.Normal, normal); + if (angle > largestAngle) + { + largestAngle = angle; + best = s; + } + } + if (best == null) + { + // No loops + return null; + } + + // Destructive: remove references to this element so it's not searched again. + // Note: only need to remove forward links (from used_as_a). The links from + // used_as_b could be cleared too, but it's not necessary for the algorithm. + next.b.used_as_a.Clear(); + //next.b.used_as_b.Clear(); + + next = best; + } + // Remove all up to the first matched index + int index = seen.IndexOf(next); + seen.RemoveRange(0, index); + + LineStrip loop = new LineStrip(); + foreach (Segment seg in seen) + { + loop.Append(seg.a.vector); + } + + return loop; + } + } + + #endregion + + public Slice GetOutsidePairs() + { + return GetPairs(true); + } + + public Slice GetInsidePairs() + { + return GetPairs(false); + } + + private Slice GetPairs(bool outside) + { + Slice s = new Slice(this); + PolyNode n = polyTree.GetFirst(); + Paths polygons = new Paths(); + while (null != n) + { + int depth = 0; + PolyNode parent = n.Parent; + while (parent != null) + { + depth++; + parent = parent.Parent; + } + int test = (depth - 1) % 4; + if ((outside && test < 2) || (!outside && test >= 2)) + { + polygons.Add(n.Contour); + } + n = n.GetNext(); + } + + s.polyTree = PolygonsToPolyTree(polygons); + return s; + } + + public void Add(Slice other) + { + Clipper c = new Clipper(); + c.Clear(); + c.AddPaths(Clipper.PolyTreeToPaths(polyTree), PolyType.ptSubject, true); + c.AddPaths(Clipper.PolyTreeToPaths(other.polyTree), PolyType.ptClip, true); + + polyTree = new PolyTree(); + c.Execute(ClipType.ctUnion, polyTree); + } + } +} diff --git a/Geometry/Triangle.cs b/Geometry/Triangle.cs new file mode 100644 index 0000000..ed39719 --- /dev/null +++ b/Geometry/Triangle.cs @@ -0,0 +1,126 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Geometry +{ + /// + /// Class describing a triangle. + /// + public class Triangle + { + private Vector3 a, b, c; + private Plane plane = null; + private Plane[] edgePlanes = null; + + public Triangle(Vector3 a, Vector3 b, Vector3 c) + { + this.a = a; + this.b = b; + this.c = c; + } + + public IEnumerable Vertices + { + get + { + yield return a; + yield return b; + yield return c; + } + } + + public Vector3 A + { + get { return a; } + } + public Vector3 B + { + get { return b; } + } + public Vector3 C + { + get { return c; } + } + + /// + /// Get the plane on which the triangle resides + /// + public Plane Plane + { + get + { + if (plane == null) + { + Vector3 normal = Vector3.Cross(b - a, c - b); + normal.Normalize(); + plane = new Plane(normal, a); + } + return plane; + } + } + + public bool IsPointInTriangle(Vector3 point) + { + foreach (Plane p in EdgePlanes) + { + if (p.Distance(point) < 0) + { + // Outside of triangle, no intersection + return false; + } + } + return true; + } + + /// + /// Iterate over planes intersecting the triangle's edges, and perpendicular to the plane of the triangle. + /// The normals for the plane point to the inside of the triangle. + /// + public IEnumerable EdgePlanes + { + get + { + if (edgePlanes == null) + { + edgePlanes = new Plane[3]; + edgePlanes[0] = ComputeEdgePlane(a, b, Plane.Normal); + edgePlanes[1] = ComputeEdgePlane(b, c, Plane.Normal); + edgePlanes[2] = ComputeEdgePlane(c, a, Plane.Normal); + } + for (int i = 0; i < 3; i++) + { + yield return edgePlanes[i]; + } + } + } + + private Plane ComputeEdgePlane(Vector3 p1, Vector3 p2, Vector3 normal) + { + Vector3 edgeNormal = Vector3.Cross(p1 - p2, normal); + edgeNormal.Normalize(); + return new Plane(edgeNormal, p1); + } + } +} diff --git a/Geometry/TriangleMesh.cs b/Geometry/TriangleMesh.cs new file mode 100644 index 0000000..158a10a --- /dev/null +++ b/Geometry/TriangleMesh.cs @@ -0,0 +1,304 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; +using System.Collections; + +namespace Geometry +{ + /// + /// Class for building and storing a mesh of triangles. Each triangle contains pointers to adjacent triangles + /// through the edges, and edges can also be enumerated. All operations are safe at any point, and iterating + /// over edges and triangles is guarenteed accurate and complete if no add or clean operation occurs. + /// + public class TriangleMesh + { + private float epsilon = 0.0001f; // Distance between vertices considered to be unique - set to a value valid for inches. + private List vertices; + private Vector3 minPoint = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + private Vector3 maxPoint = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); + private List triangles; + private List segments; + + internal class TriangleIndices + { + public int a, b, c; + public List edges = new List(3); + public int LongestEdgeIndex() + { + float longest = 0; + int index = -1; + for (int i = 0; i < 3; i++) + { + float length = edges[i].Length(); + if (length > longest || index < 0) + { + index = i; + longest = length; + } + } + return index; + } + } + public class Edge + { + internal TriangleMesh parentMesh; + internal int a, b; + internal List triangles = new List(); + + public Edge(TriangleMesh parent) + { + this.parentMesh = parent; + } + + internal bool Matches(int a, int b) + { + return (this.a == a && this.b == b) || (this.b == a && this.a == b); + } + + internal float Length() + { + Vector3 v1 = parentMesh.vertices[a]; + Vector3 v2 = parentMesh.vertices[b]; + return (v1 - v2).Length; + } + + public LineSegment LineSegment + { + get { return new LineSegment(parentMesh.vertices[a], parentMesh.vertices[b]); } + } + + public IEnumerable Triangles + { + get + { + var num = triangles.Count; + while (--num >= 0) + { + var triangleIndices = triangles[num]; + yield return new Triangle(parentMesh.vertices[triangleIndices.a], parentMesh.vertices[triangleIndices.b], parentMesh.vertices[triangleIndices.c]); + } + } + } + + public IEnumerable Vertices + { + get + { + yield return parentMesh.vertices[a]; + yield return parentMesh.vertices[b]; + } + } + } + + public TriangleMesh() + { + vertices = new List(); + triangles = new List(); + segments = new List(); + } + + /// + /// Ensure all triangles have some non-zero area and that each edge connects to exactly two triangles. + /// Returns false if the above conditions are not true and can't be repaired. + /// + /// + public bool Clean() + { + foreach (Edge e in Edges) + { + if (e.Vertices.Count() != 2) + { + return false; + } + } + + for (int i = 0; i < triangles.Count; i++) + { + var tri1 = triangles[i]; + int longestEdgeIndex = tri1.LongestEdgeIndex(); + var edge = tri1.edges[longestEdgeIndex]; + + int[] vertexIndices1 = new int[] { tri1.a, tri1.b, tri1.c }; + Vector3 oppositePoint = vertices[vertexIndices1[(longestEdgeIndex + 2) % 3]]; + + // Is the triangle really small, basically a line? + // Note: use a test value of half epsilon to guarentee + if (edge.LineSegment.Distance(oppositePoint) < (epsilon / 2.0f)) + { + // If so, find the quad formed by the two attached triangles and flip the dividing line. + var tri2 = edge.triangles.First(t => t != tri1); + + int splitEdgeIndex = tri2.edges.FindIndex(e => e == edge); + + int[] vertexIndices2 = new int[] { tri2.a, tri2.b, tri2.c }; + + + int a = vertexIndices2[(splitEdgeIndex + 1) % 3]; + int b = vertexIndices2[(splitEdgeIndex + 2) % 3]; + int c = vertexIndices2[(splitEdgeIndex + 3) % 3]; + int d = vertexIndices1[(longestEdgeIndex + 2) % 3]; + + edge.a = b; + edge.b = d; + + tri2.a = b; + tri2.b = c; + tri2.c = d; + + tri1.a = d; + tri1.b = a; + tri1.c = b; + + SetTriangleEdgePointers(tri2); + SetTriangleEdgePointers(tri1); + } + } + return true; + } + + /// + /// Get or set the minimum distance required to distinguish unique vertices. + /// + public float Epsilon + { + get { return epsilon; } + set { epsilon = value; } + } + + /// + /// Add a new triangle with vertices a, b, and c. + /// Vertices must be specified in counter-clockwise order when viewed from front. + /// + /// + /// + /// + public void AddTriangle(Vector3 a, Vector3 b, Vector3 c) + { + var newTriangle = new TriangleIndices() { a = AddVertex(a), b = AddVertex(b), c = AddVertex(c) }; + if (newTriangle.a != newTriangle.b && newTriangle.b != newTriangle.c && newTriangle.c != newTriangle.a) + { + triangles.Add(newTriangle); + var triangle = triangles[triangles.Count - 1]; + SetTriangleEdgePointers(triangle); + } + else + { + // Bad triangle = has colinear edges. + } + } + + private void SetTriangleEdgePointers(TriangleIndices tri) + { + foreach (var edge in tri.edges) + { + edge.triangles.RemoveAll(t => t == tri); + } + tri.edges.Clear(); + tri.edges.Add(AddEdge(tri.a, tri.b)); + tri.edges.Add(AddEdge(tri.b, tri.c)); + tri.edges.Add(AddEdge(tri.c, tri.a)); + tri.edges[0].triangles.Add(tri); + tri.edges[1].triangles.Add(tri); + tri.edges[2].triangles.Add(tri); + } + + public IEnumerable Edges + { + get + { + var num = segments.Count; + while (--num >= 0) + { + yield return segments[num]; + } + } + } + + private Edge AddEdge(int indexA, int indexB) + { + var index = segments.FindIndex(seg => seg.Matches(indexA, indexB)); + if (index < 0) + { + index = segments.Count; + segments.Add(new Edge(this) { a = indexA, b = indexB }); + } + return segments[index]; + } + + public void AddTriangle(Triangle tri) + { + AddTriangle(tri.A, tri.B, tri.C); + } + + public int TriangleCount + { + get { return triangles.Count; } + } + + /// + /// Iterate over the triangles in the mesh. + /// + public IEnumerable Triangles + { + get + { + var num = triangles.Count; + while (--num >= 0) + { + var triangleIndices = triangles[num]; + yield return new Triangle(vertices[triangleIndices.a], vertices[triangleIndices.b], vertices[triangleIndices.c]); + } + } + } + + public Vector3 MinPoint + { + get { return minPoint; } + } + + public Vector3 MaxPoint + { + get { return maxPoint; } + } + + private int AddVertex(Vector3 vertex) + { + var index = vertices.FindIndex(v => (v - vertex).Length < epsilon); + if (index < 0) + { + index = vertices.Count; + vertices.Add(vertex); + + minPoint.X = Math.Min(minPoint.X, vertex.X); + minPoint.Y = Math.Min(minPoint.Y, vertex.Y); + minPoint.Z = Math.Min(minPoint.Z, vertex.Z); + + maxPoint.X = Math.Max(maxPoint.X, vertex.X); + maxPoint.Y = Math.Max(maxPoint.Y, vertex.Y); + maxPoint.Z = Math.Max(maxPoint.Z, vertex.Z); + } + return index; + } + } +} diff --git a/Geometry/packages.config b/Geometry/packages.config new file mode 100644 index 0000000..4fa96a2 --- /dev/null +++ b/Geometry/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Robot/DataConverter.cs b/Robot/DataConverter.cs new file mode 100644 index 0000000..e9faabe --- /dev/null +++ b/Robot/DataConverter.cs @@ -0,0 +1,120 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Robot +{ + /// + /// Converts larger types to bytes. + /// All conversions are done little-endian style, with + /// the least significant byte first and most significant + /// byte last in the list of bytes. + /// + class DataConverter + { + [StructLayout(LayoutKind.Explicit)] + struct int_or_float + { + [FieldOffset(0)] + public float FloatValue; + + [FieldOffset(0)] + public int IntValue; + } + + /// + /// Convert a sequence of 4 bytes to a floating point value. + /// The sequence is expected to be in Little Endian format, I.E + /// least significant byte first. + /// + /// + /// + public static float FloatFromBytes(IEnumerable bytes) + { + return new int_or_float { IntValue = IntFromBytes(bytes) }.FloatValue; + } + + /// + /// Convert a float to 4 bytes. The first byte is the least + /// significant part of the float. + /// + /// + /// + public static byte[] BytesFromFloat(float f) + { + return BytesFromInt(new int_or_float { FloatValue = f }.IntValue); + } + + /// + /// Convert a short to 2 bytes, least significant part of the + /// short will be byte[0] and most significant will be byte[1]. + /// + /// + /// + public static byte[] BytesFromShort(short s) + { + byte[] bytes = new byte[2]; + for (int i = 0; i < 2; i++) + { + bytes[i] = (byte)((s >> (i * 8)) & 0xFF); + } + return bytes; + } + + /// + /// Convert an int to 2 bytes, least significant part of the + /// int will be in byte[0] and the most significant part will + /// be in byte[3]. + /// + /// + /// + public static byte[] BytesFromInt(int value) + { + byte[] bytes = new byte[4]; + for (int i = 0; i < 4; i++) + { + bytes[i] = (byte)((value >> (i * 8)) & 0xFF); + } + return bytes; + } + + /// + /// Convert 4 bytes to an integer. The first byte should + /// be the least significant, and the last the most significant. + /// + /// + /// + public static int IntFromBytes(IEnumerable bytes) + { + int raw = 0; + int shifter = 0; + foreach (byte b in bytes) + { + raw |= ((int)b) << shifter; + shifter += 8; + } + return raw; + } + } +} diff --git a/Robot/IRobotCommand.cs b/Robot/IRobotCommand.cs new file mode 100644 index 0000000..1d3aff7 --- /dev/null +++ b/Robot/IRobotCommand.cs @@ -0,0 +1,33 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Robot +{ + public abstract class IRobotCommand + { + internal abstract byte[] GenerateCommand(); + internal abstract void ProcessResponse(byte[] data); + internal abstract bool IsDataValid(); + } +} diff --git a/Robot/MoveCommand.cs b/Robot/MoveCommand.cs new file mode 100644 index 0000000..67cf25d --- /dev/null +++ b/Robot/MoveCommand.cs @@ -0,0 +1,55 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Robot +{ + internal class MoveCommand : StatusCommand + { + protected override byte CommandCode + { + get { return 0x32; } + } + + private UInt16 thousandths_per_second; + private Vector3 toLocation = new Vector3(0, 0, 0); + + public MoveCommand(Vector3 location, float inches_per_second) : base() + { + toLocation = location; + this.thousandths_per_second = (UInt16)(inches_per_second * 1000); + } + + internal override byte[] GenerateCommand() + { + List command = new List(); + command.Add(CommandCode); + command.AddRange(DataConverter.BytesFromShort((short)thousandths_per_second)); + command.AddRange(DataConverter.BytesFromFloat(toLocation.X)); + command.AddRange(DataConverter.BytesFromFloat(toLocation.Y)); + command.AddRange(DataConverter.BytesFromFloat(toLocation.Z)); + return command.ToArray(); + } + } +} diff --git a/Robot/Properties/AssemblyInfo.cs b/Robot/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5e07526 --- /dev/null +++ b/Robot/Properties/AssemblyInfo.cs @@ -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("Robot")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Robot")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("ff96beb0-6ea8-4dca-b8da-0d8feab45fb9")] + +// 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")] diff --git a/Robot/Robot.cs b/Robot/Robot.cs new file mode 100644 index 0000000..6aeea09 --- /dev/null +++ b/Robot/Robot.cs @@ -0,0 +1,313 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Serial; +using System.Timers; +using OpenTK; +using Commands; + +namespace Robot +{ + public class Robot + { + private Queue commands = new Queue(); + public EventHandler onRobotStatusChange; + bool sendResumeCommand = false; + bool sendPauseCommand = false; + bool sendCancelCommand = false; + bool sendEnableStepperCommand = false; + bool sendDisableStepperCommand = false; + public void SendPauseCommand() + { + sendPauseCommand = true; + sendResumeCommand = false; + } + public void SendResumeCommand() + { + sendResumeCommand = true; + sendPauseCommand = false; + } + public void CancelPendingCommands() + { + sendCancelCommand = true; + lock (commands) + { + commands.Clear(); + } + } + public void EnableMotors() + { + sendDisableStepperCommand = false; + sendEnableStepperCommand = true; + } + public void DisableMotors() + { + sendEnableStepperCommand = false; + sendDisableStepperCommand = true; + } + + IRobotCommand currentCommand = null; + SerialPortWrapper serial; + Timer t; + int elapsedCounter = 0; + + Vector3 currentPosition = new Vector3(0, 0, 0); + + Vector3 lastPosition = new Vector3(0, 0, 0); + bool lastPositionKnown = false; + + public Robot(SerialPortWrapper serial) + { + this.serial = serial; + serial.newDataAvailable += new SerialPortWrapper.newDataAvailableDelegate(NewDataAvailable); + serial.receiveDataError += new SerialPortWrapper.receiveDataErrorDelegate(ReceiveDataError); + t = new Timer(); + t.Interval = 50; + t.Start(); + t.Elapsed += new ElapsedEventHandler(t_Elapsed); + } + + void t_Elapsed(object sender, ElapsedEventArgs e) + { + t.Stop(); + lock (thisLock) + { + if (serial != null && serial.IsOpen) + { + elapsedCounter++; + if ((elapsedCounter * 50) > (1000)) // More than 1 second to reply + { + Console.WriteLine("Device Timeout!"); + + // Send a status command + currentCommand = new StatusCommand(); + + serial.Transmit(currentCommand.GenerateCommand(), 0x21); + elapsedCounter = 0; + } + } + } + t.Start(); + } + + #region Serial Interface Callbacks + + private void ReceiveDataError(byte err) + { + Console.WriteLine("Data Error: " + err); + } + + Object thisLock = new Object(); + private void NewDataAvailable(SerialPortWrapper.SimpleSerialPacket packet) + { + lock (thisLock) + { + if (currentCommand == null) + { + Console.WriteLine("Error: Received data, but no command was sent!"); + foreach (byte b in packet.Data) + { + Console.Write(b.ToString("x") + ", "); + } + Console.WriteLine(); + } + else + { + currentCommand.ProcessResponse(packet.Data); + if (currentCommand.IsDataValid()) + { + // See if there's any state information in the command used to + // update location or other fields... + int locations = 0; + if (currentCommand is StatusCommand) + { + StatusCommand c = currentCommand as StatusCommand; + currentPosition = c.CurrentPosition; + locations = c.Locations; + if (this.lastPositionKnown == false) + { + lastPosition = currentPosition; + lastPositionKnown = true; + } + if (onRobotStatusChange != null) + { + //onPositionUpdate(new object[] { currentPosition, c.time }, EventArgs.Empty); + onRobotStatusChange(c, EventArgs.Empty); + } + } + if (currentCommand is MoveCommand) + { + MoveCommand m = currentCommand as MoveCommand; + locations = m.Locations; + currentPosition = m.CurrentPosition; + if (this.lastPositionKnown == false) + { + lastPosition = currentPosition; + lastPositionKnown = true; + } + if (onRobotStatusChange != null) + { + //onPositionUpdate(new object[] { currentPosition, m.time }, EventArgs.Empty); + onRobotStatusChange(m, EventArgs.Empty); + } + } + + currentCommand = GetNextCommand(locations); + + elapsedCounter = 0; + serial.Transmit(currentCommand.GenerateCommand(), 0x21); + } + else + { + Console.WriteLine("Error: Did not process data correctly!"); + } + } + } + } + + #endregion + + private IRobotCommand GetNextCommand(int locations) + { + currentCommand = null; + + if (sendCancelCommand) + { + currentCommand = new CancelCommand(); + sendCancelCommand = false; + } + else if (sendResumeCommand) + { + currentCommand = new ResumeCommand(); + sendResumeCommand = false; + } + else if (sendPauseCommand) + { + currentCommand = new PauseCommand(); + sendPauseCommand = false; + } + else if (sendEnableStepperCommand) + { + currentCommand = new StepperEnableCommand(); + sendEnableStepperCommand = false; + } + else if (sendDisableStepperCommand) + { + currentCommand = new StepperDisableCommand(); + sendDisableStepperCommand = false; + } + else if (locations > 0) + { + while (currentCommand == null && commands.Count > 0) + { + ICommand command = commands.Dequeue(); + if (command is MoveTool) + { + MoveTool m = command as MoveTool; + GoTo(m.Target, m.Speed); + } + } + } + + //if (currentCommand == null && locations > 0) + //{ + // // Ok to pass in another movement command + // // TODO: rework this to use a local buffer... + // if (onRobotReady != null) + // { + // onRobotReady(this, EventArgs.Empty); + // } + //} + + if (currentCommand == null) + { + currentCommand = new StatusCommand(); + } + + return currentCommand; + } + + public Vector3 GetPosition() + { + return currentPosition; + } + + public void AddCommand(ICommand command) + { + commands.Enqueue(command); + } + + /// + /// Run the router from the current position to the given position + /// + /// Destination location in inches + /// Tool speed in inches per second + private void GoTo(Vector3 p, float inches_per_minute) + { + lock (thisLock) + { + if (this.lastPositionKnown == false) + { + inches_per_minute = Math.Min(MaxInchesPerMinute.X, Math.Min(MaxInchesPerMinute.Y, MaxInchesPerMinute.Z)); + } + Vector3 delta = lastPosition - p; + lastPosition = p; + lastPositionKnown = true; + + float inches = delta.Length; + + UInt16 time_milliseconds = (UInt16)(1000 * 60 * inches / inches_per_minute); + + if (delta.Length > 0) + { + if (Math.Abs(delta.X) > 0.0001f) + { + inches_per_minute = Math.Min(MaxInchesPerMinute.X, inches_per_minute); + } + if (Math.Abs(delta.Y) > 0.0001f) + { + inches_per_minute = Math.Min(MaxInchesPerMinute.Y, inches_per_minute); + } + if (Math.Abs(delta.Z) > 0.0001f) + { + inches_per_minute = Math.Min(MaxInchesPerMinute.Z, inches_per_minute); + } + + currentCommand = new MoveCommand(p, inches_per_minute / 60.0f); + } + else + { + Console.WriteLine("Ignoring command with time of 0"); + } + } + } + + + + Vector3 MaxInchesPerMinute + { + get { return new Vector3(400, 400, 40); } + } + + + } +} diff --git a/Robot/Robot.csproj b/Robot/Robot.csproj new file mode 100644 index 0000000..9defff4 --- /dev/null +++ b/Robot/Robot.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {C7BBABF0-0698-4C5F-A510-7E160C8771A5} + Library + Properties + Robot + Robot + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\OpenTK.1.1.1456.5398\lib\NET40\OpenTK.dll + + + + + + + + + + + + + + + + + + + + + {0be94aa8-662b-45ec-b95a-545180f54618} + Commands + + + {9477fd2c-fe8f-4653-ab25-bfa14338a932} + Serial + + + + + + + + \ No newline at end of file diff --git a/Robot/SingleByteStatusCommands.cs b/Robot/SingleByteStatusCommands.cs new file mode 100644 index 0000000..798e65f --- /dev/null +++ b/Robot/SingleByteStatusCommands.cs @@ -0,0 +1,70 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Robot +{ + internal abstract class SingleByteStatusCommand : StatusCommand + { + internal override byte[] GenerateCommand() + { + return new byte[] { CommandCode }; + } + } + + internal class StepperDisableCommand : SingleByteStatusCommand + { + protected override byte CommandCode { get { return 0x14; } } + public StepperDisableCommand() : base() { } + } + + internal class StepperEnableCommand : SingleByteStatusCommand + { + protected override byte CommandCode { get { return 0x15; } } + public StepperEnableCommand() : base() { } + } + + internal class CancelCommand : SingleByteStatusCommand + { + protected override byte CommandCode { get { return 0x13; } } + public CancelCommand() : base() { } + } + + internal class PauseCommand : SingleByteStatusCommand + { + protected override byte CommandCode { get { return 0x11; } } + public PauseCommand() : base() { } + } + + internal class ResumeCommand : SingleByteStatusCommand + { + protected override byte CommandCode { get { return 0x12; } } + public ResumeCommand() : base() { } + } + + internal class ResetCommand : SingleByteStatusCommand + { + protected override byte CommandCode { get { return 0x88; } } + public ResetCommand() : base() { } + } +} diff --git a/Robot/StatusCommand.cs b/Robot/StatusCommand.cs new file mode 100644 index 0000000..cfdb94f --- /dev/null +++ b/Robot/StatusCommand.cs @@ -0,0 +1,109 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; + +namespace Robot +{ + public class StatusCommand : IRobotCommand + { + private Vector3 currentPosition = new Vector3(0, 0, 0); + private bool data_valid = false; + private bool is_moving = false; + private int locations; + + // TODO: find a better way to handle the time + public float time; + + public StatusCommand() + { + } + + protected virtual byte CommandCode + { + get { return 0x77; } + } + + public int Locations + { + get { return locations; } + } + + public Vector3 CurrentPosition + { + get { return currentPosition; } + } + + internal override bool IsDataValid() + { + return data_valid; + } + + internal bool IsMoving() + { + return is_moving; + } + + internal override byte[] GenerateCommand() + { + return new byte[] { CommandCode }; + } + + bool paused = false; + bool pausing = false; + bool steppers_enabled = false; + public bool Paused { get { return paused; } } + public bool Pausing { get { return pausing; } } + public bool SteppersEnabled { get { return steppers_enabled; } } + + internal override void ProcessResponse(byte[] data) + { + if (data.Length <= 18) + { + data_valid = false; + return; + } + data_valid = (data[0] == CommandCode); + + List data_list = new List(data); + + time = ((float)DataConverter.IntFromBytes(data_list.GetRange(15, 4))) / 10.0f; + currentPosition = new Vector3( + DataConverter.FloatFromBytes(data_list.GetRange(1, 4)), + DataConverter.FloatFromBytes(data_list.GetRange(5, 4)), + DataConverter.FloatFromBytes(data_list.GetRange(9, 4))); + + byte status_bits = data[13]; + paused = (status_bits & 0x01) > 0; + pausing = (status_bits & 0x02) > 0; + steppers_enabled = (status_bits & 0x04) > 0; + + is_moving = true;// x_moving | y_moving | z_moving; + + locations = (int)(data[14]); + + //Console.WriteLine("{0}, {1}, {2}, {3}, l = {4}, paused = {5}, pausing = {6}, resuming = {7}", time, currentPosition.X, currentPosition.Y, currentPosition.Z, locations, paused, pausing, resuming); + } + + } +} diff --git a/Robot/packages.config b/Robot/packages.config new file mode 100644 index 0000000..d0ea9c3 --- /dev/null +++ b/Robot/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Router/GCodeLoader.cs b/Router/GCodeLoader.cs new file mode 100644 index 0000000..421d394 --- /dev/null +++ b/Router/GCodeLoader.cs @@ -0,0 +1,144 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Drawing; +using OpenTK; +using Commands; +using System.IO; + +namespace Router +{ + public class GCodeLoader + { + public static List Load(string filename) + { + string[] lines = System.IO.File.ReadAllLines(filename); + var commands = new List(); + float scale = 1.0f, speed = 0, x = 0, y = 0, z = 0f; + foreach (string s in lines) + { + Regex r = new Regex("^G(?\\d+)"); + if (r.IsMatch (s)) + { + Match m = r.Match(s); + Int32 g_value = Int32.Parse (m.Groups["G_VALUE"].Value); + + if (g_value == 0 || g_value == 1) + { + // Rapid positioning or linear interpolation + // Go to X, Y, Z at feedrate F. + GetFloat(s, "F", ref speed); + GetFloat(s, "X", ref x); + GetFloat(s, "Y", ref y); + GetFloat(s, "Z", ref z); + Vector3 toPoint = new Vector3(x, y, z); + + commands.Add(new MoveTool(toPoint * scale, speed * scale)); + + } + else if (g_value == 4) + { + // Dwell Time (X, U, or P): dwell time in milliseconds + } + else if (g_value == 20) + { + // Inch Mode + scale = 1.0f; + } + else if (g_value == 21) + { + // Metric Mode + scale = 1.0f / (25.4f); + } + else if (g_value == 90) + { + // Absolute Programming + //Console.WriteLine("Absolute Programming"); + } + else + { + Console.WriteLine("G code is not understood: " + s); + } + } + } + return commands; + } + + public static void ExportGCode(List commands, string filename) + { + using (var file = File.CreateText(filename)) + { + file.WriteLine("G20 (Units are Inches)"); + file.WriteLine("G90 (Absolute Positioning)"); + file.WriteLine("G94 (Units per Minute feed rate)"); + float lastSpeed = -1; + float lastHeight = 0; + float scale = 1.0f; + foreach (ICommand command in commands) + { + if (command is MoveTool) + { + MoveTool m = command as MoveTool; + float speed = m.Speed * scale; + Vector3 target = m.Target * scale; + float height = target.Z; + if ((height + 0.001f) < lastHeight) + { + speed = Math.Min(10.0f, speed); // Maximum plunge speed is 10 inches per minute (make a parameter...) + } + lastHeight = height; + if (lastSpeed != speed) + { + lastSpeed = speed; + file.WriteLine("G1 F{0:F4}", speed); + } + file.WriteLine("G1 X{0:F4} Y{1:F4} Z{2:F4}", target.X, target.Y, target.Z); + } + } + } + } + + /// + /// Get a float in the format of "G01 Z-0.0100 F2.00", where string is Z, F, or other preceding character + /// + /// + /// + /// + private static bool GetFloat(string input, string find, ref float f) + { + Regex r = new Regex(find + @"(?-?[\d\.]+)"); + if (r.IsMatch(input)) + { + Match m = r.Match(input); + string value_string = m.Groups["VALUE"].Value; + + f = float.Parse(value_string); + //Console.WriteLine("Value for " + find + " is " + f); + return true; + } + return false; + } + + + } +} diff --git a/Router/Paths/PathPlanner.cs b/Router/Paths/PathPlanner.cs new file mode 100644 index 0000000..526c0e4 --- /dev/null +++ b/Router/Paths/PathPlanner.cs @@ -0,0 +1,710 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Geometry; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.Drawing; + +namespace Router.Paths +{ + public class PathPlanner + { + /// + /// Find a toolpath which will remove everything described in the slice. + /// + /// + public static void PlanPaths(Slice slice) + { + // 1. Get all small holes in the slice - rout them first + // 2. Rout everything remaining in the slice from + } + + public static List PlanPaths(TriangleMesh triangles, List tabs, Router router) + { + List routs = new List(); + + float toolRadius = router.ToolDiameter / 2.0f; // Router units are inches + float maxCutDepth = router.MaxCutDepth; + float lastPassHeight = router.LastPassHeight; + float cleanPassFactor = 0.90f; // 90% of the tool radius will be removed on the clean pass + + float minZ = triangles.MinPoint.Z; + float maxZ = triangles.MaxPoint.Z; + + Slice boundary = new Slice(triangles, new Plane(Vector3.UnitZ, new Vector3(0, 0, minZ))); + boundary.Offset(toolRadius * (cleanPassFactor + 1.05f)); // Note: this is slightly larger to allow some polygon width to exist + foreach (var tab in tabs) + { + if (tab.TabLocations.Count() == 0) + { + boundary.Add(tab.Boundary); + } + } + + Slice top = new Slice(triangles, new Plane(Vector3.UnitZ, new Vector3(0, 0, maxZ))); + top.SubtractFrom(boundary); + + //GL.PushMatrix(); + //GL.Translate(0, 0, 1); + //DrawSlice(Color.Black, Color.DarkGray, boundary); + //GL.PopMatrix(); + + Slice holes = top.PolygonsWithoutHoles(); + + + List holeRouts = new List(); + foreach (var polygon in holes.IndividualPolygons()) + { + holeRouts.Add(new Hole(polygon, toolRadius, cleanPassFactor)); + } + + // Figure out a nice even maximum cut depth + int layers = (int)((maxZ - minZ) / maxCutDepth + 0.95f); + float actualCutDepth = (maxZ - minZ) / layers; + + for (float height = minZ; height < maxZ; height += actualCutDepth) + { + Slice current = new Slice(triangles, new Plane(Vector3.UnitZ, new Vector3(0, 0, height))); + current.Offset(toolRadius); + GL.PushMatrix(); + GL.Translate(0, 0, -0.001f); + //DrawSlice(Color.Tan, Color.Gray, boundary); + GL.PopMatrix(); + //DrawSlice(Color.Red, Color.Blue, current); + + + Slice original = new Slice(current); + current.SubtractFrom(boundary); + + // current will now be several polygons representing the area to rout out, minus the tool radius offset on either side. + // Split it into polygons around the outside and inside of parts (the first two will be outside polygons, the next two inside, next two outside, ...). + Slice outsidePairs = current.GetOutsidePairs(); + //DrawSlice(Color.Gold, Color.Yellow, outsidePairs); + + Slice insidePairs = current.GetInsidePairs(); + //DrawSlice(Color.Orange, Color.NavajoWhite, insidePairs); + + + // If a polygon has no holes, that means it's a hole in the actual shape to be cut out. + // These can be cut first before outside cuts are done. + holes = current.PolygonsWithoutHoles(); + foreach (var holePolygon in insidePairs.PolygonsWithoutHoles().IndividualPolygons()) + { + foreach (var hole in holeRouts) + { + if (hole.Contains(holePolygon)) + { + hole.AddPolygon(holePolygon); + break; + } + } + } + + + // Rout all outside paths. These will be done from top down, one layer at a time for structural reasons. + // For the top several layers, two paths could be combined... + //var withHoles = outsidePairs.PolygonsWithHoles(); + var outsideRouts = RoutAreasWithHoles(outsidePairs, toolRadius, cleanPassFactor, tabs, false); + var newLines = new List(); + foreach (var line in outsideRouts) + { + var r = new LineStrip(); + r.AddRange(line.Vertices); + r.Append(line.Vertices[0]); + newLines.Add(r); + } + routs.InsertRange(0, newLines); + + + outsideRouts = RoutAreasWithHoles(insidePairs.PolygonsWithHoles(), toolRadius, cleanPassFactor, tabs, true); + newLines = new List(); + foreach (var line in outsideRouts) + { + var r = new LineStrip(); + r.AddRange(line.Vertices); + r.Append(line.Vertices[0]); + newLines.Add(r); + } + routs.InsertRange(0, newLines); + } + + foreach (var hole in holeRouts) + { + var newRouts = new List(); + foreach (var line in hole.GetRouts()) + { + LineStrip r = new LineStrip(); + // Note: these might not start and end in the same place, but that's OK. + r.AddRange(line.Vertices); + newRouts.Add(r); + } + routs.InsertRange(0, newRouts); + } + + // Adjust the lowest point - allow plunging through the bottom of the material for a clean cut. + foreach (LineStrip r in routs) + { + for (int i = 0; i < r.Vertices.Count; i++) + { + var point = r.Vertices[i]; + if (point.Z < (minZ + .0001f)) + { + r.Vertices[i] = new Vector3(point.X, point.Y, lastPassHeight); + } + } + } + return routs; + } + + /// + /// Generate paths to remove the area described in polygons. The last path will be against the surface of the material, determined + /// by the "inside" parameter. If inside is true, the last pass will be the outer-most pass. Otherwise it will be the inner most. + /// The cleanPassFactor is respected on the last pass: only toolRadius * cleanPassFactor width of material will be removed. + /// + /// + /// + /// + /// + /// + /// + private static List RoutAreasWithHoles(Slice polygons, float toolRadius, float cleanPassFactor, List tabs, bool inside) + { + List paths = new List(); + foreach (var individualPolygon in polygons.IndividualPolygons()) + { + paths.AddRange(RoutAreasWithHolesHelper(individualPolygon, toolRadius, cleanPassFactor, tabs, inside)); + } + return paths; + } + private static List RoutAreasWithHolesHelper(Slice polygons, float toolRadius, float cleanPassFactor, List tabs, bool inside) + { + List paths = new List(); + + Slice.LineType firstLineType = Slice.LineType.Hole; + Slice.LineType lastLineType = Slice.LineType.Outside; + float offset = toolRadius * cleanPassFactor; + if (inside) + { + firstLineType = Slice.LineType.Outside; + lastLineType = Slice.LineType.Hole; + offset = -offset; + } + + + + // The holes are the paths surrounding the final object - rout them last for the cleanest finish. + // Also avoid any tabs that may exist on these paths. + Slice lastPaths = new Slice(polygons.GetLines(firstLineType), polygons.Plane); + + var routLast = lastPaths.GetLines(Slice.LineType.All); + foreach (var line in routLast) + { + var fixedLine = line; + foreach (Tabs t in tabs) + { + fixedLine = t.AvoidTabs(fixedLine); + } + paths.Add(fixedLine); + } + + // Compute the rest of the routs required to remove the material. Do these first - they will + // be a distance away from the final product, so if forces push the bit around some, there's no issue. + // Also avoid tabs on these paths. + lastPaths.Offset(offset); + + Slice obliterate = new Slice(polygons.GetLines(lastLineType), polygons.Plane); + if (inside) + { + obliterate.SubtractFrom(lastPaths); + } + else + { + obliterate.Subtract(lastPaths); + } + + //DrawSlice(Color.Orange, Color.Red, obliterate); + + var lines = PathTree.ObliterateSlice(obliterate, toolRadius); + + foreach (var line in lines) + { + var fixedLine = line; + foreach (Tabs t in tabs) + { + fixedLine = t.AvoidTabs(fixedLine); + } + paths.Insert(0, fixedLine); + } + + return paths; + } + + private static void DrawSlice(Color lineColor, Color planeColor, Slice s) + { + GL.Disable(EnableCap.Lighting); + GL.Color3(lineColor); + foreach (var line in s.GetLines(Slice.LineType.All)) + { + GL.Begin(PrimitiveType.LineLoop); + foreach (var p in line.Vertices) + { + GL.Vertex3(p); + } + GL.End(); + } + GL.PointSize(2); + GL.Color3(Color.Black); + foreach (var line in s.GetLines(Slice.LineType.All)) + { + GL.Begin(PrimitiveType.Points); + foreach (var p in line.Vertices) + { + GL.Vertex3(p); + } + GL.End(); + } + GL.PointSize(1); + GL.Enable(EnableCap.Lighting); + + GL.Color3(planeColor); + GL.Begin(PrimitiveType.Triangles); + GL.Normal3(s.Plane.Normal); + foreach (var t in s.Triangles()) + { + foreach (var p in t.Vertices) + { + GL.Vertex3(p); + } + } + GL.End(); + } + + #region Helper Classes + + // Contains polygons at various heights which are all in the same hole + public class Hole + { + public Slice topPolygon; + public List polygons; + public float toolRadius; + public float cleanRoutFactor; + + public Hole(Slice top, float toolRadius, float cleanRoutFactor) + { + topPolygon = top; + polygons = new List(); + this.cleanRoutFactor = cleanRoutFactor; + this.toolRadius = toolRadius; + } + + public void AddPolygon(Slice polygon) + { + polygons.Add(polygon); + } + + public List GetRouts() + { + GL.PushMatrix(); + Slice lastPolygon = null; + + for (int i = polygons.Count - 1; i >= 0; i--) + { + var p = polygons[i]; + var routLast = p.GetLines(Slice.LineType.Outside).First(s => true); + routLast.Vertices.Add(routLast.Vertices[0]); + + Slice obliterate = new Slice(p); + obliterate.Offset(-toolRadius * cleanRoutFactor); + + var routFirst = PathTree.ObliterateSlice(obliterate, toolRadius * 2.0f); + + if (lastPolygon == null) + { + lastPolygon = p; + } + + bool first = true; + foreach (var rout in routFirst) + { + first = true; + foreach (var point in rout.Vertices) + { + AddRoutPoint(lastPolygon, point, first); + first = false; + } + } + + first = true; + foreach (var point in routLast.Vertices) + { + AddRoutPoint(p, point, first); + first = false; + } + lastPolygon = p; + } + GL.PopMatrix(); + return routs; + } + + private List routs = new List(); + private void AddRoutPoint(Slice currentPolygon, Vector3 newPoint, bool check = true) + { + if (routs.Count == 0) + { + routs.Add(new LineStrip()); + } + LineStrip currentRout = routs[routs.Count-1]; + if (check) + { + if (currentRout.Vertices.Count > 0) + { + Slice larger = new Slice(currentPolygon); + larger.Offset(toolRadius * 1.05f); + Vector3 lastPoint = currentRout.Vertices[currentRout.Vertices.Count - 1]; + LineStrip path = new LineStrip(); + path.Append(lastPoint); + path.Append(newPoint); + Slice test = new Slice(path, toolRadius * 2.0f, currentPolygon.Plane); + + + if (!larger.Contains(test)) + { + // Can't move at this level - need to go to the save Z move height. + currentRout = new LineStrip(); + routs.Add(currentRout); + //DrawSlice(Color.Black, Color.Red, test); + } + else + { + //DrawSlice(Color.Black, Color.Green, test); + } + //larger.Subtract(test); + //GL.Translate(0, 0, 100); + } + } + // If the Z height changed, move to the new position and and drop, or rise and then move. + if (currentRout.Vertices.Count > 0) + { + var lastPoint = currentRout.Vertices[currentRout.Vertices.Count - 1]; + if (newPoint.Z < lastPoint.Z) + { + currentRout.Vertices.Add(new Vector3(newPoint.X, newPoint.Y, lastPoint.Z)); + } + else if (newPoint.Z > lastPoint.Z) + { + currentRout.Vertices.Add(new Vector3(lastPoint.X, lastPoint.Y, newPoint.Z)); + } + } + currentRout.Vertices.Add(newPoint); + } + + public bool Contains(Slice p) + { + return (topPolygon.Contains(p)); + } + } + + private class PathTree + { + private Slice slice; + private List children = new List(); + private List badTrees; + + #region Public Methods + + /// + /// Generate a set of tool paths which will completely remove the material specified in + /// the polygons, plus an offset equal to the radius of the tool used. + /// + /// + /// maximum distance between disjoint paths + /// + public static List ObliterateSlice(Slice polygons, float maxShrink) + { + List lines = new List(); + + foreach (Slice slice in polygons.IndividualPolygons()) + { + PathTree tree = new PathTree(); + Slice inside = new Slice(slice.GetLines(Slice.LineType.Hole), slice.Plane); + Slice shrink = new Slice(slice); + while (shrink.Area() > 0) + { + shrink = new Slice(shrink.GetLines(Slice.LineType.Outside), shrink.Plane); + foreach (var a in shrink.IndividualPolygons()) + { + if (!tree.AddPolygon(a)) + { + // The new polygon didn't fit into the path tree... shouldn't get here. + } + } + + shrink.Offset(-maxShrink); + shrink.Subtract(inside); + } + + LineStrip toolPath = new LineStrip(); + tree.GenerateToolPath(toolPath, tree.CreatePath(), maxShrink * 2.0f); + lines.Add(toolPath); + } + return lines; + } + + #endregion + + #region Private Methods + + private PathTree() + { + slice = null; + badTrees = new List(); + } + + private PathTree(Slice slice, PathTree parent) + { + this.badTrees = parent.badTrees; + this.slice = slice; + } + + private LineStrip CreatePath() + { + return slice.GetLines(Geometry.Slice.LineType.Outside).First(s => true); + } + + private void Draw(Color lineColor, Color planeColor) + { + GL.PushMatrix(); + GL.Translate(0, 0, 10); + if (slice != null) + { + DrawSlice(lineColor, planeColor, slice); + } + + foreach (var t in children) + { + t.Draw(lineColor, planeColor); + } + GL.PopMatrix(); + } + + /// + /// Generate a tool path from the current path tree + /// + /// line strip to add path information + /// line strip representing the path at the current level + /// maximum distance to jump from a parent path to a child path + private void GenerateToolPath(LineStrip start, LineStrip thisPath, float maxDistance) + { + List childs = new List(); + foreach (PathTree child in children) + { + childs.Add(child); + } + + var pathVertices = thisPath.Vertices.Count; + for (int i = 0; i < pathVertices; i++) + { + var p1 = thisPath.Vertices[i]; + var p2 = thisPath.Vertices[(i + 1) % pathVertices]; + + Segment parentSegment = new Segment(p1, p2); + start.Append(p1); + + int childIndex = 0; + int closestIndex = 0; + + for (int childTreeIndex = 0; childTreeIndex < childs.Count; childTreeIndex++) + { + var child = childs[childTreeIndex]; + bool found = false; + var childPath = child.CreatePath(); + var childVertices = childPath.Vertices.Count; + + // Prefer point to point matches + var closest = maxDistance; + for (childIndex = 0; childIndex < childVertices; childIndex++) + { + var c1 = childPath.Vertices[childIndex]; + var len = (c1 - p1).Length; + if (len < closest) + { + closest = len; + closestIndex = childIndex; + found = true; + } + } + + // If a point to point match isn't found, look for line to point matches. + if (!found) + { + closest = maxDistance; + Vector3 insertPoint = Vector3.Zero; + bool insertParent = false; + + // If there is no point to point match, find a point to line match. + for (childIndex = 0; childIndex < childVertices; childIndex++) + { + var c1 = childPath.Vertices[childIndex]; + var c2 = childPath.Vertices[(childIndex + 1) % childVertices]; + Segment childSegment = new Segment(c1, c2); + + var fromParentSegment = parentSegment.DistanceTo(c1); + if (fromParentSegment < closest) + { + insertParent = true; + closest = fromParentSegment; + insertPoint = parentSegment.PointOnLine; + closestIndex = childIndex; + found = true; + } + + var fromChildSegment = childSegment.DistanceTo(p1); + if (fromChildSegment < closest) + { + // Note: this happens very rarely + insertParent = false; + closest = fromChildSegment; + insertPoint = childSegment.PointOnLine; + closestIndex = childIndex; + found = true; + } + } + if (found) + { + if (insertParent) + { + p1 = insertPoint; + start.Append(p1); + } + else + { + closestIndex++; + childPath.Vertices.Insert(closestIndex, insertPoint); + } + } + } + + if (found) + { + // Reorder the child vertices + var last = childPath.Vertices.GetRange(0, closestIndex); + childPath.Vertices.RemoveRange(0, closestIndex); + childPath.Vertices.AddRange(last); + + child.GenerateToolPath(start, childPath, maxDistance); + start.Append(p1); + + childs.RemoveAt(childTreeIndex); + childTreeIndex--; + } + } + } + + if (childs.Count > 0) + { + // No path to these children - need to handle them some other way + badTrees.AddRange(childs); + } + + // Complete the loop + start.Append(thisPath.Vertices[0]); + } + + private bool AddPolygon(Slice slice) + { + if (this.slice == null) + { + this.slice = slice; + return true; + } + + if (this.slice.Contains(slice)) + { + foreach (var t in children) + { + if (t.AddPolygon(slice)) + { + return true; + } + } + PathTree newTree = new PathTree(slice, this); + children.Add(newTree); + return true; + } + return false; + } + + #endregion + + #region Helper Classes + + // Helper class for finding the shortest distance from a point to a plane + private class Segment + { + private Vector3 a; + private Vector3 b; + private Vector3 pointOnLine; + + public Segment(Vector3 a, Vector3 b) + { + this.a = a; + this.b = b; + pointOnLine = Vector3.Zero; + } + + public Vector3 PointOnLine + { + get { return pointOnLine; } + } + + + public float DistanceTo(Vector3 point) + { + float distanceFromA3 = new Plane(a - b, a).Distance(point); + float distanceFromB3 = new Plane(b - a, b).Distance(point); + + if (distanceFromA3 > 0 || distanceFromB3 > 0) + { + return float.PositiveInfinity; + } + + pointOnLine = (a * distanceFromB3 + b * distanceFromA3) / (distanceFromA3 + distanceFromB3); + + // Compute a normal perpendicular to the line and pointing to the point + Vector3 up = Vector3.Cross(a - point, a - b); // This points up from the line + Vector3 normal = Vector3.Cross(up, a - b); + float distanceToLine = Math.Abs(new Plane(normal, a).Distance(point)); + + return distanceToLine; + } + } + + #endregion + } + + #endregion + + + } +} diff --git a/Router/Paths/Tabs.cs b/Router/Paths/Tabs.cs new file mode 100644 index 0000000..921d70c --- /dev/null +++ b/Router/Paths/Tabs.cs @@ -0,0 +1,183 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Geometry; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace Router.Paths +{ + public class Tabs + { + private LineStrip boundary; + private int minTabs = 3; + private float desiredSpacing = 2.500f; + protected float tabRadius = 0.200f; + protected float toolRadius; + protected List tabLocations; + protected float tabHeight = 0.050f; + private Slice originalBoundary; + + + public Tabs(LineStrip boundary, float toolRadius, bool inside = false) + { + originalBoundary = new Slice(new LineStrip[] { boundary }, new Plane(Vector3.UnitZ, Vector3.Zero)); + float offset = toolRadius; + if (inside) + { + offset = -offset; + } + Slice slice = new Slice(originalBoundary); + slice.Offset(offset); + this.boundary = slice.GetLines(Slice.LineType.Outside).First(s => true); + this.toolRadius = toolRadius; + + float length = this.boundary.Length(LineStrip.Type.Closed); + int numTabs = (int)(length / desiredSpacing); + if (numTabs < minTabs) + { + numTabs = 0; + } + + + float tabSpacing = length / numTabs; + + tabLocations = new List(); + foreach (var point in this.boundary.PointsAlongLine(tabSpacing, tabSpacing / 2.0f)) + { + tabLocations.Add(point); + } + } + + public Slice Boundary + { + get { return originalBoundary; } + } + + public LineStrip TabPath + { + get { return boundary; } + } + + public Vector3 ClearHeight(Vector3 test, float height) + { + test.Z = Math.Max(test.Z, height); + return test; + } + + public IEnumerable TabLocations + { + get + { + foreach (var tab in tabLocations) + { + yield return tab; + } + } + } + + /// + /// Create another line strip which follows the same path, but avoids tab locations. + /// NOTE: this currently only works on closed input lines. The algorithm could + /// be modified to work correctly with open paths too, but that's not needed yet. + /// + /// + /// + public LineStrip AvoidTabs(LineStrip input) + { + LineStrip ret = new LineStrip(); + foreach (var segment in input.Segments(LineStrip.Type.Closed)) + { + if (segment.Length < 0.0001f) + { + continue; + } + if (segment.A.Z > tabHeight && segment.B.Z > tabHeight) + { + ret.Append(segment.B); + continue; + } + List remainingSegments = new List(); + remainingSegments.Add(segment); + foreach (Vector3 tab in this.TabLocations) + { + var i = new LineSegmentCircleIntersect(segment, tab, tabRadius + toolRadius); + if (i.type == LineSegmentCircleIntersect.IntersectType.Segment) + { + List temp = new List(); + + foreach (var seg in remainingSegments) + { + temp.AddRange(seg.Subtract(i.IntersectSegment)); + } + remainingSegments = temp; + } + } + remainingSegments.RemoveAll(s => s.Length < 0.0001f); + + if (remainingSegments.Count == 0) + { + // Entire segment is within a tab + TestAddPoint(ClearHeight(segment.B, tabHeight), ret.Vertices); + } + else + { + // Everything described in "remainingSegments" is outside of the tab, and the spaces + // between are on the tab. The path between is known since it's always a straight line. + remainingSegments.Sort((s1, s2) => (s1.A - segment.A).Length.CompareTo((s2.A - segment.A).Length)); + foreach (var s in remainingSegments) + { + TestAddPoint(ClearHeight(s.A, tabHeight), ret.Vertices); + TestAddPoint(s.A, ret.Vertices); + TestAddPoint(s.B, ret.Vertices); + TestAddPoint(ClearHeight(s.B, tabHeight), ret.Vertices); + } + TestAddPoint(ClearHeight(segment.B, tabHeight), ret.Vertices); + } + } + + return ret; + } + + private void TestAddPoint(Vector3 point, List points) + { + int i = points.Count; + // Test if this is a useless move (up+down or down+up at the same xy location) + if (i > 1 && (points[i - 2] - point).Length < 0.0001f && (points[i - 1].Xy - point.Xy).Length < 0.0001f) + { + points.RemoveAt(i - 1); + } + else + { + if (i > 0 && (points[i - 1] - point).Length < 0.0001f) + { + // Don't add a duplicate point + } + else + { + points.Add(point); + } + } + } + } +} diff --git a/Router/Paths/Tabs.cs.bak b/Router/Paths/Tabs.cs.bak new file mode 100644 index 0000000..211aa43 --- /dev/null +++ b/Router/Paths/Tabs.cs.bak @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Geometry; +using OpenTK; +using OpenTK.Graphics.OpenGL; + +namespace Router.Paths +{ + public class Tabs + { + private LineStrip boundary; + private int minTabs = 3; + private float desiredSpacing = 2500; + protected float tabRadius = 200; + protected float toolRadius = 0; + protected List tabLocations; + protected float tabHeight = 50; + public Tabs(LineStrip boundary, float toolRadius) + { + Slice slice = new Slice(new LineStrip[] { boundary }, new Plane(Vector3.UnitZ, Vector3.Zero)); + slice.Offset(toolRadius); + this.boundary = slice.GetLines(Slice.LineType.Outside).First(s => true); + this.toolRadius = toolRadius; + + float length = this.boundary.Length(LineStrip.Type.Closed); + int numTabs = (int)(length / desiredSpacing); + numTabs = Math.Max(numTabs, minTabs); + + + float tabSpacing = length / numTabs; + + tabLocations = new List(); + foreach (var point in this.boundary.PointsAlongLine(tabSpacing, tabSpacing / 2.0f)) + { + tabLocations.Add(point); + } + } + + public IEnumerable TabLocations + { + get { return tabLocations; } + } + + public LineStrip Boundary + { + get { return boundary; } + } + + public Vector3 ClearHeight(Vector3 test, float height) + { + test.Z = Math.Max(test.Z, height); + return test; + } + + /// + /// Create another line strip which follows the same path, but avoids tab locations. + /// NOTE: this currently only works on closed input lines. The algorithm could + /// be modified to work correctly with open paths too, but that's not needed yet. + /// + /// + /// + public LineStrip AvoidTabs(LineStrip input) + { + LineStrip ret = new LineStrip(); + foreach (var segment in input.Segments(LineStrip.Type.Closed)) + { + if (segment.A.Z > tabHeight && segment.B.Z > tabHeight) + { + ret.Append(segment.B); + continue; + } + List remainingSegments = new List(); + remainingSegments.Add(segment); + foreach (Vector3 tab in this.TabLocations) + { + var i = new LineSegmentCircleIntersect(segment, tab, tabRadius + toolRadius); + if (i.type == LineSegmentCircleIntersect.IntersectType.Segment) + { + List temp = new List(); + + foreach (var seg in remainingSegments) + { + temp.AddRange(seg.Subtract(i.IntersectSegment)); + } + temp.RemoveAll(s => s.Length < 0.1f); + remainingSegments = temp; + } + } + + if (remainingSegments.Count == 0) + { + // Entire segment is within a tab + ret.Append(ClearHeight(segment.B, tabHeight)); + } + else + { + // Everything described in "remainingSegments" is outside of the tab, and the spaces + // between are on the tab. The path between is known since it's always a straight line. + remainingSegments.Sort((s1, s2) => (s1.A - segment.A).Length.CompareTo((s2.A - segment.A).Length)); + + bool needsPointB = true; + foreach (var s in remainingSegments) + { + if ((segment.A - s.A).Length > 0.1f) + { + ret.Append(ClearHeight(s.A, tabHeight)); + } + + ret.Append(s.A); + ret.Append(s.B); + + if ((segment.B - s.B).Length > 0.1f) + { + ret.Append(ClearHeight(s.B, tabHeight)); + } + else + { + needsPointB = false; + } + } + + if (needsPointB) + { + ret.Append(ClearHeight(segment.B, tabHeight)); + } + } + } + + return ret; + } + } +} diff --git a/Router/Properties/AssemblyInfo.cs b/Router/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..85f5d3b --- /dev/null +++ b/Router/Properties/AssemblyInfo.cs @@ -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("GCode")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GCode")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("c60b66e6-786f-45a3-b107-3b8d8d9029da")] + +// 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")] diff --git a/Router/Router.cs b/Router/Router.cs new file mode 100644 index 0000000..a9589c9 --- /dev/null +++ b/Router/Router.cs @@ -0,0 +1,148 @@ +/* + * PathCAM - Toolpath generation software for CNC manufacturing machines + * Copyright (C) 2013 Benjamin R. Porter https://github.com/xenovacivus + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Windows.Forms; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System.ComponentModel; +using Commands; +using Geometry; + +namespace Router +{ + public class Router + { + private List commands; + private Vector3 finalPosition = new Vector3(0, 0, 0); + + private float toolDiameter = 0.120f; + private float move_height = 0.525f; // How high above the surface to move the router + private float move_speed = 250; // Moving speed (inches per minute) + private float rout_speed = 45; // Routing speed (inches per minute) + private float max_cut_depth = 1.0f / 8.0f; // Maximum cut depth in inches + private float lastPassHeight = -.020f; // Height of the last rout + + public Router() + { + commands = new List(); + } + + public List GetCommands() + { + return commands; + } + + public void AddCommand(ICommand r) + { + commands.Add(r); + if (r is MoveTool) + { + finalPosition = (r as MoveTool).Target; + } + } + + public void ClearCommands() + { + commands.Clear(); + } + + public float LastPassHeight + { + get { return lastPassHeight; } + set { lastPassHeight = value; } + } + + public float ToolDiameter + { + get { return toolDiameter; } + set { if (value > 0.0f) { toolDiameter = value; } } + } + + public float RoutSpeed + { + get { return rout_speed; } + set { if (value > 0) { rout_speed = value; } } + } + + public float MoveSpeed + { + get { return move_speed; } + set { if (value > 0) { move_speed = value; } } + } + + public float MoveHeight + { + get { return move_height; } + set { move_height = value; } + } + + public float MaxCutDepth + { + get { return max_cut_depth; } + set { max_cut_depth = value; } + } + + public void RoutPath(LineStrip line, bool backwards, Vector3 offset) + { + bool first = true; + + foreach (Vector3 point in line.Vertices) + { + // TODO: Pick some unit and stick with it! Inches would be fine. + Vector3 pointOffset = point + offset; + + MoveTool m = new MoveTool(pointOffset, rout_speed); + if (first) + { + first = false; + + if ((finalPosition.Xy - pointOffset.Xy).Length > .0001) + { + // Need to move the router up, over to new position, then down again. + MoveTool m1 = new MoveTool(new Vector3(finalPosition.X, finalPosition.Y, move_height), move_speed); + MoveTool m2 = new MoveTool(new Vector3(m.Target.X, m.Target.Y, move_height), move_speed); + AddCommand(m1); + AddCommand(m2); + } + } + AddCommand(m); + } + } + + /// + /// Safely move to (0, 0, move_height) + /// + public void Complete() + { + if (finalPosition.Z < move_height) + { + AddCommand(new MoveTool(new Vector3(finalPosition.X, finalPosition.Y, move_height), move_speed)); + } + else + { + AddCommand(new MoveTool(new Vector3(0, 0, finalPosition.Z), move_speed)); + } + AddCommand(new MoveTool(new Vector3(0, 0, move_height), move_speed)); + } + } +} diff --git a/Router/Router.csproj b/Router/Router.csproj new file mode 100644 index 0000000..19583c2 --- /dev/null +++ b/Router/Router.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {6A2ED2BC-1FEA-46E3-8403-3C484A7BCCA5} + Library + Properties + GCode + GCode + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\OpenTK.1.1.1456.5398\lib\NET40\OpenTK.dll + + + + + + + + + + + + + + + + + + + + + {0be94aa8-662b-45ec-b95a-545180f54618} + Commands + + + {29611cc3-e54f-45e4-9681-8df653459ca2} + Geometry + + + + + + + + \ No newline at end of file diff --git a/Router/packages.config b/Router/packages.config new file mode 100644 index 0000000..d0ea9c3 --- /dev/null +++ b/Router/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Serial/Properties/AssemblyInfo.cs b/Serial/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0d93798 --- /dev/null +++ b/Serial/Properties/AssemblyInfo.cs @@ -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("Serial")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Serial")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[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("ab673d94-f09a-4642-b54a-f7e25de282d1")] + +// 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")] diff --git a/Serial/Serial.csproj b/Serial/Serial.csproj new file mode 100644 index 0000000..aeaa820 --- /dev/null +++ b/Serial/Serial.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {9477FD2C-FE8F-4653-AB25-BFA14338A932} + Library + Properties + Serial + Serial + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Serial/SerialPacket.cs b/Serial/SerialPacket.cs new file mode 100644 index 0000000..c3d2b2f --- /dev/null +++ b/Serial/SerialPacket.cs @@ -0,0 +1,449 @@ +/* + * SerialPackets - A simple byte stuffed packetizer for async serial. + * Copyright (C) 2010-2013 Benjamin R. Porter + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Linq; +using System.Text; + +namespace Serial +{ + class SerialPacket + { + /** Serial Packet Definition: + // + // START_BYTE start byte + // address + // length + // data(n bytes) + // Checksum ~(address + length + data) + + // Escape Character and Byte Stuffing: + // Any control character is replaced + // with an escape character followed + // by it's "escaped" value. All data + // in the transmission except for the + // start byte can be escaped. This + // means the transmission may take up + // to twice as long as expected from + // just transmitting escape character + **/ + + + public delegate void TransmitDelegate(byte data); + public delegate void TransmitPacketeCompleteDelegate(); + public delegate void ReceivePacketCOmpleteDelegate(SerialPacket s); + public delegate void ReceiveDataErrorDelegate(byte errCode); + + //public class SerialData + //{ + public SerialPacket() + { + /* Receive State Variables */ + receive_state = PROC_STATE_AWAITING_START_BYTE; + receive_next_char_is_escaped = false; + + /* Function Pointers */ + Transmit = null; + TransmitPacketComplete = null; + ReceivePacketComplete = null; + ReceiveDataError = null; + + /* Transmit State Variables */ + transmit_state = PROC_STATE_TRANSMIT_COMPLETE; + transmit_address = 0; + transmit_length = 0; + transmit_checksum = 0; + transmit_data_index = 0; + } + + /** + * Transmit serial data + * @param address The address to send the data to + * @param length Number of bytes in data to be sent + * @param maxLength Number of bytes allocated for + * the data array. + * @return 0 for success, nonzero for failure. + */ + public int SerialTransmit(byte address, byte length) + { + if (SerialTransferInProgress()) + { + return -1; + } + + if (Transmit == null) + { + return -2; + } + + if (length > SERIAL_TRANSMIT_BUFFER_SIZE) + { + return -3; + } + + transmit_address = address; + transmit_length = length; + transmit_data_index = 0;// (byte*)transmit_data; + transmit_escaped_char = 0; + transmit_state = PROC_STATE_TRANSMIT_ADDRESS; + + Transmit(START_BYTE); + + return 0; + } + + + /** Call this method on USART data + * transmit complete. + */ + public void SerialByteTransmitComplete() + { + byte dataToTx = 0; + + // Check if we need to transmit an escaped character: + if (transmit_escaped_char != 0) + { + dataToTx = transmit_escaped_char; + transmit_escaped_char = 0; + + } + else + { + switch (transmit_state) + { + case PROC_STATE_TRANSMIT_ADDRESS: + dataToTx = transmit_address; + transmit_checksum = dataToTx; + transmit_state = PROC_STATE_TRANSMIT_LENGTH; + break; + + case PROC_STATE_TRANSMIT_LENGTH: + dataToTx = transmit_length; + transmit_checksum += dataToTx; + transmit_state = PROC_STATE_TRANSMIT_DATA; + break; + + case PROC_STATE_TRANSMIT_DATA: + dataToTx = transmit_data[transmit_data_index];// *(transmit_data_ptr); + transmit_checksum += dataToTx; + transmit_data_index++; + transmit_length--; + if (transmit_length == 0) + { + transmit_state = PROC_STATE_TRANSMIT_CHECKSUM; + } + break; + + case PROC_STATE_TRANSMIT_CHECKSUM: + dataToTx = (byte)~transmit_checksum; + transmit_state = PROC_STATE_TRANSMIT_ALMOST_COMPLETE; + break; + + case PROC_STATE_TRANSMIT_ALMOST_COMPLETE: + // Done transmitting! + transmit_state = PROC_STATE_TRANSMIT_COMPLETE; + if (TransmitPacketComplete!=null) + { + TransmitPacketComplete(); + } + return; + break; + + default: + // Shouldn't ever get here. + break; + } + + // Check for control characters + switch(dataToTx) + { + case START_BYTE: + transmit_escaped_char = START_BYTE_ESCAPED; + dataToTx = ESCAPE_CHAR; + break; + + case ESCAPE_CHAR: + transmit_escaped_char = ESCAPE_CHAR_ESCAPED; + dataToTx = ESCAPE_CHAR; + break; + + case null_BYTE: + transmit_escaped_char = null_BYTE_ESCAPED; + dataToTx = ESCAPE_CHAR; + break; + + case MAX_BYTE: + transmit_escaped_char = MAX_BYTE_ESCAPED; + dataToTx = ESCAPE_CHAR; + break; + + default: + transmit_escaped_char = 0; + break; + } + } + + // Transmit the data! + if (Transmit!=null) + { + Transmit(dataToTx); + } + + } + + + /** Processes a character from a serial stream + * and reconstructs packet + * @param data The next character in the stream + */ + public void ProcessDataChar(byte data) + { + /* Unstuff bytes and locate start bytes here */ + + /* See if the data received is value to ignore + * This most likely occurs in conjunction with + * a frame error: start byte detected, but no + * valid data afterward */ + if (data == null_BYTE || data == MAX_BYTE) + { + SerialError(ERR_RECEIVED_IGNORE_BYTE); + return; + } + + + /* If any start byte is found, any current data + * transfer will be reset, and a new data transfer + * will begin. + */ + if (data == START_BYTE) /* Start byte */ + { + if (receive_state != PROC_STATE_AWAITING_START_BYTE) + { + SerialError(ERR_START_BYTE_INSIDE_PACKET); + } + + /* Reset state */ + receive_state = PROC_STATE_AWAITING_ADDRESS; + receive_data_count = 0; + receive_next_char_is_escaped = false; + } + else + { + if (receive_state == PROC_STATE_AWAITING_START_BYTE) + { + SerialError(ERR_UNEXPECTED_START_BYTE); + //printf("Unexpected Start Byte: Expected 0x%x, Got 0x%x\n", START_BYTE, data); + } + else + { + /* Otherwise, unstuff bytes and send data to the state machine */ + if (data == ESCAPE_CHAR) // Escape Character + { + receive_next_char_is_escaped = true; + } + else + { + if (receive_next_char_is_escaped) + { + receive_next_char_is_escaped = false; + switch (data) + { + case ESCAPE_CHAR_ESCAPED: + data = ESCAPE_CHAR; + break; + + case START_BYTE_ESCAPED: + data = START_BYTE; + break; + + case null_BYTE_ESCAPED: + data = null_BYTE; + break; + + case MAX_BYTE_ESCAPED: + data = MAX_BYTE; + break; + } + } + SerialStateMachineProcess(data); + } + } + } + } + + private void SerialStateMachineProcess(byte data) + { + switch (receive_state) + { + case PROC_STATE_AWAITING_ADDRESS: + receive_address = data; + receive_checksum = data; + receive_state = PROC_STATE_AWAITING_LENGTH; + break; + + case PROC_STATE_AWAITING_LENGTH: + if (data > SERIAL_RECEIVE_BUFFER_SIZE) + { + /* Error, length too long. Ignore packet. */ + receive_state = PROC_STATE_AWAITING_START_BYTE; + + /* Look for the next start byte. Note: this + * will likey produce unexpected start byte error + */ + SerialError(ERR_EXCESSIVE_PACKET_LENGTH); + } + else + { + receive_length = data; + receive_checksum += data; + receive_state = PROC_STATE_AWAITING_DATA; + } + break; + + case PROC_STATE_AWAITING_DATA: + + receive_length--; + + receive_checksum += data; + receive_data[receive_data_count] = data; + receive_data_count++; + + if (receive_length == 0) + { + receive_state = PROC_STATE_AWAITING_CHECKSUM; + } + + break; + + case PROC_STATE_AWAITING_CHECKSUM: + receive_checksum = (byte)~receive_checksum; + if (data == receive_checksum) + { + if (ReceivePacketComplete != null) + { + ReceivePacketComplete (this); + } + } + else + { + SerialError(ERR_CHECKSUM_MISMATCH); + //printf("Error: Checksum Mismatch. Expected 0x%x, Got 0x%x\n", receive_checksum, data); + } + receive_state = PROC_STATE_AWAITING_START_BYTE; + break; + + default: + // (It'll never get here) + break; + } + } + + + /** Serial Packet Transfer Query Function + * @return true if a packet transfer is currently + * in progress, false otherwise. + */ + public bool SerialTransferInProgress() + { + return (transmit_state != PROC_STATE_TRANSMIT_COMPLETE); + } + + /** + * Helper Function: Distribute error codes to + * handling functions, if they exist. + */ + void SerialError(byte errCode) + { + if (ReceiveDataError != null) + { + ReceiveDataError(errCode); + } + } + + /* Receiving Variables */ + internal byte [] receive_data = new byte [SerialPacket.SERIAL_RECEIVE_BUFFER_SIZE]; + internal byte receive_data_count; /* Number of bytes received so far */ + internal byte receive_length; /* Expected number of bytes to receive */ + internal bool receive_next_char_is_escaped; /* need to unstuff next char? */ + internal byte receive_address; /* address of the received packet */ + internal byte receive_checksum; /* Checksum of the received packet */ + internal byte receive_state; /* Serial receive state variable */ + + /* Transmission Variables */ + internal byte[] transmit_data = new byte[SerialPacket.SERIAL_TRANSMIT_BUFFER_SIZE]; + internal byte transmit_state; + internal byte transmit_address; + internal byte transmit_length; + internal byte transmit_checksum; + internal byte transmit_escaped_char; + internal byte transmit_data_index; + + /* Function Pointers */ + public TransmitDelegate Transmit; + public TransmitPacketeCompleteDelegate TransmitPacketComplete; + public ReceivePacketCOmpleteDelegate ReceivePacketComplete; + public ReceiveDataErrorDelegate ReceiveDataError; + + + //void (*Transmit)(byte data); + //void (*TransmitPacketComplete)(void); + //void (*ReceivePacketComplete)(volatile SerialData * s); + //void (*ReceiveDataError)(byte errCode); + //} + + + + /* Control Characters and thier Escaped Equivelants */ + const byte START_BYTE = 0xCA; + const byte ESCAPE_CHAR = (byte)'\\'; + const byte null_BYTE = 0; + const byte MAX_BYTE = 255; + const byte START_BYTE_ESCAPED = 2; + const byte ESCAPE_CHAR_ESCAPED = 3; + const byte null_BYTE_ESCAPED = 4; + const byte MAX_BYTE_ESCAPED = 5; + + /* Receive State Machine States */ + const byte PROC_STATE_AWAITING_START_BYTE = 0; + const byte PROC_STATE_AWAITING_ADDRESS = 1; + const byte PROC_STATE_AWAITING_LENGTH = 2; + const byte PROC_STATE_AWAITING_DATA = 3; + const byte PROC_STATE_AWAITING_CHECKSUM = 4; + + /* Transmit State Machine States */ + const byte PROC_STATE_TRANSMIT_ADDRESS = 15; + const byte PROC_STATE_TRANSMIT_LENGTH = 16; + const byte PROC_STATE_TRANSMIT_DATA = 17; + const byte PROC_STATE_TRANSMIT_CHECKSUM = 18; + const byte PROC_STATE_TRANSMIT_ALMOST_COMPLETE = 19; + const byte PROC_STATE_TRANSMIT_COMPLETE = 20; + + /* Error Codes */ + const byte ERR_START_BYTE_INSIDE_PACKET = 1; + const byte ERR_UNEXPECTED_START_BYTE = 2; + const byte ERR_UNKNOWN_ESCAPED_CHARACTER = 3; + const byte ERR_EXCESSIVE_PACKET_LENGTH = 4; + const byte ERR_CHECKSUM_MISMATCH = 5; + const byte ERR_BUFFER_INSUFFICIENT = 6; + const byte ERR_RECEIVED_IGNORE_BYTE = 7; + + /* Buffer Sizes */ + const byte SERIAL_RECEIVE_BUFFER_SIZE = 50; + const byte SERIAL_TRANSMIT_BUFFER_SIZE = 50; + } +} diff --git a/Serial/SerialPortWrapper.cs b/Serial/SerialPortWrapper.cs new file mode 100644 index 0000000..333e564 --- /dev/null +++ b/Serial/SerialPortWrapper.cs @@ -0,0 +1,220 @@ +/* + * SerialPackets - A simple byte stuffed packetizer for async serial. + * Copyright (C) 2010-2013 Benjamin R. Porter + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see [http://www.gnu.org/licenses/]. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO.Ports; + +namespace Serial +{ + public class SerialPortWrapper + { + public SerialPort port; + SerialPacket packet; + + public class SimpleSerialPacket + { + public SimpleSerialPacket(byte[] data, byte address) + { + this.data = data; + this.address = address; + } + + private byte[] data; + private byte address; + + public byte[] Data + { + get + { + return data; + } + } + + public byte Address + { + get + { + return address; + } + } + + public byte Length + { + get + { + return (byte)data.Length; + } + } + } + + private void RxPacketComplete(SerialPacket s) + { + //Console.WriteLine("Receive Packet Complete:"); + for (int i = 0; i < s.receive_length; i++) + { + } + + SimpleSerialPacket simple = new SimpleSerialPacket(s.receive_data.Take(s.receive_data_count).ToArray(), s.receive_address); + if (newDataAvailable != null) + { + newDataAvailable(simple); + } + } + + public delegate void newDataAvailableDelegate(SimpleSerialPacket s); + public newDataAvailableDelegate newDataAvailable; + + public delegate void receiveDataErrorDelegate(byte err); + public receiveDataErrorDelegate receiveDataError; + + private void TxPacketComplete() + { + } + + private void TxByte(byte data) + { + if (port.IsOpen) + { + try + { + port.Write(new byte[] { data }, 0, 1); + //Console.WriteLine("Tx'd byte = " + data); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + packet.SerialByteTransmitComplete(); + } + + private void RxPacketError(byte err) + { + //Console.WriteLine("Error! Byte Stream: "); + //foreach (byte b in packet.receive_data) + //{ + // Console.Write(b + ", "); + //} + + if (receiveDataError != null) + { + receiveDataError(err); + } + } + + public SerialPortWrapper() + { + // Setup method delegates + packet = new SerialPacket(); + packet.ReceiveDataError = new SerialPacket.ReceiveDataErrorDelegate (RxPacketError); + packet.Transmit = new SerialPacket.TransmitDelegate(TxByte); + packet.TransmitPacketComplete = new SerialPacket.TransmitPacketeCompleteDelegate(TxPacketComplete); + packet.ReceivePacketComplete = new SerialPacket.ReceivePacketCOmpleteDelegate(RxPacketComplete); + + // Setup the serial port defaults + port = new SerialPort(); + port.BaudRate = 9600; + port.DataBits = 8; + port.Parity = Parity.None; + port.Handshake = Handshake.None; + port.StopBits = StopBits.One; + port.DiscardNull = false; + port.DtrEnable = false; + port.RtsEnable = false; + port.Encoding = System.Text.Encoding.Default; + port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived); + } + + public void Transmit(byte[] data, byte address) + { + // Wait until all pending data is sent + while (packet.SerialTransferInProgress()) + { + } + data.CopyTo(packet.transmit_data, 0); + packet.SerialTransmit(address, (byte)data.Length); + } + + void port_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + try + { + while (port.BytesToRead > 0) + { + byte data = (byte)port.ReadByte(); + packet.ProcessDataChar(data); + } + } + catch (Exception ex) + { + } + } + + public void Open(string portName, int baudRate) + { + Close(); + port.BaudRate = baudRate; + port.PortName = portName; + port.Open(); + + } + + public void Close() + { + if (port.IsOpen) + { + port.Close(); + } + } + + public bool IsOpen + { + get + { + return port.IsOpen; + } + } + + public string[] PortNames + { + get + { + return SerialPort.GetPortNames(); + } + } + + public int BaudRate + { + get + { + return port.BaudRate; + } + } + + public string PortName + { + get + { + return port.PortName; + } + } + } +} diff --git a/ThirdParty/Triangle/Algorithm/Dwyer.cs b/ThirdParty/Triangle/Algorithm/Dwyer.cs new file mode 100644 index 0000000..4ad5756 --- /dev/null +++ b/ThirdParty/Triangle/Algorithm/Dwyer.cs @@ -0,0 +1,900 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Algorithm +{ + using System; + using TriangleNet.Data; + using TriangleNet.Log; + + /// + /// Builds a delaunay triangulation using the divide-and-conquer algorithm. + /// + /// + /// The divide-and-conquer bounding box + /// + /// I originally implemented the divide-and-conquer and incremental Delaunay + /// triangulations using the edge-based data structure presented by Guibas + /// and Stolfi. Switching to a triangle-based data structure doubled the + /// speed. However, I had to think of a few extra tricks to maintain the + /// elegance of the original algorithms. + /// + /// The "bounding box" used by my variant of the divide-and-conquer + /// algorithm uses one triangle for each edge of the convex hull of the + /// triangulation. These bounding triangles all share a common apical + /// vertex, which is represented by NULL and which represents nothing. + /// The bounding triangles are linked in a circular fan about this NULL + /// vertex, and the edges on the convex hull of the triangulation appear + /// opposite the NULL vertex. You might find it easiest to imagine that + /// the NULL vertex is a point in 3D space behind the center of the + /// triangulation, and that the bounding triangles form a sort of cone. + /// + /// This bounding box makes it easy to represent degenerate cases. For + /// instance, the triangulation of two vertices is a single edge. This edge + /// is represented by two bounding box triangles, one on each "side" of the + /// edge. These triangles are also linked together in a fan about the NULL + /// vertex. + /// + /// The bounding box also makes it easy to traverse the convex hull, as the + /// divide-and-conquer algorithm needs to do. + /// + class Dwyer + { + static Random rand = new Random(DateTime.Now.Millisecond); + bool useDwyer = true; + + Vertex[] sortarray; + Mesh mesh; + + /// + /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key. + /// + /// + /// + /// + /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of + /// the usual quicksort mistakes. + /// + void VertexSort(int left, int right) + { + int oleft = left; + int oright = right; + int arraysize = right - left + 1; + int pivot; + double pivotx, pivoty; + Vertex temp; + + if (arraysize < 32) + { + // Insertion sort + for (int i = left + 1; i <= right; i++) + { + var a = sortarray[i]; + int j = i - 1; + while (j >= left && (sortarray[j].x > a.x || (sortarray[j].x == a.x && sortarray[j].y > a.y))) + { + sortarray[j + 1] = sortarray[j]; + j--; + } + sortarray[j + 1] = a; + } + + return; + } + + // Choose a random pivot to split the array. + pivot = rand.Next(left, right); + pivotx = sortarray[pivot].x; + pivoty = sortarray[pivot].y; + // Split the array. + left--; + right++; + while (left < right) + { + // Search for a vertex whose x-coordinate is too large for the left. + do + { + left++; + } + while ((left <= right) && ((sortarray[left].x < pivotx) || + ((sortarray[left].x == pivotx) && + (sortarray[left].y < pivoty)))); + // Search for a vertex whose x-coordinate is too small for the right. + do + { + right--; + } + while ((left <= right) && ((sortarray[right].x > pivotx) || + ((sortarray[right].x == pivotx) && + (sortarray[right].y > pivoty)))); + + if (left < right) + { + // Swap the left and right vertices. + temp = sortarray[left]; + sortarray[left] = sortarray[right]; + sortarray[right] = temp; + } + } + if (left > oleft) + { + // Recursively sort the left subset. + VertexSort(oleft, left); + } + if (oright > right + 1) + { + // Recursively sort the right subset. + VertexSort(right + 1, oright); + } + } + + /// + /// An order statistic algorithm, almost. Shuffles an array of vertices so that + /// the first 'median' vertices occur lexicographically before the remaining vertices. + /// + /// + /// + /// + /// + /// + /// Uses the x-coordinate as the primary key if axis == 0; the y-coordinate + /// if axis == 1. Very similar to the vertexsort() procedure, but runs in + /// randomized linear time. + /// + void VertexMedian(int left, int right, int median, int axis) + { + int arraysize = right - left + 1; + int oleft = left, oright = right; + int pivot; + double pivot1, pivot2; + Vertex temp; + + if (arraysize == 2) + { + // Recursive base case. + if ((sortarray[left][axis] > sortarray[right][axis]) || + ((sortarray[left][axis] == sortarray[right][axis]) && + (sortarray[left][1 - axis] > sortarray[right][1 - axis]))) + { + temp = sortarray[right]; + sortarray[right] = sortarray[left]; + sortarray[left] = temp; + } + return; + } + // Choose a random pivot to split the array. + pivot = rand.Next(left, right); //left + arraysize / 2; + pivot1 = sortarray[pivot][axis]; + pivot2 = sortarray[pivot][1 - axis]; + + left--; + right++; + while (left < right) + { + // Search for a vertex whose x-coordinate is too large for the left. + do + { + left++; + } + while ((left <= right) && ((sortarray[left][axis] < pivot1) || + ((sortarray[left][axis] == pivot1) && + (sortarray[left][1 - axis] < pivot2)))); + // Search for a vertex whose x-coordinate is too small for the right. + do + { + right--; + } + while ((left <= right) && ((sortarray[right][axis] > pivot1) || + ((sortarray[right][axis] == pivot1) && + (sortarray[right][1 - axis] > pivot2)))); + if (left < right) + { + // Swap the left and right vertices. + temp = sortarray[left]; + sortarray[left] = sortarray[right]; + sortarray[right] = temp; + } + } + + // Unlike in vertexsort(), at most one of the following conditionals is true. + if (left > median) + { + // Recursively shuffle the left subset. + VertexMedian(oleft, left - 1, median, axis); + } + if (right < median - 1) + { + // Recursively shuffle the right subset. + VertexMedian(right + 1, oright, median, axis); + } + } + + /// + /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with + /// alternating cuts. + /// + /// + /// + /// + /// + /// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1. + /// For the base case, subsets containing only two or three vertices are + /// always sorted by x-coordinate. + /// + void AlternateAxes(int left, int right, int axis) + { + int arraysize = right - left + 1; + int divider; + + divider = arraysize >> 1; + //divider += left; // TODO: check + if (arraysize <= 3) + { + // Recursive base case: subsets of two or three vertices will be + // handled specially, and should always be sorted by x-coordinate. + axis = 0; + } + // Partition with a horizontal or vertical cut. + VertexMedian(left, right, left + divider, axis); + // Recursively partition the subsets with a cross cut. + if (arraysize - divider >= 2) + { + if (divider >= 2) + { + AlternateAxes(left, left + divider - 1, 1 - axis); + } + AlternateAxes(left + divider, right, 1 - axis); + } + } + + /// + /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation. + /// + /// Bounding triangles of the left triangulation. + /// Bounding triangles of the left triangulation. + /// Bounding triangles of the right triangulation. + /// Bounding triangles of the right triangulation. + /// + /// + /// This is similar to the algorithm given by Guibas and Stolfi, but uses + /// a triangle-based, rather than edge-based, data structure. + /// + /// The algorithm walks up the gap between the two triangulations, knitting + /// them together. As they are merged, some of their bounding triangles + /// are converted into real triangles of the triangulation. The procedure + /// pulls each hull's bounding triangles apart, then knits them together + /// like the teeth of two gears. The Delaunay property determines, at each + /// step, whether the next "tooth" is a bounding triangle of the left hull + /// or the right. When a bounding triangle becomes real, its apex is + /// changed from NULL to a real vertex. + /// + /// Only two new triangles need to be allocated. These become new bounding + /// triangles at the top and bottom of the seam. They are used to connect + /// the remaining bounding triangles (those that have not been converted + /// into real triangles) into a single fan. + /// + /// On entry, 'farleft' and 'innerleft' are bounding triangles of the left + /// triangulation. The origin of 'farleft' is the leftmost vertex, and + /// the destination of 'innerleft' is the rightmost vertex of the + /// triangulation. Similarly, 'innerright' and 'farright' are bounding + /// triangles of the right triangulation. The origin of 'innerright' and + /// destination of 'farright' are the leftmost and rightmost vertices. + /// + /// On completion, the origin of 'farleft' is the leftmost vertex of the + /// merged triangulation, and the destination of 'farright' is the rightmost + /// vertex. + /// + void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright, + ref Otri farright, int axis) + { + Otri leftcand = default(Otri), rightcand = default(Otri); + Otri nextedge = default(Otri); + Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri); + Otri checkedge = default(Otri); + Otri baseedge = default(Otri); + Vertex innerleftdest; + Vertex innerrightorg; + Vertex innerleftapex, innerrightapex; + Vertex farleftpt, farrightpt; + Vertex farleftapex, farrightapex; + Vertex lowerleft, lowerright; + Vertex upperleft, upperright; + Vertex nextapex; + Vertex checkvertex; + bool changemade; + bool badedge; + bool leftfinished, rightfinished; + + innerleftdest = innerleft.Dest(); + innerleftapex = innerleft.Apex(); + innerrightorg = innerright.Org(); + innerrightapex = innerright.Apex(); + // Special treatment for horizontal cuts. + if (useDwyer && (axis == 1)) + { + farleftpt = farleft.Org(); + farleftapex = farleft.Apex(); + farrightpt = farright.Dest(); + farrightapex = farright.Apex(); + // The pointers to the extremal vertices are shifted to point to the + // topmost and bottommost vertex of each hull, rather than the + // leftmost and rightmost vertices. + while (farleftapex.y < farleftpt.y) + { + farleft.LnextSelf(); + farleft.SymSelf(); + farleftpt = farleftapex; + farleftapex = farleft.Apex(); + } + innerleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + while (checkvertex.y > innerleftdest.y) + { + checkedge.Lnext(ref innerleft); + innerleftapex = innerleftdest; + innerleftdest = checkvertex; + innerleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + } + while (innerrightapex.y < innerrightorg.y) + { + innerright.LnextSelf(); + innerright.SymSelf(); + innerrightorg = innerrightapex; + innerrightapex = innerright.Apex(); + } + farright.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + while (checkvertex.y > farrightpt.y) + { + checkedge.Lnext(ref farright); + farrightapex = farrightpt; + farrightpt = checkvertex; + farright.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + } + } + // Find a line tangent to and below both hulls. + do + { + changemade = false; + // Make innerleftdest the "bottommost" vertex of the left hull. + if (Primitives.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) + { + innerleft.LprevSelf(); + innerleft.SymSelf(); + innerleftdest = innerleftapex; + innerleftapex = innerleft.Apex(); + changemade = true; + } + // Make innerrightorg the "bottommost" vertex of the right hull. + if (Primitives.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) + { + innerright.LnextSelf(); + innerright.SymSelf(); + innerrightorg = innerrightapex; + innerrightapex = innerright.Apex(); + changemade = true; + } + } while (changemade); + + // Find the two candidates to be the next "gear tooth." + innerleft.Sym(ref leftcand); + innerright.Sym(ref rightcand); + // Create the bottom new bounding triangle. + mesh.MakeTriangle(ref baseedge); + // Connect it to the bounding boxes of the left and right triangulations. + baseedge.Bond(ref innerleft); + baseedge.LnextSelf(); + baseedge.Bond(ref innerright); + baseedge.LnextSelf(); + baseedge.SetOrg(innerrightorg); + baseedge.SetDest(innerleftdest); + // Apex is intentionally left NULL. + + // Fix the extreme triangles if necessary. + farleftpt = farleft.Org(); + if (innerleftdest == farleftpt) + { + baseedge.Lnext(ref farleft); + } + farrightpt = farright.Dest(); + if (innerrightorg == farrightpt) + { + baseedge.Lprev(ref farright); + } + // The vertices of the current knitting edge. + lowerleft = innerleftdest; + lowerright = innerrightorg; + // The candidate vertices for knitting. + upperleft = leftcand.Apex(); + upperright = rightcand.Apex(); + // Walk up the gap between the two triangulations, knitting them together. + while (true) + { + // Have we reached the top? (This isn't quite the right question, + // because even though the left triangulation might seem finished now, + // moving up on the right triangulation might reveal a new vertex of + // the left triangulation. And vice-versa.) + leftfinished = Primitives.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; + rightfinished = Primitives.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0; + if (leftfinished && rightfinished) + { + // Create the top new bounding triangle. + mesh.MakeTriangle(ref nextedge); + nextedge.SetOrg(lowerleft); + nextedge.SetDest(lowerright); + // Apex is intentionally left NULL. + // Connect it to the bounding boxes of the two triangulations. + nextedge.Bond(ref baseedge); + nextedge.LnextSelf(); + nextedge.Bond(ref rightcand); + nextedge.LnextSelf(); + nextedge.Bond(ref leftcand); + + // Special treatment for horizontal cuts. + if (useDwyer && (axis == 1)) + { + farleftpt = farleft.Org(); + farleftapex = farleft.Apex(); + farrightpt = farright.Dest(); + farrightapex = farright.Apex(); + farleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + // The pointers to the extremal vertices are restored to the + // leftmost and rightmost vertices (rather than topmost and + // bottommost). + while (checkvertex.x < farleftpt.x) + { + checkedge.Lprev(ref farleft); + farleftapex = farleftpt; + farleftpt = checkvertex; + farleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + } + while (farrightapex.x > farrightpt.x) + { + farright.LprevSelf(); + farright.SymSelf(); + farrightpt = farrightapex; + farrightapex = farright.Apex(); + } + } + return; + } + // Consider eliminating edges from the left triangulation. + if (!leftfinished) + { + // What vertex would be exposed if an edge were deleted? + leftcand.Lprev(ref nextedge); + nextedge.SymSelf(); + nextapex = nextedge.Apex(); + // If nextapex is NULL, then no vertex would be exposed; the + // triangulation would have been eaten right through. + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = Primitives.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; + while (badedge) + { + // Eliminate the edge with an edge flip. As a result, the + // left triangulation will have one more boundary triangle. + nextedge.LnextSelf(); + nextedge.Sym(ref topcasing); + nextedge.LnextSelf(); + nextedge.Sym(ref sidecasing); + nextedge.Bond(ref topcasing); + leftcand.Bond(ref sidecasing); + leftcand.LnextSelf(); + leftcand.Sym(ref outercasing); + nextedge.LprevSelf(); + nextedge.Bond(ref outercasing); + // Correct the vertices to reflect the edge flip. + leftcand.SetOrg(lowerleft); + leftcand.SetDest(null); + leftcand.SetApex(nextapex); + nextedge.SetOrg(null); + nextedge.SetDest(upperleft); + nextedge.SetApex(nextapex); + // Consider the newly exposed vertex. + upperleft = nextapex; + // What vertex would be exposed if another edge were deleted? + sidecasing.Copy(ref nextedge); + nextapex = nextedge.Apex(); + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = Primitives.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; + } + else + { + // Avoid eating right through the triangulation. + badedge = false; + } + } + } + } + // Consider eliminating edges from the right triangulation. + if (!rightfinished) + { + // What vertex would be exposed if an edge were deleted? + rightcand.Lnext(ref nextedge); + nextedge.SymSelf(); + nextapex = nextedge.Apex(); + // If nextapex is NULL, then no vertex would be exposed; the + // triangulation would have been eaten right through. + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = Primitives.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; + while (badedge) + { + // Eliminate the edge with an edge flip. As a result, the + // right triangulation will have one more boundary triangle. + nextedge.LprevSelf(); + nextedge.Sym(ref topcasing); + nextedge.LprevSelf(); + nextedge.Sym(ref sidecasing); + nextedge.Bond(ref topcasing); + rightcand.Bond(ref sidecasing); + rightcand.LprevSelf(); + rightcand.Sym(ref outercasing); + nextedge.LnextSelf(); + nextedge.Bond(ref outercasing); + // Correct the vertices to reflect the edge flip. + rightcand.SetOrg(null); + rightcand.SetDest(lowerright); + rightcand.SetApex(nextapex); + nextedge.SetOrg(upperright); + nextedge.SetDest(null); + nextedge.SetApex(nextapex); + // Consider the newly exposed vertex. + upperright = nextapex; + // What vertex would be exposed if another edge were deleted? + sidecasing.Copy(ref nextedge); + nextapex = nextedge.Apex(); + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = Primitives.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; + } + else + { + // Avoid eating right through the triangulation. + badedge = false; + } + } + } + } + if (leftfinished || (!rightfinished && + (Primitives.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0))) + { + // Knit the triangulations, adding an edge from 'lowerleft' + // to 'upperright'. + baseedge.Bond(ref rightcand); + rightcand.Lprev(ref baseedge); + baseedge.SetDest(lowerleft); + lowerright = upperright; + baseedge.Sym(ref rightcand); + upperright = rightcand.Apex(); + } + else + { + // Knit the triangulations, adding an edge from 'upperleft' + // to 'lowerright'. + baseedge.Bond(ref leftcand); + leftcand.Lnext(ref baseedge); + baseedge.SetOrg(lowerright); + lowerleft = upperleft; + baseedge.Sym(ref leftcand); + upperleft = leftcand.Apex(); + } + } + } + + /// + /// Recursively form a Delaunay triangulation by the divide-and-conquer method. + /// + /// + /// + /// + /// + /// + /// + /// Recursively breaks down the problem into smaller pieces, which are + /// knitted together by mergehulls(). The base cases (problems of two or + /// three vertices) are handled specially here. + /// + /// On completion, 'farleft' and 'farright' are bounding triangles such that + /// the origin of 'farleft' is the leftmost vertex (breaking ties by + /// choosing the highest leftmost vertex), and the destination of + /// 'farright' is the rightmost vertex (breaking ties by choosing the + /// lowest rightmost vertex). + /// + void DivconqRecurse(int left, int right, int axis, + ref Otri farleft, ref Otri farright) + { + Otri midtri = default(Otri); + Otri tri1 = default(Otri); + Otri tri2 = default(Otri); + Otri tri3 = default(Otri); + Otri innerleft = default(Otri), innerright = default(Otri); + double area; + int vertices = right - left + 1; + int divider; + + if (vertices == 2) + { + // The triangulation of two vertices is an edge. An edge is + // represented by two bounding triangles. + mesh.MakeTriangle(ref farleft); + farleft.SetOrg(sortarray[left]); + farleft.SetDest(sortarray[left + 1]); + // The apex is intentionally left NULL. + mesh.MakeTriangle(ref farright); + farright.SetOrg(sortarray[left + 1]); + farright.SetDest(sortarray[left]); + // The apex is intentionally left NULL. + farleft.Bond(ref farright); + farleft.LprevSelf(); + farright.LnextSelf(); + farleft.Bond(ref farright); + farleft.LprevSelf(); + farright.LnextSelf(); + farleft.Bond(ref farright); + + // Ensure that the origin of 'farleft' is sortarray[0]. + farright.Lprev(ref farleft); + return; + } + else if (vertices == 3) + { + // The triangulation of three vertices is either a triangle (with + // three bounding triangles) or two edges (with four bounding + // triangles). In either case, four triangles are created. + mesh.MakeTriangle(ref midtri); + mesh.MakeTriangle(ref tri1); + mesh.MakeTriangle(ref tri2); + mesh.MakeTriangle(ref tri3); + area = Primitives.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]); + if (area == 0.0) + { + // Three collinear vertices; the triangulation is two edges. + midtri.SetOrg(sortarray[left]); + midtri.SetDest(sortarray[left + 1]); + tri1.SetOrg(sortarray[left + 1]); + tri1.SetDest(sortarray[left]); + tri2.SetOrg(sortarray[left + 2]); + tri2.SetDest(sortarray[left + 1]); + tri3.SetOrg(sortarray[left + 1]); + tri3.SetDest(sortarray[left + 2]); + // All apices are intentionally left NULL. + midtri.Bond(ref tri1); + tri2.Bond(ref tri3); + midtri.LnextSelf(); + tri1.LprevSelf(); + tri2.LnextSelf(); + tri3.LprevSelf(); + midtri.Bond(ref tri3); + tri1.Bond(ref tri2); + midtri.LnextSelf(); + tri1.LprevSelf(); + tri2.LnextSelf(); + tri3.LprevSelf(); + midtri.Bond(ref tri1); + tri2.Bond(ref tri3); + // Ensure that the origin of 'farleft' is sortarray[0]. + tri1.Copy(ref farleft); + // Ensure that the destination of 'farright' is sortarray[2]. + tri2.Copy(ref farright); + } + else + { + // The three vertices are not collinear; the triangulation is one + // triangle, namely 'midtri'. + midtri.SetOrg(sortarray[left]); + tri1.SetDest(sortarray[left]); + tri3.SetOrg(sortarray[left]); + // Apices of tri1, tri2, and tri3 are left NULL. + if (area > 0.0) + { + // The vertices are in counterclockwise order. + midtri.SetDest(sortarray[left + 1]); + tri1.SetOrg(sortarray[left + 1]); + tri2.SetDest(sortarray[left + 1]); + midtri.SetApex(sortarray[left + 2]); + tri2.SetOrg(sortarray[left + 2]); + tri3.SetDest(sortarray[left + 2]); + } + else + { + // The vertices are in clockwise order. + midtri.SetDest(sortarray[left + 2]); + tri1.SetOrg(sortarray[left + 2]); + tri2.SetDest(sortarray[left + 2]); + midtri.SetApex(sortarray[left + 1]); + tri2.SetOrg(sortarray[left + 1]); + tri3.SetDest(sortarray[left + 1]); + } + // The topology does not depend on how the vertices are ordered. + midtri.Bond(ref tri1); + midtri.LnextSelf(); + midtri.Bond(ref tri2); + midtri.LnextSelf(); + midtri.Bond(ref tri3); + tri1.LprevSelf(); + tri2.LnextSelf(); + tri1.Bond(ref tri2); + tri1.LprevSelf(); + tri3.LprevSelf(); + tri1.Bond(ref tri3); + tri2.LnextSelf(); + tri3.LprevSelf(); + tri2.Bond(ref tri3); + // Ensure that the origin of 'farleft' is sortarray[0]. + tri1.Copy(ref farleft); + // Ensure that the destination of 'farright' is sortarray[2]. + if (area > 0.0) + { + tri2.Copy(ref farright); + } + else + { + farleft.Lnext(ref farright); + } + } + + return; + } + else + { + // Split the vertices in half. + divider = vertices >> 1; + // Recursively triangulate each half. + DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft); + //DebugWriter.Session.Write(mesh, true); + DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright); + //DebugWriter.Session.Write(mesh, true); + + // Merge the two triangulations into one. + MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis); + //DebugWriter.Session.Write(mesh, true); + } + } + + /// + /// Removes ghost triangles. + /// + /// + /// Number of vertices on the hull. + int RemoveGhosts(ref Otri startghost) + { + Otri searchedge = default(Otri); + Otri dissolveedge = default(Otri); + Otri deadtriangle = default(Otri); + Vertex markorg; + + int hullsize; + + bool noPoly = !mesh.behavior.Poly; + + // Find an edge on the convex hull to start point location from. + startghost.Lprev(ref searchedge); + searchedge.SymSelf(); + Mesh.dummytri.neighbors[0] = searchedge; + // Remove the bounding box and count the convex hull edges. + startghost.Copy(ref dissolveedge); + hullsize = 0; + do + { + hullsize++; + dissolveedge.Lnext(ref deadtriangle); + dissolveedge.LprevSelf(); + dissolveedge.SymSelf(); + + // If no PSLG is involved, set the boundary markers of all the vertices + // on the convex hull. If a PSLG is used, this step is done later. + if (noPoly) + { + // Watch out for the case where all the input vertices are collinear. + if (dissolveedge.triangle != Mesh.dummytri) + { + markorg = dissolveedge.Org(); + if (markorg.mark == 0) + { + markorg.mark = 1; + } + } + } + // Remove a bounding triangle from a convex hull triangle. + dissolveedge.Dissolve(); + // Find the next bounding triangle. + deadtriangle.Sym(ref dissolveedge); + + // Delete the bounding triangle. + mesh.TriangleDealloc(deadtriangle.triangle); + } while (!dissolveedge.Equal(startghost)); + + return hullsize; + } + + /// + /// Form a Delaunay triangulation by the divide-and-conquer method. + /// + /// + /// + /// Sorts the vertices, calls a recursive procedure to triangulate them, and + /// removes the bounding box, setting boundary markers as appropriate. + /// + public int Triangulate(Mesh m) + { + Otri hullleft = default(Otri), hullright = default(Otri); + int divider; + int i, j; + + this.mesh = m; + + //DebugWriter.Session.Start("test-dbg"); + + // Allocate an array of pointers to vertices for sorting. + // TODO: use ToArray + this.sortarray = new Vertex[m.invertices]; + i = 0; + foreach (var v in m.vertices.Values) + { + sortarray[i++] = v; + } + // Sort the vertices. + //Array.Sort(sortarray); + VertexSort(0, m.invertices - 1); + // Discard duplicate vertices, which can really mess up the algorithm. + i = 0; + for (j = 1; j < m.invertices; j++) + { + if ((sortarray[i].x == sortarray[j].x) + && (sortarray[i].y == sortarray[j].y)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning( + String.Format("A duplicate vertex appeared and was ignored (ID {0}).", sortarray[j].hash), + "DivConquer.DivconqDelaunay()"); + } + sortarray[j].type = VertexType.UndeadVertex; + m.undeads++; + } + else + { + i++; + sortarray[i] = sortarray[j]; + } + } + i++; + if (useDwyer) + { + // Re-sort the array of vertices to accommodate alternating cuts. + divider = i >> 1; + if (i - divider >= 2) + { + if (divider >= 2) + { + AlternateAxes(0, divider - 1, 1); + } + AlternateAxes(divider, i - 1, 1); + } + } + + // Form the Delaunay triangulation. + DivconqRecurse(0, i-1, 0, ref hullleft, ref hullright); + + //DebugWriter.Session.Write(mesh); + //DebugWriter.Session.Finish(); + + return RemoveGhosts(ref hullleft); + } + } +} diff --git a/ThirdParty/Triangle/Algorithm/ITriangulator.cs b/ThirdParty/Triangle/Algorithm/ITriangulator.cs new file mode 100644 index 0000000..ab11c00 --- /dev/null +++ b/ThirdParty/Triangle/Algorithm/ITriangulator.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Algorithm +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// TODO: Update summary. + /// + public interface ITriangulator + { + int Triangulate(Mesh mesh); + } +} diff --git a/ThirdParty/Triangle/Algorithm/Incremental.cs b/ThirdParty/Triangle/Algorithm/Incremental.cs new file mode 100644 index 0000000..b6a09b6 --- /dev/null +++ b/ThirdParty/Triangle/Algorithm/Incremental.cs @@ -0,0 +1,181 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Algorithm +{ + using TriangleNet.Data; + using TriangleNet.Log; + using TriangleNet.Geometry; + + /// + /// Builds a delaunay triangulation using the incremental algorithm. + /// + class Incremental + { + Mesh mesh; + + /// + /// Form an "infinite" bounding triangle to insert vertices into. + /// + /// + /// The vertices at "infinity" are assigned finite coordinates, which are + /// used by the point location routines, but (mostly) ignored by the + /// Delaunay edge flip routines. + /// + void GetBoundingBox() + { + Otri inftri = default(Otri); // Handle for the triangular bounding box. + BoundingBox box = mesh.bounds; + + // Find the width (or height, whichever is larger) of the triangulation. + double width = box.Width; + if (box.Height > width) + { + width = box.Height; + } + if (width == 0.0) + { + width = 1.0; + } + // Create the vertices of the bounding box. + mesh.infvertex1 = new Vertex(box.Xmin - 50.0 * width, box.Ymin - 40.0 * width); + mesh.infvertex2 = new Vertex(box.Xmax + 50.0 * width, box.Ymin - 40.0 * width); + mesh.infvertex3 = new Vertex(0.5 * (box.Xmin + box.Xmax), box.Ymax + 60.0 * width); + + // Create the bounding box. + mesh.MakeTriangle(ref inftri); + inftri.SetOrg(mesh.infvertex1); + inftri.SetDest(mesh.infvertex2); + inftri.SetApex(mesh.infvertex3); + // Link dummytri to the bounding box so we can always find an + // edge to begin searching (point location) from. + Mesh.dummytri.neighbors[0] = inftri; + } + + /// + /// Remove the "infinite" bounding triangle, setting boundary markers as appropriate. + /// + /// Returns the number of edges on the convex hull of the triangulation. + /// + /// The triangular bounding box has three boundary triangles (one for each + /// side of the bounding box), and a bunch of triangles fanning out from + /// the three bounding box vertices (one triangle for each edge of the + /// convex hull of the inner mesh). This routine removes these triangles. + /// + int RemoveBox() + { + Otri deadtriangle = default(Otri); + Otri searchedge = default(Otri); + Otri checkedge = default(Otri); + Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri); + Vertex markorg; + int hullsize; + + bool noPoly = !mesh.behavior.Poly; + + // Find a boundary triangle. + nextedge.triangle = Mesh.dummytri; + nextedge.orient = 0; + nextedge.SymSelf(); + // Mark a place to stop. + nextedge.Lprev(ref finaledge); + nextedge.LnextSelf(); + nextedge.SymSelf(); + // Find a triangle (on the boundary of the vertex set) that isn't + // a bounding box triangle. + nextedge.Lprev(ref searchedge); + searchedge.SymSelf(); + // Check whether nextedge is another boundary triangle + // adjacent to the first one. + nextedge.Lnext(ref checkedge); + checkedge.SymSelf(); + if (checkedge.triangle == Mesh.dummytri) + { + // Go on to the next triangle. There are only three boundary + // triangles, and this next triangle cannot be the third one, + // so it's safe to stop here. + searchedge.LprevSelf(); + searchedge.SymSelf(); + } + // Find a new boundary edge to search from, as the current search + // edge lies on a bounding box triangle and will be deleted. + Mesh.dummytri.neighbors[0] = searchedge; + hullsize = -2; + while (!nextedge.Equal(finaledge)) + { + hullsize++; + nextedge.Lprev(ref dissolveedge); + dissolveedge.SymSelf(); + // If not using a PSLG, the vertices should be marked now. + // (If using a PSLG, markhull() will do the job.) + if (noPoly) + { + // Be careful! One must check for the case where all the input + // vertices are collinear, and thus all the triangles are part of + // the bounding box. Otherwise, the setvertexmark() call below + // will cause a bad pointer reference. + if (dissolveedge.triangle != Mesh.dummytri) + { + markorg = dissolveedge.Org(); + if (markorg.mark == 0) + { + markorg.mark = 1; + } + } + } + // Disconnect the bounding box triangle from the mesh triangle. + dissolveedge.Dissolve(); + nextedge.Lnext(ref deadtriangle); + deadtriangle.Sym(ref nextedge); + // Get rid of the bounding box triangle. + mesh.TriangleDealloc(deadtriangle.triangle); + // Do we need to turn the corner? + if (nextedge.triangle == Mesh.dummytri) + { + // Turn the corner. + dissolveedge.Copy(ref nextedge); + } + } + mesh.TriangleDealloc(finaledge.triangle); + + return hullsize; + } + + /// + /// Form a Delaunay triangulation by incrementally inserting vertices. + /// + /// Returns the number of edges on the convex hull of the + /// triangulation. + public int Triangulate(Mesh mesh) + { + this.mesh = mesh; + + Otri starttri = new Otri(); + + // Create a triangular bounding box. + GetBoundingBox(); + + foreach (var v in mesh.vertices.Values) + { + starttri.triangle = Mesh.dummytri; + Osub tmp = default(Osub); + if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("A duplicate vertex appeared and was ignored.", + "Incremental.IncrementalDelaunay()"); + } + v.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } + // Remove the bounding box. + return RemoveBox(); + } + } +} diff --git a/ThirdParty/Triangle/Algorithm/SweepLine.cs b/ThirdParty/Triangle/Algorithm/SweepLine.cs new file mode 100644 index 0000000..aa487bf --- /dev/null +++ b/ThirdParty/Triangle/Algorithm/SweepLine.cs @@ -0,0 +1,804 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Algorithm +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Data; + using TriangleNet.Log; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Builds a delaunay triangulation using the sweepline algorithm. + /// + class SweepLine + { + static int randomseed = 1; + static int SAMPLERATE = 10; + + int randomnation(int choices) + { + randomseed = (randomseed * 1366 + 150889) % 714025; + return randomseed / (714025 / choices + 1); + } + + Mesh mesh; + double xminextreme; // Nonexistent x value used as a flag in sweepline. + List splaynodes; + + #region Heap + + void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent) + { + double eventx, eventy; + int eventnum; + int parent; + bool notdone; + + eventx = newevent.xkey; + eventy = newevent.ykey; + eventnum = heapsize; + notdone = eventnum > 0; + while (notdone) + { + parent = (eventnum - 1) >> 1; + if ((heap[parent].ykey < eventy) || + ((heap[parent].ykey == eventy) + && (heap[parent].xkey <= eventx))) + { + notdone = false; + } + else + { + heap[eventnum] = heap[parent]; + heap[eventnum].heapposition = eventnum; + + eventnum = parent; + notdone = eventnum > 0; + } + } + heap[eventnum] = newevent; + newevent.heapposition = eventnum; + } + + void Heapify(SweepEvent[] heap, int heapsize, int eventnum) + { + SweepEvent thisevent; + double eventx, eventy; + int leftchild, rightchild; + int smallest; + bool notdone; + + thisevent = heap[eventnum]; + eventx = thisevent.xkey; + eventy = thisevent.ykey; + leftchild = 2 * eventnum + 1; + notdone = leftchild < heapsize; + while (notdone) + { + if ((heap[leftchild].ykey < eventy) || + ((heap[leftchild].ykey == eventy) + && (heap[leftchild].xkey < eventx))) + { + smallest = leftchild; + } + else + { + smallest = eventnum; + } + rightchild = leftchild + 1; + if (rightchild < heapsize) + { + if ((heap[rightchild].ykey < heap[smallest].ykey) || + ((heap[rightchild].ykey == heap[smallest].ykey) + && (heap[rightchild].xkey < heap[smallest].xkey))) + { + smallest = rightchild; + } + } + if (smallest == eventnum) + { + notdone = false; + } + else + { + heap[eventnum] = heap[smallest]; + heap[eventnum].heapposition = eventnum; + heap[smallest] = thisevent; + thisevent.heapposition = smallest; + + eventnum = smallest; + leftchild = 2 * eventnum + 1; + notdone = leftchild < heapsize; + } + } + } + + void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum) + { + SweepEvent moveevent; + double eventx, eventy; + int parent; + bool notdone; + + moveevent = heap[heapsize - 1]; + if (eventnum > 0) + { + eventx = moveevent.xkey; + eventy = moveevent.ykey; + do + { + parent = (eventnum - 1) >> 1; + if ((heap[parent].ykey < eventy) || + ((heap[parent].ykey == eventy) + && (heap[parent].xkey <= eventx))) + { + notdone = false; + } + else + { + heap[eventnum] = heap[parent]; + heap[eventnum].heapposition = eventnum; + + eventnum = parent; + notdone = eventnum > 0; + } + } while (notdone); + } + heap[eventnum] = moveevent; + moveevent.heapposition = eventnum; + Heapify(heap, heapsize - 1, eventnum); + } + + void CreateHeap(out SweepEvent[] eventheap) + { + Vertex thisvertex; + int maxevents; + int i; + SweepEvent evt; + + maxevents = (3 * mesh.invertices) / 2; + eventheap = new SweepEvent[maxevents]; + + i = 0; + foreach (var v in mesh.vertices.Values) + { + thisvertex = v; + evt = new SweepEvent(); + evt.vertexEvent = thisvertex; + evt.xkey = thisvertex.x; + evt.ykey = thisvertex.y; + HeapInsert(eventheap, i++, evt); + + } + } + + #endregion + + #region Splaytree + + SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri) + { + SplayNode child, grandchild; + SplayNode lefttree, righttree; + SplayNode leftright; + Vertex checkvertex; + bool rightofroot, rightofchild; + + if (splaytree == null) + { + return null; + } + checkvertex = splaytree.keyedge.Dest(); + if (checkvertex == splaytree.keydest) + { + rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint); + if (rightofroot) + { + splaytree.keyedge.Copy(ref searchtri); + child = splaytree.rchild; + } + else + { + child = splaytree.lchild; + } + if (child == null) + { + return splaytree; + } + checkvertex = child.keyedge.Dest(); + if (checkvertex != child.keydest) + { + child = Splay(child, searchpoint, ref searchtri); + if (child == null) + { + if (rightofroot) + { + splaytree.rchild = null; + } + else + { + splaytree.lchild = null; + } + return splaytree; + } + } + rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint); + if (rightofchild) + { + child.keyedge.Copy(ref searchtri); + grandchild = Splay(child.rchild, searchpoint, ref searchtri); + child.rchild = grandchild; + } + else + { + grandchild = Splay(child.lchild, searchpoint, ref searchtri); + child.lchild = grandchild; + } + if (grandchild == null) + { + if (rightofroot) + { + splaytree.rchild = child.lchild; + child.lchild = splaytree; + } + else + { + splaytree.lchild = child.rchild; + child.rchild = splaytree; + } + return child; + } + if (rightofchild) + { + if (rightofroot) + { + splaytree.rchild = child.lchild; + child.lchild = splaytree; + } + else + { + splaytree.lchild = grandchild.rchild; + grandchild.rchild = splaytree; + } + child.rchild = grandchild.lchild; + grandchild.lchild = child; + } + else + { + if (rightofroot) + { + splaytree.rchild = grandchild.lchild; + grandchild.lchild = splaytree; + } + else + { + splaytree.lchild = child.rchild; + child.rchild = splaytree; + } + child.lchild = grandchild.rchild; + grandchild.rchild = child; + } + return grandchild; + } + else + { + lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri); + righttree = Splay(splaytree.rchild, searchpoint, ref searchtri); + + splaynodes.Remove(splaytree); + if (lefttree == null) + { + return righttree; + } + else if (righttree == null) + { + return lefttree; + } + else if (lefttree.rchild == null) + { + lefttree.rchild = righttree.lchild; + righttree.lchild = lefttree; + return righttree; + } + else if (righttree.lchild == null) + { + righttree.lchild = lefttree.rchild; + lefttree.rchild = righttree; + return lefttree; + } + else + { + // printf("Holy Toledo!!!\n"); + leftright = lefttree.rchild; + while (leftright.rchild != null) + { + leftright = leftright.rchild; + } + leftright.rchild = righttree; + return lefttree; + } + } + } + + SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint) + { + SplayNode newsplaynode; + + newsplaynode = new SplayNode(); //poolalloc(m.splaynodes); + splaynodes.Add(newsplaynode); + newkey.Copy(ref newsplaynode.keyedge); + newsplaynode.keydest = newkey.Dest(); + if (splayroot == null) + { + newsplaynode.lchild = null; + newsplaynode.rchild = null; + } + else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint)) + { + newsplaynode.lchild = splayroot; + newsplaynode.rchild = splayroot.rchild; + splayroot.rchild = null; + } + else + { + newsplaynode.lchild = splayroot.lchild; + newsplaynode.rchild = splayroot; + splayroot.lchild = null; + } + return newsplaynode; + } + + #endregion + + SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey, + Vertex pa, Vertex pb, Vertex pc, double topy) + { + double ccwabc; + double xac, yac, xbc, ybc; + double aclen2, bclen2; + Point searchpoint = new Point(); // TODO: mesh.nextras + Otri dummytri = default(Otri); + + ccwabc = Primitives.CounterClockwise(pa, pb, pc); + xac = pa.x - pc.x; + yac = pa.y - pc.y; + xbc = pb.x - pc.x; + ybc = pb.y - pc.y; + aclen2 = xac * xac + yac * yac; + bclen2 = xbc * xbc + ybc * ybc; + searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); + searchpoint.y = topy; + return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint); + } + + bool RightOfHyperbola(ref Otri fronttri, Point newsite) + { + Vertex leftvertex, rightvertex; + double dxa, dya, dxb, dyb; + + Statistic.HyperbolaCount++; + + leftvertex = fronttri.Dest(); + rightvertex = fronttri.Apex(); + if ((leftvertex.y < rightvertex.y) || + ((leftvertex.y == rightvertex.y) && + (leftvertex.x < rightvertex.x))) + { + if (newsite.x >= rightvertex.x) + { + return true; + } + } + else + { + if (newsite.x <= leftvertex.x) + { + return false; + } + } + dxa = leftvertex.x - newsite.x; + dya = leftvertex.y - newsite.y; + dxb = rightvertex.x - newsite.x; + dyb = rightvertex.y - newsite.y; + return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya); + } + + double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc) + { + double xac, yac, xbc, ybc, xab, yab; + double aclen2, bclen2, ablen2; + + Statistic.CircleTopCount++; + + xac = pa.x - pc.x; + yac = pa.y - pc.y; + xbc = pb.x - pc.x; + ybc = pb.y - pc.y; + xab = pa.x - pb.x; + yab = pa.y - pb.y; + aclen2 = xac * xac + yac * yac; + bclen2 = xbc * xbc + ybc * ybc; + ablen2 = xab * xab + yab * yab; + return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc); + } + + void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize) + { + SweepEvent deadevent; + SweepEventVertex eventvertex; + int eventnum = -1; + + eventvertex = checktri.Org() as SweepEventVertex; + if (eventvertex != null) + { + deadevent = eventvertex.evt; + eventnum = deadevent.heapposition; + + HeapDelete(eventheap, heapsize, eventnum); + heapsize--; + checktri.SetOrg(null); + } + } + + SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex, + ref Otri searchtri, ref bool farright) + { + bool farrightflag; + + bottommost.Copy(ref searchtri); + splayroot = Splay(splayroot, searchvertex, ref searchtri); + + farrightflag = false; + while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex)) + { + searchtri.OnextSelf(); + farrightflag = searchtri.Equal(bottommost); + } + farright = farrightflag; + return splayroot; + } + + /// + /// Removes ghost triangles. + /// + /// + /// Number of vertices on the hull. + int RemoveGhosts(ref Otri startghost) + { + Otri searchedge = default(Otri); + Otri dissolveedge = default(Otri); + Otri deadtriangle = default(Otri); + Vertex markorg; + int hullsize; + + bool noPoly = !mesh.behavior.Poly; + + // Find an edge on the convex hull to start point location from. + startghost.Lprev(ref searchedge); + searchedge.SymSelf(); + Mesh.dummytri.neighbors[0] = searchedge; + // Remove the bounding box and count the convex hull edges. + startghost.Copy(ref dissolveedge); + hullsize = 0; + do + { + hullsize++; + dissolveedge.Lnext(ref deadtriangle); + dissolveedge.LprevSelf(); + dissolveedge.SymSelf(); + + // If no PSLG is involved, set the boundary markers of all the vertices + // on the convex hull. If a PSLG is used, this step is done later. + if (noPoly) + { + // Watch out for the case where all the input vertices are collinear. + if (dissolveedge.triangle != Mesh.dummytri) + { + markorg = dissolveedge.Org(); + if (markorg.mark == 0) + { + markorg.mark = 1; + } + } + } + // Remove a bounding triangle from a convex hull triangle. + dissolveedge.Dissolve(); + // Find the next bounding triangle. + deadtriangle.Sym(ref dissolveedge); + + // Delete the bounding triangle. + mesh.TriangleDealloc(deadtriangle.triangle); + } while (!dissolveedge.Equal(startghost)); + + return hullsize; + } + + public int Triangulate(Mesh mesh) + { + this.mesh = mesh; + + // Nonexistent x value used as a flag to mark circle events in sweepline + // Delaunay algorithm. + xminextreme = 10 * mesh.bounds.Xmin - 9 * mesh.bounds.Xmax; + + SweepEvent[] eventheap; + + SweepEvent nextevent; + SweepEvent newevent; + SplayNode splayroot; + Otri bottommost = default(Otri); + Otri searchtri = default(Otri); + Otri fliptri; + Otri lefttri = default(Otri); + Otri righttri = default(Otri); + Otri farlefttri = default(Otri); + Otri farrighttri = default(Otri); + Otri inserttri = default(Otri); + Vertex firstvertex, secondvertex; + Vertex nextvertex, lastvertex; + Vertex connectvertex; + Vertex leftvertex, midvertex, rightvertex; + double lefttest, righttest; + int heapsize; + bool check4events, farrightflag = false; + + splaynodes = new List(); + splayroot = null; + + CreateHeap(out eventheap);//, out events, out freeevents); + heapsize = mesh.invertices; + + mesh.MakeTriangle(ref lefttri); + mesh.MakeTriangle(ref righttri); + lefttri.Bond(ref righttri); + lefttri.LnextSelf(); + righttri.LprevSelf(); + lefttri.Bond(ref righttri); + lefttri.LnextSelf(); + righttri.LprevSelf(); + lefttri.Bond(ref righttri); + firstvertex = eventheap[0].vertexEvent; + + HeapDelete(eventheap, heapsize, 0); + heapsize--; + do + { + if (heapsize == 0) + { + SimpleLog.Instance.Error("Input vertices are all identical.", "SweepLine.SweepLineDelaunay()"); + throw new Exception("Input vertices are all identical."); + } + secondvertex = eventheap[0].vertexEvent; + HeapDelete(eventheap, heapsize, 0); + heapsize--; + if ((firstvertex.x == secondvertex.x) && + (firstvertex.y == secondvertex.y)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("A duplicate vertex appeared and was ignored.", + "SweepLine.SweepLineDelaunay().1"); + } + secondvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } while ((firstvertex.x == secondvertex.x) && + (firstvertex.y == secondvertex.y)); + lefttri.SetOrg(firstvertex); + lefttri.SetDest(secondvertex); + righttri.SetOrg(secondvertex); + righttri.SetDest(firstvertex); + lefttri.Lprev(ref bottommost); + lastvertex = secondvertex; + + while (heapsize > 0) + { + nextevent = eventheap[0]; + HeapDelete(eventheap, heapsize, 0); + heapsize--; + check4events = true; + if (nextevent.xkey < mesh.bounds.Xmin) + { + fliptri = nextevent.otriEvent; + fliptri.Oprev(ref farlefttri); + Check4DeadEvent(ref farlefttri, eventheap, ref heapsize); + fliptri.Onext(ref farrighttri); + Check4DeadEvent(ref farrighttri, eventheap, ref heapsize); + + if (farlefttri.Equal(bottommost)) + { + fliptri.Lprev(ref bottommost); + } + mesh.Flip(ref fliptri); + fliptri.SetApex(null); + fliptri.Lprev(ref lefttri); + fliptri.Lnext(ref righttri); + lefttri.Sym(ref farlefttri); + + if (randomnation(SAMPLERATE) == 0) + { + fliptri.SymSelf(); + leftvertex = fliptri.Dest(); + midvertex = fliptri.Apex(); + rightvertex = fliptri.Org(); + splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey); + } + } + else + { + nextvertex = nextevent.vertexEvent; + if ((nextvertex.x == lastvertex.x) && + (nextvertex.y == lastvertex.y)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("A duplicate vertex appeared and was ignored.", + "SweepLine.SweepLineDelaunay().2"); + } + nextvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + check4events = false; + } + else + { + lastvertex = nextvertex; + + splayroot = FrontLocate(splayroot, bottommost, nextvertex, + ref searchtri, ref farrightflag); + // + bottommost.Copy(ref searchtri); + farrightflag = false; + while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex)) + { + searchtri.OnextSelf(); + farrightflag = searchtri.Equal(bottommost); + } + + + Check4DeadEvent(ref searchtri, eventheap, ref heapsize); + + searchtri.Copy(ref farrighttri); + searchtri.Sym(ref farlefttri); + mesh.MakeTriangle(ref lefttri); + mesh.MakeTriangle(ref righttri); + connectvertex = farrighttri.Dest(); + lefttri.SetOrg(connectvertex); + lefttri.SetDest(nextvertex); + righttri.SetOrg(nextvertex); + righttri.SetDest(connectvertex); + lefttri.Bond(ref righttri); + lefttri.LnextSelf(); + righttri.LprevSelf(); + lefttri.Bond(ref righttri); + lefttri.LnextSelf(); + righttri.LprevSelf(); + lefttri.Bond(ref farlefttri); + righttri.Bond(ref farrighttri); + if (!farrightflag && farrighttri.Equal(bottommost)) + { + lefttri.Copy(ref bottommost); + } + + if (randomnation(SAMPLERATE) == 0) + { + splayroot = SplayInsert(splayroot, lefttri, nextvertex); + } + else if (randomnation(SAMPLERATE) == 0) + { + righttri.Lnext(ref inserttri); + splayroot = SplayInsert(splayroot, inserttri, nextvertex); + } + } + } + + if (check4events) + { + leftvertex = farlefttri.Apex(); + midvertex = lefttri.Dest(); + rightvertex = lefttri.Apex(); + lefttest = Primitives.CounterClockwise(leftvertex, midvertex, rightvertex); + if (lefttest > 0.0) + { + newevent = new SweepEvent(); + + newevent.xkey = xminextreme; + newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest); + newevent.otriEvent = lefttri; + HeapInsert(eventheap, heapsize, newevent); + heapsize++; + lefttri.SetOrg(new SweepEventVertex(newevent)); + } + leftvertex = righttri.Apex(); + midvertex = righttri.Org(); + rightvertex = farrighttri.Apex(); + righttest = Primitives.CounterClockwise(leftvertex, midvertex, rightvertex); + if (righttest > 0.0) + { + newevent = new SweepEvent(); + + newevent.xkey = xminextreme; + newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest); + newevent.otriEvent = farrighttri; + HeapInsert(eventheap, heapsize, newevent); + heapsize++; + farrighttri.SetOrg(new SweepEventVertex(newevent)); + } + } + } + + splaynodes.Clear(); + bottommost.LprevSelf(); + return RemoveGhosts(ref bottommost); + } + + #region Internal classes + + /// + /// A node in a heap used to store events for the sweepline Delaunay algorithm. + /// + /// + /// Only used in the sweepline algorithm. + /// + /// Nodes do not point directly to their parents or children in the heap. Instead, each + /// node knows its position in the heap, and can look up its parent and children in a + /// separate array. To distinguish site events from circle events, all circle events are + /// given an invalid (smaller than 'xmin') x-coordinate 'xkey'. + /// + class SweepEvent + { + public double xkey, ykey; // Coordinates of the event. + public Vertex vertexEvent; // Vertex event. + public Otri otriEvent; // Circle event. + public int heapposition; // Marks this event's position in the heap. + } + + /// + /// Introducing a new class which aggregates a sweep event is the easiest way + /// to handle the pointer magic of the original code (casting a sweep event + /// to vertex etc.). + /// + class SweepEventVertex : Vertex + { + public SweepEvent evt; + + public SweepEventVertex(SweepEvent e) + { + evt = e; + } + } + + /// + /// A node in the splay tree. + /// + /// + /// Only used in the sweepline algorithm. + /// + /// Each node holds an oriented ghost triangle that represents a boundary edge + /// of the growing triangulation. When a circle event covers two boundary edges + /// with a triangle, so that they are no longer boundary edges, those edges are + /// not immediately deleted from the tree; rather, they are lazily deleted when + /// they are next encountered. (Since only a random sample of boundary edges are + /// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that + /// a triangle is still the same as when it entered the splay tree; if it has + /// been rotated (due to a circle event), it no longer represents a boundary + /// edge and should be deleted. + /// + class SplayNode + { + public Otri keyedge; // Lprev of an edge on the front. + public Vertex keydest; // Used to verify that splay node is still live. + public SplayNode lchild, rchild; // Children in splay tree. + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/BadTriQueue.cs b/ThirdParty/Triangle/BadTriQueue.cs new file mode 100644 index 0000000..3fc7259 --- /dev/null +++ b/ThirdParty/Triangle/BadTriQueue.cs @@ -0,0 +1,196 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System.Collections.Generic; + using TriangleNet.Data; + + /// + /// A (priority) queue for bad triangles. + /// + /// + // The queue is actually a set of 4096 queues. I use multiple queues to + // give priority to smaller angles. I originally implemented a heap, but + // the queues are faster by a larger margin than I'd suspected. + /// + class BadTriQueue + { + static readonly double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732; + + public int Count { get { return this.count; } } + + // Variables that maintain the bad triangle queues. The queues are + // ordered from 4095 (highest priority) to 0 (lowest priority). + BadTriangle[] queuefront; + BadTriangle[] queuetail; + int[] nextnonemptyq; + int firstnonemptyq; + + int count; + + public BadTriQueue() + { + //badtriangles = new List(); + + queuefront = new BadTriangle[4096]; + queuetail = new BadTriangle[4096]; + nextnonemptyq = new int[4096]; + + firstnonemptyq = -1; + + count = 0; + } + + /// + /// Add a bad triangle data structure to the end of a queue. + /// + /// The bad triangle to enqueue. + public void Enqueue(BadTriangle badtri) + { + double length, multiplier; + int exponent, expincrement; + int queuenumber; + int posexponent; + int i; + + this.count++; + + // Determine the appropriate queue to put the bad triangle into. + // Recall that the key is the square of its shortest edge length. + if (badtri.key >= 1.0) + { + length = badtri.key; + posexponent = 1; + } + else + { + // 'badtri.key' is 2.0 to a negative exponent, so we'll record that + // fact and use the reciprocal of 'badtri.key', which is > 1.0. + length = 1.0 / badtri.key; + posexponent = 0; + } + // 'length' is approximately 2.0 to what exponent? The following code + // determines the answer in time logarithmic in the exponent. + exponent = 0; + while (length > 2.0) + { + // Find an approximation by repeated squaring of two. + expincrement = 1; + multiplier = 0.5; + while (length * multiplier * multiplier > 1.0) + { + expincrement *= 2; + multiplier *= multiplier; + } + // Reduce the value of 'length', then iterate if necessary. + exponent += expincrement; + length *= multiplier; + } + // 'length' is approximately squareroot(2.0) to what exponent? + exponent = 2 * exponent + (length > SQRT2 ? 1 : 0); + // 'exponent' is now in the range 0...2047 for IEEE double precision. + // Choose a queue in the range 0...4095. The shortest edges have the + // highest priority (queue 4095). + if (posexponent > 0) + { + queuenumber = 2047 - exponent; + } + else + { + queuenumber = 2048 + exponent; + } + + // Are we inserting into an empty queue? + if (queuefront[queuenumber] == null) + { + // Yes, we are inserting into an empty queue. + // Will this become the highest-priority queue? + if (queuenumber > firstnonemptyq) + { + // Yes, this is the highest-priority queue. + nextnonemptyq[queuenumber] = firstnonemptyq; + firstnonemptyq = queuenumber; + } + else + { + // No, this is not the highest-priority queue. + // Find the queue with next higher priority. + i = queuenumber + 1; + while (queuefront[i] == null) + { + i++; + } + // Mark the newly nonempty queue as following a higher-priority queue. + nextnonemptyq[queuenumber] = nextnonemptyq[i]; + nextnonemptyq[i] = queuenumber; + } + // Put the bad triangle at the beginning of the (empty) queue. + queuefront[queuenumber] = badtri; + } + else + { + // Add the bad triangle to the end of an already nonempty queue. + queuetail[queuenumber].nexttriang = badtri; + } + // Maintain a pointer to the last triangle of the queue. + queuetail[queuenumber] = badtri; + // Newly enqueued bad triangle has no successor in the queue. + badtri.nexttriang = null; + } + + /// + /// Add a bad triangle to the end of a queue. + /// + /// + /// + /// + /// + /// + public void Enqueue(ref Otri enqtri, double minedge, Vertex enqapex, Vertex enqorg, Vertex enqdest) + { + // Allocate space for the bad triangle. + BadTriangle newbad = new BadTriangle(); + + newbad.poortri = enqtri; + newbad.key = minedge; + newbad.triangapex = enqapex; + newbad.triangorg = enqorg; + newbad.triangdest = enqdest; + + Enqueue(newbad); + } + + /// + /// Remove a triangle from the front of the queue. + /// + /// + public BadTriangle Dequeue() + { + // If no queues are nonempty, return NULL. + if (firstnonemptyq < 0) + { + return null; + } + + this.count--; + + // Find the first triangle of the highest-priority queue. + BadTriangle result = queuefront[firstnonemptyq]; + // Remove the triangle from the queue. + queuefront[firstnonemptyq] = result.nexttriang; + // If this queue is now empty, note the new highest-priority + // nonempty queue. + if (result == queuetail[firstnonemptyq]) + { + firstnonemptyq = nextnonemptyq[firstnonemptyq]; + } + + return result; + } + } +} diff --git a/ThirdParty/Triangle/Behavior.cs b/ThirdParty/Triangle/Behavior.cs new file mode 100644 index 0000000..4087e39 --- /dev/null +++ b/ThirdParty/Triangle/Behavior.cs @@ -0,0 +1,276 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Log; + + /// + /// Controls the behavior of the meshing software. + /// + public class Behavior + { + #region Class members + + bool poly = false; + bool quality = false; + bool varArea = false; + bool usertest = false; + bool convex = false; + bool jettison = false; + bool boundaryMarkers = true; + bool noHoles = false; + bool conformDel = false; + TriangulationAlgorithm algorithm = TriangulationAlgorithm.Dwyer; + + int noBisect = 0; + int steiner = -1; + + double minAngle = 0.0; + double maxAngle = 0.0; + double maxArea = -1.0; + + internal bool fixedArea = false; + internal bool useSegments = true; + internal bool useRegions = false; + internal double goodAngle = 0.0; + internal double maxGoodAngle = 0.0; + internal double offconstant = 0.0; + + #endregion + + /// + /// Creates an instance of the Behavior class. + /// + public Behavior(bool quality = false, double minAngle = 20.0) + { + if (quality) + { + this.quality = true; + this.minAngle = minAngle; + + Update(); + } + } + + /// + /// Update quality options dependencies. + /// + private void Update() + { + this.quality = true; + + if (this.minAngle < 0 || this.minAngle > 60) + { + this.minAngle = 0; + this.quality = false; + + SimpleLog.Instance.Warning("Invalid quality option (minimum angle).", "Mesh.Behavior"); + } + + if ((this.maxAngle != 0.0) && this.maxAngle < 90 || this.maxAngle > 180) + { + this.maxAngle = 0; + this.quality = false; + + SimpleLog.Instance.Warning("Invalid quality option (maximum angle).", "Mesh.Behavior"); + } + + this.useSegments = this.Poly || this.Quality || this.Convex; + this.goodAngle = Math.Cos(this.MinAngle * Math.PI / 180.0); + this.maxGoodAngle = Math.Cos(this.MaxAngle * Math.PI / 180.0); + + if (this.goodAngle == 1.0) + { + this.offconstant = 0.0; + } + else + { + this.offconstant = 0.475 * Math.Sqrt((1.0 + this.goodAngle) / (1.0 - this.goodAngle)); + } + + this.goodAngle *= this.goodAngle; + } + + #region Static properties + + /// + /// No exact arithmetic. + /// + public static bool NoExact { get; set; } + + /// + /// Log detailed information. + /// + public static bool Verbose { get; set; } + + #endregion + + #region Public properties + + /// + /// Quality mesh generation. + /// + public bool Quality + { + get { return quality; } + set + { + quality = value; + if (quality) + { + Update(); + } + } + } + + /// + /// Minimum angle constraint. + /// + public double MinAngle + { + get { return minAngle; } + set { minAngle = value; Update(); } + } + + /// + /// Maximum angle constraint. + /// + public double MaxAngle + { + get { return maxAngle; } + set { maxAngle = value; Update(); } + } + + /// + /// Maximum area constraint. + /// + public double MaxArea + { + get { return maxArea; } + set + { + maxArea = value; + fixedArea = value > 0; + } + } + + /// + /// Apply a maximum triangle area constraint. + /// + public bool VarArea + { + get { return varArea; } + set { varArea = value; } + } + + /// + /// Input is a Planar Straight Line Graph. + /// + public bool Poly + { + get { return poly; } + set { poly = value; } + } + + /// + /// Apply a user-defined triangle constraint. + /// + public bool Usertest + { + get { return usertest; } + set { usertest = value; } + } + + /// + /// Enclose the convex hull with segments. + /// + public bool Convex + { + get { return convex; } + set { convex = value; } + } + + /// + /// Conforming Delaunay (all triangles are truly Delaunay). + /// + public bool ConformingDelaunay + { + get { return conformDel; } + set { conformDel = value; } + } + + /// + /// Algorithm to use for triangulation. + /// + public TriangulationAlgorithm Algorithm + { + get { return algorithm; } + set { algorithm = value; } + } + + /// + /// Suppresses boundary segment splitting. + /// + /// + /// 0 = split segments + /// 1 = no new vertices on the boundary + /// 2 = prevent all segment splitting, including internal boundaries + /// + public int NoBisect + { + get { return noBisect; } + set + { + noBisect = value; + if (noBisect < 0 || noBisect > 2) + { + noBisect = 0; + } + } + } + + /// + /// Use maximum number of Steiner points. + /// + public int SteinerPoints + { + get { return steiner; } + set { steiner = value; } + } + + /// + /// Compute boundary information. + /// + public bool UseBoundaryMarkers + { + get { return boundaryMarkers; } + set { boundaryMarkers = value; } + } + + /// + /// Ignores holes in polygons. + /// + public bool NoHoles + { + get { return noHoles; } + set { noHoles = value; } + } + + /// + /// Jettison unused vertices from output. + /// + public bool Jettison + { + get { return jettison; } + set { jettison = value; } + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/Carver.cs b/ThirdParty/Triangle/Carver.cs new file mode 100644 index 0000000..a0ae46a --- /dev/null +++ b/ThirdParty/Triangle/Carver.cs @@ -0,0 +1,421 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using TriangleNet.Data; + using System; + using TriangleNet.Geometry; + using System.Collections.Generic; + using TriangleNet.Tools; + + /// + /// Carves holes into the triangulation. + /// + class Carver + { + Mesh mesh; + List viri; + + public Carver(Mesh mesh) + { + this.mesh = mesh; + this.viri = new List(); + } + + /// + /// Virally infect all of the triangles of the convex hull that are not + /// protected by subsegments. Where there are subsegments, set boundary + /// markers as appropriate. + /// + private void InfectHull() + { + Otri hulltri = default(Otri); + Otri nexttri = default(Otri); + Otri starttri = default(Otri); + Osub hullsubseg = default(Osub); + Vertex horg, hdest; + + // Find a triangle handle on the hull. + hulltri.triangle = Mesh.dummytri; + hulltri.orient = 0; + hulltri.SymSelf(); + // Remember where we started so we know when to stop. + hulltri.Copy(ref starttri); + // Go once counterclockwise around the convex hull. + do + { + // Ignore triangles that are already infected. + if (!hulltri.IsInfected()) + { + // Is the triangle protected by a subsegment? + hulltri.SegPivot(ref hullsubseg); + if (hullsubseg.seg == Mesh.dummysub) + { + // The triangle is not protected; infect it. + if (!hulltri.IsInfected()) + { + hulltri.Infect(); + viri.Add(hulltri.triangle); + } + } + else + { + // The triangle is protected; set boundary markers if appropriate. + if (hullsubseg.seg.boundary == 0) + { + hullsubseg.seg.boundary = 1; + horg = hulltri.Org(); + hdest = hulltri.Dest(); + if (horg.mark == 0) + { + horg.mark = 1; + } + if (hdest.mark == 0) + { + hdest.mark = 1; + } + } + } + } + // To find the next hull edge, go clockwise around the next vertex. + hulltri.LnextSelf(); + hulltri.Oprev(ref nexttri); + while (nexttri.triangle != Mesh.dummytri) + { + nexttri.Copy(ref hulltri); + hulltri.Oprev(ref nexttri); + } + + } while (!hulltri.Equal(starttri)); + } + + /// + /// Spread the virus from all infected triangles to any neighbors not + /// protected by subsegments. Delete all infected triangles. + /// + /// + /// This is the procedure that actually creates holes and concavities. + /// + /// This procedure operates in two phases. The first phase identifies all + /// the triangles that will die, and marks them as infected. They are + /// marked to ensure that each triangle is added to the virus pool only + /// once, so the procedure will terminate. + /// + /// The second phase actually eliminates the infected triangles. It also + /// eliminates orphaned vertices. + /// + void Plague() + { + Otri testtri = default(Otri); + Otri neighbor = default(Otri); + Osub neighborsubseg = default(Osub); + Vertex testvertex; + Vertex norg, ndest; + + bool killorg; + + // Loop through all the infected triangles, spreading the virus to + // their neighbors, then to their neighbors' neighbors. + for (int i = 0; i < viri.Count; i++) + { + // WARNING: Don't use foreach, mesh.viri list may get modified. + + testtri.triangle = viri[i]; + // A triangle is marked as infected by messing with one of its pointers + // to subsegments, setting it to an illegal value. Hence, we have to + // temporarily uninfect this triangle so that we can examine its + // adjacent subsegments. + // TODO: Not true in the C# version (so we could skip this). + testtri.Uninfect(); + + // Check each of the triangle's three neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + // Find the neighbor. + testtri.Sym(ref neighbor); + // Check for a subsegment between the triangle and its neighbor. + testtri.SegPivot(ref neighborsubseg); + // Check if the neighbor is nonexistent or already infected. + if ((neighbor.triangle == Mesh.dummytri) || neighbor.IsInfected()) + { + if (neighborsubseg.seg != Mesh.dummysub) + { + // There is a subsegment separating the triangle from its + // neighbor, but both triangles are dying, so the subsegment + // dies too. + mesh.SubsegDealloc(neighborsubseg.seg); + if (neighbor.triangle != Mesh.dummytri) + { + // Make sure the subsegment doesn't get deallocated again + // later when the infected neighbor is visited. + neighbor.Uninfect(); + neighbor.SegDissolve(); + neighbor.Infect(); + } + } + } + else + { // The neighbor exists and is not infected. + if (neighborsubseg.seg == Mesh.dummysub) + { + // There is no subsegment protecting the neighbor, so + // the neighbor becomes infected. + neighbor.Infect(); + // Ensure that the neighbor's neighbors will be infected. + viri.Add(neighbor.triangle); + } + else + { + // The neighbor is protected by a subsegment. + // Remove this triangle from the subsegment. + neighborsubseg.TriDissolve(); + // The subsegment becomes a boundary. Set markers accordingly. + if (neighborsubseg.seg.boundary == 0) + { + neighborsubseg.seg.boundary = 1; + } + norg = neighbor.Org(); + ndest = neighbor.Dest(); + if (norg.mark == 0) + { + norg.mark = 1; + } + if (ndest.mark == 0) + { + ndest.mark = 1; + } + } + } + } + // Remark the triangle as infected, so it doesn't get added to the + // virus pool again. + testtri.Infect(); + } + + foreach (var virus in viri) + { + testtri.triangle = virus; + + // Check each of the three corners of the triangle for elimination. + // This is done by walking around each vertex, checking if it is + // still connected to at least one live triangle. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + testvertex = testtri.Org(); + // Check if the vertex has already been tested. + if (testvertex != null) + { + killorg = true; + // Mark the corner of the triangle as having been tested. + testtri.SetOrg(null); + // Walk counterclockwise about the vertex. + testtri.Onext(ref neighbor); + // Stop upon reaching a boundary or the starting triangle. + while ((neighbor.triangle != Mesh.dummytri) && + (!neighbor.Equal(testtri))) + { + if (neighbor.IsInfected()) + { + // Mark the corner of this triangle as having been tested. + neighbor.SetOrg(null); + } + else + { + // A live triangle. The vertex survives. + killorg = false; + } + // Walk counterclockwise about the vertex. + neighbor.OnextSelf(); + } + // If we reached a boundary, we must walk clockwise as well. + if (neighbor.triangle == Mesh.dummytri) + { + // Walk clockwise about the vertex. + testtri.Oprev(ref neighbor); + // Stop upon reaching a boundary. + while (neighbor.triangle != Mesh.dummytri) + { + if (neighbor.IsInfected()) + { + // Mark the corner of this triangle as having been tested. + neighbor.SetOrg(null); + } + else + { + // A live triangle. The vertex survives. + killorg = false; + } + // Walk clockwise about the vertex. + neighbor.OprevSelf(); + } + } + if (killorg) + { + // Deleting vertex + testvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } + } + + // Record changes in the number of boundary edges, and disconnect + // dead triangles from their neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + testtri.Sym(ref neighbor); + if (neighbor.triangle == Mesh.dummytri) + { + // There is no neighboring triangle on this edge, so this edge + // is a boundary edge. This triangle is being deleted, so this + // boundary edge is deleted. + mesh.hullsize--; + } + else + { + // Disconnect the triangle from its neighbor. + neighbor.Dissolve(); + // There is a neighboring triangle on this edge, so this edge + // becomes a boundary edge when this triangle is deleted. + mesh.hullsize++; + } + } + // Return the dead triangle to the pool of triangles. + mesh.TriangleDealloc(testtri.triangle); + } + + // Empty the virus pool. + viri.Clear(); + } + + /// + /// Find the holes and infect them. Find the area constraints and infect + /// them. Infect the convex hull. Spread the infection and kill triangles. + /// Spread the area constraints. + /// + public void CarveHoles() + { + Otri searchtri = default(Otri); + Vertex searchorg, searchdest; + LocateResult intersect; + + Triangle[] regionTris = null; + + if (!mesh.behavior.Convex) + { + // Mark as infected any unprotected triangles on the boundary. + // This is one way by which concavities are created. + InfectHull(); + } + + if (!mesh.behavior.NoHoles) + { + // Infect each triangle in which a hole lies. + foreach (var hole in mesh.holes) + { + // Ignore holes that aren't within the bounds of the mesh. + if (mesh.bounds.Contains(hole)) + { + // Start searching from some triangle on the outer boundary. + searchtri.triangle = Mesh.dummytri; + searchtri.orient = 0; + searchtri.SymSelf(); + // Ensure that the hole is to the left of this boundary edge; + // otherwise, locate() will falsely report that the hole + // falls within the starting triangle. + searchorg = searchtri.Org(); + searchdest = searchtri.Dest(); + if (Primitives.CounterClockwise(searchorg, searchdest, hole) > 0.0) + { + // Find a triangle that contains the hole. + intersect = mesh.locator.Locate(hole, ref searchtri); + if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) + { + // Infect the triangle. This is done by marking the triangle + // as infected and including the triangle in the virus pool. + searchtri.Infect(); + viri.Add(searchtri.triangle); + } + } + } + } + } + + // Now, we have to find all the regions BEFORE we carve the holes, because locate() won't + // work when the triangulation is no longer convex. (Incidentally, this is the reason why + // regional attributes and area constraints can't be used when refining a preexisting mesh, + // which might not be convex; they can only be used with a freshly triangulated PSLG.) + if (mesh.regions.Count > 0) + { + int i = 0; + + regionTris = new Triangle[mesh.regions.Count]; + + // Find the starting triangle for each region. + foreach (var region in mesh.regions) + { + regionTris[i] = Mesh.dummytri; + // Ignore region points that aren't within the bounds of the mesh. + if (mesh.bounds.Contains(region.point)) + { + // Start searching from some triangle on the outer boundary. + searchtri.triangle = Mesh.dummytri; + searchtri.orient = 0; + searchtri.SymSelf(); + // Ensure that the region point is to the left of this boundary + // edge; otherwise, locate() will falsely report that the + // region point falls within the starting triangle. + searchorg = searchtri.Org(); + searchdest = searchtri.Dest(); + if (Primitives.CounterClockwise(searchorg, searchdest, region.point) > 0.0) + { + // Find a triangle that contains the region point. + intersect = mesh.locator.Locate(region.point, ref searchtri); + if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) + { + // Record the triangle for processing after the + // holes have been carved. + regionTris[i] = searchtri.triangle; + regionTris[i].region = region.id; + } + } + } + + i++; + } + } + + if (viri.Count > 0) + { + // Carve the holes and concavities. + Plague(); + } + + if (regionTris != null) + { + var iterator = new RegionIterator(mesh); + + for (int i = 0; i < regionTris.Length; i++) + { + if (regionTris[i] != Mesh.dummytri) + { + // Make sure the triangle under consideration still exists. + // It may have been eaten by the virus. + if (!Otri.IsDead(regionTris[i])) + { + // Apply one region's attribute and/or area constraint. + iterator.Process(regionTris[i]); + } + } + } + } + + // Free up memory (virus pool should be empty anyway). + viri.Clear(); + } + } +} diff --git a/ThirdParty/Triangle/Data/BadSubseg.cs b/ThirdParty/Triangle/Data/BadSubseg.cs new file mode 100644 index 0000000..36f29ed --- /dev/null +++ b/ThirdParty/Triangle/Data/BadSubseg.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// A queue used to store encroached subsegments. + /// + /// + /// Each subsegment's vertices are stored so that we can check whether a + /// subsegment is still the same. + /// + class BadSubseg + { + private static int hashSeed = 0; + internal int Hash; + + public Osub encsubseg; // An encroached subsegment. + public Vertex subsegorg, subsegdest; // Its two vertices. + + public BadSubseg() + { + this.Hash = hashSeed++; + } + + public override int GetHashCode() + { + return this.Hash; + } + + public override string ToString() + { + return String.Format("B-SID {0}", encsubseg.seg.hash); + } + }; +} diff --git a/ThirdParty/Triangle/Data/BadTriangle.cs b/ThirdParty/Triangle/Data/BadTriangle.cs new file mode 100644 index 0000000..bc9501a --- /dev/null +++ b/ThirdParty/Triangle/Data/BadTriangle.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// A queue used to store bad triangles. + /// + /// + /// The key is the square of the cosine of the smallest angle of the triangle. + /// Each triangle's vertices are stored so that one can check whether a + /// triangle is still the same. + /// + class BadTriangle + { + public static int OTID = 0; + public int ID = 0; + + public Otri poortri; // A skinny or too-large triangle. + public double key; // cos^2 of smallest (apical) angle. + public Vertex triangorg, triangdest, triangapex; // Its three vertices. + public BadTriangle nexttriang; // Pointer to next bad triangle. + + public BadTriangle() + { + ID = OTID++; + } + public override string ToString() + { + return String.Format("B-TID {0}", poortri.triangle.hash); + } + } +} diff --git a/ThirdParty/Triangle/Data/Osub.cs b/ThirdParty/Triangle/Data/Osub.cs new file mode 100644 index 0000000..a7891cd --- /dev/null +++ b/ThirdParty/Triangle/Data/Osub.cs @@ -0,0 +1,255 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// An oriented subsegment. + /// + /// + /// Iincludes a pointer to a subsegment and an orientation. The orientation + /// denotes a side of the edge. Hence, there are two possible orientations. + /// By convention, the edge is always directed so that the "side" denoted + /// is the right side of the edge. + /// + struct Osub + { + public Segment seg; + public int orient; // Ranges from 0 to 1. + + public override string ToString() + { + if (seg == null) + { + return "O-TID [null]"; + } + return String.Format("O-SID {0}", seg.hash); + } + + #region Osub primitives + + /// + /// Reverse the orientation of a subsegment. [sym(ab) -> ba] + /// + /// ssym() toggles the orientation of a subsegment. + /// + public void Sym(ref Osub o2) + { + o2.seg = seg; + o2.orient = 1 - orient; + } + + /// + /// Reverse the orientation of a subsegment. [sym(ab) -> ba] + /// + public void SymSelf() + { + orient = 1 - orient; + } + + /// + /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] + /// + /// spivot() finds the other subsegment (from the same segment) + /// that shares the same origin. + /// + public void Pivot(ref Osub o2) + { + o2 = seg.subsegs[orient]; + //sdecode(sptr, o2); + } + + /// + /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] + /// + public void PivotSelf() + { + this = seg.subsegs[orient]; + //sdecode(sptr, osub); + } + + /// + /// Find next subsegment in sequence. [next(ab) -> b*] + /// + /// snext() finds the next subsegment (from the same segment) in + /// sequence; one whose origin is the input subsegment's destination. + /// + public void Next(ref Osub o2) + { + o2 = seg.subsegs[1 - orient]; + //sdecode(sptr, o2); + } + + /// + /// Find next subsegment in sequence. [next(ab) -> b*] + /// + public void NextSelf() + { + this = seg.subsegs[1 - orient]; + //sdecode(sptr, osub); + } + + /// + /// Get the origin of a subsegment + /// + public Vertex Org() + { + return seg.vertices[orient]; + } + + /// + /// Get the destination of a subsegment + /// + public Vertex Dest() + { + return seg.vertices[1 - orient]; + } + + /// + /// Set the origin or destination of a subsegment. + /// + public void SetOrg(Vertex ptr) + { + seg.vertices[orient] = ptr; + } + + /// + /// Set destination of a subsegment. + /// + public void SetDest(Vertex ptr) + { + seg.vertices[1 - orient] = ptr; + } + + /// + /// Get the origin of the segment that includes the subsegment. + /// + public Vertex SegOrg() + { + return seg.vertices[2 + orient]; + } + + /// + /// Get the destination of the segment that includes the subsegment. + /// + public Vertex SegDest() + { + return seg.vertices[3 - orient]; + } + + /// + /// Set the origin of the segment that includes the subsegment. + /// + public void SetSegOrg(Vertex ptr) + { + seg.vertices[2 + orient] = ptr; + } + + /// + /// Set the destination of the segment that includes the subsegment. + /// + public void SetSegDest(Vertex ptr) + { + seg.vertices[3 - orient] = ptr; + } + + /// + /// Read a boundary marker. + /// + /// Boundary markers are used to hold user-defined tags for + /// setting boundary conditions in finite element solvers. + public int Mark() + { + return seg.boundary; + } + + /// + /// Set a boundary marker. + /// + public void SetMark(int value) + { + seg.boundary = value; + } + + /// + /// Bond two subsegments together. [bond(abc, ba)] + /// + public void Bond(ref Osub o2) + { + seg.subsegs[orient] = o2; + o2.seg.subsegs[o2.orient] = this; + } + + /// + /// Dissolve a subsegment bond (from one side). + /// + /// Note that the other subsegment will still think it's + /// connected to this subsegment. + public void Dissolve() + { + seg.subsegs[orient].seg = Mesh.dummysub; + } + + /// + /// Copy a subsegment. + /// + public void Copy(ref Osub o2) + { + o2.seg = seg; + o2.orient = orient; + } + + /// + /// Test for equality of subsegments. + /// + public bool Equal(Osub o2) + { + return ((seg == o2.seg) && (orient == o2.orient)); + } + + /// + /// Check a subsegment's deallocation. + /// + public static bool IsDead(Segment sub) + { + return sub.subsegs[0].seg == null; + } + + /// + /// Set a subsegment's deallocation. + /// + public static void Kill(Segment sub) + { + sub.subsegs[0].seg = null; + sub.subsegs[1].seg = null; + } + + /// + /// Finds a triangle abutting a subsegment. + /// + public void TriPivot(ref Otri ot) + { + ot = seg.triangles[orient]; + //decode(ptr, otri) + } + + /// + /// Dissolve a bond (from the subsegment side). + /// + public void TriDissolve() + { + seg.triangles[orient].triangle = Mesh.dummytri; + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/Data/Otri.cs b/ThirdParty/Triangle/Data/Otri.cs new file mode 100644 index 0000000..97fd949 --- /dev/null +++ b/ThirdParty/Triangle/Data/Otri.cs @@ -0,0 +1,484 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// An oriented triangle. + /// + /// + /// Includes a pointer to a triangle and orientation. + /// The orientation denotes an edge of the triangle. Hence, there are + /// three possible orientations. By convention, each edge always points + /// counterclockwise about the corresponding triangle. + /// + struct Otri + { + public Triangle triangle; + public int orient; // Ranges from 0 to 2. + + public override string ToString() + { + if (triangle == null) + { + return "O-TID [null]"; + } + return String.Format("O-TID {0}", triangle.hash); + } + + #region Otri primitives + + // For fast access + static readonly int[] plus1Mod3 = { 1, 2, 0 }; + static readonly int[] minus1Mod3 = { 2, 0, 1 }; + + // The following handle manipulation primitives are all described by Guibas + // and Stolfi. However, Guibas and Stolfi use an edge-based data structure, + // whereas I use a triangle-based data structure. + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + /// + /// Note that the edge direction is necessarily reversed, because the handle specified + /// by an oriented triangle is directed counterclockwise around the triangle. + /// + public void Sym(ref Otri o2) + { + //o2 = tri.triangles[orient]; + // decode(ptr, otri2); + + o2.triangle = triangle.neighbors[orient].triangle; + o2.orient = triangle.neighbors[orient].orient; + } + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + public void SymSelf() + { + //this = tri.triangles[orient]; + // decode(ptr, otri); + + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + } + // lnext() finds the next edge (counterclockwise) of a triangle. + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void Lnext(ref Otri o2) + { + o2.triangle = triangle; + o2.orient = plus1Mod3[orient]; + } + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void LnextSelf() + { + orient = plus1Mod3[orient]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void Lprev(ref Otri o2) + { + o2.triangle = triangle; + o2.orient = minus1Mod3[orient]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void LprevSelf() + { + orient = minus1Mod3[orient]; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + /// onext() spins counterclockwise around a vertex; that is, it finds + /// the next edge with the same origin in the counterclockwise direction. This + /// edge is part of a different triangle. + /// + public void Onext(ref Otri o2) + { + //Lprev(ref o2); + o2.triangle = triangle; + o2.orient = minus1Mod3[orient]; + + //o2.SymSelf(); + int tmp = o2.orient; + o2.orient = o2.triangle.neighbors[tmp].orient; + o2.triangle = o2.triangle.neighbors[tmp].triangle; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + public void OnextSelf() + { + //LprevSelf(); + orient = minus1Mod3[orient]; + + //SymSelf(); + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + /// oprev() spins clockwise around a vertex; that is, it finds the + /// next edge with the same origin in the clockwise direction. This edge is + /// part of a different triangle. + /// + public void Oprev(ref Otri o2) + { + //Sym(ref o2); + o2.triangle = triangle.neighbors[orient].triangle; + o2.orient = triangle.neighbors[orient].orient; + + //o2.LnextSelf(); + o2.orient = plus1Mod3[o2.orient]; + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + public void OprevSelf() + { + //SymSelf(); + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + + //LnextSelf(); + orient = plus1Mod3[orient]; + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + /// dnext() spins counterclockwise around a vertex; that is, it finds + /// the next edge with the same destination in the counterclockwise direction. + /// This edge is part of a different triangle. + /// + public void Dnext(ref Otri o2) + { + //Sym(ref o2); + o2.triangle = triangle.neighbors[orient].triangle; + o2.orient = triangle.neighbors[orient].orient; + + //o2.LprevSelf(); + o2.orient = minus1Mod3[o2.orient]; + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + public void DnextSelf() + { + //SymSelf(); + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + + //LprevSelf(); + orient = minus1Mod3[orient]; + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + /// dprev() spins clockwise around a vertex; that is, it finds the + /// next edge with the same destination in the clockwise direction. This edge + /// is part of a different triangle. + /// + public void Dprev(ref Otri o2) + { + //Lnext(ref o2); + o2.triangle = triangle; + o2.orient = plus1Mod3[orient]; + + //o2.SymSelf(); + int tmp = o2.orient; + o2.orient = o2.triangle.neighbors[tmp].orient; + o2.triangle = o2.triangle.neighbors[tmp].triangle; + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + public void DprevSelf() + { + //LnextSelf(); + orient = plus1Mod3[orient]; + + //SymSelf(); + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + /// rnext() moves one edge counterclockwise about the adjacent + /// triangle. (It's best understood by reading Guibas and Stolfi. It + /// involves changing triangles twice.) + /// + public void Rnext(ref Otri o2) + { + //Sym(ref o2); + o2.triangle = triangle.neighbors[orient].triangle; + o2.orient = triangle.neighbors[orient].orient; + + //o2.LnextSelf(); + o2.orient = plus1Mod3[o2.orient]; + + //o2.SymSelf(); + int tmp = o2.orient; + o2.orient = o2.triangle.neighbors[tmp].orient; + o2.triangle = o2.triangle.neighbors[tmp].triangle; + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + public void RnextSelf() + { + //SymSelf(); + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + + //LnextSelf(); + orient = plus1Mod3[orient]; + + //SymSelf(); + tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + /// rprev() moves one edge clockwise about the adjacent triangle. + /// (It's best understood by reading Guibas and Stolfi. It involves + /// changing triangles twice.) + /// + public void Rprev(ref Otri o2) + { + //Sym(ref o2); + o2.triangle = triangle.neighbors[orient].triangle; + o2.orient = triangle.neighbors[orient].orient; + + //o2.LprevSelf(); + o2.orient = minus1Mod3[o2.orient]; + + //o2.SymSelf(); + int tmp = o2.orient; + o2.orient = o2.triangle.neighbors[tmp].orient; + o2.triangle = o2.triangle.neighbors[tmp].triangle; + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + public void RprevSelf() + { + //SymSelf(); + int tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + + //LprevSelf(); + orient = minus1Mod3[orient]; + + //SymSelf(); + tmp = orient; + orient = triangle.neighbors[tmp].orient; + triangle = triangle.neighbors[tmp].triangle; + } + + /// + /// Origin [org(abc) -> a] + /// + public Vertex Org() + { + return triangle.vertices[plus1Mod3[orient]]; + } + + /// + /// Destination [dest(abc) -> b] + /// + public Vertex Dest() + { + return triangle.vertices[minus1Mod3[orient]]; + } + + /// + /// Apex [apex(abc) -> c] + /// + public Vertex Apex() + { + return triangle.vertices[orient]; + } + + /// + /// Set Origin + /// + public void SetOrg(Vertex ptr) + { + triangle.vertices[plus1Mod3[orient]] = ptr; + } + + /// + /// Set Destination + /// + public void SetDest(Vertex ptr) + { + triangle.vertices[minus1Mod3[orient]] = ptr; + } + + /// + /// Set Apex + /// + public void SetApex(Vertex ptr) + { + triangle.vertices[orient] = ptr; + } + + /// + /// Bond two triangles together at the resepective handles. [bond(abc, bad)] + /// + public void Bond(ref Otri o2) + { + //triangle.neighbors[orient]= o2; + //o2.triangle.neighbors[o2.orient] = this; + + triangle.neighbors[orient].triangle = o2.triangle; + triangle.neighbors[orient].orient = o2.orient; + + o2.triangle.neighbors[o2.orient].triangle = this.triangle; + o2.triangle.neighbors[o2.orient].orient = this.orient; + } + + /// + /// Dissolve a bond (from one side). + /// + /// Note that the other triangle will still think it's connected to + /// this triangle. Usually, however, the other triangle is being deleted + /// entirely, or bonded to another triangle, so it doesn't matter. + /// + public void Dissolve() + { + triangle.neighbors[orient].triangle = Mesh.dummytri; + triangle.neighbors[orient].orient = 0; + } + + /// + /// Copy an oriented triangle. + /// + public void Copy(ref Otri o2) + { + o2.triangle = triangle; + o2.orient = orient; + } + + /// + /// Test for equality of oriented triangles. + /// + public bool Equal(Otri o2) + { + return ((triangle == o2.triangle) && (orient == o2.orient)); + } + + /// + /// Infect a triangle with the virus. + /// + public void Infect() + { + triangle.infected = true; + } + + /// + /// Cure a triangle from the virus. + /// + public void Uninfect() + { + triangle.infected = false; + } + + /// + /// Test a triangle for viral infection. + /// + public bool IsInfected() + { + return triangle.infected; + } + + /// + /// Check a triangle's deallocation. + /// + public static bool IsDead(Triangle tria) + { + return tria.neighbors[0].triangle == null; + } + + /// + /// Set a triangle's deallocation. + /// + public static void Kill(Triangle tria) + { + tria.neighbors[0].triangle = null; + tria.neighbors[2].triangle = null; + } + + /// + /// Finds a subsegment abutting a triangle. + /// + public void SegPivot(ref Osub os) + { + os = triangle.subsegs[orient]; + //sdecode(sptr, osub) + } + + /// + /// Bond a triangle to a subsegment. + /// + public void SegBond(ref Osub os) + { + triangle.subsegs[orient] = os; + os.seg.triangles[os.orient] = this; + } + + /// + /// Dissolve a bond (from the triangle side). + /// + public void SegDissolve() + { + triangle.subsegs[orient].seg = Mesh.dummysub; + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/Data/Segment.cs b/ThirdParty/Triangle/Data/Segment.cs new file mode 100644 index 0000000..b17c51f --- /dev/null +++ b/ThirdParty/Triangle/Data/Segment.cs @@ -0,0 +1,108 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + + /// + /// The subsegment data structure. + /// + /// + /// Each subsegment contains two pointers to adjoining subsegments, plus + /// four pointers to vertices, plus two pointers to adjoining triangles, + /// plus one boundary marker. + /// + public class Segment : ISegment + { + // Hash for dictionary. Will be set by mesh instance. + internal int hash; + + internal Osub[] subsegs; + internal Vertex[] vertices; + internal Otri[] triangles; + internal int boundary; + + public Segment() + { + // Initialize the two adjoining subsegments to be the omnipresent + // subsegment. + subsegs = new Osub[2]; + subsegs[0].seg = Mesh.dummysub; + subsegs[1].seg = Mesh.dummysub; + + // Four NULL vertices. + vertices = new Vertex[4]; + + // Initialize the two adjoining triangles to be "outer space." + triangles = new Otri[2]; + triangles[0].triangle = Mesh.dummytri; + triangles[1].triangle = Mesh.dummytri; + + // Set the boundary marker to zero. + boundary = 0; + } + + #region Public properties + + /// + /// Gets the first endpoints vertex id. + /// + public int P0 + { + get { return this.vertices[0].id; } + } + + /// + /// Gets the seconds endpoints vertex id. + /// + public int P1 + { + get { return this.vertices[1].id; } + } + + /// + /// Gets the segment boundary mark. + /// + public int Boundary + { + get { return this.boundary; } + } + + #endregion + + /// + /// Gets the segments endpoint. + /// + public Vertex GetVertex(int index) + { + return this.vertices[index]; // TODO: Check range? + } + + /// + /// Gets an adjoining triangle. + /// + public ITriangle GetTriangle(int index) + { + return triangles[index].triangle == Mesh.dummytri ? null : triangles[index].triangle; + } + + public override int GetHashCode() + { + return this.hash; + } + + public override string ToString() + { + return String.Format("SID {0}", hash); + } + } +} diff --git a/ThirdParty/Triangle/Data/Triangle.cs b/ThirdParty/Triangle/Data/Triangle.cs new file mode 100644 index 0000000..5d7d1b8 --- /dev/null +++ b/ThirdParty/Triangle/Data/Triangle.cs @@ -0,0 +1,185 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using TriangleNet.Geometry; + + /// + /// The triangle data structure. + /// + /// + /// Each triangle contains three pointers to adjoining triangles, plus three + /// pointers to vertices, plus three pointers to subsegments (declared below; + /// these pointers are usually 'dummysub'). It may or may not also contain + /// user-defined attributes and/or a floating-point "area constraint". + /// + public class Triangle : ITriangle + { + // Hash for dictionary. Will be set by mesh instance. + internal int hash; + + // The ID is only used for mesh output. + internal int id; + + internal Otri[] neighbors; + internal Vertex[] vertices; + internal Osub[] subsegs; + internal int region; + internal double area; + internal bool infected; + + public Triangle() + { + // Initialize the three adjoining triangles to be "outer space". + neighbors = new Otri[3]; + neighbors[0].triangle = Mesh.dummytri; + neighbors[1].triangle = Mesh.dummytri; + neighbors[2].triangle = Mesh.dummytri; + + // Three NULL vertices. + vertices = new Vertex[3]; + + // TODO: if (Behavior.UseSegments) + { + // Initialize the three adjoining subsegments to be the + // omnipresent subsegment. + subsegs = new Osub[3]; + subsegs[0].seg = Mesh.dummysub; + subsegs[1].seg = Mesh.dummysub; + subsegs[2].seg = Mesh.dummysub; + } + + // TODO: + //if (Behavior.VarArea) + //{ + // area = -1.0; + //} + } + + #region Public properties + + /// + /// Gets the triangle id. + /// + public int ID + { + get { return this.id; } + } + + /// + /// Gets the first corners vertex id. + /// + public int P0 + { + get { return this.vertices[0] == null ? -1 : this.vertices[0].id; } + } + + /// + /// Gets the seconds corners vertex id. + /// + public int P1 + { + get { return this.vertices[1] == null ? -1 : this.vertices[1].id; } + } + + /// + /// Gets the specified corners vertex. + /// + public Vertex GetVertex(int index) + { + return this.vertices[index]; // TODO: Check range? + } + + /// + /// Gets the third corners vertex id. + /// + public int P2 + { + get { return this.vertices[2] == null ? -1 : this.vertices[2].id; } + } + + public bool SupportsNeighbors + { + get { return true; } + } + + /// + /// Gets the first neighbors id. + /// + public int N0 + { + get { return this.neighbors[0].triangle.id; } + } + + /// + /// Gets the second neighbors id. + /// + public int N1 + { + get { return this.neighbors[1].triangle.id; } + } + + /// + /// Gets the third neighbors id. + /// + public int N2 + { + get { return this.neighbors[2].triangle.id; } + } + + /// + /// Gets a triangles' neighbor. + /// + /// The neighbor index (0, 1 or 2). + /// The neigbbor opposite of vertex with given index. + public ITriangle GetNeighbor(int index) + { + return neighbors[index].triangle == Mesh.dummytri ? null : neighbors[index].triangle; + } + + /// + /// Gets a triangles segment. + /// + /// The vertex index (0, 1 or 2). + /// The segment opposite of vertex with given index. + public ISegment GetSegment(int index) + { + return subsegs[index].seg == Mesh.dummysub ? null : subsegs[index].seg; + } + + /// + /// Gets the triangle area constraint. + /// + public double Area + { + get { return this.area; } + set { this.area = value; } + } + + /// + /// Region ID the triangle belongs to. + /// + public int Region + { + get { return this.region; } + } + + #endregion + + public override int GetHashCode() + { + return this.hash; + } + + public override string ToString() + { + return String.Format("TID {0}", hash); + } + } +} diff --git a/ThirdParty/Triangle/Data/Vertex.cs b/ThirdParty/Triangle/Data/Vertex.cs new file mode 100644 index 0000000..1d4453d --- /dev/null +++ b/ThirdParty/Triangle/Data/Vertex.cs @@ -0,0 +1,114 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + + /// + /// The vertex data structure. + /// + public class Vertex : Point + { + // Hash for dictionary. Will be set by mesh instance. + internal int hash; + + internal VertexType type; + internal Otri tri; + + /// + /// Initializes a new instance of the class. + /// + public Vertex() + : this(0, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + public Vertex(double x, double y) + : this(x, y, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + /// The boundary mark. + public Vertex(double x, double y, int mark) + : this(x, y, mark, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + /// The boundary mark. + /// The number of point attributes. + public Vertex(double x, double y, int mark, int attribs) + : base(x, y, mark) + { + this.type = VertexType.InputVertex; + + if (attribs > 0) + { + this.attributes = new double[attribs]; + } + } + + #region Public properties + + /// + /// Gets the vertex type. + /// + public VertexType Type + { + get { return this.type; } + } + + /// + /// Gets the specified coordinate of the vertex. + /// + /// Coordinate index. + /// X coordinate, if index is 0, Y coordinate, if index is 1. + public double this[int i] + { + get + { + if (i == 0) + { + return x; + } + + if (i == 1) + { + return y; + } + + throw new ArgumentOutOfRangeException("Index must be 0 or 1."); + } + } + + #endregion + + public override int GetHashCode() + { + return this.hash; + } + } +} diff --git a/ThirdParty/Triangle/Enums.cs b/ThirdParty/Triangle/Enums.cs new file mode 100644 index 0000000..ad4a4b6 --- /dev/null +++ b/ThirdParty/Triangle/Enums.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + /// + /// Implemented triangulation algorithms. + /// + public enum TriangulationAlgorithm + { + Dwyer, + Incremental, + SweepLine + }; + + /// + /// Labels that signify the result of point location. + /// + /// The result of a search indicates that the point falls in the + /// interior of a triangle, on an edge, on a vertex, or outside the mesh. + /// + enum LocateResult { InTriangle, OnEdge, OnVertex, Outside }; + + /// + /// Labels that signify the result of vertex insertion. + /// + /// The result indicates that the vertex was inserted with complete + /// success, was inserted but encroaches upon a subsegment, was not inserted + /// because it lies on a segment, or was not inserted because another vertex + /// occupies the same location. + /// + enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate }; + + /// + /// Labels that signify the result of direction finding. + /// + /// The result indicates that a segment connecting the two query + /// points falls within the direction triangle, along the left edge of the + /// direction triangle, or along the right edge of the direction triangle. + /// + enum FindDirectionResult { Within, Leftcollinear, Rightcollinear }; + + /// + /// The type of the mesh vertex. + /// + public enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex }; + + /// + /// Node renumbering algorithms. + /// + public enum NodeNumbering { None, Linear, CuthillMcKee }; +} diff --git a/ThirdParty/Triangle/Geometry/BoundingBox.cs b/ThirdParty/Triangle/Geometry/BoundingBox.cs new file mode 100644 index 0000000..3135d13 --- /dev/null +++ b/ThirdParty/Triangle/Geometry/BoundingBox.cs @@ -0,0 +1,129 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + + /// + /// A simple bounding box class. + /// + public class BoundingBox + { + double xmin, ymin, xmax, ymax; + + /// + /// Initializes a new instance of the class. + /// + public BoundingBox() + { + xmin = double.MaxValue; + ymin = double.MaxValue; + xmax = -double.MaxValue; + ymax = -double.MaxValue; + } + + /// + /// Initializes a new instance of the class + /// with predefined bounds. + /// + /// Minimum x value. + /// Minimum y value. + /// Maximum x value. + /// Maximum y value. + public BoundingBox(double xmin, double ymin, double xmax, double ymax) + { + this.xmin = xmin; + this.ymin = ymin; + this.xmax = xmax; + this.ymax = ymax; + } + + /// + /// Gets the minimum x value (left boundary). + /// + public double Xmin + { + get { return xmin; } + } + + /// + /// Gets the minimum y value (bottom boundary). + /// + public double Ymin + { + get { return ymin; } + } + + /// + /// Gets the maximum x value (right boundary). + /// + public double Xmax + { + get { return xmax; } + } + + /// + /// Gets the maximum y value (top boundary). + /// + public double Ymax + { + get { return ymax; } + } + + /// + /// Gets the width of the bounding box. + /// + public double Width + { + get { return xmax - xmin; } + } + + /// + /// Gets the height of the bounding box. + /// + public double Height + { + get { return ymax - ymin; } + } + + /// + /// Update bounds. + /// + /// X coordinate. + /// Y coordinate. + public void Update(double x, double y) + { + xmin = Math.Min(xmin, x); + ymin = Math.Min(ymin, y); + xmax = Math.Max(xmax, x); + ymax = Math.Max(ymax, y); + } + + /// + /// Scale bounds. + /// + /// Add dx to left and right bounds. + /// Add dy to top and bottom bounds. + public void Scale(double dx, double dy) + { + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + } + + /// + /// Check if given point is inside bounding box. + /// + /// Point to check. + /// Return true, if bounding box contains given point. + public bool Contains(Point pt) + { + return ((pt.x >= xmin) && (pt.x <= xmax) && (pt.y >= ymin) && (pt.y <= ymax)); + } + } +} diff --git a/ThirdParty/Triangle/Geometry/Edge.cs b/ThirdParty/Triangle/Geometry/Edge.cs new file mode 100644 index 0000000..b47a093 --- /dev/null +++ b/ThirdParty/Triangle/Geometry/Edge.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Data; + + /// + /// Represents a straight line segment in 2D space. + /// + public class Edge + { + /// + /// Gets the first endpoints index. + /// + public int P0 + { + get; + private set; + } + + /// + /// Gets the second endpoints index. + /// + public int P1 + { + get; + private set; + } + + /// + /// Gets the segments boundary mark. + /// + public int Boundary + { + get; + private set; + } + + /// + /// Initializes a new instance of the class. + /// + public Edge(int p0, int p1) + : this(p0, p1, 0) + { } + + /// + /// Initializes a new instance of the class. + /// + public Edge(int p0, int p1, int boundary) + { + this.P0 = p0; + this.P1 = p1; + this.Boundary = boundary; + } + } +} diff --git a/ThirdParty/Triangle/Geometry/EdgeEnumerator.cs b/ThirdParty/Triangle/Geometry/EdgeEnumerator.cs new file mode 100644 index 0000000..7f8fd86 --- /dev/null +++ b/ThirdParty/Triangle/Geometry/EdgeEnumerator.cs @@ -0,0 +1,103 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Data; + + /// + /// Enumerates the edges of a triangulation. + /// + public class EdgeEnumerator : IEnumerator + { + IEnumerator triangles; + Otri tri = default(Otri); + Otri neighbor = default(Otri); + Osub sub = default(Osub); + Edge current; + Vertex p1, p2; + + /// + /// Initializes a new instance of the class. + /// + public EdgeEnumerator(Mesh mesh) + { + triangles = mesh.triangles.Values.GetEnumerator(); + triangles.MoveNext(); + + tri.triangle = triangles.Current; + tri.orient = 0; + } + + public Edge Current + { + get { return current; } + } + + public void Dispose() + { + this.triangles.Dispose(); + } + + object System.Collections.IEnumerator.Current + { + get { return current; } + } + + public bool MoveNext() + { + if (tri.triangle == null) + { + return false; + } + + current = null; + + while (current == null) + { + if (tri.orient == 3) + { + if (triangles.MoveNext()) + { + tri.triangle = triangles.Current; + tri.orient = 0; + } + else + { + // Finally no more triangles + return false; + } + } + + tri.Sym(ref neighbor); + + if ((tri.triangle.id < neighbor.triangle.id) || (neighbor.triangle == Mesh.dummytri)) + { + p1 = tri.Org(); + p2 = tri.Dest(); + + tri.SegPivot(ref sub); + + // Boundary mark of dummysub is 0, so we don't need to worry about that. + current = new Edge(p1.id, p2.id, sub.seg.boundary); + } + + tri.orient++; + } + + return true; + } + + public void Reset() + { + this.triangles.Reset(); + } + } +} diff --git a/ThirdParty/Triangle/Geometry/ISegment.cs b/ThirdParty/Triangle/Geometry/ISegment.cs new file mode 100644 index 0000000..f0e19c6 --- /dev/null +++ b/ThirdParty/Triangle/Geometry/ISegment.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using TriangleNet.Data; + + /// + /// Interface for segment geometry. + /// + public interface ISegment + { + #region Public properties + + /// + /// Gets the first endpoints vertex id. + /// + int P0 { get; } + + /// + /// Gets the seconds endpoints vertex id. + /// + int P1 { get; } + + /// + /// Gets the segment boundary mark. + /// + int Boundary { get; } + + /// + /// Gets the segments endpoint. + /// + /// The vertex index (0 or 1). + Vertex GetVertex(int index); + + /// + /// Gets an adjoining triangle. + /// + /// The triangle index (0 or 1). + ITriangle GetTriangle(int index); + + #endregion + } +} diff --git a/ThirdParty/Triangle/Geometry/ITriangle.cs b/ThirdParty/Triangle/Geometry/ITriangle.cs new file mode 100644 index 0000000..82d2dd1 --- /dev/null +++ b/ThirdParty/Triangle/Geometry/ITriangle.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using TriangleNet.Data; + + /// + /// Triangle interface. + /// + public interface ITriangle + { + /// + /// The triangle id. + /// + int ID { get; } + + /// + /// First vertex id of the triangle. + /// + int P0 { get; } + /// + /// Second vertex id of the triangle. + /// + int P1 { get; } + /// + /// Third vertex id of the triangle. + /// + int P2 { get; } + + /// + /// Gets a triangles vertex. + /// + /// The vertex index (0, 1 or 2). + /// The vertex of the specified corner index. + Vertex GetVertex(int index); + + /// + /// True if the triangle implementation contains neighbor information. + /// + bool SupportsNeighbors { get; } + + /// + /// First neighbor. + /// + int N0 { get; } + /// + /// Second neighbor. + /// + int N1 { get; } + /// + /// Third neighbor. + /// + int N2 { get; } + + /// + /// Gets a triangles neighbor. + /// + /// The vertex index (0, 1 or 2). + /// The neigbbor opposite of vertex with given index. + ITriangle GetNeighbor(int index); + + /// + /// Gets a triangles segment. + /// + /// The vertex index (0, 1 or 2). + /// The segment opposite of vertex with given index. + ISegment GetSegment(int index); + + /// + /// Triangle area constraint. + /// + double Area { get; set; } + + /// + /// Region ID the triangle belongs to. + /// + int Region { get; } + } +} diff --git a/ThirdParty/Triangle/Geometry/InputGeometry.cs b/ThirdParty/Triangle/Geometry/InputGeometry.cs new file mode 100644 index 0000000..b1d72ed --- /dev/null +++ b/ThirdParty/Triangle/Geometry/InputGeometry.cs @@ -0,0 +1,232 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + using TriangleNet.Data; + + /// + /// The input geometry which will be triangulated. May represent a + /// pointset or a planar straight line graph. + /// + public class InputGeometry + { + internal List points; + internal List segments; + internal List holes; + internal List regions; + + BoundingBox bounds; + + // Used to check consitent use of point attributes. + private int pointAttributes = -1; + + /// + /// Initializes a new instance of the class. + /// + public InputGeometry() + : this(3) + { + } + + /// + /// Initializes a new instance of the class. + /// The point list will be initialized with a given capacity. + /// + /// Point list capacity. + public InputGeometry(int capacity) + { + points = new List(capacity); + segments = new List(); + holes = new List(); + regions = new List(); + + bounds = new BoundingBox(); + + pointAttributes = -1; + } + + /// + /// Gets the bounding box of the input geometry. + /// + public BoundingBox Bounds + { + get { return bounds; } + } + + /// + /// Indicates, whether the geometry should be treated as a PSLG. + /// + public bool HasSegments + { + get { return segments.Count > 0; } + } + + /// + /// Gets the number of points. + /// + public int Count + { + get { return points.Count; } + } + + /// + /// Gets the list of input points. + /// + public IEnumerable Points + { + get { return points; } + } + + /// + /// Gets the list of input segments. + /// + public ICollection Segments + { + get { return segments; } + } + + /// + /// Gets the list of input holes. + /// + public ICollection Holes + { + get { return holes; } + } + + /// + /// Gets the list of regions. + /// + public ICollection Regions + { + get { return regions; } + } + + /// + /// Clear input geometry. + /// + public void Clear() + { + points.Clear(); + segments.Clear(); + holes.Clear(); + regions.Clear(); + + pointAttributes = -1; + } + + /// + /// Adds a point to the geometry. + /// + /// X coordinate. + /// Y coordinate. + public void AddPoint(double x, double y) + { + AddPoint(x, y, 0); + } + + /// + /// Adds a point to the geometry. + /// + /// X coordinate. + /// Y coordinate. + /// Boundary marker. + public void AddPoint(double x, double y, int boundary) + { + points.Add(new Vertex(x, y, boundary)); + + bounds.Update(x, y); + } + + /// + /// Adds a point to the geometry. + /// + /// X coordinate. + /// Y coordinate. + /// Boundary marker. + /// Point attribute. + public void AddPoint(double x, double y, int boundary, double attribute) + { + AddPoint(x, y, 0, new double[] { attribute }); + } + + /// + /// Adds a point to the geometry. + /// + /// X coordinate. + /// Y coordinate. + /// Boundary marker. + /// Point attributes. + public void AddPoint(double x, double y, int boundary, double[] attribs) + { + if (pointAttributes < 0) + { + pointAttributes = attribs == null ? 0 : attribs.Length; + } + else if (attribs == null && pointAttributes > 0) + { + throw new ArgumentException("Inconsitent use of point attributes."); + } + else if (attribs != null && pointAttributes != attribs.Length) + { + throw new ArgumentException("Inconsitent use of point attributes."); + } + + points.Add(new Vertex(x, y, boundary) { attributes = attribs }); + + bounds.Update(x, y); + } + + /// + /// Adds a hole location to the geometry. + /// + /// X coordinate of the hole. + /// Y coordinate of the hole. + public void AddHole(double x, double y) + { + holes.Add(new Point(x, y)); + } + + /// + /// Adds a hole location to the geometry. + /// + /// X coordinate of the hole. + /// Y coordinate of the hole. + /// The region id. + public void AddRegion(double x, double y, int id) + { + regions.Add(new RegionPointer(x, y, id)); + } + + /// + /// Adds a segment to the geometry. + /// + /// First endpoint. + /// Second endpoint. + public void AddSegment(int p0, int p1) + { + AddSegment(p0, p1, 0); + } + + /// + /// Adds a segment to the geometry. + /// + /// First endpoint. + /// Second endpoint. + /// Segment marker. + public void AddSegment(int p0, int p1, int boundary) + { + if (p0 == p1 || p0 < 0 || p1 < 0) + { + throw new NotSupportedException("Invalid endpoints."); + } + + segments.Add(new Edge(p0, p1, boundary)); + } + } +} diff --git a/ThirdParty/Triangle/Geometry/Point.cs b/ThirdParty/Triangle/Geometry/Point.cs new file mode 100644 index 0000000..37611ef --- /dev/null +++ b/ThirdParty/Triangle/Geometry/Point.cs @@ -0,0 +1,165 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// Represents a 2D point. + /// + public class Point : IComparable, IEquatable + { + internal int id; + internal double x; + internal double y; + internal int mark; + internal double[] attributes; + + public Point() + : this(0, 0, 0) + { + } + + public Point(double x, double y) + : this(x, y, 0) + { + } + + public Point(double x, double y, int mark) + { + this.x = x; + this.y = y; + this.mark = mark; + } + + #region Public properties + + /// + /// Gets the vertex id. + /// + public int ID + { + get { return this.id; } + } + + /// + /// Gets the vertex x coordinate. + /// + public double X + { + get { return this.x; } + } + + /// + /// Gets the vertex y coordinate. + /// + public double Y + { + get { return this.y; } + } + + /// + /// Gets the vertex boundary mark. + /// + public int Boundary + { + get { return this.mark; } + } + + /// + /// Gets the vertex attributes (may be null). + /// + public double[] Attributes + { + get { return this.attributes; } + } + + #endregion + + #region Operator overloading / overriding Equals + + // Compare "Guidelines for Overriding Equals() and Operator ==" + // http://msdn.microsoft.com/en-us/library/ms173147.aspx + + public static bool operator ==(Point a, Point b) + { + // If both are null, or both are same instance, return true. + if (Object.ReferenceEquals(a, b)) + { + return true; + } + + // If one is null, but not both, return false. + if (((object)a == null) || ((object)b == null)) + { + return false; + } + + return a.Equals(b); + } + + public static bool operator !=(Point a, Point b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + // If parameter is null return false. + if (obj == null) + { + return false; + } + + Point p = obj as Point; + + if ((object)p == null) + { + return false; + } + + return (x == p.x) && (y == p.y); + } + + public bool Equals(Point p) + { + // If vertex is null return false. + if ((object)p == null) + { + return false; + } + + // Return true if the fields match: + return (x == p.x) && (y == p.y); + } + + #endregion + + public int CompareTo(Point other) + { + if (x == other.x && y == other.y) + { + return 0; + } + + return (x < other.x || (x == other.x && y < other.y)) ? -1 : 1; + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ y.GetHashCode(); + } + + public override string ToString() + { + return String.Format("[{0},{1}]", x, y); + } + } +} diff --git a/ThirdParty/Triangle/Geometry/RegionPointer.cs b/ThirdParty/Triangle/Geometry/RegionPointer.cs new file mode 100644 index 0000000..10672bd --- /dev/null +++ b/ThirdParty/Triangle/Geometry/RegionPointer.cs @@ -0,0 +1,33 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + + /// + /// Pointer to a region in the mesh geometry. A region is a well-defined + /// subset of the geomerty (enclosed by subsegments). + /// + public class RegionPointer + { + internal Point point; + internal int id; + + /// + /// Initializes a new instance of the class. + /// + /// X coordinate of the region. + /// Y coordinate of the region. + /// Region id. + public RegionPointer(double x, double y, int id) + { + this.point = new Point(x, y); + this.id = id; + } + } +} diff --git a/ThirdParty/Triangle/IO/DataReader.cs b/ThirdParty/Triangle/IO/DataReader.cs new file mode 100644 index 0000000..d2234d7 --- /dev/null +++ b/ThirdParty/Triangle/IO/DataReader.cs @@ -0,0 +1,322 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Globalization; + using TriangleNet.Data; + using TriangleNet.Log; + using TriangleNet.Geometry; + + /// + /// The DataReader class provides methods for mesh reconstruction. + /// + static class DataReader + { + /// + /// Reconstruct a triangulation from its raw data representation. + /// + /// + /// + /// + /// + /// Reads an .ele file and reconstructs the original mesh. If the -p switch + /// is used, this procedure will also read a .poly file and reconstruct the + /// subsegments of the original mesh. If the -a switch is used, this + /// procedure will also read an .area file and set a maximum area constraint + /// on each triangle. + /// + /// Vertices that are not corners of triangles, such as nodes on edges of + /// subparametric elements, are discarded. + /// + /// This routine finds the adjacencies between triangles (and subsegments) + /// by forming one stack of triangles for each vertex. Each triangle is on + /// three different stacks simultaneously. Each triangle's subsegment + /// pointers are used to link the items in each stack. This memory-saving + /// feature makes the code harder to read. The most important thing to keep + /// in mind is that each triangle is removed from a stack precisely when + /// the corresponding pointer is adjusted to refer to a subsegment rather + /// than the next triangle of the stack. + /// + public static int Reconstruct(Mesh mesh, InputGeometry input, ITriangle[] triangles) + { + int hullsize = 0; + + Otri tri = default(Otri); + Otri triangleleft = default(Otri); + Otri checktri = default(Otri); + Otri checkleft = default(Otri); + Otri checkneighbor = default(Otri); + Osub subseg = default(Osub); + List[] vertexarray; // Triangle + Otri prevlink; // Triangle + Otri nexttri; // Triangle + Vertex tdest, tapex; + Vertex checkdest, checkapex; + Vertex shorg; + Vertex segmentorg, segmentdest; + int[] corner = new int[3]; + int[] end = new int[2]; + //bool segmentmarkers = false; + int boundmarker; + int aroundvertex; + bool notfound; + int i = 0; + + int elements = triangles == null ? 0 : triangles.Length; + int numberofsegments = input.segments.Count; + + mesh.inelements = elements; + mesh.regions.AddRange(input.regions); + + // Create the triangles. + for (i = 0; i < mesh.inelements; i++) + { + mesh.MakeTriangle(ref tri); + // Mark the triangle as living. + //tri.triangle.neighbors[0].triangle = tri.triangle; + } + + if (mesh.behavior.Poly) + { + mesh.insegments = numberofsegments; + + // Create the subsegments. + for (i = 0; i < mesh.insegments; i++) + { + mesh.MakeSegment(ref subseg); + // Mark the subsegment as living. + //subseg.ss.subsegs[0].ss = subseg.ss; + } + } + + // Allocate a temporary array that maps each vertex to some adjacent + // triangle. I took care to allocate all the permanent memory for + // triangles and subsegments first. + vertexarray = new List[mesh.vertices.Count]; + // Each vertex is initially unrepresented. + for (i = 0; i < mesh.vertices.Count; i++) + { + Otri tmp = default(Otri); + tmp.triangle = Mesh.dummytri; + vertexarray[i] = new List(3); + vertexarray[i].Add(tmp); + } + + i = 0; + + // Read the triangles from the .ele file, and link + // together those that share an edge. + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + corner[0] = triangles[i].P0; + corner[1] = triangles[i].P1; + corner[2] = triangles[i].P2; + + // Copy the triangle's three corners. + for (int j = 0; j < 3; j++) + { + if ((corner[j] < 0) || (corner[j] >= mesh.invertices)) + { + SimpleLog.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()"); + throw new Exception("Triangle has an invalid vertex index."); + } + } + + // Read the triangle's attributes. + tri.triangle.region = triangles[i].Region; + + // TODO: VarArea + if (mesh.behavior.VarArea) + { + tri.triangle.area = triangles[i].Area; + } + + // Set the triangle's vertices. + tri.orient = 0; + tri.SetOrg(mesh.vertices[corner[0]]); + tri.SetDest(mesh.vertices[corner[1]]); + tri.SetApex(mesh.vertices[corner[2]]); + + // Try linking the triangle to others that share these vertices. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + // Take the number for the origin of triangleloop. + aroundvertex = corner[tri.orient]; + int index = vertexarray[aroundvertex].Count - 1; + // Look for other triangles having this vertex. + nexttri = vertexarray[aroundvertex][index]; + // Link the current triangle to the next one in the stack. + //tri.triangle.neighbors[tri.orient] = nexttri; + // Push the current triangle onto the stack. + vertexarray[aroundvertex].Add(tri); + + checktri = nexttri; + + if (checktri.triangle != Mesh.dummytri) + { + tdest = tri.Dest(); + tapex = tri.Apex(); + + // Look for other triangles that share an edge. + do + { + checkdest = checktri.Dest(); + checkapex = checktri.Apex(); + + if (tapex == checkdest) + { + // The two triangles share an edge; bond them together. + tri.Lprev(ref triangleleft); + triangleleft.Bond(ref checktri); + } + if (tdest == checkapex) + { + // The two triangles share an edge; bond them together. + checktri.Lprev(ref checkleft); + tri.Bond(ref checkleft); + } + // Find the next triangle in the stack. + index--; + nexttri = vertexarray[aroundvertex][index]; + + checktri = nexttri; + } while (checktri.triangle != Mesh.dummytri); + } + } + + i++; + } + + // Prepare to count the boundary edges. + hullsize = 0; + if (mesh.behavior.Poly) + { + // Read the segments from the .poly file, and link them + // to their neighboring triangles. + boundmarker = 0; + i = 0; + foreach (var item in mesh.subsegs.Values) + { + subseg.seg = item; + + end[0] = input.segments[i].P0; + end[1] = input.segments[i].P1; + boundmarker = input.segments[i].Boundary; + + for (int j = 0; j < 2; j++) + { + if ((end[j] < 0) || (end[j] >= mesh.invertices)) + { + SimpleLog.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()"); + throw new Exception("Segment has an invalid vertex index."); + } + } + + // set the subsegment's vertices. + subseg.orient = 0; + segmentorg = mesh.vertices[end[0]]; + segmentdest = mesh.vertices[end[1]]; + subseg.SetOrg(segmentorg); + subseg.SetDest(segmentdest); + subseg.SetSegOrg(segmentorg); + subseg.SetSegDest(segmentdest); + subseg.seg.boundary = boundmarker; + // Try linking the subsegment to triangles that share these vertices. + for (subseg.orient = 0; subseg.orient < 2; subseg.orient++) + { + // Take the number for the destination of subsegloop. + aroundvertex = end[1 - subseg.orient]; + int index = vertexarray[aroundvertex].Count - 1; + // Look for triangles having this vertex. + prevlink = vertexarray[aroundvertex][index]; + nexttri = vertexarray[aroundvertex][index]; + + checktri = nexttri; + shorg = subseg.Org(); + notfound = true; + // Look for triangles having this edge. Note that I'm only + // comparing each triangle's destination with the subsegment; + // each triangle's apex is handled through a different vertex. + // Because each triangle appears on three vertices' lists, each + // occurrence of a triangle on a list can (and does) represent + // an edge. In this way, most edges are represented twice, and + // every triangle-subsegment bond is represented once. + while (notfound && (checktri.triangle != Mesh.dummytri)) + { + checkdest = checktri.Dest(); + + if (shorg == checkdest) + { + // We have a match. Remove this triangle from the list. + //prevlink = vertexarray[aroundvertex][index]; + vertexarray[aroundvertex].Remove(prevlink); + // Bond the subsegment to the triangle. + checktri.SegBond(ref subseg); + // Check if this is a boundary edge. + checktri.Sym(ref checkneighbor); + if (checkneighbor.triangle == Mesh.dummytri) + { + // The next line doesn't insert a subsegment (because there's + // already one there), but it sets the boundary markers of + // the existing subsegment and its vertices. + mesh.InsertSubseg(ref checktri, 1); + hullsize++; + } + notfound = false; + } + index--; + // Find the next triangle in the stack. + prevlink = vertexarray[aroundvertex][index]; + nexttri = vertexarray[aroundvertex][index]; + + checktri = nexttri; + } + } + + i++; + } + } + + // Mark the remaining edges as not being attached to any subsegment. + // Also, count the (yet uncounted) boundary edges. + for (i = 0; i < mesh.vertices.Count; i++) + { + // Search the stack of triangles adjacent to a vertex. + int index = vertexarray[i].Count - 1; + nexttri = vertexarray[i][index]; + checktri = nexttri; + + while (checktri.triangle != Mesh.dummytri) + { + // Find the next triangle in the stack before this + // information gets overwritten. + index--; + nexttri = vertexarray[i][index]; + // No adjacent subsegment. (This overwrites the stack info.) + checktri.SegDissolve(); + checktri.Sym(ref checkneighbor); + if (checkneighbor.triangle == Mesh.dummytri) + { + mesh.InsertSubseg(ref checktri, 1); + hullsize++; + } + + checktri = nexttri; + } + } + + return hullsize; + } + } +} diff --git a/ThirdParty/Triangle/IO/DebugWriter.cs b/ThirdParty/Triangle/IO/DebugWriter.cs new file mode 100644 index 0000000..6844097 --- /dev/null +++ b/ThirdParty/Triangle/IO/DebugWriter.cs @@ -0,0 +1,263 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Text; + using TriangleNet.Data; + using TriangleNet.Geometry; + + /// + /// Writes a the current mesh into a text file. + /// + /// + /// File format: + /// + /// num_nodes + /// id_1 nx ny mark + /// ... + /// id_n nx ny mark + /// + /// num_segs + /// id_1 p1 p2 mark + /// ... + /// id_n p1 p2 mark + /// + /// num_tris + /// id_1 p1 p2 p3 n1 n2 n3 + /// ... + /// id_n p1 p2 p3 n1 n2 n3 + /// + class DebugWriter + { + static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat; + + int iteration; + string session; + StreamWriter stream; + string tmpFile; + int[] vertices; + int triangles; + + #region Singleton pattern + + private static readonly DebugWriter instance = new DebugWriter(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static DebugWriter() { } + + private DebugWriter() { } + + public static DebugWriter Session + { + get + { + return instance; + } + } + + #endregion + + /// + /// Start a new session with given name. + /// + /// Name of the session (and output files). + public void Start(string session) + { + this.iteration = 0; + this.session = session; + + if (this.stream != null) + { + throw new Exception("A session is active. Finish before starting a new."); + } + + this.tmpFile = Path.GetTempFileName(); + this.stream = new StreamWriter(tmpFile); + } + + /// + /// Write complete mesh to file. + /// + public void Write(Mesh mesh, bool skip = false) + { + this.WriteMesh(mesh, skip); + + this.triangles = mesh.Triangles.Count; + } + + /// + /// Finish this session. + /// + public void Finish() + { + this.Finish(session + ".mshx"); + } + + private void Finish(string path) + { + if (stream != null) + { + stream.Flush(); + stream.Dispose(); + stream = null; + + string header = "#!N" + this.iteration + Environment.NewLine; + + using (var gzFile = new FileStream(path, FileMode.Create)) + { + using (var gzStream = new GZipStream(gzFile, CompressionMode.Compress, false)) + { + byte[] bytes = Encoding.UTF8.GetBytes(header); + gzStream.Write(bytes, 0, bytes.Length); + + // TODO: read with stream + bytes = File.ReadAllBytes(tmpFile); + gzStream.Write(bytes, 0, bytes.Length); + } + } + + File.Delete(this.tmpFile); + } + } + + private void WriteGeometry(InputGeometry geometry) + { + stream.WriteLine("#!G{0}", this.iteration++); + } + + private void WriteMesh(Mesh mesh, bool skip) + { + // Mesh may have changed, but we choose to skip + if (triangles == mesh.triangles.Count && skip) + { + return; + } + + // Header line + stream.WriteLine("#!M{0}", this.iteration++); + + Vertex p1, p2, p3; + + if (VerticesChanged(mesh)) + { + HashVertices(mesh); + + // Number of vertices. + stream.WriteLine("{0}", mesh.vertices.Count); + + foreach (var v in mesh.vertices.Values) + { + // Vertex number, x and y coordinates and marker. + stream.WriteLine("{0} {1} {2} {3}", v.hash, v.x.ToString(nfi), v.y.ToString(nfi), v.mark); + } + } + else + { + stream.WriteLine("0"); + } + + // Number of segments. + stream.WriteLine("{0}", mesh.subsegs.Count); + + Osub subseg = default(Osub); + subseg.orient = 0; + + foreach (var item in mesh.subsegs.Values) + { + if (item.hash <= 0) + { + continue; + } + + subseg.seg = item; + + p1 = subseg.Org(); + p2 = subseg.Dest(); + + // Segment number, indices of its two endpoints, and marker. + stream.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.hash, p2.hash, subseg.seg.boundary); + } + + Otri tri = default(Otri), trisym = default(Otri); + tri.orient = 0; + + int n1, n2, n3, h1, h2, h3; + + // Number of triangles. + stream.WriteLine("{0}", mesh.triangles.Count); + + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + p1 = tri.Org(); + p2 = tri.Dest(); + p3 = tri.Apex(); + + h1 = (p1 == null) ? -1 : p1.hash; + h2 = (p2 == null) ? -1 : p2.hash; + h3 = (p3 == null) ? -1 : p3.hash; + + // Triangle number, indices for three vertices. + stream.Write("{0} {1} {2} {3}", tri.triangle.hash, h1, h2, h3); + + tri.orient = 1; + tri.Sym(ref trisym); + n1 = trisym.triangle.hash; + + tri.orient = 2; + tri.Sym(ref trisym); + n2 = trisym.triangle.hash; + + tri.orient = 0; + tri.Sym(ref trisym); + n3 = trisym.triangle.hash; + + // Neighboring triangle numbers. + stream.WriteLine(" {0} {1} {2}", n1, n2, n3); + } + } + + private bool VerticesChanged(Mesh mesh) + { + if (vertices == null || mesh.Vertices.Count != vertices.Length) + { + return true; + } + + int i = 0; + foreach (var v in mesh.Vertices) + { + if (v.id != vertices[i++]) + { + return true; + } + } + + return false; + } + + private void HashVertices(Mesh mesh) + { + if (vertices == null || mesh.Vertices.Count != vertices.Length) + { + vertices = new int[mesh.Vertices.Count]; + } + + int i = 0; + foreach (var v in mesh.Vertices) + { + vertices[i++] = v.id; + } + } + } +} diff --git a/ThirdParty/Triangle/IO/FileReader.cs b/ThirdParty/Triangle/IO/FileReader.cs new file mode 100644 index 0000000..470c7de --- /dev/null +++ b/ThirdParty/Triangle/IO/FileReader.cs @@ -0,0 +1,711 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.IO; + using System.Globalization; + using TriangleNet.Data; + using TriangleNet.Log; + using TriangleNet.Geometry; + using System.Collections.Generic; + + /// + /// Helper methods for reading Triangle file formats. + /// + public static class FileReader + { + static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat; + static int startIndex = 0; + + #region Helper methods + + static bool TryReadLine(StreamReader reader, out string[] token) + { + token = null; + + if (reader.EndOfStream) + { + return false; + } + + string line = reader.ReadLine().Trim(); + + while (String.IsNullOrWhiteSpace(line) || line.StartsWith("#")) + { + if (reader.EndOfStream) + { + return false; + } + + line = reader.ReadLine().Trim(); + } + + token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + return true; + } + + /// + /// Read vertex information of the given line. + /// + /// The input geometry. + /// The current vertex index. + /// The current line. + /// Number of point attributes + /// Number of point markers (0 or 1) + static void ReadVertex(InputGeometry data, int index, string[] line, int attributes, int marks) + { + double x = double.Parse(line[1], nfi); + double y = double.Parse(line[2], nfi); + int mark = 0; + double[] attribs = attributes == 0 ? null : new double[attributes]; + + // Read the vertex attributes. + for (int j = 0; j < attributes; j++) + { + if (line.Length > 3 + j) + { + attribs[j] = double.Parse(line[3 + j]); + } + } + + // Read a vertex marker. + if (marks > 0 && line.Length > 3 + attributes) + { + mark = int.Parse(line[3 + attributes]); + } + + data.AddPoint(x, y, mark, attribs); + } + + #endregion + + #region Main I/O methods + + /// + /// Reads geometry information from .node or .poly files. + /// + public static void Read(string filename, out InputGeometry geometry) + { + geometry = null; + + string path = Path.ChangeExtension(filename, ".poly"); + + if (File.Exists(path)) + { + geometry = FileReader.ReadPolyFile(path); + } + else + { + path = Path.ChangeExtension(filename, ".node"); + geometry = FileReader.ReadNodeFile(path); + } + } + + /// + /// Reads a mesh from .node, .poly or .ele files. + /// + public static void Read(string filename, out InputGeometry geometry, out List triangles) + { + triangles = null; + + FileReader.Read(filename, out geometry); + + string path = Path.ChangeExtension(filename, ".ele"); + + if (File.Exists(path) && geometry != null) + { + triangles = FileReader.ReadEleFile(path); + } + } + + /// + /// Reads geometry information from .node or .poly files. + /// + public static InputGeometry Read(string filename) + { + InputGeometry geometry = null; + + FileReader.Read(filename, out geometry); + + return geometry; + } + + #endregion + + /// + /// Read the vertices from a file, which may be a .node or .poly file. + /// + /// + /// Will NOT read associated .ele by default. + public static InputGeometry ReadNodeFile(string nodefilename) + { + return ReadNodeFile(nodefilename, false); + } + + /// + /// Read the vertices from a file, which may be a .node or .poly file. + /// + /// + /// + public static InputGeometry ReadNodeFile(string nodefilename, bool readElements) + { + InputGeometry data; + + startIndex = 0; + + string[] line; + int invertices = 0, attributes = 0, nodemarkers = 0; + + using (StreamReader reader = new StreamReader(nodefilename)) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file."); + } + + // Read number of vertices, number of dimensions, number of vertex + // attributes, and number of boundary markers. + invertices = int.Parse(line[0]); + + if (invertices < 3) + { + throw new Exception("Input must have at least three input vertices."); + } + + if (line.Length > 1) + { + if (int.Parse(line[1]) != 2) + { + throw new Exception("Triangle only works with two-dimensional meshes."); + } + } + + if (line.Length > 2) + { + attributes = int.Parse(line[2]); + } + + if (line.Length > 3) + { + nodemarkers = int.Parse(line[3]); + } + + data = new InputGeometry(invertices); + + // Read the vertices. + if (invertices > 0) + { + for (int i = 0; i < invertices; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (vertices)."); + } + + if (line.Length < 3) + { + throw new Exception("Invalid vertex."); + } + + if (i == 0) + { + startIndex = int.Parse(line[0], nfi); + } + + ReadVertex(data, i, line, attributes, nodemarkers); + } + } + } + + if (readElements) + { + // Read area file + string elefile = Path.ChangeExtension(nodefilename, ".ele"); + if (File.Exists(elefile)) + { + ReadEleFile(elefile, true); + } + } + + return data; + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// Will NOT read associated .ele by default. + public static InputGeometry ReadPolyFile(string polyfilename) + { + return ReadPolyFile(polyfilename, false, false); + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// If true, look for an associated .ele file. + /// Will NOT read associated .area by default. + public static InputGeometry ReadPolyFile(string polyfilename, bool readElements) + { + return ReadPolyFile(polyfilename, readElements, false); + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// If true, look for an associated .ele file. + /// If true, look for an associated .area file. + public static InputGeometry ReadPolyFile(string polyfilename, bool readElements, bool readArea) + { + // Read poly file + InputGeometry data; + + startIndex = 0; + + string[] line; + int invertices = 0, attributes = 0, nodemarkers = 0; + + using (StreamReader reader = new StreamReader(polyfilename)) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file."); + } + + // Read number of vertices, number of dimensions, number of vertex + // attributes, and number of boundary markers. + invertices = int.Parse(line[0]); + + if (line.Length > 1) + { + if (int.Parse(line[1]) != 2) + { + throw new Exception("Triangle only works with two-dimensional meshes."); + } + } + + if (line.Length > 2) + { + attributes = int.Parse(line[2]); + } + + if (line.Length > 3) + { + nodemarkers = int.Parse(line[3]); + } + + // Read the vertices. + if (invertices > 0) + { + data = new InputGeometry(invertices); + + for (int i = 0; i < invertices; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (vertices)."); + } + + if (line.Length < 3) + { + throw new Exception("Invalid vertex."); + } + + if (i == 0) + { + // Set the start index! + startIndex = int.Parse(line[0], nfi); + } + + ReadVertex(data, i, line, attributes, nodemarkers); + } + } + else + { + // If the .poly file claims there are zero vertices, that means that + // the vertices should be read from a separate .node file. + string nodefile = Path.ChangeExtension(polyfilename, ".node"); + data = ReadNodeFile(nodefile); + invertices = data.Count; + } + + if (data.Points == null) + { + throw new Exception("No nodes available."); + } + + // Read the segments from a .poly file. + + // Read number of segments and number of boundary markers. + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + int insegments = int.Parse(line[0]); + + int segmentmarkers = 0; + if (line.Length > 1) + { + segmentmarkers = int.Parse(line[1]); + } + + int end1, end2, mark; + // Read and insert the segments. + for (int i = 0; i < insegments; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + if (line.Length < 3) + { + throw new Exception("Segment has no endpoints."); + } + + // TODO: startIndex ok? + end1 = int.Parse(line[1]) - startIndex; + end2 = int.Parse(line[2]) - startIndex; + mark = 0; + + if (segmentmarkers > 0 && line.Length > 3) + { + mark = int.Parse(line[3]); + } + + if ((end1 < 0) || (end1 >= invertices)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("Invalid first endpoint of segment.", + "MeshReader.ReadPolyfile()"); + } + } + else if ((end2 < 0) || (end2 >= invertices)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("Invalid second endpoint of segment.", + "MeshReader.ReadPolyfile()"); + } + } + else + { + data.AddSegment(end1, end2, mark); + } + } + + // Read holes from a .poly file. + + // Read the holes. + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (holes)."); + } + + int holes = int.Parse(line[0]); + if (holes > 0) + { + for (int i = 0; i < holes; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (holes)."); + } + + if (line.Length < 3) + { + throw new Exception("Invalid hole."); + } + + data.AddHole(double.Parse(line[1], nfi), + double.Parse(line[2], nfi)); + } + } + + // Read area constraints (optional). + if (TryReadLine(reader, out line)) + { + int regions = int.Parse(line[0]); + + if (regions > 0) + { + for (int i = 0; i < regions; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (region)."); + } + + if (line.Length < 4) + { + throw new Exception("Invalid region attributes."); + } + + data.AddRegion( + // Region x and y + double.Parse(line[1], nfi), + double.Parse(line[2], nfi), + // Region id + int.Parse(line[3])); + } + } + } + } + + // Read ele file + if (readElements) + { + string elefile = Path.ChangeExtension(polyfilename, ".ele"); + if (File.Exists(elefile)) + { + ReadEleFile(elefile, readArea); + } + } + + return data; + } + + /// + /// Read elements from an .ele file. + /// + /// The file name. + /// A list of triangles. + public static List ReadEleFile(string elefilename) + { + return ReadEleFile(elefilename, false); + } + + /// + /// Read the elements from an .ele file. + /// + /// + /// + /// + private static List ReadEleFile(string elefilename, bool readArea) + { + int intriangles = 0, attributes = 0; + + List triangles; + + using (StreamReader reader = new StreamReader(elefilename)) + { + // Read number of elements and number of attributes. + string[] line; + bool validRegion = false; + + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (elements)."); + } + + intriangles = int.Parse(line[0]); + + // We irgnore index 1 (number of nodes per triangle) + attributes = 0; + if (line.Length > 2) + { + attributes = int.Parse(line[2]); + validRegion = true; + } + + if (attributes > 1) + { + SimpleLog.Instance.Warning("Triangle attributes not supported.", "FileReader.Read"); + } + + triangles = new List(intriangles); + + InputTriangle tri; + + // Read triangles. + for (int i = 0; i < intriangles; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (elements)."); + } + + if (line.Length < 4) + { + throw new Exception("Triangle has no nodes."); + } + + // TODO: startIndex ok? + tri = new InputTriangle( + int.Parse(line[1]) - startIndex, + int.Parse(line[2]) - startIndex, + int.Parse(line[3]) - startIndex); + + // Read triangle region + if (attributes > 0 && validRegion) + { + int region = 0; + validRegion = int.TryParse(line[4], out region); + tri.region = region; + } + + triangles.Add(tri); + } + } + + // Read area file + if (readArea) + { + string areafile = Path.ChangeExtension(elefilename, ".area"); + if (File.Exists(areafile)) + { + ReadAreaFile(areafile, intriangles); + } + } + + return triangles; + } + + /// + /// Read the area constraints from an .area file. + /// + /// + /// + /// + private static double[] ReadAreaFile(string areafilename, int intriangles) + { + double[] data = null; + + using (StreamReader reader = new StreamReader(areafilename)) + { + string[] line; + + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (area)."); + } + + if (int.Parse(line[0]) != intriangles) + { + SimpleLog.Instance.Warning("Number of area constraints doesn't match number of triangles.", + "ReadAreaFile()"); + return null; + } + + data = new double[intriangles]; + + // Read area constraints. + for (int i = 0; i < intriangles; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (area)."); + } + + if (line.Length != 2) + { + throw new Exception("Triangle has no nodes."); + } + + data[i] = double.Parse(line[1], nfi); + } + } + + return data; + } + + /// + /// Read an .edge file. + /// + /// The file name. + /// The number of input vertices (read from a .node or .poly file). + /// A List of edges. + public static List ReadEdgeFile(string edgeFile, int invertices) + { + // Read poly file + List data = null; + + startIndex = 0; + + string[] line; + + using (StreamReader reader = new StreamReader(edgeFile)) + { + // Read the edges from a .edge file. + + // Read number of segments and number of boundary markers. + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + int inedges = int.Parse(line[0]); + + int edgemarkers = 0; + if (line.Length > 1) + { + edgemarkers = int.Parse(line[1]); + } + + if (inedges > 0) + { + data = new List(inedges); + } + + int end1, end2, mark; + // Read and insert the segments. + for (int i = 0; i < inedges; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + if (line.Length < 3) + { + throw new Exception("Segment has no endpoints."); + } + + // TODO: startIndex ok? + end1 = int.Parse(line[1]) - startIndex; + end2 = int.Parse(line[2]) - startIndex; + mark = 0; + + if (edgemarkers > 0 && line.Length > 3) + { + mark = int.Parse(line[3]); + } + + if ((end1 < 0) || (end1 >= invertices)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("Invalid first endpoint of segment.", + "MeshReader.ReadPolyfile()"); + } + } + else if ((end2 < 0) || (end2 >= invertices)) + { + if (Behavior.Verbose) + { + SimpleLog.Instance.Warning("Invalid second endpoint of segment.", + "MeshReader.ReadPolyfile()"); + } + } + else + { + data.Add(new Edge(end1, end2, mark)); + } + } + } + + return data; + } + } +} diff --git a/ThirdParty/Triangle/IO/FileWriter.cs b/ThirdParty/Triangle/IO/FileWriter.cs new file mode 100644 index 0000000..1901bfd --- /dev/null +++ b/ThirdParty/Triangle/IO/FileWriter.cs @@ -0,0 +1,551 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.IO; + using System.Globalization; + using TriangleNet.Data; + using TriangleNet.Geometry; + using System.Collections.Generic; + + /// + /// Helper methods for writing Triangle file formats. + /// + public static class FileWriter + { + static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat; + + /// + /// Number the vertices and write them to a .node file. + /// + /// + /// + public static void Write(Mesh mesh, string filename) + { + FileWriter.WritePoly(mesh, Path.ChangeExtension(filename, ".poly")); + FileWriter.WriteElements(mesh, Path.ChangeExtension(filename, ".ele")); + } + + /// + /// Number the vertices and write them to a .node file. + /// + /// + /// + public static void WriteNodes(Mesh mesh, string filename) + { + using (StreamWriter writer = new StreamWriter(filename)) + { + FileWriter.WriteNodes(writer, mesh); + } + } + + /// + /// Number the vertices and write them to a .node file. + /// + private static void WriteNodes(StreamWriter writer, Mesh mesh) + { + int outvertices = mesh.vertices.Count; + + Behavior behavior = mesh.behavior; + + if (behavior.Jettison) + { + outvertices = mesh.vertices.Count - mesh.undeads; + } + + if (writer != null) + { + // Number of vertices, number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + writer.WriteLine("{0} {1} {2} {3}", outvertices, mesh.mesh_dim, mesh.nextras, + behavior.UseBoundaryMarkers ? "1" : "0"); + + if (mesh.numbering == NodeNumbering.None) + { + // If the mesh isn't numbered yet, use linear node numbering. + mesh.Renumber(); + } + + if (mesh.numbering == NodeNumbering.Linear) + { + // If numbering is linear, just use the dictionary values. + WriteNodes(writer, mesh.vertices.Values, behavior.UseBoundaryMarkers, + mesh.nextras, behavior.Jettison); + } + else + { + // If numbering is not linear, a simple 'foreach' traversal of the dictionary + // values doesn't reflect the actual numbering. Use an array instead. + + // TODO: Could use a custom sorting function on dictionary values instead. + Vertex[] nodes = new Vertex[mesh.vertices.Count]; + + foreach (var node in mesh.vertices.Values) + { + nodes[node.id] = node; + } + + WriteNodes(writer, nodes, behavior.UseBoundaryMarkers, + mesh.nextras, behavior.Jettison); + } + } + } + + /// + /// Write the vertices to a stream. + /// + /// + /// + private static void WriteNodes(StreamWriter writer, IEnumerable nodes, bool markers, + int attribs, bool jettison) + { + int index = 0; + + foreach (var vertex in nodes) + { + if (!jettison || vertex.type != VertexType.UndeadVertex) + { + // Vertex number, x and y coordinates. + writer.Write("{0} {1} {2}", index, vertex.x.ToString(nfi), vertex.y.ToString(nfi)); + + // Write attributes. + for (int j = 0; j < attribs; j++) + { + writer.Write(" {0}", vertex.attributes[j].ToString(nfi)); + } + + if (markers) + { + // Write the boundary marker. + writer.Write(" {0}", vertex.mark); + } + + writer.WriteLine(); + + index++; + } + } + } + + /// + /// Write the triangles to an .ele file. + /// + /// + /// + public static void WriteElements(Mesh mesh, string filename) + { + Otri tri = default(Otri); + Vertex p1, p2, p3; + bool regions = mesh.behavior.useRegions; + + int j = 0; + + tri.orient = 0; + + using (StreamWriter writer = new StreamWriter(filename)) + { + // Number of triangles, vertices per triangle, attributes per triangle. + writer.WriteLine("{0} 3 {1}", mesh.triangles.Count, regions ? 1 : 0); + + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + p1 = tri.Org(); + p2 = tri.Dest(); + p3 = tri.Apex(); + + // Triangle number, indices for three vertices. + writer.Write("{0} {1} {2} {3}", j, p1.id, p2.id, p3.id); + + if (regions) + { + writer.Write(" {0}", tri.triangle.region); + } + + writer.WriteLine(); + + // Number elements + item.id = j++; + } + } + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// + /// + public static void WritePoly(Mesh mesh, string filename) + { + FileWriter.WritePoly(mesh, filename, true); + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// Data source. + /// File name. + /// Write nodes into this file. + /// If the nodes should not be written into this file, + /// make sure a .node file was written before, so that the nodes + /// are numbered right. + public static void WritePoly(Mesh mesh, string filename, bool writeNodes) + { + Osub subseg = default(Osub); + Vertex pt1, pt2; + + bool useBoundaryMarkers = mesh.behavior.UseBoundaryMarkers; + + using (StreamWriter writer = new StreamWriter(filename)) + { + if (writeNodes) + { + // Write nodes to this file. + FileWriter.WriteNodes(writer, mesh); + } + else + { + // The zero indicates that the vertices are in a separate .node file. + // Followed by number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + writer.WriteLine("0 {0} {1} {2}", mesh.mesh_dim, mesh.nextras, + useBoundaryMarkers ? "1" : "0"); + } + + // Number of segments, number of boundary markers (zero or one). + writer.WriteLine("{0} {1}", mesh.subsegs.Count, + useBoundaryMarkers ? "1" : "0"); + + subseg.orient = 0; + + int j = 0; + foreach (var item in mesh.subsegs.Values) + { + subseg.seg = item; + + pt1 = subseg.Org(); + pt2 = subseg.Dest(); + + // Segment number, indices of its two endpoints, and possibly a marker. + if (useBoundaryMarkers) + { + writer.WriteLine("{0} {1} {2} {3}", j, pt1.id, pt2.id, subseg.seg.boundary); + } + else + { + writer.WriteLine("{0} {1} {2}", j, pt1.id, pt2.id); + } + + j++; + } + + // Holes + j = 0; + writer.WriteLine("{0}", mesh.holes.Count); + foreach (var hole in mesh.holes) + { + writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi)); + } + + // Regions + if (mesh.regions.Count > 0) + { + j = 0; + writer.WriteLine("{0}", mesh.regions.Count); + foreach (var region in mesh.regions) + { + writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi), + region.point.Y.ToString(nfi), region.id); + + j++; + } + } + } + } + + /// + /// Write the edges to an .edge file. + /// + /// + /// + public static void WriteEdges(Mesh mesh, string filename) + { + Otri tri = default(Otri), trisym = default(Otri); + Osub checkmark = default(Osub); + Vertex p1, p2; + + Behavior behavior = mesh.behavior; + + using (StreamWriter writer = new StreamWriter(filename)) + { + // Number of edges, number of boundary markers (zero or one). + writer.WriteLine("{0} {1}", mesh.edges, behavior.UseBoundaryMarkers ? "1" : "0"); + + long index = 0; + // To loop over the set of edges, loop over all triangles, and look at + // the three edges of each triangle. If there isn't another triangle + // adjacent to the edge, operate on the edge. If there is another + // adjacent triangle, operate on the edge only if the current triangle + // has a smaller pointer than its neighbor. This way, each edge is + // considered only once. + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + tri.Sym(ref trisym); + if ((tri.triangle.id < trisym.triangle.id) || (trisym.triangle == Mesh.dummytri)) + { + p1 = tri.Org(); + p2 = tri.Dest(); + + if (behavior.UseBoundaryMarkers) + { + // Edge number, indices of two endpoints, and a boundary marker. + // If there's no subsegment, the boundary marker is zero. + if (behavior.useSegments) + { + tri.SegPivot(ref checkmark); + + if (checkmark.seg == Mesh.dummysub) + { + writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, 0); + } + else + { + writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, + checkmark.seg.boundary); + } + } + else + { + writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, + trisym.triangle == Mesh.dummytri ? "1" : "0"); + } + } + else + { + // Edge number, indices of two endpoints. + writer.WriteLine("{0} {1} {2}", index, p1.id, p2.id); + } + + index++; + } + } + } + } + } + + /// + /// Write the triangle neighbors to a .neigh file. + /// + /// + /// + /// WARNING: Be sure WriteElements has been called before, + /// so the elements are numbered right! + public static void WriteNeighbors(Mesh mesh, string filename) + { + Otri tri = default(Otri), trisym = default(Otri); + int n1, n2, n3; + int i = 0; + + using (StreamWriter writer = new StreamWriter(filename)) + { + // Number of triangles, three neighbors per triangle. + writer.WriteLine("{0} 3", mesh.triangles.Count); + + Mesh.dummytri.id = -1; + + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + tri.orient = 1; + tri.Sym(ref trisym); + n1 = trisym.triangle.id; + + tri.orient = 2; + tri.Sym(ref trisym); + n2 = trisym.triangle.id; + + tri.orient = 0; + tri.Sym(ref trisym); + n3 = trisym.triangle.id; + + // Triangle number, neighboring triangle numbers. + writer.WriteLine("{0} {1} {2} {3}", i++, n1, n2, n3); + } + } + } + + /// + /// Write the Voronoi diagram to a .voro file. + /// + /// + /// + /// + /// + /// The Voronoi diagram is the geometric dual of the Delaunay triangulation. + /// Hence, the Voronoi vertices are listed by traversing the Delaunay + /// triangles, and the Voronoi edges are listed by traversing the Delaunay + /// edges. + /// + /// WARNING: In order to assign numbers to the Voronoi vertices, this + /// procedure messes up the subsegments or the extra nodes of every + /// element. Hence, you should call this procedure last. + public static void WriteVoronoi(Mesh mesh, string filename) + { + Otri tri = default(Otri), trisym = default(Otri); + Vertex torg, tdest, tapex; + Point circumcenter; + double xi = 0, eta = 0; + + int p1, p2, index = 0; + tri.orient = 0; + + using (StreamWriter writer = new StreamWriter(filename)) + { + // Number of triangles, two dimensions, number of vertex attributes, no markers. + writer.WriteLine("{0} 2 {1} 0", mesh.triangles.Count, mesh.nextras); + + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + torg = tri.Org(); + tdest = tri.Dest(); + tapex = tri.Apex(); + circumcenter = Primitives.FindCircumcenter(torg, tdest, tapex, ref xi, ref eta); + + // X and y coordinates. + writer.Write("{0} {1} {2}", index, circumcenter.X.ToString(nfi), + circumcenter.Y.ToString(nfi)); + + for (int i = 0; i < mesh.nextras; i++) + { + writer.Write(" 0"); + // TODO + // Interpolate the vertex attributes at the circumcenter. + //writer.Write(" {0}", torg.attribs[i] + xi * (tdes.attribst[i] - torg.attribs[i]) + + // eta * (tapex.attribs[i] - torg.attribs[i])); + } + writer.WriteLine(); + + tri.triangle.id = index++; + } + + + // Number of edges, zero boundary markers. + writer.WriteLine("{0} 0", mesh.edges); + + index = 0; + // To loop over the set of edges, loop over all triangles, and look at + // the three edges of each triangle. If there isn't another triangle + // adjacent to the edge, operate on the edge. If there is another + // adjacent triangle, operate on the edge only if the current triangle + // has a smaller pointer than its neighbor. This way, each edge is + // considered only once. + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + tri.Sym(ref trisym); + if ((tri.triangle.id < trisym.triangle.id) || (trisym.triangle == Mesh.dummytri)) + { + // Find the number of this triangle (and Voronoi vertex). + p1 = tri.triangle.id; + + if (trisym.triangle == Mesh.dummytri) + { + torg = tri.Org(); + tdest = tri.Dest(); + + // Write an infinite ray. Edge number, index of one endpoint, + // -1, and x and y coordinates of a vector representing the + // direction of the ray. + writer.WriteLine("{0} {1} -1 {2} {3}", index, p1, + (tdest[1] - torg[1]).ToString(nfi), + (torg[0] - tdest[0]).ToString(nfi)); + } + else + { + // Find the number of the adjacent triangle (and Voronoi vertex). + p2 = trisym.triangle.id; + // Finite edge. Write indices of two endpoints. + writer.WriteLine("{0} {1} {2}", index, p1, p2); + } + + index++; + } + } + } + } + } + + /// + /// Write the triangulation to an .off file. + /// + /// + /// + /// + /// OFF stands for the Object File Format, a format used by the Geometry + /// Center's Geomview package. + /// + public static void WriteOffFile(Mesh mesh, string filename) + { + Otri tri; + Vertex p1, p2, p3; + + long outvertices = mesh.vertices.Count; + + if (mesh.behavior.Jettison) + { + outvertices = mesh.vertices.Count - mesh.undeads; + } + + int index = 0; + + using (StreamWriter writer = new StreamWriter(filename)) + { + writer.WriteLine("OFF"); + writer.WriteLine("{0} {1} {2}", outvertices, mesh.triangles.Count, mesh.edges); + + foreach (var item in mesh.vertices.Values) + { + p1 = item; + + if (!mesh.behavior.Jettison || p1.type != VertexType.UndeadVertex) + { + // The "0.0" is here because the OFF format uses 3D coordinates. + writer.WriteLine(" {0} {1} 0.0", p1[0].ToString(nfi), p1[1].ToString(nfi)); + + p1.id = index++; + } + } + + // Write the triangles. + tri.orient = 0; + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + p1 = tri.Org(); + p2 = tri.Dest(); + p3 = tri.Apex(); + + // The "3" means a three-vertex polygon. + writer.WriteLine(" 3 {0} {1} {2}", p1.id, p2.id, p3.id); + } + } + } + } +} diff --git a/ThirdParty/Triangle/IO/IGeometryFormat.cs b/ThirdParty/Triangle/IO/IGeometryFormat.cs new file mode 100644 index 0000000..f73eef3 --- /dev/null +++ b/ThirdParty/Triangle/IO/IGeometryFormat.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + + /// + /// Interface for geometry input. + /// + public interface IGeometryFormat + { + /// + /// Read a file containing geometry information. + /// + /// The path of the file to read. + /// An instance of the class. + InputGeometry Read(string filename); + } +} diff --git a/ThirdParty/Triangle/IO/IMeshFormat.cs b/ThirdParty/Triangle/IO/IMeshFormat.cs new file mode 100644 index 0000000..e685504 --- /dev/null +++ b/ThirdParty/Triangle/IO/IMeshFormat.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + + /// + /// Interface for mesh I/O. + /// + public interface IMeshFormat + { + /// + /// Read a file containing a mesh. + /// + /// The path of the file to read. + /// An instance of the class. + Mesh Import(string filename); + + /// + /// Save a mesh to disk. + /// + /// An instance of the class. + /// The path of the file to save. + void Write(Mesh mesh, string filename); + } +} diff --git a/ThirdParty/Triangle/IO/InputTriangle.cs b/ThirdParty/Triangle/IO/InputTriangle.cs new file mode 100644 index 0000000..d8938c5 --- /dev/null +++ b/ThirdParty/Triangle/IO/InputTriangle.cs @@ -0,0 +1,118 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using TriangleNet.Data; + using TriangleNet.Geometry; + + /// + /// Simple triangle class for input. + /// + public class InputTriangle : ITriangle + { + internal int[] vertices; + internal int region; + internal double area; + + public InputTriangle(int p0, int p1, int p2) + { + this.vertices = new int[] { p0, p1, p2 }; + } + + #region Public properties + + /// + /// Gets the triangle id. + /// + public int ID + { + get { return 0; } + } + + /// + /// Gets the first corners vertex id. + /// + public int P0 + { + get { return this.vertices[0]; } + } + + /// + /// Gets the seconds corners vertex id. + /// + public int P1 + { + get { return this.vertices[1]; } + } + + /// + /// Gets the third corners vertex id. + /// + public int P2 + { + get { return this.vertices[2]; } + } + + /// + /// Gets the specified corners vertex. + /// + public Vertex GetVertex(int index) + { + return null; // TODO: throw NotSupportedException? + } + + public bool SupportsNeighbors + { + get { return false; } + } + + public int N0 + { + get { return -1; } + } + + public int N1 + { + get { return -1; } + } + + public int N2 + { + get { return -1; } + } + + public ITriangle GetNeighbor(int index) + { + return null; + } + + public ISegment GetSegment(int index) + { + return null; + } + + /// + /// Gets the triangle area constraint. + /// + public double Area + { + get { return area; } + set { area = value; } + } + + /// + /// Region ID the triangle belongs to. + /// + public int Region + { + get { return region; } + set { region = value; } + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/IO/TriangleFormat.cs b/ThirdParty/Triangle/IO/TriangleFormat.cs new file mode 100644 index 0000000..a3cab79 --- /dev/null +++ b/ThirdParty/Triangle/IO/TriangleFormat.cs @@ -0,0 +1,67 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + using System.IO; + + /// + /// Implements geometry and mesh file formats of the the original Triangle code. + /// + public class TriangleFormat : IGeometryFormat, IMeshFormat + { + public Mesh Import(string filename) + { + string ext = Path.GetExtension(filename); + + if (ext == ".node" || ext == ".poly" || ext == ".ele") + { + List triangles; + InputGeometry geometry; + + FileReader.Read(filename, out geometry, out triangles); + + if (geometry != null && triangles != null) + { + Mesh mesh = new Mesh(); + mesh.Load(geometry, triangles); + + return mesh; + } + } + + throw new NotSupportedException("Could not load '" + filename + "' file."); + } + + public void Write(Mesh mesh, string filename) + { + FileWriter.WritePoly(mesh, Path.ChangeExtension(filename, ".poly")); + FileWriter.WriteElements(mesh, Path.ChangeExtension(filename, ".ele")); + } + + public InputGeometry Read(string filename) + { + string ext = Path.GetExtension(filename); + + if (ext == ".node") + { + return FileReader.ReadNodeFile(filename); + } + + if (ext == ".poly") + { + return FileReader.ReadPolyFile(filename); + } + + throw new NotSupportedException("File format '" + ext + "' not supported."); + } + } +} diff --git a/ThirdParty/Triangle/Log/ILog.cs b/ThirdParty/Triangle/Log/ILog.cs new file mode 100644 index 0000000..ca139ab --- /dev/null +++ b/ThirdParty/Triangle/Log/ILog.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Log +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + public enum LogLevel + { + Info = 0, + Warning = 1, + Error = 2 + } + + /// + /// A basic log interface. + /// + public interface ILog where T : ILogItem + { + void Add(T item); + void Clear(); + + void Info(string message); + void Error(string message, string info); + void Warning(string message, string info); + + IList Data { get; } + + LogLevel Level { get; } + } +} diff --git a/ThirdParty/Triangle/Log/ILogItem.cs b/ThirdParty/Triangle/Log/ILogItem.cs new file mode 100644 index 0000000..72b4f12 --- /dev/null +++ b/ThirdParty/Triangle/Log/ILogItem.cs @@ -0,0 +1,24 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Log +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// A basic log item interface. + /// + public interface ILogItem + { + DateTime Time { get; } + LogLevel Level { get; } + string Message { get; } + string Info { get; } + } +} diff --git a/ThirdParty/Triangle/Log/SimpleLog.cs b/ThirdParty/Triangle/Log/SimpleLog.cs new file mode 100644 index 0000000..dad41bc --- /dev/null +++ b/ThirdParty/Triangle/Log/SimpleLog.cs @@ -0,0 +1,81 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Log +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// A simple logger, which logs messages to a List. + /// + /// Using singleton pattern as proposed by Jon Skeet. + /// http://csharpindepth.com/Articles/General/Singleton.aspx + /// + public sealed class SimpleLog : ILog + { + private List log = new List(); + + private LogLevel level = LogLevel.Info; + + #region Singleton pattern + + private static readonly SimpleLog instance = new SimpleLog(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static SimpleLog() { } + + private SimpleLog() { } + + public static ILog Instance + { + get + { + return instance; + } + } + + #endregion + + public void Add(SimpleLogItem item) + { + log.Add(item); + } + + public void Clear() + { + log.Clear(); + } + + public void Info(string message) + { + log.Add(new SimpleLogItem(LogLevel.Info, message)); + } + + public void Warning(string message, string location) + { + log.Add(new SimpleLogItem(LogLevel.Warning, message, location)); + } + + public void Error(string message, string location) + { + log.Add(new SimpleLogItem(LogLevel.Error, message, location)); + } + + public IList Data + { + get { return log; } + } + + public LogLevel Level + { + get { return level; } + } + } +} diff --git a/ThirdParty/Triangle/Log/SimpleLogItem.cs b/ThirdParty/Triangle/Log/SimpleLogItem.cs new file mode 100644 index 0000000..b4b7d3c --- /dev/null +++ b/ThirdParty/Triangle/Log/SimpleLogItem.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Log +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// Represents an item stored in the log. + /// + public class SimpleLogItem : ILogItem + { + DateTime time; + LogLevel level; + string message; + string info; + + public DateTime Time + { + get { return time; } + } + + public LogLevel Level + { + get { return level; } + } + + public string Message + { + get { return message; } + } + + public string Info + { + get { return info; } + } + + public SimpleLogItem(LogLevel level, string message) + : this(level, message, "") + { } + + public SimpleLogItem(LogLevel level, string message, string info) + { + this.time = DateTime.Now; + this.level = level; + this.message = message; + this.info = info; + } + } +} diff --git a/ThirdParty/Triangle/Mesh.cs b/ThirdParty/Triangle/Mesh.cs new file mode 100644 index 0000000..ae9639e --- /dev/null +++ b/ThirdParty/Triangle/Mesh.cs @@ -0,0 +1,2768 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Data; + using TriangleNet.Log; + using TriangleNet.IO; + using TriangleNet.Algorithm; + using TriangleNet.Smoothing; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Mesh data structure. + /// + public class Mesh + { + #region Variables + + ILog logger; + + Quality quality; + + // Stack that maintains a list of recently flipped triangles. + Stack flipstack; + + // TODO: Check if custom hashmap implementation could be faster. + + // Using hashsets for memory management should quite fast. + internal Dictionary triangles; + internal Dictionary subsegs; + internal Dictionary vertices; + + // Hash seeds (should belong to mesh instance) + internal int hash_vtx = 0; + internal int hash_seg = 0; + internal int hash_tri = 0; + + internal List holes; + internal List regions; + + // Other variables. + internal BoundingBox bounds; // x and y bounds. + internal int invertices; // Number of input vertices. + internal int inelements; // Number of input triangles. + internal int insegments; // Number of input segments. + internal int undeads; // Number of input vertices that don't appear in the mesh. + internal int edges; // Number of output edges. + internal int mesh_dim; // Dimension (ought to be 2). + internal int nextras; // Number of attributes per vertex. + //internal int eextras; // Number of attributes per triangle. + internal int hullsize; // Number of edges in convex hull. + internal int steinerleft; // Number of Steiner points not yet used. + internal bool checksegments; // Are there segments in the triangulation yet? + internal bool checkquality; // Has quality triangulation begun yet? + + // Triangular bounding box vertices. + internal Vertex infvertex1, infvertex2, infvertex3; + + // The 'triangle' that occupies all of 'outer space'. + internal static Triangle dummytri; + + // The omnipresent subsegment. Referenced by any triangle or subsegment + // that isn't really connected to a subsegment at that location. + internal static Segment dummysub; + + internal TriangleLocator locator; + + // Controls the behavior of the mesh instance. + internal Behavior behavior; + + // The current node numbering + internal NodeNumbering numbering; + + #endregion + + #region Public properties + + /// + /// Gets the mesh behavior instance. + /// + public Behavior Behavior + { + get { return this.behavior; } + } + + /// + /// Gets the mesh bounding box. + /// + public BoundingBox Bounds + { + get { return this.bounds; } + } + + /// + /// Gets the mesh vertices. + /// + public ICollection Vertices + { + get { return this.vertices.Values; } + } + + /// + /// Gets the mesh holes. + /// + public IList Holes + { + get { return this.holes; } + } + + /// + /// Gets the mesh triangles. + /// + public ICollection Triangles + { + get { return this.triangles.Values; } + } + + /// + /// Gets the mesh segments. + /// + public ICollection Segments + { + get { return this.subsegs.Values; } + } + + /// + /// Gets the mesh edges. + /// + public IEnumerable Edges + { + get + { + EdgeEnumerator e = new EdgeEnumerator(this); + while (e.MoveNext()) + { + yield return e.Current; + } + } + } + + /// + /// Gets the number of input vertices. + /// + public int NumberOfInputPoints { get { return invertices; } } + + /// + /// Gets the number of mesh edges. + /// + public int NumberOfEdges { get { return this.edges; } } + + /// + /// Indicates whether the input is a PSLG or a point set. + /// + public bool IsPolygon { get { return this.insegments > 0; } } + + /// + /// Gets the current node numbering. + /// + public NodeNumbering CurrentNumbering + { + get { return numbering; } + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public Mesh() + : this(new Behavior()) + { + } + + /// + /// Initializes a new instance of the class. + /// + public Mesh(Behavior behavior) + { + this.behavior = behavior; + + logger = SimpleLog.Instance; + + behavior = new Behavior(); + + vertices = new Dictionary(); + triangles = new Dictionary(); + subsegs = new Dictionary(); + + flipstack = new Stack(); + + holes = new List(); + regions = new List(); + + quality = new Quality(this); + + locator = new TriangleLocator(this); + + Primitives.ExactInit(); + + if (dummytri == null) + { + // Initialize static dummy triangle and subseg. + DummyInit(); + } + } + + /// + /// Load a mesh from file (.node/poly and .ele). + /// + public void Load(string filename) + { + List triangles; + InputGeometry geometry; + + FileReader.Read(filename, out geometry, out triangles); + + if (geometry != null && triangles != null) + { + Load(geometry, triangles); + } + } + + /// + /// Reconstructs a mesh from raw input data. + /// + public void Load(InputGeometry input, List triangles) + { + if (input == null || triangles == null) + { + throw new ArgumentException("Invalid input (argument is null)."); + } + + // Clear all data structures / reset hash seeds + this.ResetData(); + + if (input.HasSegments) + { + behavior.Poly = true; + + this.holes.AddRange(input.Holes); + } + + //if (input.EdgeMarkers != null) + //{ + // behavior.UseBoundaryMarkers = true; + //} + + //if (input.TriangleAreas != null) + //{ + // behavior.VarArea = true; + //} + + // TODO: remove + if (!behavior.Poly) + { + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + behavior.VarArea = false; + + // Be careful not to add an extra attribute to each element unless the + // input supports it (PSLG in, but not refining a preexisting mesh). + behavior.useRegions = false; + } + + behavior.useRegions = input.Regions.Count > 0; + + TransferNodes(input); + + // Read and reconstruct a mesh. + hullsize = DataReader.Reconstruct(this, input, triangles.ToArray()); + + // Calculate the number of edges. + edges = (3 * triangles.Count + hullsize) / 2; + } + + /// + /// Triangulate given input file (.node or .poly). + /// + /// + public void Triangulate(string inputFile) + { + InputGeometry input = FileReader.Read(inputFile); + + this.Triangulate(input); + } + + /// + /// Triangulate given input data. + /// + /// + public void Triangulate(InputGeometry input) + { + ResetData(); + + behavior.Poly = input.HasSegments; + //behavior.useSegments = input.HasSegments; + + //if (input.EdgeMarkers != null) + //{ + // behavior.UseBoundaryMarkers = true; + //} + + // TODO: remove + if (!behavior.Poly) + { + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + behavior.VarArea = false; + + // Be careful not to add an extra attribute to each element unless the + // input supports it (PSLG in, but not refining a preexisting mesh). + behavior.useRegions = false; + } + + behavior.useRegions = input.Regions.Count > 0; + + steinerleft = behavior.SteinerPoints; + + TransferNodes(input); + + hullsize = Delaunay(); // Triangulate the vertices. + + // Ensure that no vertex can be mistaken for a triangular bounding + // box vertex in insertvertex(). + infvertex1 = null; + infvertex2 = null; + infvertex3 = null; + + if (behavior.useSegments) + { + // Segments will be introduced next. + checksegments = true; + + // Insert PSLG segments and/or convex hull segments. + FormSkeleton(input); + } + + if (behavior.Poly && (triangles.Count > 0)) + { + // Copy holes + foreach (var item in input.holes) + { + holes.Add(item); + } + + // Copy regions + foreach (var item in input.regions) + { + regions.Add(item); + } + + //dummytri.neighbors[2].triangle = dummytri; + + // Carve out holes and concavities. + Carver c = new Carver(this); + c.CarveHoles(); + } + else + { + // Without a PSLG, there can be no holes or regional attributes + // or area constraints. The following are set to zero to avoid + // an accidental free() later. + // + // TODO: - + holes.Clear(); + regions.Clear(); + } + + if (behavior.Quality && (triangles.Count > 0)) + { + quality.EnforceQuality(); // Enforce angle and area constraints. + } + + // Calculate the number of edges. + edges = (3 * triangles.Count + hullsize) / 2; + } + + /// + /// Refines the current mesh by finding the maximum triangle area and setting + /// the a global area constraint to half its size. + /// + public void Refine(bool halfArea) + { + if (halfArea) + { + double tmp, maxArea = 0; + + foreach (var t in this.triangles.Values) + { + tmp = (t.vertices[2].x - t.vertices[0].x) * (t.vertices[1].y - t.vertices[0].y) - + (t.vertices[1].x - t.vertices[0].x) * (t.vertices[2].y - t.vertices[0].y); + + tmp = Math.Abs(tmp) / 2.0; + + if (tmp > maxArea) + { + maxArea = tmp; + } + } + + this.Refine(maxArea / 2); + } + else + { + Refine(); + } + } + + /// + /// Refines the current mesh by setting a global area constraint. + /// + /// Global area constraint. + public void Refine(double areaConstraint) + { + behavior.fixedArea = true; + behavior.MaxArea = areaConstraint; + + this.Refine(); + + // Reset option for sanity + behavior.fixedArea = false; + behavior.MaxArea = -1.0; + } + + /// + /// Refines the current mesh. + /// + public void Refine() + { + inelements = triangles.Count; + invertices = vertices.Count; + + // TODO: Set all vertex types to input (i.e. NOT free)? + + if (behavior.Poly) + { + if (behavior.useSegments) + { + insegments = subsegs.Count; + } + else + { + insegments = (int)hullsize; + } + } + + Reset(); + + steinerleft = behavior.SteinerPoints; + + // Ensure that no vertex can be mistaken for a triangular bounding + // box vertex in insertvertex(). + infvertex1 = null; + infvertex2 = null; + infvertex3 = null; + + if (behavior.useSegments) + { + checksegments = true; + } + + // TODO + //holes.Clear(); + //regions.Clear(); + + if (triangles.Count > 0) + { + // Enforce angle and area constraints. + quality.EnforceQuality(); + } + + // Calculate the number of edges. + edges = (3 * triangles.Count + hullsize) / 2; + } + + /// + /// Smooth the current mesh. + /// + public void Smooth() + { + numbering = NodeNumbering.None; + + ISmoother smoother = new SimpleSmoother(this); + smoother.Smooth(); + } + + /// + /// Renumber vertex and triangle id's. + /// + public void Renumber() + { + this.Renumber(NodeNumbering.Linear); + } + + /// + /// Renumber vertex and triangle id's. + /// + public void Renumber(NodeNumbering num) + { + // Don't need to do anything if the nodes are already numbered. + if (num == this.numbering) + { + return; + } + + int id; + + if (num == NodeNumbering.Linear) + { + id = 0; + foreach (var node in this.vertices.Values) + { + node.id = id++; + } + } + else if (num == NodeNumbering.CuthillMcKee) + { + CuthillMcKee rcm = new CuthillMcKee(); + int[] perm_inv = rcm.Renumber(this); + + // Permute the node indices. + foreach (var node in this.vertices.Values) + { + node.id = perm_inv[node.id]; + } + } + + // Remember the current numbering. + numbering = num; + + // Triangles will always be numbered from 0 to n-1 + id = 0; + foreach (var item in this.triangles.Values) + { + item.id = id++; + } + } + + /// + /// Check mesh consistency and (constrained) Delaunay property. + /// + /// Value indicating if mesh topology is consistent. + /// Value indicating if mesh is Delaunay. + public void Check(out bool isConsistent, out bool isDelaunay) + { + isConsistent = quality.CheckMesh(); + isDelaunay = quality.CheckDelaunay(); + } + + #region Misc + + /// + /// Form a Delaunay triangulation. + /// + /// The number of points on the hull. + private int Delaunay() + { + int hulledges = 0; + + if (behavior.Algorithm == TriangulationAlgorithm.Dwyer) + { + Dwyer alg = new Dwyer(); + hulledges = alg.Triangulate(this); + } + else if (behavior.Algorithm == TriangulationAlgorithm.SweepLine) + { + SweepLine alg = new SweepLine(); + hulledges = alg.Triangulate(this); + } + else + { + Incremental alg = new Incremental(); + hulledges = alg.Triangulate(this); + } + + // The input vertices may all be collinear, so there are + // no triangles. + return (triangles.Count == 0) ? 0 : hulledges; + } + + /// + /// Reset all the mesh data. This method will also wipe + /// out all mesh data. + /// + private void ResetData() + { + vertices.Clear(); + triangles.Clear(); + subsegs.Clear(); + + holes.Clear(); + regions.Clear(); + + this.hash_vtx = 0; + this.hash_seg = 0; + this.hash_tri = 0; + + flipstack.Clear(); + + hullsize = 0; + edges = 0; + + Reset(); + + locator.Reset(); + } + + /// + /// Reset the mesh triangulation state. + /// + private void Reset() + { + numbering = NodeNumbering.None; + + undeads = 0; // No eliminated input vertices yet. + checksegments = false; // There are no segments in the triangulation yet. + checkquality = false; // The quality triangulation stage has not begun. + + Statistic.InCircleCount = 0; + Statistic.CounterClockwiseCount = 0; + Statistic.InCircleCountDecimal = 0; + Statistic.CounterClockwiseCountDecimal = 0; + Statistic.Orient3dCount = 0; + Statistic.HyperbolaCount = 0; + Statistic.CircleTopCount = 0; + Statistic.CircumcenterCount = 0; + } + + /// + /// Initialize the triangle that fills "outer space" and the omnipresent subsegment. + /// + /// + /// The triangle that fills "outer space," called 'dummytri', is pointed to + /// by every triangle and subsegment on a boundary (be it outer or inner) of + /// the triangulation. Also, 'dummytri' points to one of the triangles on + /// the convex hull (until the holes and concavities are carved), making it + /// possible to find a starting triangle for point location. + // + /// The omnipresent subsegment, 'dummysub', is pointed to by every triangle + /// or subsegment that doesn't have a full complement of real subsegments + /// to point to. + // + /// 'dummytri' and 'dummysub' are generally required to fulfill only a few + /// invariants: their vertices must remain NULL and 'dummytri' must always + /// be bonded (at offset zero) to some triangle on the convex hull of the + /// mesh, via a boundary edge. Otherwise, the connections of 'dummytri' and + /// 'dummysub' may change willy-nilly. This makes it possible to avoid + /// writing a good deal of special-case code (in the edge flip, for example) + /// for dealing with the boundary of the mesh, places where no subsegment is + /// present, and so forth. Other entities are frequently bonded to + /// 'dummytri' and 'dummysub' as if they were real mesh entities, with no + /// harm done. + /// + private void DummyInit() + { + // Set up 'dummytri', the 'triangle' that occupies "outer space." + dummytri = new Triangle(); + dummytri.hash = -1; + dummytri.id = -1; + + // Initialize the three adjoining triangles to be "outer space." These + // will eventually be changed by various bonding operations, but their + // values don't really matter, as long as they can legally be + // dereferenced. + dummytri.neighbors[0].triangle = dummytri; + dummytri.neighbors[1].triangle = dummytri; + dummytri.neighbors[2].triangle = dummytri; + + if (behavior.useSegments) + { + // Set up 'dummysub', the omnipresent subsegment pointed to by any + // triangle side or subsegment end that isn't attached to a real + // subsegment. + dummysub = new Segment(); + dummysub.hash = -1; + + // Initialize the two adjoining subsegments to be the omnipresent + // subsegment. These will eventually be changed by various bonding + // operations, but their values don't really matter, as long as they + // can legally be dereferenced. + dummysub.subsegs[0].seg = dummysub; + dummysub.subsegs[1].seg = dummysub; + + // Initialize the three adjoining subsegments of 'dummytri' to be + // the omnipresent subsegment. + dummytri.subsegs[0].seg = dummysub; + dummytri.subsegs[1].seg = dummysub; + dummytri.subsegs[2].seg = dummysub; + } + } + + /// + /// Read the vertices from memory. + /// + /// The input data. + private void TransferNodes(InputGeometry data) + { + List points = data.points; + + this.invertices = points.Count; + this.mesh_dim = 2; + + if (this.invertices < 3) + { + logger.Error("Input must have at least three input vertices.", "MeshReader.TransferNodes()"); + throw new Exception("Input must have at least three input vertices."); + } + + this.nextras = points[0].attributes == null ? 0 : points[0].attributes.Length; + + foreach (Vertex vertex in points) + { + vertex.hash = this.hash_vtx++; + vertex.id = vertex.hash; + + this.vertices.Add(vertex.hash, vertex); + } + + this.bounds = data.Bounds; + } + + /// + /// Construct a mapping from vertices to triangles to improve the speed of + /// point location for segment insertion. + /// + /// + /// Traverses all the triangles, and provides each corner of each triangle + /// with a pointer to that triangle. Of course, pointers will be overwritten + /// by other pointers because (almost) each vertex is a corner of several + /// triangles, but in the end every vertex will point to some triangle + /// that contains it. + /// + internal void MakeVertexMap() + { + Otri tri = default(Otri); + Vertex triorg; + + foreach (var t in this.triangles.Values) + { + tri.triangle = t; + // Check all three vertices of the triangle. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + triorg = tri.Org(); + triorg.tri = tri; + } + } + } + + #endregion + + #region Factory + + /// + /// Create a new triangle with orientation zero. + /// + /// Reference to the new triangle. + internal void MakeTriangle(ref Otri newotri) + { + Triangle tri = new Triangle(); + tri.hash = this.hash_tri++; + tri.id = tri.hash; + + newotri.triangle = tri; + newotri.orient = 0; + + triangles.Add(tri.hash, tri); + } + + /// + /// Create a new subsegment with orientation zero. + /// + /// Reference to the new subseg. + internal void MakeSegment(ref Osub newsubseg) + { + Segment seg = new Segment(); + seg.hash = this.hash_seg++; + + newsubseg.seg = seg; + newsubseg.orient = 0; + + subsegs.Add(seg.hash, seg); + } + + #endregion + + #region Manipulation + + /// + /// Insert a vertex into a Delaunay triangulation, performing flips as necessary + /// to maintain the Delaunay property. + /// + /// The point to be inserted. + /// The triangle to start the search. + /// Segment to split. + /// Check for creation of encroached subsegments. + /// Check for creation of bad quality triangles. + /// If a duplicate vertex or violated segment does not prevent the + /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if + /// the vertex encroaches upon a subsegment (and checking is enabled), or + /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle + /// whose origin is the newly inserted vertex. + /// + /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, + /// the search for the containing triangle begins from 'searchtri'. If + /// 'searchtri.triangle' is NULL, a full point location procedure is called. + /// If 'insertvertex' is found inside a triangle, the triangle is split into + /// three; if 'insertvertex' lies on an edge, the edge is split in two, + /// thereby splitting the two adjacent triangles into four. Edge flips are + /// used to restore the Delaunay property. If 'insertvertex' lies on an + /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is + /// returned. On return, 'searchtri' is set to a handle whose origin is the + /// existing vertex. + /// + /// InsertVertex() does not use flip() for reasons of speed; some + /// information can be reused from edge flip to edge flip, like the + /// locations of subsegments. + /// + /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, + /// implying that no subsegment should be split. In this case, if 'insertvertex' + /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX + /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the + /// violated subsegment. + /// If the calling routine wishes to split a subsegment by inserting a vertex in it, + /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' + /// MUST be the triangle handle reached by pivoting from that subsegment; no point + /// location is done. + /// + /// Param 'segmentflaws': Flags that indicate whether or not there should + /// be checks for the creation of encroached subsegments. If a newly inserted + /// vertex encroaches upon subsegments, these subsegments are added to the list + /// of subsegments to be split if 'segmentflaws' is set. + /// + /// Param 'triflaws': Flags that indicate whether or not there should be + /// checks for the creation of bad quality triangles. If bad triangles are + /// created, these are added to the queue if 'triflaws' is set. + /// + internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, + ref Osub splitseg, bool segmentflaws, bool triflaws) + { + Otri horiz = default(Otri); + Otri top = default(Otri); + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri newbotleft = default(Otri), newbotright = default(Otri); + Otri newtopright = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Otri testtri = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Osub brokensubseg = default(Osub); + Osub checksubseg = default(Osub); + Osub rightsubseg = default(Osub); + Osub newsubseg = default(Osub); + BadSubseg encroached; + //FlipStacker newflip; + Vertex first; + Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; + Vertex segmentorg, segmentdest; + int region; + double area; + InsertVertexResult success; + LocateResult intersect; + bool doflip; + bool mirrorflag; + bool enq; + + if (splitseg.seg == null) + { + // Find the location of the vertex to be inserted. Check if a good + // starting triangle has already been provided by the caller. + if (searchtri.triangle == dummytri) + { + // Find a boundary triangle. + horiz.triangle = dummytri; + horiz.orient = 0; + horiz.SymSelf(); + // Search for a triangle containing 'newvertex'. + intersect = locator.Locate(newvertex, ref horiz); + } + else + { + // Start searching from the triangle provided by the caller. + searchtri.Copy(ref horiz); + intersect = locator.PreciseLocate(newvertex, ref horiz, true); + } + } + else + { + // The calling routine provides the subsegment in which + // the vertex is inserted. + searchtri.Copy(ref horiz); + intersect = LocateResult.OnEdge; + } + + if (intersect == LocateResult.OnVertex) + { + // There's already a vertex there. Return in 'searchtri' a triangle + // whose origin is the existing vertex. + horiz.Copy(ref searchtri); + locator.Update(ref horiz); + return InsertVertexResult.Duplicate; + } + if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) + { + // The vertex falls on an edge or boundary. + if (checksegments && (splitseg.seg == null)) + { + // Check whether the vertex falls on a subsegment. + horiz.SegPivot(ref brokensubseg); + if (brokensubseg.seg != dummysub) + { + // The vertex falls on a subsegment, and hence will not be inserted. + if (segmentflaws) + { + enq = behavior.NoBisect != 2; + if (enq && (behavior.NoBisect == 1)) + { + // This subsegment may be split only if it is an + // internal boundary. + horiz.Sym(ref testtri); + enq = testtri.triangle != dummytri; + } + if (enq) + { + // Add the subsegment to the list of encroached subsegments. + encroached = new BadSubseg(); + encroached.encsubseg = brokensubseg; + encroached.subsegorg = brokensubseg.Org(); + encroached.subsegdest = brokensubseg.Dest(); + + quality.AddBadSubseg(encroached); + } + } + // Return a handle whose primary edge contains the vertex, + // which has not been inserted. + horiz.Copy(ref searchtri); + locator.Update(ref horiz); + return InsertVertexResult.Violating; + } + } + + // Insert the vertex on an edge, dividing one triangle into two (if + // the edge lies on a boundary) or two triangles into four. + horiz.Lprev(ref botright); + botright.Sym(ref botrcasing); + horiz.Sym(ref topright); + // Is there a second triangle? (Or does this edge lie on a boundary?) + mirrorflag = topright.triangle != dummytri; + if (mirrorflag) + { + topright.LnextSelf(); + topright.Sym(ref toprcasing); + MakeTriangle(ref newtopright); + } + else + { + // Splitting a boundary edge increases the number of boundary edges. + hullsize++; + } + MakeTriangle(ref newbotright); + + // Set the vertices of changed and new triangles. + rightvertex = horiz.Org(); + leftvertex = horiz.Dest(); + botvertex = horiz.Apex(); + newbotright.SetOrg(botvertex); + newbotright.SetDest(rightvertex); + newbotright.SetApex(newvertex); + horiz.SetOrg(newvertex); + + // Set the region of a new triangle. + newbotright.triangle.region = botright.triangle.region; + + if (behavior.VarArea) + { + // Set the area constraint of a new triangle. + newbotright.triangle.area = botright.triangle.area; + } + + if (mirrorflag) + { + topvertex = topright.Dest(); + newtopright.SetOrg(rightvertex); + newtopright.SetDest(topvertex); + newtopright.SetApex(newvertex); + topright.SetOrg(newvertex); + + // Set the region of another new triangle. + newtopright.triangle.region = topright.triangle.region; + + if (behavior.VarArea) + { + // Set the area constraint of another new triangle. + newtopright.triangle.area = topright.triangle.area; + } + } + + // There may be subsegments that need to be bonded + // to the new triangle(s). + if (checksegments) + { + botright.SegPivot(ref botrsubseg); + + if (botrsubseg.seg != dummysub) + { + botright.SegDissolve(); + newbotright.SegBond(ref botrsubseg); + } + + if (mirrorflag) + { + topright.SegPivot(ref toprsubseg); + if (toprsubseg.seg != dummysub) + { + topright.SegDissolve(); + newtopright.SegBond(ref toprsubseg); + } + } + } + + // Bond the new triangle(s) to the surrounding triangles. + newbotright.Bond(ref botrcasing); + newbotright.LprevSelf(); + newbotright.Bond(ref botright); + newbotright.LprevSelf(); + + if (mirrorflag) + { + newtopright.Bond(ref toprcasing); + newtopright.LnextSelf(); + newtopright.Bond(ref topright); + newtopright.LnextSelf(); + newtopright.Bond(ref newbotright); + } + + if (splitseg.seg != null) + { + // Split the subsegment into two. + splitseg.SetDest(newvertex); + segmentorg = splitseg.SegOrg(); + segmentdest = splitseg.SegDest(); + splitseg.SymSelf(); + splitseg.Pivot(ref rightsubseg); + InsertSubseg(ref newbotright, splitseg.seg.boundary); + newbotright.SegPivot(ref newsubseg); + newsubseg.SetSegOrg(segmentorg); + newsubseg.SetSegDest(segmentdest); + splitseg.Bond(ref newsubseg); + newsubseg.SymSelf(); + newsubseg.Bond(ref rightsubseg); + splitseg.SymSelf(); + + // Transfer the subsegment's boundary marker to the vertex if required. + if (newvertex.mark == 0) + { + newvertex.mark = splitseg.seg.boundary; + } + } + + if (checkquality) + { + flipstack.Clear(); + + flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) + flipstack.Push(horiz); + } + + // Position 'horiz' on the first edge to check for + // the Delaunay property. + horiz.LnextSelf(); + } + else + { + // Insert the vertex in a triangle, splitting it into three. + horiz.Lnext(ref botleft); + horiz.Lprev(ref botright); + botleft.Sym(ref botlcasing); + botright.Sym(ref botrcasing); + MakeTriangle(ref newbotleft); + MakeTriangle(ref newbotright); + + // Set the vertices of changed and new triangles. + rightvertex = horiz.Org(); + leftvertex = horiz.Dest(); + botvertex = horiz.Apex(); + newbotleft.SetOrg(leftvertex); + newbotleft.SetDest(botvertex); + newbotleft.SetApex(newvertex); + newbotright.SetOrg(botvertex); + newbotright.SetDest(rightvertex); + newbotright.SetApex(newvertex); + horiz.SetApex(newvertex); + + // Set the region of the new triangles. + newbotleft.triangle.region = horiz.triangle.region; + newbotright.triangle.region = horiz.triangle.region; + + if (behavior.VarArea) + { + // Set the area constraint of the new triangles. + area = horiz.triangle.area; + newbotleft.triangle.area = area; + newbotright.triangle.area = area; + } + + // There may be subsegments that need to be bonded + // to the new triangles. + if (checksegments) + { + botleft.SegPivot(ref botlsubseg); + if (botlsubseg.seg != dummysub) + { + botleft.SegDissolve(); + newbotleft.SegBond(ref botlsubseg); + } + botright.SegPivot(ref botrsubseg); + if (botrsubseg.seg != dummysub) + { + botright.SegDissolve(); + newbotright.SegBond(ref botrsubseg); + } + } + + // Bond the new triangles to the surrounding triangles. + newbotleft.Bond(ref botlcasing); + newbotright.Bond(ref botrcasing); + newbotleft.LnextSelf(); + newbotright.LprevSelf(); + newbotleft.Bond(ref newbotright); + newbotleft.LnextSelf(); + botleft.Bond(ref newbotleft); + newbotright.LprevSelf(); + botright.Bond(ref newbotright); + + if (checkquality) + { + flipstack.Clear(); + flipstack.Push(horiz); + } + } + + // The insertion is successful by default, unless an encroached + // subsegment is found. + success = InsertVertexResult.Successful; + // Circle around the newly inserted vertex, checking each edge opposite it + // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is + // always the edge being checked. 'first' marks where to stop circling. + first = horiz.Org(); + rightvertex = first; + leftvertex = horiz.Dest(); + // Circle until finished. + while (true) + { + // By default, the edge will be flipped. + doflip = true; + + if (checksegments) + { + // Check for a subsegment, which cannot be flipped. + horiz.SegPivot(ref checksubseg); + if (checksubseg.seg != dummysub) + { + // The edge is a subsegment and cannot be flipped. + doflip = false; + + if (segmentflaws) + { + // Does the new vertex encroach upon this subsegment? + if (quality.CheckSeg4Encroach(ref checksubseg) > 0) + { + success = InsertVertexResult.Encroaching; + } + } + } + } + + if (doflip) + { + // Check if the edge is a boundary edge. + horiz.Sym(ref top); + if (top.triangle == dummytri) + { + // The edge is a boundary edge and cannot be flipped. + doflip = false; + } + else + { + // Find the vertex on the other side of the edge. + farvertex = top.Apex(); + // In the incremental Delaunay triangulation algorithm, any of + // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices + // of the triangular bounding box. These vertices must be + // treated as if they are infinitely distant, even though their + // "coordinates" are not. + if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || + (leftvertex == infvertex3)) + { + // 'leftvertex' is infinitely distant. Check the convexity of + // the boundary of the triangulation. 'farvertex' might be + // infinite as well, but trust me, this same condition should + // be applied. + doflip = Primitives.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; + } + else if ((rightvertex == infvertex1) || + (rightvertex == infvertex2) || + (rightvertex == infvertex3)) + { + // 'rightvertex' is infinitely distant. Check the convexity of + // the boundary of the triangulation. 'farvertex' might be + // infinite as well, but trust me, this same condition should + // be applied. + doflip = Primitives.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; + } + else if ((farvertex == infvertex1) || + (farvertex == infvertex2) || + (farvertex == infvertex3)) + { + // 'farvertex' is infinitely distant and cannot be inside + // the circumcircle of the triangle 'horiz'. + doflip = false; + } + else + { + // Test whether the edge is locally Delaunay. + doflip = Primitives.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; + } + if (doflip) + { + // We made it! Flip the edge 'horiz' by rotating its containing + // quadrilateral (the two triangles adjacent to 'horiz'). + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + horiz.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + horiz.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn counterclockwise. + topleft.Bond(ref botlcasing); + botleft.Bond(ref botrcasing); + botright.Bond(ref toprcasing); + topright.Bond(ref toplcasing); + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.SegPivot(ref toplsubseg); + botleft.SegPivot(ref botlsubseg); + botright.SegPivot(ref botrsubseg); + topright.SegPivot(ref toprsubseg); + if (toplsubseg.seg == dummysub) + { + topright.SegDissolve(); + } + else + { + topright.SegBond(ref toplsubseg); + } + if (botlsubseg.seg == dummysub) + { + topleft.SegDissolve(); + } + else + { + topleft.SegBond(ref botlsubseg); + } + if (botrsubseg.seg == dummysub) + { + botleft.SegDissolve(); + } + else + { + botleft.SegBond(ref botrsubseg); + } + if (toprsubseg.seg == dummysub) + { + botright.SegDissolve(); + } + else + { + botright.SegBond(ref toprsubseg); + } + } + // New vertex assignments for the rotated quadrilateral. + horiz.SetOrg(farvertex); + horiz.SetDest(newvertex); + horiz.SetApex(rightvertex); + top.SetOrg(newvertex); + top.SetDest(farvertex); + top.SetApex(leftvertex); + + // Assign region. + // TODO: check region ok (no Math.Min necessary) + region = Math.Min(top.triangle.region, horiz.triangle.region); + top.triangle.region = region; + horiz.triangle.region = region; + + if (behavior.VarArea) + { + if ((top.triangle.area <= 0.0) || (horiz.triangle.area <= 0.0)) + { + area = -1.0; + } + else + { + // Take the average of the two triangles' area constraints. + // This prevents small area constraints from migrating a + // long, long way from their original location due to flips. + area = 0.5 * (top.triangle.area + horiz.triangle.area); + } + + top.triangle.area = area; + horiz.triangle.area = area; + } + + if (checkquality) + { + flipstack.Push(horiz); + } + + // On the next iterations, consider the two edges that were exposed (this + // is, are now visible to the newly inserted vertex) by the edge flip. + horiz.LprevSelf(); + leftvertex = farvertex; + } + } + } + if (!doflip) + { + // The handle 'horiz' is accepted as locally Delaunay. + if (triflaws) + { + // Check the triangle 'horiz' for quality. + quality.TestTriangle(ref horiz); + } + + // Look for the next edge around the newly inserted vertex. + horiz.LnextSelf(); + horiz.Sym(ref testtri); + // Check for finishing a complete revolution about the new vertex, or + // falling outside of the triangulation. The latter will happen when + // a vertex is inserted at a boundary. + if ((leftvertex == first) || (testtri.triangle == dummytri)) + { + // We're done. Return a triangle whose origin is the new vertex. + horiz.Lnext(ref searchtri); + + Otri recenttri = default(Otri); + horiz.Lnext(ref recenttri); + locator.Update(ref recenttri); + + return success; + } + // Finish finding the next edge around the newly inserted vertex. + testtri.Lnext(ref horiz); + rightvertex = leftvertex; + leftvertex = horiz.Dest(); + } + } + } + + /// + /// Create a new subsegment and inserts it between two triangles. Its + /// vertices are properly initialized. + /// + /// The new subsegment is inserted at the edge + /// described by this handle. + /// The marker 'subsegmark' is applied to the + /// subsegment and, if appropriate, its vertices. + internal void InsertSubseg(ref Otri tri, int subsegmark) + { + Otri oppotri = default(Otri); + Osub newsubseg = default(Osub); + Vertex triorg, tridest; + + triorg = tri.Org(); + tridest = tri.Dest(); + // Mark vertices if possible. + if (triorg.mark == 0) + { + triorg.mark = subsegmark; + } + if (tridest.mark == 0) + { + tridest.mark = subsegmark; + } + // Check if there's already a subsegment here. + tri.SegPivot(ref newsubseg); + if (newsubseg.seg == dummysub) + { + // Make new subsegment and initialize its vertices. + MakeSegment(ref newsubseg); + newsubseg.SetOrg(tridest); + newsubseg.SetDest(triorg); + newsubseg.SetSegOrg(tridest); + newsubseg.SetSegDest(triorg); + // Bond new subsegment to the two triangles it is sandwiched between. + // Note that the facing triangle 'oppotri' might be equal to 'dummytri' + // (outer space), but the new subsegment is bonded to it all the same. + tri.SegBond(ref newsubseg); + tri.Sym(ref oppotri); + newsubseg.SymSelf(); + oppotri.SegBond(ref newsubseg); + newsubseg.seg.boundary = subsegmark; + } + else + { + if (newsubseg.seg.boundary == 0) + { + newsubseg.seg.boundary = subsegmark; + } + } + } + + /// + /// Transform two triangles to two different triangles by flipping an edge + /// counterclockwise within a quadrilateral. + /// + /// Handle to the edge that will be flipped. + /// Imagine the original triangles, abc and bad, oriented so that the + /// shared edge ab lies in a horizontal plane, with the vertex b on the left + /// and the vertex a on the right. The vertex c lies below the edge, and + /// the vertex d lies above the edge. The 'flipedge' handle holds the edge + /// ab of triangle abc, and is directed left, from vertex a to vertex b. + /// + /// The triangles abc and bad are deleted and replaced by the triangles cdb + /// and dca. The triangles that represent abc and bad are NOT deallocated; + /// they are reused for dca and cdb, respectively. Hence, any handles that + /// may have held the original triangles are still valid, although not + /// directed as they were before. + /// + /// Upon completion of this routine, the 'flipedge' handle holds the edge + /// dc of triangle dca, and is directed down, from vertex d to vertex c. + /// (Hence, the two triangles have rotated counterclockwise.) + /// + /// WARNING: This transformation is geometrically valid only if the + /// quadrilateral adbc is convex. Furthermore, this transformation is + /// valid only if there is not a subsegment between the triangles abc and + /// bad. This routine does not check either of these preconditions, and + /// it is the responsibility of the calling routine to ensure that they are + /// met. If they are not, the streets shall be filled with wailing and + /// gnashing of teeth. + /// + /// Terminology + /// + /// A "local transformation" replaces a small set of triangles with another + /// set of triangles. This may or may not involve inserting or deleting a + /// vertex. + /// + /// The term "casing" is used to describe the set of triangles that are + /// attached to the triangles being transformed, but are not transformed + /// themselves. Think of the casing as a fixed hollow structure inside + /// which all the action happens. A "casing" is only defined relative to + /// a single transformation; each occurrence of a transformation will + /// involve a different casing. + /// + internal void Flip(ref Otri flipedge) + { + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri top = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Vertex leftvertex, rightvertex, botvertex; + Vertex farvertex; + + // Identify the vertices of the quadrilateral. + rightvertex = flipedge.Org(); + leftvertex = flipedge.Dest(); + botvertex = flipedge.Apex(); + flipedge.Sym(ref top); + + // SELF CHECK + + //if (top.triangle == dummytri) + //{ + // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); + // flipedge.LnextSelf(); + // return; + //} + + //if (checksegments) + //{ + // flipedge.SegPivot(ref toplsubseg); + // if (toplsubseg.ss != dummysub) + // { + // logger.Error("Attempt to flip a segment.", "Mesh.Flip()"); + // flipedge.LnextSelf(); + // return; + // } + //} + + farvertex = top.Apex(); + + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + flipedge.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + flipedge.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn counterclockwise. + topleft.Bond(ref botlcasing); + botleft.Bond(ref botrcasing); + botright.Bond(ref toprcasing); + topright.Bond(ref toplcasing); + + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.SegPivot(ref toplsubseg); + botleft.SegPivot(ref botlsubseg); + botright.SegPivot(ref botrsubseg); + topright.SegPivot(ref toprsubseg); + + if (toplsubseg.seg == Mesh.dummysub) + { + topright.SegDissolve(); + } + else + { + topright.SegBond(ref toplsubseg); + } + + if (botlsubseg.seg == Mesh.dummysub) + { + topleft.SegDissolve(); + } + else + { + topleft.SegBond(ref botlsubseg); + } + + if (botrsubseg.seg == Mesh.dummysub) + { + botleft.SegDissolve(); + } + else + { + botleft.SegBond(ref botrsubseg); + } + + if (toprsubseg.seg == Mesh.dummysub) + { + botright.SegDissolve(); + } + else + { + botright.SegBond(ref toprsubseg); + } + } + + // New vertex assignments for the rotated quadrilateral. + flipedge.SetOrg(farvertex); + flipedge.SetDest(botvertex); + flipedge.SetApex(rightvertex); + top.SetOrg(botvertex); + top.SetDest(farvertex); + top.SetApex(leftvertex); + } + + /// + /// Transform two triangles to two different triangles by flipping an edge + /// clockwise within a quadrilateral. Reverses the flip() operation so that + /// the data structures representing the triangles are back where they were + /// before the flip(). + /// + /// + /// + /// See above Flip() remarks for more information. + /// + /// Upon completion of this routine, the 'flipedge' handle holds the edge + /// cd of triangle cdb, and is directed up, from vertex c to vertex d. + /// (Hence, the two triangles have rotated clockwise.) + /// + internal void Unflip(ref Otri flipedge) + { + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri top = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Vertex leftvertex, rightvertex, botvertex; + Vertex farvertex; + + // Identify the vertices of the quadrilateral. + rightvertex = flipedge.Org(); + leftvertex = flipedge.Dest(); + botvertex = flipedge.Apex(); + flipedge.Sym(ref top); + + farvertex = top.Apex(); + + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + flipedge.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + flipedge.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn clockwise. + topleft.Bond(ref toprcasing); + botleft.Bond(ref toplcasing); + botright.Bond(ref botlcasing); + topright.Bond(ref botrcasing); + + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.SegPivot(ref toplsubseg); + botleft.SegPivot(ref botlsubseg); + botright.SegPivot(ref botrsubseg); + topright.SegPivot(ref toprsubseg); + if (toplsubseg.seg == Mesh.dummysub) + { + botleft.SegDissolve(); + } + else + { + botleft.SegBond(ref toplsubseg); + } + if (botlsubseg.seg == Mesh.dummysub) + { + botright.SegDissolve(); + } + else + { + botright.SegBond(ref botlsubseg); + } + if (botrsubseg.seg == Mesh.dummysub) + { + topright.SegDissolve(); + } + else + { + topright.SegBond(ref botrsubseg); + } + if (toprsubseg.seg == Mesh.dummysub) + { + topleft.SegDissolve(); + } + else + { + topleft.SegBond(ref toprsubseg); + } + } + + // New vertex assignments for the rotated quadrilateral. + flipedge.SetOrg(botvertex); + flipedge.SetDest(farvertex); + flipedge.SetApex(leftvertex); + top.SetOrg(farvertex); + top.SetDest(botvertex); + top.SetApex(rightvertex); + } + + /// + /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. + /// This includes the polygons that result from deletion of a vertex or insertion + /// of a segment. + /// + /// The primary edge of the first triangle. + /// The primary edge of the last triangle. + /// The number of sides of the polygon, including its + /// base. + /// A flag, wether to perform the last flip. + /// A flag that determines whether the new triangles should + /// be tested for quality, and enqueued if they are bad. + /// + // This is a conceptually difficult routine. The starting assumption is + // that we have a polygon with n sides. n - 1 of these sides are currently + // represented as edges in the mesh. One side, called the "base", need not + // be. + // + // Inside the polygon is a structure I call a "fan", consisting of n - 1 + // triangles that share a common origin. For each of these triangles, the + // edge opposite the origin is one of the sides of the polygon. The + // primary edge of each triangle is the edge directed from the origin to + // the destination; note that this is not the same edge that is a side of + // the polygon. 'firstedge' is the primary edge of the first triangle. + // From there, the triangles follow in counterclockwise order about the + // polygon, until 'lastedge', the primary edge of the last triangle. + // 'firstedge' and 'lastedge' are probably connected to other triangles + // beyond the extremes of the fan, but their identity is not important, as + // long as the fan remains connected to them. + // + // Imagine the polygon oriented so that its base is at the bottom. This + // puts 'firstedge' on the far right, and 'lastedge' on the far left. + // The right vertex of the base is the destination of 'firstedge', and the + // left vertex of the base is the apex of 'lastedge'. + // + // The challenge now is to find the right sequence of edge flips to + // transform the fan into a Delaunay triangulation of the polygon. Each + // edge flip effectively removes one triangle from the fan, committing it + // to the polygon. The resulting polygon has one fewer edge. If 'doflip' + // is set, the final flip will be performed, resulting in a fan of one + // (useless?) triangle. If 'doflip' is not set, the final flip is not + // performed, resulting in a fan of two triangles, and an unfinished + // triangular polygon that is not yet filled out with a single triangle. + // On completion of the routine, 'lastedge' is the last remaining triangle, + // or the leftmost of the last two. + // + // Although the flips are performed in the order described above, the + // decisions about what flips to perform are made in precisely the reverse + // order. The recursive triangulatepolygon() procedure makes a decision, + // uses up to two recursive calls to triangulate the "subproblems" + // (polygons with fewer edges), and then performs an edge flip. + // + // The "decision" it makes is which vertex of the polygon should be + // connected to the base. This decision is made by testing every possible + // vertex. Once the best vertex is found, the two edges that connect this + // vertex to the base become the bases for two smaller polygons. These + // are triangulated recursively. Unfortunately, this approach can take + // O(n^2) time not only in the worst case, but in many common cases. It's + // rarely a big deal for vertex deletion, where n is rarely larger than + // ten, but it could be a big deal for segment insertion, especially if + // there's a lot of long segments that each cut many triangles. I ought to + // code a faster algorithm some day. + /// + private void TriangulatePolygon(Otri firstedge, Otri lastedge, + int edgecount, bool doflip, bool triflaws) + { + Otri testtri = default(Otri); + Otri besttri = default(Otri); + Otri tempedge = default(Otri); + Vertex leftbasevertex, rightbasevertex; + Vertex testvertex; + Vertex bestvertex; + + int bestnumber = 1; + + // Identify the base vertices. + leftbasevertex = lastedge.Apex(); + rightbasevertex = firstedge.Dest(); + + // Find the best vertex to connect the base to. + firstedge.Onext(ref besttri); + bestvertex = besttri.Dest(); + besttri.Copy(ref testtri); + + for (int i = 2; i <= edgecount - 2; i++) + { + testtri.OnextSelf(); + testvertex = testtri.Dest(); + // Is this a better vertex? + if (Primitives.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) + { + testtri.Copy(ref besttri); + bestvertex = testvertex; + bestnumber = i; + } + } + + if (bestnumber > 1) + { + // Recursively triangulate the smaller polygon on the right. + besttri.Oprev(ref tempedge); + TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); + } + + if (bestnumber < edgecount - 2) + { + // Recursively triangulate the smaller polygon on the left. + besttri.Sym(ref tempedge); + TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); + // Find 'besttri' again; it may have been lost to edge flips. + tempedge.Sym(ref besttri); + } + + if (doflip) + { + // Do one final edge flip. + Flip(ref besttri); + if (triflaws) + { + // Check the quality of the newly committed triangle. + besttri.Sym(ref testtri); + quality.TestTriangle(ref testtri); + } + } + // Return the base triangle. + besttri.Copy(ref lastedge); + } + + /// + /// Delete a vertex from a Delaunay triangulation, ensuring that the + /// triangulation remains Delaunay. + /// + /// + /// The origin of 'deltri' is deleted. The union of the triangles + /// adjacent to this vertex is a polygon, for which the Delaunay triangulation + /// is found. Two triangles are removed from the mesh. + /// + /// Only interior vertices that do not lie on segments or boundaries + /// may be deleted. + /// + internal void DeleteVertex(ref Otri deltri) + { + Otri countingtri = default(Otri); + Otri firstedge = default(Otri), lastedge = default(Otri); + Otri deltriright = default(Otri); + Otri lefttri = default(Otri), righttri = default(Otri); + Otri leftcasing = default(Otri), rightcasing = default(Otri); + Osub leftsubseg = default(Osub), rightsubseg = default(Osub); + Vertex delvertex; + Vertex neworg; + int edgecount; + + delvertex = deltri.Org(); + + VertexDealloc(delvertex); + + // Count the degree of the vertex being deleted. + deltri.Onext(ref countingtri); + edgecount = 1; + while (!deltri.Equal(countingtri)) + { + edgecount++; + countingtri.OnextSelf(); + } + + if (edgecount > 3) + { + // Triangulate the polygon defined by the union of all triangles + // adjacent to the vertex being deleted. Check the quality of + // the resulting triangles. + deltri.Onext(ref firstedge); + deltri.Oprev(ref lastedge); + TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); + } + // Splice out two triangles. + deltri.Lprev(ref deltriright); + deltri.Dnext(ref lefttri); + lefttri.Sym(ref leftcasing); + deltriright.Oprev(ref righttri); + righttri.Sym(ref rightcasing); + deltri.Bond(ref leftcasing); + deltriright.Bond(ref rightcasing); + lefttri.SegPivot(ref leftsubseg); + if (leftsubseg.seg != Mesh.dummysub) + { + deltri.SegBond(ref leftsubseg); + } + righttri.SegPivot(ref rightsubseg); + if (rightsubseg.seg != Mesh.dummysub) + { + deltriright.SegBond(ref rightsubseg); + } + + // Set the new origin of 'deltri' and check its quality. + neworg = lefttri.Org(); + deltri.SetOrg(neworg); + if (behavior.NoBisect == 0) + { + quality.TestTriangle(ref deltri); + } + + // Delete the two spliced-out triangles. + TriangleDealloc(lefttri.triangle); + TriangleDealloc(righttri.triangle); + } + + /// + /// Undo the most recent vertex insertion. + /// + /// + /// Walks through the list of transformations (flips and a vertex insertion) + /// in the reverse of the order in which they were done, and undoes them. + /// The inserted vertex is removed from the triangulation and deallocated. + /// Two triangles (possibly just one) are also deallocated. + /// + internal void UndoVertex() + { + Otri fliptri; + + Otri botleft = default(Otri), botright = default(Otri), topright = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri), toprcasing = default(Otri); + Otri gluetri = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub), toprsubseg = default(Osub); + Vertex botvertex, rightvertex; + + // Walk through the list of transformations (flips and a vertex insertion) + // in the reverse of the order in which they were done, and undo them. + while (flipstack.Count > 0) + { + // Find a triangle involved in the last unreversed transformation. + fliptri = flipstack.Pop(); + + // We are reversing one of three transformations: a trisection of one + // triangle into three (by inserting a vertex in the triangle), a + // bisection of two triangles into four (by inserting a vertex in an + // edge), or an edge flip. + if (flipstack.Count == 0) + { + // Restore a triangle that was split into three triangles, + // so it is again one triangle. + fliptri.Dprev(ref botleft); + botleft.LnextSelf(); + fliptri.Onext(ref botright); + botright.LprevSelf(); + botleft.Sym(ref botlcasing); + botright.Sym(ref botrcasing); + botvertex = botleft.Dest(); + + fliptri.SetApex(botvertex); + fliptri.LnextSelf(); + fliptri.Bond(ref botlcasing); + botleft.SegPivot(ref botlsubseg); + fliptri.SegBond(ref botlsubseg); + fliptri.LnextSelf(); + fliptri.Bond(ref botrcasing); + botright.SegPivot(ref botrsubseg); + fliptri.SegBond(ref botrsubseg); + + // Delete the two spliced-out triangles. + TriangleDealloc(botleft.triangle); + TriangleDealloc(botright.triangle); + } + else if (flipstack.Peek().triangle == null) // Dummy flip + { + // Restore two triangles that were split into four triangles, + // so they are again two triangles. + fliptri.Lprev(ref gluetri); + gluetri.Sym(ref botright); + botright.LnextSelf(); + botright.Sym(ref botrcasing); + rightvertex = botright.Dest(); + + fliptri.SetOrg(rightvertex); + gluetri.Bond(ref botrcasing); + botright.SegPivot(ref botrsubseg); + gluetri.SegBond(ref botrsubseg); + + // Delete the spliced-out triangle. + TriangleDealloc(botright.triangle); + + fliptri.Sym(ref gluetri); + if (gluetri.triangle != Mesh.dummytri) + { + gluetri.LnextSelf(); + gluetri.Dnext(ref topright); + topright.Sym(ref toprcasing); + + gluetri.SetOrg(rightvertex); + gluetri.Bond(ref toprcasing); + topright.SegPivot(ref toprsubseg); + gluetri.SegBond(ref toprsubseg); + + // Delete the spliced-out triangle. + TriangleDealloc(topright.triangle); + } + + flipstack.Clear(); + } + else + { + // Undo an edge flip. + Unflip(ref fliptri); + } + } + } + + #endregion + + #region Segment insertion + + /// + /// Find the first triangle on the path from one point to another. + /// + /// + /// + /// + /// The return value notes whether the destination or apex of the found + /// triangle is collinear with the two points in question. + /// + /// Finds the triangle that intersects a line segment drawn from the + /// origin of 'searchtri' to the point 'searchpoint', and returns the result + /// in 'searchtri'. The origin of 'searchtri' does not change, even though + /// the triangle returned may differ from the one passed in. This routine + /// is used to find the direction to move in to get from one point to + /// another. + /// + private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint) + { + Otri checktri = default(Otri); + Vertex startvertex; + Vertex leftvertex, rightvertex; + double leftccw, rightccw; + bool leftflag, rightflag; + + startvertex = searchtri.Org(); + rightvertex = searchtri.Dest(); + leftvertex = searchtri.Apex(); + // Is 'searchpoint' to the left? + leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex); + leftflag = leftccw > 0.0; + // Is 'searchpoint' to the right? + rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex); + rightflag = rightccw > 0.0; + if (leftflag && rightflag) + { + // 'searchtri' faces directly away from 'searchpoint'. We could go left + // or right. Ask whether it's a triangle or a boundary on the left. + searchtri.Onext(ref checktri); + if (checktri.triangle == Mesh.dummytri) + { + leftflag = false; + } + else + { + rightflag = false; + } + } + while (leftflag) + { + // Turn left until satisfied. + searchtri.OnextSelf(); + if (searchtri.triangle == Mesh.dummytri) + { + logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1"); + throw new Exception("Unable to find a triangle on path."); + } + leftvertex = searchtri.Apex(); + rightccw = leftccw; + leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex); + leftflag = leftccw > 0.0; + } + while (rightflag) + { + // Turn right until satisfied. + searchtri.OprevSelf(); + if (searchtri.triangle == Mesh.dummytri) + { + logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2"); + throw new Exception("Unable to find a triangle on path."); + } + rightvertex = searchtri.Dest(); + leftccw = rightccw; + rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex); + rightflag = rightccw > 0.0; + } + if (leftccw == 0.0) + { + return FindDirectionResult.Leftcollinear; + } + else if (rightccw == 0.0) + { + return FindDirectionResult.Rightcollinear; + } + else + { + return FindDirectionResult.Within; + } + } + + /// + /// Find the intersection of an existing segment and a segment that is being + /// inserted. Insert a vertex at the intersection, splitting an existing subsegment. + /// + /// + /// + /// + /// + /// The segment being inserted connects the apex of splittri to endpoint2. + /// splitsubseg is the subsegment being split, and MUST adjoin splittri. + /// Hence, endpoints of the subsegment being split are the origin and + /// destination of splittri. + /// + /// On completion, splittri is a handle having the newly inserted + /// intersection point as its origin, and endpoint1 as its destination. + /// + private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2) + { + Osub opposubseg = default(Osub); + Vertex endpoint1; + Vertex torg, tdest; + Vertex leftvertex, rightvertex; + Vertex newvertex; + InsertVertexResult success; + + double ex, ey; + double tx, ty; + double etx, ety; + double split, denom; + + // Find the other three segment endpoints. + endpoint1 = splittri.Apex(); + torg = splittri.Org(); + tdest = splittri.Dest(); + // Segment intersection formulae; see the Antonio reference. + tx = tdest.x - torg.x; + ty = tdest.y - torg.y; + ex = endpoint2.x - endpoint1.x; + ey = endpoint2.y - endpoint1.y; + etx = torg.x - endpoint2.x; + ety = torg.y - endpoint2.y; + denom = ty * ex - tx * ey; + if (denom == 0.0) + { + logger.Error("Attempt to find intersection of parallel segments.", + "Mesh.SegmentIntersection()"); + throw new Exception("Attempt to find intersection of parallel segments."); + } + split = (ey * etx - ex * ety) / denom; + + // Create the new vertex. + newvertex = new Vertex( + torg.x + split * (tdest.x - torg.x), + torg.y + split * (tdest.y - torg.y), + splitsubseg.seg.boundary, + this.nextras); + + newvertex.hash = this.hash_vtx++; + newvertex.id = newvertex.hash; + + // Interpolate its attributes. + for (int i = 0; i < nextras; i++) + { + newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]); + } + + vertices.Add(newvertex.hash, newvertex); + + // Insert the intersection vertex. This should always succeed. + success = InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false); + if (success != InsertVertexResult.Successful) + { + logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()"); + throw new Exception("Failure to split a segment."); + } + // Record a triangle whose origin is the new vertex. + newvertex.tri = splittri; + if (steinerleft > 0) + { + steinerleft--; + } + + // Divide the segment into two, and correct the segment endpoints. + splitsubseg.SymSelf(); + splitsubseg.Pivot(ref opposubseg); + splitsubseg.Dissolve(); + opposubseg.Dissolve(); + do + { + splitsubseg.SetSegOrg(newvertex); + splitsubseg.NextSelf(); + } while (splitsubseg.seg != Mesh.dummysub); + do + { + opposubseg.SetSegOrg(newvertex); + opposubseg.NextSelf(); + } while (opposubseg.seg != Mesh.dummysub); + + // Inserting the vertex may have caused edge flips. We wish to rediscover + // the edge connecting endpoint1 to the new intersection vertex. + FindDirection(ref splittri, endpoint1); + + rightvertex = splittri.Dest(); + leftvertex = splittri.Apex(); + if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y)) + { + splittri.OnextSelf(); + } + else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y)) + { + logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()"); + throw new Exception("Topological inconsistency after splitting a segment."); + } + // 'splittri' should have destination endpoint1. + } + + /// + /// Scout the first triangle on the path from one endpoint to another, and check + /// for completion (reaching the second endpoint), a collinear vertex, or the + /// intersection of two segments. + /// + /// + /// + /// + /// Returns true if the entire segment is successfully inserted, and false + /// if the job must be finished by ConstrainedEdge(). + /// + /// If the first triangle on the path has the second endpoint as its + /// destination or apex, a subsegment is inserted and the job is done. + /// + /// If the first triangle on the path has a destination or apex that lies on + /// the segment, a subsegment is inserted connecting the first endpoint to + /// the collinear vertex, and the search is continued from the collinear + /// vertex. + /// + /// If the first triangle on the path has a subsegment opposite its origin, + /// then there is a segment that intersects the segment being inserted. + /// Their intersection vertex is inserted, splitting the subsegment. + /// + private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark) + { + Otri crosstri = default(Otri); + Osub crosssubseg = default(Osub); + Vertex leftvertex, rightvertex; + FindDirectionResult collinear; + + collinear = FindDirection(ref searchtri, endpoint2); + rightvertex = searchtri.Dest(); + leftvertex = searchtri.Apex(); + if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) || + ((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y))) + { + // The segment is already an edge in the mesh. + if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) + { + searchtri.LprevSelf(); + } + // Insert a subsegment, if there isn't already one there. + InsertSubseg(ref searchtri, newmark); + return true; + } + else if (collinear == FindDirectionResult.Leftcollinear) + { + // We've collided with a vertex between the segment's endpoints. + // Make the collinear vertex be the triangle's origin. + searchtri.LprevSelf(); + InsertSubseg(ref searchtri, newmark); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + else if (collinear == FindDirectionResult.Rightcollinear) + { + // We've collided with a vertex between the segment's endpoints. + InsertSubseg(ref searchtri, newmark); + // Make the collinear vertex be the triangle's origin. + searchtri.LnextSelf(); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + else + { + searchtri.Lnext(ref crosstri); + crosstri.SegPivot(ref crosssubseg); + // Check for a crossing segment. + if (crosssubseg.seg == Mesh.dummysub) + { + return false; + } + else + { + // Insert a vertex at the intersection. + SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2); + crosstri.Copy(ref searchtri); + InsertSubseg(ref searchtri, newmark); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + } + } + + /// + /// Enforce the Delaunay condition at an edge, fanning out recursively from + /// an existing vertex. Pay special attention to stacking inverted triangles. + /// + /// + /// Indicates whether or not fixuptri is to the left of + /// the segment being inserted. (Imagine that the segment is pointing up from + /// endpoint1 to endpoint2.) + /// + /// This is a support routine for inserting segments into a constrained + /// Delaunay triangulation. + /// + /// The origin of fixuptri is treated as if it has just been inserted, and + /// the local Delaunay condition needs to be enforced. It is only enforced + /// in one sector, however, that being the angular range defined by + /// fixuptri. + /// + /// This routine also needs to make decisions regarding the "stacking" of + /// triangles. (Read the description of ConstrainedEdge() below before + /// reading on here, so you understand the algorithm.) If the position of + /// the new vertex (the origin of fixuptri) indicates that the vertex before + /// it on the polygon is a reflex vertex, then "stack" the triangle by + /// doing nothing. (fixuptri is an inverted triangle, which is how stacked + /// triangles are identified.) + /// + /// Otherwise, check whether the vertex before that was a reflex vertex. + /// If so, perform an edge flip, thereby eliminating an inverted triangle + /// (popping it off the stack). The edge flip may result in the creation + /// of a new inverted triangle, depending on whether or not the new vertex + /// is visible to the vertex three edges behind on the polygon. + /// + /// If neither of the two vertices behind the new vertex are reflex + /// vertices, fixuptri and fartri, the triangle opposite it, are not + /// inverted; hence, ensure that the edge between them is locally Delaunay. + /// + private void DelaunayFixup(ref Otri fixuptri, bool leftside) + { + Otri neartri = default(Otri); + Otri fartri = default(Otri); + Osub faredge = default(Osub); + Vertex nearvertex, leftvertex, rightvertex, farvertex; + + fixuptri.Lnext(ref neartri); + neartri.Sym(ref fartri); + // Check if the edge opposite the origin of fixuptri can be flipped. + if (fartri.triangle == Mesh.dummytri) + { + return; + } + neartri.SegPivot(ref faredge); + if (faredge.seg != Mesh.dummysub) + { + return; + } + // Find all the relevant vertices. + nearvertex = neartri.Apex(); + leftvertex = neartri.Org(); + rightvertex = neartri.Dest(); + farvertex = fartri.Apex(); + // Check whether the previous polygon vertex is a reflex vertex. + if (leftside) + { + if (Primitives.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) + { + // leftvertex is a reflex vertex too. Nothing can + // be done until a convex section is found. + return; + } + } + else + { + if (Primitives.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) + { + // rightvertex is a reflex vertex too. Nothing can + // be done until a convex section is found. + return; + } + } + if (Primitives.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) + { + // fartri is not an inverted triangle, and farvertex is not a reflex + // vertex. As there are no reflex vertices, fixuptri isn't an + // inverted triangle, either. Hence, test the edge between the + // triangles to ensure it is locally Delaunay. + if (Primitives.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) + { + return; + } + // Not locally Delaunay; go on to an edge flip. + } + // else fartri is inverted; remove it from the stack by flipping. + Flip(ref neartri); + fixuptri.LprevSelf(); // Restore the origin of fixuptri after the flip. + // Recursively process the two triangles that result from the flip. + DelaunayFixup(ref fixuptri, leftside); + DelaunayFixup(ref fartri, leftside); + } + + /// + /// Force a segment into a constrained Delaunay triangulation by deleting the + /// triangles it intersects, and triangulating the polygons that form on each + /// side of it. + /// + /// + /// + /// + /// + /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'. + /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the + /// boundary marker of the segment. + /// + /// To insert a segment, every triangle whose interior intersects the + /// segment is deleted. The union of these deleted triangles is a polygon + /// (which is not necessarily monotone, but is close enough), which is + /// divided into two polygons by the new segment. This routine's task is + /// to generate the Delaunay triangulation of these two polygons. + /// + /// You might think of this routine's behavior as a two-step process. The + /// first step is to walk from endpoint1 to endpoint2, flipping each edge + /// encountered. This step creates a fan of edges connected to endpoint1, + /// including the desired edge to endpoint2. The second step enforces the + /// Delaunay condition on each side of the segment in an incremental manner: + /// proceeding along the polygon from endpoint1 to endpoint2 (this is done + /// independently on each side of the segment), each vertex is "enforced" + /// as if it had just been inserted, but affecting only the previous + /// vertices. The result is the same as if the vertices had been inserted + /// in the order they appear on the polygon, so the result is Delaunay. + /// + /// In truth, ConstrainedEdge() interleaves these two steps. The procedure + /// walks from endpoint1 to endpoint2, and each time an edge is encountered + /// and flipped, the newly exposed vertex (at the far end of the flipped + /// edge) is "enforced" upon the previously flipped edges, usually affecting + /// only one side of the polygon (depending upon which side of the segment + /// the vertex falls on). + /// + /// The algorithm is complicated by the need to handle polygons that are not + /// convex. Although the polygon is not necessarily monotone, it can be + /// triangulated in a manner similar to the stack-based algorithms for + /// monotone polygons. For each reflex vertex (local concavity) of the + /// polygon, there will be an inverted triangle formed by one of the edge + /// flips. (An inverted triangle is one with negative area - that is, its + /// vertices are arranged in clockwise order - and is best thought of as a + /// wrinkle in the fabric of the mesh.) Each inverted triangle can be + /// thought of as a reflex vertex pushed on the stack, waiting to be fixed + /// later. + /// + /// A reflex vertex is popped from the stack when a vertex is inserted that + /// is visible to the reflex vertex. (However, if the vertex behind the + /// reflex vertex is not visible to the reflex vertex, a new inverted + /// triangle will take its place on the stack.) These details are handled + /// by the DelaunayFixup() routine above. + /// + private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark) + { + Otri fixuptri = default(Otri), fixuptri2 = default(Otri); + Osub crosssubseg = default(Osub); + Vertex endpoint1; + Vertex farvertex; + double area; + bool collision; + bool done; + + endpoint1 = starttri.Org(); + starttri.Lnext(ref fixuptri); + Flip(ref fixuptri); + // 'collision' indicates whether we have found a vertex directly + // between endpoint1 and endpoint2. + collision = false; + done = false; + do + { + farvertex = fixuptri.Org(); + // 'farvertex' is the extreme point of the polygon we are "digging" + // to get from endpoint1 to endpoint2. + if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y)) + { + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around endpoint2. + DelaunayFixup(ref fixuptri, false); + DelaunayFixup(ref fixuptri2, true); + done = true; + } + else + { + // Check whether farvertex is to the left or right of the segment being + // inserted, to decide which edge of fixuptri to dig through next. + area = Primitives.CounterClockwise(endpoint1, endpoint2, farvertex); + if (area == 0.0) + { + // We've collided with a vertex between endpoint1 and endpoint2. + collision = true; + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around farvertex. + DelaunayFixup(ref fixuptri, false); + DelaunayFixup(ref fixuptri2, true); + done = true; + } + else + { + if (area > 0.0) + { + // farvertex is to the left of the segment. + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around farvertex, on the + // left side of the segment only. + DelaunayFixup(ref fixuptri2, true); + // Flip the edge that crosses the segment. After the edge is + // flipped, one of its endpoints is the fan vertex, and the + // destination of fixuptri is the fan vertex. + fixuptri.LprevSelf(); + } + else + { + // farvertex is to the right of the segment. + DelaunayFixup(ref fixuptri, false); + // Flip the edge that crosses the segment. After the edge is + // flipped, one of its endpoints is the fan vertex, and the + // destination of fixuptri is the fan vertex. + fixuptri.OprevSelf(); + } + // Check for two intersecting segments. + fixuptri.SegPivot(ref crosssubseg); + if (crosssubseg.seg == Mesh.dummysub) + { + Flip(ref fixuptri); // May create inverted triangle at left. + } + else + { + // We've collided with a segment between endpoint1 and endpoint2. + collision = true; + // Insert a vertex at the intersection. + SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2); + done = true; + } + } + } + } while (!done); + // Insert a subsegment to make the segment permanent. + InsertSubseg(ref fixuptri, newmark); + // If there was a collision with an interceding vertex, install another + // segment connecting that vertex with endpoint2. + if (collision) + { + // Insert the remainder of the segment. + if (!ScoutSegment(ref fixuptri, endpoint2, newmark)) + { + ConstrainedEdge(ref fixuptri, endpoint2, newmark); + } + } + } + + /// + /// Insert a PSLG segment into a triangulation. + /// + /// + /// + /// + private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark) + { + Otri searchtri1 = default(Otri), searchtri2 = default(Otri); + Vertex checkvertex = null; + + // Find a triangle whose origin is the segment's first endpoint. + searchtri1 = endpoint1.tri; + if (searchtri1.triangle != null) + { + checkvertex = searchtri1.Org(); + } + + if (checkvertex != endpoint1) + { + // Find a boundary triangle to search from. + searchtri1.triangle = Mesh.dummytri; + searchtri1.orient = 0; + searchtri1.SymSelf(); + // Search for the segment's first endpoint by point location. + if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) + { + logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1"); + throw new Exception("Unable to locate PSLG vertex in triangulation."); + } + } + // Remember this triangle to improve subsequent point location. + locator.Update(ref searchtri1); + + // Scout the beginnings of a path from the first endpoint + // toward the second. + if (ScoutSegment(ref searchtri1, endpoint2, newmark)) + { + // The segment was easily inserted. + return; + } + // The first endpoint may have changed if a collision with an intervening + // vertex on the segment occurred. + endpoint1 = searchtri1.Org(); + + // Find a triangle whose origin is the segment's second endpoint. + checkvertex = null; + searchtri2 = endpoint2.tri; + if (searchtri2.triangle != null) + { + checkvertex = searchtri2.Org(); + } + if (checkvertex != endpoint2) + { + // Find a boundary triangle to search from. + searchtri2.triangle = Mesh.dummytri; + searchtri2.orient = 0; + searchtri2.SymSelf(); + // Search for the segment's second endpoint by point location. + if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) + { + logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2"); + throw new Exception("Unable to locate PSLG vertex in triangulation."); + } + } + // Remember this triangle to improve subsequent point location. + locator.Update(ref searchtri2); + // Scout the beginnings of a path from the second endpoint + // toward the first. + if (ScoutSegment(ref searchtri2, endpoint1, newmark)) + { + // The segment was easily inserted. + return; + } + // The second endpoint may have changed if a collision with an intervening + // vertex on the segment occurred. + endpoint2 = searchtri2.Org(); + + // Insert the segment directly into the triangulation. + ConstrainedEdge(ref searchtri1, endpoint2, newmark); + } + + /// + /// Cover the convex hull of a triangulation with subsegments. + /// + private void MarkHull() + { + Otri hulltri = default(Otri); + Otri nexttri = default(Otri); + Otri starttri = default(Otri); + + // Find a triangle handle on the hull. + hulltri.triangle = Mesh.dummytri; + hulltri.orient = 0; + hulltri.SymSelf(); + // Remember where we started so we know when to stop. + hulltri.Copy(ref starttri); + // Go once counterclockwise around the convex hull. + do + { + // Create a subsegment if there isn't already one here. + InsertSubseg(ref hulltri, 1); + // To find the next hull edge, go clockwise around the next vertex. + hulltri.LnextSelf(); + hulltri.Oprev(ref nexttri); + while (nexttri.triangle != Mesh.dummytri) + { + nexttri.Copy(ref hulltri); + hulltri.Oprev(ref nexttri); + } + } while (!hulltri.Equal(starttri)); + } + + /// + /// Create the segments of a triangulation, including PSLG segments and edges + /// on the convex hull. + /// + /// + /// + /// + private void FormSkeleton(InputGeometry input) + { + Vertex endpoint1, endpoint2; + int end1, end2; + int boundmarker; + + this.insegments = 0; + + if (behavior.Poly) + { + // If the input vertices are collinear, there is no triangulation, + // so don't try to insert segments. + if (triangles.Count == 0) + { + return; + } + + // If segments are to be inserted, compute a mapping + // from vertices to triangles. + if (input.HasSegments) + { + MakeVertexMap(); + } + + boundmarker = 0; + + // Read and insert the segments. + foreach (var seg in input.segments) + { + this.insegments++; + + end1 = seg.P0; + end2 = seg.P1; + boundmarker = seg.Boundary; + + if ((end1 < 0) || (end1 >= invertices)) + { + if (Behavior.Verbose) + { + logger.Warning("Invalid first endpoint of segment.", "Mesh.FormSkeleton().1"); + } + } + else if ((end2 < 0) || (end2 >= invertices)) + { + if (Behavior.Verbose) + { + logger.Warning("Invalid second endpoint of segment.", "Mesh.FormSkeleton().2"); + } + } + else + { + // TODO: Is using the vertex ID reliable??? + // It should be. The ID gets appropriately set in TransferNodes(). + + // Find the vertices numbered 'end1' and 'end2'. + endpoint1 = vertices[end1]; + endpoint2 = vertices[end2]; + if ((endpoint1.x == endpoint2.x) && (endpoint1.y == endpoint2.y)) + { + if (Behavior.Verbose) + { + logger.Warning("Endpoints of segments are coincident.", "Mesh.FormSkeleton()"); + } + } + else + { + InsertSegment(endpoint1, endpoint2, boundmarker); + } + } + } + } + + if (behavior.Convex || !behavior.Poly) + { + // Enclose the convex hull with subsegments. + MarkHull(); + } + } + + #endregion + + #region Dealloc + + /// + /// Deallocate space for a triangle, marking it dead. + /// + /// + internal void TriangleDealloc(Triangle dyingtriangle) + { + // Mark the triangle as dead. This makes it possible to detect dead + // triangles when traversing the list of all triangles. + Otri.Kill(dyingtriangle); + triangles.Remove(dyingtriangle.hash); + } + + /// + /// Deallocate space for a vertex, marking it dead. + /// + /// + internal void VertexDealloc(Vertex dyingvertex) + { + // Mark the vertex as dead. This makes it possible to detect dead + // vertices when traversing the list of all vertices. + dyingvertex.type = VertexType.DeadVertex; + vertices.Remove(dyingvertex.hash); + } + + /// + /// Deallocate space for a subsegment, marking it dead. + /// + /// + internal void SubsegDealloc(Segment dyingsubseg) + { + // Mark the subsegment as dead. This makes it possible to detect dead + // subsegments when traversing the list of all subsegments. + Osub.Kill(dyingsubseg); + subsegs.Remove(dyingsubseg.hash); + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/NewLocation.cs b/ThirdParty/Triangle/NewLocation.cs new file mode 100644 index 0000000..1111cba --- /dev/null +++ b/ThirdParty/Triangle/NewLocation.cs @@ -0,0 +1,4133 @@ +// ----------------------------------------------------------------------- +// +// Original code by Hale Erten and Alper Üngör, http://www.cise.ufl.edu/~ungor/aCute/index.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Data; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Find new Steiner Point locations. + /// + /// + /// http://www.cise.ufl.edu/~ungor/aCute/index.html + /// + class NewLocation + { + const double EPS = 1e-50; + + Mesh mesh; + Behavior behavior; + + public NewLocation(Mesh mesh) + { + this.mesh = mesh; + this.behavior = mesh.behavior; + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Point FindLocation(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + // Based on using -U switch, call the corresponding function + if (behavior.MaxAngle == 0.0) + { + return FindNewLocationWithoutMaxAngle(torg, tdest, tapex, ref xi, ref eta, true, badotri); + } + + // With max angle + return FindNewLocation(torg, tdest, tapex, ref xi, ref eta, true, badotri); + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + private Point FindNewLocationWithoutMaxAngle(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + double offconstant = behavior.offconstant; + + // for calculating the distances of the edges + double xdo, ydo, xao, yao, xda, yda; + double dodist, aodist, dadist; + // for exact calculation + double denominator; + double dx, dy, dxoff, dyoff; + + ////////////////////////////// HALE'S VARIABLES ////////////////////////////// + // keeps the difference of coordinates edge + double xShortestEdge = 0, yShortestEdge = 0, xMiddleEdge, yMiddleEdge, xLongestEdge, yLongestEdge; + + // keeps the square of edge lengths + double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; + + // keeps the vertices according to the angle incident to that vertex in a triangle + Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; + + // keeps the type of orientation if the triangle + int orientation = 0; + // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter + Point myCircumcenter, neighborCircumcenter; + + // keeps if bad triangle is almost good or not + int almostGood = 0; + // keeps the cosine of the largest angle + double cosMaxAngle; + bool isObtuse; // 1: obtuse 0: nonobtuse + // keeps the radius of petal + double petalRadius; + // for calculating petal center + double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; + double dxcenter1, dycenter1, dxcenter2, dycenter2; + // for finding neighbor + Otri neighborotri = default(Otri); + double[] thirdPoint = new double[2]; + //int neighborNotFound = -1; + bool neighborNotFound; + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1; + Vertex neighborvertex_2; + Vertex neighborvertex_3; + // dummy variables + double xi_tmp = 0, eta_tmp = 0; + //vertex thirdVertex; + // for petal intersection + double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; + double[] p = new double[5], voronoiOrInter = new double[4]; + bool isCorrect; + + // for vector calculations in perturbation + double ax, ay, d; + double pertConst = 0.06; // perturbation constant + + double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance + double justAcute = 1; // used for making the program working for one direction only + // for smoothing + int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point + double[] newloc = new double[2]; // new location suggested by smoothing + double origin_x = 0, origin_y = 0; // for keeping torg safe + Otri delotri; // keeping the original orientation for relocation process + // keeps the first and second direction suggested points + double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; + // second direction variables + double xMidOfMiddleEdge, yMidOfMiddleEdge; + ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + xda = tapex.x - tdest.x; + yda = tapex.y - tdest.y; + // keeps the square of the distances + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + // checking if the user wanted exact arithmetic or not + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / Primitives.CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + // calculate the circumcenter in terms of distance to origin point + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + // for debugging and for keeping circumcenter to use later + // coordinate value of the circumcenter + myCircumcenter = new Point(torg.x + dx, torg.y + dy); + + delotri = badotri; // save for later + ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + // find the orientation of the triangle, basically shortest and longest edges + orientation = LongestShortestEdge(aodist, dadist, dodist); + //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); + ///////////////////////////////////////////////////////////////////////////////////////////// + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // + // middle: dadist // middle: aodist // middle: aodist // + // longest: dodist // longest: dodist // longest: dadist // + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // + // middle: dodist // middle: dodist // middle: dadist // + // longest: dadist // longest: aodist // longest: aodist // + ///////////////////////////////////////////////////////////////////////////////////////////// + + switch (orientation) + { + case 123: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: apex + xShortestEdge = xao; yShortestEdge = yao; + xMiddleEdge = xda; yMiddleEdge = yda; + xLongestEdge = xdo; yLongestEdge = ydo; + + shortestEdgeDist = aodist; + middleEdgeDist = dadist; + longestEdgeDist = dodist; + + smallestAngleCorner = tdest; + middleAngleCorner = torg; + largestAngleCorner = tapex; + break; + + case 132: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: org + xShortestEdge = xao; yShortestEdge = yao; + xMiddleEdge = xdo; yMiddleEdge = ydo; + xLongestEdge = xda; yLongestEdge = yda; + + shortestEdgeDist = aodist; + middleEdgeDist = dodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tdest; + middleAngleCorner = tapex; + largestAngleCorner = torg; + + break; + case 213: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: apex + xShortestEdge = xda; yShortestEdge = yda; + xMiddleEdge = xao; yMiddleEdge = yao; + xLongestEdge = xdo; yLongestEdge = ydo; + + shortestEdgeDist = dadist; + middleEdgeDist = aodist; + longestEdgeDist = dodist; + + smallestAngleCorner = torg; + middleAngleCorner = tdest; + largestAngleCorner = tapex; + break; + case 231: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: dest + xShortestEdge = xda; yShortestEdge = yda; + xMiddleEdge = xdo; yMiddleEdge = ydo; + xLongestEdge = xao; yLongestEdge = yao; + + shortestEdgeDist = dadist; + middleEdgeDist = dodist; + longestEdgeDist = aodist; + + smallestAngleCorner = torg; + middleAngleCorner = tapex; + largestAngleCorner = tdest; + break; + case 312: // assign necessary information + /// smallest angle corner: apex + /// largest angle corner: org + xShortestEdge = xdo; yShortestEdge = ydo; + xMiddleEdge = xao; yMiddleEdge = yao; + xLongestEdge = xda; yLongestEdge = yda; + + shortestEdgeDist = dodist; + middleEdgeDist = aodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tapex; + middleAngleCorner = tdest; + largestAngleCorner = torg; + break; + case 321: // assign necessary information + default: // TODO: is this safe? + /// smallest angle corner: apex + /// largest angle corner: dest + xShortestEdge = xdo; yShortestEdge = ydo; + xMiddleEdge = xda; yMiddleEdge = yda; + xLongestEdge = xao; yLongestEdge = yao; + + shortestEdgeDist = dodist; + middleEdgeDist = dadist; + longestEdgeDist = aodist; + + smallestAngleCorner = tapex; + middleAngleCorner = torg; + largestAngleCorner = tdest; + break; + + }// end of switch + // check for offcenter condition + if (offcenter && (offconstant > 0.0)) + { + // origin has the smallest angle + if (orientation == 213 || orientation == 231) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to destination than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // destination has the smallest angle + } + else if (orientation == 123 || orientation == 132) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // apex has the smallest angle + } + else + {//orientation == 312 || orientation == 321 + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + } + } + // if the bad triangle is almost good, apply our approach + if (almostGood == 1) + { + + /// calculate cosine of largest angle /// + cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); + if (cosMaxAngle < 0.0) + { + // obtuse + isObtuse = true; + } + else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) + { + // right triangle (largest angle is 90 degrees) + isObtuse = true; + } + else + { + // nonobtuse + isObtuse = false; + } + /// RELOCATION (LOCAL SMOOTHING) /// + /// check for possible relocation of one of triangle's points /// + relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); + /// if relocation is possible, delete that vertex and insert a vertex at the new location /// + if (relocated > 0) + { + Statistic.RelocationCount++; + + dx = newloc[0] - torg.x; + dy = newloc[1] - torg.y; + origin_x = torg.x; // keep for later use + origin_y = torg.y; + switch (relocated) + { + case 1: + //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); + mesh.DeleteVertex(ref delotri); + break; + case 2: + //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); + delotri.LnextSelf(); + mesh.DeleteVertex(ref delotri); + break; + case 3: + //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); + delotri.LprevSelf(); + mesh.DeleteVertex(ref delotri); + break; + + } + } + else + { + // calculate radius of the petal according to angle constraint + // first find the visible region, PETAL + // find the center of the circle and radius + petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(behavior.MinAngle * Math.PI / 180.0)); + /// compute two possible centers of the petal /// + // finding the center + // first find the middle point of smallest edge + xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; + yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; + // two possible centers + xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + + xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + // find the correct circle since there will be two possible circles + // calculate the distance to smallest angle corner + dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); + dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); + dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); + dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); + + // whichever is closer to smallest angle corner, it must be the center + if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) + { + xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; + } + else + { + xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; + } + + /// find the third point of the neighbor triangle /// + neighborNotFound = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dyFirstSuggestion = dy; + // if there is a neighbor triangle + if (!neighborNotFound) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - middleAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; + // we need to find correct intersection point, since line intersects circle twice + isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, isObtuse); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxFirstSuggestion = voronoiOrInter[2] - torg.x; + dyFirstSuggestion = voronoiOrInter[3] - torg.y; + } + + } + else + { // there is no voronoi vertex between intersection point and circumcenter + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + + } + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) + { + // use circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + }// else we stick to what we have found + }// intersection point + + }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction + + /// DO THE SAME THING FOR THE OTHER DIRECTION /// + /// find the third point of the neighbor triangle /// + neighborNotFound = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dySecondSuggestion = dy; + // if there is a neighbor triangle + if (!neighborNotFound) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - largestAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + + /// choose the correct intersection point /// + // calcuwedgeslate middle point of the longest edge(bisector) + xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; + // we need to find correct intersection point, since line intersects circle twice + // this direction is always ACUTE + isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxSecondSuggestion = voronoiOrInter[2] - torg.x; + dySecondSuggestion = voronoiOrInter[3] - torg.y; + + } + + } + else + { // there is no voronoi vertex between intersection point and circumcenter + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + else + { + + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) + { + // use circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + }// else we stick on what we have found + } + }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok + if (isObtuse) + { + //obtuse: do nothing + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + else + { // acute : consider other direction + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + + }// end if obtuse + }// end of relocation + }// end of almostGood + + Point circumcenter = new Point(); + + if (relocated <= 0) + { + circumcenter.x = torg.x + dx; + circumcenter.y = torg.y + dy; + } + else + { + circumcenter.x = origin_x + dx; + circumcenter.y = origin_y + dy; + } + + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return circumcenter; + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + private Point FindNewLocation(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + double offconstant = behavior.offconstant; + + // for calculating the distances of the edges + double xdo, ydo, xao, yao, xda, yda; + double dodist, aodist, dadist; + // for exact calculation + double denominator; + double dx, dy, dxoff, dyoff; + + ////////////////////////////// HALE'S VARIABLES ////////////////////////////// + // keeps the difference of coordinates edge + double xShortestEdge = 0, yShortestEdge = 0, xMiddleEdge, yMiddleEdge, xLongestEdge, yLongestEdge; + + // keeps the square of edge lengths + double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; + + // keeps the vertices according to the angle incident to that vertex in a triangle + Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; + + // keeps the type of orientation if the triangle + int orientation = 0; + // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter + Point myCircumcenter, neighborCircumcenter; + + // keeps if bad triangle is almost good or not + int almostGood = 0; + // keeps the cosine of the largest angle + double cosMaxAngle; + bool isObtuse; // 1: obtuse 0: nonobtuse + // keeps the radius of petal + double petalRadius; + // for calculating petal center + double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; + double dxcenter1, dycenter1, dxcenter2, dycenter2; + // for finding neighbor + Otri neighborotri = default(Otri); + double[] thirdPoint = new double[2]; + //int neighborNotFound = -1; + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1; + Vertex neighborvertex_2; + Vertex neighborvertex_3; + // dummy variables + double xi_tmp = 0, eta_tmp = 0; + //vertex thirdVertex; + // for petal intersection + double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; + double[] p = new double[5], voronoiOrInter = new double[4]; + bool isCorrect; + + // for vector calculations in perturbation + double ax, ay, d; + double pertConst = 0.06; // perturbation constant + + double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance + double justAcute = 1; // used for making the program working for one direction only + // for smoothing + int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point + double[] newloc = new double[2]; // new location suggested by smoothing + double origin_x = 0, origin_y = 0; // for keeping torg safe + Otri delotri; // keeping the original orientation for relocation process + // keeps the first and second direction suggested points + double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; + // second direction variables + double xMidOfMiddleEdge, yMidOfMiddleEdge; + + double minangle; // in order to make sure that the circumcircle of the bad triangle is greater than petal + // for calculating the slab + double linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y; // two points of the line + double line_inter_x = 0, line_inter_y = 0; + double line_vector_x, line_vector_y; + double[] line_p = new double[3]; // used for getting the return values of functions related to line intersection + double[] line_result = new double[4]; + // intersection of slab and the petal + double petal_slab_inter_x_first, petal_slab_inter_y_first, petal_slab_inter_x_second, petal_slab_inter_y_second, x_1, y_1, x_2, y_2; + double petal_bisector_x, petal_bisector_y, dist; + double alpha; + bool neighborNotFound_first; + bool neighborNotFound_second; + ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + xda = tapex.x - tdest.x; + yda = tapex.y - tdest.y; + // keeps the square of the distances + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + // checking if the user wanted exact arithmetic or not + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / Primitives.CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + // calculate the circumcenter in terms of distance to origin point + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + // for debugging and for keeping circumcenter to use later + // coordinate value of the circumcenter + myCircumcenter = new Point(torg.x + dx, torg.y + dy); + + delotri = badotri; // save for later + ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + // find the orientation of the triangle, basically shortest and longest edges + orientation = LongestShortestEdge(aodist, dadist, dodist); + //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); + ///////////////////////////////////////////////////////////////////////////////////////////// + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // + // middle: dadist // middle: aodist // middle: aodist // + // longest: dodist // longest: dodist // longest: dadist // + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // + // middle: dodist // middle: dodist // middle: dadist // + // longest: dadist // longest: aodist // longest: aodist // + ///////////////////////////////////////////////////////////////////////////////////////////// + + switch (orientation) + { + case 123: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: apex + xShortestEdge = xao; yShortestEdge = yao; + xMiddleEdge = xda; yMiddleEdge = yda; + xLongestEdge = xdo; yLongestEdge = ydo; + + shortestEdgeDist = aodist; + middleEdgeDist = dadist; + longestEdgeDist = dodist; + + smallestAngleCorner = tdest; + middleAngleCorner = torg; + largestAngleCorner = tapex; + break; + + case 132: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: org + xShortestEdge = xao; yShortestEdge = yao; + xMiddleEdge = xdo; yMiddleEdge = ydo; + xLongestEdge = xda; yLongestEdge = yda; + + shortestEdgeDist = aodist; + middleEdgeDist = dodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tdest; + middleAngleCorner = tapex; + largestAngleCorner = torg; + + break; + case 213: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: apex + xShortestEdge = xda; yShortestEdge = yda; + xMiddleEdge = xao; yMiddleEdge = yao; + xLongestEdge = xdo; yLongestEdge = ydo; + + shortestEdgeDist = dadist; + middleEdgeDist = aodist; + longestEdgeDist = dodist; + + smallestAngleCorner = torg; + middleAngleCorner = tdest; + largestAngleCorner = tapex; + break; + case 231: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: dest + xShortestEdge = xda; yShortestEdge = yda; + xMiddleEdge = xdo; yMiddleEdge = ydo; + xLongestEdge = xao; yLongestEdge = yao; + + shortestEdgeDist = dadist; + middleEdgeDist = dodist; + longestEdgeDist = aodist; + + smallestAngleCorner = torg; + middleAngleCorner = tapex; + largestAngleCorner = tdest; + break; + case 312: // assign necessary information + /// smallest angle corner: apex + /// largest angle corner: org + xShortestEdge = xdo; yShortestEdge = ydo; + xMiddleEdge = xao; yMiddleEdge = yao; + xLongestEdge = xda; yLongestEdge = yda; + + shortestEdgeDist = dodist; + middleEdgeDist = aodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tapex; + middleAngleCorner = tdest; + largestAngleCorner = torg; + break; + case 321: // assign necessary information + default: // TODO: is this safe? + /// smallest angle corner: apex + /// largest angle corner: dest + xShortestEdge = xdo; yShortestEdge = ydo; + xMiddleEdge = xda; yMiddleEdge = yda; + xLongestEdge = xao; yLongestEdge = yao; + + shortestEdgeDist = dodist; + middleEdgeDist = dadist; + longestEdgeDist = aodist; + + smallestAngleCorner = tapex; + middleAngleCorner = torg; + largestAngleCorner = tdest; + break; + + }// end of switch + // check for offcenter condition + if (offcenter && (offconstant > 0.0)) + { + // origin has the smallest angle + if (orientation == 213 || orientation == 231) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to destination than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // destination has the smallest angle + } + else if (orientation == 123 || orientation == 132) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // apex has the smallest angle + } + else + {//orientation == 312 || orientation == 321 + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + } + } + // if the bad triangle is almost good, apply our approach + if (almostGood == 1) + { + + /// calculate cosine of largest angle /// + cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); + if (cosMaxAngle < 0.0) + { + // obtuse + isObtuse = true; + } + else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) + { + // right triangle (largest angle is 90 degrees) + isObtuse = true; + } + else + { + // nonobtuse + isObtuse = false; + } + /// RELOCATION (LOCAL SMOOTHING) /// + /// check for possible relocation of one of triangle's points /// + relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); + /// if relocation is possible, delete that vertex and insert a vertex at the new location /// + if (relocated > 0) + { + Statistic.RelocationCount++; + + dx = newloc[0] - torg.x; + dy = newloc[1] - torg.y; + origin_x = torg.x; // keep for later use + origin_y = torg.y; + switch (relocated) + { + case 1: + //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); + mesh.DeleteVertex(ref delotri); + break; + case 2: + //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); + delotri.LnextSelf(); + mesh.DeleteVertex(ref delotri); + break; + case 3: + //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); + delotri.LprevSelf(); + mesh.DeleteVertex(ref delotri); + break; + } + } + else + { + // calculate radius of the petal according to angle constraint + // first find the visible region, PETAL + // find the center of the circle and radius + // choose minimum angle as the maximum of quality angle and the minimum angle of the bad triangle + minangle = Math.Acos((middleEdgeDist + longestEdgeDist - shortestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(longestEdgeDist))) * 180.0 / Math.PI; + if (behavior.MinAngle > minangle) + { + minangle = behavior.MinAngle; + } + else + { + minangle = minangle + 0.5; + } + petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(minangle * Math.PI / 180.0)); + /// compute two possible centers of the petal /// + // finding the center + // first find the middle point of smallest edge + xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; + yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; + // two possible centers + xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + + xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + // find the correct circle since there will be two possible circles + // calculate the distance to smallest angle corner + dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); + dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); + dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); + dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); + + // whichever is closer to smallest angle corner, it must be the center + if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) + { + xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; + } + else + { + xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; + } + /// find the third point of the neighbor triangle /// + neighborNotFound_first = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dyFirstSuggestion = dy; + /// before checking the neighbor, find the petal and slab intersections /// + // calculate the intersection point of the petal and the slab lines + // first find the vector + // distance between xmid and petal center + dist = Math.Sqrt((xPetalCtr - xMidOfShortestEdge) * (xPetalCtr - xMidOfShortestEdge) + (yPetalCtr - yMidOfShortestEdge) * (yPetalCtr - yMidOfShortestEdge)); + // find the unit vector goes from mid point to petal center + line_vector_x = (xPetalCtr - xMidOfShortestEdge) / dist; + line_vector_y = (yPetalCtr - yMidOfShortestEdge) / dist; + // find the third point other than p and q + petal_bisector_x = xPetalCtr + line_vector_x * petalRadius; + petal_bisector_y = yPetalCtr + line_vector_y * petalRadius; + alpha = (2.0 * behavior.MaxAngle + minangle - 180.0) * Math.PI / 180.0; + // rotate the vector cw around the petal center + x_1 = petal_bisector_x * Math.Cos(alpha) + petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) - yPetalCtr * Math.Sin(alpha); + y_1 = -petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr + xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); + // rotate the vector ccw around the petal center + x_2 = petal_bisector_x * Math.Cos(alpha) - petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) + yPetalCtr * Math.Sin(alpha); + y_2 = petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr - xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); + // we need to find correct intersection point, since there are two possibilities + // weather it is obtuse/acute the one closer to the minimum angle corner is the first direction + isCorrect = ChooseCorrectPoint(x_2, y_2, middleAngleCorner.x, middleAngleCorner.y, x_1, y_1, true); + // make sure which point is the correct one to be considered + if (isCorrect) + { + petal_slab_inter_x_first = x_1; + petal_slab_inter_y_first = y_1; + petal_slab_inter_x_second = x_2; + petal_slab_inter_y_second = y_2; + } + else + { + petal_slab_inter_x_first = x_2; + petal_slab_inter_y_first = y_2; + petal_slab_inter_x_second = x_1; + petal_slab_inter_y_second = y_1; + } + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; + // if there is a neighbor triangle + if (!neighborNotFound_first) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - middleAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + // we need to find correct intersection point, since line intersects circle twice + isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, isObtuse); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + //----------------------hale new first direction: for slab calculation---------------// + // calculate the intersection of angle lines and Voronoi + linepnt1_x = middleAngleCorner.x; + linepnt1_y = middleAngleCorner.y; + // vector from middleAngleCorner to largestAngleCorner + line_vector_x = largestAngleCorner.x - middleAngleCorner.x; + line_vector_y = largestAngleCorner.y - middleAngleCorner.y; + // rotate the vector around middleAngleCorner in cw by maxangle degrees + linepnt2_x = petal_slab_inter_x_first; + linepnt2_y = petal_slab_inter_y_first; + // now calculate the intersection of two lines + LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); + // check if there is a suitable intersection + if (line_p[0] > 0.0) + { + line_inter_x = line_p[1]; + line_inter_y = line_p[2]; + } + else + { + // for debugging (to make sure) + //printf("1) No intersection between two lines!!!\n"); + //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); + } + + //---------------------------------------------------------------------// + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + //-----------------hale new continues 1------------------// + // now check if the line intersection is between cc and voronoi + PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + // check if we create a bad triangle or not + if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + + (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) + && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // check the neighbor's vertices also, which one if better + //slab and petal intersection is advised + dxFirstSuggestion = petal_slab_inter_x_first - torg.x; + dyFirstSuggestion = petal_slab_inter_y_first - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = line_inter_x - torg.x; + dyFirstSuggestion = line_inter_y - torg.y; + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxFirstSuggestion = line_result[2] - torg.x; + dyFirstSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + /// NOW APPLY A BREADTH-FIRST SEARCH ON THE VORONOI + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxFirstSuggestion = voronoiOrInter[2] - torg.x; + dyFirstSuggestion = voronoiOrInter[3] - torg.y; + } + } + } + else + { // there is no voronoi vertex between intersection point and circumcenter + //-----------------hale new continues 2-----------------// + // now check if the line intersection is between cc and intersection point + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + + (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) + && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + //slab and petal intersection is advised + dxFirstSuggestion = petal_slab_inter_x_first - torg.x; + dyFirstSuggestion = petal_slab_inter_y_first - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = line_inter_x - torg.x; + dyFirstSuggestion = line_inter_y - torg.y; + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxFirstSuggestion = line_result[2] - torg.x; + dyFirstSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) + { + //printf("testtriangle returned false! bad triangle\n"); + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) + { + // use circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + }// else we stick to what we have found + }// intersection point + + }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction + + /// DO THE SAME THING FOR THE OTHER DIRECTION /// + /// find the third point of the neighbor triangle /// + neighborNotFound_second = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dySecondSuggestion = dy; + + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; + // if there is a neighbor triangle + if (!neighborNotFound_second) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - largestAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + + // we need to find correct intersection point, since line intersects circle twice + // this direction is always ACUTE + isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + //----------------------hale new second direction:for slab calculation---------------// + // calculate the intersection of angle lines and Voronoi + linepnt1_x = largestAngleCorner.x; + linepnt1_y = largestAngleCorner.y; + // vector from largestAngleCorner to middleAngleCorner + line_vector_x = middleAngleCorner.x - largestAngleCorner.x; + line_vector_y = middleAngleCorner.y - largestAngleCorner.y; + // rotate the vector around largestAngleCorner in ccw by maxangle degrees + linepnt2_x = petal_slab_inter_x_second; + linepnt2_y = petal_slab_inter_y_second; + // now calculate the intersection of two lines + LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); + // check if there is a suitable intersection + if (line_p[0] > 0.0) + { + line_inter_x = line_p[1]; + line_inter_y = line_p[2]; + } + else + { + // for debugging (to make sure) + //printf("1) No intersection between two lines!!!\n"); + //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); + } + //---------------------------------------------------------------------// + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + //-----------------hale new continues 1------------------// + // now check if the line intersection is between cc and voronoi + PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + // + if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + + (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) + && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // slab and petal intersection is advised + dxSecondSuggestion = petal_slab_inter_x_second - torg.x; + dySecondSuggestion = petal_slab_inter_y_second - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = line_inter_x - torg.x; + dySecondSuggestion = line_inter_y - torg.y; + + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxSecondSuggestion = line_result[2] - torg.x; + dySecondSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxSecondSuggestion = voronoiOrInter[2] - torg.x; + dySecondSuggestion = voronoiOrInter[3] - torg.y; + } + } + } + else + { // there is no voronoi vertex between intersection point and circumcenter + //-----------------hale new continues 2-----------------// + // now check if the line intersection is between cc and intersection point + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + + (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) + && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // slab and petal intersection is advised + dxSecondSuggestion = petal_slab_inter_x_second - torg.x; + dySecondSuggestion = petal_slab_inter_y_second - torg.y; + } + else + { // slab intersection point is further away ; + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = line_inter_x - torg.x; + dySecondSuggestion = line_inter_y - torg.y; + } + } + else + { + // we are not creating a bad triangle + // slab intersection is advised + dxSecondSuggestion = line_result[2] - torg.x; + dySecondSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + } + + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) + { + // use circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + }// else we stick on what we have found + } + }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok + if (isObtuse) + { + if (neighborNotFound_first && neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_first) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + } + else + { // acute : consider other direction + if (neighborNotFound_first && neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_first) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + + }// end if obtuse + }// end of relocation + }// end of almostGood + + Point circumcenter = new Point(); + + if (relocated <= 0) + { + circumcenter.x = torg.x + dx; + circumcenter.y = torg.y + dy; + } + else + { + circumcenter.x = origin_x + dx; + circumcenter.y = origin_y + dy; + } + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return circumcenter; + } + + /// + /// Given square of edge lengths of a triangle, + // determine its orientation + /// + /// + /// + /// + /// Returns a number indicating an orientation. + private int LongestShortestEdge(double aodist, double dadist, double dodist) + { + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist + // middle: dadist // middle: aodist // middle: aodist + // longest: dodist // longest: dodist // longest: dadist + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist + // middle: dodist // middle: dodist // middle: dadist + // longest: dadist // longest: aodist // longest: aodist + + int max = 0, min = 0, mid = 0, minMidMax; + if (dodist < aodist && dodist < dadist) + { + min = 3; // apex is the smallest angle, dodist is the longest edge + if (aodist < dadist) + { + max = 2; // dadist is the longest edge + mid = 1; // aodist is the middle longest edge + } + else + { + max = 1; // aodist is the longest edge + mid = 2; // dadist is the middle longest edge + } + } + else if (aodist < dadist) + { + min = 1; // dest is the smallest angle, aodist is the biggest edge + if (dodist < dadist) + { + max = 2; // dadist is the longest edge + mid = 3; // dodist is the middle longest edge + } + else + { + max = 3; // dodist is the longest edge + mid = 2; // dadist is the middle longest edge + } + } + else + { + min = 2; // origin is the smallest angle, dadist is the biggest edge + if (aodist < dodist) + { + max = 3; // dodist is the longest edge + mid = 1; // aodist is the middle longest edge + } + else + { + max = 1; // aodist is the longest edge + mid = 3; // dodist is the middle longest edge + } + } + minMidMax = min * 100 + mid * 10 + max; + // HANDLE ISOSCELES TRIANGLE CASE + return minMidMax; + } + + /// + /// Checks if smothing is possible for a given bad triangle. + /// + /// + /// + /// + /// + /// The new location for the point, if somothing is possible. + /// Returns 1, 2 or 3 if smoothing will work, 0 otherwise. + private int DoSmoothing(Otri badotri, Vertex torg, Vertex tdest, Vertex tapex, + ref double[] newloc) + { + + int numpoints_p = 0;// keeps the number of points in a star of point p, q, r + int numpoints_q = 0; + int numpoints_r = 0; + //int i; + double[] possibilities = new double[6];//there can be more than one possibilities + int num_pos = 0; // number of possibilities + int flag1 = 0, flag2 = 0, flag3 = 0; + bool newLocFound = false; + + double[] points_p = new double[500];// keeps the points in a star of point p, q, r + double[] points_q = new double[500]; + double[] points_r = new double[500]; + + //vertex v1, v2, v3; // for ccw test + //double p1[2], p2[2], p3[2]; + //double temp[2]; + + //********************* TRY TO RELOCATE POINT "p" *************** + + // get the surrounding points of p, so this gives us the triangles + numpoints_p = GetStarPoints(badotri, torg, tdest, tapex, 1, ref points_p); + // check if the points in counterclockwise order + // p1[0] = points_p[0]; p1[1] = points_p[1]; + // p2[0] = points_p[2]; p2[1] = points_p[3]; + // p3[0] = points_p[4]; p3[1] = points_p[5]; + // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_p/2; i++){ + // temp[0] = points_p[2*i]; + // temp[1] = points_p[2*i+1]; + // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i]; + // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i]; + // points_p[2*(numpoints_p-1)-2*i] = temp[0]; + // points_p[2*(numpoints_p-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p)) + { + //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_p, points_p, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_p, points_p, ref newloc); + } + //printf("call petal intersection for p\n"); + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[0] = newloc[0];// something found + possibilities[1] = newloc[1]; + num_pos++;// increase the number of possibilities + flag1 = 1; + } + } + + //********************* TRY TO RELOCATE POINT "q" *************** + + // get the surrounding points of q, so this gives us the triangles + numpoints_q = GetStarPoints(badotri, torg, tdest, tapex, 2, ref points_q); + // // check if the points in counterclockwise order + // v1[0] = points_q[0]; v1[1] = points_q[1]; + // v2[0] = points_q[2]; v2[1] = points_q[3]; + // v3[0] = points_q[4]; v3[1] = points_q[5]; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_q/2; i++){ + // temp[0] = points_q[2*i]; + // temp[1] = points_q[2*i+1]; + // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i]; + // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i]; + // points_q[2*(numpoints_q-1)-2*i] = temp[0]; + // points_q[2*(numpoints_q-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q)) + { + //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_q, points_q, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_q, points_q, ref newloc); + } + //printf("call petal intersection for q\n"); + + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[2] = newloc[0];// something found + possibilities[3] = newloc[1]; + num_pos++;// increase the number of possibilities + flag2 = 2; + } + } + + + //********************* TRY TO RELOCATE POINT "q" *************** + // get the surrounding points of r, so this gives us the triangles + numpoints_r = GetStarPoints(badotri, torg, tdest, tapex, 3, ref points_r); + // check if the points in counterclockwise order + // v1[0] = points_r[0]; v1[1] = points_r[1]; + // v2[0] = points_r[2]; v2[1] = points_r[3]; + // v3[0] = points_r[4]; v3[1] = points_r[5]; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_r/2; i++){ + // temp[0] = points_r[2*i]; + // temp[1] = points_r[2*i+1]; + // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i]; + // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i]; + // points_r[2*(numpoints_r-1)-2*i] = temp[0]; + // points_r[2*(numpoints_r-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r)) + { + //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_r, points_r, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_r, points_r, ref newloc); + } + + //printf("call petal intersection for r\n"); + + + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[4] = newloc[0];// something found + possibilities[5] = newloc[1]; + num_pos++;// increase the number of possibilities + flag3 = 3; + } + } + //printf("numpossibilities %d\n",num_pos); + //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE ////////////////////// + if (num_pos > 0) + { + if (flag1 > 0) + { // suggest to relocate origin + newloc[0] = possibilities[0]; + newloc[1] = possibilities[1]; + return flag1; + + } + else + { + if (flag2 > 0) + { // suggest to relocate apex + newloc[0] = possibilities[2]; + newloc[1] = possibilities[3]; + return flag2; + + } + else + {// suggest to relocate destination + if (flag3 > 0) + { + newloc[0] = possibilities[4]; + newloc[1] = possibilities[5]; + return flag3; + + } + } + } + } + + return 0;// could not find any good relocation + } + + /// + /// Finds the star of a given point. + /// + /// + /// + /// + /// + /// + /// List of points on the star of the given point. + /// Number of points on the star of the given point. + private int GetStarPoints(Otri badotri, Vertex p, Vertex q, Vertex r, + int whichPoint, ref double[] points) + { + + Otri neighotri = default(Otri); // for return value of the function + Otri tempotri; // for temporary usage + double first_x = 0, first_y = 0; // keeps the first point to be considered + double second_x = 0, second_y = 0; // for determining the edge we will begin + double third_x = 0, third_y = 0; // termination + double[] returnPoint = new double[2]; // for keeping the returned point + int numvertices = 0; // for keeping number of surrounding vertices + + // first determine which point to be used to find its neighbor triangles + switch (whichPoint) + { + case 1: + first_x = p.x; // point at the center + first_y = p.y; + second_x = r.x; // second vertex of first edge to consider + second_y = r.y; + third_x = q.x; // for terminating the search + third_y = q.y; + break; + case 2: + first_x = q.x; // point at the center + first_y = q.y; + second_x = p.x; // second vertex of first edge to consider + second_y = p.y; + third_x = r.x; // for terminating the search + third_y = r.y; + break; + case 3: + first_x = r.x; // point at the center + first_y = r.y; + second_x = q.x; // second vertex of first edge to consider + second_y = q.y; + third_x = p.x; // for terminating the search + third_y = p.y; + break; + } + tempotri = badotri; + // add first point as the end of first edge + points[numvertices] = second_x; + numvertices++; + points[numvertices] = second_y; + numvertices++; + // assign as dummy value + returnPoint[0] = second_x; returnPoint[1] = second_y; + // until we reach the third point of the beginning triangle + do + { + // find the neighbor's third point where it is incident to given edge + if (!GetNeighborsVertex(tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri)) + { + // go to next triangle + tempotri = neighotri; + // now the second point is the neighbor's third vertex + second_x = returnPoint[0]; + second_y = returnPoint[1]; + // add a new point to the list of surrounding points + points[numvertices] = returnPoint[0]; + numvertices++; + points[numvertices] = returnPoint[1]; + numvertices++; + } + else + { + numvertices = 0; + break; + } + + } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) && + (Math.Abs(returnPoint[1] - third_y) <= EPS))); + return numvertices / 2; + + } + + /// + /// Gets a neighbours vertex. + /// + /// + /// + /// + /// + /// + /// Neighbor's third vertex incident to given edge. + /// Pointer for the neighbor triangle. + /// Returns true if vertex was found. + private bool GetNeighborsVertex(Otri badotri, + double first_x, double first_y, + double second_x, double second_y, + ref double[] thirdpoint, ref Otri neighotri) + { + + Otri neighbor = default(Otri); // keeps the neighbor triangles + bool notFound = false; // boolean variable if we can find that neighbor or not + + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1 = null; + Vertex neighborvertex_2 = null; + Vertex neighborvertex_3 = null; + + // used for finding neighbor triangle + int firstVertexMatched = 0, secondVertexMatched = 0; // to find the correct neighbor + //triangle ptr; // Temporary variable used by sym() + //int i; // index variable + // find neighbors + // Check each of the triangle's three neighbors to find the correct one + for (badotri.orient = 0; badotri.orient < 3; badotri.orient++) + { + // Find the neighbor. + badotri.Sym(ref neighbor); + // check if it is the one we are looking for by checking the corners + // first check if the neighbor is nonexistent, since it can be on the border + if ((neighbor.triangle != Mesh.dummytri)) + { + // then check if two wanted corners are also in this triangle + // take the vertices of the candidate neighbor + neighborvertex_1 = neighbor.Org(); + neighborvertex_2 = neighbor.Dest(); + neighborvertex_3 = neighbor.Apex(); + + // check if it is really a triangle + if ((neighborvertex_1.x == neighborvertex_2.x && neighborvertex_1.y == neighborvertex_2.y) + || (neighborvertex_2.x == neighborvertex_3.x && neighborvertex_2.y == neighborvertex_3.y) + || (neighborvertex_1.x == neighborvertex_3.x && neighborvertex_1.y == neighborvertex_3.y)) + { + //printf("Two vertices are the same!!!!!!!\n"); + } + else + { + // begin searching for the correct neighbor triangle + firstVertexMatched = 0; + if ((Math.Abs(first_x - neighborvertex_1.x) < EPS) && + (Math.Abs(first_y - neighborvertex_1.y) < EPS)) + { + firstVertexMatched = 11; // neighbor's 1st vertex is matched to first vertex + + } + else if ((Math.Abs(first_x - neighborvertex_2.x) < EPS) && + (Math.Abs(first_y - neighborvertex_2.y) < EPS)) + { + firstVertexMatched = 12; // neighbor's 2nd vertex is matched to first vertex + + } + else if ((Math.Abs(first_x - neighborvertex_3.x) < EPS) && + (Math.Abs(first_y - neighborvertex_3.y) < EPS)) + { + firstVertexMatched = 13; // neighbor's 3rd vertex is matched to first vertex + + }/*else{ + // none of them matched + } // end of first vertex matching */ + + secondVertexMatched = 0; + if ((Math.Abs(second_x - neighborvertex_1.x) < EPS) && + (Math.Abs(second_y - neighborvertex_1.y) < EPS)) + { + secondVertexMatched = 21; // neighbor's 1st vertex is matched to second vertex + } + else if ((Math.Abs(second_x - neighborvertex_2.x) < EPS) && + (Math.Abs(second_y - neighborvertex_2.y) < EPS)) + { + secondVertexMatched = 22; // neighbor's 2nd vertex is matched to second vertex + } + else if ((Math.Abs(second_x - neighborvertex_3.x) < EPS) && + (Math.Abs(second_y - neighborvertex_3.y) < EPS)) + { + secondVertexMatched = 23; // neighbor's 3rd vertex is matched to second vertex + }/*else{ + // none of them matched + } // end of second vertex matching*/ + + } + + }// if neighbor exists or not + + if (((firstVertexMatched == 11) && (secondVertexMatched == 22 || secondVertexMatched == 23)) + || ((firstVertexMatched == 12) && (secondVertexMatched == 21 || secondVertexMatched == 23)) + || ((firstVertexMatched == 13) && (secondVertexMatched == 21 || secondVertexMatched == 22))) + break; + }// end of for loop over all orientations + + switch (firstVertexMatched) + { + case 0: + notFound = true; + break; + case 11: + if (secondVertexMatched == 22) + { + thirdpoint[0] = neighborvertex_3.x; + thirdpoint[1] = neighborvertex_3.y; + } + else if (secondVertexMatched == 23) + { + thirdpoint[0] = neighborvertex_2.x; + thirdpoint[1] = neighborvertex_2.y; + } + else { notFound = true; } + break; + case 12: + if (secondVertexMatched == 21) + { + thirdpoint[0] = neighborvertex_3.x; + thirdpoint[1] = neighborvertex_3.y; + } + else if (secondVertexMatched == 23) + { + thirdpoint[0] = neighborvertex_1.x; + thirdpoint[1] = neighborvertex_1.y; + } + else { notFound = true; } + break; + case 13: + if (secondVertexMatched == 21) + { + thirdpoint[0] = neighborvertex_2.x; + thirdpoint[1] = neighborvertex_2.y; + } + else if (secondVertexMatched == 22) + { + thirdpoint[0] = neighborvertex_1.x; + thirdpoint[1] = neighborvertex_1.y; + } + else { notFound = true; } + break; + default: + if (secondVertexMatched == 0) { notFound = true; } + break; + } + // pointer of the neighbor triangle + neighotri = neighbor; + return notFound; + + } + + /// + /// Find a new point location by wedge intersection. + /// + /// + /// + /// A new location for the point according to surrounding points. + /// Returns true if new location found + private bool GetWedgeIntersectionWithoutMaxAngle(int numpoints, + double[] points, ref double[] newloc) + { + //double total_x = 0; + //double total_y = 0; + double x0, y0, x1, y1, x2, y2; + //double compConst = 0.01; // for comparing real numbers + + double x01, y01; + //double x12, y12; + + //double ax, ay, bx, by; //two intersections of two petals disks + + double d01;//, d12 + + //double petalx0, petaly0, petalr0, petalx1, petaly1, petalr1; + + //double p[5]; + + double[] petalx = new double[2 * numpoints]; + double[] petaly = new double[2 * numpoints]; + double[] petalr = new double[2 * numpoints]; + + double[] wedges = new double[2000]; + double xmid, ymid, dist, x3, y3; + double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy; + double ux, uy; + double alpha; + double[] p1 = new double[3]; + double[] initialConvexPoly = new double[500]; + //double poly_points; + int numpolypoints = 0; + + //int numBadTriangle; + + int i, j; + + int s, flag, count, num; + + double petalcenterconstant, petalradiusconstant; + + x0 = points[2 * numpoints - 4]; + y0 = points[2 * numpoints - 3]; + x1 = points[2 * numpoints - 2]; + y1 = points[2 * numpoints - 1]; + + // minimum angle + alpha = behavior.MinAngle * Math.PI / 180.0; + // initialize the constants + if (behavior.goodAngle == 1.0) + { + petalcenterconstant = 0; + petalradiusconstant = 0; + } + else + { + petalcenterconstant = 0.5 / Math.Tan(alpha); + petalradiusconstant = 0.5 / Math.Sin(alpha); + } + + for (i = 0; i < numpoints * 2; i = i + 2) + { + x2 = points[i]; + y2 = points[i + 1]; + + //printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); + + x01 = x1 - x0; + y01 = y1 - y0; + d01 = Math.Sqrt(x01 * x01 + y01 * y01); + // find the petal of each edge 01; + + // printf("PETAL CONSTANT (%.12f, %.12f)\n", + // b.petalcenterconstant, b.petalradiusconstant ); + // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); + + petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; + petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; + petalr[i / 2] = petalradiusconstant * d01; + petalx[numpoints + i / 2] = petalx[i / 2]; + petaly[numpoints + i / 2] = petaly[i / 2]; + petalr[numpoints + i / 2] = petalr[i / 2]; + //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); + + /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL + xmid = (x0 + x1) / 2.0; // mid point of pq + ymid = (y0 + y1) / 2.0; + + // distance between xmid and petal center + dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); + // find the unit vector goes from mid point to petal center + ux = (petalx[i / 2] - xmid) / dist; + uy = (petaly[i / 2] - ymid) / dist; + // find the third point other than p and q + x3 = petalx[i / 2] + ux * petalr[i / 2]; + y3 = petaly[i / 2] + uy * petalr[i / 2]; + /// FIND THE LINE POINTS BY THE ROTATION MATRIX + // cw rotation matrix [cosX sinX; -sinX cosX] + // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] + // ccw rotation matrix [cosX -sinX; sinX cosX] + // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] + /// LINE #1: (x1,y1) & (x_1,y_1) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_1 = x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha) + x0 - x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha); + y_1 = x1 * Math.Sin(alpha) + y1 * Math.Cos(alpha) + y0 - x0 * Math.Sin(alpha) - y0 * Math.Cos(alpha); + // add these to wedges list as lines in order + wedges[i * 16] = x0; wedges[i * 16 + 1] = y0; + wedges[i * 16 + 2] = x_1; wedges[i * 16 + 3] = y_1; + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// LINE #2: (x2,y2) & (x_2,y_2) + // vector from p to q + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_2 = x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha) + x1 - x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha); + y_2 = -x0 * Math.Sin(alpha) + y0 * Math.Cos(alpha) + y1 + x1 * Math.Sin(alpha) - y1 * Math.Cos(alpha); + // add these to wedges list as lines in order + wedges[i * 16 + 4] = x_2; wedges[i * 16 + 5] = y_2; + wedges[i * 16 + 6] = x1; wedges[i * 16 + 7] = y1; + //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); + // vector from (petalx, petaly) to (x3,y3) + ux = x3 - petalx[i / 2]; + uy = y3 - petaly[i / 2]; + tempx = x3; tempy = y3; + /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) + for (j = 1; j < 4; j++) + { + // rotate the vector around (petalx,petaly) in cw by (60 - alpha)*j degrees + x_3 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); + y_3 = -x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] + petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); + // add these to wedges list as lines in order + wedges[i * 16 + 8 + 4 * (j - 1)] = x_3; wedges[i * 16 + 9 + 4 * (j - 1)] = y_3; + wedges[i * 16 + 10 + 4 * (j - 1)] = tempx; wedges[i * 16 + 11 + 4 * (j - 1)] = tempy; + tempx = x_3; tempy = y_3; + } + tempx = x3; tempy = y3; + /// LINE #6, #7, #8: (x3,y3) & (x_4,y_4) + for (j = 1; j < 4; j++) + { + // rotate the vector around (petalx,petaly) in ccw by (60 - alpha)*j degrees + x_4 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) - y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); + y_4 = x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); + + // add these to wedges list as lines in order + wedges[i * 16 + 20 + 4 * (j - 1)] = tempx; wedges[i * 16 + 21 + 4 * (j - 1)] = tempy; + wedges[i * 16 + 22 + 4 * (j - 1)] = x_4; wedges[i * 16 + 23 + 4 * (j - 1)] = y_4; + tempx = x_4; tempy = y_4; + } + //printf("LINE #3 (%.12f, %.12f) (%.12f, %.12f)\n", x_3,y_3,x3,y3); + //printf("LINE #4 (%.12f, %.12f) (%.12f, %.12f)\n", x3,y3,x_4,y_4); + + /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON + if (i == 0) + { + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + if ((p1[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = wedges[i * 16 + 16]; initialConvexPoly[3] = wedges[i * 16 + 17]; + // #2 + initialConvexPoly[4] = wedges[i * 16 + 12]; initialConvexPoly[5] = wedges[i * 16 + 13]; + // #3 + initialConvexPoly[6] = wedges[i * 16 + 8]; initialConvexPoly[7] = wedges[i * 16 + 9]; + // #4 + initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; + // #5 + initialConvexPoly[10] = wedges[i * 16 + 22]; initialConvexPoly[11] = wedges[i * 16 + 23]; + // #6 + initialConvexPoly[12] = wedges[i * 16 + 26]; initialConvexPoly[13] = wedges[i * 16 + 27]; + // #7 + initialConvexPoly[14] = wedges[i * 16 + 30]; initialConvexPoly[15] = wedges[i * 16 + 31]; + //printf("INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15]); + } + } + + x0 = x1; y0 = y1; + x1 = x2; y1 = y2; + } + + /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION + if (numpoints != 0) + { + // first intersect the opposite located ones + s = (numpoints - 1) / 2 + 1; + flag = 0; + count = 0; + i = 1; + num = 8; + for (j = 0; j < 32; j = j + 4) + { + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * s + j], wedges[32 * s + 1 + j], wedges[32 * s + 2 + j], wedges[32 * s + 3 + j]); + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + count++; + while (count < numpoints - 1) + { + for (j = 0; j < 32; j = j + 4) + { + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * (i + s * flag) + j], wedges[32 * (i + s * flag) + 1 + j], wedges[32 * (i + s * flag) + 2 + j], wedges[32 * (i + s * flag) + 3 + j]); + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + i = i + flag; + flag = (flag + 1) % 2; + count++; + } + /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION + FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); + + if (behavior.fixedArea) + { + // numBadTriangle = 0; + // for(j= 0; j < numpoints *2-2; j = j+2){ + // if(testTriangleAngleArea(m,b,&newloc[0],&newloc[1], &points[j], &points[j+1], &points[j+2], &points[j+3] )){ + // numBadTriangle++; + // } + // } + // if(testTriangleAngleArea(m,b, &newloc[0],&newloc[1], &points[0], &points[1], &points[numpoints*2-2], &points[numpoints*2-1] )){ + // numBadTriangle++; + // } + // + // if (numBadTriangle == 0) { + // + // return 1; + // } + } + else + { + //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); + // for(i = 0; i < 2*numpolypoints; i = i+2){ + // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); + // } + // printf("numpoints %d\n",numpoints); + return true; + } + } + + + return false; + } + + /// + /// Find a new point location by wedge intersection. + /// + /// + /// + /// A new location for the point according to surrounding points. + /// Returns true if new location found + private bool GetWedgeIntersection(int numpoints, double[] points, ref double[] newloc) + { + //double total_x = 0; + //double total_y = 0; + double x0, y0, x1, y1, x2, y2; + //double compConst = 0.01; // for comparing real numbers + + double x01, y01; + //double x12, y12; + + //double ax, ay, bx, by; //two intersections of two petals disks + + double d01;//, d12 + + //double petalx0, petaly1, petaly0, petalr0, petalx1, petalr1; + + //double p[5]; + + double[] petalx = new double[2 * numpoints]; + double[] petaly = new double[2 * numpoints]; + double[] petalr = new double[2 * numpoints]; + + double[] wedges = new double[2000]; + double xmid, ymid, dist, x3, y3; + double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy, x_5, y_5, x_6, y_6; + double ux, uy; + double[] p1 = new double[3], p2 = new double[3], p3 = new double[3], p4 = new double[3]; + double[] initialConvexPoly = new double[500]; + //double poly_points; + int numpolypoints = 0; + int howManyPoints = 0; // keeps the number of points used for representing the wedge + double line345 = 4.0, line789 = 4.0; // flag keeping which line to skip or construct + + int numBadTriangle; + + int i, j, k; + + int s, flag, count, num; + + int n, e; + + double weight; + + double petalcenterconstant, petalradiusconstant; + + x0 = points[2 * numpoints - 4]; + y0 = points[2 * numpoints - 3]; + x1 = points[2 * numpoints - 2]; + y1 = points[2 * numpoints - 1]; + + // minimum / maximum angle + double alpha, sinAlpha, cosAlpha, beta, sinBeta, cosBeta; + alpha = behavior.MinAngle * Math.PI / 180.0; + sinAlpha = Math.Sin(alpha); + cosAlpha = Math.Cos(alpha); + beta = behavior.MaxAngle * Math.PI / 180.0; + sinBeta = Math.Sin(beta); + cosBeta = Math.Cos(beta); + + // initialize the constants + if (behavior.goodAngle == 1.0) + { + petalcenterconstant = 0; + petalradiusconstant = 0; + } + else + { + petalcenterconstant = 0.5 / Math.Tan(alpha); + petalradiusconstant = 0.5 / Math.Sin(alpha); + } + + for (i = 0; i < numpoints * 2; i = i + 2) + { + // go to the next point + x2 = points[i]; + y2 = points[i + 1]; + + // printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); + + x01 = x1 - x0; + y01 = y1 - y0; + d01 = Math.Sqrt(x01 * x01 + y01 * y01); + // find the petal of each edge 01; + + // printf("PETAL CONSTANT (%.12f, %.12f)\n", + // b.petalcenterconstant, b.petalradiusconstant ); + // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); + //printf("i:%d numpoints:%d\n", i, numpoints); + petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; + petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; + petalr[i / 2] = petalradiusconstant * d01; + petalx[numpoints + i / 2] = petalx[i / 2]; + petaly[numpoints + i / 2] = petaly[i / 2]; + petalr[numpoints + i / 2] = petalr[i / 2]; + //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); + + /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL + xmid = (x0 + x1) / 2.0; // mid point of pq + ymid = (y0 + y1) / 2.0; + + // distance between xmid and petal center + dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); + // find the unit vector goes from mid point to petal center + ux = (petalx[i / 2] - xmid) / dist; + uy = (petaly[i / 2] - ymid) / dist; + // find the third point other than p and q + x3 = petalx[i / 2] + ux * petalr[i / 2]; + y3 = petaly[i / 2] + uy * petalr[i / 2]; + /// FIND THE LINE POINTS BY THE ROTATION MATRIX + // cw rotation matrix [cosX sinX; -sinX cosX] + // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] + // ccw rotation matrix [cosX -sinX; sinX cosX] + // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] + /// LINE #1: (x1,y1) & (x_1,y_1) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_1 = x1 * cosAlpha - y1 * sinAlpha + x0 - x0 * cosAlpha + y0 * sinAlpha; + y_1 = x1 * sinAlpha + y1 * cosAlpha + y0 - x0 * sinAlpha - y0 * cosAlpha; + // add these to wedges list as lines in order + wedges[i * 20] = x0; wedges[i * 20 + 1] = y0; + wedges[i * 20 + 2] = x_1; wedges[i * 20 + 3] = y_1; + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// LINE #2: (x2,y2) & (x_2,y_2) + // vector from q to p + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_2 = x0 * cosAlpha + y0 * sinAlpha + x1 - x1 * cosAlpha - y1 * sinAlpha; + y_2 = -x0 * sinAlpha + y0 * cosAlpha + y1 + x1 * sinAlpha - y1 * cosAlpha; + // add these to wedges list as lines in order + wedges[i * 20 + 4] = x_2; wedges[i * 20 + 5] = y_2; + wedges[i * 20 + 6] = x1; wedges[i * 20 + 7] = y1; + //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); + // vector from (petalx, petaly) to (x3,y3) + ux = x3 - petalx[i / 2]; + uy = y3 - petaly[i / 2]; + tempx = x3; tempy = y3; + + /// DETERMINE HOW MANY POINTS TO USE ACCORDING TO THE MINANGLE-MAXANGLE COMBINATION + // petal center angle + alpha = (2.0 * behavior.MaxAngle + behavior.MinAngle - 180.0); + if (alpha <= 0.0) + {// when only angle lines needed + // 4 point case + howManyPoints = 4; + //printf("4 point case\n"); + line345 = 1.0; + line789 = 1.0; + } + else if (alpha <= 5.0) + {// when only angle lines plus two other lines are needed + // 6 point case + howManyPoints = 6; + //printf("6 point case\n"); + line345 = 2.0; + line789 = 2.0; + } + else if (alpha <= 10.0) + {// when we need more lines + // 8 point case + howManyPoints = 8; + line345 = 3.0; + line789 = 3.0; + //printf("8 point case\n"); + } + else + {// when we have a big wedge + // 10 point case + howManyPoints = 10; + //printf("10 point case\n"); + line345 = 4.0; + line789 = 4.0; + } + alpha = alpha * Math.PI / 180.0; + /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) + for (j = 1; j < line345; j++) + { + if (line345 == 1) + continue; + // rotate the vector around (petalx,petaly) in cw by (alpha/3.0)*j degrees + x_3 = x3 * Math.Cos((alpha / (line345 - 1.0)) * j) + y3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + petalx[i / 2] - petalx[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)); + y_3 = -x3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + y3 * Math.Cos(((alpha / (line345 - 1.0)) * j)) + petaly[i / 2] + petalx[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)); + // add these to wedges list as lines in order + wedges[i * 20 + 8 + 4 * (j - 1)] = x_3; wedges[i * 20 + 9 + 4 * (j - 1)] = y_3; + wedges[i * 20 + 10 + 4 * (j - 1)] = tempx; wedges[i * 20 + 11 + 4 * (j - 1)] = tempy; + tempx = x_3; tempy = y_3; + } + /// LINE #6: (x2,y2) & (x_3,y_3) + // vector from q to p + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_5 = x0 * cosBeta + y0 * sinBeta + x1 - x1 * cosBeta - y1 * sinBeta; + y_5 = -x0 * sinBeta + y0 * cosBeta + y1 + x1 * sinBeta - y1 * cosBeta; + wedges[i * 20 + 20] = x1; wedges[i * 20 + 21] = y1; + wedges[i * 20 + 22] = x_5; wedges[i * 20 + 23] = y_5; + + tempx = x3; tempy = y3; + /// LINE #7, #8, #9: (x3,y3) & (x_4,y_4) + for (j = 1; j < line789; j++) + { + if (line789 == 1) + continue; + // rotate the vector around (petalx,petaly) in ccw by (alpha/3.0)*j degrees + x_4 = x3 * Math.Cos((alpha / (line789 - 1.0)) * j) - y3 * Math.Sin((alpha / (line789 - 1.0)) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j); + y_4 = x3 * Math.Sin((alpha / (line789 - 1.0)) * j) + y3 * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j) - petaly[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j); + + // add these to wedges list as lines in order + wedges[i * 20 + 24 + 4 * (j - 1)] = tempx; wedges[i * 20 + 25 + 4 * (j - 1)] = tempy; + wedges[i * 20 + 26 + 4 * (j - 1)] = x_4; wedges[i * 20 + 27 + 4 * (j - 1)] = y_4; + tempx = x_4; tempy = y_4; + } + /// LINE #10: (x1,y1) & (x_3,y_3) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_6 = x1 * cosBeta - y1 * sinBeta + x0 - x0 * cosBeta + y0 * sinBeta; + y_6 = x1 * sinBeta + y1 * cosBeta + y0 - x0 * sinBeta - y0 * cosBeta; + wedges[i * 20 + 36] = x_6; wedges[i * 20 + 37] = y_6; + wedges[i * 20 + 38] = x0; wedges[i * 20 + 39] = y0; + + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON + if (i == 0) + { + switch (howManyPoints) + { + case 4: + // line1 & line2 & line3 & line4 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_5, y_5, ref p3); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p4); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0) && (p4[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = p3[1]; initialConvexPoly[5] = p3[2]; + // #3 + initialConvexPoly[6] = p4[1]; initialConvexPoly[7] = p4[2]; + } + break; + case 6: + // line1 & line2 & line3 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 8]; initialConvexPoly[5] = wedges[i * 20 + 9]; + // #3 + initialConvexPoly[6] = x3; initialConvexPoly[7] = y3; + // #4 + initialConvexPoly[8] = wedges[i * 20 + 26]; initialConvexPoly[9] = wedges[i * 20 + 27]; + // #5 + initialConvexPoly[10] = p3[1]; initialConvexPoly[11] = p3[2]; + } + break; + case 8: + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 12]; initialConvexPoly[5] = wedges[i * 20 + 13]; + // #3 + initialConvexPoly[6] = wedges[i * 20 + 8]; initialConvexPoly[7] = wedges[i * 20 + 9]; + // #4 + initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; + // #5 + initialConvexPoly[10] = wedges[i * 20 + 26]; initialConvexPoly[11] = wedges[i * 20 + 27]; + // #6 + initialConvexPoly[12] = wedges[i * 20 + 30]; initialConvexPoly[13] = wedges[i * 20 + 31]; + // #7 + initialConvexPoly[14] = p3[1]; initialConvexPoly[15] = p3[2]; + } + break; + case 10: + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + //printf("p3 %f %f %f (%f %f) (%f %f) (%f %f) (%f %f)\n",p3[0],p3[1],p3[2], x0, y0, x_6, x_6, x1, y1, x_2, y_2); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 16]; initialConvexPoly[5] = wedges[i * 20 + 17]; + // #3 + initialConvexPoly[6] = wedges[i * 20 + 12]; initialConvexPoly[7] = wedges[i * 20 + 13]; + // #4 + initialConvexPoly[8] = wedges[i * 20 + 8]; initialConvexPoly[9] = wedges[i * 20 + 9]; + // #5 + initialConvexPoly[10] = x3; initialConvexPoly[11] = y3; + // #6 + initialConvexPoly[12] = wedges[i * 20 + 28]; initialConvexPoly[13] = wedges[i * 20 + 29]; + // #7 + initialConvexPoly[14] = wedges[i * 20 + 32]; initialConvexPoly[15] = wedges[i * 20 + 33]; + // #8 + initialConvexPoly[16] = wedges[i * 20 + 34]; initialConvexPoly[17] = wedges[i * 20 + 35]; + // #9 + initialConvexPoly[18] = p3[1]; initialConvexPoly[19] = p3[2]; + } + break; + } + // printf("smallest edge (%f,%f) (%f,%f)\n", x0,y0, x1,y1); + // printf("real INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + } + + x0 = x1; y0 = y1; + x1 = x2; y1 = y2; + } + /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION + if (numpoints != 0) + { + // first intersect the opposite located ones + s = (numpoints - 1) / 2 + 1; + flag = 0; + count = 0; + i = 1; + num = howManyPoints; + for (j = 0; j < 40; j = j + 4) + { + // in order to skip non-existent lines + if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 8 && (j == 16 || j == 32)) + { + continue; + } + // printf("%d 1 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",num, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + // printf("line (%f, %f) (%f, %f)\n",wedges[40*s+j],wedges[40*s+1+j], wedges[40*s+2+j], wedges[40*s+3+j]); + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * s + j], wedges[40 * s + 1 + j], wedges[40 * s + 2 + j], wedges[40 * s + 3 + j]); + + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + count++; + //printf("yes here\n"); + while (count < numpoints - 1) + { + for (j = 0; j < 40; j = j + 4) + { + // in order to skip non-existent lines + if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 8 && (j == 16 || j == 32)) + { + continue; + } + ////printf("%d 2 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",numpolypoints, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + //printf("line (%.20f, %.20f) (%.20f, %.20f)\n", wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); + + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + i = i + flag; + flag = (flag + 1) % 2; + count++; + } + /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION + FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); + + if (behavior.MaxAngle != 0.0) + { + numBadTriangle = 0; + for (j = 0; j < numpoints * 2 - 2; j = j + 2) + { + if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) + { + numBadTriangle++; + } + } + if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) + { + numBadTriangle++; + } + + if (numBadTriangle == 0) + { + + return true; + } + n = (numpoints <= 2) ? 20 : 30; + // try points other than centroid + for (k = 0; k < 2 * numpoints; k = k + 2) + { + for (e = 1; e < n; e = e + 1) + { + newloc[0] = 0.0; newloc[1] = 0.0; + for (i = 0; i < 2 * numpoints; i = i + 2) + { + weight = 1.0 / numpoints; + if (i == k) + { + newloc[0] = newloc[0] + 0.1 * e * weight * points[i]; + newloc[1] = newloc[1] + 0.1 * e * weight * points[i + 1]; + } + else + { + weight = (1.0 - 0.1 * e * weight) / (double)(numpoints - 1.0); + newloc[0] = newloc[0] + weight * points[i]; + newloc[1] = newloc[1] + weight * points[i + 1]; + } + + } + numBadTriangle = 0; + for (j = 0; j < numpoints * 2 - 2; j = j + 2) + { + if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) + { + numBadTriangle++; + } + } + if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) + { + numBadTriangle++; + } + + if (numBadTriangle == 0) + { + + return true; + } + } + } + } + else + { + //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); + // for(i = 0; i < 2*numpolypoints; i = i+2){ + // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); + // } + // printf("numpoints %d\n",numpoints); + return true; + } + } + + + return false; + } + + /// + /// Check polygon for min angle. + /// + /// + /// + /// Returns true if the polygon has angles greater than 2*minangle. + private bool ValidPolygonAngles(int numpoints, double[] points) + { + int i;//,j + for (i = 0; i < numpoints; i++) + { + if (i == numpoints - 1) + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[0], points[1], points[2], points[3])) + { + return false; // one of the inner angles is less than required + } + } + else if (i == numpoints - 2) + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[0], points[1])) + { + return false; // one of the inner angles is less than required + } + } + else + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[(i + 2) * 2], points[(i + 2) * 2 + 1])) + { + return false; // one of the inner angles is less than required + } + } + } + return true; // all angles are valid + } + + /// + /// Given three coordinates of a polygon, tests to see if it satisfies the minimum + /// angle condition for relocation. + /// + /// + /// + /// + /// + /// + /// + /// Returns true, if it is a BAD polygon corner, returns false if it is a GOOD + /// polygon corner + private bool IsBadPolygonAngle(double x1, double y1, + double x2, double y2, double x3, double y3) + { + // variables keeping the distance values for the edges + double dx12, dy12, dx23, dy23, dx31, dy31; + double dist12, dist23, dist31; + + double cosAngle; // in order to check minimum angle condition + + // calculate the side lengths + + dx12 = x1 - x2; + dy12 = y1 - y2; + dx23 = x2 - x3; + dy23 = y2 - y3; + dx31 = x3 - x1; + dy31 = y3 - y1; + // calculate the squares of the side lentghs + dist12 = dx12 * dx12 + dy12 * dy12; + dist23 = dx23 * dx23 + dy23 * dy23; + dist31 = dx31 * dx31 + dy31 * dy31; + + /// calculate cosine of largest angle /// + cosAngle = (dist12 + dist23 - dist31) / (2 * Math.Sqrt(dist12) * Math.Sqrt(dist23)); + // Check whether the angle is smaller than permitted which is 2*minangle!!! + //printf("angle: %f 2*minangle = %f\n",acos(cosAngle)*180/PI, 2*acos(Math.Sqrt(b.goodangle))*180/PI); + if (Math.Acos(cosAngle) < 2 * Math.Acos(Math.Sqrt(behavior.goodAngle))) + { + return true;// it is a BAD triangle + } + return false;// it is a GOOD triangle + + } + + /// + /// Given four points representing two lines, returns the intersection point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The intersection point. + /// + // referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ + /// + private void LineLineIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4, ref double[] p) + { + // x1,y1 P1 coordinates (point of line 1) + // x2,y2 P2 coordinates (point of line 1) + // x3,y3 P3 coordinates (point of line 2) + // x4,y4 P4 coordinates (point of line 2) + // p[1],p[2] intersection coordinates + // + // This function returns a pointer array which first index indicates + // weather they intersect on one point or not, followed by coordinate pairs. + + double u_a, u_b, denom; + + // calculate denominator first + denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + // if denominator and numerator equal to zero, lines are coincident + if (Math.Abs(denom - 0.0) < EPS && (Math.Abs(u_b - 0.0) < EPS && Math.Abs(u_a - 0.0) < EPS)) + { + p[0] = 0.0; + } + // if denominator equals to zero, lines are parallel + else if (Math.Abs(denom - 0.0) < EPS) + { + p[0] = 0.0; + } + else + { + p[0] = 1.0; + u_a = u_a / denom; + u_b = u_b / denom; + p[1] = x1 + u_a * (x2 - x1); // not the intersection point + p[2] = y1 + u_a * (y2 - y1); + } + } + + /// + /// Returns the convex polygon which is the intersection of the given convex + /// polygon with the halfplane on the left side (regarding the directional vector) + /// of the given line. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int HalfPlaneIntersection(int numvertices, ref double[] convexPoly, double x1, double y1, double x2, double y2) + { + double dx, dy; // direction of the line + double z, min, max; + int i, j; + + double[][] polys = new double[3][]; + polys[0] = new double[2]; + polys[1] = new double[2]; + polys[2] = new double[2]; + + int numpolys; + double[] res = null; + int count = 0; + int intFound = 0; + dx = x2 - x1; + dy = y2 - y1; + numpolys = SplitConvexPolygon(numvertices, convexPoly, x1, y1, x2, y2, ref polys); + + if (numpolys == 3) + { + count = numvertices; + } + else + { + for (i = 0; i < numpolys; i++) + { + min = 99999999999999999; + max = -99999999999999999; + // compute the minimum and maximum of the + // third coordinate of the cross product + for (j = 1; j <= 2 * polys[i][0] - 1; j = j + 2) + { + z = dx * (polys[i][j + 1] - y1) - dy * (polys[i][j] - x1); + min = (z < min ? z : min); + max = (z > max ? z : max); + } + // ... and choose the (absolute) greater of both + z = (Math.Abs(min) > Math.Abs(max) ? min : max); + // and if it is positive, the polygon polys[i] + // is on the left side of line + if (z > 0.0) + { + res = polys[i]; + intFound = 1; + break; + } + } + if (intFound == 1) + { + while (count < res[0]) + { + convexPoly[2 * count] = res[2 * count + 1]; + convexPoly[2 * count + 1] = res[2 * count + 2]; + count++; + + } + } + } + // update convexPoly + return count; + } + + /// + /// Splits a convex polygons into one or two polygons through the intersection + /// with the given line (regarding the directional vector of the given line). + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int SplitConvexPolygon(int numvertices, double[] convexPoly, double x1, double y1, double x2, double y2, ref double[][] polys) + { + // state = 0: before the first intersection (with the line) + // state = 1: after the first intersection (with the line) + // state = 2: after the second intersection (with the line) + + int state = 0; + double[] p = new double[3]; + // poly1 is constructed in states 0 and 2 + double[] poly1 = new double[100]; + int poly1counter = 0; + // poly2 is constructed in state 1 + double[] poly2 = new double[100]; + int poly2counter = 0; + int numpolys; + int i; + double compConst = 0.000000000001; + // for debugging + int case1 = 0, case2 = 0, case3 = 0, case31 = 0, case32 = 0, case33 = 0, case311 = 0, case3111 = 0; + // intersect all edges of poly with line + for (i = 0; i < 2 * numvertices; i = i + 2) + { + int j = (i + 2 >= 2 * numvertices) ? 0 : i + 2; + LineLineSegmentIntersection(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1], convexPoly[j], convexPoly[j + 1], ref p); + // if this edge does not intersect with line + if (Math.Abs(p[0] - 0.0) <= compConst) + { + //System.out.println("null"); + // add p[j] to the proper polygon + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + else + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + // debug + case1++; + } + // ... or if the intersection is the whole edge + else if (Math.Abs(p[0] - 2.0) <= compConst) + { + //System.out.println(o); + // then we can not reach state 1 and 2 + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + // debug + case2++; + } + // ... or if the intersection is a point + else + { + // debug + case3++; + // if the point is the second vertex of the edge + if (Math.Abs(p[1] - convexPoly[j]) <= compConst && Math.Abs(p[2] - convexPoly[j + 1]) <= compConst) + { + // debug + case31++; + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + state++; + } + else if (state == 0) + { + // debug + case311++; + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + // test whether the polygon is splitted + // or the line only touches the polygon + if (i + 4 < 2 * numvertices) + { + int s1 = LinePointLocation(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1]); + int s2 = LinePointLocation(x1, y1, x2, y2, convexPoly[i + 4], convexPoly[i + 5]); + // the line only splits the polygon + // when the previous and next vertex lie + // on different sides of the line + if (s1 != s2 && s1 != 0 && s2 != 0) + { + // debug + case3111++; + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + state++; + } + } + } + } + // ... if the point is not the other vertex of the edge + else if (!(Math.Abs(p[1] - convexPoly[i]) <= compConst && Math.Abs(p[2] - convexPoly[i + 1]) <= compConst)) + { + // debug + case32++; + poly1counter++; + poly1[2 * poly1counter - 1] = p[1]; + poly1[2 * poly1counter] = p[2]; + poly2counter++; + poly2[2 * poly2counter - 1] = p[1]; + poly2[2 * poly2counter] = p[2]; + if (state == 1) + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + else if (state == 0) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + state++; + } + // ... else if the point is the second vertex of the edge + else + { + // debug + case33++; + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + else + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + } + } + } + // after splitting the state must be 0 or 2 + // (depending whether the polygon was splitted or not) + if (state != 0 && state != 2) + { + // printf("there is something wrong state: %d\n", state); + // printf("polygon might not be convex!!\n"); + // printf("case1: %d\ncase2: %d\ncase3: %d\ncase31: %d case311: %d case3111: %d\ncase32: %d\ncase33: %d\n", case1, case2, case3, case31, case311, case3111, case32, case33); + // printf("numvertices %d\n=============\n", numvertices); + // if there is something wrong with the intersection, just ignore this one + numpolys = 3; + } + else + { + // finally convert the vertex lists into convex polygons + numpolys = (state == 0) ? 1 : 2; + poly1[0] = poly1counter; + poly2[0] = poly2counter; + // convert the first convex polygon + polys[0] = poly1; + // convert the second convex polygon + if (state == 2) + { + polys[1] = poly2; + } + } + return numpolys; + } + + /// + /// Determines on which side (relative to the direction) of the given line and the + /// point lies (regarding the directional vector) of the given line. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int LinePointLocation(double x1, double y1, double x2, double y2, double x, double y) + { + double z; + if (Math.Atan((y2 - y1) / (x2 - x1)) * 180.0 / Math.PI == 90.0) + { + if (Math.Abs(x1 - x) <= 0.00000000001) + return 0; + } + else + { + if (Math.Abs(y1 + (((y2 - y1) * (x - x1)) / (x2 - x1)) - y) <= EPS) + return 0; + } + // third component of the 3 dimensional product + z = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1); + if (Math.Abs(z - 0.0) <= 0.00000000001) + { + return 0; + } + else if (z > 0) + { + return 1; + } + else + { + return 2; + } + } + + /// + /// Given four points representing one line and a line segment, returns the intersection point + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ + /// + private void LineLineSegmentIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4, ref double[] p) + { + // x1,y1 P1 coordinates (point of line) + // x2,y2 P2 coordinates (point of line) + // x3,y3 P3 coordinates (point of line segment) + // x4,y4 P4 coordinates (point of line segment) + // p[1],p[2] intersection coordinates + // + // This function returns a pointer array which first index indicates + // weather they intersect on one point or not, followed by coordinate pairs. + + double u_a, u_b, denom; + double compConst = 0.0000000000001; + // calculate denominator first + denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + + + //if(fabs(denom-0.0) < compConst && (fabs(u_b-0.0) < compConst && fabs(u_a-0.0) < compConst)){ + //printf("denom %.20f u_b %.20f u_a %.20f\n",denom, u_b, u_a); + if (Math.Abs(denom - 0.0) < compConst) + { + if (Math.Abs(u_b - 0.0) < compConst && Math.Abs(u_a - 0.0) < compConst) + { + p[0] = 2.0; // if denominator and numerator equal to zero, lines are coincident + } + else + { + p[0] = 0.0;// if denominator equals to zero, lines are parallel + } + + } + else + { + u_b = u_b / denom; + u_a = u_a / denom; + // printf("u_b %.20f\n", u_b); + if (u_b < -compConst || u_b > 1.0 + compConst) + { // check if it is on the line segment + // printf("line (%.20f, %.20f) (%.20f, %.20f) line seg (%.20f, %.20f) (%.20f, %.20f) \n",x1, y1 ,x2, y2 ,x3, y3 , x4, y4); + p[0] = 0.0; + } + else + { + p[0] = 1.0; + p[1] = x1 + u_a * (x2 - x1); // intersection point + p[2] = y1 + u_a * (y2 - y1); + } + } + + } + + /// + /// Returns the centroid of a given polygon + /// + /// + /// + /// Centroid of a given polygon + private void FindPolyCentroid(int numpoints, double[] points, ref double[] centroid) + { + int i; + //double area = 0.0;//, temp + centroid[0] = 0.0; centroid[1] = 0.0; + + for (i = 0; i < 2 * numpoints; i = i + 2) + { + + centroid[0] = centroid[0] + points[i]; + centroid[1] = centroid[1] + points[i + 1]; + + } + centroid[0] = centroid[0] / numpoints; + centroid[1] = centroid[1] / numpoints; + } + + /// + /// Given two points representing a line and a radius together with a center point + /// representing a circle, returns the intersection points. + /// + /// + /// + /// + /// + /// + /// + /// + /// Pointer to list of intersection points + /// + /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/ + /// + private void CircleLineIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, double r, ref double[] p) + { + // x1,y1 P1 coordinates [point of line] + // x2,y2 P2 coordinates [point of line] + // x3,y3, r P3 coordinates(circle center) and radius [circle] + // p[1],p[2]; p[3],p[4] intersection coordinates + // + // This function returns a pointer array which first index indicates + // the number of intersection points, followed by coordinate pairs. + + //double x , y ; + double a, b, c, mu, i; + + a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + b = 2 * ((x2 - x1) * (x1 - x3) + (y2 - y1) * (y1 - y3)); + c = x3 * x3 + y3 * y3 + x1 * x1 + y1 * y1 - 2 * (x3 * x1 + y3 * y1) - r * r; + i = b * b - 4 * a * c; + + if (i < 0.0) + { + // no intersection + p[0] = 0.0; + } + else if (Math.Abs(i - 0.0) < EPS) + { + // one intersection + p[0] = 1.0; + + mu = -b / (2 * a); + p[1] = x1 + mu * (x2 - x1); + p[2] = y1 + mu * (y2 - y1); + + } + else if (i > 0.0 && !(Math.Abs(a - 0.0) < EPS)) + { + // two intersections + p[0] = 2.0; + // first intersection + mu = (-b + Math.Sqrt(i)) / (2 * a); + p[1] = x1 + mu * (x2 - x1); + p[2] = y1 + mu * (y2 - y1); + // second intersection + mu = (-b - Math.Sqrt(i)) / (2 * a); + p[3] = x1 + mu * (x2 - x1); + p[4] = y1 + mu * (y2 - y1); + + + } + else + { + p[0] = 0.0; + } + } + + /// + /// Given three points, check if the point is the correct point that we are looking for. + /// + /// P1 coordinates (bisector point of dual edge on triangle) + /// P1 coordinates (bisector point of dual edge on triangle) + /// P2 coordinates (intersection point) + /// P2 coordinates (intersection point) + /// P3 coordinates (circumcenter point) + /// P3 coordinates (circumcenter point) + /// + /// Returns true, if given point is the correct one otherwise return false. + private bool ChooseCorrectPoint( + double x1, double y1, + double x2, double y2, + double x3, double y3, bool isObtuse) + { + double d1, d2; + bool p; + + // squared distance between circumcenter and intersection point + d1 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); + // squared distance between bisector point and intersection point + d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + + if (isObtuse) + { + // obtuse case + if (d2 >= d1) + { + p = true; // means we have found the right point + } + else + { + p = false; // means take the other point + } + } + else + { + // non-obtuse case + if (d2 < d1) + { + p = true; // means we have found the right point + } + else + { + p = false; // means take the other point + } + } + /// HANDLE RIGHT TRIANGLE CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!! + return p; + + } + + /// + /// This function returns a pointer array which first index indicates the whether + /// the point is in between the other points, followed by coordinate pairs. + /// + /// P1 coordinates [point of line] (point on Voronoi edge - intersection) + /// P1 coordinates [point of line] (point on Voronoi edge - intersection) + /// P2 coordinates [point of line] (circumcenter) + /// P2 coordinates [point of line] (circumcenter) + /// P3 coordinates [point to be compared] (neighbor's circumcenter) + /// P3 coordinates [point to be compared] (neighbor's circumcenter) + /// + private void PointBetweenPoints(double x1, double y1, double x2, double y2, double x, double y, ref double[] p) + { + // now check whether the point is close to circumcenter than intersection point + // BETWEEN THE POINTS + if ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y) < (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + { + p[0] = 1.0; + // calculate the squared distance to circumcenter + p[1] = (x - x2) * (x - x2) + (y - y2) * (y - y2); + p[2] = x; + p[3] = y; + }// *NOT* BETWEEN THE POINTS + else + { + p[0] = 0.0; + p[1] = 0.0; + p[2] = 0.0; + p[3] = 0.0; + } + } + + /// + /// Given three coordinates of a triangle, tests a triangle to see if it satisfies + /// the minimum and/or maximum angle condition. + /// + /// + /// + /// + /// + /// + /// + /// Returns true, if it is a BAD triangle, returns false if it is a GOOD triangle. + private bool IsBadTriangleAngle(double x1, double y1, double x2, double y2, double x3, double y3) + { + // variables keeping the distance values for the edges + double dxod, dyod, dxda, dyda, dxao, dyao; + double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; + + double apexlen, orglen, destlen, minedge; + double angle; // in order to check minimum angle condition + + double maxangle, maxedge; // in order to check minimum angle condition + // calculate the side lengths + + dxod = x1 - x2; + dyod = y1 - y2; + dxda = x2 - x3; + dyda = y2 - y3; + dxao = x3 - x1; + dyao = y3 - y1; + // calculate the squares of the side lentghs + dxod2 = dxod * dxod; + dyod2 = dyod * dyod; + dxda2 = dxda * dxda; + dyda2 = dyda * dyda; + dxao2 = dxao * dxao; + dyao2 = dyao * dyao; + + // Find the lengths of the triangle's three edges. + apexlen = dxod2 + dyod2; + orglen = dxda2 + dyda2; + destlen = dxao2 + dyao2; + + // try to find the minimum edge and accordingly the pqr orientation + if ((apexlen < orglen) && (apexlen < destlen)) + { + // The edge opposite the apex is shortest. + minedge = apexlen; + // Find the square of the cosine of the angle at the apex. + angle = dxda * dxao + dyda * dyao; + angle = angle * angle / (orglen * destlen); + + + } + else if (orglen < destlen) + { + // The edge opposite the origin is shortest. + minedge = orglen; + // Find the square of the cosine of the angle at the origin. + angle = dxod * dxao + dyod * dyao; + angle = angle * angle / (apexlen * destlen); + + + } + else + { + // The edge opposite the destination is shortest. + minedge = destlen; + // Find the square of the cosine of the angle at the destination. + angle = dxod * dxda + dyod * dyda; + angle = angle * angle / (apexlen * orglen); + + } + // try to find the maximum edge and accordingly the pqr orientation + if ((apexlen > orglen) && (apexlen > destlen)) + { + // The edge opposite the apex is longest. + maxedge = apexlen; + // Find the cosine of the angle at the apex. + maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); + } + else if (orglen > destlen) + { + // The edge opposite the origin is longest. + maxedge = orglen; + // Find the cosine of the angle at the origin. + maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); + } + else + { + // The edge opposite the destination is longest. + maxedge = destlen; + // Find the cosine of the angle at the destination. + maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); + } + + + // Check whether the angle is smaller than permitted. + if ((angle > behavior.goodAngle) || (behavior.MaxAngle != 0.00 && maxangle < behavior.maxGoodAngle)) + { + return true;// it is a bad triangle + } + return false;// it is a good triangle + + } + + /// + /// Given the triangulation, and a vertex returns the minimum distance to the + /// vertices of the triangle where the given vertex located. + /// + /// + /// + /// + /// + private double MinDistanceToNeighbor(double newlocX, double newlocY, ref Otri searchtri) + { + Otri horiz = default(Otri); // for search operation + LocateResult intersect = LocateResult.Outside; + Vertex v1, v2, v3, torg, tdest; + double d1, d2, d3, ahead; + //triangle ptr; // Temporary variable used by sym(). + + Point newvertex = new Point(newlocX, newlocY); + + // printf("newvertex %f,%f\n", newvertex[0], newvertex[1]); + // Find the location of the vertex to be inserted. Check if a good + // starting triangle has already been provided by the caller. + // Find a boundary triangle. + //horiz.tri = m.dummytri; + //horiz.orient = 0; + //horiz.symself(); + // Search for a triangle containing 'newvertex'. + // Start searching from the triangle provided by the caller. + // Where are we? + torg = searchtri.Org(); + tdest = searchtri.Dest(); + // Check the starting triangle's vertices. + if ((torg.x == newvertex.x) && (torg.y == newvertex.y)) + { + intersect = LocateResult.OnVertex; + searchtri.Copy(ref horiz); + + } + else if ((tdest.x == newvertex.x) && (tdest.y == newvertex.y)) + { + searchtri.LnextSelf(); + intersect = LocateResult.OnVertex; + searchtri.Copy(ref horiz); + } + else + { + // Orient 'searchtri' to fit the preconditions of calling preciselocate(). + ahead = Primitives.CounterClockwise(torg, tdest, newvertex); + if (ahead < 0.0) + { + // Turn around so that 'searchpoint' is to the left of the + // edge specified by 'searchtri'. + searchtri.SymSelf(); + searchtri.Copy(ref horiz); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); + } + else if (ahead == 0.0) + { + // Check if 'searchpoint' is between 'torg' and 'tdest'. + if (((torg.x < newvertex.x) == (newvertex.x < tdest.x)) && + ((torg.y < newvertex.y) == (newvertex.y < tdest.y))) + { + intersect = LocateResult.OnEdge; + searchtri.Copy(ref horiz); + + } + } + else + { + searchtri.Copy(ref horiz); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); + } + } + if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside) + { + // set distance to 0 + //m.VertexDealloc(newvertex); + return 0.0; + } + else + { // intersect == ONEDGE || intersect == INTRIANGLE + // find the triangle vertices + v1 = horiz.Org(); + v2 = horiz.Dest(); + v3 = horiz.Apex(); + d1 = (v1.x - newvertex.x) * (v1.x - newvertex.x) + (v1.y - newvertex.y) * (v1.y - newvertex.y); + d2 = (v2.x - newvertex.x) * (v2.x - newvertex.x) + (v2.y - newvertex.y) * (v2.y - newvertex.y); + d3 = (v3.x - newvertex.x) * (v3.x - newvertex.x) + (v3.y - newvertex.y) * (v3.y - newvertex.y); + //m.VertexDealloc(newvertex); + // find minimum of the distance + if (d1 <= d2 && d1 <= d3) + { + return d1; + } + else if (d2 <= d3) + { + return d2; + } + else + { + return d3; + } + } + } + } +} \ No newline at end of file diff --git a/ThirdParty/Triangle/Primitives.cs b/ThirdParty/Triangle/Primitives.cs new file mode 100644 index 0000000..4ba435e --- /dev/null +++ b/ThirdParty/Triangle/Primitives.cs @@ -0,0 +1,488 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Data; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Provides some primitives regularly used in computational geometry. + /// + public static class Primitives + { + static double splitter; // Used to split double factors for exact multiplication. + static double epsilon; // Floating-point machine epsilon. + //static double resulterrbound; + static double ccwerrboundA; // ccwerrboundB, ccwerrboundC; + static double iccerrboundA; // iccerrboundB, iccerrboundC; + + /// + /// Initialize the variables used for exact arithmetic. + /// + /// + /// 'epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in + /// floating-point arithmetic. 'epsilon' bounds the relative roundoff + /// error. It is used for floating-point error analysis. + /// + /// 'splitter' is used to split floating-point numbers into two half- + /// length significands for exact multiplication. + /// + /// I imagine that a highly optimizing compiler might be too smart for its + /// own good, and somehow cause this routine to fail, if it pretends that + /// floating-point arithmetic is too much like real arithmetic. + /// + /// Don't change this routine unless you fully understand it. + /// + public static void ExactInit() + { + double half; + double check, lastcheck; + bool every_other; + + every_other = true; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + // Repeatedly divide 'epsilon' by two until it is too small to add to + // one without causing roundoff. (Also check if the sum is equal to + // the previous sum, for machines that round up instead of using exact + // rounding. Not that these routines will work on such machines.) + do + { + lastcheck = check; + epsilon *= half; + if (every_other) + { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + // Error bounds for orientation and incircle tests. + //resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + //ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + //ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + //iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + //iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + } + + /// + /// Check, if the three points appear in counterclockwise order. The result is + /// also a rough approximation of twice the signed area of the triangle defined + /// by the three points. + /// + /// Point a. + /// Point b. + /// Point c. + /// Return a positive value if the points pa, pb, and pc occur in + /// counterclockwise order; a negative value if they occur in clockwise order; + /// and zero if they are collinear. + /// + /// Uses exact arithmetic if necessary to ensure a correct answer. The + /// result returned is the determinant of a matrix. This determinant is + /// computed adaptively, in the sense that exact arithmetic is used only to + /// the degree it is needed to ensure that the returned value has the + /// correct sign. Hence, this function is usually quite fast, but will run + /// more slowly when the input points are collinear or nearly so. + /// + /// See Robust Predicates paper for details. + /// + public static double CounterClockwise(Point pa, Point pb, Point pc) + { + double detleft, detright, det; + double detsum, errbound; + + Statistic.CounterClockwiseCount++; + + detleft = (pa.x - pc.x) * (pb.y - pc.y); + detright = (pa.y - pc.y) * (pb.x - pc.x); + det = detleft - detright; + + if (Behavior.NoExact) + { + return det; + } + + if (detleft > 0.0) + { + if (detright <= 0.0) + { + return det; + } + else + { + detsum = detleft + detright; + } + } + else if (detleft < 0.0) + { + if (detright >= 0.0) + { + return det; + } + else + { + detsum = -detleft - detright; + } + } + else + { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + return (double)CounterClockwiseDecimal(pa, pb, pc); + } + + private static decimal CounterClockwiseDecimal(Point pa, Point pb, Point pc) + { + Statistic.CounterClockwiseCountDecimal++; + + decimal detleft, detright, det, detsum; + + detleft = ((decimal)pa.x - (decimal)pc.x) * ((decimal)pb.y - (decimal)pc.y); + detright = ((decimal)pa.y - (decimal)pc.y) * ((decimal)pb.x - (decimal)pc.x); + det = detleft - detright; + + if (detleft > 0.0m) + { + if (detright <= 0.0m) + { + return det; + } + else + { + detsum = detleft + detright; + } + } + else if (detleft < 0.0m) + { + if (detright >= 0.0m) + { + return det; + } + else + { + detsum = -detleft - detright; + } + } + + return det; + } + + /// + /// Check if the point pd lies inside the circle passing through pa, pb, and pc. The + /// points pa, pb, and pc must be in counterclockwise order, or the sign of the result + /// will be reversed. + /// + /// Point a. + /// Point b. + /// Point c. + /// Point d. + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. + /// + /// Uses exact arithmetic if necessary to ensure a correct answer. The + /// result returned is the determinant of a matrix. This determinant is + /// computed adaptively, in the sense that exact arithmetic is used only to + /// the degree it is needed to ensure that the returned value has the + /// correct sign. Hence, this function is usually quite fast, but will run + /// more slowly when the input points are cocircular or nearly so. + /// + /// See Robust Predicates paper for details. + /// + public static double InCircle(Point pa, Point pb, Point pc, Point pd) + { + double adx, bdx, cdx, ady, bdy, cdy; + double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + double alift, blift, clift; + double det; + double permanent, errbound; + + Statistic.InCircleCount++; + + adx = pa.x - pd.x; + bdx = pb.x - pd.x; + cdx = pc.x - pd.x; + ady = pa.y - pd.y; + bdy = pb.y - pd.y; + cdy = pc.y - pd.y; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + if (Behavior.NoExact) + { + return det; + } + + permanent = (Math.Abs(bdxcdy) + Math.Abs(cdxbdy)) * alift + + (Math.Abs(cdxady) + Math.Abs(adxcdy)) * blift + + (Math.Abs(adxbdy) + Math.Abs(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) + { + return det; + } + + return (double)InCircleDecimal(pa, pb, pc, pd); + } + + private static decimal InCircleDecimal(Point pa, Point pb, Point pc, Point pd) + { + Statistic.InCircleCountDecimal++; + + decimal adx, bdx, cdx, ady, bdy, cdy; + decimal bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + decimal alift, blift, clift; + + adx = (decimal)pa.x - (decimal)pd.x; + bdx = (decimal)pb.x - (decimal)pd.x; + cdx = (decimal)pc.x - (decimal)pd.x; + ady = (decimal)pa.y - (decimal)pd.y; + bdy = (decimal)pb.y - (decimal)pd.y; + cdy = (decimal)pc.y - (decimal)pd.y; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + return alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + } + + /// + /// Return a positive value if the point pd is incompatible with the circle + /// or plane passing through pa, pb, and pc (meaning that pd is inside the + /// circle or below the plane); a negative value if it is compatible; and + /// zero if the four points are cocircular/coplanar. The points pa, pb, and + /// pc must be in counterclockwise order, or the sign of the result will be + /// reversed. + /// + /// Point a. + /// Point b. + /// Point c. + /// Point d. + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. + public static double NonRegular(Point pa, Point pb, Point pc, Point pd) + { + return InCircle(pa, pb, pc, pd); + } + + /// + /// Find the circumcenter of a triangle. + /// + /// Triangle point. + /// Triangle point. + /// Triangle point. + /// Relative coordinate of new location. + /// Relative coordinate of new location. + /// Off-center constant. + /// Coordinates of the circumcenter (or off-center) + public static Point FindCircumcenter(Point torg, Point tdest, Point tapex, + ref double xi, ref double eta, double offconstant) + { + double xdo, ydo, xao, yao; + double dodist, aodist, dadist; + double denominator; + double dx, dy, dxoff, dyoff; + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + if ((dodist < aodist) && (dodist < dadist)) + { + if (offconstant > 0.0) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xdo - offconstant * ydo; + dyoff = 0.5 * ydo + offconstant * xdo; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + } + } + else if (aodist < dadist) + { + if (offconstant > 0.0) + { + dxoff = 0.5 * xao + offconstant * yao; + dyoff = 0.5 * yao - offconstant * xao; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + } + } + else + { + if (offconstant > 0.0) + { + dxoff = 0.5 * (tapex.x - tdest.x) - offconstant * (tapex.y - tdest.y); + dyoff = 0.5 * (tapex.y - tdest.y) + offconstant * (tapex.x - tdest.x); + // If the off-center is closer to the destination than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + } + } + + // To interpolate vertex attributes for the new vertex inserted at + // the circumcenter, define a coordinate system with a xi-axis, + // directed from the triangle's origin to its destination, and + // an eta-axis, directed from its origin to its apex. + // Calculate the xi and eta coordinates of the circumcenter. + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return new Point(torg.x + dx, torg.y + dy); + } + + /// + /// Find the circumcenter of a triangle. + /// + /// Triangle point. + /// Triangle point. + /// Triangle point. + /// Relative coordinate of new location. + /// Relative coordinate of new location. + /// Coordinates of the circumcenter + /// + /// The result is returned both in terms of x-y coordinates and xi-eta + /// (barycentric) coordinates. The xi-eta coordinate system is defined in + /// terms of the triangle: the origin of the triangle is the origin of the + /// coordinate system; the destination of the triangle is one unit along the + /// xi axis; and the apex of the triangle is one unit along the eta axis. + /// This procedure also returns the square of the length of the triangle's + /// shortest edge. + /// + public static Point FindCircumcenter(Point torg, Point tdest, Point tapex, + ref double xi, ref double eta) + { + double xdo, ydo, xao, yao; + double dodist, aodist; + double denominator; + double dx, dy; + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + + // To interpolate vertex attributes for the new vertex inserted at + // the circumcenter, define a coordinate system with a xi-axis, + // directed from the triangle's origin to its destination, and + // an eta-axis, directed from its origin to its apex. + // Calculate the xi and eta coordinates of the circumcenter. + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return new Point(torg.x + dx, torg.y + dy); + } + } +} diff --git a/ThirdParty/Triangle/Properties/AssemblyInfo.cs b/ThirdParty/Triangle/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c6c70bd --- /dev/null +++ b/ThirdParty/Triangle/Properties/AssemblyInfo.cs @@ -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("Triangle")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Triangle")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[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("96a540d0-1772-4bed-8d25-ef5fa23cd1bc")] + +// 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")] diff --git a/ThirdParty/Triangle/Quality.cs b/ThirdParty/Triangle/Quality.cs new file mode 100644 index 0000000..ae2acc5 --- /dev/null +++ b/ThirdParty/Triangle/Quality.cs @@ -0,0 +1,1000 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Data; + using TriangleNet.Log; + using TriangleNet.Geometry; + + /// + /// Provides methods for mesh quality enforcement and testing. + /// + class Quality + { + Queue badsubsegs; + BadTriQueue queue; + Mesh mesh; + Behavior behavior; + + NewLocation newLocation; + + // Not used at the moment + Func userTest; + + ILog logger; + + public Quality(Mesh mesh) + { + logger = SimpleLog.Instance; + + badsubsegs = new Queue(); + queue = new BadTriQueue(); + + this.mesh = mesh; + this.behavior = mesh.behavior; + + newLocation = new NewLocation(mesh); + } + + /// + /// Add a bad subsegment to the queue. + /// + /// Bad subsegment. + public void AddBadSubseg(BadSubseg badseg) + { + badsubsegs.Enqueue(badseg); + } + + #region Check + + /// + /// Test the mesh for topological consistency. + /// + public bool CheckMesh() + { + Otri tri = default(Otri); + Otri oppotri = default(Otri), oppooppotri = default(Otri); + Vertex triorg, tridest, triapex; + Vertex oppoorg, oppodest; + int horrors; + bool saveexact; + + // Temporarily turn on exact arithmetic if it's off. + saveexact = Behavior.NoExact; + Behavior.NoExact = false; + horrors = 0; + + // Run through the list of triangles, checking each one. + foreach (var t in mesh.triangles.Values) + { + tri.triangle = t; + + // Check all three edges of the triangle. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + triorg = tri.Org(); + tridest = tri.Dest(); + if (tri.orient == 0) + { // Only test for inversion once. + // Test if the triangle is flat or inverted. + triapex = tri.Apex(); + if (Primitives.CounterClockwise(triorg, tridest, triapex) <= 0.0) + { + logger.Warning("Triangle is flat or inverted.", + "Quality.CheckMesh()"); + horrors++; + } + } + // Find the neighboring triangle on this edge. + tri.Sym(ref oppotri); + if (oppotri.triangle != Mesh.dummytri) + { + // Check that the triangle's neighbor knows it's a neighbor. + oppotri.Sym(ref oppooppotri); + if ((tri.triangle != oppooppotri.triangle) || (tri.orient != oppooppotri.orient)) + { + if (tri.triangle == oppooppotri.triangle) + { + logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)", + "Quality.CheckMesh()"); + } + + horrors++; + } + // Check that both triangles agree on the identities + // of their shared vertices. + oppoorg = oppotri.Org(); + oppodest = oppotri.Dest(); + if ((triorg != oppodest) || (tridest != oppoorg)) + { + logger.Warning("Mismatched edge coordinates between two triangles.", + "Quality.CheckMesh()"); + + horrors++; + } + } + } + } + + // Check for unconnected vertices + mesh.MakeVertexMap(); + foreach (var v in mesh.vertices.Values) + { + if (v.tri.triangle == null) + { + logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)", + "Quality.CheckMesh()"); + } + } + + if (horrors == 0) // && Behavior.Verbose + { + logger.Info("Mesh topology appears to be consistent."); + } + + // Restore the status of exact arithmetic. + Behavior.NoExact = saveexact; + + return (horrors == 0); + } + + /// + /// Ensure that the mesh is (constrained) Delaunay. + /// + public bool CheckDelaunay() + { + Otri loop = default(Otri); + Otri oppotri = default(Otri); + Osub opposubseg = default(Osub); + Vertex triorg, tridest, triapex; + Vertex oppoapex; + bool shouldbedelaunay; + int horrors; + bool saveexact; + + // Temporarily turn on exact arithmetic if it's off. + saveexact = Behavior.NoExact; + Behavior.NoExact = false; + horrors = 0; + + // Run through the list of triangles, checking each one. + foreach (var tri in mesh.triangles.Values) + { + loop.triangle = tri; + + // Check all three edges of the triangle. + for (loop.orient = 0; loop.orient < 3; + loop.orient++) + { + triorg = loop.Org(); + tridest = loop.Dest(); + triapex = loop.Apex(); + loop.Sym(ref oppotri); + oppoapex = oppotri.Apex(); + // Only test that the edge is locally Delaunay if there is an + // adjoining triangle whose pointer is larger (to ensure that + // each pair isn't tested twice). + shouldbedelaunay = (oppotri.triangle != Mesh.dummytri) && + !Otri.IsDead(oppotri.triangle) && loop.triangle.id < oppotri.triangle.id && + (triorg != mesh.infvertex1) && (triorg != mesh.infvertex2) && + (triorg != mesh.infvertex3) && + (tridest != mesh.infvertex1) && (tridest != mesh.infvertex2) && + (tridest != mesh.infvertex3) && + (triapex != mesh.infvertex1) && (triapex != mesh.infvertex2) && + (triapex != mesh.infvertex3) && + (oppoapex != mesh.infvertex1) && (oppoapex != mesh.infvertex2) && + (oppoapex != mesh.infvertex3); + if (mesh.checksegments && shouldbedelaunay) + { + // If a subsegment separates the triangles, then the edge is + // constrained, so no local Delaunay test should be done. + loop.SegPivot(ref opposubseg); + if (opposubseg.seg != Mesh.dummysub) + { + shouldbedelaunay = false; + } + } + if (shouldbedelaunay) + { + if (Primitives.NonRegular(triorg, tridest, triapex, oppoapex) > 0.0) + { + logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).", + loop.triangle.id, oppotri.triangle.id), "Quality.CheckDelaunay()"); + horrors++; + } + } + } + + } + + if (horrors == 0) // && Behavior.Verbose + { + logger.Info("Mesh is Delaunay."); + } + + // Restore the status of exact arithmetic. + Behavior.NoExact = saveexact; + + return (horrors == 0); + } + + /// + /// Check a subsegment to see if it is encroached; add it to the list if it is. + /// + /// The subsegment to check. + /// Returns a nonzero value if the subsegment is encroached. + /// + /// A subsegment is encroached if there is a vertex in its diametral lens. + /// For Ruppert's algorithm (-D switch), the "diametral lens" is the + /// diametral circle. For Chew's algorithm (default), the diametral lens is + /// just big enough to enclose two isosceles triangles whose bases are the + /// subsegment. Each of the two isosceles triangles has two angles equal + /// to 'b.minangle'. + /// + /// Chew's algorithm does not require diametral lenses at all--but they save + /// time. Any vertex inside a subsegment's diametral lens implies that the + /// triangle adjoining the subsegment will be too skinny, so it's only a + /// matter of time before the encroaching vertex is deleted by Chew's + /// algorithm. It's faster to simply not insert the doomed vertex in the + /// first place, which is why I use diametral lenses with Chew's algorithm. + /// + public int CheckSeg4Encroach(ref Osub testsubseg) + { + Otri neighbortri = default(Otri); + Osub testsym = default(Osub); + BadSubseg encroachedseg; + double dotproduct; + int encroached; + int sides; + Vertex eorg, edest, eapex; + + encroached = 0; + sides = 0; + + eorg = testsubseg.Org(); + edest = testsubseg.Dest(); + // Check one neighbor of the subsegment. + testsubseg.TriPivot(ref neighbortri); + // Does the neighbor exist, or is this a boundary edge? + if (neighbortri.triangle != Mesh.dummytri) + { + sides++; + // Find a vertex opposite this subsegment. + eapex = neighbortri.Apex(); + // Check whether the apex is in the diametral lens of the subsegment + // (the diametral circle if 'conformdel' is set). A dot product + // of two sides of the triangle is used to check whether the angle + // at the apex is greater than (180 - 2 'minangle') degrees (for + // lenses; 90 degrees for diametral circles). + dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y); + if (dotproduct < 0.0) + { + if (behavior.ConformingDelaunay || + (dotproduct * dotproduct >= + (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * + ((eorg.x - eapex.x) * (eorg.x - eapex.x) + + (eorg.y - eapex.y) * (eorg.y - eapex.y)) * + ((edest.x - eapex.x) * (edest.x - eapex.x) + + (edest.y - eapex.y) * (edest.y - eapex.y)))) + { + encroached = 1; + } + } + } + // Check the other neighbor of the subsegment. + testsubseg.Sym(ref testsym); + testsym.TriPivot(ref neighbortri); + // Does the neighbor exist, or is this a boundary edge? + if (neighbortri.triangle != Mesh.dummytri) + { + sides++; + // Find the other vertex opposite this subsegment. + eapex = neighbortri.Apex(); + // Check whether the apex is in the diametral lens of the subsegment + // (or the diametral circle, if 'conformdel' is set). + dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y); + if (dotproduct < 0.0) + { + if (behavior.ConformingDelaunay || + (dotproduct * dotproduct >= + (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * + ((eorg.x - eapex.x) * (eorg.x - eapex.x) + + (eorg.y - eapex.y) * (eorg.y - eapex.y)) * + ((edest.x - eapex.x) * (edest.x - eapex.x) + + (edest.y - eapex.y) * (edest.y - eapex.y)))) + { + encroached += 2; + } + } + } + + if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2)))) + { + // Add the subsegment to the list of encroached subsegments. + // Be sure to get the orientation right. + encroachedseg = new BadSubseg(); + if (encroached == 1) + { + encroachedseg.encsubseg = testsubseg; + encroachedseg.subsegorg = eorg; + encroachedseg.subsegdest = edest; + } + else + { + encroachedseg.encsubseg = testsym; + encroachedseg.subsegorg = edest; + encroachedseg.subsegdest = eorg; + } + + badsubsegs.Enqueue(encroachedseg); + } + + return encroached; + } + + /// + /// Test a triangle for quality and size. + /// + /// Triangle to check. + /// + /// Tests a triangle to see if it satisfies the minimum angle condition and + /// the maximum area condition. Triangles that aren't up to spec are added + /// to the bad triangle queue. + /// + public void TestTriangle(ref Otri testtri) + { + Otri tri1 = default(Otri), tri2 = default(Otri); + Osub testsub = default(Osub); + Vertex torg, tdest, tapex; + Vertex base1, base2; + Vertex org1, dest1, org2, dest2; + Vertex joinvertex; + double dxod, dyod, dxda, dyda, dxao, dyao; + double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; + double apexlen, orglen, destlen, minedge; + double angle; + double area; + double dist1, dist2; + + double maxangle; + + torg = testtri.Org(); + tdest = testtri.Dest(); + tapex = testtri.Apex(); + dxod = torg.x - tdest.x; + dyod = torg.y - tdest.y; + dxda = tdest.x - tapex.x; + dyda = tdest.y - tapex.y; + dxao = tapex.x - torg.x; + dyao = tapex.y - torg.y; + dxod2 = dxod * dxod; + dyod2 = dyod * dyod; + dxda2 = dxda * dxda; + dyda2 = dyda * dyda; + dxao2 = dxao * dxao; + dyao2 = dyao * dyao; + // Find the lengths of the triangle's three edges. + apexlen = dxod2 + dyod2; + orglen = dxda2 + dyda2; + destlen = dxao2 + dyao2; + + if ((apexlen < orglen) && (apexlen < destlen)) + { + // The edge opposite the apex is shortest. + minedge = apexlen; + // Find the square of the cosine of the angle at the apex. + angle = dxda * dxao + dyda * dyao; + angle = angle * angle / (orglen * destlen); + base1 = torg; + base2 = tdest; + testtri.Copy(ref tri1); + } + else if (orglen < destlen) + { + // The edge opposite the origin is shortest. + minedge = orglen; + // Find the square of the cosine of the angle at the origin. + angle = dxod * dxao + dyod * dyao; + angle = angle * angle / (apexlen * destlen); + base1 = tdest; + base2 = tapex; + testtri.Lnext(ref tri1); + } + else + { + // The edge opposite the destination is shortest. + minedge = destlen; + // Find the square of the cosine of the angle at the destination. + angle = dxod * dxda + dyod * dyda; + angle = angle * angle / (apexlen * orglen); + base1 = tapex; + base2 = torg; + testtri.Lprev(ref tri1); + } + + if (behavior.VarArea || behavior.fixedArea || behavior.Usertest) + { + // Check whether the area is larger than permitted. + area = 0.5 * (dxod * dyda - dyod * dxda); + if (behavior.fixedArea && (area > behavior.MaxArea)) + { + // Add this triangle to the list of bad triangles. + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + return; + } + + // Nonpositive area constraints are treated as unconstrained. + if ((behavior.VarArea) && (area > testtri.triangle.area) && (testtri.triangle.area > 0.0)) + { + // Add this triangle to the list of bad triangles. + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + return; + } + + // Check whether the user thinks this triangle is too large. + if (behavior.Usertest && userTest != null) + { + if (userTest(torg, tdest, tapex, area)) + { + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + return; + } + } + } + + // find the maximum edge and accordingly the pqr orientation + if ((apexlen > orglen) && (apexlen > destlen)) + { + // The edge opposite the apex is longest. + // maxedge = apexlen; + // Find the cosine of the angle at the apex. + maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); + } + else if (orglen > destlen) + { + // The edge opposite the origin is longest. + // maxedge = orglen; + // Find the cosine of the angle at the origin. + maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); + } + else + { + // The edge opposite the destination is longest. + // maxedge = destlen; + // Find the cosine of the angle at the destination. + maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); + } + + // Check whether the angle is smaller than permitted. + if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0)) + { + // Use the rules of Miller, Pav, and Walkington to decide that certain + // triangles should not be split, even if they have bad angles. + // A skinny triangle is not split if its shortest edge subtends a + // small input angle, and both endpoints of the edge lie on a + // concentric circular shell. For convenience, I make a small + // adjustment to that rule: I check if the endpoints of the edge + // both lie in segment interiors, equidistant from the apex where + // the two segments meet. + // First, check if both points lie in segment interiors. + if ((base1.type == VertexType.SegmentVertex) && + (base2.type == VertexType.SegmentVertex)) + { + // Check if both points lie in a common segment. If they do, the + // skinny triangle is enqueued to be split as usual. + tri1.SegPivot(ref testsub); + if (testsub.seg == Mesh.dummysub) + { + // No common segment. Find a subsegment that contains 'torg'. + tri1.Copy(ref tri2); + do + { + tri1.OprevSelf(); + tri1.SegPivot(ref testsub); + } while (testsub.seg == Mesh.dummysub); + // Find the endpoints of the containing segment. + org1 = testsub.SegOrg(); + dest1 = testsub.SegDest(); + // Find a subsegment that contains 'tdest'. + do + { + tri2.DnextSelf(); + tri2.SegPivot(ref testsub); + } while (testsub.seg == Mesh.dummysub); + // Find the endpoints of the containing segment. + org2 = testsub.SegOrg(); + dest2 = testsub.SegDest(); + // Check if the two containing segments have an endpoint in common. + joinvertex = null; + if ((dest1.x == org2.x) && (dest1.y == org2.y)) + { + joinvertex = dest1; + } + else if ((org1.x == dest2.x) && (org1.y == dest2.y)) + { + joinvertex = org1; + } + if (joinvertex != null) + { + // Compute the distance from the common endpoint (of the two + // segments) to each of the endpoints of the shortest edge. + dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) + + (base1.y - joinvertex.y) * (base1.y - joinvertex.y)); + dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) + + (base2.y - joinvertex.y) * (base2.y - joinvertex.y)); + // If the two distances are equal, don't split the triangle. + if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2)) + { + // Return now to avoid enqueueing the bad triangle. + return; + } + } + } + } + + // Add this triangle to the list of bad triangles. + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + } + } + + #endregion + + #region Maintanance + + /// + /// Traverse the entire list of subsegments, and check each to see if it + /// is encroached. If so, add it to the list. + /// + private void TallyEncs() + { + Osub subsegloop = default(Osub); + subsegloop.orient = 0; + + foreach (var seg in mesh.subsegs.Values) + { + subsegloop.seg = seg; + // If the segment is encroached, add it to the list. + CheckSeg4Encroach(ref subsegloop); + } + } + + /// + /// Split all the encroached subsegments. + /// + /// A flag that specifies whether one should take + /// note of new bad triangles that result from inserting vertices to repair + /// encroached subsegments. + /// + /// Each encroached subsegment is repaired by splitting it - inserting a + /// vertex at or near its midpoint. Newly inserted vertices may encroach + /// upon other subsegments; these are also repaired. + /// + private void SplitEncSegs(bool triflaws) + { + Otri enctri = default(Otri); + Otri testtri = default(Otri); + Osub testsh = default(Osub); + Osub currentenc = default(Osub); + BadSubseg seg; + Vertex eorg, edest, eapex; + Vertex newvertex; + InsertVertexResult success; + double segmentlength, nearestpoweroftwo; + double split; + double multiplier, divisor; + bool acuteorg, acuteorg2, acutedest, acutedest2; + + // Note that steinerleft == -1 if an unlimited number + // of Steiner points is allowed. + while (badsubsegs.Count > 0) + { + if (mesh.steinerleft == 0) + { + break; + } + + seg = badsubsegs.Dequeue(); + + currentenc = seg.encsubseg; + eorg = currentenc.Org(); + edest = currentenc.Dest(); + // Make sure that this segment is still the same segment it was + // when it was determined to be encroached. If the segment was + // enqueued multiple times (because several newly inserted + // vertices encroached it), it may have already been split. + if (!Osub.IsDead(currentenc.seg) && (eorg == seg.subsegorg) && (edest == seg.subsegdest)) + { + // To decide where to split a segment, we need to know if the + // segment shares an endpoint with an adjacent segment. + // The concern is that, if we simply split every encroached + // segment in its center, two adjacent segments with a small + // angle between them might lead to an infinite loop; each + // vertex added to split one segment will encroach upon the + // other segment, which must then be split with a vertex that + // will encroach upon the first segment, and so on forever. + // To avoid this, imagine a set of concentric circles, whose + // radii are powers of two, about each segment endpoint. + // These concentric circles determine where the segment is + // split. (If both endpoints are shared with adjacent + // segments, split the segment in the middle, and apply the + // concentric circles for later splittings.) + + // Is the origin shared with another segment? + currentenc.TriPivot(ref enctri); + enctri.Lnext(ref testtri); + testtri.SegPivot(ref testsh); + acuteorg = testsh.seg != Mesh.dummysub; + // Is the destination shared with another segment? + testtri.LnextSelf(); + testtri.SegPivot(ref testsh); + acutedest = testsh.seg != Mesh.dummysub; + + // If we're using Chew's algorithm (rather than Ruppert's) + // to define encroachment, delete free vertices from the + // subsegment's diametral circle. + if (!behavior.ConformingDelaunay && !acuteorg && !acutedest) + { + eapex = enctri.Apex(); + while ((eapex.type == VertexType.FreeVertex) && + ((eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) + { + mesh.DeleteVertex(ref testtri); + currentenc.TriPivot(ref enctri); + eapex = enctri.Apex(); + enctri.Lprev(ref testtri); + } + } + + // Now, check the other side of the segment, if there's a triangle there. + enctri.Sym(ref testtri); + if (testtri.triangle != Mesh.dummytri) + { + // Is the destination shared with another segment? + testtri.LnextSelf(); + testtri.SegPivot(ref testsh); + acutedest2 = testsh.seg != Mesh.dummysub; + acutedest = acutedest || acutedest2; + // Is the origin shared with another segment? + testtri.LnextSelf(); + testtri.SegPivot(ref testsh); + acuteorg2 = testsh.seg != Mesh.dummysub; + acuteorg = acuteorg || acuteorg2; + + // Delete free vertices from the subsegment's diametral circle. + if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2) + { + eapex = testtri.Org(); + while ((eapex.type == VertexType.FreeVertex) && + ((eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) + { + mesh.DeleteVertex(ref testtri); + enctri.Sym(ref testtri); + eapex = testtri.Apex(); + testtri.LprevSelf(); + } + } + } + + // Use the concentric circles if exactly one endpoint is shared + // with another adjacent segment. + if (acuteorg || acutedest) + { + segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) + + (edest.y - eorg.y) * (edest.y - eorg.y)); + // Find the power of two that most evenly splits the segment. + // The worst case is a 2:1 ratio between subsegment lengths. + nearestpoweroftwo = 1.0; + while (segmentlength > 3.0 * nearestpoweroftwo) + { + nearestpoweroftwo *= 2.0; + } + while (segmentlength < 1.5 * nearestpoweroftwo) + { + nearestpoweroftwo *= 0.5; + } + // Where do we split the segment? + split = nearestpoweroftwo / segmentlength; + if (acutedest) + { + split = 1.0 - split; + } + } + else + { + // If we're not worried about adjacent segments, split + // this segment in the middle. + split = 0.5; + } + + // Create the new vertex (interpolate coordinates). + newvertex = new Vertex( + eorg.x + split * (edest.x - eorg.x), + eorg.y + split * (edest.y - eorg.y), + currentenc.Mark(), + mesh.nextras); + + newvertex.type = VertexType.SegmentVertex; + + newvertex.hash = mesh.hash_vtx++; + newvertex.id = newvertex.hash; + + mesh.vertices.Add(newvertex.hash, newvertex); + + // Interpolate attributes. + for (int i = 0; i < mesh.nextras; i++) + { + newvertex.attributes[i] = eorg.attributes[i] + + split * (edest.attributes[i] - eorg.attributes[i]); + } + + if (!Behavior.NoExact) + { + // Roundoff in the above calculation may yield a 'newvertex' + // that is not precisely collinear with 'eorg' and 'edest'. + // Improve collinearity by one step of iterative refinement. + multiplier = Primitives.CounterClockwise(eorg, edest, newvertex); + divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) + + (eorg.y - edest.y) * (eorg.y - edest.y)); + if ((multiplier != 0.0) && (divisor != 0.0)) + { + multiplier = multiplier / divisor; + // Watch out for NANs. + if (!double.IsNaN(multiplier)) + { + newvertex.x += multiplier * (edest.y - eorg.y); + newvertex.y += multiplier * (eorg.x - edest.x); + } + } + } + + // Check whether the new vertex lies on an endpoint. + if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) || + ((newvertex.x == edest.x) && (newvertex.y == edest.y))) + { + + logger.Error("Ran out of precision: I attempted to split a" + + " segment to a smaller size than can be accommodated by" + + " the finite precision of floating point arithmetic.", + "Quality.SplitEncSegs()"); + + throw new Exception("Ran out of precision"); + } + // Insert the splitting vertex. This should always succeed. + success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws); + if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching)) + { + logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()"); + throw new Exception("Failure to split a segment."); + } + if (mesh.steinerleft > 0) + { + mesh.steinerleft--; + } + // Check the two new subsegments to see if they're encroached. + CheckSeg4Encroach(ref currentenc); + currentenc.NextSelf(); + CheckSeg4Encroach(ref currentenc); + } + + // Set subsegment's origin to NULL. This makes it possible to detect dead + // badsubsegs when traversing the list of all badsubsegs. + seg.subsegorg = null; + } + } + + /// + /// Test every triangle in the mesh for quality measures. + /// + private void TallyFaces() + { + Otri triangleloop = default(Otri); + triangleloop.orient = 0; + + foreach (var tri in mesh.triangles.Values) + { + triangleloop.triangle = tri; + + // If the triangle is bad, enqueue it. + TestTriangle(ref triangleloop); + } + } + + /// + /// Inserts a vertex at the circumcenter of a triangle. Deletes + /// the newly inserted vertex if it encroaches upon a segment. + /// + /// + private void SplitTriangle(BadTriangle badtri) + { + Otri badotri = default(Otri); + Vertex borg, bdest, bapex; + Point newloc; // Location of the new vertex + double xi = 0, eta = 0; + InsertVertexResult success; + bool errorflag; + + badotri = badtri.poortri; + borg = badotri.Org(); + bdest = badotri.Dest(); + bapex = badotri.Apex(); + + // Make sure that this triangle is still the same triangle it was + // when it was tested and determined to be of bad quality. + // Subsequent transformations may have made it a different triangle. + if (!Otri.IsDead(badotri.triangle) && (borg == badtri.triangorg) && + (bdest == badtri.triangdest) && (bapex == badtri.triangapex)) + { + errorflag = false; + // Create a new vertex at the triangle's circumcenter. + + // Using the original (simpler) Steiner point location method + // for mesh refinement. + // TODO: NewLocation doesn't work for refinement. Why? Maybe + // reset VertexType? + if (behavior.fixedArea || behavior.VarArea) + { + newloc = Primitives.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant); + } + else + { + newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri); + } + + // Check whether the new vertex lies on a triangle vertex. + if (((newloc.x == borg.x) && (newloc.y == borg.y)) || + ((newloc.x == bdest.x) && (newloc.y == bdest.y)) || + ((newloc.x == bapex.x) && (newloc.y == bapex.y))) + { + if (Behavior.Verbose) + { + logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); + errorflag = true; + } + } + else + { + // The new vertex must be in the interior, and therefore is a + // free vertex with a marker of zero. + Vertex newvertex = new Vertex(newloc.x, newloc.y, 0, mesh.nextras); + newvertex.type = VertexType.FreeVertex; + + for (int i = 0; i < mesh.nextras; i++) + { + // Interpolate the vertex attributes at the circumcenter. + newvertex.attributes[i] = borg.attributes[i] + + xi * (bdest.attributes[i] - borg.attributes[i]) + + eta * (bapex.attributes[i] - borg.attributes[i]); + } + + // Ensure that the handle 'badotri' does not represent the longest + // edge of the triangle. This ensures that the circumcenter must + // fall to the left of this edge, so point location will work. + // (If the angle org-apex-dest exceeds 90 degrees, then the + // circumcenter lies outside the org-dest edge, and eta is + // negative. Roundoff error might prevent eta from being + // negative when it should be, so I test eta against xi.) + if (eta < xi) + { + badotri.LprevSelf(); + } + + // Insert the circumcenter, searching from the edge of the triangle, + // and maintain the Delaunay property of the triangulation. + Osub tmp = default(Osub); + success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true); + + if (success == InsertVertexResult.Successful) + { + newvertex.hash = mesh.hash_vtx++; + newvertex.id = newvertex.hash; + + mesh.vertices.Add(newvertex.hash, newvertex); + + if (mesh.steinerleft > 0) + { + mesh.steinerleft--; + } + } + else if (success == InsertVertexResult.Encroaching) + { + // If the newly inserted vertex encroaches upon a subsegment, + // delete the new vertex. + mesh.UndoVertex(); + } + else if (success == InsertVertexResult.Violating) + { + // Failed to insert the new vertex, but some subsegment was + // marked as being encroached. + } + else + { // success == DUPLICATEVERTEX + // Couldn't insert the new vertex because a vertex is already there. + if (Behavior.Verbose) + { + logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); + errorflag = true; + } + } + } + if (errorflag) + { + logger.Error("The new vertex is at the circumcenter of triangle: This probably " + + "means that I am trying to refine triangles to a smaller size than can be " + + "accommodated by the finite precision of floating point arithmetic.", + "Quality.SplitTriangle()"); + + throw new Exception("The new vertex is at the circumcenter of triangle."); + } + } + } + + /// + /// Remove all the encroached subsegments and bad triangles from the triangulation. + /// + public void EnforceQuality() + { + BadTriangle badtri; + + // Test all segments to see if they're encroached. + TallyEncs(); + + // Fix encroached subsegments without noting bad triangles. + SplitEncSegs(false); + // At this point, if we haven't run out of Steiner points, the + // triangulation should be (conforming) Delaunay. + + // Next, we worry about enforcing triangle quality. + if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.Usertest) + { + // TODO: Reset queue? (Or is it always empty at this point) + + // Test all triangles to see if they're bad. + TallyFaces(); + + mesh.checkquality = true; + while ((queue.Count > 0) && (mesh.steinerleft != 0)) + { + // Fix one bad triangle by inserting a vertex at its circumcenter. + badtri = queue.Dequeue(); + SplitTriangle(badtri); + + if (badsubsegs.Count > 0) + { + // Put bad triangle back in queue for another try later. + queue.Enqueue(badtri); + // Fix any encroached subsegments that resulted. + // Record any new bad triangles that result. + SplitEncSegs(true); + } + } + } + + // At this point, if the "-D" switch was selected and we haven't run out + // of Steiner points, the triangulation should be (conforming) Delaunay + // and have no low-quality triangles. + + // Might we have run out of Steiner points too soon? + if (Behavior.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0)) + { + + logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, " + + "and therefore might not be truly Delaunay. If the Delaunay property is important " + + "to you, try increasing the number of Steiner points.", + "Quality.EnforceQuality()"); + } + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/Sampler.cs b/ThirdParty/Triangle/Sampler.cs new file mode 100644 index 0000000..6e42cb6 --- /dev/null +++ b/ThirdParty/Triangle/Sampler.cs @@ -0,0 +1,111 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// Used for triangle sampling in the Mesh.Locate method. + /// + class Sampler + { + static Random rand = new Random(DateTime.Now.Millisecond); + + // Number of random samples for point location (at least 1). + int samples = 1; + + // Number of triangles in mesh. + int triangleCount = 0; + + // Empirically chosen factor. + static int samplefactor = 11; + + // Keys of the triangle dictionary. + int[] keys; + + /// + /// Reset the sampler. + /// + public void Reset() + { + this.samples = 1; + this.triangleCount = 0; + } + + /// + /// Update sampling parameters if mesh changed. + /// + /// Current mesh. + public void Update(Mesh mesh) + { + this.Update(mesh, false); + } + + /// + /// Update sampling parameters if mesh changed. + /// + /// Current mesh. + public void Update(Mesh mesh, bool forceUpdate) + { + int count = mesh.triangles.Count; + + // TODO: Is checking the triangle count a good way to monitor mesh changes? + if (triangleCount != count || forceUpdate) + { + triangleCount = count; + + // The number of random samples taken is proportional to the cube root of + // the number of triangles in the mesh. The next bit of code assumes + // that the number of triangles increases monotonically (or at least + // doesn't decrease enough to matter). + while (samplefactor * samples * samples * samples < count) + { + samples++; + } + + // TODO: Is there a way not calling ToArray()? + keys = mesh.triangles.Keys.ToArray(); + } + } + + /// + /// Get a random sample set of triangle keys. + /// + /// Array of triangle keys. + public int[] GetSamples(Mesh mesh) + { + // TODO: Using currKeys to check key availability? + List randSamples = new List(samples); + + int range = triangleCount / samples; + int key; + + for (int i = 0; i < samples; i++) + { + // Yeah, rand should be equally distributed, but just to make + // sure, use a range variable... + key = rand.Next(i * range, (i + 1) * range - 1); + + if (!mesh.triangles.Keys.Contains(keys[key])) + { + // Keys collection isn't up to date anymore! + this.Update(mesh, true); + i--; + } + else + { + randSamples.Add(keys[key]); + } + } + + return randSamples.ToArray(); + } + } +} diff --git a/ThirdParty/Triangle/Smoothing/ISmoother.cs b/ThirdParty/Triangle/Smoothing/ISmoother.cs new file mode 100644 index 0000000..8fec1cf --- /dev/null +++ b/ThirdParty/Triangle/Smoothing/ISmoother.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Smoothing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// Interface for mesh smoothers. + /// + public interface ISmoother + { + void Smooth(); + } +} \ No newline at end of file diff --git a/ThirdParty/Triangle/Smoothing/SimpleSmoother.cs b/ThirdParty/Triangle/Smoothing/SimpleSmoother.cs new file mode 100644 index 0000000..4e1af7d --- /dev/null +++ b/ThirdParty/Triangle/Smoothing/SimpleSmoother.cs @@ -0,0 +1,106 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Smoothing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Simple mesh smoother implementation. + /// + /// + /// Vertices wich should not move (e.g. segment vertices) MUST have a + /// boundary mark greater than 0. + /// + public class SimpleSmoother : ISmoother + { + Mesh mesh; + + public SimpleSmoother(Mesh mesh) + { + this.mesh = mesh; + } + + public void Smooth() + { + mesh.behavior.Quality = false; + + // Take a few smoothing rounds. + for (int i = 0; i < 5; i++) + { + Step(); + + // Actually, we only want to rebuild, if mesh is no longer + // Delaunay. Flipping edges could be the right choice instead + // of re-triangulating... + mesh.Triangulate(Rebuild()); + } + } + + /// + /// Smooth all free nodes. + /// + private void Step() + { + BoundedVoronoi voronoi = new BoundedVoronoi(this.mesh, false); + + var cells = voronoi.Regions; + + double x, y; + int n; + + foreach (var cell in cells) + { + n = 0; + x = y = 0.0; + foreach (var p in cell.Vertices) + { + n++; + x += p.x; + y += p.y; + } + + cell.Generator.x = x / n; + cell.Generator.y = y / n; + } + } + + /// + /// Rebuild the input geometry. + /// + private InputGeometry Rebuild() + { + InputGeometry geometry = new InputGeometry(mesh.vertices.Count); + + foreach (var vertex in mesh.vertices.Values) + { + geometry.AddPoint(vertex.x, vertex.y, vertex.mark); + } + + foreach (var segment in mesh.subsegs.Values) + { + geometry.AddSegment(segment.P0, segment.P1, segment.Boundary); + } + + foreach (var hole in mesh.holes) + { + geometry.AddHole(hole.x, hole.y); + } + + foreach (var region in mesh.regions) + { + geometry.AddRegion(region.point.x, region.point.y, region.id); + } + + return geometry; + } + } +} diff --git a/ThirdParty/Triangle/Tools/AdjacencyMatrix.cs b/ThirdParty/Triangle/Tools/AdjacencyMatrix.cs new file mode 100644 index 0000000..9c71850 --- /dev/null +++ b/ThirdParty/Triangle/Tools/AdjacencyMatrix.cs @@ -0,0 +1,404 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// The adjacency matrix of the mesh. + /// + public class AdjacencyMatrix + { + // Number of nodes in the mesh. + int node_num; + + // Number of adjacency entries. + int adj_num; + + // Pointers into the actual adjacency structure adj. Information about row k is + // stored in entries adj_row(k) through adj_row(k+1)-1 of adj. Size: node_num + 1 + int[] adj_row; + + // The adjacency structure. For each row, it contains the column indices + // of the nonzero entries. Size: adj_num + int[] adj; + + public int[] AdjacencyRow + { + get { return adj_row; } + } + + public int[] Adjacency + { + get { return adj; } + } + + public AdjacencyMatrix(Mesh mesh) + { + this.node_num = mesh.vertices.Count; + + // Set up the adj_row adjacency pointer array. + this.adj_row = AdjacencyCount(mesh); + this.adj_num = adj_row[node_num] - 1; + + // Set up the adj adjacency array. + this.adj = AdjacencySet(mesh, this.adj_row); + } + + /// + /// Computes the bandwidth of an adjacency matrix. + /// + /// Bandwidth of the adjacency matrix. + public int Bandwidth() + { + int band_hi; + int band_lo; + int col; + int i, j; + + band_lo = 0; + band_hi = 0; + + for (i = 0; i < node_num; i++) + { + for (j = adj_row[i]; j <= adj_row[i + 1] - 1; j++) + { + col = adj[j - 1]; + band_lo = Math.Max(band_lo, i - col); + band_hi = Math.Max(band_hi, col - i); + } + } + + return band_lo + 1 + band_hi; + } + + #region Adjacency matrix + + /// + /// Counts adjacencies in a triangulation. + /// + /// + /// This routine is called to count the adjacencies, so that the + /// appropriate amount of memory can be set aside for storage when + /// the adjacency structure is created. + /// + /// The triangulation is assumed to involve 3-node triangles. + /// + /// Two nodes are "adjacent" if they are both nodes in some triangle. + /// Also, a node is considered to be adjacent to itself. + /// + /// Diagram: + /// + /// 3 + /// s |\ + /// i | \ + /// d | \ + /// e | \ side 1 + /// | \ + /// 2 | \ + /// | \ + /// 1-------2 + /// + /// side 3 + /// + int[] AdjacencyCount(Mesh mesh) + { + int i; + int node; + int n1, n2, n3; + int tri_id; + int neigh_id; + + int[] adj_rows = new int[node_num + 1]; + + // Set every node to be adjacent to itself. + for (node = 0; node < node_num; node++) + { + adj_rows[node] = 1; + } + + // Examine each triangle. + foreach (var tri in mesh.triangles.Values) + { + tri_id = tri.id; + + n1 = tri.vertices[0].id; + n2 = tri.vertices[1].id; + n3 = tri.vertices[2].id; + + // Add edge (1,2) if this is the first occurrence, that is, if + // the edge (1,2) is on a boundary (nid <= 0) or if this triangle + // is the first of the pair in which the edge occurs (tid < nid). + neigh_id = tri.neighbors[2].triangle.id; + + if (neigh_id < 0 || tri_id < neigh_id) + { + adj_rows[n1] += 1; + adj_rows[n2] += 1; + } + + // Add edge (2,3). + neigh_id = tri.neighbors[0].triangle.id; + + if (neigh_id < 0 || tri_id < neigh_id) + { + adj_rows[n2] += 1; + adj_rows[n3] += 1; + } + + // Add edge (3,1). + neigh_id = tri.neighbors[1].triangle.id; + + if (neigh_id < 0 || tri_id < neigh_id) + { + adj_rows[n3] += 1; + adj_rows[n1] += 1; + } + } + + // We used ADJ_COL to count the number of entries in each column. + // Convert it to pointers into the ADJ array. + for (node = node_num; 1 <= node; node--) + { + adj_rows[node] = adj_rows[node - 1]; + } + + adj_rows[0] = 1; + for (i = 1; i <= node_num; i++) + { + adj_rows[i] = adj_rows[i - 1] + adj_rows[i]; + } + + return adj_rows; + } + + /// + /// Sets adjacencies in a triangulation. + /// + /// + /// This routine can be used to create the compressed column storage + /// for a linear triangle finite element discretization of Poisson's + /// equation in two dimensions. + /// + int[] AdjacencySet(Mesh mesh, int[] rows) + { + // Output list, stores the actual adjacency information. + int[] list; + + // Copy of the adjacency rows input. + int[] rowsCopy = new int[node_num]; + Array.Copy(rows, rowsCopy, node_num); + + int i, n = rows[node_num] - 1; + + list = new int[n]; + for (i = 0; i < n; i++) + { + list[i] = -1; + } + + // Set every node to be adjacent to itself. + for (i = 0; i < node_num; i++) + { + list[rowsCopy[i] - 1] = i; + rowsCopy[i] += 1; + } + + int n1, n2, n3; // Vertex numbers. + int tid, nid; // Triangle and neighbor id. + + // Examine each triangle. + foreach (var tri in mesh.triangles.Values) + { + tid = tri.id; + + n1 = tri.vertices[0].id; + n2 = tri.vertices[1].id; + n3 = tri.vertices[2].id; + + // Add edge (1,2) if this is the first occurrence, that is, if + // the edge (1,2) is on a boundary (nid <= 0) or if this triangle + // is the first of the pair in which the edge occurs (tid < nid). + nid = tri.neighbors[2].triangle.id; + + if (nid < 0 || tid < nid) + { + list[rowsCopy[n1] - 1] = n2; + rowsCopy[n1] += 1; + list[rowsCopy[n2] - 1] = n1; + rowsCopy[n2] += 1; + } + + // Add edge (2,3). + nid = tri.neighbors[0].triangle.id; + + if (nid < 0 || tid < nid) + { + list[rowsCopy[n2] - 1] = n3; + rowsCopy[n2] += 1; + list[rowsCopy[n3] - 1] = n2; + rowsCopy[n3] += 1; + } + + // Add edge (3,1). + nid = tri.neighbors[1].triangle.id; + + if (nid < 0 || tid < nid) + { + list[rowsCopy[n1] - 1] = n3; + rowsCopy[n1] += 1; + list[rowsCopy[n3] - 1] = n1; + rowsCopy[n3] += 1; + } + } + + int k1, k2; + + // Ascending sort the entries for each node. + for (i = 0; i < node_num; i++) + { + k1 = rows[i]; + k2 = rows[i + 1] - 1; + HeapSort(list, k1 - 1, k2 + 1 - k1); + } + + return list; + } + + #endregion + + #region Heap sort + + /// + /// Reorders an array of integers into a descending heap. + /// + /// the size of the input array. + /// an unsorted array. + /// + /// A heap is an array A with the property that, for every index J, + /// A[J] >= A[2*J+1] and A[J] >= A[2*J+2], (as long as the indices + /// 2*J+1 and 2*J+2 are legal). + /// + /// Diagram: + /// + /// A(0) + /// / \ + /// A(1) A(2) + /// / \ / \ + /// A(3) A(4) A(5) A(6) + /// / \ / \ + /// A(7) A(8) A(9) A(10) + /// + private void CreateHeap(int[] a, int offset, int size) + { + int i; + int ifree; + int key; + int m; + + // Only nodes (N/2)-1 down to 0 can be "parent" nodes. + for (i = (size / 2) - 1; 0 <= i; i--) + { + // Copy the value out of the parent node. + // Position IFREE is now "open". + key = a[offset + i]; + ifree = i; + + for (; ; ) + { + // Positions 2*IFREE + 1 and 2*IFREE + 2 are the descendants of position + // IFREE. (One or both may not exist because they equal or exceed N.) + m = 2 * ifree + 1; + + // Does the first position exist? + if (size <= m) + { + break; + } + else + { + // Does the second position exist? + if (m + 1 < size) + { + // If both positions exist, take the larger of the two values, + // and update M if necessary. + if (a[offset + m] < a[offset + m + 1]) + { + m = m + 1; + } + } + + // If the large descendant is larger than KEY, move it up, + // and update IFREE, the location of the free position, and + // consider the descendants of THIS position. + if (key < a[offset + m]) + { + a[offset + ifree] = a[offset + m]; + ifree = m; + } + else + { + break; + } + } + } + + // When you have stopped shifting items up, return the item you + // pulled out back to the heap. + a[offset + ifree] = key; + } + + return; + } + + + /// + /// ascending sorts an array of integers using heap sort. + /// + /// Number of entries in the array. + /// Array to be sorted; + private void HeapSort(int[] a, int offset, int size) + { + int n1; + int temp; + + if (size <= 1) + { + return; + } + + // 1: Put A into descending heap form. + CreateHeap(a, offset, size); + + // 2: Sort A. + // The largest object in the heap is in A[0]. + // Move it to position A[N-1]. + temp = a[offset]; + a[offset] = a[offset + size - 1]; + a[offset + size - 1] = temp; + + // Consider the diminished heap of size N1. + for (n1 = size - 1; 2 <= n1; n1--) + { + // Restore the heap structure of the initial N1 entries of A. + CreateHeap(a, offset, n1); + + // Take the largest object from A[0] and move it to A[N1-1]. + temp = a[offset]; + a[offset] = a[offset + n1 - 1]; + a[offset + n1 - 1] = temp; + } + + return; + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/Tools/BoundedVoronoi.cs b/ThirdParty/Triangle/Tools/BoundedVoronoi.cs new file mode 100644 index 0000000..d891f38 --- /dev/null +++ b/ThirdParty/Triangle/Tools/BoundedVoronoi.cs @@ -0,0 +1,656 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Data; + using TriangleNet.Geometry; + + /// + /// The Bounded Voronoi Diagram is the dual of a PSLG triangulation. + /// + /// + /// 2D Centroidal Voronoi Tessellations with Constraints, 2010, + /// Jane Tournois, Pierre Alliez and Olivier Devillers + /// + public class BoundedVoronoi : IVoronoi + { + Mesh mesh; + + Point[] points; + List regions; + + int segIndex; + + Dictionary subsegMap; + + bool includeBoundary = true; + + /// + /// Initializes a new instance of the class. + /// + /// Mesh instance. + public BoundedVoronoi(Mesh mesh) + : this(mesh, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Mesh instance. + public BoundedVoronoi(Mesh mesh, bool includeBoundary) + { + this.mesh = mesh; + this.includeBoundary = includeBoundary; + + Generate(); + } + + /// + /// Gets the list of Voronoi vertices. + /// + public Point[] Points + { + get { return points; } + } + + /// + /// Gets the list of Voronoi regions. + /// + public List Regions + { + get { return regions; } + } + + /// + /// Computes the bounded voronoi diagram. + /// + private void Generate() + { + mesh.Renumber(); + mesh.MakeVertexMap(); + + // Allocate space for voronoi diagram + this.points = new Point[mesh.triangles.Count + mesh.subsegs.Count * 5]; // This is an upper bound. + this.regions = new List(mesh.vertices.Count); + + ComputeCircumCenters(); + + TagBlindTriangles(); + + foreach (var v in mesh.vertices.Values) + { + // TODO: Need a reliable way to check if a vertex is on a segment + if (v.type == VertexType.FreeVertex || v.Boundary == 0) + { + ConstructBvdCell(v); + } + else if (includeBoundary) + { + ConstructBoundaryBvdCell(v); + } + } + } + + private void ComputeCircumCenters() + { + Otri tri = default(Otri); + double xi = 0, eta = 0; + Point pt; + + // Compue triangle circumcenters + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + pt = Primitives.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); + pt.id = item.id; + + points[item.id] = pt; + } + } + + /// + /// Tag all blind triangles. + /// + /// + /// A triangle is said to be blind if the triangle and its circumcenter + /// lie on two different sides of a constrained edge. + /// + private void TagBlindTriangles() + { + int blinded = 0; + + Stack triangles; + subsegMap = new Dictionary(); + + Otri f = default(Otri); + Otri f0 = default(Otri); + Osub e = default(Osub); + Osub sub1 = default(Osub); + + // Tag all triangles non-blind + foreach (var t in mesh.triangles.Values) + { + // Use the infected flag for 'blinded' attribute. + t.infected = false; + } + + // for each constrained edge e of cdt do + foreach (var ss in mesh.subsegs.Values) + { + // Create a stack: triangles + triangles = new Stack(); + + // for both adjacent triangles fe to e tagged non-blind do + // Push fe into triangles + e.seg = ss; + e.orient = 0; + e.TriPivot(ref f); + + if (f.triangle != Mesh.dummytri && !f.triangle.infected) + { + triangles.Push(f.triangle); + } + + e.SymSelf(); + e.TriPivot(ref f); + + if (f.triangle != Mesh.dummytri && !f.triangle.infected) + { + triangles.Push(f.triangle); + } + + // while triangles is non-empty + while (triangles.Count > 0) + { + // Pop f from stack triangles + f.triangle = triangles.Pop(); + f.orient = 0; + + // if f is blinded by e (use P) then + if (TriangleIsBlinded(ref f, ref e)) + { + // Tag f as blinded by e + f.triangle.infected = true; + blinded++; + + // Store association triangle -> subseg + subsegMap.Add(f.triangle.hash, e.seg); + + // for each adjacent triangle f0 to f do + for (f.orient = 0; f.orient < 3; f.orient++) + { + f.Sym(ref f0); + + f0.SegPivot(ref sub1); + + // if f0 is finite and tagged non-blind & the common edge + // between f and f0 is unconstrained then + if (f0.triangle != Mesh.dummytri && !f0.triangle.infected && sub1.seg == Mesh.dummysub) + { + // Push f0 into triangles. + triangles.Push(f0.triangle); + } + } + } + } + } + + blinded = 0; + } + + /// + /// Check if given triangle is blinded by given segment. + /// + /// Triangle. + /// Segments + /// Returns true, if the triangle is blinded. + private bool TriangleIsBlinded(ref Otri tri, ref Osub seg) + { + Point c, pt; + + Vertex torg = tri.Org(); + Vertex tdest = tri.Dest(); + Vertex tapex = tri.Apex(); + + Vertex sorg = seg.Org(); + Vertex sdest = seg.Dest(); + + c = this.points[tri.triangle.id]; + + if (SegmentsIntersect(sorg, sdest, c, torg, out pt, true)) + { + return true; + } + + if (SegmentsIntersect(sorg, sdest, c, tdest, out pt, true)) + { + return true; + } + + if (SegmentsIntersect(sorg, sdest, c, tapex, out pt, true)) + { + return true; + } + + return false; + } + + private void ConstructBvdCell(Vertex vertex) + { + VoronoiRegion region = new VoronoiRegion(vertex); + regions.Add(region); + + Otri f = default(Otri); + Otri f_init = default(Otri); + Otri f_next = default(Otri); + Osub sf = default(Osub); + Osub sfn = default(Osub); + + Point cc_f, cc_f_next, p; + + int n = mesh.triangles.Count; + + // Call P the polygon (cell) in construction + List vpoints = new List(); + + // Call f_init a triangle incident to x + vertex.tri.Copy(ref f_init); + + if (f_init.Org() != vertex) + { + throw new Exception("ConstructBvdCell: inconsistent topology."); + } + + // Let f be initialized to f_init + f_init.Copy(ref f); + // Call f_next the next triangle counterclockwise around x + f_init.Onext(ref f_next); + + // repeat ... until f = f_init + do + { + // Call Lffnext the line going through the circumcenters of f and f_next + cc_f = this.points[f.triangle.id]; + cc_f_next = this.points[f_next.triangle.id]; + + // if f is tagged non-blind then + if (!f.triangle.infected) + { + // Insert the circumcenter of f into P + vpoints.Add(cc_f); + + if (f_next.triangle.infected) + { + // Call S_fnext the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.triangle.hash]; + + // Insert point Lf,f_next /\ Sf_next into P + if (SegmentsIntersect(sfn.SegOrg(), sfn.SegDest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + } + else + { + // Call Sf the constrained edge blinding f + sf.seg = subsegMap[f.triangle.hash]; + + // if f_next is tagged non-blind then + if (!f_next.triangle.infected) + { + // Insert point Lf,f_next /\ Sf into P + if (SegmentsIntersect(sf.SegOrg(), sf.SegDest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + else + { + // Call Sf_next the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.triangle.hash]; + + // if Sf != Sf_next then + if (!sf.Equal(sfn)) + { + // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P + if (SegmentsIntersect(sf.SegOrg(), sf.SegDest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + + if (SegmentsIntersect(sfn.SegOrg(), sfn.SegDest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + } + } + + // f <- f_next + f_next.Copy(ref f); + + // Call f_next the next triangle counterclockwise around x + f_next.OnextSelf(); + } + while (!f.Equal(f_init)); + + // Output: Bounded Voronoi cell of x in counterclockwise order. + region.Add(vpoints); + } + + private void ConstructBoundaryBvdCell(Vertex vertex) + { + VoronoiRegion region = new VoronoiRegion(vertex); + regions.Add(region); + + Otri f = default(Otri); + Otri f_init = default(Otri); + Otri f_next = default(Otri); + Otri f_prev = default(Otri); + Osub sf = default(Osub); + Osub sfn = default(Osub); + + Vertex torg, tdest, tapex, sorg, sdest; + Point cc_f, cc_f_next, p; + + int n = mesh.triangles.Count; + + // Call P the polygon (cell) in construction + List vpoints = new List(); + + // Call f_init a triangle incident to x + vertex.tri.Copy(ref f_init); + + if (f_init.Org() != vertex) + { + throw new Exception("ConstructBoundaryBvdCell: inconsistent topology."); + } + // Let f be initialized to f_init + f_init.Copy(ref f); + // Call f_next the next triangle counterclockwise around x + f_init.Onext(ref f_next); + + f_init.Oprev(ref f_prev); + + // Is the border to the left? + if (f_prev.triangle != Mesh.dummytri) + { + // Go clockwise until we reach the border (or the initial triangle) + while (f_prev.triangle != Mesh.dummytri && !f_prev.Equal(f_init)) + { + f_prev.Copy(ref f); + f_prev.OprevSelf(); + } + + f.Copy(ref f_init); + f.Onext(ref f_next); + } + + if (f_prev.triangle == Mesh.dummytri) + { + // For vertices on the domain boundaray, add the vertex. For + // internal boundaries don't add it. + p = new Point(vertex.x, vertex.y); + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + + // Add midpoint of start triangles' edge. + torg = f.Org(); + tdest = f.Dest(); + p = new Point((torg.X + tdest.X) / 2, (torg.Y + tdest.Y) / 2); + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + + // repeat ... until f = f_init + do + { + // Call Lffnext the line going through the circumcenters of f and f_next + cc_f = this.points[f.triangle.id]; + + if (f_next.triangle == Mesh.dummytri) + { + if (!f.triangle.infected) + { + // Add last circumcenter + vpoints.Add(cc_f); + } + + // Add midpoint of last triangles' edge (chances are it has already + // been added, so post process cell to remove duplicates???) + torg = f.Org(); + tapex = f.Apex(); + p = new Point((torg.X + tapex.X) / 2, (torg.Y + tapex.Y) / 2); + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + + break; + } + + cc_f_next = this.points[f_next.triangle.id]; + + // if f is tagged non-blind then + if (!f.triangle.infected) + { + // Insert the circumcenter of f into P + vpoints.Add(cc_f); + + if (f_next.triangle.infected) + { + // Call S_fnext the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.triangle.hash]; + + // Insert point Lf,f_next /\ Sf_next into P + if (SegmentsIntersect(sfn.SegOrg(), sfn.SegDest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + } + else + { + // Call Sf the constrained edge blinding f + sf.seg = subsegMap[f.triangle.hash]; + + sorg = sf.SegOrg(); + sdest = sf.SegDest(); + + // if f_next is tagged non-blind then + if (!f_next.triangle.infected) + { + tdest = f.Dest(); + tapex = f.Apex(); + + // Both circumcenters lie on the blinded side, but we + // have to add the intersection with the segment. + + // Center of f edge dest->apex + Point bisec = new Point((tdest.X + tapex.X) / 2, (tdest.Y + tapex.Y) / 2); + + // Find intersection of seg with line through f's bisector and circumcenter + if (SegmentsIntersect(sorg, sdest, bisec, cc_f, out p, false)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + + // Insert point Lf,f_next /\ Sf into P + if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + else + { + // Call Sf_next the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.triangle.hash]; + + // if Sf != Sf_next then + if (!sf.Equal(sfn)) + { + // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P + if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + + if (SegmentsIntersect(sfn.SegOrg(), sfn.SegDest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + else + { + // Both circumcenters lie on the blinded side, but we + // have to add the intersection with the segment. + + // Center of f_next edge org->dest + Point bisec = new Point((torg.X + tdest.X) / 2, (torg.Y + tdest.Y) / 2); + + // Find intersection of seg with line through f_next's bisector and circumcenter + if (SegmentsIntersect(sorg, sdest, bisec, cc_f_next, out p, false)) + { + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; + + vpoints.Add(p); + } + } + } + } + + // f <- f_next + f_next.Copy(ref f); + + // Call f_next the next triangle counterclockwise around x + f_next.OnextSelf(); + } + while (!f.Equal(f_init)); + + // Output: Bounded Voronoi cell of x in counterclockwise order. + region.Add(vpoints); + } + + /// + /// Determines the intersection point of the line segment defined by points A and B with the + /// line segment defined by points C and D. + /// + /// The first segment AB. + /// Endpoint C of second segment. + /// Endpoint D of second segment. + /// Reference to the intersection point. + /// If false, pa and pb represent a line. + /// Returns true if the intersection point was found, and stores that point in X,Y. + /// Returns false if there is no determinable intersection point, in which case X,Y will + /// be unmodified. + /// + private bool SegmentsIntersect(Point p1, Point p2, Point p3, Point p4, out Point p, bool strictIntersect) + { + p = null; + + double Ax = p1.X, Ay = p1.Y; + double Bx = p2.X, By = p2.Y; + double Cx = p3.X, Cy = p3.Y; + double Dx = p4.X, Dy = p4.Y; + + double distAB, theCos, theSin, newX, ABpos; + + // Fail if either line segment is zero-length. + if (Ax == Bx && Ay == By || Cx == Dx && Cy == Dy) return false; + + // Fail if the segments share an end-point. + if (Ax == Cx && Ay == Cy || Bx == Cx && By == Cy + || Ax == Dx && Ay == Dy || Bx == Dx && By == Dy) + { + return false; + } + + // (1) Translate the system so that point A is on the origin. + Bx -= Ax; By -= Ay; + Cx -= Ax; Cy -= Ay; + Dx -= Ax; Dy -= Ay; + + // Discover the length of segment A-B. + distAB = Math.Sqrt(Bx * Bx + By * By); + + // (2) Rotate the system so that point B is on the positive X axis. + theCos = Bx / distAB; + theSin = By / distAB; + newX = Cx * theCos + Cy * theSin; + Cy = Cy * theCos - Cx * theSin; Cx = newX; + newX = Dx * theCos + Dy * theSin; + Dy = Dy * theCos - Dx * theSin; Dx = newX; + + // Fail if segment C-D doesn't cross line A-B. + if (Cy < 0 && Dy < 0 || Cy >= 0 && Dy >= 0 && strictIntersect) return false; + + // (3) Discover the position of the intersection point along line A-B. + ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy); + + // Fail if segment C-D crosses line A-B outside of segment A-B. + if (ABpos < 0 || ABpos > distAB && strictIntersect) return false; + + // (4) Apply the discovered position to line A-B in the original coordinate system. + p = new Point(Ax + ABpos * theCos, Ay + ABpos * theSin); + + // Success. + return true; + } + } +} diff --git a/ThirdParty/Triangle/Tools/CuthillMcKee.cs b/ThirdParty/Triangle/Tools/CuthillMcKee.cs new file mode 100644 index 0000000..856d12e --- /dev/null +++ b/ThirdParty/Triangle/Tools/CuthillMcKee.cs @@ -0,0 +1,659 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Log; + + /// + /// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of + /// the adjacency matrix associated with the mesh. + /// + /// + /// Some useful slides: + /// http://bobbyness.net/NerdyStuff/node%20ordering/node_ordering.html + /// + public class CuthillMcKee + { + // Number of nodes in the mesh. + int node_num; + + // The adjacency matrix of the mesh. + AdjacencyMatrix matrix; + + /// + /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. + /// + /// The mesh. + /// Permutation vector. + public int[] Renumber(Mesh mesh) + { + int bandwidth1, bandwidth2; + + this.node_num = mesh.vertices.Count; + + // Algorithm needs linear numbering of the nodes. + mesh.Renumber(NodeNumbering.Linear); + + // Set up the adj_row adjacency pointer array. + matrix = new AdjacencyMatrix(mesh); + + bandwidth1 = matrix.Bandwidth(); + + // Compute the RCM permutation. + int[] perm = GenerateRcm(); + + int[] perm_inv = PermInverse(node_num, perm); + + bandwidth2 = PermBandwidth(perm, perm_inv); + + if (Behavior.Verbose) + { + SimpleLog.Instance.Info(String.Format("Reverse Cuthill-McKee (Bandwidth: {0} > {1})", + bandwidth1, bandwidth2)); + } + + return perm_inv; + } + + /// + /// Computes the bandwidth of a permuted adjacency matrix. + /// + /// The permutation. + /// The inverse permutation. + /// Bandwidth of the permuted adjacency matrix. + /// + /// The matrix is defined by the adjacency information and a permutation. + /// The routine also computes the bandwidth and the size of the envelope. + /// + int PermBandwidth(int[] perm, int[] perm_inv) + { + int[] adj_row = matrix.AdjacencyRow; + int[] adj = matrix.Adjacency; + + int col, i, j; + + int band_lo = 0; + int band_hi = 0; + + for (i = 0; i < node_num; i++) + { + for (j = adj_row[perm[i]]; j <= adj_row[perm[i] + 1] - 1; j++) + { + col = perm_inv[adj[j - 1]]; + band_lo = Math.Max(band_lo, i - col); + band_hi = Math.Max(band_hi, col - i); + } + } + + return band_lo + 1 + band_hi; + } + + #region RCM + + /// + /// Finds the reverse Cuthill-Mckee ordering for a general graph. + /// + /// The RCM ordering. + /// + /// For each connected component in the graph, the routine obtains + /// an ordering by calling RCM. + /// + int[] GenerateRcm() + { + int[] perm = new int[node_num]; + + int i, num, root; + int iccsze = 0; + int level_num = 0; + + /// Index vector for a level structure. The level structure is stored in the + /// currently unused spaces in the permutation vector PERM. + int[] level_row = new int[node_num + 1]; + + /// Marks variables that have been numbered. + int[] mask = new int[node_num]; + + for (i = 0; i < node_num; i++) + { + mask[i] = 1; + } + + num = 1; + + for (i = 0; i < node_num; i++) + { + // For each masked connected component... + if (mask[i] != 0) + { + root = i; + + // Find a pseudo-peripheral node ROOT. The level structure found by + // ROOT_FIND is stored starting at PERM(NUM). + FindRoot(ref root, mask, ref level_num, level_row, perm, num - 1); + + // RCM orders the component using ROOT as the starting node. + Rcm(root, mask, perm, num - 1, ref iccsze); + + num += iccsze; + + // We can stop once every node is in one of the connected components. + if (node_num < num) + { + return perm; + } + } + } + + return perm; + } + + /// + /// RCM renumbers a connected component by the reverse Cuthill McKee algorithm. + /// + /// the node that defines the connected component. It is used as the starting + /// point for the RCM ordering. + /// Input/output, int MASK(NODE_NUM), a mask for the nodes. Only those nodes with + /// nonzero input mask values are considered by the routine. The nodes numbered by RCM will have + /// their mask values set to zero. + /// Output, int PERM(NODE_NUM), the RCM ordering. + /// Output, int ICCSZE, the size of the connected component that has been numbered. + /// the number of nodes. + /// + /// The connected component is specified by a node ROOT and a mask. + /// The numbering starts at the root node. + /// + /// An outline of the algorithm is as follows: + /// + /// X(1) = ROOT. + /// + /// for ( I = 1 to N-1) + /// Find all unlabeled neighbors of X(I), + /// assign them the next available labels, in order of increasing degree. + /// + /// When done, reverse the ordering. + /// + void Rcm(int root, int[] mask, int[] perm, int offset, ref int iccsze) + { + int[] adj_row = matrix.AdjacencyRow; + int[] adj = matrix.Adjacency; + + int fnbr; + int i, j, k, l; + int jstop, jstrt; + int lbegin, lnbr, lperm, lvlend; + int nbr, node; + + /// Workspace, int DEG[NODE_NUM], a temporary vector used to hold + /// the degree of the nodes in the section graph specified by mask and root. + int[] deg = new int[node_num]; + + // Find the degrees of the nodes in the component specified by MASK and ROOT. + Degree(root, mask, deg, ref iccsze, perm, offset); + + mask[root] = 0; + + if (iccsze <= 1) + { + return; + } + + lvlend = 0; + lnbr = 1; + + // LBEGIN and LVLEND point to the beginning and + // the end of the current level respectively. + while (lvlend < lnbr) + { + lbegin = lvlend + 1; + lvlend = lnbr; + + for (i = lbegin; i <= lvlend; i++) + { + // For each node in the current level... + node = perm[offset + i - 1]; + jstrt = adj_row[node]; + jstop = adj_row[node + 1] - 1; + + // Find the unnumbered neighbors of NODE. + + // FNBR and LNBR point to the first and last neighbors + // of the current node in PERM. + fnbr = lnbr + 1; + + for (j = jstrt; j <= jstop; j++) + { + nbr = adj[j - 1]; + + if (mask[nbr] != 0) + { + lnbr += 1; + mask[nbr] = 0; + perm[offset + lnbr - 1] = nbr; + } + } + + // Node has neighbors + if (lnbr > fnbr) + { + // Sort the neighbors of NODE in increasing order by degree. + // Linear insertion is used. + k = fnbr; + + while (k < lnbr) + { + l = k; + k = k + 1; + nbr = perm[offset + k - 1]; + + while (fnbr < l) + { + lperm = perm[offset + l - 1]; + + if (deg[lperm - 1] <= deg[nbr - 1]) + { + break; + } + + perm[offset + l] = lperm; + l = l - 1; + } + perm[offset + l] = nbr; + } + } + } + } + + // We now have the Cuthill-McKee ordering. Reverse it. + ReverseVector(perm, offset, iccsze); + + return; + } + + /// + /// Finds a pseudo-peripheral node. + /// + /// On input, ROOT is a node in the the component of the graph for + /// which a pseudo-peripheral node is sought. On output, ROOT is the pseudo-peripheral + /// node obtained. + /// MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK + /// is zero are ignored by FNROOT. + /// Output, int LEVEL_NUM, is the number of levels in the level + /// structure rooted at the node ROOT. + /// Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair + /// containing the level structure found. + /// Output, int LEVEL(NODE_NUM), the level structure array pair + /// containing the level structure found. + /// the number of nodes. + /// + /// The diameter of a graph is the maximum distance (number of edges) + /// between any two nodes of the graph. + /// + /// The eccentricity of a node is the maximum distance between that + /// node and any other node of the graph. + /// + /// A peripheral node is a node whose eccentricity equals the + /// diameter of the graph. + /// + /// A pseudo-peripheral node is an approximation to a peripheral node; + /// it may be a peripheral node, but all we know is that we tried our + /// best. + /// + /// The routine is given a graph, and seeks pseudo-peripheral nodes, + /// using a modified version of the scheme of Gibbs, Poole and + /// Stockmeyer. It determines such a node for the section subgraph + /// specified by MASK and ROOT. + /// + /// The routine also determines the level structure associated with + /// the given pseudo-peripheral node; that is, how far each node + /// is from the pseudo-peripheral node. The level structure is + /// returned as a list of nodes LS, and pointers to the beginning + /// of the list of nodes that are at a distance of 0, 1, 2, ..., + /// NODE_NUM-1 from the pseudo-peripheral node. + /// + /// Reference: + /// Alan George, Joseph Liu, + /// Computer Solution of Large Sparse Positive Definite Systems, + /// Prentice Hall, 1981. + /// + /// Norman Gibbs, William Poole, Paul Stockmeyer, + /// An Algorithm for Reducing the Bandwidth and Profile of a Sparse Matrix, + /// SIAM Journal on Numerical Analysis, + /// Volume 13, pages 236-250, 1976. + /// + /// Norman Gibbs, + /// Algorithm 509: A Hybrid Profile Reduction Algorithm, + /// ACM Transactions on Mathematical Software, + /// Volume 2, pages 378-387, 1976. + /// + void FindRoot(ref int root, int[] mask, ref int level_num, int[] level_row, + int[] level, int offset) + { + int[] adj_row = matrix.AdjacencyRow; + int[] adj = matrix.Adjacency; + + int iccsze; + int j, jstrt; + int k, kstop, kstrt; + int mindeg; + int nghbor, ndeg; + int node; + int level_num2 = 0; + + // Determine the level structure rooted at ROOT. + GetLevelSet(ref root, mask, ref level_num, level_row, level, offset); + + // Count the number of nodes in this level structure. + iccsze = level_row[level_num] - 1; + + // Extreme cases: + // A complete graph has a level set of only a single level. + // Every node is equally good (or bad). + // or + // A "line graph" 0--0--0--0--0 has every node in its only level. + // By chance, we've stumbled on the ideal root. + if (level_num == 1 || level_num == iccsze) + { + return; + } + + // Pick any node from the last level that has minimum degree + // as the starting point to generate a new level set. + for (; ; ) + { + mindeg = iccsze; + + jstrt = level_row[level_num - 1]; + root = level[offset + jstrt - 1]; + + if (jstrt < iccsze) + { + for (j = jstrt; j <= iccsze; j++) + { + node = level[offset + j - 1]; + ndeg = 0; + kstrt = adj_row[node - 1]; + kstop = adj_row[node] - 1; + + for (k = kstrt; k <= kstop; k++) + { + nghbor = adj[k - 1]; + if (mask[nghbor] > 0) + { + ndeg += 1; + } + } + + if (ndeg < mindeg) + { + root = node; + mindeg = ndeg; + } + } + } + + // Generate the rooted level structure associated with this node. + GetLevelSet(ref root, mask, ref level_num2, level_row, level, offset); + + // If the number of levels did not increase, accept the new ROOT. + if (level_num2 <= level_num) + { + break; + } + + level_num = level_num2; + + // In the unlikely case that ROOT is one endpoint of a line graph, + // we can exit now. + if (iccsze <= level_num) + { + break; + } + } + + return; + } + + /// + /// Generates the connected level structure rooted at a given node. + /// + /// the node at which the level structure is to be rooted. + /// MASK[NODE_NUM]. On input, only nodes with nonzero MASK are to be processed. + /// On output, those nodes which were included in the level set have MASK set to 1. + /// Output, int LEVEL_NUM, the number of levels in the level structure. ROOT is + /// in level 1. The neighbors of ROOT are in level 2, and so on. + /// Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure. + /// Output, int LEVEL[NODE_NUM], the rooted level structure. + /// the number of nodes. + /// + /// Only nodes for which MASK is nonzero will be considered. + /// + /// The root node chosen by the user is assigned level 1, and masked. + /// All (unmasked) nodes reachable from a node in level 1 are + /// assigned level 2 and masked. The process continues until there + /// are no unmasked nodes adjacent to any node in the current level. + /// The number of levels may vary between 2 and NODE_NUM. + /// + /// Reference: + /// Alan George, Joseph Liu, + /// Computer Solution of Large Sparse Positive Definite Systems, + /// Prentice Hall, 1981. + /// + void GetLevelSet(ref int root, int[] mask, ref int level_num, int[] level_row, + int[] level, int offset) + { + int[] adj_row = matrix.AdjacencyRow; + int[] adj = matrix.Adjacency; + + int i, iccsze; + int j, jstop, jstrt; + int lbegin, lvlend, lvsize; + int nbr; + int node; + + mask[root] = 0; + level[offset] = root; + level_num = 0; + lvlend = 0; + iccsze = 1; + + // LBEGIN is the pointer to the beginning of the current level, and + // LVLEND points to the end of this level. + for (; ; ) + { + lbegin = lvlend + 1; + lvlend = iccsze; + level_num += 1; + level_row[level_num - 1] = lbegin; + + // Generate the next level by finding all the masked neighbors of nodes + // in the current level. + for (i = lbegin; i <= lvlend; i++) + { + node = level[offset + i - 1]; + jstrt = adj_row[node]; + jstop = adj_row[node + 1] - 1; + + for (j = jstrt; j <= jstop; j++) + { + nbr = adj[j - 1]; + + if (mask[nbr] != 0) + { + iccsze += 1; + level[offset + iccsze - 1] = nbr; + mask[nbr] = 0; + } + } + } + + // Compute the current level width (the number of nodes encountered.) + // If it is positive, generate the next level. + lvsize = iccsze - lvlend; + + if (lvsize <= 0) + { + break; + } + } + + level_row[level_num] = lvlend + 1; + + // Reset MASK to 1 for the nodes in the level structure. + for (i = 0; i < iccsze; i++) + { + mask[level[offset + i]] = 1; + } + + return; + } + + /// + /// Computes the degrees of the nodes in the connected component. + /// + /// the node that defines the connected component. + /// MASK[NODE_NUM], is nonzero for those nodes which are to be considered. + /// Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree. + /// Output, int ICCSIZE, the number of nodes in the connected component. + /// Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the + /// connected component, starting with ROOT, and proceeding by levels. + /// the number of nodes. + /// + /// The connected component is specified by MASK and ROOT. + /// Nodes for which MASK is zero are ignored. + /// + /// Reference: + /// Alan George, Joseph Liu, + /// Computer Solution of Large Sparse Positive Definite Systems, + /// Prentice Hall, 1981. + /// + void Degree(int root, int[] mask, int[] deg, ref int iccsze, int[] ls, int offset) + { + int[] adj_row = matrix.AdjacencyRow; + int[] adj = matrix.Adjacency; + + int i, ideg; + int j, jstop, jstrt; + int lbegin, lvlend; + int lvsize = 1; + int nghbr, node; + + // The sign of ADJ_ROW(I) is used to indicate if node I has been considered. + ls[offset] = root; + adj_row[root] = -adj_row[root]; + lvlend = 0; + iccsze = 1; + + // If the current level width is nonzero, generate another level. + while (lvsize > 0) + { + // LBEGIN is the pointer to the beginning of the current level, and + // LVLEND points to the end of this level. + lbegin = lvlend + 1; + lvlend = iccsze; + + // Find the degrees of nodes in the current level, + // and at the same time, generate the next level. + for (i = lbegin; i <= lvlend; i++) + { + node = ls[offset + i - 1]; + jstrt = -adj_row[node]; + jstop = Math.Abs(adj_row[node + 1]) - 1; + ideg = 0; + + for (j = jstrt; j <= jstop; j++) + { + nghbr = adj[j - 1]; + + if (mask[nghbr] != 0) // EDIT: [nbr - 1] + { + ideg = ideg + 1; + + if (0 <= adj_row[nghbr]) // EDIT: [nbr - 1] + { + adj_row[nghbr] = -adj_row[nghbr]; // EDIT: [nbr - 1] + iccsze = iccsze + 1; + ls[offset + iccsze - 1] = nghbr; + } + } + } + deg[node] = ideg; + } + + // Compute the current level width. + lvsize = iccsze - lvlend; + } + + // Reset ADJ_ROW to its correct sign and return. + for (i = 0; i < iccsze; i++) + { + node = ls[offset + i]; + adj_row[node] = -adj_row[node]; + } + + return; + } + + #endregion + + #region Tools + + /// + /// Produces the inverse of a given permutation. + /// + /// Number of items permuted. + /// PERM[N], a permutation. + /// The inverse permutation. + int[] PermInverse(int n, int[] perm) + { + int[] perm_inv = new int[node_num]; + + int i; + + for (i = 0; i < n; i++) + { + perm_inv[perm[i]] = i; + } + + return perm_inv; + } + + /// + /// Reverses the elements of an integer vector. + /// + /// number of entries in the array. + /// the array to be reversed. + /// + /// Input: + /// N = 5, + /// A = ( 11, 12, 13, 14, 15 ). + /// + /// Output: + /// A = ( 15, 14, 13, 12, 11 ). + /// + void ReverseVector(int[] a, int offset, int size) + { + int i; + int j; + + for (i = 0; i < size / 2; i++) + { + j = a[offset + i]; + a[offset + i] = a[offset + size - 1 - i]; + a[offset + size - 1 - i] = j; + } + + return; + } + + #endregion + } +} diff --git a/ThirdParty/Triangle/Tools/IVoronoi.cs b/ThirdParty/Triangle/Tools/IVoronoi.cs new file mode 100644 index 0000000..2557fa5 --- /dev/null +++ b/ThirdParty/Triangle/Tools/IVoronoi.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// TODO: Update summary. + /// + public interface IVoronoi + { + /// + /// Gets the list of Voronoi vertices. + /// + Point[] Points { get; } + + /// + /// Gets the list of Voronoi regions. + /// + List Regions { get; } + } +} diff --git a/ThirdParty/Triangle/Tools/QuadTree.cs b/ThirdParty/Triangle/Tools/QuadTree.cs new file mode 100644 index 0000000..fe426a9 --- /dev/null +++ b/ThirdParty/Triangle/Tools/QuadTree.cs @@ -0,0 +1,423 @@ +// ----------------------------------------------------------------------- +// +// Original code by Frank Dockhorn, http://sourceforge.net/projects/quadtreesim/ +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + + /// + /// A Quadtree implementation optimised for triangles. + /// + public class QuadTree + { + QuadNode root; + + internal ITriangle[] triangles; + + internal int sizeBound; + internal int maxDepth; + + /// + /// Initializes a new instance of the class. + /// + /// Mesh containing triangles. + /// The maximum depth of the tree. + /// The maximum number of triangles contained in a leaf. + /// + /// The quadtree does not track changes of the mesh. If a mesh is refined or + /// changed in any other way, a new quadtree has to be built to make the point + /// location work. + /// + /// A node of the tree will be split, if its level if less than the max depth parameter + /// AND the number of triangles in the node is greater than the size bound. + /// + public QuadTree(Mesh mesh, int maxDepth = 10, int sizeBound = 10) + { + this.maxDepth = maxDepth; + this.sizeBound = sizeBound; + + triangles = mesh.Triangles.ToArray(); + + int currentDepth = 0; + + root = new QuadNode(mesh.Bounds, this, true); + root.CreateSubRegion(++currentDepth); + } + + public ITriangle Query(double x, double y) + { + var point = new Point(x, y); + var indices = root.FindTriangles(point); + + var result = new List(); + + foreach (var i in indices) + { + var tri = this.triangles[i]; + + if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2))) + { + result.Add(tri); + } + } + + return result.FirstOrDefault(); + } + + /// + /// Test, if a given point lies inside a triangle. + /// + /// Point to locate. + /// Corner point of triangle. + /// Corner point of triangle. + /// Corner point of triangle. + /// True, if point is inside or on the edge of this triangle. + internal static bool IsPointInTriangle(Point p, Point t0, Point t1, Point t2) + { + // TODO: no need to create new Point instances here + Point d0 = new Point(t1.X - t0.X, t1.Y - t0.Y); + Point d1 = new Point(t2.X - t0.X, t2.Y - t0.Y); + Point d2 = new Point(p.X - t0.X, p.Y - t0.Y); + + // crossproduct of (0, 0, 1) and d0 + Point c0 = new Point(-d0.Y, d0.X); + + // crossproduct of (0, 0, 1) and d1 + Point c1 = new Point(-d1.Y, d1.X); + + // Linear combination d2 = s * d0 + v * d1. + // + // Multiply both sides of the equation with c0 and c1 + // and solve for s and v respectively + // + // s = d2 * c1 / d0 * c1 + // v = d2 * c0 / d1 * c0 + + double s = DotProduct(d2, c1) / DotProduct(d0, c1); + double v = DotProduct(d2, c0) / DotProduct(d1, c0); + + if (s >= 0 && v >= 0 && ((s + v) <= 1)) + { + // Point is inside or on the edge of this triangle. + return true; + } + return false; + } + + internal static double DotProduct(Point p, Point q) + { + return p.X * q.X + p.Y * q.Y; + } + } + + #region QuadNode class + + /// + /// A node of the quadtree. + /// + class QuadNode + { + const int SW = 0; + const int SE = 1; + const int NW = 2; + const int NE = 3; + + const double EPS = 1e-6; + + static readonly byte[] BITVECTOR = { 0x1, 0x2, 0x4, 0x8 }; + + BoundingBox bounds; + Point pivot; + QuadTree tree; + QuadNode[] regions; + List triangles; + + byte bitRegions; + + public QuadNode(BoundingBox box, QuadTree tree) + : this(box, tree, false) + { + } + + public QuadNode(BoundingBox box, QuadTree tree, bool init) + { + this.tree = tree; + + this.bounds = new BoundingBox(box.Xmin, box.Ymin, box.Xmax, box.Ymax); + this.pivot = new Point((box.Xmin + box.Xmax) / 2, (box.Ymin + box.Ymax) / 2); + + this.bitRegions = 0; + + this.regions = new QuadNode[4]; + this.triangles = new List(); + + if (init) + { + // Allocate memory upfront + triangles.Capacity = tree.triangles.Length; + + foreach (var tri in tree.triangles) + { + triangles.Add(tri.ID); + } + } + } + + public List FindTriangles(Point searchPoint) + { + int region = FindRegion(searchPoint); + if (regions[region] == null) + { + return triangles; + } + return regions[region].FindTriangles(searchPoint); + } + + public void CreateSubRegion(int currentDepth) + { + // The four sub regions of the quad tree + // +--------------+ + // | nw | ne | + // |------+pivot--| + // | sw | se | + // +--------------+ + BoundingBox box; + + // 1. region south west + box = new BoundingBox(bounds.Xmin, bounds.Ymin, pivot.X, pivot.Y); + regions[0] = new QuadNode(box, tree); + + // 2. region south east + box = new BoundingBox(pivot.X, bounds.Ymin, bounds.Xmax, pivot.Y); + regions[1] = new QuadNode(box, tree); + + // 3. region north west + box = new BoundingBox(bounds.Xmin, pivot.Y, pivot.X, bounds.Ymax); + regions[2] = new QuadNode(box, tree); + + // 4. region north east + box = new BoundingBox(pivot.X, pivot.Y, bounds.Xmax, bounds.Ymax); + regions[3] = new QuadNode(box, tree); + + Point[] triangle = new Point[3]; + + // Find region for every triangle vertex + foreach (var index in triangles) + { + ITriangle tri = tree.triangles[index]; + + triangle[0] = tri.GetVertex(0); + triangle[1] = tri.GetVertex(1); + triangle[2] = tri.GetVertex(2); + + AddTriangleToRegion(triangle, tri.ID); + } + + for (int i = 0; i < 4; i++) + { + if (regions[i].triangles.Count > tree.sizeBound && currentDepth < tree.maxDepth) + { + regions[i].CreateSubRegion(currentDepth + 1); + } + } + } + + void AddTriangleToRegion(Point[] triangle, int index) + { + bitRegions = 0; + if (QuadTree.IsPointInTriangle(pivot, triangle[0], triangle[1], triangle[2])) + { + AddToRegion(index, SW); + AddToRegion(index, SE); + AddToRegion(index, NW); + AddToRegion(index, NE); + return; + } + + FindTriangleIntersections(triangle, index); + + if (bitRegions == 0) + { + // we didn't find any intersection so we add this triangle to a point's region + int region = FindRegion(triangle[0]); + regions[region].triangles.Add(index); + } + } + + void FindTriangleIntersections(Point[] triangle, int index) + { + // PLEASE NOTE: Handling of component comparison is tightly associated with the implementation + // of the findRegion() function. That means when the point to be compared equals + // the pivot point the triangle must be put at least into region 2. + // Linear equations are in parametric form. + // m_pivot.dx = triangle[0].dx + t * (triangle[1].dx - triangle[0].dx) + // m_pivot.dy = triangle[0].dy + t * (triangle[1].dy - triangle[0].dy) + + int k = 2; + + double dx, dy; + // Iterate through all triangle laterals and find bounding box intersections + for (int i = 0; i < 3; k = i++) + { + dx = triangle[i].X - triangle[k].X; + dy = triangle[i].Y - triangle[k].Y; + + if (dx != 0.0) + { + FindIntersectionsWithX(dx, dy, triangle, index, k); + } + if (dy != 0.0) + { + FindIntersectionsWithY(dx, dy, triangle, index, k); + } + } + } + + void FindIntersectionsWithX(double dx, double dy, Point[] triangle, int index, int k) + { + // find intersection with plane x = m_pivot.dX + double t = (pivot.X - triangle[k].X) / dx; + + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double yComponent = triangle[k].Y + t * dy; + + if (yComponent < pivot.Y) + { + if (yComponent >= bounds.Ymin) + { + AddToRegion(index, SW); + AddToRegion(index, SE); + } + } + else if (yComponent <= bounds.Ymax) + { + AddToRegion(index, NW); + AddToRegion(index, NE); + } + } + // find intersection with plane x = m_boundingBox[0].dX + t = (bounds.Xmin - triangle[k].X) / dx; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double yComponent = triangle[k].Y + t * dy; + + if (yComponent <= pivot.Y && yComponent >= bounds.Ymin) + { + AddToRegion(index, SW); + } + else if (yComponent >= pivot.Y && yComponent <= bounds.Ymax) + { + AddToRegion(index, NW); + } + } + // find intersection with plane x = m_boundingBox[1].dX + t = (bounds.Xmax - triangle[k].X) / dx; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double yComponent = triangle[k].Y + t * dy; + + if (yComponent <= pivot.Y && yComponent >= bounds.Ymin) + { + AddToRegion(index, SE); + } + else if (yComponent >= pivot.Y && yComponent <= bounds.Ymax) + { + AddToRegion(index, NE); + } + } + } + + void FindIntersectionsWithY(double dx, double dy, Point[] triangle, int index, int k) + { + // find intersection with plane y = m_pivot.dY + double t = (pivot.Y - triangle[k].Y) / (dy); + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double xComponent = triangle[k].X + t * (dy); + + if (xComponent > pivot.X) + { + if (xComponent <= bounds.Xmax) + { + AddToRegion(index, SE); + AddToRegion(index, NE); + } + } + else if (xComponent >= bounds.Xmin) + { + AddToRegion(index, SW); + AddToRegion(index, NW); + } + } + // find intersection with plane y = m_boundingBox[0].dY + t = (bounds.Ymin - triangle[k].Y) / dy; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double xComponent = triangle[k].X + t * dx; + + if (xComponent <= pivot.X && xComponent >= bounds.Xmin) + { + AddToRegion(index, SW); + } + else if (xComponent >= pivot.X && xComponent <= bounds.Xmax) + { + AddToRegion(index, SE); + } + } + // find intersection with plane y = m_boundingBox[1].dY + t = (bounds.Ymax - triangle[k].Y) / dy; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double xComponent = triangle[k].X + t * dx; + + if (xComponent <= pivot.X && xComponent >= bounds.Xmin) + { + AddToRegion(index, NW); + } + else if (xComponent >= pivot.X && xComponent <= bounds.Xmax) + { + AddToRegion(index, NE); + } + } + } + + int FindRegion(Point point) + { + int b = 2; + if (point.Y < pivot.Y) + { + b = 0; + } + if (point.X > pivot.X) + { + b++; + } + return b; + } + + void AddToRegion(int index, int region) + { + //if (!(m_bitRegions & BITVECTOR[region])) + if ((bitRegions & BITVECTOR[region]) == 0) + { + regions[region].triangles.Add(index); + bitRegions |= BITVECTOR[region]; + } + } + } + + #endregion +} diff --git a/ThirdParty/Triangle/Tools/QualityMeasure.cs b/ThirdParty/Triangle/Tools/QualityMeasure.cs new file mode 100644 index 0000000..e93d877 --- /dev/null +++ b/ThirdParty/Triangle/Tools/QualityMeasure.cs @@ -0,0 +1,544 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + + /// + /// Provides mesh quality information. + /// + /// + /// Given a triangle abc with points A (ax, ay), B (bx, by), C (cx, cy). + /// + /// The side lengths are given as + /// a = sqrt((cx - bx)^2 + (cy - by)^2) -- side BC opposite of A + /// b = sqrt((cx - ax)^2 + (cy - ay)^2) -- side CA opposite of B + /// c = sqrt((ax - bx)^2 + (ay - by)^2) -- side AB opposite of C + /// + /// The angles are given as + /// ang_a = acos((b^2 + c^2 - a^2) / (2 * b * c)) -- angle at A + /// ang_b = acos((c^2 + a^2 - b^2) / (2 * c * a)) -- angle at B + /// ang_c = acos((a^2 + b^2 - c^2) / (2 * a * b)) -- angle at C + /// + /// The semiperimeter is given as + /// s = (a + b + c) / 2 + /// + /// The area is given as + /// D = abs(ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) / 2 + /// = sqrt(s * (s - a) * (s - b) * (s - c)) + /// + /// The inradius is given as + /// r = D / s + /// + /// The circumradius is given as + /// R = a * b * c / (4 * D) + /// + /// The altitudes are given as + /// alt_a = 2 * D / a -- altitude above side a + /// alt_b = 2 * D / b -- altitude above side b + /// alt_c = 2 * D / c -- altitude above side c + /// + /// The aspect ratio may be given as the ratio of the longest to the + /// shortest edge or, more commonly as the ratio of the circumradius + /// to twice the inradius + /// ar = R / (2 * r) + /// = a * b * c / (8 * (s - a) * (s - b) * (s - c)) + /// = a * b * c / ((b + c - a) * (c + a - b) * (a + b - c)) + /// + public class QualityMeasure + { + AreaMeasure areaMeasure; + AlphaMeasure alphaMeasure; + Q_Measure qMeasure; + + Mesh mesh; + + public QualityMeasure() + { + areaMeasure = new AreaMeasure(); + alphaMeasure = new AlphaMeasure(); + qMeasure = new Q_Measure(); + } + + #region Public properties + + /// + /// Minimum triangle area. + /// + public double AreaMinimum + { + get { return areaMeasure.area_min; } + } + + /// + /// Maximum triangle area. + /// + public double AreaMaximum + { + get { return areaMeasure.area_max; } + } + + /// + /// Ratio of maximum and minimum triangle area. + /// + public double AreaRatio + { + get { return areaMeasure.area_max / areaMeasure.area_min; } + } + + /// + /// Smallest angle. + /// + public double AlphaMinimum + { + get { return alphaMeasure.alpha_min; } + } + + /// + /// Maximum smallest angle. + /// + public double AlphaMaximum + { + get { return alphaMeasure.alpha_max; } + } + + /// + /// Average angle. + /// + public double AlphaAverage + { + get { return alphaMeasure.alpha_ave; } + } + + /// + /// Average angle weighted by area. + /// + public double AlphaArea + { + get { return alphaMeasure.alpha_area; } + } + + /// + /// Smallest aspect ratio. + /// + public double Q_Minimum + { + get { return qMeasure.q_min; } + } + + /// + /// Largest aspect ratio. + /// + public double Q_Maximum + { + get { return qMeasure.q_max; } + } + + /// + /// Average aspect ratio. + /// + public double Q_Average + { + get { return qMeasure.q_ave; } + } + + /// + /// Average aspect ratio weighted by area. + /// + public double Q_Area + { + get { return qMeasure.q_area; } + } + + #endregion + + public void Update(Mesh mesh) + { + this.mesh = mesh; + + // Reset all measures. + areaMeasure.Reset(); + alphaMeasure.Reset(); + qMeasure.Reset(); + + Compute(); + } + + private void Compute() + { + Point a, b, c; + double ab, bc, ca; + double lx, ly; + double area; + + int n = 0; + + foreach (var tri in mesh.triangles.Values) + { + n++; + + a = tri.vertices[0]; + b = tri.vertices[1]; + c = tri.vertices[2]; + + lx = a.x - b.x; + ly = a.y - b.y; + ab = Math.Sqrt(lx * lx + ly * ly); + lx = b.x - c.x; + ly = b.y - c.y; + bc = Math.Sqrt(lx * lx + ly * ly); + lx = c.x - a.x; + ly = c.y - a.y; + ca = Math.Sqrt(lx * lx + ly * ly); + + area = areaMeasure.Measure(a, b, c); + alphaMeasure.Measure(ab, bc, ca, area); + qMeasure.Measure(ab, bc, ca, area); + } + + // Normalize measures + alphaMeasure.Normalize(n, areaMeasure.area_total); + qMeasure.Normalize(n, areaMeasure.area_total); + } + + /// + /// Determines the bandwidth of the coefficient matrix. + /// + /// Bandwidth of the coefficient matrix. + /// + /// The quantity computed here is the "geometric" bandwidth determined + /// by the finite element mesh alone. + /// + /// If a single finite element variable is associated with each node + /// of the mesh, and if the nodes and variables are numbered in the + /// same way, then the geometric bandwidth is the same as the bandwidth + /// of a typical finite element matrix. + /// + /// The bandwidth M is defined in terms of the lower and upper bandwidths: + /// + /// M = ML + 1 + MU + /// + /// where + /// + /// ML = maximum distance from any diagonal entry to a nonzero + /// entry in the same row, but earlier column, + /// + /// MU = maximum distance from any diagonal entry to a nonzero + /// entry in the same row, but later column. + /// + /// Because the finite element node adjacency relationship is symmetric, + /// we are guaranteed that ML = MU. + /// + public int Bandwidth() + { + if (mesh == null) return 0; + + // Lower and upper bandwidth of the matrix + int ml = 0, mu = 0; + + int gi, gj; + + foreach (var tri in mesh.triangles.Values) + { + for (int j = 0; j < 3; j++) + { + gi = tri.GetVertex(j).id; + + for (int k = 0; k < 3; k++) + { + gj = tri.GetVertex(k).id; + + mu = Math.Max(mu, gj - gi); + ml = Math.Max(ml, gi - gj); + } + } + } + + return ml + 1 + mu; + } + + class AreaMeasure + { + // Minimum area + public double area_min = double.MaxValue; + // Maximum area + public double area_max = -double.MaxValue; + // Total area of geometry + public double area_total = 0; + // Nmber of triangles with zero area + public int area_zero = 0; + + /// + /// Reset all values. + /// + public void Reset() + { + area_min = double.MaxValue; + area_max = -double.MaxValue; + area_total = 0; + area_zero = 0; + } + + /// + /// Compute the area of given triangle. + /// + /// Triangle corner a. + /// Triangle corner b. + /// Triangle corner c. + /// Triangle area. + public double Measure(Point a, Point b, Point c) + { + double area = 0.5 * Math.Abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)); + + area_min = Math.Min(area_min, area); + area_max = Math.Max(area_max, area); + area_total += area; + + if (area == 0.0) + { + area_zero = area_zero + 1; + } + + return area; + } + } + + /// + /// The alpha measure determines the triangulated pointset quality. + /// + /// + /// The alpha measure evaluates the uniformity of the shapes of the triangles + /// defined by a triangulated pointset. + /// + /// We compute the minimum angle among all the triangles in the triangulated + /// dataset and divide by the maximum possible value (which, in degrees, + /// is 60). The best possible value is 1, and the worst 0. A good + /// triangulation should have an alpha score close to 1. + /// + class AlphaMeasure + { + // Minimum value over all triangles + public double alpha_min; + // Maximum value over all triangles + public double alpha_max; + // Value averaged over all triangles + public double alpha_ave; + // Value averaged over all triangles and weighted by area + public double alpha_area; + + /// + /// Reset all values. + /// + public void Reset() + { + alpha_min = double.MaxValue; + alpha_max = -double.MaxValue; + alpha_ave = 0; + alpha_area = 0; + } + + double acos(double c) + { + if (c <= -1.0) + { + return Math.PI; + } + else if (1.0 <= c) + { + return 0.0; + } + else + { + return Math.Acos(c); + } + } + + /// + /// Compute q value of given triangle. + /// + /// Side length ab. + /// Side length bc. + /// Side length ca. + /// Triangle area. + /// + public double Measure(double ab, double bc, double ca, double area) + { + double alpha = double.MaxValue; + + double ab2 = ab * ab; + double bc2 = bc * bc; + double ca2 = ca * ca; + + double a_angle; + double b_angle; + double c_angle; + + // Take care of a ridiculous special case. + if (ab == 0.0 && bc == 0.0 && ca == 0.0) + { + a_angle = 2.0 * Math.PI / 3.0; + b_angle = 2.0 * Math.PI / 3.0; + c_angle = 2.0 * Math.PI / 3.0; + } + else + { + if (ca == 0.0 || ab == 0.0) + { + a_angle = Math.PI; + } + else + { + a_angle = acos((ca2 + ab2 - bc2) / (2.0 * ca * ab)); + } + + if (ab == 0.0 || bc == 0.0) + { + b_angle = Math.PI; + } + else + { + b_angle = acos((ab2 + bc2 - ca2) / (2.0 * ab * bc)); + } + + if (bc == 0.0 || ca == 0.0) + { + c_angle = Math.PI; + } + else + { + c_angle = acos((bc2 + ca2 - ab2) / (2.0 * bc * ca)); + } + } + + alpha = Math.Min(alpha, a_angle); + alpha = Math.Min(alpha, b_angle); + alpha = Math.Min(alpha, c_angle); + + // Normalize angle from [0,pi/3] radians into qualities in [0,1]. + alpha = alpha * 3.0 / Math.PI; + + alpha_ave += alpha; + alpha_area += area * alpha; + + alpha_min = Math.Min(alpha, alpha_min); + alpha_max = Math.Max(alpha, alpha_max); + + return alpha; + } + + /// + /// Normalize values. + /// + public void Normalize(int n, double area_total) + { + if (n > 0) + { + alpha_ave /= n; + } + else + { + alpha_ave = 0.0; + } + + if (0.0 < area_total) + { + alpha_area /= area_total; + } + else + { + alpha_area = 0.0; + } + } + } + + /// + /// The Q measure determines the triangulated pointset quality. + /// + /// + /// The Q measure evaluates the uniformity of the shapes of the triangles + /// defined by a triangulated pointset. It uses the aspect ratio + /// + /// 2 * (incircle radius) / (circumcircle radius) + /// + /// In an ideally regular mesh, all triangles would have the same + /// equilateral shape, for which Q = 1. A good mesh would have + /// 0.5 < Q. + /// + class Q_Measure + { + // Minimum value over all triangles + public double q_min; + // Maximum value over all triangles + public double q_max; + // Average value + public double q_ave; + // Average value weighted by the area of each triangle + public double q_area; + + /// + /// Reset all values. + /// + public void Reset() + { + q_min = double.MaxValue; + q_max = -double.MaxValue; + q_ave = 0; + q_area = 0; + } + + /// + /// Compute q value of given triangle. + /// + /// Side length ab. + /// Side length bc. + /// Side length ca. + /// Triangle area. + /// + public double Measure(double ab, double bc, double ca, double area) + { + double q = (bc + ca - ab) * (ca + ab - bc) * (ab + bc - ca) / (ab * bc * ca); + + q_min = Math.Min(q_min, q); + q_max = Math.Max(q_max, q); + + q_ave += q; + q_area += q * area; + + return q; + } + + /// + /// Normalize values. + /// + public void Normalize(int n, double area_total) + { + if (n > 0) + { + q_ave /= n; + } + else + { + q_ave = 0.0; + } + + if (area_total > 0.0) + { + q_area /= area_total; + } + else + { + q_area = 0.0; + } + } + } + } +} diff --git a/ThirdParty/Triangle/Tools/RegionIterator.cs b/ThirdParty/Triangle/Tools/RegionIterator.cs new file mode 100644 index 0000000..9100f80 --- /dev/null +++ b/ThirdParty/Triangle/Tools/RegionIterator.cs @@ -0,0 +1,139 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Data; + + /// + /// Iterates the region a given triangle belongs to and applies an action + /// to each connected trianlge in that region. Default action is to set the + /// region id. + /// + public class RegionIterator + { + Mesh mesh; + List viri; + + public RegionIterator(Mesh mesh) + { + this.mesh = mesh; + this.viri = new List(); + } + + /// + /// Spread regional attributes and/or area constraints (from a .poly file) + /// throughout the mesh. + /// + /// + /// + /// + /// This procedure operates in two phases. The first phase spreads an + /// attribute and/or an area constraint through a (segment-bounded) region. + /// The triangles are marked to ensure that each triangle is added to the + /// virus pool only once, so the procedure will terminate. + /// + /// The second phase uninfects all infected triangles, returning them to + /// normal. + /// + void ProcessRegion(Action func) + { + Otri testtri = default(Otri); + Otri neighbor = default(Otri); + Osub neighborsubseg = default(Osub); + + Behavior behavior = mesh.behavior; + + // Loop through all the infected triangles, spreading the attribute + // and/or area constraint to their neighbors, then to their neighbors' + // neighbors. + for (int i = 0; i < viri.Count; i++) + { + // WARNING: Don't use foreach, viri list gets modified. + + testtri.triangle = viri[i]; + // A triangle is marked as infected by messing with one of its pointers + // to subsegments, setting it to an illegal value. Hence, we have to + // temporarily uninfect this triangle so that we can examine its + // adjacent subsegments. + // TODO: Not true in the C# version (so we could skip this). + testtri.Uninfect(); + + // Apply function. + func(testtri.triangle); + + // Check each of the triangle's three neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + // Find the neighbor. + testtri.Sym(ref neighbor); + // Check for a subsegment between the triangle and its neighbor. + testtri.SegPivot(ref neighborsubseg); + // Make sure the neighbor exists, is not already infected, and + // isn't protected by a subsegment. + if ((neighbor.triangle != Mesh.dummytri) && !neighbor.IsInfected() + && (neighborsubseg.seg == Mesh.dummysub)) + { + // Infect the neighbor. + neighbor.Infect(); + // Ensure that the neighbor's neighbors will be infected. + viri.Add(neighbor.triangle); + } + } + // Remark the triangle as infected, so it doesn't get added to the + // virus pool again. + testtri.Infect(); + } + + // Uninfect all triangles. + foreach (var virus in viri) + { + virus.infected = false; + } + + // Empty the virus pool. + viri.Clear(); + } + + /// + /// Set the region attribute of all trianlges connected to given triangle. + /// + public void Process(Triangle triangle) + { + // Default action is to just set the region id for all trianlges. + this.Process(triangle, (tri) => { tri.region = triangle.region; }); + } + + /// + /// Process all trianlges connected to given triangle and apply given action. + /// + public void Process(Triangle triangle, Action func) + { + if (triangle != Mesh.dummytri) + { + // Make sure the triangle under consideration still exists. + // It may have been eaten by the virus. + if (!Otri.IsDead(triangle)) + { + // Put one triangle in the virus pool. + triangle.infected = true; + viri.Add(triangle); + // Apply one region's attribute and/or area constraint. + ProcessRegion(func); + // The virus pool should be empty now. + } + } + + // Free up memory (virus pool should be empty anyway). + viri.Clear(); + } + } +} diff --git a/ThirdParty/Triangle/Tools/Statistic.cs b/ThirdParty/Triangle/Tools/Statistic.cs new file mode 100644 index 0000000..b2ace5e --- /dev/null +++ b/ThirdParty/Triangle/Tools/Statistic.cs @@ -0,0 +1,518 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Text; + using TriangleNet.Data; + using TriangleNet.Geometry; + + /// + /// Gather mesh statistics. + /// + public class Statistic + { + #region Static members + + /// + /// Number of incircle tests performed. + /// + public static long InCircleCount = 0; + public static long InCircleCountDecimal = 0; + + /// + /// Number of counterclockwise tests performed. + /// + public static long CounterClockwiseCount = 0; + public static long CounterClockwiseCountDecimal = 0; + + /// + /// Number of 3D orientation tests performed. + /// + public static long Orient3dCount = 0; + + /// + /// Number of right-of-hyperbola tests performed. + /// + public static long HyperbolaCount = 0; + + /// + /// // Number of circumcenter calculations performed. + /// + public static long CircumcenterCount = 0; + + /// + /// Number of circle top calculations performed. + /// + public static long CircleTopCount = 0; + + /// + /// Number of vertex relocations. + /// + public static long RelocationCount = 0; + + #endregion + + #region Properties + + double minEdge = 0; + /// + /// Gets the shortest edge. + /// + public double ShortestEdge { get { return minEdge; } } + + double maxEdge = 0; + /// + /// Gets the longest edge. + /// + public double LongestEdge { get { return maxEdge; } } + + // + double minAspect = 0; + /// + /// Gets the shortest altitude. + /// + public double ShortestAltitude { get { return minAspect; } } + + double maxAspect = 0; + /// + /// Gets the largest aspect ratio. + /// + public double LargestAspectRatio { get { return maxAspect; } } + + double minArea = 0; + /// + /// Gets the smallest area. + /// + public double SmallestArea { get { return minArea; } } + + double maxArea = 0; + /// + /// Gets the largest area. + /// + public double LargestArea { get { return maxArea; } } + + double minAngle = 0; + /// + /// Gets the smallest angle. + /// + public double SmallestAngle { get { return minAngle; } } + + double maxAngle = 0; + /// + /// Gets the largest angle. + /// + public double LargestAngle { get { return maxAngle; } } + + int inVetrices = 0; + /// + /// Gets the number of input vertices. + /// + public int InputVertices { get { return inVetrices; } } + + int inTriangles = 0; + /// + /// Gets the number of input triangles. + /// + public int InputTriangles { get { return inTriangles; } } + + int inSegments = 0; + /// + /// Gets the number of input segments. + /// + public int InputSegments { get { return inSegments; } } + + int inHoles = 0; + /// + /// Gets the number of input holes. + /// + public int InputHoles { get { return inHoles; } } + + int outVertices = 0; + /// + /// Gets the number of mesh vertices. + /// + public int Vertices { get { return outVertices; } } + + int outTriangles = 0; + /// + /// Gets the number of mesh triangles. + /// + public int Triangles { get { return outTriangles; } } + + int outEdges = 0; + /// + /// Gets the number of mesh edges. + /// + public int Edges { get { return outEdges; } } + + int boundaryEdges = 0; + /// + /// Gets the number of exterior boundary edges. + /// + public int BoundaryEdges { get { return boundaryEdges; } } + + int intBoundaryEdges = 0; + /// + /// Gets the number of interior boundary edges. + /// + public int InteriorBoundaryEdges { get { return intBoundaryEdges; } } + + int constrainedEdges = 0; + /// + /// Gets the number of constrained edges. + /// + public int ConstrainedEdges { get { return constrainedEdges; } } + + int[] angleTable; + /// + /// Gets the angle histogram. + /// + public int[] AngleHistogram { get { return angleTable; } } + + int[] minAngles; + /// + /// Gets the min angles histogram. + /// + public int[] MinAngleHistogram { get { return minAngles; } } + + int[] maxAngles; + /// + /// Gets the max angles histogram. + /// + public int[] MaxAngleHistogram { get { return maxAngles; } } + + #endregion + + #region Private methods + + private void GetAspectHistogram(Mesh mesh) + { + int[] aspecttable; + double[] ratiotable; + + aspecttable = new int[16]; + ratiotable = new double[] { + 1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 10.0, 15.0, 25.0, 50.0, + 100.0, 300.0, 1000.0, 10000.0, 100000.0, 0.0 }; + + + Otri tri = default(Otri); + Vertex[] p = new Vertex[3]; + double[] dx = new double[3], dy = new double[3]; + double[] edgelength = new double[3]; + double triarea; + double trilongest2; + double triminaltitude2; + double triaspect2; + + int aspectindex; + int i, j, k; + + tri.orient = 0; + foreach (var t in mesh.triangles.Values) + { + tri.triangle = t; + p[0] = tri.Org(); + p[1] = tri.Dest(); + p[2] = tri.Apex(); + trilongest2 = 0.0; + + for (i = 0; i < 3; i++) + { + j = plus1Mod3[i]; + k = minus1Mod3[i]; + dx[i] = p[j].x - p[k].x; + dy[i] = p[j].y - p[k].y; + edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i]; + if (edgelength[i] > trilongest2) + { + trilongest2 = edgelength[i]; + } + } + + //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]); + triarea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) - + (p[1].x - p[0].x) * (p[2].y - p[0].y)) / 2.0; + + triminaltitude2 = triarea * triarea / trilongest2; + + triaspect2 = trilongest2 / triminaltitude2; + + aspectindex = 0; + while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) && (aspectindex < 15)) + { + aspectindex++; + } + aspecttable[aspectindex]++; + } + } + + #endregion + + static readonly int[] plus1Mod3 = { 1, 2, 0 }; + static readonly int[] minus1Mod3 = { 2, 0, 1 }; + + /// + /// Update statistics about the quality of the mesh. + /// + /// + public void Update(Mesh mesh, int sampleDegrees) + { + inVetrices = mesh.invertices; + inTriangles = mesh.inelements; + inSegments = mesh.insegments; + inHoles = mesh.holes.Count; + outVertices = mesh.vertices.Count - mesh.undeads; + outTriangles = mesh.triangles.Count; + outEdges = (int)mesh.edges; + boundaryEdges = (int)mesh.hullsize; + intBoundaryEdges = mesh.subsegs.Count - (int)mesh.hullsize; + constrainedEdges = mesh.subsegs.Count; + + Point[] p = new Point[3]; + + int k1, k2; + int degreeStep; + + //sampleDegrees = 36; // sample every 5 degrees + //sampleDegrees = 45; // sample every 4 degrees + sampleDegrees = 60; // sample every 3 degrees + + double[] cosSquareTable = new double[sampleDegrees / 2 - 1]; + double[] dx = new double[3]; + double[] dy = new double[3]; + double[] edgeLength = new double[3]; + double dotProduct; + double cosSquare; + double triArea; + double triLongest2; + double triMinAltitude2; + double triAspect2; + + double radconst = Math.PI / sampleDegrees; + double degconst = 180.0 / Math.PI; + + // New angle table + angleTable = new int[sampleDegrees]; + minAngles = new int[sampleDegrees]; + maxAngles = new int[sampleDegrees]; + + for (int i = 0; i < sampleDegrees / 2 - 1; i++) + { + cosSquareTable[i] = Math.Cos(radconst * (i + 1)); + cosSquareTable[i] = cosSquareTable[i] * cosSquareTable[i]; + } + for (int i = 0; i < sampleDegrees; i++) + { + angleTable[i] = 0; + } + + minAspect = mesh.bounds.Width + mesh.bounds.Height; + minAspect = minAspect * minAspect; + maxAspect = 0.0; + minEdge = minAspect; + maxEdge = 0.0; + minArea = minAspect; + maxArea = 0.0; + minAngle = 0.0; + maxAngle = 2.0; + + bool acuteBiggest = true; + bool acuteBiggestTri = true; + + double triMinAngle, triMaxAngle = 1; + + foreach (var tri in mesh.triangles.Values) + { + triMinAngle = 0; // Min angle: 0 < a < 60 degress + triMaxAngle = 1; // Max angle: 60 < a < 180 degress + + p[0] = tri.vertices[0]; + p[1] = tri.vertices[1]; + p[2] = tri.vertices[2]; + + triLongest2 = 0.0; + + for (int i = 0; i < 3; i++) + { + k1 = plus1Mod3[i]; + k2 = minus1Mod3[i]; + + dx[i] = p[k1].X - p[k2].X; + dy[i] = p[k1].Y - p[k2].Y; + + edgeLength[i] = dx[i] * dx[i] + dy[i] * dy[i]; + + if (edgeLength[i] > triLongest2) + { + triLongest2 = edgeLength[i]; + } + + if (edgeLength[i] > maxEdge) + { + maxEdge = edgeLength[i]; + } + + if (edgeLength[i] < minEdge) + { + minEdge = edgeLength[i]; + } + } + + //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]); + triArea = Math.Abs((p[2].X - p[0].X) * (p[1].Y - p[0].Y) - + (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + + if (triArea < minArea) + { + minArea = triArea; + } + + if (triArea > maxArea) + { + maxArea = triArea; + } + + triMinAltitude2 = triArea * triArea / triLongest2; + if (triMinAltitude2 < minAspect) + { + minAspect = triMinAltitude2; + } + + triAspect2 = triLongest2 / triMinAltitude2; + if (triAspect2 > maxAspect) + { + maxAspect = triAspect2; + } + + for (int i = 0; i < 3; i++) + { + k1 = plus1Mod3[i]; + k2 = minus1Mod3[i]; + + dotProduct = dx[k1] * dx[k2] + dy[k1] * dy[k2]; + cosSquare = dotProduct * dotProduct / (edgeLength[k1] * edgeLength[k2]); + degreeStep = sampleDegrees / 2 - 1; + + for (int j = degreeStep - 1; j >= 0; j--) + { + if (cosSquare > cosSquareTable[j]) + { + degreeStep = j; + } + } + + if (dotProduct <= 0.0) + { + angleTable[degreeStep]++; + if (cosSquare > minAngle) + { + minAngle = cosSquare; + } + if (acuteBiggest && (cosSquare < maxAngle)) + { + maxAngle = cosSquare; + } + + // Update min/max angle per triangle + if (cosSquare > triMinAngle) + { + triMinAngle = cosSquare; + } + if (acuteBiggestTri && (cosSquare < triMaxAngle)) + { + triMaxAngle = cosSquare; + } + } + else + { + angleTable[sampleDegrees - degreeStep - 1]++; + if (acuteBiggest || (cosSquare > maxAngle)) + { + maxAngle = cosSquare; + acuteBiggest = false; + } + + // Update max angle for (possibly non-acute) triangle + if (acuteBiggestTri || (cosSquare > triMaxAngle)) + { + triMaxAngle = cosSquare; + acuteBiggestTri = false; + } + } + } + + // Update min angle histogram + degreeStep = sampleDegrees / 2 - 1; + + for (int j = degreeStep - 1; j >= 0; j--) + { + if (triMinAngle > cosSquareTable[j]) + { + degreeStep = j; + } + } + minAngles[degreeStep]++; + + // Update max angle histogram + degreeStep = sampleDegrees / 2 - 1; + + for (int j = degreeStep - 1; j >= 0; j--) + { + if (triMaxAngle > cosSquareTable[j]) + { + degreeStep = j; + } + } + + if (acuteBiggestTri) + { + maxAngles[degreeStep]++; + } + else + { + maxAngles[sampleDegrees - degreeStep - 1]++; + } + + acuteBiggestTri = true; + } + + minEdge = Math.Sqrt(minEdge); + maxEdge = Math.Sqrt(maxEdge); + minAspect = Math.Sqrt(minAspect); + maxAspect = Math.Sqrt(maxAspect); + minArea *= 0.5; + maxArea *= 0.5; + if (minAngle >= 1.0) + { + minAngle = 0.0; + } + else + { + minAngle = degconst * Math.Acos(Math.Sqrt(minAngle)); + } + + if (maxAngle >= 1.0) + { + maxAngle = 180.0; + } + else + { + if (acuteBiggest) + { + maxAngle = degconst * Math.Acos(Math.Sqrt(maxAngle)); + } + else + { + maxAngle = 180.0 - degconst * Math.Acos(Math.Sqrt(maxAngle)); + } + } + } + } +} diff --git a/ThirdParty/Triangle/Tools/Voronoi.cs b/ThirdParty/Triangle/Tools/Voronoi.cs new file mode 100644 index 0000000..c8470b0 --- /dev/null +++ b/ThirdParty/Triangle/Tools/Voronoi.cs @@ -0,0 +1,337 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using TriangleNet.Data; +using TriangleNet.Geometry; + +namespace TriangleNet.Tools +{ + /// + /// The Voronoi Diagram is the dual of a pointset triangulation. + /// + public class Voronoi : IVoronoi + { + Mesh mesh; + + Point[] points; + List regions; + + // Stores the endpoints of rays of infinite Voronoi cells + Dictionary rayPoints; + int rayIndex; + + // Bounding box of the triangles circumcenters. + BoundingBox bounds; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// Be sure MakeVertexMap has been called (should always be the case). + /// + public Voronoi(Mesh mesh) + { + this.mesh = mesh; + + Generate(); + } + + /// + /// Gets the list of Voronoi vertices. + /// + public Point[] Points + { + get { return points; } + } + + /// + /// Gets the list of Voronoi regions. + /// + public List Regions + { + get { return regions; } + } + + /// + /// Gets the Voronoi diagram as raw output data. + /// + /// + /// + /// + /// The Voronoi diagram is the geometric dual of the Delaunay triangulation. + /// Hence, the Voronoi vertices are listed by traversing the Delaunay + /// triangles, and the Voronoi edges are listed by traversing the Delaunay + /// edges. + /// + private void Generate() + { + mesh.Renumber(); + mesh.MakeVertexMap(); + + // Allocate space for voronoi diagram + this.points = new Point[mesh.triangles.Count + mesh.hullsize]; + this.regions = new List(mesh.vertices.Count); + + rayPoints = new Dictionary(); + rayIndex = 0; + + bounds = new BoundingBox(); + + // Compute triangles circumcenters and setup bounding box + ComputeCircumCenters(); + + // Loop over the mesh vertices (Voronoi generators). + foreach (var item in mesh.vertices.Values) + { + //if (item.Boundary == 0) + { + ConstructVoronoiRegion(item); + } + } + } + + private void ComputeCircumCenters() + { + Otri tri = default(Otri); + double xi = 0, eta = 0; + Point pt; + + // Compue triangle circumcenters + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + pt = Primitives.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); + pt.id = item.id; + + points[item.id] = pt; + + bounds.Update(pt.x, pt.y); + } + + double ds = Math.Max(bounds.Width, bounds.Height); + bounds.Scale(ds, ds); + } + + /// + /// Construct Voronoi region for given vertex. + /// + /// + /// The circumcenter indices which make up the cell. + private void ConstructVoronoiRegion(Vertex vertex) + { + VoronoiRegion region = new VoronoiRegion(vertex); + regions.Add(region); + + List vpoints = new List(); + + Otri f = default(Otri); + Otri f_init = default(Otri); + Otri f_next = default(Otri); + Otri f_prev = default(Otri); + + Osub sub = default(Osub); + + // Call f_init a triangle incident to x + vertex.tri.Copy(ref f_init); + + f_init.Copy(ref f); + f_init.Onext(ref f_next); + + // Check if f_init lies on the boundary of the triangulation. + if (f_next.triangle == Mesh.dummytri) + { + f_init.Oprev(ref f_prev); + + if (f_prev.triangle != Mesh.dummytri) + { + f_init.Copy(ref f_next); + // Move one triangle clockwise + f_init.OprevSelf(); + f_init.Copy(ref f); + } + } + + // Go counterclockwise until we reach the border or the initial triangle. + while (f_next.triangle != Mesh.dummytri) + { + // Add circumcenter of current triangle + vpoints.Add(points[f.triangle.id]); + + if (f_next.Equal(f_init)) + { + // Voronoi cell is complete (bounded case). + region.Add(vpoints); + return; + } + + f_next.Copy(ref f); + f_next.OnextSelf(); + } + + // Voronoi cell is unbounded + region.Bounded = false; + + Vertex torg, tdest, tapex, intersection; + int sid, n = mesh.triangles.Count; + + // Find the boundary segment id. + f.Lprev(ref f_next); + f_next.SegPivot(ref sub); + sid = sub.seg.hash; + + // Last valid f lies at the boundary. Add the circumcenter. + vpoints.Add(points[f.triangle.id]); + + // Check if the intersection with the bounding box has already been computed. + if (rayPoints.ContainsKey(sid)) + { + vpoints.Add(rayPoints[sid]); + } + else + { + torg = f.Org(); + tapex = f.Apex(); + BoxRayIntersection(points[f.triangle.id], torg.y - tapex.y, tapex.x - torg.x, out intersection); + + // Set the correct id for the vertex + intersection.id = n + rayIndex; + + points[n + rayIndex] = intersection; + + rayIndex++; + + vpoints.Add(intersection); + rayPoints.Add(sid, intersection); + } + + // Now walk from f_init clockwise till we reach the boundary. + vpoints.Reverse(); + + f_init.Copy(ref f); + f.Oprev(ref f_prev); + + while (f_prev.triangle != Mesh.dummytri) + { + vpoints.Add(points[f_prev.triangle.id]); + + f_prev.Copy(ref f); + f_prev.OprevSelf(); + } + + // Find the boundary segment id. + f.SegPivot(ref sub); + sid = sub.seg.hash; + + if (rayPoints.ContainsKey(sid)) + { + vpoints.Add(rayPoints[sid]); + } + else + { + // Intersection has not been computed yet. + torg = f.Org(); + tdest = f.Dest(); + + BoxRayIntersection(points[f.triangle.id], tdest.y - torg.y, torg.x - tdest.x, out intersection); + + // Set the correct id for the vertex + intersection.id = n + rayIndex; + + points[n + rayIndex] = intersection; + + rayIndex++; + + vpoints.Add(intersection); + rayPoints.Add(sid, intersection); + } + + // Add the new points to the region (in counter-clockwise order) + vpoints.Reverse(); + region.Add(vpoints); + } + + private bool BoxRayIntersection(Point pt, double dx, double dy, out Vertex intersect) + { + double x = pt.X; + double y = pt.Y; + + double t1, x1, y1, t2, x2, y2; + + // Bounding box + double minX = bounds.Xmin; + double maxX = bounds.Xmax; + double minY = bounds.Ymin; + double maxY = bounds.Ymax; + + // Check if point is inside the bounds + if (x < minX || x > maxX || y < minY || y > maxY) + { + intersect = null; + return false; + } + + // Calculate the cut through the vertical boundaries + if (dx < 0) + { + // Line going to the left: intersect with x = minX + t1 = (minX - x) / dx; + x1 = minX; + y1 = y + t1 * dy; + } + else if (dx > 0) + { + // Line going to the right: intersect with x = maxX + t1 = (maxX - x) / dx; + x1 = maxX; + y1 = y + t1 * dy; + } + else + { + // Line going straight up or down: no intersection possible + t1 = double.MaxValue; + x1 = y1 = 0; + } + + // Calculate the cut through upper and lower boundaries + if (dy < 0) + { + // Line going downwards: intersect with y = minY + t2 = (minY - y) / dy; + x2 = x + t2 * dx; + y2 = minY; + } + else if (dx > 0) + { + // Line going upwards: intersect with y = maxY + t2 = (maxY - y) / dy; + x2 = x + t2 * dx; + y2 = maxY; + } + else + { + // Horizontal line: no intersection possible + t2 = double.MaxValue; + x2 = y2 = 0; + } + + if (t1 < t2) + { + intersect = new Vertex(x1, y1, -1); + } + else + { + intersect = new Vertex(x2, y2, -1); + } + + return true; + } + } +} diff --git a/ThirdParty/Triangle/Tools/VoronoiRegion.cs b/ThirdParty/Triangle/Tools/VoronoiRegion.cs new file mode 100644 index 0000000..ea26b86 --- /dev/null +++ b/ThirdParty/Triangle/Tools/VoronoiRegion.cs @@ -0,0 +1,82 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + using TriangleNet.Data; + + /// + /// Represents a region in the Voronoi diagram. + /// + public class VoronoiRegion + { + int id; + Point generator; + List vertices; + bool bounded; + + /// + /// Gets the Voronoi region id (which is the same as the generators vertex id). + /// + public int ID + { + get { return id; } + } + + /// + /// Gets the Voronoi regions generator. + /// + public Point Generator + { + get { return generator; } + } + + /// + /// Gets the Voronoi vertices on the regions boundary. + /// + public ICollection Vertices + { + get { return vertices; } + } + + /// + /// Gets or sets whether the Voronoi region is bounded. + /// + public bool Bounded + { + get { return bounded; } + set { bounded = value; } + } + + public VoronoiRegion(Vertex generator) + { + this.id = generator.id; + this.generator = generator; + this.vertices = new List(); + this.bounded = true; + } + + public void Add(Point point) + { + this.vertices.Add(point); + } + + public void Add(List points) + { + this.vertices.AddRange(points); + } + + public override string ToString() + { + return String.Format("R-ID {0}", id); + } + } +} diff --git a/ThirdParty/Triangle/Triangle.csproj b/ThirdParty/Triangle/Triangle.csproj new file mode 100644 index 0000000..7fd80b9 --- /dev/null +++ b/ThirdParty/Triangle/Triangle.csproj @@ -0,0 +1,108 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B} + Library + Properties + TriangleNet + Triangle + v4.0 + 512 + Client + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ThirdParty/Triangle/TriangleLocator.cs b/ThirdParty/Triangle/TriangleLocator.cs new file mode 100644 index 0000000..06d0a08 --- /dev/null +++ b/ThirdParty/Triangle/TriangleLocator.cs @@ -0,0 +1,339 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using TriangleNet.Data; + using TriangleNet.Geometry; + + /// + /// TODO: Update summary. + /// + class TriangleLocator + { + Sampler sampler; + Mesh mesh; + + // Pointer to a recently visited triangle. Improves point location if + // proximate vertices are inserted sequentially. + internal Otri recenttri; + + public TriangleLocator(Mesh mesh) + { + this.mesh = mesh; + + sampler = new Sampler(); + } + + public void Update(ref Otri otri) + { + otri.Copy(ref recenttri); + } + + public void Reset() + { + recenttri.triangle = null; // No triangle has been visited yet. + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// If 'stopatsubsegment' is set, the search + /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. + /// Location information. + /// + /// Begins its search from 'searchtri'. It is important that 'searchtri' + /// be a handle with the property that 'searchpoint' is strictly to the left + /// of the edge denoted by 'searchtri', or is collinear with that edge and + /// does not intersect that edge. (In particular, 'searchpoint' should not + /// be the origin or destination of that edge.) + /// + /// These conditions are imposed because preciselocate() is normally used in + /// one of two situations: + /// + /// (1) To try to find the location to insert a new point. Normally, we + /// know an edge that the point is strictly to the left of. In the + /// incremental Delaunay algorithm, that edge is a bounding box edge. + /// In Ruppert's Delaunay refinement algorithm for quality meshing, + /// that edge is the shortest edge of the triangle whose circumcenter + /// is being inserted. + /// + /// (2) To try to find an existing point. In this case, any edge on the + /// convex hull is a good starting edge. You must screen out the + /// possibility that the vertex sought is an endpoint of the starting + /// edge before you call preciselocate(). + /// + /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. + /// + /// This implementation differs from that given by Guibas and Stolfi. It + /// walks from triangle to triangle, crossing an edge only if 'searchpoint' + /// is on the other side of the line containing that edge. After entering + /// a triangle, there are two edges by which one can leave that triangle. + /// If both edges are valid ('searchpoint' is on the other side of both + /// edges), one of the two is chosen by drawing a line perpendicular to + /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through + /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' + /// falls on, an exit edge is chosen. + /// + /// This implementation is empirically faster than the Guibas and Stolfi + /// point location routine (which I originally used), which tends to spiral + /// in toward its target. + /// + /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' + /// is a handle whose origin is the existing vertex. + /// + /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a + /// handle whose primary edge is the edge on which the point lies. + /// + /// Returns INTRIANGLE if the point lies strictly within a triangle. + /// 'searchtri' is a handle on the triangle that contains the point. + /// + /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a + /// handle whose primary edge the point is to the right of. This might + /// occur when the circumcenter of a triangle falls just slightly outside + /// the mesh due to floating-point roundoff error. It also occurs when + /// seeking a hole or region point that a foolish user has placed outside + /// the mesh. + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// However, it can still be used to find the circumcenter of a triangle, as + /// long as the search is begun from the triangle in question. + public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, + bool stopatsubsegment) + { + Otri backtracktri = default(Otri); + Osub checkedge = default(Osub); + Vertex forg, fdest, fapex; + double orgorient, destorient; + bool moveleft; + + // Where are we? + forg = searchtri.Org(); + fdest = searchtri.Dest(); + fapex = searchtri.Apex(); + while (true) + { + // Check whether the apex is the point we seek. + if ((fapex.x == searchpoint.X) && (fapex.y == searchpoint.Y)) + { + searchtri.LprevSelf(); + return LocateResult.OnVertex; + } + // Does the point lie on the other side of the line defined by the + // triangle edge opposite the triangle's destination? + destorient = Primitives.CounterClockwise(forg, fapex, searchpoint); + // Does the point lie on the other side of the line defined by the + // triangle edge opposite the triangle's origin? + orgorient = Primitives.CounterClockwise(fapex, fdest, searchpoint); + if (destorient > 0.0) + { + if (orgorient > 0.0) + { + // Move left if the inner product of (fapex - searchpoint) and + // (fdest - forg) is positive. This is equivalent to drawing + // a line perpendicular to the line (forg, fdest) and passing + // through 'fapex', and determining which side of this line + // 'searchpoint' falls on. + moveleft = (fapex.x - searchpoint.X) * (fdest.x - forg.x) + + (fapex.y - searchpoint.Y) * (fdest.y - forg.y) > 0.0; + } + else + { + moveleft = true; + } + } + else + { + if (orgorient > 0.0) + { + moveleft = false; + } + else + { + // The point we seek must be on the boundary of or inside this + // triangle. + if (destorient == 0.0) + { + searchtri.LprevSelf(); + return LocateResult.OnEdge; + } + if (orgorient == 0.0) + { + searchtri.LnextSelf(); + return LocateResult.OnEdge; + } + return LocateResult.InTriangle; + } + } + + // Move to another triangle. Leave a trace 'backtracktri' in case + // floating-point roundoff or some such bogey causes us to walk + // off a boundary of the triangulation. + if (moveleft) + { + searchtri.Lprev(ref backtracktri); + fdest = fapex; + } + else + { + searchtri.Lnext(ref backtracktri); + forg = fapex; + } + backtracktri.Sym(ref searchtri); + + if (mesh.checksegments && stopatsubsegment) + { + // Check for walking through a subsegment. + backtracktri.SegPivot(ref checkedge); + if (checkedge.seg != Mesh.dummysub) + { + // Go back to the last triangle. + backtracktri.Copy(ref searchtri); + return LocateResult.Outside; + } + } + // Check for walking right out of the triangulation. + if (searchtri.triangle == Mesh.dummytri) + { + // Go back to the last triangle. + backtracktri.Copy(ref searchtri); + return LocateResult.Outside; + } + + fapex = searchtri.Apex(); + } + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// Location information. + /// + /// Searching begins from one of: the input 'searchtri', a recently + /// encountered triangle 'recenttri', or from a triangle chosen from a + /// random sample. The choice is made by determining which triangle's + /// origin is closest to the point we are searching for. Normally, + /// 'searchtri' should be a handle on the convex hull of the triangulation. + /// + /// Details on the random sampling method can be found in the Mucke, Saias, + /// and Zhu paper cited in the header of this code. + /// + /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. + /// + /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' + /// is a handle whose origin is the existing vertex. + /// + /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a + /// handle whose primary edge is the edge on which the point lies. + /// + /// Returns INTRIANGLE if the point lies strictly within a triangle. + /// 'searchtri' is a handle on the triangle that contains the point. + /// + /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a + /// handle whose primary edge the point is to the right of. This might + /// occur when the circumcenter of a triangle falls just slightly outside + /// the mesh due to floating-point roundoff error. It also occurs when + /// seeking a hole or region point that a foolish user has placed outside + /// the mesh. + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// + public LocateResult Locate(Point searchpoint, ref Otri searchtri) + { + Otri sampletri = default(Otri); + Vertex torg, tdest; + double searchdist, dist; + double ahead; + + // Record the distance from the suggested starting triangle to the + // point we seek. + torg = searchtri.Org(); + searchdist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); + + // If a recently encountered triangle has been recorded and has not been + // deallocated, test it as a good starting point. + if (recenttri.triangle != null) + { + if (!Otri.IsDead(recenttri.triangle)) + { + torg = recenttri.Org(); + if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) + { + recenttri.Copy(ref searchtri); + return LocateResult.OnVertex; + } + dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); + if (dist < searchdist) + { + recenttri.Copy(ref searchtri); + searchdist = dist; + } + } + } + + // TODO: Improve sampling. + sampler.Update(mesh); + int[] samples = sampler.GetSamples(mesh); + + foreach (var key in samples) + { + sampletri.triangle = mesh.triangles[key]; + if (!Otri.IsDead(sampletri.triangle)) + { + torg = sampletri.Org(); + dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); + if (dist < searchdist) + { + sampletri.Copy(ref searchtri); + searchdist = dist; + } + } + } + + // Where are we? + torg = searchtri.Org(); + tdest = searchtri.Dest(); + // Check the starting triangle's vertices. + if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) + { + return LocateResult.OnVertex; + } + if ((tdest.x == searchpoint.X) && (tdest.y == searchpoint.Y)) + { + searchtri.LnextSelf(); + return LocateResult.OnVertex; + } + // Orient 'searchtri' to fit the preconditions of calling preciselocate(). + ahead = Primitives.CounterClockwise(torg, tdest, searchpoint); + if (ahead < 0.0) + { + // Turn around so that 'searchpoint' is to the left of the + // edge specified by 'searchtri'. + searchtri.SymSelf(); + } + else if (ahead == 0.0) + { + // Check if 'searchpoint' is between 'torg' and 'tdest'. + if (((torg.x < searchpoint.X) == (searchpoint.X < tdest.x)) && + ((torg.y < searchpoint.Y) == (searchpoint.Y < tdest.y))) + { + return LocateResult.OnEdge; + } + } + return PreciseLocate(searchpoint, ref searchtri, false); + } + } +} diff --git a/ThirdParty/clipper_library/Properties/AssemblyInfo.cs b/ThirdParty/clipper_library/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..296eaae --- /dev/null +++ b/ThirdParty/clipper_library/Properties/AssemblyInfo.cs @@ -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("clipper_library")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("clipper_library")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[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("51a6bdca-bc4e-4b2c-ae69-36e2497204f2")] + +// 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")] diff --git a/ThirdParty/clipper_library/clipper.cs b/ThirdParty/clipper_library/clipper.cs new file mode 100644 index 0000000..f14ecdf --- /dev/null +++ b/ThirdParty/clipper_library/clipper.cs @@ -0,0 +1,4782 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.1.2 * +* Date : 15 December 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to performance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +//#define use_lines + +//use_deprecated: Enables support for the obsolete OffsetPaths() function +//which has been replace with the ClipperOffset class. +#define use_deprecated + + +using System; +using System.Collections.Generic; +//using System.Text; //for Int128.AsString() & StringBuilder +//using System.IO; //debugging with streamReader & StreamWriter +//using System.Windows.Forms; //debugging to clipboard + +namespace ClipperLib +{ + +#if use_int32 + using cInt = Int32; +#else + using cInt = Int64; +#endif + + using Path = List; + using Paths = List>; + + public struct DoublePoint + { + public double X; + public double Y; + + public DoublePoint(double x = 0, double y = 0) + { + this.X = x; this.Y = y; + } + public DoublePoint(DoublePoint dp) + { + this.X = dp.X; this.Y = dp.Y; + } + public DoublePoint(IntPoint ip) + { + this.X = ip.X; this.Y = ip.Y; + } + }; + + + //------------------------------------------------------------------------------ + // PolyTree & PolyNode classes + //------------------------------------------------------------------------------ + + public class PolyTree : PolyNode + { + internal List m_AllPolys = new List(); + + ~PolyTree() + { + Clear(); + } + + public void Clear() + { + for (int i = 0; i < m_AllPolys.Count; i++) + m_AllPolys[i] = null; + m_AllPolys.Clear(); + m_Childs.Clear(); + } + + public PolyNode GetFirst() + { + if (m_Childs.Count > 0) + return m_Childs[0]; + else + return null; + } + + public int Total + { + get { return m_AllPolys.Count; } + } + + } + + public class PolyNode + { + internal PolyNode m_Parent; + internal Path m_polygon = new Path(); + internal int m_Index; + internal JoinType m_jointype; + internal EndType m_endtype; + internal List m_Childs = new List(); + + private bool IsHoleNode() + { + bool result = true; + PolyNode node = m_Parent; + while (node != null) + { + result = !result; + node = node.m_Parent; + } + return result; + } + + public int ChildCount + { + get { return m_Childs.Count; } + } + + public Path Contour + { + get { return m_polygon; } + } + + internal void AddChild(PolyNode Child) + { + int cnt = m_Childs.Count; + m_Childs.Add(Child); + Child.m_Parent = this; + Child.m_Index = cnt; + } + + public PolyNode GetNext() + { + if (m_Childs.Count > 0) + return m_Childs[0]; + else + return GetNextSiblingUp(); + } + + internal PolyNode GetNextSiblingUp() + { + if (m_Parent == null) + return null; + else if (m_Index == m_Parent.m_Childs.Count - 1) + return m_Parent.GetNextSiblingUp(); + else + return m_Parent.m_Childs[m_Index + 1]; + } + + public List Childs + { + get { return m_Childs; } + } + + public PolyNode Parent + { + get { return m_Parent; } + } + + public bool IsHole + { + get { return IsHoleNode(); } + } + + public bool IsOpen { get; set; } + } + + + //------------------------------------------------------------------------------ + // Int128 struct (enables safe math on signed 64bit integers) + // eg Int128 val1((Int64)9223372036854775807); //ie 2^63 -1 + // Int128 val2((Int64)9223372036854775807); + // Int128 val3 = val1 * val2; + // val3.ToString => "85070591730234615847396907784232501249" (8.5e+37) + //------------------------------------------------------------------------------ + + internal struct Int128 + { + private Int64 hi; + private UInt64 lo; + + public Int128(Int64 _lo) + { + lo = (UInt64)_lo; + if (_lo < 0) hi = -1; + else hi = 0; + } + + public Int128(Int64 _hi, UInt64 _lo) + { + lo = _lo; + hi = _hi; + } + + public Int128(Int128 val) + { + hi = val.hi; + lo = val.lo; + } + + public bool IsNegative() + { + return hi < 0; + } + + public static bool operator ==(Int128 val1, Int128 val2) + { + if ((object)val1 == (object)val2) return true; + else if ((object)val1 == null || (object)val2 == null) return false; + return (val1.hi == val2.hi && val1.lo == val2.lo); + } + + public static bool operator !=(Int128 val1, Int128 val2) + { + return !(val1 == val2); + } + + public override bool Equals(System.Object obj) + { + if (obj == null || !(obj is Int128)) + return false; + Int128 i128 = (Int128)obj; + return (i128.hi == hi && i128.lo == lo); + } + + public override int GetHashCode() + { + return hi.GetHashCode() ^ lo.GetHashCode(); + } + + public static bool operator >(Int128 val1, Int128 val2) + { + if (val1.hi != val2.hi) + return val1.hi > val2.hi; + else + return val1.lo > val2.lo; + } + + public static bool operator <(Int128 val1, Int128 val2) + { + if (val1.hi != val2.hi) + return val1.hi < val2.hi; + else + return val1.lo < val2.lo; + } + + public static Int128 operator +(Int128 lhs, Int128 rhs) + { + lhs.hi += rhs.hi; + lhs.lo += rhs.lo; + if (lhs.lo < rhs.lo) lhs.hi++; + return lhs; + } + + public static Int128 operator -(Int128 lhs, Int128 rhs) + { + return lhs + -rhs; + } + + public static Int128 operator -(Int128 val) + { + if (val.lo == 0) + return new Int128(-val.hi, 0); + else + return new Int128(~val.hi, ~val.lo + 1); + } + + //nb: Constructing two new Int128 objects every time we want to multiply longs + //is slow. So, although calling the Int128Mul method doesn't look as clean, the + //code runs significantly faster than if we'd used the * operator. + + public static Int128 Int128Mul(Int64 lhs, Int64 rhs) + { + bool negate = (lhs < 0) != (rhs < 0); + if (lhs < 0) lhs = -lhs; + if (rhs < 0) rhs = -rhs; + UInt64 int1Hi = (UInt64)lhs >> 32; + UInt64 int1Lo = (UInt64)lhs & 0xFFFFFFFF; + UInt64 int2Hi = (UInt64)rhs >> 32; + UInt64 int2Lo = (UInt64)rhs & 0xFFFFFFFF; + + //nb: see comments in clipper.pas + UInt64 a = int1Hi * int2Hi; + UInt64 b = int1Lo * int2Lo; + UInt64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + UInt64 lo; + Int64 hi; + hi = (Int64)(a + (c >> 32)); + + unchecked { lo = (c << 32) + b; } + if (lo < b) hi++; + Int128 result = new Int128(hi, lo); + return negate ? -result : result; + } + + public static Int128 operator /(Int128 lhs, Int128 rhs) + { + if (rhs.lo == 0 && rhs.hi == 0) + throw new ClipperException("Int128: divide by zero"); + + bool negate = (rhs.hi < 0) != (lhs.hi < 0); + if (lhs.hi < 0) lhs = -lhs; + if (rhs.hi < 0) rhs = -rhs; + + if (rhs < lhs) + { + Int128 result = new Int128(0); + Int128 cntr = new Int128(1); + while (rhs.hi >= 0 && !(rhs > lhs)) + { + rhs.hi <<= 1; + if ((Int64)rhs.lo < 0) rhs.hi++; + rhs.lo <<= 1; + + cntr.hi <<= 1; + if ((Int64)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + rhs.lo >>= 1; + if ((rhs.hi & 1) == 1) + rhs.lo |= 0x8000000000000000; + rhs.hi = (Int64)((UInt64)rhs.hi >> 1); + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(lhs < rhs)) + { + lhs -= rhs; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + rhs.lo >>= 1; + if ((rhs.hi & 1) == 1) + rhs.lo |= 0x8000000000000000; + rhs.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000; + cntr.hi >>= 1; + } + return negate ? -result : result; + } + else if (rhs == lhs) + return new Int128(negate ? -1 : 1); + else + return new Int128(0); + } + + public double ToDouble() + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + UInt64 lo_ = (~lo + 1); + if (lo_ == 0) + return (double)hi * shift64; + else + return -(double)(lo_ + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + + }; + + //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + + public struct IntPoint + { + public cInt X; + public cInt Y; +#if use_xyz + public cInt Z; + + public IntPoint(cInt x, cInt y, cInt z = 0) + { + this.X = x; this.Y = y; this.Z = z; + } + + public IntPoint(double x, double y, double z = 0) + { + this.X = (cInt)x; this.Y = (cInt)y; this.Z = (cInt)z; + } + + public IntPoint(DoublePoint dp) + { + this.X = (cInt)dp.X; this.Y = (cInt)dp.Y; this.Z = 0; + } + + public IntPoint(IntPoint pt) + { + this.X = pt.X; this.Y = pt.Y; this.Z = pt.Z; + } +#else + public IntPoint(cInt X, cInt Y) + { + this.X = X; this.Y = Y; + } + public IntPoint(double x, double y) + { + this.X = (cInt)x; this.Y = (cInt)y; + } + + public IntPoint(IntPoint pt) + { + this.X = pt.X; this.Y = pt.Y; + } +#endif + + public static bool operator ==(IntPoint a, IntPoint b) + { + return a.X == b.X && a.Y == b.Y; + } + + public static bool operator !=(IntPoint a, IntPoint b) + { + return a.X != b.X || a.Y != b.Y; + } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (obj is IntPoint) + { + IntPoint a = (IntPoint)obj; + return (X == a.X) && (Y == a.Y); + } + else return false; + } + + public override int GetHashCode() + { + //simply prevents a compiler warning + return base.GetHashCode(); + } +} + + public struct IntRect + { + public cInt left; + public cInt top; + public cInt right; + public cInt bottom; + + public IntRect(cInt l, cInt t, cInt r, cInt b) + { + this.left = l; this.top = t; + this.right = r; this.bottom = b; + } + public IntRect(IntRect ir) + { + this.left = ir.left; this.top = ir.top; + this.right = ir.right; this.bottom = ir.bottom; + } + } + + public enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; + public enum PolyType { ptSubject, ptClip }; + + //By far the most widely used winding rules for polygon filling are + //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) + //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) + //see http://glprogramming.com/red/chapter11.html + public enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + + public enum JoinType { jtSquare, jtRound, jtMiter }; + public enum EndType { etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound }; +#if use_deprecated + public enum EndType_ { etClosed, etButt, etSquare, etRound }; +#endif + + internal enum EdgeSide {esLeft, esRight}; + internal enum Direction {dRightToLeft, dLeftToRight}; + + internal class TEdge { + internal IntPoint Bot; + internal IntPoint Curr; + internal IntPoint Top; + internal IntPoint Delta; + internal double Dx; + internal PolyType PolyTyp; + internal EdgeSide Side; + internal int WindDelta; //1 or -1 depending on winding direction + internal int WindCnt; + internal int WindCnt2; //winding count of the opposite polytype + internal int OutIdx; + internal TEdge Next; + internal TEdge Prev; + internal TEdge NextInLML; + internal TEdge NextInAEL; + internal TEdge PrevInAEL; + internal TEdge NextInSEL; + internal TEdge PrevInSEL; + }; + + public class IntersectNode + { + internal TEdge Edge1; + internal TEdge Edge2; + internal IntPoint Pt; + }; + + public class MyIntersectNodeSort : IComparer + { + public int Compare(IntersectNode node1, IntersectNode node2) + { + return (int)(node2.Pt.Y - node1.Pt.Y); + } + } + + internal class LocalMinima + { + internal cInt Y; + internal TEdge LeftBound; + internal TEdge RightBound; + internal LocalMinima Next; + }; + + internal class Scanbeam + { + internal cInt Y; + internal Scanbeam Next; + }; + + internal class OutRec + { + internal int Idx; + internal bool IsHole; + internal bool IsOpen; + internal OutRec FirstLeft; //see comments in clipper.pas + internal OutPt Pts; + internal OutPt BottomPt; + internal PolyNode PolyNode; + }; + + internal class OutPt + { + internal int Idx; + internal IntPoint Pt; + internal OutPt Next; + internal OutPt Prev; + }; + + internal class Join + { + internal OutPt OutPt1; + internal OutPt OutPt2; + internal IntPoint OffPt; + }; + + public class ClipperBase + { + protected const double horizontal = -3.4E+38; + protected const int Skip = -2; + protected const int Unassigned = -1; + protected const double tolerance = 1.0E-20; + internal static bool near_zero(double val){return (val > -tolerance) && (val < tolerance);} + +#if use_int32 + internal const cInt loRange = 46340; + internal const cInt hiRange = 46340; +#else + internal const cInt loRange = 0x3FFFFFFF; + internal const cInt hiRange = 0x3FFFFFFFFFFFFFFFL; +#endif + + internal LocalMinima m_MinimaList; + internal LocalMinima m_CurrentLM; + internal List> m_edges = new List>(); + internal bool m_UseFullRange; + internal bool m_HasOpenPaths; + + //------------------------------------------------------------------------------ + + public bool PreserveCollinear + { + get; + set; + } + //------------------------------------------------------------------------------ + + internal static bool IsHorizontal(TEdge e) + { + return e.Delta.Y == 0; + } + //------------------------------------------------------------------------------ + + internal bool PointIsVertex(IntPoint pt, OutPt pp) + { + OutPt pp2 = pp; + do + { + if (pp2.Pt == pt) return true; + pp2 = pp2.Next; + } + while (pp2 != pp); + return false; + } + //------------------------------------------------------------------------------ + + internal bool PointOnLineSegment(IntPoint pt, + IntPoint linePt1, IntPoint linePt2, bool UseFullRange) + { + if (UseFullRange) + return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || + ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || + (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && + ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && + ((Int128.Int128Mul((pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) == + Int128.Int128Mul((linePt2.X - linePt1.X), (pt.Y - linePt1.Y))))); + else + return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || + ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || + (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && + ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && + ((pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == + (linePt2.X - linePt1.X) * (pt.Y - linePt1.Y))); + } + //------------------------------------------------------------------------------ + + internal bool PointOnPolygon(IntPoint pt, OutPt pp, bool UseFullRange) + { + OutPt pp2 = pp; + while (true) + { + if (PointOnLineSegment(pt, pp2.Pt, pp2.Next.Pt, UseFullRange)) + return true; + pp2 = pp2.Next; + if (pp2 == pp) break; + } + return false; + } + //------------------------------------------------------------------------------ + + internal bool PointInPolygon(IntPoint pt, OutPt pp, bool UseFullRange) + { + OutPt pp2 = pp; + bool result = false; + if (UseFullRange) + { + do + { + if (((pp2.Pt.Y > pt.Y) != (pp2.Prev.Pt.Y > pt.Y)) && + (new Int128(pt.X - pp2.Pt.X) < + Int128.Int128Mul(pp2.Prev.Pt.X - pp2.Pt.X, pt.Y - pp2.Pt.Y) / + new Int128(pp2.Prev.Pt.Y - pp2.Pt.Y))) result = !result; + pp2 = pp2.Next; + } + while (pp2 != pp); + } + else + { + do + { + //http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + if (((pp2.Pt.Y > pt.Y) != (pp2.Prev.Pt.Y > pt.Y)) && + ((pt.X - pp2.Pt.X) < (pp2.Prev.Pt.X - pp2.Pt.X) * (pt.Y - pp2.Pt.Y) / + (pp2.Prev.Pt.Y - pp2.Pt.Y))) result = !result; + pp2 = pp2.Next; + } + while (pp2 != pp); + } + return result; + } + //------------------------------------------------------------------------------ + + internal static bool SlopesEqual(TEdge e1, TEdge e2, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(e1.Delta.Y, e2.Delta.X) == + Int128.Int128Mul(e1.Delta.X, e2.Delta.Y); + else return (cInt)(e1.Delta.Y) * (e2.Delta.X) == + (cInt)(e1.Delta.X) * (e2.Delta.Y); + } + //------------------------------------------------------------------------------ + + protected static bool SlopesEqual(IntPoint pt1, IntPoint pt2, + IntPoint pt3, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == + Int128.Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); + else return + (cInt)(pt1.Y - pt2.Y) * (pt2.X - pt3.X) - (cInt)(pt1.X - pt2.X) * (pt2.Y - pt3.Y) == 0; + } + //------------------------------------------------------------------------------ + + protected static bool SlopesEqual(IntPoint pt1, IntPoint pt2, + IntPoint pt3, IntPoint pt4, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == + Int128.Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); + else return + (cInt)(pt1.Y - pt2.Y) * (pt3.X - pt4.X) - (cInt)(pt1.X - pt2.X) * (pt3.Y - pt4.Y) == 0; + } + //------------------------------------------------------------------------------ + + internal ClipperBase() //constructor (nb: no external instantiation) + { + m_MinimaList = null; + m_CurrentLM = null; + m_UseFullRange = false; + m_HasOpenPaths = false; + } + //------------------------------------------------------------------------------ + + public virtual void Clear() + { + DisposeLocalMinimaList(); + for (int i = 0; i < m_edges.Count; ++i) + { + for (int j = 0; j < m_edges[i].Count; ++j) m_edges[i][j] = null; + m_edges[i].Clear(); + } + m_edges.Clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; + } + //------------------------------------------------------------------------------ + + private void DisposeLocalMinimaList() + { + while( m_MinimaList != null ) + { + LocalMinima tmpLm = m_MinimaList.Next; + m_MinimaList = null; + m_MinimaList = tmpLm; + } + m_CurrentLM = null; + } + //------------------------------------------------------------------------------ + + void RangeTest(IntPoint Pt, ref bool useFullRange) + { + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw new ClipperException("Coordinate outside allowed range"); + } + else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, ref useFullRange); + } + } + //------------------------------------------------------------------------------ + + private void InitEdge(TEdge e, TEdge eNext, + TEdge ePrev, IntPoint pt) + { + e.Next = eNext; + e.Prev = ePrev; + e.Curr = pt; + e.OutIdx = Unassigned; + } + //------------------------------------------------------------------------------ + + private void InitEdge2(TEdge e, PolyType polyType) + { + if (e.Curr.Y >= e.Next.Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next.Curr; + } + else + { + e.Top = e.Curr; + e.Bot = e.Next.Curr; + } + SetDx(e); + e.PolyTyp = polyType; + } + //------------------------------------------------------------------------------ + + private TEdge FindNextLocMin(TEdge E) + { + TEdge E2; + for (;;) + { + while (E.Bot != E.Prev.Bot || E.Curr == E.Top) E = E.Next; + if (E.Dx != horizontal && E.Prev.Dx != horizontal) break; + while (E.Prev.Dx == horizontal) E = E.Prev; + E2 = E; + while (E.Dx == horizontal) E = E.Next; + if (E.Top.Y == E.Prev.Bot.Y) continue; //ie just an intermediate horz. + if (E2.Prev.Bot.X < E.Bot.X) E = E2; + break; + } + return E; + } + //------------------------------------------------------------------------------ + + private TEdge ProcessBound(TEdge E, bool IsClockwise) + { + TEdge EStart = E, Result = E; + TEdge Horz; + cInt StartX; + if (E.Dx == horizontal) + { + //it's possible for adjacent overlapping horz edges to start heading left + //before finishing right, so ... + if (IsClockwise) StartX = E.Prev.Bot.X; + else StartX = E.Next.Bot.X; + if (E.Bot.X != StartX) ReverseHorizontal(E); + } + if (Result.OutIdx != Skip) + { + if (IsClockwise) + { + while (Result.Top.Y == Result.Next.Bot.Y && Result.Next.OutIdx != Skip) + Result = Result.Next; + if (Result.Dx == horizontal && Result.Next.OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (Horz.Prev.Dx == horizontal) Horz = Horz.Prev; + if (Horz.Prev.Top.X == Result.Next.Top.X) + { + if (!IsClockwise) Result = Horz.Prev; + } + else if (Horz.Prev.Top.X > Result.Next.Top.X) Result = Horz.Prev; + } + while (E != Result) + { + E.NextInLML = E.Next; + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Prev.Top.X) + ReverseHorizontal(E); + E = E.Next; + } + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Prev.Top.X) + ReverseHorizontal(E); + Result = Result.Next; //move to the edge just beyond current bound + } + else + { + while (Result.Top.Y == Result.Prev.Bot.Y && Result.Prev.OutIdx != Skip) + Result = Result.Prev; + if (Result.Dx == horizontal && Result.Prev.OutIdx != Skip) + { + Horz = Result; + while (Horz.Next.Dx == horizontal) Horz = Horz.Next; + if (Horz.Next.Top.X == Result.Prev.Top.X) + { + if (!IsClockwise) Result = Horz.Next; + } + else if (Horz.Next.Top.X > Result.Prev.Top.X) Result = Horz.Next; + } + + while (E != Result) + { + E.NextInLML = E.Prev; + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Next.Top.X) + ReverseHorizontal(E); + E = E.Prev; + } + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Next.Top.X) + ReverseHorizontal(E); + Result = Result.Prev; //move to the edge just beyond current bound + } + } + + if (Result.OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + E = Result; + if (IsClockwise) + { + while (E.Top.Y == E.Next.Bot.Y) E = E.Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && E.Dx == horizontal) E = E.Prev; + } else + { + while (E.Top.Y == E.Prev.Bot.Y) E = E.Prev; + while (E != Result && E.Dx == horizontal) E = E.Next; + } + if (E == Result) + { + if (IsClockwise) Result = E.Next; + else Result = E.Prev; + } else + { + //there are more edges in the bound beyond result starting with E + if (IsClockwise) + E = Result.Next; + else + E = Result.Prev; + LocalMinima locMin = new LocalMinima(); + locMin.Next = null; + locMin.Y = E.Bot.Y; + locMin.LeftBound = null; + locMin.RightBound = E; + locMin.RightBound.WindDelta = 0; + Result = ProcessBound(locMin.RightBound, IsClockwise); + InsertLocalMinima(locMin); + } + } + return Result; + } + //------------------------------------------------------------------------------ + + + public bool AddPath(Path pg, PolyType polyType, bool Closed) + { +#if use_lines + if (!Closed && polyType == PolyType.ptClip) + throw new ClipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw new ClipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.Count - 1; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI - 1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; + + //create a new edge array ... + List edges = new List(highI+1); + for (int i = 0; i <= highI; i++) edges.Add(new TEdge()); + + bool IsFlat = true; + + //1. Basic (first) edge initialization ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], ref m_UseFullRange); + RangeTest(pg[highI], ref m_UseFullRange); + InitEdge(edges[0], edges[1], edges[highI], pg[0]); + InitEdge(edges[highI], edges[0], edges[highI - 1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], ref m_UseFullRange); + InitEdge(edges[i], edges[i + 1], edges[i - 1], pg[i]); + } + } + catch + { + return false; //almost certainly a vertex has exceeded range + }; + + TEdge eStart = edges[0]; + if (!Closed) eStart.Prev.OutIdx = Skip; + + //2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge E = eStart, eLoopStop = eStart; + for (;;) + { + if (E.Curr == E.Next.Curr) + { + if (E == E.Next) break; + if (E == eStart) eStart = E.Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E.Prev == E.Next) + break; //only two vertices + else if (Closed && + SlopesEqual(E.Prev.Curr, E.Curr, E.Next.Curr, m_UseFullRange) && + (!PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E.Prev.Curr, E.Curr, E.Next.Curr))) + { + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E.Next; + E = RemoveEdge(E); + E = E.Prev; + eLoopStop = E; + continue; + } + E = E.Next; + if (E == eLoopStop) break; + } + + if ((!Closed && (E == E.Next)) || (Closed && (E.Prev == E.Next))) + return false; + + if (!Closed) m_HasOpenPaths = true; + + //3. Do second stage of edge initialization ... + TEdge eHighest = eStart; + E = eStart; + do + { + InitEdge2(E, polyType); + E = E.Next; + if (IsFlat && E.Curr.Y != eStart.Curr.Y) IsFlat = false; + } + while (E != eStart); + + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) + { + if (Closed) return false; + E.Prev.OutIdx = Skip; + if (E.Prev.Bot.X < E.Prev.Top.X) ReverseHorizontal(E.Prev); + LocalMinima locMin = new LocalMinima(); + locMin.Next = null; + locMin.Y = E.Bot.Y; + locMin.LeftBound = null; + locMin.RightBound = E; + locMin.RightBound.Side = EdgeSide.esRight; + locMin.RightBound.WindDelta = 0; + while (E.Next.OutIdx != Skip) + { + E.NextInLML = E.Next; + if (E.Bot.X != E.Prev.Top.X) ReverseHorizontal(E); + E = E.Next; + } + InsertLocalMinima(locMin); + m_edges.Add(edges); + return true; + } + + m_edges.Add(edges); + bool clockwise; + TEdge EMin = null; + for (;;) + { + E = FindNextLocMin(E); + if (E == EMin) break; + else if (EMin == null) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + LocalMinima locMin = new LocalMinima(); + locMin.Next = null; + locMin.Y = E.Bot.Y; + if (E.Dx < E.Prev.Dx) + { + locMin.LeftBound = E.Prev; + locMin.RightBound = E; + clockwise = false; //Q.nextInLML = Q.prev + } else + { + locMin.LeftBound = E; + locMin.RightBound = E.Prev; + clockwise = true; //Q.nextInLML = Q.next + } + locMin.LeftBound.Side = EdgeSide.esLeft; + locMin.RightBound.Side = EdgeSide.esRight; + + if (!Closed) locMin.LeftBound.WindDelta = 0; + else if (locMin.LeftBound.Next == locMin.RightBound) + locMin.LeftBound.WindDelta = -1; + else locMin.LeftBound.WindDelta = 1; + locMin.RightBound.WindDelta = -locMin.LeftBound.WindDelta; + + E = ProcessBound(locMin.LeftBound, clockwise); + TEdge E2 = ProcessBound(locMin.RightBound, !clockwise); + + if (locMin.LeftBound.OutIdx == Skip) + locMin.LeftBound = null; + else if (locMin.RightBound.OutIdx == Skip) + locMin.RightBound = null; + InsertLocalMinima(locMin); + if (!clockwise) E = E2; + } + return true; + + } + //------------------------------------------------------------------------------ + + public bool AddPaths(Paths ppg, PolyType polyType, bool closed) + { + bool result = false; + for (int i = 0; i < ppg.Count; ++i) + if (AddPath(ppg[i], polyType, closed)) result = true; + return result; + } + //------------------------------------------------------------------------------ + + internal bool Pt2IsBetweenPt1AndPt3(IntPoint pt1, IntPoint pt2, IntPoint pt3) + { + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false; + else if (pt1.X != pt3.X) return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); + } + //------------------------------------------------------------------------------ + + TEdge RemoveEdge(TEdge e) + { + //removes e from double_linked_list (but without removing from memory) + e.Prev.Next = e.Next; + e.Next.Prev = e.Prev; + TEdge result = e.Next; + e.Prev = null; //flag as removed (see ClipperBase.Clear) + return result; + } + //------------------------------------------------------------------------------ + + private void SetDx(TEdge e) + { + e.Delta.X = (e.Top.X - e.Bot.X); + e.Delta.Y = (e.Top.Y - e.Bot.Y); + if (e.Delta.Y == 0) e.Dx = horizontal; + else e.Dx = (double)(e.Delta.X) / (e.Delta.Y); + } + //--------------------------------------------------------------------------- + + private void InsertLocalMinima(LocalMinima newLm) + { + if( m_MinimaList == null ) + { + m_MinimaList = newLm; + } + else if( newLm.Y >= m_MinimaList.Y ) + { + newLm.Next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima tmpLm = m_MinimaList; + while( tmpLm.Next != null && ( newLm.Y < tmpLm.Next.Y ) ) + tmpLm = tmpLm.Next; + newLm.Next = tmpLm.Next; + tmpLm.Next = newLm; + } + } + //------------------------------------------------------------------------------ + + protected void PopLocalMinima() + { + if (m_CurrentLM == null) return; + m_CurrentLM = m_CurrentLM.Next; + } + //------------------------------------------------------------------------------ + + private void ReverseHorizontal(TEdge e) + { + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + cInt tmp = e.Top.X; + e.Top.X = e.Bot.X; + e.Bot.X = tmp; +#if use_xyz + tmp = e.Top.Z; + e.Top.Z = e.Bot.Z; + e.Bot.Z = tmp; +#endif + } + //------------------------------------------------------------------------------ + + protected virtual void Reset() + { + m_CurrentLM = m_MinimaList; + if (m_CurrentLM == null) return; //ie nothing to process + + //reset all edges ... + LocalMinima lm = m_MinimaList; + while (lm != null) + { + TEdge e = lm.LeftBound; + if (e != null) + { + e.Curr = e.Bot; + e.Side = EdgeSide.esLeft; + e.OutIdx = Unassigned; + } + e = lm.RightBound; + if (e != null) + { + e.Curr = e.Bot; + e.Side = EdgeSide.esRight; + e.OutIdx = Unassigned; + } + lm = lm.Next; + } + } + //------------------------------------------------------------------------------ + + public static IntRect GetBounds(Paths paths) + { + int i = 0, cnt = paths.Count; + while (i < cnt && paths[i].Count == 0) i++; + if (i == cnt) return new IntRect(0,0,0,0); + IntRect result = new IntRect(); + result.left = paths[i][0].X; + result.right = result.left; + result.top = paths[i][0].Y; + result.bottom = result.top; + for (; i < cnt; i++) + for (int j = 0; j < paths[i].Count; j++) + { + if (paths[i][j].X < result.left) result.left = paths[i][j].X; + else if (paths[i][j].X > result.right) result.right = paths[i][j].X; + if (paths[i][j].Y < result.top) result.top = paths[i][j].Y; + else if (paths[i][j].Y > result.bottom) result.bottom = paths[i][j].Y; + } + return result; + } + + } //end ClipperBase + + public class Clipper : ClipperBase + { + //InitOptions that can be passed to the constructor ... + public const int ioReverseSolution = 1; + public const int ioStrictlySimple = 2; + public const int ioPreserveCollinear = 4; + + private List m_PolyOuts; + private ClipType m_ClipType; + private Scanbeam m_Scanbeam; + private TEdge m_ActiveEdges; + private TEdge m_SortedEdges; + private List m_IntersectList; + IComparer m_IntersectNodeComparer; + private bool m_ExecuteLocked; + private PolyFillType m_ClipFillType; + private PolyFillType m_SubjFillType; + private List m_Joins; + private List m_GhostJoins; + private bool m_UsingPolyTree; +#if use_xyz + public delegate void TZFillCallback(IntPoint vert1, IntPoint vert2, ref IntPoint intersectPt); + public TZFillCallback ZFillFunction { get; set; } +#endif + public Clipper(int InitOptions = 0): base() //constructor + { + m_Scanbeam = null; + m_ActiveEdges = null; + m_SortedEdges = null; + m_IntersectList = new List(); + m_IntersectNodeComparer = new MyIntersectNodeSort(); + m_ExecuteLocked = false; + m_UsingPolyTree = false; + m_PolyOuts = new List(); + m_Joins = new List(); + m_GhostJoins = new List(); + ReverseSolution = (ioReverseSolution & InitOptions) != 0; + StrictlySimple = (ioStrictlySimple & InitOptions) != 0; + PreserveCollinear = (ioPreserveCollinear & InitOptions) != 0; +#if use_xyz + ZFillFunction = null; +#endif + } + //------------------------------------------------------------------------------ + + public override void Clear() + { + if (m_edges.Count == 0) return; //avoids problems with ClipperBase destructor + DisposeAllPolyPts(); + base.Clear(); + } + //------------------------------------------------------------------------------ + + void DisposeScanbeamList() + { + while ( m_Scanbeam != null ) { + Scanbeam sb2 = m_Scanbeam.Next; + m_Scanbeam = null; + m_Scanbeam = sb2; + } + } + //------------------------------------------------------------------------------ + + protected override void Reset() + { + base.Reset(); + m_Scanbeam = null; + m_ActiveEdges = null; + m_SortedEdges = null; + DisposeAllPolyPts(); + LocalMinima lm = m_MinimaList; + while (lm != null) + { + InsertScanbeam(lm.Y); + lm = lm.Next; + } + } + //------------------------------------------------------------------------------ + + public bool ReverseSolution + { + get; + set; + } + //------------------------------------------------------------------------------ + + public bool StrictlySimple + { + get; + set; + } + //------------------------------------------------------------------------------ + + private void InsertScanbeam(cInt Y) + { + if( m_Scanbeam == null ) + { + m_Scanbeam = new Scanbeam(); + m_Scanbeam.Next = null; + m_Scanbeam.Y = Y; + } + else if( Y > m_Scanbeam.Y ) + { + Scanbeam newSb = new Scanbeam(); + newSb.Y = Y; + newSb.Next = m_Scanbeam; + m_Scanbeam = newSb; + } else + { + Scanbeam sb2 = m_Scanbeam; + while( sb2.Next != null && ( Y <= sb2.Next.Y ) ) sb2 = sb2.Next; + if( Y == sb2.Y ) return; //ie ignores duplicates + Scanbeam newSb = new Scanbeam(); + newSb.Y = Y; + newSb.Next = sb2.Next; + sb2.Next = newSb; + } + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, Paths solution, + PolyFillType subjFillType, PolyFillType clipFillType) + { + if (m_ExecuteLocked) return false; + if (m_HasOpenPaths) throw + new ClipperException("Error: PolyTree struct is need for open path clipping."); + + m_ExecuteLocked = true; + solution.Clear(); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + //build the return polygons ... + if (succeeded) BuildResult(solution); + m_ExecuteLocked = false; + return succeeded; + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, PolyTree polytree, + PolyFillType subjFillType, PolyFillType clipFillType) + { + if (m_ExecuteLocked) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + //build the return polygons ... + if (succeeded) BuildResult2(polytree); + m_ExecuteLocked = false; + return succeeded; + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, Paths solution) + { + return Execute(clipType, solution, + PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, PolyTree polytree) + { + return Execute(clipType, polytree, + PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); + } + //------------------------------------------------------------------------------ + + internal void FixHoleLinkage(OutRec outRec) + { + //skip if an outermost polygon or + //already already points to the correct FirstLeft ... + if (outRec.FirstLeft == null || + (outRec.IsHole != outRec.FirstLeft.IsHole && + outRec.FirstLeft.Pts != null)) return; + + OutRec orfl = outRec.FirstLeft; + while (orfl != null && ((orfl.IsHole == outRec.IsHole) || orfl.Pts == null)) + orfl = orfl.FirstLeft; + outRec.FirstLeft = orfl; + } + //------------------------------------------------------------------------------ + + private bool ExecuteInternal() + { + try + { + Reset(); + if (m_CurrentLM == null) return false; + + cInt botY = PopScanbeam(); + do + { + InsertLocalMinimaIntoAEL(botY); + m_GhostJoins.Clear(); + ProcessHorizontals(false); + if (m_Scanbeam == null) break; + cInt topY = PopScanbeam(); + if (!ProcessIntersections(botY, topY)) return false; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while (m_Scanbeam != null || m_CurrentLM != null); + + //fix orientations ... + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.Pts == null || outRec.IsOpen) continue; + if ((outRec.IsHole ^ ReverseSolution) == (Area(outRec) > 0)) + ReversePolyPtLinks(outRec.Pts); + } + + JoinCommonEdges(); + + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.Pts != null && !outRec.IsOpen) + FixupOutPolygon(outRec); + } + + if (StrictlySimple) DoSimplePolygons(); + return true; + } + //catch { return false; } + finally + { + m_Joins.Clear(); + m_GhostJoins.Clear(); + } + } + //------------------------------------------------------------------------------ + + private cInt PopScanbeam() + { + cInt Y = m_Scanbeam.Y; + Scanbeam sb2 = m_Scanbeam; + m_Scanbeam = m_Scanbeam.Next; + sb2 = null; + return Y; + } + //------------------------------------------------------------------------------ + + private void DisposeAllPolyPts(){ + for (int i = 0; i < m_PolyOuts.Count; ++i) DisposeOutRec(i); + m_PolyOuts.Clear(); + } + //------------------------------------------------------------------------------ + + void DisposeOutRec(int index) + { + OutRec outRec = m_PolyOuts[index]; + if (outRec.Pts != null) DisposeOutPts(outRec.Pts); + outRec = null; + m_PolyOuts[index] = null; + } + //------------------------------------------------------------------------------ + + private void DisposeOutPts(OutPt pp) + { + if (pp == null) return; + OutPt tmpPp = null; + pp.Prev.Next = null; + while (pp != null) + { + tmpPp = pp; + pp = pp.Next; + tmpPp = null; + } + } + //------------------------------------------------------------------------------ + + private void AddJoin(OutPt Op1, OutPt Op2, IntPoint OffPt) + { + Join j = new Join(); + j.OutPt1 = Op1; + j.OutPt2 = Op2; + j.OffPt = OffPt; + m_Joins.Add(j); + } + //------------------------------------------------------------------------------ + + private void AddGhostJoin(OutPt Op, IntPoint OffPt) + { + Join j = new Join(); + j.OutPt1 = Op; + j.OffPt = OffPt; + m_GhostJoins.Add(j); + } + //------------------------------------------------------------------------------ + +#if use_xyz + internal void SetZ(ref IntPoint pt, TEdge e) + { + pt.Z = 0; + if (ZFillFunction != null) + { + //put the 'preferred' point as first parameter ... + if (e.OutIdx < 0) + ZFillFunction(e.Bot, e.Top, ref pt); //outside a path so presume entering + else + ZFillFunction(e.Top, e.Bot, ref pt); //inside a path so presume exiting + } + } + //------------------------------------------------------------------------------ +#endif + + private void InsertLocalMinimaIntoAEL(cInt botY) + { + while( m_CurrentLM != null && ( m_CurrentLM.Y == botY ) ) + { + TEdge lb = m_CurrentLM.LeftBound; + TEdge rb = m_CurrentLM.RightBound; + PopLocalMinima(); + + OutPt Op1 = null; + if (lb == null) + { + InsertEdgeIntoAEL(rb, null); + SetWindingCount(rb); + if (IsContributing(rb)) + Op1 = AddOutPt(rb, rb.Bot); + } + else if (rb == null) + { + InsertEdgeIntoAEL(lb, null); + SetWindingCount(lb); + if (IsContributing(lb)) + Op1 = AddOutPt(lb, lb.Bot); + InsertScanbeam(lb.Top.Y); + } + else + { + InsertEdgeIntoAEL(lb, null); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount(lb); + rb.WindCnt = lb.WindCnt; + rb.WindCnt2 = lb.WindCnt2; + if (IsContributing(lb)) + Op1 = AddLocalMinPoly(lb, rb, lb.Bot); + InsertScanbeam(lb.Top.Y); + } + + if (rb != null) + { + if (IsHorizontal(rb)) + AddEdgeToSEL(rb); + else + InsertScanbeam(rb.Top.Y); + } + + if (lb == null || rb == null) continue; + + //if output polygons share an Edge with a horizontal rb, they'll need joining later ... + if (Op1 != null && IsHorizontal(rb) && + m_GhostJoins.Count > 0 && rb.WindDelta != 0) + { + for (int i = 0; i < m_GhostJoins.Count; i++) + { + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + Join j = m_GhostJoins[i]; + if (HorzSegmentsOverlap(j.OutPt1.Pt, j.OffPt, rb.Bot, rb.Top)) + AddJoin(j.OutPt1, Op1, j.OffPt); + } + } + + if (lb.OutIdx >= 0 && lb.PrevInAEL != null && + lb.PrevInAEL.Curr.X == lb.Bot.X && + lb.PrevInAEL.OutIdx >= 0 && + SlopesEqual(lb.PrevInAEL, lb, m_UseFullRange) && + lb.WindDelta != 0 && lb.PrevInAEL.WindDelta != 0) + { + OutPt Op2 = AddOutPt(lb.PrevInAEL, lb.Bot); + AddJoin(Op1, Op2, lb.Top); + } + + if( lb.NextInAEL != rb ) + { + + if (rb.OutIdx >= 0 && rb.PrevInAEL.OutIdx >= 0 && + SlopesEqual(rb.PrevInAEL, rb, m_UseFullRange) && + rb.WindDelta != 0 && rb.PrevInAEL.WindDelta != 0) + { + OutPt Op2 = AddOutPt(rb.PrevInAEL, rb.Bot); + AddJoin(Op1, Op2, rb.Top); + } + + TEdge e = lb.NextInAEL; + if (e != null) + while (e != rb) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges(rb, e, lb.Curr); //order important here + e = e.NextInAEL; + } + } + } + } + //------------------------------------------------------------------------------ + + private void InsertEdgeIntoAEL(TEdge edge, TEdge startEdge) + { + if (m_ActiveEdges == null) + { + edge.PrevInAEL = null; + edge.NextInAEL = null; + m_ActiveEdges = edge; + } + else if (startEdge == null && E2InsertsBeforeE1(m_ActiveEdges, edge)) + { + edge.PrevInAEL = null; + edge.NextInAEL = m_ActiveEdges; + m_ActiveEdges.PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if (startEdge == null) startEdge = m_ActiveEdges; + while (startEdge.NextInAEL != null && + !E2InsertsBeforeE1(startEdge.NextInAEL, edge)) + startEdge = startEdge.NextInAEL; + edge.NextInAEL = startEdge.NextInAEL; + if (startEdge.NextInAEL != null) startEdge.NextInAEL.PrevInAEL = edge; + edge.PrevInAEL = startEdge; + startEdge.NextInAEL = edge; + } + } + //---------------------------------------------------------------------- + + private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) + { + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; + } + //------------------------------------------------------------------------------ + + private bool IsEvenOddFillType(TEdge edge) + { + if (edge.PolyTyp == PolyType.ptSubject) + return m_SubjFillType == PolyFillType.pftEvenOdd; + else + return m_ClipFillType == PolyFillType.pftEvenOdd; + } + //------------------------------------------------------------------------------ + + private bool IsEvenOddAltFillType(TEdge edge) + { + if (edge.PolyTyp == PolyType.ptSubject) + return m_ClipFillType == PolyFillType.pftEvenOdd; + else + return m_SubjFillType == PolyFillType.pftEvenOdd; + } + //------------------------------------------------------------------------------ + + private bool IsContributing(TEdge edge) + { + PolyFillType pft, pft2; + if (edge.PolyTyp == PolyType.ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } + else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch (pft) + { + case PolyFillType.pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case PolyFillType.pftNonZero: + if (Math.Abs(edge.WindCnt) != 1) return false; + break; + case PolyFillType.pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //PolyFillType.pftNegative + if (edge.WindCnt != -1) return false; + break; + } + + switch (m_ClipType) + { + case ClipType.ctIntersection: + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 != 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + case ClipType.ctUnion: + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + case ClipType.ctDifference: + if (edge.PolyTyp == PolyType.ptSubject) + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 != 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + case ClipType.ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + } + return true; + } + //------------------------------------------------------------------------------ + + private void SetWindingCount(TEdge edge) + { + TEdge e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e != null && ((e.PolyTyp != edge.PolyTyp) || (e.WindDelta == 0))) e = e.PrevInAEL; + if (e == null) + { + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ClipType.ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e.WindCnt2; + e = e.NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge e2 = e.PrevInAEL; + while (e2 != null) + { + if (e2.PolyTyp == e.PolyTyp && e2.WindDelta != 0) + Inside = !Inside; + e2 = e2.PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e.WindCnt2; + e = e.NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e.WindCnt * e.WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Math.Abs(e.WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e.WindDelta * edge.WindDelta < 0) edge.WindCnt = e.WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e.WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } + else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e.WindCnt < 0 ? e.WindCnt - 1 : e.WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e.WindDelta * edge.WindDelta < 0) + edge.WindCnt = e.WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e.WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e.WindCnt2; + e = e.NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != edge) + { + if (e.WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e.NextInAEL; + } + } + else + { + //nonZero, Positive or Negative filling ... + while (e != edge) + { + edge.WindCnt2 += e.WindDelta; + e = e.NextInAEL; + } + } + } + //------------------------------------------------------------------------------ + + private void AddEdgeToSEL(TEdge edge) + { + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if (m_SortedEdges == null) + { + m_SortedEdges = edge; + edge.PrevInSEL = null; + edge.NextInSEL = null; + } + else + { + edge.NextInSEL = m_SortedEdges; + edge.PrevInSEL = null; + m_SortedEdges.PrevInSEL = edge; + m_SortedEdges = edge; + } + } + //------------------------------------------------------------------------------ + + private void CopyAELToSEL() + { + TEdge e = m_ActiveEdges; + m_SortedEdges = e; + while (e != null) + { + e.PrevInSEL = e.PrevInAEL; + e.NextInSEL = e.NextInAEL; + e = e.NextInAEL; + } + } + //------------------------------------------------------------------------------ + + private void SwapPositionsInAEL(TEdge edge1, TEdge edge2) + { + //check that one or other edge hasn't already been removed from AEL ... + if (edge1.NextInAEL == edge1.PrevInAEL || + edge2.NextInAEL == edge2.PrevInAEL) return; + + if (edge1.NextInAEL == edge2) + { + TEdge next = edge2.NextInAEL; + if (next != null) + next.PrevInAEL = edge1; + TEdge prev = edge1.PrevInAEL; + if (prev != null) + prev.NextInAEL = edge2; + edge2.PrevInAEL = prev; + edge2.NextInAEL = edge1; + edge1.PrevInAEL = edge2; + edge1.NextInAEL = next; + } + else if (edge2.NextInAEL == edge1) + { + TEdge next = edge1.NextInAEL; + if (next != null) + next.PrevInAEL = edge2; + TEdge prev = edge2.PrevInAEL; + if (prev != null) + prev.NextInAEL = edge1; + edge1.PrevInAEL = prev; + edge1.NextInAEL = edge2; + edge2.PrevInAEL = edge1; + edge2.NextInAEL = next; + } + else + { + TEdge next = edge1.NextInAEL; + TEdge prev = edge1.PrevInAEL; + edge1.NextInAEL = edge2.NextInAEL; + if (edge1.NextInAEL != null) + edge1.NextInAEL.PrevInAEL = edge1; + edge1.PrevInAEL = edge2.PrevInAEL; + if (edge1.PrevInAEL != null) + edge1.PrevInAEL.NextInAEL = edge1; + edge2.NextInAEL = next; + if (edge2.NextInAEL != null) + edge2.NextInAEL.PrevInAEL = edge2; + edge2.PrevInAEL = prev; + if (edge2.PrevInAEL != null) + edge2.PrevInAEL.NextInAEL = edge2; + } + + if (edge1.PrevInAEL == null) + m_ActiveEdges = edge1; + else if (edge2.PrevInAEL == null) + m_ActiveEdges = edge2; + } + //------------------------------------------------------------------------------ + + private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) + { + if (edge1.NextInSEL == null && edge1.PrevInSEL == null) + return; + if (edge2.NextInSEL == null && edge2.PrevInSEL == null) + return; + + if (edge1.NextInSEL == edge2) + { + TEdge next = edge2.NextInSEL; + if (next != null) + next.PrevInSEL = edge1; + TEdge prev = edge1.PrevInSEL; + if (prev != null) + prev.NextInSEL = edge2; + edge2.PrevInSEL = prev; + edge2.NextInSEL = edge1; + edge1.PrevInSEL = edge2; + edge1.NextInSEL = next; + } + else if (edge2.NextInSEL == edge1) + { + TEdge next = edge1.NextInSEL; + if (next != null) + next.PrevInSEL = edge2; + TEdge prev = edge2.PrevInSEL; + if (prev != null) + prev.NextInSEL = edge1; + edge1.PrevInSEL = prev; + edge1.NextInSEL = edge2; + edge2.PrevInSEL = edge1; + edge2.NextInSEL = next; + } + else + { + TEdge next = edge1.NextInSEL; + TEdge prev = edge1.PrevInSEL; + edge1.NextInSEL = edge2.NextInSEL; + if (edge1.NextInSEL != null) + edge1.NextInSEL.PrevInSEL = edge1; + edge1.PrevInSEL = edge2.PrevInSEL; + if (edge1.PrevInSEL != null) + edge1.PrevInSEL.NextInSEL = edge1; + edge2.NextInSEL = next; + if (edge2.NextInSEL != null) + edge2.NextInSEL.PrevInSEL = edge2; + edge2.PrevInSEL = prev; + if (edge2.PrevInSEL != null) + edge2.PrevInSEL.NextInSEL = edge2; + } + + if (edge1.PrevInSEL == null) + m_SortedEdges = edge1; + else if (edge2.PrevInSEL == null) + m_SortedEdges = edge2; + } + //------------------------------------------------------------------------------ + + + private void AddLocalMaxPoly(TEdge e1, TEdge e2, IntPoint pt) + { + AddOutPt(e1, pt); + if (e2.WindDelta == 0) AddOutPt(e2, pt); + if (e1.OutIdx == e2.OutIdx) + { + e1.OutIdx = Unassigned; + e2.OutIdx = Unassigned; + } + else if (e1.OutIdx < e2.OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); + } + //------------------------------------------------------------------------------ + + private OutPt AddLocalMinPoly(TEdge e1, TEdge e2, IntPoint pt) + { + OutPt result; + TEdge e, prevE; + if (IsHorizontal(e2) || (e1.Dx > e2.Dx)) + { + result = AddOutPt(e1, pt); + e2.OutIdx = e1.OutIdx; + e1.Side = EdgeSide.esLeft; + e2.Side = EdgeSide.esRight; + e = e1; + if (e.PrevInAEL == e2) + prevE = e2.PrevInAEL; + else + prevE = e.PrevInAEL; + } + else + { + result = AddOutPt(e2, pt); + e1.OutIdx = e2.OutIdx; + e1.Side = EdgeSide.esRight; + e2.Side = EdgeSide.esLeft; + e = e2; + if (e.PrevInAEL == e1) + prevE = e1.PrevInAEL; + else + prevE = e.PrevInAEL; + } + + if (prevE != null && prevE.OutIdx >= 0 && + (TopX(prevE, pt.Y) == TopX(e, pt.Y)) && + SlopesEqual(e, prevE, m_UseFullRange) && + (e.WindDelta != 0) && (prevE.WindDelta != 0)) + { + OutPt outPt = AddOutPt(prevE, pt); + AddJoin(result, outPt, e.Top); + } + return result; + } + //------------------------------------------------------------------------------ + + private OutRec CreateOutRec() + { + OutRec result = new OutRec(); + result.Idx = Unassigned; + result.IsHole = false; + result.IsOpen = false; + result.FirstLeft = null; + result.Pts = null; + result.BottomPt = null; + result.PolyNode = null; + m_PolyOuts.Add(result); + result.Idx = m_PolyOuts.Count - 1; + return result; + } + //------------------------------------------------------------------------------ + + private OutPt AddOutPt(TEdge e, IntPoint pt) + { + bool ToFront = (e.Side == EdgeSide.esLeft); + if( e.OutIdx < 0 ) + { + OutRec outRec = CreateOutRec(); + outRec.IsOpen = (e.WindDelta == 0); + OutPt newOp = new OutPt(); + outRec.Pts = newOp; + newOp.Idx = outRec.Idx; + newOp.Pt = pt; + newOp.Next = newOp; + newOp.Prev = newOp; + if (!outRec.IsOpen) + SetHoleState(e, outRec); +#if use_xyz + if (pt == e.Bot) + newOp.Pt = e.Bot; + else if (pt == e.Top) + newOp.Pt = e.Top; + else + SetZ(ref newOp.Pt, e); +#endif + e.OutIdx = outRec.Idx; //nb: do this after SetZ ! + return newOp; + } else + { + OutRec outRec = m_PolyOuts[e.OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt op = outRec.Pts; + if (ToFront && pt == op.Pt) return op; + else if (!ToFront && pt == op.Prev.Pt) return op.Prev; + + OutPt newOp = new OutPt(); + newOp.Idx = outRec.Idx; + newOp.Pt = pt; + newOp.Next = op; + newOp.Prev = op.Prev; + newOp.Prev.Next = newOp; + op.Prev = newOp; + if (ToFront) outRec.Pts = newOp; +#if use_xyz + if (pt == e.Bot) + newOp.Pt = e.Bot; + else if (pt == e.Top) + newOp.Pt = e.Top; + else + SetZ(ref newOp.Pt, e); +#endif + return newOp; + } + } + //------------------------------------------------------------------------------ + + internal void SwapPoints(ref IntPoint pt1, ref IntPoint pt2) + { + IntPoint tmp = new IntPoint(pt1); + pt1 = pt2; + pt2 = tmp; + } + //------------------------------------------------------------------------------ + + private bool HorzSegmentsOverlap( + IntPoint Pt1a, IntPoint Pt1b, IntPoint Pt2a, IntPoint Pt2b) + { + //precondition: both segments are horizontal + if ((Pt1a.X > Pt2a.X) == (Pt1a.X < Pt2b.X)) return true; + else if ((Pt1b.X > Pt2a.X) == (Pt1b.X < Pt2b.X)) return true; + else if ((Pt2a.X > Pt1a.X) == (Pt2a.X < Pt1b.X)) return true; + else if ((Pt2b.X > Pt1a.X) == (Pt2b.X < Pt1b.X)) return true; + else if ((Pt1a.X == Pt2a.X) && (Pt1b.X == Pt2b.X)) return true; + else if ((Pt1a.X == Pt2b.X) && (Pt1b.X == Pt2a.X)) return true; + else return false; + } + //------------------------------------------------------------------------------ + + private OutPt InsertPolyPtBetween(OutPt p1, OutPt p2, IntPoint pt) + { + OutPt result = new OutPt(); + result.Pt = pt; + if (p2 == p1.Next) + { + p1.Next = result; + p2.Prev = result; + result.Next = p2; + result.Prev = p1; + } else + { + p2.Next = result; + p1.Prev = result; + result.Next = p1; + result.Prev = p2; + } + return result; + } + //------------------------------------------------------------------------------ + + private void SetHoleState(TEdge e, OutRec outRec) + { + bool isHole = false; + TEdge e2 = e.PrevInAEL; + while (e2 != null) + { + if (e2.OutIdx >= 0 && e2.WindDelta != 0) + { + isHole = !isHole; + if (outRec.FirstLeft == null) + outRec.FirstLeft = m_PolyOuts[e2.OutIdx]; + } + e2 = e2.PrevInAEL; + } + if (isHole) + outRec.IsHole = true; + } + //------------------------------------------------------------------------------ + + private double GetDx(IntPoint pt1, IntPoint pt2) + { + if (pt1.Y == pt2.Y) return horizontal; + else return (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); + } + //--------------------------------------------------------------------------- + + private bool FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2) + { + OutPt p = btmPt1.Prev; + while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) p = p.Prev; + double dx1p = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); + p = btmPt1.Next; + while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) p = p.Next; + double dx1n = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); + + p = btmPt2.Prev; + while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) p = p.Prev; + double dx2p = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); + p = btmPt2.Next; + while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) p = p.Next; + double dx2n = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); + } + //------------------------------------------------------------------------------ + + private OutPt GetBottomPt(OutPt pp) + { + OutPt dups = null; + OutPt p = pp.Next; + while (p != pp) + { + if (p.Pt.Y > pp.Pt.Y) + { + pp = p; + dups = null; + } + else if (p.Pt.Y == pp.Pt.Y && p.Pt.X <= pp.Pt.X) + { + if (p.Pt.X < pp.Pt.X) + { + dups = null; + pp = p; + } else + { + if (p.Next != pp && p.Prev != pp) dups = p; + } + } + p = p.Next; + } + if (dups != null) + { + //there appears to be at least 2 vertices at bottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups.Next; + while (dups.Pt != pp.Pt) dups = dups.Next; + } + } + return pp; + } + //------------------------------------------------------------------------------ + + private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) + { + //work out which polygon fragment has the correct hole state ... + if (outRec1.BottomPt == null) + outRec1.BottomPt = GetBottomPt(outRec1.Pts); + if (outRec2.BottomPt == null) + outRec2.BottomPt = GetBottomPt(outRec2.Pts); + OutPt bPt1 = outRec1.BottomPt; + OutPt bPt2 = outRec2.BottomPt; + if (bPt1.Pt.Y > bPt2.Pt.Y) return outRec1; + else if (bPt1.Pt.Y < bPt2.Pt.Y) return outRec2; + else if (bPt1.Pt.X < bPt2.Pt.X) return outRec1; + else if (bPt1.Pt.X > bPt2.Pt.X) return outRec2; + else if (bPt1.Next == bPt1) return outRec2; + else if (bPt2.Next == bPt2) return outRec1; + else if (FirstIsBottomPt(bPt1, bPt2)) return outRec1; + else return outRec2; + } + //------------------------------------------------------------------------------ + + bool Param1RightOfParam2(OutRec outRec1, OutRec outRec2) + { + do + { + outRec1 = outRec1.FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1 != null); + return false; + } + //------------------------------------------------------------------------------ + + private OutRec GetOutRec(int idx) + { + OutRec outrec = m_PolyOuts[idx]; + while (outrec != m_PolyOuts[outrec.Idx]) + outrec = m_PolyOuts[outrec.Idx]; + return outrec; + } + //------------------------------------------------------------------------------ + + private void AppendPolygon(TEdge e1, TEdge e2) + { + //get the start and ends of both output polygons ... + OutRec outRec1 = m_PolyOuts[e1.OutIdx]; + OutRec outRec2 = m_PolyOuts[e2.OutIdx]; + + OutRec holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt p1_lft = outRec1.Pts; + OutPt p1_rt = p1_lft.Prev; + OutPt p2_lft = outRec2.Pts; + OutPt p2_rt = p2_lft.Prev; + + EdgeSide side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1.Side == EdgeSide.esLeft ) + { + if (e2.Side == EdgeSide.esLeft) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft.Next = p1_lft; + p1_lft.Prev = p2_lft; + p1_rt.Next = p2_rt; + p2_rt.Prev = p1_rt; + outRec1.Pts = p2_rt; + } else + { + //x y z a b c + p2_rt.Next = p1_lft; + p1_lft.Prev = p2_rt; + p2_lft.Prev = p1_rt; + p1_rt.Next = p2_lft; + outRec1.Pts = p2_lft; + } + side = EdgeSide.esLeft; + } else + { + if (e2.Side == EdgeSide.esRight) + { + //a b c z y x + ReversePolyPtLinks( p2_lft ); + p1_rt.Next = p2_rt; + p2_rt.Prev = p1_rt; + p2_lft.Next = p1_lft; + p1_lft.Prev = p2_lft; + } else + { + //a b c x y z + p1_rt.Next = p2_lft; + p2_lft.Prev = p1_rt; + p1_lft.Prev = p2_rt; + p2_rt.Next = p1_lft; + } + side = EdgeSide.esRight; + } + + outRec1.BottomPt = null; + if (holeStateRec == outRec2) + { + if (outRec2.FirstLeft != outRec1) + outRec1.FirstLeft = outRec2.FirstLeft; + outRec1.IsHole = outRec2.IsHole; + } + outRec2.Pts = null; + outRec2.BottomPt = null; + + outRec2.FirstLeft = outRec1; + + int OKIdx = e1.OutIdx; + int ObsoleteIdx = e2.OutIdx; + + e1.OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2.OutIdx = Unassigned; + + TEdge e = m_ActiveEdges; + while( e != null ) + { + if( e.OutIdx == ObsoleteIdx ) + { + e.OutIdx = OKIdx; + e.Side = side; + break; + } + e = e.NextInAEL; + } + outRec2.Idx = outRec1.Idx; + } + //------------------------------------------------------------------------------ + + private void ReversePolyPtLinks(OutPt pp) + { + if (pp == null) return; + OutPt pp1; + OutPt pp2; + pp1 = pp; + do + { + pp2 = pp1.Next; + pp1.Next = pp1.Prev; + pp1.Prev = pp2; + pp1 = pp2; + } while (pp1 != pp); + } + //------------------------------------------------------------------------------ + + private static void SwapSides(TEdge edge1, TEdge edge2) + { + EdgeSide side = edge1.Side; + edge1.Side = edge2.Side; + edge2.Side = side; + } + //------------------------------------------------------------------------------ + + private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) + { + int outIdx = edge1.OutIdx; + edge1.OutIdx = edge2.OutIdx; + edge2.OutIdx = outIdx; + } + //------------------------------------------------------------------------------ + + private void IntersectEdges(TEdge e1, TEdge e2, IntPoint pt, bool protect = false) + { + //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + + bool e1stops = !protect && e1.NextInLML == null && + e1.Top.X == pt.X && e1.Top.Y == pt.Y; + bool e2stops = !protect && e2.NextInLML == null && + e2.Top.X == pt.X && e2.Top.Y == pt.Y; + bool e1Contributing = (e1.OutIdx >= 0); + bool e2Contributing = (e2.OutIdx >= 0); + +#if use_lines + //if either edge is on an OPEN path ... + if (e1.WindDelta == 0 || e2.WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1.WindDelta == 0 && e2.WindDelta == 0) + { + if ((e1stops || e2stops) && e1Contributing && e2Contributing) + AddLocalMaxPoly(e1, e2, pt); + } + //if intersecting a subj line with a subj poly ... + else if (e1.PolyTyp == e2.PolyTyp && + e1.WindDelta != e2.WindDelta && m_ClipType == ClipType.ctUnion) + { + if (e1.WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, pt); + if (e1Contributing) e1.OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, pt); + if (e2Contributing) e2.OutIdx = Unassigned; + } + } + } + else if (e1.PolyTyp != e2.PolyTyp) + { + if ((e1.WindDelta == 0) && Math.Abs(e2.WindCnt) == 1 && + (m_ClipType != ClipType.ctUnion || e2.WindCnt2 == 0)) + { + AddOutPt(e1, pt); + if (e1Contributing) e1.OutIdx = Unassigned; + } + else if ((e2.WindDelta == 0) && (Math.Abs(e1.WindCnt) == 1) && + (m_ClipType != ClipType.ctUnion || e1.WindCnt2 == 0)) + { + AddOutPt(e2, pt); + if (e2Contributing) e2.OutIdx = Unassigned; + } + } + + if (e1stops) + if (e1.OutIdx < 0) DeleteFromAEL(e1); + else throw new ClipperException("Error intersecting polylines"); + if (e2stops) + if (e2.OutIdx < 0) DeleteFromAEL(e2); + else throw new ClipperException("Error intersecting polylines"); + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if (e1.PolyTyp == e2.PolyTyp) + { + if (IsEvenOddFillType(e1)) + { + int oldE1WindCnt = e1.WindCnt; + e1.WindCnt = e2.WindCnt; + e2.WindCnt = oldE1WindCnt; + } + else + { + if (e1.WindCnt + e2.WindDelta == 0) e1.WindCnt = -e1.WindCnt; + else e1.WindCnt += e2.WindDelta; + if (e2.WindCnt - e1.WindDelta == 0) e2.WindCnt = -e2.WindCnt; + else e2.WindCnt -= e1.WindDelta; + } + } + else + { + if (!IsEvenOddFillType(e2)) e1.WindCnt2 += e2.WindDelta; + else e1.WindCnt2 = (e1.WindCnt2 == 0) ? 1 : 0; + if (!IsEvenOddFillType(e1)) e2.WindCnt2 -= e1.WindDelta; + else e2.WindCnt2 = (e2.WindCnt2 == 0) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1.PolyTyp == PolyType.ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } + else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2.PolyTyp == PolyType.ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } + else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + int e1Wc, e2Wc; + switch (e1FillType) + { + case PolyFillType.pftPositive: e1Wc = e1.WindCnt; break; + case PolyFillType.pftNegative: e1Wc = -e1.WindCnt; break; + default: e1Wc = Math.Abs(e1.WindCnt); break; + } + switch (e2FillType) + { + case PolyFillType.pftPositive: e2Wc = e2.WindCnt; break; + case PolyFillType.pftNegative: e2Wc = -e2.WindCnt; break; + default: e2Wc = Math.Abs(e2.WindCnt); break; + } + + if (e1Contributing && e2Contributing) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1.PolyTyp != e2.PolyTyp && m_ClipType != ClipType.ctXor)) + AddLocalMaxPoly(e1, e2, pt); + else + { + AddOutPt(e1, pt); + AddOutPt(e2, pt); + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + } + else if (e1Contributing) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, pt); + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + + } + else if (e2Contributing) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, pt); + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case PolyFillType.pftPositive: e1Wc2 = e1.WindCnt2; break; + case PolyFillType.pftNegative: e1Wc2 = -e1.WindCnt2; break; + default: e1Wc2 = Math.Abs(e1.WindCnt2); break; + } + switch (e2FillType2) + { + case PolyFillType.pftPositive: e2Wc2 = e2.WindCnt2; break; + case PolyFillType.pftNegative: e2Wc2 = -e2.WindCnt2; break; + default: e2Wc2 = Math.Abs(e2.WindCnt2); break; + } + + if (e1.PolyTyp != e2.PolyTyp) + AddLocalMinPoly(e1, e2, pt); + else if (e1Wc == 1 && e2Wc == 1) + switch (m_ClipType) + { + case ClipType.ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ClipType.ctUnion: + if (e1Wc2 <= 0 && e2Wc2 <= 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ClipType.ctDifference: + if (((e1.PolyTyp == PolyType.ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1.PolyTyp == PolyType.ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, pt); + break; + case ClipType.ctXor: + AddLocalMinPoly(e1, e2, pt); + break; + } + else + SwapSides(e1, e2); + } + + if ((e1stops != e2stops) && + ((e1stops && (e1.OutIdx >= 0)) || (e2stops && (e2.OutIdx >= 0)))) + { + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + + //finally, delete any non-contributing maxima edges ... + if (e1stops) DeleteFromAEL(e1); + if (e2stops) DeleteFromAEL(e2); + } + //------------------------------------------------------------------------------ + + private void DeleteFromAEL(TEdge e) + { + TEdge AelPrev = e.PrevInAEL; + TEdge AelNext = e.NextInAEL; + if (AelPrev == null && AelNext == null && (e != m_ActiveEdges)) + return; //already deleted + if (AelPrev != null) + AelPrev.NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if (AelNext != null) + AelNext.PrevInAEL = AelPrev; + e.NextInAEL = null; + e.PrevInAEL = null; + } + //------------------------------------------------------------------------------ + + private void DeleteFromSEL(TEdge e) + { + TEdge SelPrev = e.PrevInSEL; + TEdge SelNext = e.NextInSEL; + if (SelPrev == null && SelNext == null && (e != m_SortedEdges)) + return; //already deleted + if (SelPrev != null) + SelPrev.NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if (SelNext != null) + SelNext.PrevInSEL = SelPrev; + e.NextInSEL = null; + e.PrevInSEL = null; + } + //------------------------------------------------------------------------------ + + private void UpdateEdgeIntoAEL(ref TEdge e) + { + if (e.NextInLML == null) + throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); + TEdge AelPrev = e.PrevInAEL; + TEdge AelNext = e.NextInAEL; + e.NextInLML.OutIdx = e.OutIdx; + if (AelPrev != null) + AelPrev.NextInAEL = e.NextInLML; + else m_ActiveEdges = e.NextInLML; + if (AelNext != null) + AelNext.PrevInAEL = e.NextInLML; + e.NextInLML.Side = e.Side; + e.NextInLML.WindDelta = e.WindDelta; + e.NextInLML.WindCnt = e.WindCnt; + e.NextInLML.WindCnt2 = e.WindCnt2; + e = e.NextInLML; + e.Curr = e.Bot; + e.PrevInAEL = AelPrev; + e.NextInAEL = AelNext; + if (!IsHorizontal(e)) InsertScanbeam(e.Top.Y); + } + //------------------------------------------------------------------------------ + + private void ProcessHorizontals(bool isTopOfScanbeam) + { + TEdge horzEdge = m_SortedEdges; + while (horzEdge != null) + { + DeleteFromSEL(horzEdge); + ProcessHorizontal(horzEdge, isTopOfScanbeam); + horzEdge = m_SortedEdges; + } + } + //------------------------------------------------------------------------------ + + void GetHorzDirection(TEdge HorzEdge, out Direction Dir, out cInt Left, out cInt Right) + { + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = Direction.dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = Direction.dRightToLeft; + } + } + //------------------------------------------------------------------------ + + void PrepareHorzJoins(TEdge horzEdge, bool isTopOfScanbeam) + { + //get the last Op for this horizontal edge + //the point may be anywhere along the horizontal ... + OutPt outPt = m_PolyOuts[horzEdge.OutIdx].Pts; + if (horzEdge.Side != EdgeSide.esLeft) outPt = outPt.Prev; + + //First, match up overlapping horizontal edges (eg when one polygon's + //intermediate horz edge overlaps an intermediate horz edge of another, or + //when one polygon sits on top of another) ... + for (int i = 0; i < m_GhostJoins.Count; ++i) + { + Join j = m_GhostJoins[i]; + if (HorzSegmentsOverlap(j.OutPt1.Pt, j.OffPt, horzEdge.Bot, horzEdge.Top)) + AddJoin(j.OutPt1, outPt, j.OffPt); + } + //Also, since horizontal edges at the top of one SB are often removed from + //the AEL before we process the horizontal edges at the bottom of the next, + //we need to create 'ghost' Join records of 'contrubuting' horizontals that + //we can compare with horizontals at the bottom of the next SB. + if (isTopOfScanbeam) + if (outPt.Pt == horzEdge.Top) + AddGhostJoin(outPt, horzEdge.Bot); + else + AddGhostJoin(outPt, horzEdge.Top); + } + //------------------------------------------------------------------------------ + + private void ProcessHorizontal(TEdge horzEdge, bool isTopOfScanbeam) + { + Direction dir; + cInt horzLeft, horzRight; + + GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); + + TEdge eLastHorz = horzEdge, eMaxPair = null; + while (eLastHorz.NextInLML != null && IsHorizontal(eLastHorz.NextInLML)) + eLastHorz = eLastHorz.NextInLML; + if (eLastHorz.NextInLML == null) + eMaxPair = GetMaximaPair(eLastHorz); + + for (;;) + { + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge e = GetNextInAEL(horzEdge, dir); + while(e != null) + { + //Break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e.Curr.X == horzEdge.Top.X && horzEdge.NextInLML != null && + e.Dx < horzEdge.NextInLML.Dx) break; + + TEdge eNext = GetNextInAEL(e, dir); //saves eNext for later + + if ((dir == Direction.dLeftToRight && e.Curr.X <= horzRight) || + (dir == Direction.dRightToLeft && e.Curr.X >= horzLeft)) + { + if (horzEdge.OutIdx >= 0 && horzEdge.WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + //so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + if (dir == Direction.dLeftToRight) + IntersectEdges(horzEdge, e, e.Top); + else + IntersectEdges(e, horzEdge, e.Top); + if (eMaxPair.OutIdx >= 0) throw + new ClipperException("ProcessHorizontal error"); + return; + } + else if(dir == Direction.dLeftToRight) + { + IntPoint Pt = new IntPoint(e.Curr.X, horzEdge.Curr.Y); + IntersectEdges(horzEdge, e, Pt, true); + } + else + { + IntPoint Pt = new IntPoint(e.Curr.X, horzEdge.Curr.Y); + IntersectEdges(e, horzEdge, Pt, true); + } + SwapPositionsInAEL(horzEdge, e); + } + else if ((dir == Direction.dLeftToRight && e.Curr.X >= horzRight) || + (dir == Direction.dRightToLeft && e.Curr.X <= horzLeft)) break; + e = eNext; + } //end while + + if (horzEdge.OutIdx >= 0 && horzEdge.WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + + if (horzEdge.NextInLML != null && IsHorizontal(horzEdge.NextInLML)) + { + UpdateEdgeIntoAEL(ref horzEdge); + if (horzEdge.OutIdx >= 0) AddOutPt(horzEdge, horzEdge.Bot); + GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); + } else + break; + } //end for (;;) + + if(horzEdge.NextInLML != null) + { + if(horzEdge.OutIdx >= 0) + { + OutPt op1 = AddOutPt( horzEdge, horzEdge.Top); + UpdateEdgeIntoAEL(ref horzEdge); + if (horzEdge.WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge ePrev = horzEdge.PrevInAEL; + TEdge eNext = horzEdge.NextInAEL; + if (ePrev != null && ePrev.Curr.X == horzEdge.Bot.X && + ePrev.Curr.Y == horzEdge.Bot.Y && ePrev.WindDelta != 0 && + (ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && + SlopesEqual(horzEdge, ePrev, m_UseFullRange))) + { + OutPt op2 = AddOutPt(ePrev, horzEdge.Bot); + AddJoin(op1, op2, horzEdge.Top); + } + else if (eNext != null && eNext.Curr.X == horzEdge.Bot.X && + eNext.Curr.Y == horzEdge.Bot.Y && eNext.WindDelta != 0 && + eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && + SlopesEqual(horzEdge, eNext, m_UseFullRange)) + { + OutPt op2 = AddOutPt(eNext, horzEdge.Bot); + AddJoin(op1, op2, horzEdge.Top); + } + } + else + UpdateEdgeIntoAEL(ref horzEdge); + } + else if (eMaxPair != null) + { + if (eMaxPair.OutIdx >= 0) + { + if (dir == Direction.dLeftToRight) + IntersectEdges(horzEdge, eMaxPair, horzEdge.Top); + else + IntersectEdges(eMaxPair, horzEdge, horzEdge.Top); + if (eMaxPair.OutIdx >= 0) throw + new ClipperException("ProcessHorizontal error"); + } else + { + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + } + } else + { + if (horzEdge.OutIdx >= 0) AddOutPt(horzEdge, horzEdge.Top); + DeleteFromAEL(horzEdge); + } + } + //------------------------------------------------------------------------------ + + private TEdge GetNextInAEL(TEdge e, Direction Direction) + { + return Direction == Direction.dLeftToRight ? e.NextInAEL: e.PrevInAEL; + } + //------------------------------------------------------------------------------ + + private bool IsMinima(TEdge e) + { + return e != null && (e.Prev.NextInLML != e) && (e.Next.NextInLML != e); + } + //------------------------------------------------------------------------------ + + private bool IsMaxima(TEdge e, double Y) + { + return (e != null && e.Top.Y == Y && e.NextInLML == null); + } + //------------------------------------------------------------------------------ + + private bool IsIntermediate(TEdge e, double Y) + { + return (e.Top.Y == Y && e.NextInLML != null); + } + //------------------------------------------------------------------------------ + + private TEdge GetMaximaPair(TEdge e) + { + TEdge result = null; + if ((e.Next.Top == e.Top) && e.Next.NextInLML == null) + result = e.Next; + else if ((e.Prev.Top == e.Top) && e.Prev.NextInLML == null) + result = e.Prev; + if (result != null && (result.OutIdx == Skip || + (result.NextInAEL == result.PrevInAEL && !IsHorizontal(result)))) + return null; + return result; + } + //------------------------------------------------------------------------------ + + private bool ProcessIntersections(cInt botY, cInt topY) + { + if( m_ActiveEdges == null ) return true; + try { + BuildIntersectList(botY, topY); + if ( m_IntersectList.Count == 0) return true; + if (m_IntersectList.Count == 1 || FixupIntersectionOrder()) + ProcessIntersectList(); + else + return false; + } + catch { + m_SortedEdges = null; + m_IntersectList.Clear(); + throw new ClipperException("ProcessIntersections error"); + } + m_SortedEdges = null; + return true; + } + //------------------------------------------------------------------------------ + + private void BuildIntersectList(cInt botY, cInt topY) + { + if ( m_ActiveEdges == null ) return; + + //prepare for sorting ... + TEdge e = m_ActiveEdges; + m_SortedEdges = e; + while( e != null ) + { + e.PrevInSEL = e.PrevInAEL; + e.NextInSEL = e.NextInAEL; + e.Curr.X = TopX( e, topY ); + e = e.NextInAEL; + } + + //bubblesort ... + bool isModified = true; + while( isModified && m_SortedEdges != null ) + { + isModified = false; + e = m_SortedEdges; + while( e.NextInSEL != null ) + { + TEdge eNext = e.NextInSEL; + IntPoint pt; + if (e.Curr.X > eNext.Curr.X) + { + if (!IntersectPoint(e, eNext, out pt) && e.Curr.X > eNext.Curr.X +1) + throw new ClipperException("Intersection error"); + if (pt.Y > botY) + { + pt.Y = botY; + if (Math.Abs(e.Dx) > Math.Abs(eNext.Dx)) + pt.X = TopX(eNext, botY); else + pt.X = TopX(e, botY); + } + + IntersectNode newNode = new IntersectNode(); + newNode.Edge1 = e; + newNode.Edge2 = eNext; + newNode.Pt = pt; + m_IntersectList.Add(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e.PrevInSEL != null ) e.PrevInSEL.NextInSEL = null; + else break; + } + m_SortedEdges = null; + } + //------------------------------------------------------------------------------ + + private bool EdgesAdjacent(IntersectNode inode) + { + return (inode.Edge1.NextInSEL == inode.Edge2) || + (inode.Edge1.PrevInSEL == inode.Edge2); + } + //------------------------------------------------------------------------------ + + private static int IntersectNodeSort(IntersectNode node1, IntersectNode node2) + { + //the following typecast is safe because the differences in Pt.Y will + //be limited to the height of the scanbeam. + return (int)(node2.Pt.Y - node1.Pt.Y); + } + //------------------------------------------------------------------------------ + + private bool FixupIntersectionOrder() + { + //pre-condition: intersections are sorted bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + m_IntersectList.Sort(m_IntersectNodeComparer); + + CopyAELToSEL(); + int cnt = m_IntersectList.Count; + for (int i = 0; i < cnt; i++) + { + if (!EdgesAdjacent(m_IntersectList[i])) + { + int j = i + 1; + while (j < cnt && !EdgesAdjacent(m_IntersectList[j])) j++; + if (j == cnt) return false; + + IntersectNode tmp = m_IntersectList[i]; + m_IntersectList[i] = m_IntersectList[j]; + m_IntersectList[j] = tmp; + + } + SwapPositionsInSEL(m_IntersectList[i].Edge1, m_IntersectList[i].Edge2); + } + return true; + } + //------------------------------------------------------------------------------ + + private void ProcessIntersectList() + { + for (int i = 0; i < m_IntersectList.Count; i++) + { + IntersectNode iNode = m_IntersectList[i]; + { + IntersectEdges(iNode.Edge1, iNode.Edge2, iNode.Pt, true); + SwapPositionsInAEL(iNode.Edge1, iNode.Edge2); + } + } + m_IntersectList.Clear(); + } + //------------------------------------------------------------------------------ + + internal static cInt Round(double value) + { + return value < 0 ? (cInt)(value - 0.5) : (cInt)(value + 0.5); + } + //------------------------------------------------------------------------------ + + private static cInt TopX(TEdge edge, cInt currentY) + { + if (currentY == edge.Top.Y) + return edge.Top.X; + return edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); + } + //------------------------------------------------------------------------------ + + private bool IntersectPoint(TEdge edge1, TEdge edge2, out IntPoint ip) + { + ip = new IntPoint(); + double b1, b2; + //nb: with very large coordinate values, it's possible for SlopesEqual() to + //return false but for the edge.Dx value be equal due to double precision rounding. + if (SlopesEqual(edge1, edge2, m_UseFullRange) || edge1.Dx == edge2.Dx) + { + if (edge2.Bot.Y > edge1.Bot.Y) + ip = edge2.Bot; + else + ip = edge1.Bot; + return false; + } + else if (edge1.Delta.X == 0) + { + ip.X = edge1.Bot.X; + if (IsHorizontal(edge2)) + { + ip.Y = edge2.Bot.Y; + } + else + { + b2 = edge2.Bot.Y - (edge2.Bot.X / edge2.Dx); + ip.Y = Round(ip.X / edge2.Dx + b2); + } + } + else if (edge2.Delta.X == 0) + { + ip.X = edge2.Bot.X; + if (IsHorizontal(edge1)) + { + ip.Y = edge1.Bot.Y; + } + else + { + b1 = edge1.Bot.Y - (edge1.Bot.X / edge1.Dx); + ip.Y = Round(ip.X / edge1.Dx + b1); + } + } + else + { + b1 = edge1.Bot.X - edge1.Bot.Y * edge1.Dx; + b2 = edge2.Bot.X - edge2.Bot.Y * edge2.Dx; + double q = (b2 - b1) / (edge1.Dx - edge2.Dx); + ip.Y = Round(q); + if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) + ip.X = Round(edge1.Dx * q + b1); + else + ip.X = Round(edge2.Dx * q + b2); + } + + if (ip.Y < edge1.Top.Y || ip.Y < edge2.Top.Y) + { + if (edge1.Top.Y > edge2.Top.Y) + ip.Y = edge1.Top.Y; + else + ip.Y = edge2.Top.Y; + if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) + ip.X = TopX(edge1, ip.Y); + else + ip.X = TopX(edge2, ip.Y); + } + return true; + } + //------------------------------------------------------------------------------ + + private void ProcessEdgesAtTopOfScanbeam(cInt topY) + { + TEdge e = m_ActiveEdges; + while(e != null) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge eMaxPair = GetMaximaPair(e); + IsMaximaEdge = (eMaxPair == null || !IsHorizontal(eMaxPair)); + } + + if(IsMaximaEdge) + { + TEdge ePrev = e.PrevInAEL; + DoMaxima(e); + if( ePrev == null) e = m_ActiveEdges; + else e = ePrev.NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(e.NextInLML)) + { + UpdateEdgeIntoAEL(ref e); + if (e.OutIdx >= 0) + AddOutPt(e, e.Bot); + AddEdgeToSEL(e); + } + else + { + e.Curr.X = TopX( e, topY ); + e.Curr.Y = topY; + } + + if (StrictlySimple) + { + TEdge ePrev = e.PrevInAEL; + if ((e.OutIdx >= 0) && (e.WindDelta != 0) && ePrev != null && + (ePrev.OutIdx >= 0) && (ePrev.Curr.X == e.Curr.X) && + (ePrev.WindDelta != 0)) + { + OutPt op = AddOutPt(ePrev, e.Curr); + OutPt op2 = AddOutPt(e, e.Curr); + AddJoin(op, op2, e.Curr); //StrictlySimple (type-3) join + } + } + + e = e.NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + ProcessHorizontals(true); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while (e != null) + { + if(IsIntermediate(e, topY)) + { + OutPt op = null; + if( e.OutIdx >= 0 ) + op = AddOutPt(e, e.Top); + UpdateEdgeIntoAEL(ref e); + + //if output polygons share an edge, they'll need joining later ... + TEdge ePrev = e.PrevInAEL; + TEdge eNext = e.NextInAEL; + if (ePrev != null && ePrev.Curr.X == e.Bot.X && + ePrev.Curr.Y == e.Bot.Y && op != null && + ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && + SlopesEqual(e, ePrev, m_UseFullRange) && + (e.WindDelta != 0) && (ePrev.WindDelta != 0)) + { + OutPt op2 = AddOutPt(ePrev, e.Bot); + AddJoin(op, op2, e.Top); + } + else if (eNext != null && eNext.Curr.X == e.Bot.X && + eNext.Curr.Y == e.Bot.Y && op != null && + eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && + SlopesEqual(e, eNext, m_UseFullRange) && + (e.WindDelta != 0) && (eNext.WindDelta != 0)) + { + OutPt op2 = AddOutPt(eNext, e.Bot); + AddJoin(op, op2, e.Top); + } + } + e = e.NextInAEL; + } + } + //------------------------------------------------------------------------------ + + private void DoMaxima(TEdge e) + { + TEdge eMaxPair = GetMaximaPair(e); + if (eMaxPair == null) + { + if (e.OutIdx >= 0) + AddOutPt(e, e.Top); + DeleteFromAEL(e); + return; + } + + TEdge eNext = e.NextInAEL; + while(eNext != null && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e.Top, true); + SwapPositionsInAEL(e, eNext); + eNext = e.NextInAEL; + } + + if(e.OutIdx == Unassigned && eMaxPair.OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e.OutIdx >= 0 && eMaxPair.OutIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, e.Top); + } +#if use_lines + else if (e.WindDelta == 0) + { + if (e.OutIdx >= 0) + { + AddOutPt(e, e.Top); + e.OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair.OutIdx >= 0) + { + AddOutPt(eMaxPair, e.Top); + eMaxPair.OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw new ClipperException("DoMaxima error"); + } + //------------------------------------------------------------------------------ + + public static void ReversePaths(Paths polys) + { + foreach (var poly in polys) { poly.Reverse(); } + } + //------------------------------------------------------------------------------ + + public static bool Orientation(Path poly) + { + return Area(poly) >= 0; + } + //------------------------------------------------------------------------------ + + private int PointCount(OutPt pts) + { + if (pts == null) return 0; + int result = 0; + OutPt p = pts; + do + { + result++; + p = p.Next; + } + while (p != pts); + return result; + } + //------------------------------------------------------------------------------ + + private void BuildResult(Paths polyg) + { + polyg.Clear(); + polyg.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.Pts == null) continue; + OutPt p = outRec.Pts.Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + Path pg = new Path(cnt); + for (int j = 0; j < cnt; j++) + { + pg.Add(p.Pt); + p = p.Prev; + } + polyg.Add(pg); + } + } + //------------------------------------------------------------------------------ + + private void BuildResult2(PolyTree polytree) + { + polytree.Clear(); + + //add each output polygon/contour to polytree ... + polytree.m_AllPolys.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec.Pts); + if ((outRec.IsOpen && cnt < 2) || + (!outRec.IsOpen && cnt < 3)) continue; + FixHoleLinkage(outRec); + PolyNode pn = new PolyNode(); + polytree.m_AllPolys.Add(pn); + outRec.PolyNode = pn; + pn.m_polygon.Capacity = cnt; + OutPt op = outRec.Pts.Prev; + for (int j = 0; j < cnt; j++) + { + pn.m_polygon.Add(op.Pt); + op = op.Prev; + } + } + + //fixup PolyNode links etc ... + polytree.m_Childs.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.PolyNode == null) continue; + else if (outRec.IsOpen) + { + outRec.PolyNode.IsOpen = true; + polytree.AddChild(outRec.PolyNode); + } + else if (outRec.FirstLeft != null && + outRec.FirstLeft.PolyNode != null) + outRec.FirstLeft.PolyNode.AddChild(outRec.PolyNode); + else + polytree.AddChild(outRec.PolyNode); + } + } + //------------------------------------------------------------------------------ + + private void FixupOutPolygon(OutRec outRec) + { + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt lastOK = null; + outRec.BottomPt = null; + OutPt pp = outRec.Pts; + for (;;) + { + if (pp.Prev == pp || pp.Prev == pp.Next) + { + DisposeOutPts(pp); + outRec.Pts = null; + return; + } + //test for duplicate points and collinear edges ... + if ((pp.Pt == pp.Next.Pt) || (pp.Pt == pp.Prev.Pt) || + (SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt, m_UseFullRange) && + (!PreserveCollinear || !Pt2IsBetweenPt1AndPt3(pp.Prev.Pt, pp.Pt, pp.Next.Pt)))) + { + lastOK = null; + OutPt tmp = pp; + pp.Prev.Next = pp.Next; + pp.Next.Prev = pp.Prev; + pp = pp.Prev; + tmp = null; + } + else if (pp == lastOK) break; + else + { + if (lastOK == null) lastOK = pp; + pp = pp.Next; + } + } + outRec.Pts = pp; + } + //------------------------------------------------------------------------------ + + OutPt DupOutPt(OutPt outPt, bool InsertAfter) + { + OutPt result = new OutPt(); + result.Pt = outPt.Pt; + result.Idx = outPt.Idx; + if (InsertAfter) + { + result.Next = outPt.Next; + result.Prev = outPt; + outPt.Next.Prev = result; + outPt.Next = result; + } + else + { + result.Prev = outPt.Prev; + result.Next = outPt; + outPt.Prev.Next = result; + outPt.Prev = result; + } + return result; + } + //------------------------------------------------------------------------------ + + bool GetOverlap(cInt a1, cInt a2, cInt b1, cInt b2, out cInt Left, out cInt Right) + { + if (a1 < a2) + { + if (b1 < b2) {Left = Math.Max(a1,b1); Right = Math.Min(a2,b2);} + else {Left = Math.Max(a1,b2); Right = Math.Min(a2,b1);} + } + else + { + if (b1 < b2) {Left = Math.Max(a2,b1); Right = Math.Min(a1,b2);} + else { Left = Math.Max(a2, b2); Right = Math.Min(a1, b1); } + } + return Left < Right; + } + //------------------------------------------------------------------------------ + + bool JoinHorz(OutPt op1, OutPt op1b, OutPt op2, OutPt op2b, + IntPoint Pt, bool DiscardLeft) + { + Direction Dir1 = (op1.Pt.X > op1b.Pt.X ? + Direction.dRightToLeft : Direction.dLeftToRight); + Direction Dir2 = (op2.Pt.X > op2b.Pt.X ? + Direction.dRightToLeft : Direction.dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == Direction.dLeftToRight) + { + while (op1.Next.Pt.X <= Pt.X && + op1.Next.Pt.X >= op1.Pt.X && op1.Next.Pt.Y == Pt.Y) + op1 = op1.Next; + if (DiscardLeft && (op1.Pt.X != Pt.X)) op1 = op1.Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b.Pt != Pt) + { + op1 = op1b; + op1.Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1.Next.Pt.X >= Pt.X && + op1.Next.Pt.X <= op1.Pt.X && op1.Next.Pt.Y == Pt.Y) + op1 = op1.Next; + if (!DiscardLeft && (op1.Pt.X != Pt.X)) op1 = op1.Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b.Pt != Pt) + { + op1 = op1b; + op1.Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == Direction.dLeftToRight) + { + while (op2.Next.Pt.X <= Pt.X && + op2.Next.Pt.X >= op2.Pt.X && op2.Next.Pt.Y == Pt.Y) + op2 = op2.Next; + if (DiscardLeft && (op2.Pt.X != Pt.X)) op2 = op2.Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b.Pt != Pt) + { + op2 = op2b; + op2.Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2.Next.Pt.X >= Pt.X && + op2.Next.Pt.X <= op2.Pt.X && op2.Next.Pt.Y == Pt.Y) + op2 = op2.Next; + if (!DiscardLeft && (op2.Pt.X != Pt.X)) op2 = op2.Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b.Pt != Pt) + { + op2 = op2b; + op2.Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == Direction.dLeftToRight) == DiscardLeft) + { + op1.Prev = op2; + op2.Next = op1; + op1b.Next = op2b; + op2b.Prev = op1b; + } + else + { + op1.Next = op2; + op2.Prev = op1; + op1b.Prev = op2b; + op2b.Next = op1b; + } + return true; + } + //------------------------------------------------------------------------------ + + private bool JoinPoints(Join j, OutRec outRec1, OutRec outRec2) + { + OutPt op1 = j.OutPt1, op1b; + OutPt op2 = j.OutPt2, op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictlySimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j.OutPt1.Pt.Y == j.OffPt.Y); + + if (isHorizontal && (j.OffPt == j.OutPt1.Pt) && (j.OffPt == j.OutPt2.Pt)) + { + //Strictly Simple join ... + op1b = j.OutPt1.Next; + while (op1b != op1 && (op1b.Pt == j.OffPt)) + op1b = op1b.Next; + bool reverse1 = (op1b.Pt.Y > j.OffPt.Y); + op2b = j.OutPt2.Next; + while (op2b != op2 && (op2b.Pt == j.OffPt)) + op2b = op2b.Next; + bool reverse2 = (op2b.Pt.Y > j.OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1.Prev = op2; + op2.Next = op1; + op1b.Next = op2b; + op2b.Prev = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1.Next = op2; + op2.Prev = op1; + op1b.Prev = op2b; + op2b.Next = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1.Prev.Pt.Y == op1.Pt.Y && op1.Prev != op1b && op1.Prev != op2) + op1 = op1.Prev; + while (op1b.Next.Pt.Y == op1b.Pt.Y && op1b.Next != op1 && op1b.Next != op2) + op1b = op1b.Next; + if (op1b.Next == op1 || op1b.Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2.Prev.Pt.Y == op2.Pt.Y && op2.Prev != op2b && op2.Prev != op1b) + op2 = op2.Prev; + while (op2b.Next.Pt.Y == op2b.Pt.Y && op2b.Next != op2 && op2b.Next != op1) + op2b = op2b.Next; + if (op2b.Next == op2 || op2b.Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1.Pt.X, op1b.Pt.X, op2.Pt.X, op2b.Pt.X, out Left, out Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1.Pt.X >= Left && op1.Pt.X <= Right) + { + Pt = op1.Pt; DiscardLeftSide = (op1.Pt.X > op1b.Pt.X); + } + else if (op2.Pt.X >= Left&& op2.Pt.X <= Right) + { + Pt = op2.Pt; DiscardLeftSide = (op2.Pt.X > op2b.Pt.X); + } + else if (op1b.Pt.X >= Left && op1b.Pt.X <= Right) + { + Pt = op1b.Pt; DiscardLeftSide = op1b.Pt.X > op1.Pt.X; + } + else + { + Pt = op2b.Pt; DiscardLeftSide = (op2b.Pt.X > op2.Pt.X); + } + j.OutPt1 = op1; + j.OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1.Next; + while ((op1b.Pt == op1.Pt) && (op1b != op1)) op1b = op1b.Next; + bool Reverse1 = ((op1b.Pt.Y > op1.Pt.Y) || + !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1.Prev; + while ((op1b.Pt == op1.Pt) && (op1b != op1)) op1b = op1b.Prev; + if ((op1b.Pt.Y > op1.Pt.Y) || + !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt, m_UseFullRange)) return false; + }; + op2b = op2.Next; + while ((op2b.Pt == op2.Pt) && (op2b != op2)) op2b = op2b.Next; + bool Reverse2 = ((op2b.Pt.Y > op2.Pt.Y) || + !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2.Prev; + while ((op2b.Pt == op2.Pt) && (op2b != op2)) op2b = op2b.Prev; + if ((op2b.Pt.Y > op2.Pt.Y) || + !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1.Prev = op2; + op2.Next = op1; + op1b.Next = op2b; + op2b.Prev = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1.Next = op2; + op2.Prev = op1; + op1b.Prev = op2b; + op2b.Next = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } + } + } + //---------------------------------------------------------------------- + + private int PointInPolygon(IntPoint pt, OutPt op) + { + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0; + OutPt startOp = op; + for (; ; ) + { + double poly0x = op.Pt.X, poly0y = op.Pt.Y; + double poly1x = op.Next.Pt.X, poly1y = op.Next.Pt.Y; + + if (poly1y == pt.Y) + { + if ((poly1x == pt.X) || (poly0y == pt.Y && + ((poly1x > pt.X) == (poly0x < pt.X)))) return -1; + } + if ((poly0y < pt.Y) != (poly1y < pt.Y)) + { + if (poly0x >= pt.X) + { + if (poly1x > pt.X) result = 1 - result; + else + { + double d = (double)(poly0x - pt.X) * (poly1y - pt.Y) - + (double)(poly1x - pt.X) * (poly0y - pt.Y); + if (d == 0) return -1; + if ((d > 0) == (poly1y > poly0y)) result = 1 - result; + } + } + else + { + if (poly1x > pt.X) + { + double d = (double)(poly0x - pt.X) * (poly1y - pt.Y) - + (double)(poly1x - pt.X) * (poly0y - pt.Y); + if (d == 0) return -1; + if ((d > 0) == (poly1y > poly0y)) result = 1 - result; + } + } + } + op = op.Next; + if (startOp == op) break; + } + return result; + } + //------------------------------------------------------------------------------ + + private bool Poly2ContainsPoly1(OutPt outPt1, OutPt outPt2) + { + OutPt op = outPt1; + do + { + int res = PointInPolygon(op.Pt, outPt2); + if (res >= 0) return res != 0; + op = op.Next; + } + while (op != outPt1); + return true; + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts1(OutRec OldOutRec, OutRec NewOutRec) + { + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.Pts != null && outRec.FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec.Pts, NewOutRec.Pts)) + outRec.FirstLeft = NewOutRec; + } + } + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts2(OutRec OldOutRec, OutRec NewOutRec) + { + foreach (OutRec outRec in m_PolyOuts) + if (outRec.FirstLeft == OldOutRec) outRec.FirstLeft = NewOutRec; + } + //---------------------------------------------------------------------- + + private static OutRec ParseFirstLeft(OutRec FirstLeft) + { + while (FirstLeft != null && FirstLeft.Pts == null) + FirstLeft = FirstLeft.FirstLeft; + return FirstLeft; + } + //------------------------------------------------------------------------------ + + private void JoinCommonEdges() + { + for (int i = 0; i < m_Joins.Count; i++) + { + Join join = m_Joins[i]; + + OutRec outRec1 = GetOutRec(join.OutPt1.Idx); + OutRec outRec2 = GetOutRec(join.OutPt2.Idx); + + if (outRec1.Pts == null || outRec2.Pts == null) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1.Pts = join.OutPt1; + outRec1.BottomPt = null; + outRec2 = CreateOutRec(); + outRec2.Pts = join.OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(outRec2); + + //We now need to check every OutRec.FirstLeft pointer. If it points + //to OutRec1 it may need to point to OutRec2 instead ... + if (m_UsingPolyTree) + for (int j = 0; j < m_PolyOuts.Count - 1; j++) + { + OutRec oRec = m_PolyOuts[j]; + if (oRec.Pts == null || ParseFirstLeft(oRec.FirstLeft) != outRec1 || + oRec.IsHole == outRec1.IsHole) continue; + if (Poly2ContainsPoly1(oRec.Pts, join.OutPt2)) + oRec.FirstLeft = outRec2; + } + + if (Poly2ContainsPoly1(outRec2.Pts, outRec1.Pts)) + { + //outRec2 is contained by outRec1 ... + outRec2.IsHole = !outRec1.IsHole; + outRec2.FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2.IsHole ^ ReverseSolution) == (Area(outRec2) > 0)) + ReversePolyPtLinks(outRec2.Pts); + + } + else if (Poly2ContainsPoly1(outRec1.Pts, outRec2.Pts)) + { + //outRec1 is contained by outRec2 ... + outRec2.IsHole = outRec1.IsHole; + outRec1.IsHole = !outRec2.IsHole; + outRec2.FirstLeft = outRec1.FirstLeft; + outRec1.FirstLeft = outRec2; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1.IsHole ^ ReverseSolution) == (Area(outRec1) > 0)) + ReversePolyPtLinks(outRec1.Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2.IsHole = outRec1.IsHole; + outRec2.FirstLeft = outRec1.FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2.Pts = null; + outRec2.BottomPt = null; + outRec2.Idx = outRec1.Idx; + + outRec1.IsHole = holeStateRec.IsHole; + if (holeStateRec == outRec2) + outRec1.FirstLeft = outRec2.FirstLeft; + outRec2.FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } + } + //------------------------------------------------------------------------------ + + private void UpdateOutPtIdxs(OutRec outrec) + { + OutPt op = outrec.Pts; + do + { + op.Idx = outrec.Idx; + op = op.Prev; + } + while(op != outrec.Pts); + } + //------------------------------------------------------------------------------ + + private void DoSimplePolygons() + { + int i = 0; + while (i < m_PolyOuts.Count) + { + OutRec outrec = m_PolyOuts[i++]; + OutPt op = outrec.Pts; + if (op == null) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt op2 = op.Next; + while (op2 != outrec.Pts) + { + if ((op.Pt == op2.Pt) && op2.Next != op && op2.Prev != op) + { + //split the polygon into two ... + OutPt op3 = op.Prev; + OutPt op4 = op2.Prev; + op.Prev = op4; + op4.Next = op; + op2.Prev = op3; + op3.Next = op2; + + outrec.Pts = op; + OutRec outrec2 = CreateOutRec(); + outrec2.Pts = op2; + UpdateOutPtIdxs(outrec2); + if (Poly2ContainsPoly1(outrec2.Pts, outrec.Pts)) + { + //OutRec2 is contained by OutRec1 ... + outrec2.IsHole = !outrec.IsHole; + outrec2.FirstLeft = outrec; + } + else + if (Poly2ContainsPoly1(outrec.Pts, outrec2.Pts)) + { + //OutRec1 is contained by OutRec2 ... + outrec2.IsHole = outrec.IsHole; + outrec.IsHole = !outrec2.IsHole; + outrec2.FirstLeft = outrec.FirstLeft; + outrec.FirstLeft = outrec2; + } else + { + //the 2 polygons are separate ... + outrec2.IsHole = outrec.IsHole; + outrec2.FirstLeft = outrec.FirstLeft; + } + op2 = op; //ie get ready for the next iteration + } + op2 = op2.Next; + } + op = op.Next; + } + while (op != outrec.Pts); + } + } + //------------------------------------------------------------------------------ + + public static double Area(Path poly) + { + int cnt = (int)poly.Count; + if (cnt < 3) return 0; + double a = 0; + for (int i = 0, j = cnt - 1; i < cnt; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; + } + //------------------------------------------------------------------------------ + + double Area(OutRec outRec) + { + OutPt op = outRec.Pts; + if (op == null) return 0; + double a = 0; + do { + a = a + (double)(op.Prev.Pt.X + op.Pt.X) * (double)(op.Prev.Pt.Y - op.Pt.Y); + op = op.Next; + } while (op != outRec.Pts); + return a * 0.5; + } + +#if use_deprecated + + public static Paths OffsetPaths(Paths polys, double delta, + JoinType jointype, EndType_ endtype, double MiterLimit) + { + Paths result = new Paths(); + ClipperOffset co = new ClipperOffset(MiterLimit, MiterLimit); + co.AddPaths(polys, jointype, (EndType)endtype); + co.Execute(ref result, delta); + return result; + } + //------------------------------------------------------------------------------ +#endif + + //------------------------------------------------------------------------------ + // SimplifyPolygon functions ... + // Convert self-intersecting polygons into simple polygons + //------------------------------------------------------------------------------ + + public static Paths SimplifyPolygon(Path poly, + PolyFillType fillType = PolyFillType.pftEvenOdd) + { + Paths result = new Paths(); + Clipper c = new Clipper(); + c.StrictlySimple = true; + c.AddPath(poly, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, result, fillType, fillType); + return result; + } + //------------------------------------------------------------------------------ + + public static Paths SimplifyPolygons(Paths polys, + PolyFillType fillType = PolyFillType.pftEvenOdd) + { + Paths result = new Paths(); + Clipper c = new Clipper(); + c.StrictlySimple = true; + c.AddPaths(polys, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, result, fillType, fillType); + return result; + } + //------------------------------------------------------------------------------ + + private static double DistanceSqrd(IntPoint pt1, IntPoint pt2) + { + double dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (dx*dx + dy*dy); + } + //------------------------------------------------------------------------------ + + private static double DistanceFromLineSqrd(IntPoint pt, IntPoint ln1, IntPoint ln2) + { + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x¹,y¹) & (x²,y²) is ... + //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 + //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = ln1.Y - ln2.Y; + double B = ln2.X - ln1.X; + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); + } + //--------------------------------------------------------------------------- + + private static bool SlopesNearCollinear(IntPoint pt1, + IntPoint pt2, IntPoint pt3, double distSqrd) + { + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + } + //------------------------------------------------------------------------------ + + private static bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) + { + double dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((dx * dx) + (dy * dy) <= distSqrd); + } + //------------------------------------------------------------------------------ + + private static OutPt ExcludeOp(OutPt op) + { + OutPt result = op.Prev; + result.Next = op.Next; + op.Next.Prev = result; + result.Idx = 0; + return result; + } + //------------------------------------------------------------------------------ + + public static Path CleanPolygon(Path path, double distance = 1.415) + { + //distance = proximity in units/pixels below which vertices will be stripped. + //Default ~= sqrt(2) so when adjacent vertices or semi-adjacent vertices have + //both x & y coords within 1 unit, then the second vertex will be stripped. + + int cnt = path.Count; + + if (cnt == 0) return new Path(); + + OutPt [] outPts = new OutPt[cnt]; + for (int i = 0; i < cnt; ++i) outPts[i] = new OutPt(); + + for (int i = 0; i < cnt; ++i) + { + outPts[i].Pt = path[i]; + outPts[i].Next = outPts[(i + 1) % cnt]; + outPts[i].Next.Prev = outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt op = outPts[0]; + while (op.Idx == 0 && op.Next != op.Prev) + { + if (PointsAreClose(op.Pt, op.Prev.Pt, distSqrd)) + { + op = ExcludeOp(op); + cnt--; + } + else if (PointsAreClose(op.Prev.Pt, op.Next.Pt, distSqrd)) + { + ExcludeOp(op.Next); + op = ExcludeOp(op); + cnt -= 2; + } + else if (SlopesNearCollinear(op.Prev.Pt, op.Pt, op.Next.Pt, distSqrd)) + { + op = ExcludeOp(op); + cnt--; + } + else + { + op.Idx = 1; + op = op.Next; + } + } + + if (cnt < 3) cnt = 0; + Path result = new Path(cnt); + for (int i = 0; i < cnt; ++i) + { + result.Add(op.Pt); + op = op.Next; + } + outPts = null; + return result; + } + //------------------------------------------------------------------------------ + + public static Paths CleanPolygons(Paths polys, + double distance = 1.415) + { + Paths result = new Paths(polys.Count); + for (int i = 0; i < polys.Count; i++) + result.Add(CleanPolygon(polys[i], distance)); + return result; + } + //------------------------------------------------------------------------------ + + internal static Paths Minkowski(Path poly, Path path, bool IsSum, bool IsClosed) + { + int delta = (IsClosed ? 1 : 0); + int polyCnt = poly.Count; + int pathCnt = path.Count; + Paths result = new Paths(pathCnt); + if (IsSum) + for (int i = 0; i < pathCnt; i++) + { + Path p = new Path(polyCnt); + foreach (IntPoint ip in poly) + p.Add(new IntPoint(path[i].X + ip.X, path[i].Y + ip.Y)); + result.Add(p); + } + else + for (int i = 0; i < pathCnt; i++) + { + Path p = new Path(polyCnt); + foreach (IntPoint ip in poly) + p.Add(new IntPoint(path[i].X - ip.X, path[i].Y - ip.Y)); + result.Add(p); + } + + Paths quads = new Paths((pathCnt + delta) * (polyCnt + 1)); + for (int i = 0; i <= pathCnt - 2 + delta; i++) + for (int j = 0; j <= polyCnt - 1; j++) + { + Path quad = new Path(4); + quad.Add(result[i % pathCnt][j % polyCnt]); + quad.Add(result[(i + 1) % pathCnt][j % polyCnt]); + quad.Add(result[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.Add(result[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) quad.Reverse(); + quads.Add(quad); + } + + Clipper c = new Clipper(); + c.AddPaths(quads, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, result, PolyFillType.pftNonZero, PolyFillType.pftNonZero); + return result; + } + //------------------------------------------------------------------------------ + + public static Paths MinkowskiSum(Path poly, Path path, bool IsClosed) + { + return Minkowski(poly, path, true, IsClosed); + } + //------------------------------------------------------------------------------ + + public static Paths MinkowskiDiff(Path poly, Path path, bool IsClosed) + { + return Minkowski(poly, path, false, IsClosed); + } + //------------------------------------------------------------------------------ + + internal enum NodeType { ntAny, ntOpen, ntClosed }; + + public static Paths PolyTreeToPaths(PolyTree polytree) + { + + Paths result = new Paths(); + result.Capacity = polytree.Total; + AddPolyNodeToPaths(polytree, NodeType.ntAny, result); + return result; + } + //------------------------------------------------------------------------------ + + internal static void AddPolyNodeToPaths(PolyNode polynode, NodeType nt, Paths paths) + { + bool match = true; + switch (nt) + { + case NodeType.ntOpen: return; + case NodeType.ntClosed: match = !polynode.IsOpen; break; + default: break; + } + + if (polynode.m_polygon.Count > 0 && match) + paths.Add(polynode.m_polygon); + foreach (PolyNode pn in polynode.Childs) + AddPolyNodeToPaths(pn, nt, paths); + } + //------------------------------------------------------------------------------ + + public static Paths OpenPathsFromPolyTree(PolyTree polytree) + { + Paths result = new Paths(); + result.Capacity = polytree.ChildCount; + for (int i = 0; i < polytree.ChildCount; i++) + if (polytree.Childs[i].IsOpen) + result.Add(polytree.Childs[i].m_polygon); + return result; + } + //------------------------------------------------------------------------------ + + public static Paths ClosedPathsFromPolyTree(PolyTree polytree) + { + Paths result = new Paths(); + result.Capacity = polytree.Total; + AddPolyNodeToPaths(polytree, NodeType.ntClosed, result); + return result; + } + //------------------------------------------------------------------------------ + + } //end Clipper + + public class ClipperOffset + { + private Paths m_destPolys; + private Path m_srcPoly; + private Path m_destPoly; + private List m_normals = new List(); + private double m_delta, m_sinA, m_sin, m_cos; + private double m_miterLim, m_StepsPerRad; + + private IntPoint m_lowest; + private PolyNode m_polyNodes = new PolyNode(); + + public double ArcTolerance { get; set; } + public double MiterLimit { get; set; } + + private const double two_pi = Math.PI * 2; + private const double def_arc_tolerance = 0.25; + + public ClipperOffset( + double miterLimit = 2.0, double arcTolerance = def_arc_tolerance) + { + MiterLimit = miterLimit; + ArcTolerance = arcTolerance; + m_lowest.X = -1; + } + //------------------------------------------------------------------------------ + + public void Clear() + { + m_polyNodes.Childs.Clear(); + m_lowest.X = -1; + } + //------------------------------------------------------------------------------ + + internal static cInt Round(double value) + { + return value < 0 ? (cInt)(value - 0.5) : (cInt)(value + 0.5); + } + //------------------------------------------------------------------------------ + + public void AddPath(Path path, JoinType joinType, EndType endType) + { + int highI = path.Count - 1; + if (highI < 0) return; + PolyNode newNode = new PolyNode(); + newNode.m_jointype = joinType; + newNode.m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == EndType.etClosedLine || endType == EndType.etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode.m_polygon.Capacity = highI + 1; + newNode.m_polygon.Add(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode.m_polygon[j] != path[i]) + { + j++; + newNode.m_polygon.Add(path[i]); + if (path[i].Y > newNode.m_polygon[k].Y || + (path[i].Y == newNode.m_polygon[k].Y && + path[i].X < newNode.m_polygon[k].X)) k = j; + } + if ((endType == EndType.etClosedPolygon && j < 2) || + (endType != EndType.etClosedPolygon && j < 0)) return; + + m_polyNodes.AddChild(newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != EndType.etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = new IntPoint(0, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X].m_polygon[(int)m_lowest.Y]; + if (newNode.m_polygon[k].Y > ip.Y || + (newNode.m_polygon[k].Y == ip.Y && + newNode.m_polygon[k].X < ip.X)) + m_lowest = new IntPoint(m_polyNodes.ChildCount - 1, k); + } + } + //------------------------------------------------------------------------------ + + public void AddPaths(Paths paths, JoinType joinType, EndType endType) + { + foreach (Path p in paths) + AddPath(p, joinType, endType); + } + //------------------------------------------------------------------------------ + + private void FixOrientations() + { + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Clipper.Orientation(m_polyNodes.Childs[(int)m_lowest.X].m_polygon)) + { + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + if (node.m_endtype == EndType.etClosedPolygon || + (node.m_endtype == EndType.etClosedLine && + Clipper.Orientation(node.m_polygon))) + node.m_polygon.Reverse(); + } + } + else + { + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + if (node.m_endtype == EndType.etClosedLine && + !Clipper.Orientation(node.m_polygon)) + node.m_polygon.Reverse(); + } + } + } + //------------------------------------------------------------------------------ + + internal static DoublePoint GetUnitNormal(IntPoint pt1, IntPoint pt2) + { + double dx = (pt2.X - pt1.X); + double dy = (pt2.Y - pt1.Y); + if ((dx == 0) && (dy == 0)) return new DoublePoint(); + + double f = 1 * 1.0 / Math.Sqrt(dx * dx + dy * dy); + dx *= f; + dy *= f; + + return new DoublePoint(dy, -dx); + } + //------------------------------------------------------------------------------ + + private void DoOffset(double delta) + { + m_destPolys = new Paths(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (ClipperBase.near_zero(delta)) + { + m_destPolys.Capacity = m_polyNodes.ChildCount; + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + if (node.m_endtype == EndType.etClosedPolygon) + m_destPolys.Add(node.m_polygon); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2 / (MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) + y = def_arc_tolerance; + else if (ArcTolerance > Math.Abs(delta) * def_arc_tolerance) + y = Math.Abs(delta) * def_arc_tolerance; + else + y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = Math.PI / Math.Acos(1 - y / Math.Abs(delta)); + m_sin = Math.Sin(two_pi / steps); + m_cos = Math.Cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.Capacity = m_polyNodes.ChildCount * 2; + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + m_srcPoly = node.m_polygon; + + int len = m_srcPoly.Count; + + if (len == 0 || (delta <= 0 && (len < 3 || + node.m_endtype != EndType.etClosedPolygon))) + continue; + + m_destPoly = new Path(); + + if (len == 1) + { + if (node.m_jointype == JoinType.jtRound) + { + double X = 1.0, Y = 0.0; + for (int j = 1; j <= steps; j++) + { + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.Add(m_destPoly); + continue; + } + + //build m_normals ... + m_normals.Clear(); + m_normals.Capacity = len; + for (int j = 0; j < len - 1; j++) + m_normals.Add(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == EndType.etClosedLine || + node.m_endtype == EndType.etClosedPolygon) + m_normals.Add(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.Add(new DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == EndType.etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; j++) + OffsetPoint(j, ref k, node.m_jointype); + m_destPolys.Add(m_destPoly); + } + else if (node.m_endtype == EndType.etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; j++) + OffsetPoint(j, ref k, node.m_jointype); + m_destPolys.Add(m_destPoly); + m_destPoly = new Path(); + //re-build m_normals ... + DoublePoint n = m_normals[len - 1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = new DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = new DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, ref k, node.m_jointype); + m_destPolys.Add(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, ref k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == EndType.etOpenButt) + { + int j = len - 1; + pt1 = new IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.Add(pt1); + pt1 = new IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.Add(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = new DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == EndType.etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = new DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + + m_normals[0] = new DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) + OffsetPoint(j, ref k, node.m_jointype); + + if (node.m_endtype == EndType.etOpenButt) + { + pt1 = new IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.Add(pt1); + pt1 = new IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.Add(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == EndType.etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.Add(m_destPoly); + } + } + } + //------------------------------------------------------------------------------ + + public void Execute(ref Paths solution, double delta) + { + solution.Clear(); + FixOrientations(); + DoOffset(delta); + //now clean up 'corners' ... + Clipper clpr = new Clipper(); + clpr.AddPaths(m_destPolys, PolyType.ptSubject, true); + if (delta > 0) + { + clpr.Execute(ClipType.ctUnion, solution, + PolyFillType.pftPositive, PolyFillType.pftPositive); + } + else + { + IntRect r = Clipper.GetBounds(m_destPolys); + Path outer = new Path(4); + + outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.top - 10)); + outer.Add(new IntPoint(r.left - 10, r.top - 10)); + + clpr.AddPath(outer, PolyType.ptSubject, true); + clpr.ReverseSolution = true; + clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); + if (solution.Count > 0) solution.RemoveAt(0); + } + } + //------------------------------------------------------------------------------ + + public void Execute(ref PolyTree solution, double delta) + { + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr = new Clipper(); + clpr.AddPaths(m_destPolys, PolyType.ptSubject, true); + if (delta > 0) + { + clpr.Execute(ClipType.ctUnion, solution, + PolyFillType.pftPositive, PolyFillType.pftPositive); + } + else + { + IntRect r = Clipper.GetBounds(m_destPolys); + Path outer = new Path(4); + + outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.top - 10)); + outer.Add(new IntPoint(r.left - 10, r.top - 10)); + + clpr.AddPath(outer, PolyType.ptSubject, true); + clpr.ReverseSolution = true; + clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount == 1 && solution.Childs[0].ChildCount > 0) + { + PolyNode outerNode = solution.Childs[0]; + solution.Childs.Capacity = outerNode.ChildCount; + solution.Childs[0] = outerNode.Childs[0]; + for (int i = 1; i < outerNode.ChildCount; i++) + solution.AddChild(outerNode.Childs[i]); + } + else + solution.Clear(); + } + } + //------------------------------------------------------------------------------ + + void OffsetPoint(int j, ref int k, JoinType jointype) + { + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (m_sinA < 0.00005 && m_sinA > -0.00005) return; + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.Add(m_srcPoly[j]); + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case JoinType.jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case JoinType.jtSquare: DoSquare(j, k); break; + case JoinType.jtRound: DoRound(j, k); break; + } + k = j; + } + //------------------------------------------------------------------------------ + + internal void DoSquare(int j, int k) + { + double dx = Math.Tan(Math.Atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); + } + //------------------------------------------------------------------------------ + + internal void DoMiter(int j, int k, double r) + { + double q = m_delta / r; + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); + } + //------------------------------------------------------------------------------ + + internal void DoRound(int j, int k) + { + double a = Math.Atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = (int)Round(m_StepsPerRad * Math.Abs(a)); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + //------------------------------------------------------------------------------ + } + + class ClipperException : Exception + { + public ClipperException(string description) : base(description){} + } + //------------------------------------------------------------------------------ + +} //end ClipperLib namespace diff --git a/ThirdParty/clipper_library/clipper_library.csproj b/ThirdParty/clipper_library/clipper_library.csproj new file mode 100644 index 0000000..4f8ee9f --- /dev/null +++ b/ThirdParty/clipper_library/clipper_library.csproj @@ -0,0 +1,54 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {9B062971-A88E-4A3D-B3C9-12B78D15FA66} + Library + Properties + ClipperLib + clipper_library + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file