[mapnik-clipper] 01/02: Imported Upstream version 0.0~20150707-33c9329+ds
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Sat Sep 12 16:09:50 UTC 2015
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository mapnik-clipper.
commit 2923135d2d21ffb6c18be38cd93c7afaa3481fe2
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Sat Sep 12 14:03:53 2015 +0200
Imported Upstream version 0.0~20150707-33c9329+ds
---
C#/ConsoleDemo/ConsoleDemo.sln | 45 +
C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj | 103 +
C#/ConsoleDemo/ConsoleDemo/Program.cs | 453 ++
.../ConsoleDemo/Properties/AssemblyInfo.cs | 36 +
C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt | 6 +
C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt | 13 +
C#/GuiDemo/GuiDemo.sln | 45 +
C#/GuiDemo/GuiDemo/Form1.Designer.cs | 419 ++
C#/GuiDemo/GuiDemo/Form1.cs | 682 +++
C#/GuiDemo/GuiDemo/Form1.resx | 225 +
C#/GuiDemo/GuiDemo/GuiDemo.csproj | 135 +
C#/GuiDemo/GuiDemo/Program.cs | 21 +
C#/GuiDemo/GuiDemo/Properties/AssemblyInfo.cs | 36 +
.../GuiDemo/Properties/Resources.Designer.cs | 70 +
C#/GuiDemo/GuiDemo/Properties/Resources.resx | 124 +
C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs | 26 +
C#/GuiDemo/GuiDemo/Properties/Settings.settings | 7 +
C#/GuiDemo/GuiDemo/Settings.cs | 28 +
C#/GuiDemo/GuiDemo/aust.bin | Bin 0 -> 6828 bytes
C#/clipper_library/Properties/AssemblyInfo.cs | 36 +
C#/clipper_library/clipper.cs | 4839 +++++++++++++++++
C#/clipper_library/clipper_library.csproj | 54 +
Delphi/cairo demo/Cairo Resources.txt | 12 +
Delphi/cairo demo/CairoClipperDemo1.dpr | 267 +
Delphi/cairo demo/CairoClipperDemo1.res | Bin 0 -> 876 bytes
Delphi/cairo demo/cairo_clipper.pas | 121 +
Delphi/clipper.pas | 5514 ++++++++++++++++++++
Delphi/main demo/GR32_Misc.pas | 295 ++
Delphi/main demo/clipper_demo.dpr | 15 +
Delphi/main demo/clipper_demo.res | Bin 0 -> 6672 bytes
Delphi/main demo/main.dfm | 394 ++
Delphi/main demo/main.pas | 797 +++
Delphi/main demo/polygons.res | Bin 0 -> 19376 bytes
Documentation/offset_triginometry.svg | 42 +
Documentation/offset_triginometry2.svg | 28 +
Documentation/offset_triginometry3.svg | 391 ++
License.txt | 24 +
README | 407 ++
Third Party/Flash/AS3_flash_readme.txt | 2 +
Third Party/Go/Go_readme.txt | 2 +
Third Party/Haskell/Haskell_readme.txt | 4 +
Third Party/LuaJIT/LuaJIT_readme.txt | 1 +
Third Party/Matlab/matlab_readme.txt | 3 +
Third Party/Matlab/mexclipper.cpp | 283 +
Third Party/ObjectiveC/objectivec_readme.txt | 4 +
Third Party/perl/perl_readme.txt | 5 +
Third Party/python/clipper.py | 2259 ++++++++
Third Party/python/clipper_demo.py | 267 +
Third Party/python/python_readme.txt | 10 +
Third Party/ruby/ruby_readme.txt | 4 +
cpp/CMakeLists.txt | 21 +
cpp/clipper.cpp | 4577 ++++++++++++++++
cpp/clipper.hpp | 435 ++
cpp/cpp_agg/agg_conv_clipper.h | 295 ++
cpp/cpp_agg/clipper_test.cpp | 574 ++
cpp/cpp_agg/clipper_test.sln | 20 +
cpp/cpp_agg/clipper_test.vcxproj | 114 +
cpp/cpp_agg/clipper_test.vcxproj.filters | 118 +
cpp/cpp_agg/icon.res | Bin 0 -> 5556 bytes
cpp/cpp_cairo/Cairo Resources.txt | 6 +
cpp/cpp_cairo/cairo.sln | 20 +
cpp/cpp_cairo/cairo.vcxproj | 93 +
cpp/cpp_cairo/cairo_clipper.cpp | 134 +
cpp/cpp_cairo/cairo_clipper.hpp | 59 +
cpp/cpp_cairo/libcairo-2.lib | Bin 0 -> 83700 bytes
cpp/cpp_cairo/main.cpp | 182 +
cpp/cpp_console/clipper_console_demo.cpp | 461 ++
cpp/cpp_console/clipper_console_demo.sln | 20 +
cpp/cpp_console/clipper_console_demo.vcxproj | 92 +
cpp/cpp_opengl/clipper_demo.sln | 20 +
cpp/cpp_opengl/clipper_demo.vcxproj | 98 +
cpp/cpp_opengl/icon.res | Bin 0 -> 5556 bytes
cpp/cpp_opengl/main.cpp | 621 +++
cpp/cpp_opengl/menu.res | Bin 0 -> 1364 bytes
cpp/fix_members.sh | 24 +
cpp/polyclipping.pc.cmakein | 13 +
mapnik-changes.md | 24 +
77 files changed, 26575 insertions(+)
diff --git a/C#/ConsoleDemo/ConsoleDemo.sln b/C#/ConsoleDemo/ConsoleDemo.sln
new file mode 100644
index 0000000..0ee75df
--- /dev/null
+++ b/C#/ConsoleDemo/ConsoleDemo.sln
@@ -0,0 +1,45 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C# Express 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleDemo", "ConsoleDemo\ConsoleDemo.csproj", "{185E6664-6A68-4377-99BE-4D4BFED19298}"
+ ProjectSection(ProjectDependencies) = postProject
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66} = {9B062971-A88E-4A3D-B3C9-12B78D15FA66}
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clipper_library", "..\clipper_library\clipper_library.csproj", "{9B062971-A88E-4A3D-B3C9-12B78D15FA66}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|x86.ActiveCfg = Debug|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Debug|x86.Build.0 = Debug|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|Any CPU.ActiveCfg = Release|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|Mixed Platforms.Build.0 = Release|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|x86.ActiveCfg = Release|x86
+ {185E6664-6A68-4377-99BE-4D4BFED19298}.Release|x86.Build.0 = Release|x86
+ {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}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|x86.ActiveCfg = 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
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|x86.ActiveCfg = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj b/C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj
new file mode 100644
index 0000000..ab09c30
--- /dev/null
+++ b/C#/ConsoleDemo/ConsoleDemo/ConsoleDemo.csproj
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{185E6664-6A68-4377-99BE-4D4BFED19298}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>ConsoleDemo</RootNamespace>
+ <AssemblyName>ConsoleDemo</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+ <FileAlignment>512</FileAlignment>
+ <PublishUrl>publish\</PublishUrl>
+ <Install>true</Install>
+ <InstallFrom>Disk</InstallFrom>
+ <UpdateEnabled>false</UpdateEnabled>
+ <UpdateMode>Foreground</UpdateMode>
+ <UpdateInterval>7</UpdateInterval>
+ <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+ <UpdatePeriodically>false</UpdatePeriodically>
+ <UpdateRequired>false</UpdateRequired>
+ <MapFileExtensions>true</MapFileExtensions>
+ <ApplicationRevision>0</ApplicationRevision>
+ <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+ <IsWebBootstrapper>false</IsWebBootstrapper>
+ <UseApplicationTrust>false</UseApplicationTrust>
+ <BootstrapperEnabled>true</BootstrapperEnabled>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <PlatformTarget>x86</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <UseVSHostingProcess>true</UseVSHostingProcess>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <PlatformTarget>x86</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <UseVSHostingProcess>true</UseVSHostingProcess>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
+ <Visible>False</Visible>
+ <ProductName>Microsoft .NET Framework 4 Client Profile %28x86 and x64%29</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+ <Visible>False</Visible>
+ <ProductName>Windows Installer 3.1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\clipper_library\clipper_library.csproj">
+ <Project>{9B062971-A88E-4A3D-B3C9-12B78D15FA66}</Project>
+ <Name>clipper_library</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
diff --git a/C#/ConsoleDemo/ConsoleDemo/Program.cs b/C#/ConsoleDemo/ConsoleDemo/Program.cs
new file mode 100644
index 0000000..83f056c
--- /dev/null
+++ b/C#/ConsoleDemo/ConsoleDemo/Program.cs
@@ -0,0 +1,453 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Globalization;
+using ClipperLib;
+
+namespace ClipperTest1
+{
+ using Path = List<IntPoint>;
+ using Paths = List<List<IntPoint>>;
+
+ class Program
+ {
+
+ //a very simple class that builds an SVG file with any number of
+ //polygons of the specified formats ...
+ class SVGBuilder
+ {
+
+ public class StyleInfo
+ {
+ public PolyFillType pft;
+ public Color brushClr;
+ public Color penClr;
+ public double penWidth;
+ public int[] dashArray;
+ public Boolean showCoords;
+ public StyleInfo Clone()
+ {
+ StyleInfo si = new StyleInfo();
+ si.pft = this.pft;
+ si.brushClr = this.brushClr;
+ si.dashArray = this.dashArray;
+ si.penClr = this.penClr;
+ si.penWidth = this.penWidth;
+ si.showCoords = this.showCoords;
+ return si;
+ }
+ public StyleInfo()
+ {
+ pft = PolyFillType.pftNonZero;
+ brushClr = Color.AntiqueWhite;
+ dashArray = null;
+ penClr = Color.Black;
+ penWidth = 0.8;
+ showCoords = false;
+ }
+ }
+
+ public class PolyInfo
+ {
+ public Paths polygons;
+ public StyleInfo si;
+ }
+
+ public StyleInfo style;
+ private List<PolyInfo> PolyInfoList;
+ const string svg_header = "<?xml version=\"1.0\" standalone=\"no\"?>\n" +
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" +
+ "\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n\n" +
+ "<svg width=\"{0}px\" height=\"{1}px\" viewBox=\"0 0 {2} {3}\" " +
+ "version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n\n";
+ const string svg_path_format = "\"\n style=\"fill:{0};" +
+ " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" +
+ " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n";
+
+ public SVGBuilder()
+ {
+ PolyInfoList = new List<PolyInfo>();
+ style = new StyleInfo();
+ }
+
+ public void AddPaths(Paths poly)
+ {
+ if (poly.Count == 0) return;
+ PolyInfo pi = new PolyInfo();
+ pi.polygons = poly;
+ pi.si = style.Clone();
+ PolyInfoList.Add(pi);
+ }
+
+ public Boolean SaveToFile(string filename, double scale = 1.0, int margin = 10)
+ {
+ if (scale == 0) scale = 1.0;
+ if (margin < 0) margin = 0;
+
+ //calculate the bounding rect ...
+ int i = 0, j = 0;
+ while (i < PolyInfoList.Count)
+ {
+ j = 0;
+ while (j < PolyInfoList[i].polygons.Count &&
+ PolyInfoList[i].polygons[j].Count == 0) j++;
+ if (j < PolyInfoList[i].polygons.Count) break;
+ i++;
+ }
+ if (i == PolyInfoList.Count) return false;
+ IntRect rec = new IntRect();
+ rec.left = PolyInfoList[i].polygons[j][0].X;
+ rec.right = rec.left;
+ rec.top = PolyInfoList[0].polygons[j][0].Y;
+ rec.bottom = rec.top;
+
+ for ( ; i < PolyInfoList.Count; i++ )
+ {
+ foreach (Path pg in PolyInfoList[i].polygons)
+ foreach (IntPoint pt in pg)
+ {
+ if (pt.X < rec.left) rec.left = pt.X;
+ else if (pt.X > rec.right) rec.right = pt.X;
+ if (pt.Y < rec.top) rec.top = pt.Y;
+ else if (pt.Y > rec.bottom) rec.bottom = pt.Y;
+ }
+ }
+
+ rec.left = (Int64)((double)rec.left * scale);
+ rec.top = (Int64)((double)rec.top * scale);
+ rec.right = (Int64)((double)rec.right * scale);
+ rec.bottom = (Int64)((double)rec.bottom * scale);
+ Int64 offsetX = -rec.left + margin;
+ Int64 offsetY = -rec.top + margin;
+
+ StreamWriter writer = new StreamWriter(filename);
+ if (writer == null) return false;
+ writer.Write(svg_header,
+ (rec.right - rec.left) + margin * 2,
+ (rec.bottom - rec.top) + margin * 2,
+ (rec.right - rec.left) + margin * 2,
+ (rec.bottom - rec.top) + margin * 2);
+
+ foreach (PolyInfo pi in PolyInfoList)
+ {
+ writer.Write(" <path d=\"");
+ foreach (Path p in pi.polygons)
+ {
+ if (p.Count < 3) continue;
+ writer.Write(String.Format(NumberFormatInfo.InvariantInfo, " M {0:f2} {1:f2}",
+ (double)((double)p[0].X * scale + offsetX),
+ (double)((double)p[0].Y * scale + offsetY)));
+ for (int k = 1; k < p.Count; k++)
+ {
+ writer.Write(String.Format(NumberFormatInfo.InvariantInfo, " L {0:f2} {1:f2}",
+ (double)((double)p[k].X * scale + offsetX),
+ (double)((double)p[k].Y * scale + offsetY)));
+ }
+ writer.Write(" z");
+ }
+
+ writer.Write(String.Format(NumberFormatInfo.InvariantInfo, svg_path_format,
+ ColorTranslator.ToHtml(pi.si.brushClr),
+ (float)pi.si.brushClr.A /255,
+ (pi.si.pft == PolyFillType.pftEvenOdd ? "evenodd" : "nonzero"),
+ ColorTranslator.ToHtml(pi.si.penClr),
+ (float)pi.si.penClr.A / 255,
+ pi.si.penWidth));
+
+ if (pi.si.showCoords)
+ {
+ writer.Write("<g font-family=\"Verdana\" font-size=\"11\" fill=\"black\">\n\n");
+ foreach (Path p in pi.polygons)
+ {
+ foreach (IntPoint pt in p)
+ {
+ Int64 x = pt.X;
+ Int64 y = pt.Y;
+ writer.Write(String.Format(
+ "<text x=\"{0}\" y=\"{1}\">{2},{3}</text>\n",
+ (int)(x * scale + offsetX), (int)(y * scale + offsetY), x, y));
+
+ }
+ writer.Write("\n");
+ }
+ writer.Write("</g>\n");
+ }
+ }
+ writer.Write("</svg>\n");
+ writer.Close();
+ return true;
+ }
+ }
+
+ ////////////////////////////////////////////////
+
+ static bool LoadFromFile(string filename, Paths ppg, int dec_places, int xOffset = 0, int yOffset = 0)
+ {
+ double scaling;
+ scaling = Math.Pow(10, dec_places);
+
+ ppg.Clear();
+ if (!File.Exists(filename)) return false;
+ StreamReader sr = new StreamReader(filename);
+ if (sr == null) return false;
+ string line;
+ if ((line = sr.ReadLine()) == null) return false;
+ int polyCnt, vertCnt;
+ if (!Int32.TryParse(line, out polyCnt) || polyCnt < 0) return false;
+ ppg.Capacity = polyCnt;
+ for (int i = 0; i < polyCnt; i++)
+ {
+ if ((line = sr.ReadLine()) == null) return false;
+ if (!Int32.TryParse(line, out vertCnt) || vertCnt < 0) return false;
+ Path pg = new Path(vertCnt);
+ ppg.Add(pg);
+ if (scaling > 0.999 & scaling < 1.001)
+ for (int j = 0; j < vertCnt; j++)
+ {
+ Int64 x, y;
+ if ((line = sr.ReadLine()) == null) return false;
+ char[] delimiters = new char[] { ',', ' ' };
+ string[] vals = line.Split(delimiters);
+ if (vals.Length < 2) return false;
+ if (!Int64.TryParse(vals[0], out x)) return false;
+ if (!Int64.TryParse(vals[1], out y))
+ if (vals.Length < 2 || !Int64.TryParse(vals[2], out y)) return false;
+ x = x + xOffset;
+ y = y + yOffset;
+ pg.Add(new IntPoint(x, y));
+ }
+ else
+ for (int j = 0; j < vertCnt; j++)
+ {
+ double x, y;
+ if ((line = sr.ReadLine()) == null) return false;
+ char[] delimiters = new char[] { ',', ' ' };
+ string[] vals = line.Split(delimiters);
+ if (vals.Length < 2) return false;
+ if (!double.TryParse(vals[0], out x)) return false;
+ if (!double.TryParse(vals[1], out y))
+ if (vals.Length < 2 || !double.TryParse(vals[2], out y)) return false;
+ x = x * scaling + xOffset;
+ y = y * scaling + yOffset;
+ pg.Add(new IntPoint((Int64)Math.Round(x), (Int64)Math.Round(y)));
+ }
+ }
+ return true;
+ }
+
+ ////////////////////////////////////////////////
+ static void SaveToFile(string filename, Paths ppg, int dec_places)
+ {
+ double scaling = Math.Pow(10, dec_places);
+ StreamWriter writer = new StreamWriter(filename);
+ if (writer == null) return;
+ writer.Write("{0}\r\n", ppg.Count);
+ foreach (Path pg in ppg)
+ {
+ writer.Write("{0}\r\n", pg.Count);
+ foreach (IntPoint ip in pg)
+ writer.Write("{0:0.####}, {1:0.####}\r\n", (double)ip.X / scaling, (double)ip.Y / scaling);
+ }
+ writer.Close();
+ }
+
+ ////////////////////////////////////////////////
+
+ static void OutputFileFormat()
+ {
+ Console.WriteLine("The expected (text) file format is ...");
+ Console.WriteLine("Polygon Count");
+ Console.WriteLine("First polygon vertex count");
+ Console.WriteLine("first X, Y coordinate of first polygon");
+ Console.WriteLine("second X, Y coordinate of first polygon");
+ Console.WriteLine("etc.");
+ Console.WriteLine("Second polygon vertex count (if there is one)");
+ Console.WriteLine("first X, Y coordinate of second polygon");
+ Console.WriteLine("second X, Y coordinate of second polygon");
+ Console.WriteLine("etc.");
+ }
+
+ ////////////////////////////////////////////////
+
+ static Path IntsToPolygon(int[] ints)
+ {
+ int len1 = ints.Length /2;
+ Path result = new Path(len1);
+ for (int i = 0; i < len1; i++)
+ result.Add(new IntPoint(ints[i * 2], ints[i * 2 +1]));
+ return result;
+ }
+
+ ////////////////////////////////////////////////
+
+ static Path MakeRandomPolygon(Random r, int maxWidth, int maxHeight, int edgeCount, Int64 scale = 1)
+ {
+ Path result = new Path(edgeCount);
+ for (int i = 0; i < edgeCount; i++)
+ {
+ result.Add(new IntPoint(r.Next(maxWidth)*scale, r.Next(maxHeight)*scale));
+ }
+ return result;
+ }
+ ////////////////////////////////////////////////
+
+ static void Main(string[] args)
+ {
+ ////quick test with random polygons ...
+ //Paths ss = new Paths(1), cc = new Paths(1), sss = new Paths();
+ //Random r = new Random((int)DateTime.Now.Ticks);
+ //int scale = 1000000000; //tests 128bit math
+ //ss.Add(MakeRandomPolygon(r, 400, 350, 9, scale));
+ //cc.Add(MakeRandomPolygon(r, 400, 350, 9, scale));
+ //Clipper cpr = new Clipper();
+ //cpr.AddPaths(ss, PolyType.ptSubject, true);
+ //cpr.AddPaths(cc, PolyType.ptClip, true);
+ //cpr.Execute(ClipType.ctUnion, sss, PolyFillType.pftNonZero, PolyFillType.pftNonZero);
+ //sss = Clipper.OffsetPolygons(sss, -5.0 * scale, JoinType.jtMiter, 4);
+ //SVGBuilder svg1 = new SVGBuilder();
+ //svg1.style.brushClr = Color.FromArgb(0x20, 0, 0, 0x9c);
+ //svg1.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda);
+ //svg1.AddPaths(ss);
+ //svg1.style.brushClr = Color.FromArgb(0x20, 0x9c, 0, 0);
+ //svg1.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a);
+ //svg1.AddPaths(cc);
+ //svg1.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c);
+ //svg1.style.penClr = Color.FromArgb(0, 0x33, 0);
+ //svg1.AddPaths(sss);
+ //svg1.SaveToFile("solution.svg", 1.0 / scale);
+ //return;
+
+ if (args.Length < 5)
+ {
+ string appname = System.Environment.GetCommandLineArgs()[0];
+ appname = System.IO.Path.GetFileName(appname);
+ Console.WriteLine("");
+ Console.WriteLine("Usage:");
+ Console.WriteLine(" {0} CLIPTYPE s_file c_file INPUT_DEC_PLACES SVG_SCALE [S_FILL, C_FILL]", appname);
+ Console.WriteLine(" where ...");
+ Console.WriteLine(" CLIPTYPE = INTERSECTION|UNION|DIFFERENCE|XOR");
+ Console.WriteLine(" FILLMODE = NONZERO|EVENODD");
+ Console.WriteLine(" INPUT_DEC_PLACES = signific. decimal places for subject & clip coords.");
+ Console.WriteLine(" SVG_SCALE = scale of SVG image as power of 10. (Fractions are accepted.)");
+ Console.WriteLine(" both S_FILL and C_FILL are optional. The default is EVENODD.");
+ Console.WriteLine("Example:");
+ Console.WriteLine(" Intersect polygons, rnd to 4 dec places, SVG is 1/100 normal size ...");
+ Console.WriteLine(" {0} INTERSECTION subj.txt clip.txt 0 0 NONZERO NONZERO", appname);
+ return;
+ }
+
+ ClipType ct;
+ switch (args[0].ToUpper())
+ {
+ case "INTERSECTION": ct = ClipType.ctIntersection; break;
+ case "UNION": ct = ClipType.ctUnion; break;
+ case "DIFFERENCE": ct = ClipType.ctDifference; break;
+ case "XOR": ct = ClipType.ctXor; break;
+ default: Console.WriteLine("Error: invalid operation - {0}", args[0]); return;
+ }
+
+ string subjFilename = args[1];
+ string clipFilename = args[2];
+ if (!File.Exists(subjFilename))
+ {
+ Console.WriteLine("Error: file - {0} - does not exist.", subjFilename);
+ return;
+ }
+ if (!File.Exists(clipFilename))
+ {
+ Console.WriteLine("Error: file - {0} - does not exist.", clipFilename);
+ return;
+ }
+
+ int decimal_places = 0;
+ if (!Int32.TryParse(args[3], out decimal_places))
+ {
+ Console.WriteLine("Error: invalid number of decimal places - {0}", args[3]);
+ return;
+ }
+ if (decimal_places > 8) decimal_places = 8;
+ else if (decimal_places < 0) decimal_places = 0;
+
+ double svg_scale = 0;
+ if (!double.TryParse(args[4], out svg_scale))
+ {
+ Console.WriteLine("Error: invalid value for SVG_SCALE - {0}", args[4]);
+ return;
+ }
+ if (svg_scale < -18) svg_scale = -18;
+ else if (svg_scale > 18) svg_scale = 18;
+ svg_scale = Math.Pow(10, svg_scale - decimal_places);//nb: also compensate for decimal places
+
+
+ PolyFillType pftSubj = PolyFillType.pftEvenOdd;
+ PolyFillType pftClip = PolyFillType.pftEvenOdd;
+ if (args.Length > 6)
+ {
+ switch (args[5].ToUpper())
+ {
+ case "EVENODD": pftSubj = PolyFillType.pftEvenOdd; break;
+ case "NONZERO": pftSubj = PolyFillType.pftNonZero; break;
+ default: Console.WriteLine("Error: invalid cliptype - {0}", args[5]); return;
+ }
+ switch (args[6].ToUpper())
+ {
+ case "EVENODD": pftClip = PolyFillType.pftEvenOdd; break;
+ case "NONZERO": pftClip = PolyFillType.pftNonZero; break;
+ default: Console.WriteLine("Error: invalid cliptype - {0}", args[6]); return;
+ }
+ }
+
+ Paths subjs = new Paths();
+ Paths clips = new Paths();
+ if (!LoadFromFile(subjFilename, subjs, decimal_places))
+ {
+ Console.WriteLine("Error processing subject polygons file - {0} ", subjFilename);
+ OutputFileFormat();
+ return;
+ }
+ if (!LoadFromFile(clipFilename, clips, decimal_places))
+ {
+ Console.WriteLine("Error processing clip polygons file - {0} ", clipFilename);
+ OutputFileFormat();
+ return;
+ }
+
+ Console.WriteLine("wait ...");
+ Clipper cp = new Clipper();
+ cp.AddPaths(subjs, PolyType.ptSubject, true);
+ cp.AddPaths(clips, PolyType.ptClip, true);
+
+ Paths solution = new Paths();
+ //Paths solution = new Paths();
+ if (cp.Execute(ct, solution, pftSubj, pftClip))
+ {
+ SaveToFile("solution.txt", solution, decimal_places);
+
+ //solution = Clipper.OffsetPolygons(solution, -4, JoinType.jtRound);
+
+ SVGBuilder svg = new SVGBuilder();
+ svg.style.brushClr = Color.FromArgb(0x20, 0, 0, 0x9c);
+ svg.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda);
+ svg.AddPaths(subjs);
+ svg.style.brushClr = Color.FromArgb(0x20, 0x9c, 0, 0);
+ svg.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a);
+ svg.AddPaths(clips);
+ svg.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c);
+ svg.style.penClr = Color.FromArgb(0, 0x33, 0);
+ svg.AddPaths(solution);
+ svg.SaveToFile("solution.svg", svg_scale);
+
+ Console.WriteLine("finished!");
+ }
+ else
+ {
+ Console.WriteLine("failed!");
+ }
+ }
+
+ } //class Program
+}
diff --git a/C#/ConsoleDemo/ConsoleDemo/Properties/AssemblyInfo.cs b/C#/ConsoleDemo/ConsoleDemo/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..aac993a
--- /dev/null
+++ b/C#/ConsoleDemo/ConsoleDemo/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("ClipperTest1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Angus Johnson")]
+[assembly: AssemblyProduct("ClipperTest1")]
+[assembly: AssemblyCopyright("Copyright © Angus Johnson 2010-14")]
+[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("a8009709-1ebc-44a6-a094-298ec0b23d42")]
+
+// 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/C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt b/C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt
new file mode 100644
index 0000000..6a2a2b0
--- /dev/null
+++ b/C#/ConsoleDemo/ConsoleDemo/bin/Release/clip.txt
@@ -0,0 +1,6 @@
+1
+4
+120, 10,
+240, 10,
+240, 90,
+120, 90
\ No newline at end of file
diff --git a/C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt b/C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt
new file mode 100644
index 0000000..7033a4c
--- /dev/null
+++ b/C#/ConsoleDemo/ConsoleDemo/bin/Release/subj.txt
@@ -0,0 +1,13 @@
+1
+11
+0, 0,
+200, 0,
+200, 100,
+50, 100,
+50, 50,
+75, 75,
+100, 50,
+75, 25,
+50, 50,
+50, 100,
+0, 100,
diff --git a/C#/GuiDemo/GuiDemo.sln b/C#/GuiDemo/GuiDemo.sln
new file mode 100644
index 0000000..d17656f
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo.sln
@@ -0,0 +1,45 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C# Express 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GuiDemo", "GuiDemo\GuiDemo.csproj", "{8BD44147-3290-4A73-BAA2-1C171566BC25}"
+ ProjectSection(ProjectDependencies) = postProject
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66} = {9B062971-A88E-4A3D-B3C9-12B78D15FA66}
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clipper_library", "..\clipper_library\clipper_library.csproj", "{9B062971-A88E-4A3D-B3C9-12B78D15FA66}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|x86.ActiveCfg = Debug|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Debug|x86.Build.0 = Debug|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|Any CPU.ActiveCfg = Release|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|Mixed Platforms.Build.0 = Release|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|x86.ActiveCfg = Release|x86
+ {8BD44147-3290-4A73-BAA2-1C171566BC25}.Release|x86.Build.0 = Release|x86
+ {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}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|x86.ActiveCfg = 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
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|x86.ActiveCfg = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/C#/GuiDemo/GuiDemo/Form1.Designer.cs b/C#/GuiDemo/GuiDemo/Form1.Designer.cs
new file mode 100644
index 0000000..79e0846
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Form1.Designer.cs
@@ -0,0 +1,419 @@
+namespace WindowsFormsApplication1
+{
+ partial class Form1
+ {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
+ this.statusStrip1 = new System.Windows.Forms.StatusStrip();
+ this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
+ this.panel1 = new System.Windows.Forms.Panel();
+ this.bSave = new System.Windows.Forms.Button();
+ this.groupBox3 = new System.Windows.Forms.GroupBox();
+ this.rbNone = new System.Windows.Forms.RadioButton();
+ this.rbXor = new System.Windows.Forms.RadioButton();
+ this.rbDifference = new System.Windows.Forms.RadioButton();
+ this.rbUnion = new System.Windows.Forms.RadioButton();
+ this.rbIntersect = new System.Windows.Forms.RadioButton();
+ this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.rbTest2 = new System.Windows.Forms.RadioButton();
+ this.rbTest1 = new System.Windows.Forms.RadioButton();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.nudOffset = new System.Windows.Forms.NumericUpDown();
+ this.lblCount = new System.Windows.Forms.Label();
+ this.nudCount = new System.Windows.Forms.NumericUpDown();
+ this.rbNonZero = new System.Windows.Forms.RadioButton();
+ this.rbEvenOdd = new System.Windows.Forms.RadioButton();
+ this.bRefresh = new System.Windows.Forms.Button();
+ this.bCancel = new System.Windows.Forms.Button();
+ this.panel2 = new System.Windows.Forms.Panel();
+ this.pictureBox1 = new System.Windows.Forms.PictureBox();
+ this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog();
+ this.statusStrip1.SuspendLayout();
+ this.panel1.SuspendLayout();
+ this.groupBox3.SuspendLayout();
+ this.groupBox2.SuspendLayout();
+ this.groupBox1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.nudOffset)).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)(this.nudCount)).BeginInit();
+ this.panel2.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
+ this.SuspendLayout();
+ //
+ // statusStrip1
+ //
+ this.statusStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible;
+ this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.toolStripStatusLabel1});
+ this.statusStrip1.Location = new System.Drawing.Point(0, 459);
+ this.statusStrip1.Name = "statusStrip1";
+ this.statusStrip1.Size = new System.Drawing.Size(716, 22);
+ this.statusStrip1.TabIndex = 4;
+ //
+ // toolStripStatusLabel1
+ //
+ this.toolStripStatusLabel1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
+ this.toolStripStatusLabel1.Size = new System.Drawing.Size(0, 17);
+ //
+ // panel1
+ //
+ this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+ this.panel1.Controls.Add(this.bSave);
+ this.panel1.Controls.Add(this.groupBox3);
+ this.panel1.Controls.Add(this.groupBox2);
+ this.panel1.Controls.Add(this.groupBox1);
+ this.panel1.Controls.Add(this.bRefresh);
+ this.panel1.Controls.Add(this.bCancel);
+ this.panel1.Dock = System.Windows.Forms.DockStyle.Left;
+ this.panel1.Location = new System.Drawing.Point(0, 0);
+ this.panel1.Name = "panel1";
+ this.panel1.Size = new System.Drawing.Size(121, 459);
+ this.panel1.TabIndex = 5;
+ //
+ // bSave
+ //
+ this.bSave.Location = new System.Drawing.Point(9, 412);
+ this.bSave.Name = "bSave";
+ this.bSave.Size = new System.Drawing.Size(100, 25);
+ this.bSave.TabIndex = 9;
+ this.bSave.Text = "S&ave as SVG File";
+ this.bSave.UseVisualStyleBackColor = true;
+ this.bSave.Click += new System.EventHandler(this.bSave_Click);
+ //
+ // groupBox3
+ //
+ this.groupBox3.Controls.Add(this.rbNone);
+ this.groupBox3.Controls.Add(this.rbXor);
+ this.groupBox3.Controls.Add(this.rbDifference);
+ this.groupBox3.Controls.Add(this.rbUnion);
+ this.groupBox3.Controls.Add(this.rbIntersect);
+ this.groupBox3.Location = new System.Drawing.Point(9, 12);
+ this.groupBox3.Name = "groupBox3";
+ this.groupBox3.Size = new System.Drawing.Size(100, 125);
+ this.groupBox3.TabIndex = 5;
+ this.groupBox3.TabStop = false;
+ this.groupBox3.Text = "&Boolean Op:";
+ //
+ // rbNone
+ //
+ this.rbNone.AutoSize = true;
+ this.rbNone.Location = new System.Drawing.Point(14, 100);
+ this.rbNone.Name = "rbNone";
+ this.rbNone.Size = new System.Drawing.Size(51, 17);
+ this.rbNone.TabIndex = 4;
+ this.rbNone.Text = "None";
+ this.rbNone.UseVisualStyleBackColor = true;
+ this.rbNone.CheckedChanged += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // rbXor
+ //
+ this.rbXor.AutoSize = true;
+ this.rbXor.Location = new System.Drawing.Point(14, 81);
+ this.rbXor.Name = "rbXor";
+ this.rbXor.Size = new System.Drawing.Size(48, 17);
+ this.rbXor.TabIndex = 3;
+ this.rbXor.Text = "XOR";
+ this.rbXor.UseVisualStyleBackColor = true;
+ this.rbXor.CheckedChanged += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // rbDifference
+ //
+ this.rbDifference.AutoSize = true;
+ this.rbDifference.Location = new System.Drawing.Point(14, 60);
+ this.rbDifference.Name = "rbDifference";
+ this.rbDifference.Size = new System.Drawing.Size(74, 17);
+ this.rbDifference.TabIndex = 2;
+ this.rbDifference.Text = "Difference";
+ this.rbDifference.UseVisualStyleBackColor = true;
+ this.rbDifference.CheckedChanged += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // rbUnion
+ //
+ this.rbUnion.AutoSize = true;
+ this.rbUnion.Location = new System.Drawing.Point(14, 39);
+ this.rbUnion.Name = "rbUnion";
+ this.rbUnion.Size = new System.Drawing.Size(53, 17);
+ this.rbUnion.TabIndex = 1;
+ this.rbUnion.Text = "Union";
+ this.rbUnion.UseVisualStyleBackColor = true;
+ this.rbUnion.CheckedChanged += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // rbIntersect
+ //
+ this.rbIntersect.AutoSize = true;
+ this.rbIntersect.Checked = true;
+ this.rbIntersect.Location = new System.Drawing.Point(14, 19);
+ this.rbIntersect.Name = "rbIntersect";
+ this.rbIntersect.Size = new System.Drawing.Size(66, 17);
+ this.rbIntersect.TabIndex = 0;
+ this.rbIntersect.TabStop = true;
+ this.rbIntersect.Text = "Intersect";
+ this.rbIntersect.UseVisualStyleBackColor = true;
+ this.rbIntersect.CheckedChanged += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // groupBox2
+ //
+ this.groupBox2.Controls.Add(this.rbTest2);
+ this.groupBox2.Controls.Add(this.rbTest1);
+ this.groupBox2.Location = new System.Drawing.Point(9, 310);
+ this.groupBox2.Name = "groupBox2";
+ this.groupBox2.Size = new System.Drawing.Size(100, 61);
+ this.groupBox2.TabIndex = 7;
+ this.groupBox2.TabStop = false;
+ this.groupBox2.Text = "Sample";
+ //
+ // rbTest2
+ //
+ this.rbTest2.AutoSize = true;
+ this.rbTest2.Location = new System.Drawing.Point(14, 35);
+ this.rbTest2.Name = "rbTest2";
+ this.rbTest2.Size = new System.Drawing.Size(46, 17);
+ this.rbTest2.TabIndex = 1;
+ this.rbTest2.Text = "&Two";
+ this.rbTest2.UseVisualStyleBackColor = true;
+ this.rbTest2.Click += new System.EventHandler(this.rbTest1_Click);
+ //
+ // rbTest1
+ //
+ this.rbTest1.AutoSize = true;
+ this.rbTest1.Checked = true;
+ this.rbTest1.Location = new System.Drawing.Point(14, 17);
+ this.rbTest1.Name = "rbTest1";
+ this.rbTest1.Size = new System.Drawing.Size(45, 17);
+ this.rbTest1.TabIndex = 0;
+ this.rbTest1.TabStop = true;
+ this.rbTest1.Text = "&One";
+ this.rbTest1.UseVisualStyleBackColor = true;
+ this.rbTest1.Click += new System.EventHandler(this.rbTest1_Click);
+ //
+ // groupBox1
+ //
+ this.groupBox1.Controls.Add(this.label2);
+ this.groupBox1.Controls.Add(this.nudOffset);
+ this.groupBox1.Controls.Add(this.lblCount);
+ this.groupBox1.Controls.Add(this.nudCount);
+ this.groupBox1.Controls.Add(this.rbNonZero);
+ this.groupBox1.Controls.Add(this.rbEvenOdd);
+ this.groupBox1.Location = new System.Drawing.Point(9, 144);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Size = new System.Drawing.Size(100, 159);
+ this.groupBox1.TabIndex = 6;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "Options:";
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(11, 108);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(38, 13);
+ this.label2.TabIndex = 4;
+ this.label2.Text = "O&ffset:";
+ //
+ // nudOffset
+ //
+ this.nudOffset.DecimalPlaces = 1;
+ this.nudOffset.Location = new System.Drawing.Point(14, 126);
+ this.nudOffset.Maximum = new decimal(new int[] {
+ 10,
+ 0,
+ 0,
+ 0});
+ this.nudOffset.Minimum = new decimal(new int[] {
+ 10,
+ 0,
+ 0,
+ -2147483648});
+ this.nudOffset.Name = "nudOffset";
+ this.nudOffset.Size = new System.Drawing.Size(54, 20);
+ this.nudOffset.TabIndex = 5;
+ this.nudOffset.ValueChanged += new System.EventHandler(this.nudCount_ValueChanged);
+ //
+ // lblCount
+ //
+ this.lblCount.AutoSize = true;
+ this.lblCount.Location = new System.Drawing.Point(11, 62);
+ this.lblCount.Name = "lblCount";
+ this.lblCount.Size = new System.Drawing.Size(71, 13);
+ this.lblCount.TabIndex = 2;
+ this.lblCount.Text = "Vertex &Count:";
+ //
+ // nudCount
+ //
+ this.nudCount.Location = new System.Drawing.Point(14, 80);
+ this.nudCount.Minimum = new decimal(new int[] {
+ 3,
+ 0,
+ 0,
+ 0});
+ this.nudCount.Name = "nudCount";
+ this.nudCount.Size = new System.Drawing.Size(54, 20);
+ this.nudCount.TabIndex = 3;
+ this.nudCount.Value = new decimal(new int[] {
+ 50,
+ 0,
+ 0,
+ 0});
+ this.nudCount.ValueChanged += new System.EventHandler(this.bRefresh_Click);
+ //
+ // rbNonZero
+ //
+ this.rbNonZero.AutoSize = true;
+ this.rbNonZero.Checked = true;
+ this.rbNonZero.Location = new System.Drawing.Point(14, 39);
+ this.rbNonZero.Name = "rbNonZero";
+ this.rbNonZero.Size = new System.Drawing.Size(67, 17);
+ this.rbNonZero.TabIndex = 1;
+ this.rbNonZero.TabStop = true;
+ this.rbNonZero.Text = "Non&Zero";
+ this.rbNonZero.UseVisualStyleBackColor = true;
+ this.rbNonZero.Click += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // rbEvenOdd
+ //
+ this.rbEvenOdd.AutoSize = true;
+ this.rbEvenOdd.Location = new System.Drawing.Point(14, 21);
+ this.rbEvenOdd.Name = "rbEvenOdd";
+ this.rbEvenOdd.Size = new System.Drawing.Size(70, 17);
+ this.rbEvenOdd.TabIndex = 0;
+ this.rbEvenOdd.Text = "&EvenOdd";
+ this.rbEvenOdd.UseVisualStyleBackColor = true;
+ this.rbEvenOdd.Click += new System.EventHandler(this.rbNonZero_Click);
+ //
+ // bRefresh
+ //
+ this.bRefresh.Location = new System.Drawing.Point(9, 381);
+ this.bRefresh.Name = "bRefresh";
+ this.bRefresh.Size = new System.Drawing.Size(100, 25);
+ this.bRefresh.TabIndex = 8;
+ this.bRefresh.Text = "&New Sample";
+ this.bRefresh.UseVisualStyleBackColor = true;
+ this.bRefresh.Click += new System.EventHandler(this.bRefresh_Click);
+ //
+ // bCancel
+ //
+ this.bCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.bCancel.Location = new System.Drawing.Point(9, 458);
+ this.bCancel.Name = "bCancel";
+ this.bCancel.Size = new System.Drawing.Size(100, 27);
+ this.bCancel.TabIndex = 11;
+ this.bCancel.Text = "E&xit";
+ this.bCancel.UseVisualStyleBackColor = true;
+ this.bCancel.Click += new System.EventHandler(this.bClose_Click);
+ //
+ // panel2
+ //
+ this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+ this.panel2.Controls.Add(this.pictureBox1);
+ this.panel2.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.panel2.Location = new System.Drawing.Point(121, 0);
+ this.panel2.Name = "panel2";
+ this.panel2.Size = new System.Drawing.Size(595, 459);
+ this.panel2.TabIndex = 6;
+ //
+ // pictureBox1
+ //
+ this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.pictureBox1.Location = new System.Drawing.Point(0, 0);
+ this.pictureBox1.Name = "pictureBox1";
+ this.pictureBox1.Size = new System.Drawing.Size(591, 455);
+ this.pictureBox1.TabIndex = 1;
+ this.pictureBox1.TabStop = false;
+ this.pictureBox1.DoubleClick += new System.EventHandler(this.bRefresh_Click);
+ //
+ // saveFileDialog1
+ //
+ this.saveFileDialog1.DefaultExt = "svg";
+ this.saveFileDialog1.Filter = "SVG Files (*.svg)|*.svg";
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(716, 481);
+ this.Controls.Add(this.panel2);
+ this.Controls.Add(this.panel1);
+ this.Controls.Add(this.statusStrip1);
+ this.DoubleBuffered = true;
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+ this.KeyPreview = true;
+ this.Name = "Form1";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "Clipper C# Demo1";
+ this.Load += new System.EventHandler(this.Form1_Load);
+ this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
+ this.Resize += new System.EventHandler(this.Form1_Resize);
+ this.statusStrip1.ResumeLayout(false);
+ this.statusStrip1.PerformLayout();
+ this.panel1.ResumeLayout(false);
+ this.groupBox3.ResumeLayout(false);
+ this.groupBox3.PerformLayout();
+ this.groupBox2.ResumeLayout(false);
+ this.groupBox2.PerformLayout();
+ this.groupBox1.ResumeLayout(false);
+ this.groupBox1.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.nudOffset)).EndInit();
+ ((System.ComponentModel.ISupportInitialize)(this.nudCount)).EndInit();
+ this.panel2.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.StatusStrip statusStrip1;
+ private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;
+ private System.Windows.Forms.Panel panel1;
+ private System.Windows.Forms.GroupBox groupBox3;
+ private System.Windows.Forms.RadioButton rbNone;
+ private System.Windows.Forms.RadioButton rbXor;
+ private System.Windows.Forms.RadioButton rbDifference;
+ private System.Windows.Forms.RadioButton rbUnion;
+ private System.Windows.Forms.RadioButton rbIntersect;
+ private System.Windows.Forms.GroupBox groupBox2;
+ private System.Windows.Forms.RadioButton rbTest2;
+ private System.Windows.Forms.RadioButton rbTest1;
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.NumericUpDown nudOffset;
+ private System.Windows.Forms.Label lblCount;
+ private System.Windows.Forms.NumericUpDown nudCount;
+ private System.Windows.Forms.RadioButton rbNonZero;
+ private System.Windows.Forms.RadioButton rbEvenOdd;
+ private System.Windows.Forms.Button bRefresh;
+ private System.Windows.Forms.Button bCancel;
+ private System.Windows.Forms.Panel panel2;
+ private System.Windows.Forms.PictureBox pictureBox1;
+ private System.Windows.Forms.Button bSave;
+ private System.Windows.Forms.SaveFileDialog saveFileDialog1;
+ }
+}
+
diff --git a/C#/GuiDemo/GuiDemo/Form1.cs b/C#/GuiDemo/GuiDemo/Form1.cs
new file mode 100644
index 0000000..ec2cd66
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Form1.cs
@@ -0,0 +1,682 @@
+//#define UsePolyTree
+
+using System;
+using System.Diagnostics;
+using System.Text;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Reflection;
+using System.Linq;
+using System.Windows.Forms;
+using System.Globalization;
+using ClipperLib;
+
+namespace WindowsFormsApplication1
+{
+ using Polygon = List<IntPoint>;
+ using Polygons = List<List<IntPoint>>;
+
+ public partial class Form1 : Form
+ {
+ private Bitmap mybitmap;
+ private Polygons subjects = new Polygons();
+ private Polygons clips = new Polygons();
+ private Polygons solution = new Polygons();
+#if UsePolyTree
+ private PolyTree solutionTree = new PolyTree();
+#endif
+ //Here we are scaling all coordinates up by 100 when they're passed to Clipper
+ //via Polygon (or Polygons) objects because Clipper no longer accepts floating
+ //point values. Likewise when Clipper returns a solution in a Polygons object,
+ //we need to scale down these returned values by the same amount before displaying.
+ private float scale = 100; //or 1 or 10 or 10000 etc for lesser or greater precision.
+
+ //------------------------------------------------------------------------------
+ //---------------------------------------------------------------------
+
+ //a very simple class that builds an SVG file with any number of
+ //polygons of the specified formats ...
+ class SVGBuilder
+ {
+ public class StyleInfo
+ {
+ public PolyFillType pft;
+ public Color brushClr;
+ public Color penClr;
+ public double penWidth;
+ public int[] dashArray;
+ public Boolean showCoords;
+ public StyleInfo Clone()
+ {
+ StyleInfo si = new StyleInfo();
+ si.pft = this.pft;
+ si.brushClr = this.brushClr;
+ si.dashArray = this.dashArray;
+ si.penClr = this.penClr;
+ si.penWidth = this.penWidth;
+ si.showCoords = this.showCoords;
+ return si;
+ }
+ public StyleInfo()
+ {
+ pft = PolyFillType.pftNonZero;
+ brushClr = Color.AntiqueWhite;
+ dashArray = null;
+ penClr = Color.Black;
+ penWidth = 0.8;
+ showCoords = false;
+ }
+ }
+
+ public class PolyInfo
+ {
+ public Polygons polygons;
+ public StyleInfo si;
+ }
+
+ public StyleInfo style;
+ private List<PolyInfo> PolyInfoList;
+ const string svg_header = "<?xml version=\"1.0\" standalone=\"no\"?>\n" +
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" +
+ "\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n\n" +
+ "<svg width=\"{0}px\" height=\"{1}px\" viewBox=\"0 0 {2} {3}\" " +
+ "version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n\n";
+ const string svg_path_format = "\"\n style=\"fill:{0};" +
+ " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" +
+ " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n";
+
+ public SVGBuilder()
+ {
+ PolyInfoList = new List<PolyInfo>();
+ style = new StyleInfo();
+ }
+
+ public void AddPolygons(Polygons poly)
+ {
+ if (poly.Count == 0) return;
+ PolyInfo pi = new PolyInfo();
+ pi.polygons = poly;
+ pi.si = style.Clone();
+ PolyInfoList.Add(pi);
+ }
+
+ public Boolean SaveToFile(string filename, double scale = 1.0, int margin = 10)
+ {
+ if (scale == 0) scale = 1.0;
+ if (margin < 0) margin = 0;
+
+ //calculate the bounding rect ...
+ int i = 0, j = 0;
+ while (i < PolyInfoList.Count)
+ {
+ j = 0;
+ while (j < PolyInfoList[i].polygons.Count &&
+ PolyInfoList[i].polygons[j].Count == 0) j++;
+ if (j < PolyInfoList[i].polygons.Count) break;
+ i++;
+ }
+ if (i == PolyInfoList.Count) return false;
+ IntRect rec = new IntRect();
+ rec.left = PolyInfoList[i].polygons[j][0].X;
+ rec.right = rec.left;
+ rec.top = PolyInfoList[0].polygons[j][0].Y;
+ rec.bottom = rec.top;
+
+ for (; i < PolyInfoList.Count; i++)
+ {
+ foreach (Polygon pg in PolyInfoList[i].polygons)
+ foreach (IntPoint pt in pg)
+ {
+ if (pt.X < rec.left) rec.left = pt.X;
+ else if (pt.X > rec.right) rec.right = pt.X;
+ if (pt.Y < rec.top) rec.top = pt.Y;
+ else if (pt.Y > rec.bottom) rec.bottom = pt.Y;
+ }
+ }
+
+ rec.left = (Int64)(rec.left * scale);
+ rec.top = (Int64)(rec.top * scale);
+ rec.right = (Int64)(rec.right * scale);
+ rec.bottom = (Int64)(rec.bottom * scale);
+ Int64 offsetX = -rec.left + margin;
+ Int64 offsetY = -rec.top + margin;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ writer.Write(svg_header,
+ (rec.right - rec.left) + margin * 2,
+ (rec.bottom - rec.top) + margin * 2,
+ (rec.right - rec.left) + margin * 2,
+ (rec.bottom - rec.top) + margin * 2);
+
+ foreach (PolyInfo pi in PolyInfoList)
+ {
+ writer.Write(" <path d=\"");
+ foreach (Polygon p in pi.polygons)
+ {
+ if (p.Count < 3) continue;
+ writer.Write(String.Format(NumberFormatInfo.InvariantInfo, " M {0:f2} {1:f2}",
+ (double)((double)p[0].X * scale + offsetX),
+ (double)((double)p[0].Y * scale + offsetY)));
+ for (int k = 1; k < p.Count; k++)
+ {
+ writer.Write(String.Format(NumberFormatInfo.InvariantInfo, " L {0:f2} {1:f2}",
+ (double)((double)p[k].X * scale + offsetX),
+ (double)((double)p[k].Y * scale + offsetY)));
+ }
+ writer.Write(" z");
+ }
+
+ writer.Write(String.Format(NumberFormatInfo.InvariantInfo, svg_path_format,
+ ColorTranslator.ToHtml(pi.si.brushClr),
+ (float)pi.si.brushClr.A / 255,
+ (pi.si.pft == PolyFillType.pftEvenOdd ? "evenodd" : "nonzero"),
+ ColorTranslator.ToHtml(pi.si.penClr),
+ (float)pi.si.penClr.A / 255,
+ pi.si.penWidth));
+
+ if (pi.si.showCoords)
+ {
+ writer.Write("<g font-family=\"Verdana\" font-size=\"11\" fill=\"black\">\n\n");
+ foreach (Polygon p in pi.polygons)
+ {
+ foreach (IntPoint pt in p)
+ {
+ Int64 x = pt.X;
+ Int64 y = pt.Y;
+ writer.Write(String.Format(
+ "<text x=\"{0}\" y=\"{1}\">{2},{3}</text>\n",
+ (int)(x * scale + offsetX), (int)(y * scale + offsetY), x, y));
+
+ }
+ writer.Write("\n");
+ }
+ writer.Write("</g>\n");
+ }
+ }
+ writer.Write("</svg>\n");
+ }
+ return true;
+ }
+ }
+
+ //------------------------------------------------------------------------------
+ //------------------------------------------------------------------------------
+
+ static private PointF[] PolygonToPointFArray(Polygon pg, float scale)
+ {
+ PointF[] result = new PointF[pg.Count];
+ for (int i = 0; i < pg.Count; ++i)
+ {
+ result[i].X = (float)pg[i].X / scale;
+ result[i].Y = (float)pg[i].Y / scale;
+ }
+ return result;
+ }
+
+ public Form1()
+ {
+ InitializeComponent();
+ this.MouseWheel += new MouseEventHandler(Form1_MouseWheel);
+ mybitmap = new Bitmap(
+ pictureBox1.ClientRectangle.Width,
+ pictureBox1.ClientRectangle.Height,
+ PixelFormat.Format32bppArgb);
+ }
+ //---------------------------------------------------------------------
+
+ private void Form1_MouseWheel(object sender, MouseEventArgs e)
+ {
+ if (e.Delta > 0 && nudOffset.Value < 10) nudOffset.Value += (decimal)0.5;
+ else if (e.Delta < 0 && nudOffset.Value > -10) nudOffset.Value -= (decimal)0.5;
+ }
+ //---------------------------------------------------------------------
+
+ private void bRefresh_Click(object sender, EventArgs e)
+ {
+ DrawBitmap();
+ }
+ //---------------------------------------------------------------------
+
+ private void GenerateAustPlusRandomEllipses(int count)
+ {
+ subjects.Clear();
+ //load map of Australia from resource ...
+ Assembly _assembly = Assembly.GetExecutingAssembly();
+ using (BinaryReader polyStream = new BinaryReader(_assembly.GetManifestResourceStream("GuiDemo.aust.bin")))
+ {
+ int polyCnt = polyStream.ReadInt32();
+ for (int i = 0; i < polyCnt; ++i)
+ {
+ int vertCnt = polyStream.ReadInt32();
+ Polygon pg = new Polygon(vertCnt);
+ for (int j = 0; j < vertCnt; ++j)
+ {
+ float x = polyStream.ReadSingle() * scale;
+ float y = polyStream.ReadSingle() * scale;
+ pg.Add(new IntPoint((int)x, (int)y));
+ }
+ subjects.Add(pg);
+ }
+ }
+ clips.Clear();
+ Random rand = new Random();
+ using (GraphicsPath path = new GraphicsPath())
+ {
+ const int ellipse_size = 100, margin = 10;
+ for (int i = 0; i < count; ++i)
+ {
+ int w = pictureBox1.ClientRectangle.Width - ellipse_size - margin * 2;
+ int h = pictureBox1.ClientRectangle.Height - ellipse_size - margin * 2 - statusStrip1.Height;
+
+ int x = rand.Next(w) + margin;
+ int y = rand.Next(h) + margin;
+ int size = rand.Next(ellipse_size - 20) + 20;
+ path.Reset();
+ path.AddEllipse(x, y, size, size);
+ path.Flatten();
+ Polygon clip = new Polygon(path.PathPoints.Count());
+ foreach (PointF p in path.PathPoints)
+ clip.Add(new IntPoint((int)(p.X * scale), (int)(p.Y * scale)));
+ clips.Add(clip);
+ }
+ }
+ }
+ //---------------------------------------------------------------------
+
+ private IntPoint GenerateRandomPoint(int l, int t, int r, int b, Random rand)
+ {
+ int Q = 10;
+ return new IntPoint(
+ Convert.ToInt64((rand.Next(r / Q) * Q + l + 10) * scale),
+ Convert.ToInt64((rand.Next(b / Q) * Q + t + 10) * scale));
+ }
+ //---------------------------------------------------------------------
+
+ private void GenerateRandomPolygon(int count)
+ {
+ int Q = 10;
+ Random rand = new Random();
+ int l = 10;
+ int t = 10;
+ int r = (pictureBox1.ClientRectangle.Width - 20) / Q * Q;
+ int b = (pictureBox1.ClientRectangle.Height - 20) / Q * Q;
+
+ subjects.Clear();
+ clips.Clear();
+
+ Polygon subj = new Polygon(count);
+ for (int i = 0; i < count; ++i)
+ subj.Add(GenerateRandomPoint(l, t, r, b, rand));
+ subjects.Add(subj);
+
+ Polygon clip = new Polygon(count);
+ for (int i = 0; i < count; ++i)
+ clip.Add(GenerateRandomPoint(l, t, r, b, rand));
+ clips.Add(clip);
+ }
+ //---------------------------------------------------------------------
+
+ ClipType GetClipType()
+ {
+ if (rbIntersect.Checked) return ClipType.ctIntersection;
+ if (rbUnion.Checked) return ClipType.ctUnion;
+ if (rbDifference.Checked) return ClipType.ctDifference;
+ else return ClipType.ctXor;
+ }
+ //---------------------------------------------------------------------
+
+ PolyFillType GetPolyFillType()
+ {
+ if (rbNonZero.Checked) return PolyFillType.pftNonZero;
+ else return PolyFillType.pftEvenOdd;
+ }
+ //---------------------------------------------------------------------
+
+ bool LoadFromFile(string filename, Polygons ppg, double scale = 0,
+ int xOffset = 0, int yOffset = 0)
+ {
+ double scaling = Math.Pow(10, scale);
+ ppg.Clear();
+ if (!File.Exists(filename)) return false;
+ using (StreamReader sr = new StreamReader(filename))
+ {
+ string line;
+ if ((line = sr.ReadLine()) == null)
+ return false;
+ int polyCnt, vertCnt;
+ if (!Int32.TryParse(line, out polyCnt) || polyCnt < 0)
+ return false;
+ ppg.Capacity = polyCnt;
+ for (int i = 0; i < polyCnt; i++)
+ {
+ if ((line = sr.ReadLine()) == null)
+ return false;
+ if (!Int32.TryParse(line, out vertCnt) || vertCnt < 0)
+ return false;
+ Polygon pg = new Polygon(vertCnt);
+ ppg.Add(pg);
+ for (int j = 0; j < vertCnt; j++)
+ {
+ double x, y;
+ if ((line = sr.ReadLine()) == null)
+ return false;
+ char[] delimiters = new char[] { ',', ' ' };
+ string[] vals = line.Split(delimiters);
+ if (vals.Length < 2)
+ return false;
+ if (!double.TryParse(vals[0], out x))
+ return false;
+ if (!double.TryParse(vals[1], out y))
+ if (vals.Length < 2 || !double.TryParse(vals[2], out y))
+ return false;
+ x = x * scaling + xOffset;
+ y = y * scaling + yOffset;
+ pg.Add(new IntPoint((int)Math.Round(x), (int)Math.Round(y)));
+ }
+ }
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------
+
+ void SaveToFile(string filename, Polygons ppg, int scale = 0)
+ {
+ double scaling = Math.Pow(10, scale);
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ writer.Write("{0}\n", ppg.Count);
+ foreach (Polygon pg in ppg)
+ {
+ writer.Write("{0}\n", pg.Count);
+ foreach (IntPoint ip in pg)
+ writer.Write("{0:0.0000}, {1:0.0000}\n", ip.X / scaling, ip.Y / scaling);
+ }
+ }
+ }
+ //---------------------------------------------------------------------------
+
+ private void DrawBitmap(bool justClip = false)
+ {
+ Cursor.Current = Cursors.WaitCursor;
+ try
+ {
+ if (!justClip)
+ {
+ if (rbTest2.Checked)
+ GenerateAustPlusRandomEllipses((int)nudCount.Value);
+ else
+ GenerateRandomPolygon((int)nudCount.Value);
+ }
+ using (Graphics newgraphic = Graphics.FromImage(mybitmap))
+ using (GraphicsPath path = new GraphicsPath())
+ {
+ newgraphic.SmoothingMode = SmoothingMode.AntiAlias;
+ newgraphic.Clear(Color.White);
+ if (rbNonZero.Checked)
+ path.FillMode = FillMode.Winding;
+
+ //draw subjects ...
+ foreach (Polygon pg in subjects)
+ {
+ PointF[] pts = PolygonToPointFArray(pg, scale);
+ path.AddPolygon(pts);
+ pts = null;
+ }
+ using (Pen myPen = new Pen(Color.FromArgb(196, 0xC3, 0xC9, 0xCF), (float)0.6))
+ using (SolidBrush myBrush = new SolidBrush(Color.FromArgb(127, 0xDD, 0xDD, 0xF0)))
+ {
+ newgraphic.FillPath(myBrush, path);
+ newgraphic.DrawPath(myPen, path);
+ path.Reset();
+
+ //draw clips ...
+ if (rbNonZero.Checked)
+ path.FillMode = FillMode.Winding;
+ foreach (Polygon pg in clips)
+ {
+ PointF[] pts = PolygonToPointFArray(pg, scale);
+ path.AddPolygon(pts);
+ pts = null;
+ }
+ myPen.Color = Color.FromArgb(196, 0xF9, 0xBE, 0xA6);
+ myBrush.Color = Color.FromArgb(127, 0xFF, 0xE0, 0xE0);
+ newgraphic.FillPath(myBrush, path);
+ newgraphic.DrawPath(myPen, path);
+
+ //do the clipping ...
+ if ((clips.Count > 0 || subjects.Count > 0) && !rbNone.Checked)
+ {
+ Polygons solution2 = new Polygons();
+ Clipper c = new Clipper();
+ c.AddPaths(subjects, PolyType.ptSubject, true);
+ c.AddPaths(clips, PolyType.ptClip, true);
+ solution.Clear();
+#if UsePolyTree
+ bool succeeded = c.Execute(GetClipType(), solutionTree, GetPolyFillType(), GetPolyFillType());
+ //nb: we aren't doing anything useful here with solutionTree except to show
+ //that it works. Convert PolyTree back to Polygons structure ...
+ Clipper.PolyTreeToPolygons(solutionTree, solution);
+#else
+ bool succeeded = c.Execute(GetClipType(), solution, GetPolyFillType(), GetPolyFillType());
+#endif
+ if (succeeded)
+ {
+ //SaveToFile("solution", solution);
+ myBrush.Color = Color.Black;
+ path.Reset();
+
+ //It really shouldn't matter what FillMode is used for solution
+ //polygons because none of the solution polygons overlap.
+ //However, FillMode.Winding will show any orientation errors where
+ //holes will be stroked (outlined) correctly but filled incorrectly ...
+ path.FillMode = FillMode.Winding;
+
+ //or for something fancy ...
+
+ if (nudOffset.Value != 0)
+ {
+ ClipperOffset co = new ClipperOffset();
+ co.AddPaths(solution, JoinType.jtRound, EndType.etClosedPolygon);
+ co.Execute(ref solution2, (double)nudOffset.Value * scale);
+ }
+ else
+ solution2 = new Polygons(solution);
+
+ foreach (Polygon pg in solution2)
+ {
+ PointF[] pts = PolygonToPointFArray(pg, scale);
+ if (pts.Count() > 2)
+ path.AddPolygon(pts);
+ pts = null;
+ }
+ myBrush.Color = Color.FromArgb(127, 0x66, 0xEF, 0x7F);
+ myPen.Color = Color.FromArgb(255, 0, 0x33, 0);
+ myPen.Width = 1.0f;
+ newgraphic.FillPath(myBrush, path);
+ newgraphic.DrawPath(myPen, path);
+
+ //now do some fancy testing ...
+ using (Font f = new Font("Arial", 8))
+ using (SolidBrush b = new SolidBrush(Color.Navy))
+ {
+ double subj_area = 0, clip_area = 0, int_area = 0, union_area = 0;
+ c.Clear();
+ c.AddPaths(subjects, PolyType.ptSubject, true);
+ c.Execute(ClipType.ctUnion, solution2, GetPolyFillType(), GetPolyFillType());
+ foreach (Polygon pg in solution2)
+ subj_area += Clipper.Area(pg);
+ c.Clear();
+ c.AddPaths(clips, PolyType.ptClip, true);
+ c.Execute(ClipType.ctUnion, solution2, GetPolyFillType(), GetPolyFillType());
+ foreach (Polygon pg in solution2)
+ clip_area += Clipper.Area(pg);
+ c.AddPaths(subjects, PolyType.ptSubject, true);
+ c.Execute(ClipType.ctIntersection, solution2, GetPolyFillType(), GetPolyFillType());
+ foreach (Polygon pg in solution2)
+ int_area += Clipper.Area(pg);
+ c.Execute(ClipType.ctUnion, solution2, GetPolyFillType(), GetPolyFillType());
+ foreach (Polygon pg in solution2)
+ union_area += Clipper.Area(pg);
+
+ using (StringFormat lftStringFormat = new StringFormat())
+ using (StringFormat rtStringFormat = new StringFormat())
+ {
+ lftStringFormat.Alignment = StringAlignment.Near;
+ lftStringFormat.LineAlignment = StringAlignment.Near;
+ rtStringFormat.Alignment = StringAlignment.Far;
+ rtStringFormat.LineAlignment = StringAlignment.Near;
+ Rectangle rec = new Rectangle(pictureBox1.ClientSize.Width - 108,
+ pictureBox1.ClientSize.Height - 116, 104, 106);
+ newgraphic.FillRectangle(new SolidBrush(Color.FromArgb(196, Color.WhiteSmoke)), rec);
+ newgraphic.DrawRectangle(myPen, rec);
+ rec.Inflate(new Size(-2, 0));
+ newgraphic.DrawString("Areas", f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 14));
+ newgraphic.DrawString("subj: ", f, b, rec, lftStringFormat);
+ newgraphic.DrawString((subj_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 12));
+ newgraphic.DrawString("clip: ", f, b, rec, lftStringFormat);
+ newgraphic.DrawString((clip_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 12));
+ newgraphic.DrawString("intersect: ", f, b, rec, lftStringFormat);
+ newgraphic.DrawString((int_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 12));
+ newgraphic.DrawString("---------", f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 10));
+ newgraphic.DrawString("s + c - i: ", f, b, rec, lftStringFormat);
+ newgraphic.DrawString(((subj_area + clip_area - int_area) / 100000).ToString("0,0"), f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 10));
+ newgraphic.DrawString("---------", f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 10));
+ newgraphic.DrawString("union: ", f, b, rec, lftStringFormat);
+ newgraphic.DrawString((union_area / 100000).ToString("0,0"), f, b, rec, rtStringFormat);
+ rec.Offset(new Point(0, 10));
+ newgraphic.DrawString("---------", f, b, rec, rtStringFormat);
+ }
+ }
+ } //end if succeeded
+ } //end if something to clip
+ pictureBox1.Image = mybitmap;
+ }
+ }
+ }
+ finally
+ {
+ Cursor.Current = Cursors.Default;
+ }
+ }
+ //---------------------------------------------------------------------
+
+ private void Form1_Load(object sender, EventArgs e)
+ {
+ toolStripStatusLabel1.Text =
+ "Tip: Use the mouse-wheel (or +,-,0) to adjust the offset of the solution polygons.";
+ DrawBitmap();
+ }
+ //---------------------------------------------------------------------
+
+ private void bClose_Click(object sender, EventArgs e)
+ {
+ Close();
+ }
+ //---------------------------------------------------------------------
+
+ private void Form1_Resize(object sender, EventArgs e)
+ {
+ if (pictureBox1.ClientRectangle.Width == 0 ||
+ pictureBox1.ClientRectangle.Height == 0) return;
+ if (mybitmap != null)
+ mybitmap.Dispose();
+ mybitmap = new Bitmap(
+ pictureBox1.ClientRectangle.Width,
+ pictureBox1.ClientRectangle.Height,
+ PixelFormat.Format32bppArgb);
+ pictureBox1.Image = mybitmap;
+ DrawBitmap();
+ }
+ //---------------------------------------------------------------------
+
+ private void rbNonZero_Click(object sender, EventArgs e)
+ {
+ DrawBitmap(true);
+ }
+ //---------------------------------------------------------------------
+
+ private void Form1_KeyDown(object sender, KeyEventArgs e)
+ {
+ switch (e.KeyCode)
+ {
+ case Keys.Escape:
+ this.Close();
+ return;
+ case Keys.F1:
+ MessageBox.Show(this.Text + "\nby Angus Johnson\nCopyright © 2010, 2011",
+ this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ e.Handled = true;
+ return;
+ case Keys.Oemplus:
+ case Keys.Add:
+ if (nudOffset.Value == 10) return;
+ nudOffset.Value += (decimal)0.5;
+ e.Handled = true;
+ break;
+ case Keys.OemMinus:
+ case Keys.Subtract:
+ if (nudOffset.Value == -10) return;
+ nudOffset.Value -= (decimal)0.5;
+ e.Handled = true;
+ break;
+ case Keys.NumPad0:
+ case Keys.D0:
+ if (nudOffset.Value == 0) return;
+ nudOffset.Value = (decimal)0;
+ e.Handled = true;
+ break;
+ default: return;
+ }
+
+ }
+ //---------------------------------------------------------------------
+
+ private void nudCount_ValueChanged(object sender, EventArgs e)
+ {
+ DrawBitmap(true);
+ }
+ //---------------------------------------------------------------------
+
+ private void rbTest1_Click(object sender, EventArgs e)
+ {
+ if (rbTest1.Checked)
+ lblCount.Text = "Vertex &Count:";
+ else
+ lblCount.Text = "Ellipse &Count:";
+ DrawBitmap();
+ }
+ //---------------------------------------------------------------------
+
+ private void bSave_Click(object sender, EventArgs e)
+ {
+ //save to SVG ...
+ if (saveFileDialog1.ShowDialog() == DialogResult.OK)
+ {
+ SVGBuilder svg = new SVGBuilder();
+ svg.style.brushClr = Color.FromArgb(0x10, 0, 0, 0x9c);
+ svg.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda);
+ svg.AddPolygons(subjects);
+ svg.style.brushClr = Color.FromArgb(0x10, 0x9c, 0, 0);
+ svg.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a);
+ svg.AddPolygons(clips);
+ svg.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c);
+ svg.style.penClr = Color.FromArgb(0, 0x33, 0);
+ svg.AddPolygons(solution);
+ svg.SaveToFile(saveFileDialog1.FileName, 1.0 / scale);
+ }
+ }
+ //---------------------------------------------------------------------
+
+ }
+}
diff --git a/C#/GuiDemo/GuiDemo/Form1.resx b/C#/GuiDemo/GuiDemo/Form1.resx
new file mode 100644
index 0000000..2f4b5d7
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Form1.resx
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <value>17, 17</value>
+ </metadata>
+ <metadata name="saveFileDialog1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+ <value>127, 17</value>
+ </metadata>
+ <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+ <value>25</value>
+ </metadata>
+ <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+ <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>
+ AAABAAIAICAAAAAAAACoEAAAJgAAABAQAAAAAAAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A72bOwO9EwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9YzsDvf8AAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvTM8Bb35PAS9/AAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRAxPBUQM3wVEDT8FRA0+dPDVGLAWG6zsD
+ vf8+B77jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9AwAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDD8FRA3fBUgXJwVED/8FRA//BUQP/v1AF/y4M
+ RP8AAAD/Eg0m/y4HevTBUQMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAA7A71TOwO9nzsDvT8AAAAAAAAAAAAAAAAAAAAAAAAAAMFRA2vBUgXtwVED/8VhGPzQik7/16Rw/9uz
+ hP9YOWf/AAAA/yZIS/8LFRb/AAAA/6dHBOXBUQNXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAA7A72zOwO9/z0GvuI7A71/NgOsIhMIAFPBUgS0wVED/8hqJP3bs4T/5ty6/+bc
+ uv/m3Lr/i3iT/wICBv8kREf/eOPs/1uttP8AAAD/rlgZ/cFRA//BUQObwVEDAwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvRM8Bb3tOwO9/zsDvf8eAl//AAAA/xsNEf+NZUb/5ty6/+bc
+ uv/m3Lr/5ty6/7ytqf8HBBL/FSUp/3Tc5f955u//W620/wAAAP/Kv6L/0YxR/8FRA//CUwbCwVEDAwAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvVM7A73/VDan/wICA/8cNTj/CxUW/wAA
+ AP8XEiT/gnOB/+bcuv/VyrL/Egon/wkQEv9qytL/eebv/3nm7/9brbT/AAAA/8rBo//m3Lr/2KZz/8FR
+ A//BUQOTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoDuqwOAir/Dhga/3LY
+ 4f924Oj/RIKH/wsVFv8AAAD/EAkl/xsPNv8CAwT/W620/3nm7/955u//eebv/1uttP8AAAD/ysGj/+bc
+ uv/m3Lr/0YxR/8FRA//BUQNLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGggZZgAA
+ AP87cHT/eebv/3nm7/955u//duDo/0SCh/8LFRb/AAAA/0qNkv955u//eebv/3nm7/955u//W620/wAA
+ AP/KwaP/5ty6/+bcuv/m2rj/xmMb/MFSBNjBUQMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAC8TwN5KxER/wUHCv9jvMP/eebv/3nm7/955u//eebv/3bg6P9Voqn/eebv/3nm7/955u//eebv/3nm
+ 7/9brbT/AAAA/8rBo//m3Lr/5ty6/+bcuv/Zqnn/wVED/8FRA1cAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAMFSBcm9aSj6EQ0b/w4XHf9y2OH/eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm
+ 7/955u//eebv/1uttP8AAAD/oI2h/+bcuv/m3Lr/5ty6/+bcuv/EXBL7wVEDpwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAADBUQMTwVED/9SXX/+6r57/BAIJ/yE8Qv944+z/eebv/3nm7/955u//eebv/3nm
+ 7/955u//eebv/3nm7/955u//W620/wAAAP8EBAr/GAs5/1M/cP+xoaT/5ty6/8+FSP/BUgT1AAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRAzvBUQP/27OE/+bcuv+FdYT/AAAA/ztvdP955u//eebv/3nm
+ 7/955u//eebv/3nm7/955u//eebv/3nm7/924Oj/Upuh/y9aXv8NGRr/AAAA/wAAAP8OCxz/Pic9/4M1
+ Dv+uSQMeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDU8FRA//fwpj/5ty6/+TZuv8cDTv/AAAA/2bD
+ yv955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/9lv8f/Qn6D/yA9
+ QP8EBgf/AAAA/zsIlcw7A71bOwO9BwAAAAAAAAAAAAAAAAAAAADBUQNTwVED/9/CmP/m3Lr/Xkh8/wAA
+ AP8xXmH/eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm
+ 7/955u//eebv/1emrP8AAAD/NAOm/zsDvf87A73zOwO9jwAAAAAAAAAAAAAAAMFRAzvBUQP/27OE/4Vw
+ k/8DAwn/ID1A/3jj7P955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm
+ 7/903OX/V6as/zlscP8aMTT/AgME/wAAAP83CJ3POwO9dzsDvTcAAAAAAAAAAAAAAAAAAAAAwVEDE8FR
+ A/+ibGb/BwQS/xMiJf903OX/eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//duDo/1em
+ rP85bHD/GjE0/wIDBP8AAAD/AgED/x4XKv9OLjH/hTgJ+AAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAqUUZ0A0EH/8LExb/aMbO/3nm7/955u//eebv/3nm7/955u//eebv/3nm7/955u//eebv/3nm
+ 7/9brbT/AAAA/wEBA/8bFCr/UENY/5uQhv/c07L/5ty6/8RcEvvBUQOnAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAACkChAoVBS72AgME/12xuP955u//eebv/3nm7/955u//duDo/1+0u/955u//eebv/3nm
+ 7/955u//eebv/1uttP8AAAD/w7qe/+bcuv/m3Lr/5ty6/+bcuv/Zqnn/wVED/8FRA1cAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAIAJnwAAAAP8/d3z/eebv/3nm7/9mw8r/RoWL/yJAQ/8DBgf/AAAA/1Wi
+ qf955u//eebv/3nm7/955u//W620/wAAAP/KwaP/5ty6/+bcuv/m3Lr/5tq4/8ZjG/zBUgTYwVEDAwAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAADsDvXM4A7L/BgMM/xowNP8xXmH/Dhod/wAAAP8AAAD/FRIb/1pS
+ Uv8uKyz/DBQZ/3LY4f955u//eebv/3nm7/9brbT/AAAA/8rBo//m3Lr/5ty6/+bcuv/RjFH/wVED/8FR
+ A0sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A71HPAS9/DsDvf8eAl//AAAA/wIBAv8mDw//dV9L/8a+
+ oP/m3Lr/5ty6/8rBo/8GBgn/LVRZ/3nm7/955u//eebv/1uttP8AAAD/ysGj/+bcuv/m3Lr/2KZz/8FR
+ A//BUQOTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9JzsDvec7A723OwO9aykCgjAAAABXYCgBFsJU
+ BtvBUwX+1Ztl/+bcuv/m3Lr/5ty6/4N8bP8AAAD/VaKp/3nm7/955u//W620/wAAAP/KwaP/5tq4/9GO
+ U//BUQP/wlMGwsFRAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A70jOwO9BwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAwVEDC8FSBLTBUQP/ynMw/t26jf/m3Lr/49m4/yomKf8MFBn/ctjh/3nm7/9brbT/AAAA/8Cb
+ cf/IaiT9wVED/8FRA5vBUQMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRA2vBUgXxwVED/8ZjG/zQik7/totf/wQDBv8pTVL/W620/0SC
+ h/8AAAD/qkcD/8FTBebBUQNXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMFRAw/BUQN3wVIFycFRA//BUQP/ZysG/wAA
+ AP8AAAD/AAAA/wAAAP+RPQJ9wVEDBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDE8FR
+ Azu6TgNVSBdAnywCjv8sAo7/JAJynwAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAOwO9nzsDvf87A71/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A70PPAS95DsDvXsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A71TOwO9XwAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//8////+f////H///4B/+/wAP/j4AB/8AA
+ AH/AAAA/4AAAP/AAAB/wAAAP8AAAD/AAAA/gAAAP4AAAB+AAAAHgAAAA4AAAAeAAAAfwAAAP4AAAD+AA
+ AA/AAAAfgAAAPwAAAD8+AAB//4AB///AA///+Af///+P////j////8//KAAAABAAAAAgAAAAAQAgAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsD
+ vRk4A6/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBUQMFwVEDIrBH
+ GiU3BK3GOASv5gAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9FjgDrts4BK5VAAAAAMFRAxvBUQSdxmQd8c1+
+ P/9RJSz/ERsi/0UZKrjBUQMWAAAAAAAAAAAAAAAAAAAAAAAAAAA4BK/wOASw9xICMJ2GRBns46OF/+Oj
+ hf+NgYL/LFJX/2rJ0f9eRi//xWEZ5sJTBjIAAAAAAAAAAAAAAAAAAAAAOwO9FTURjuooSk7/MV5h/ykn
+ L/96cHL/HjM8/3Xf6P9qytL/ZWFS/+Ojhf/FYRnkwVEDEwAAAAAAAAAAAAAAAAAAAAAyFAq3R4aM/3nm
+ 7/9rzNP/NmZq/23Q2P955u//asrS/2VhUv/jo4X/46OF/8FRA4wAAAAAAAAAAAAAAADBUQMFxWol8Dc1
+ OP9ht8D/eebv/3nm7/955u//eebv/2rK0v8pJCv/joGH/+Ojhf/GYxvmAAAAAAAAAAAAAAAAwVEDJM+G
+ Sf/jo4X/Fh8s/3Td5v955u//eebv/3nm7/945e3/XbC3/ztwdP8cMjj/MRkV/zgErN87A70CAAAAAMFR
+ AyTPhkn/c2Z1/zJgY/955u//eebv/3nm7/955u//eebv/3nm7/9v1Nz/UZqh/xYqLP84BK7ROAOv5AAA
+ AADBUQMFhUEp8yNAR/945O3/eebv/3nm7/955u//eebv/0qNkv8cLTT/OzY5/3lyZv+SRBXmAAAAAQAA
+ AAAAAAAAIAJoMxYgK/1y2eH/aMXN/0WDiP9Lj5X/eebv/3nm7/8uV1r/46OF/+Ojhf/jo4X/wVEDjAAA
+ AAAAAAAAOwO9EjoDutsQDSj/GiIk/09HO/+Ph3j/Q0I8/2S+xv955u//Llda/+Ojhf/jo4X/xWEZ5MFR
+ AxMAAAAAAAAAADgDrt84A67eDwEuIrlQBT/GZh7s46OF/+Ojhf8jNzv/d+Ps/y5XWv/jo4X/xWIZ5sJT
+ BjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDG8FRBJ7GZR7xeUMc/yE/Qv8RISL/rkoEmsFR
+ AxYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwVEDBb1PAyQ4BKnqNwOr9AAA
+ AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwO9BDgD
+ r+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/nwAA/B8AABAPAACABwAAgAMAAMADAACAAwAAgAAAAIAA
+ AACAAQAAgAMAAAADAAAABwAA8A8AAPwfAAD/PwAA
+</value>
+ </data>
+</root>
\ No newline at end of file
diff --git a/C#/GuiDemo/GuiDemo/GuiDemo.csproj b/C#/GuiDemo/GuiDemo/GuiDemo.csproj
new file mode 100644
index 0000000..d592685
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/GuiDemo.csproj
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{8BD44147-3290-4A73-BAA2-1C171566BC25}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>GuiDemo</RootNamespace>
+ <AssemblyName>GuiDemo</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <TargetFrameworkProfile>
+ </TargetFrameworkProfile>
+ <FileAlignment>512</FileAlignment>
+ <IsWebBootstrapper>false</IsWebBootstrapper>
+ <PublishUrl>publish\</PublishUrl>
+ <Install>true</Install>
+ <InstallFrom>Disk</InstallFrom>
+ <UpdateEnabled>false</UpdateEnabled>
+ <UpdateMode>Foreground</UpdateMode>
+ <UpdateInterval>7</UpdateInterval>
+ <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+ <UpdatePeriodically>false</UpdatePeriodically>
+ <UpdateRequired>false</UpdateRequired>
+ <MapFileExtensions>true</MapFileExtensions>
+ <ApplicationRevision>0</ApplicationRevision>
+ <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+ <UseApplicationTrust>false</UseApplicationTrust>
+ <BootstrapperEnabled>true</BootstrapperEnabled>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <PlatformTarget>x86</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <PlatformTarget>x86</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Deployment" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Form1.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ <Compile Include="Form1.Designer.cs">
+ <DependentUpon>Form1.cs</DependentUpon>
+ </Compile>
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Settings.cs" />
+ <EmbeddedResource Include="Form1.resx">
+ <DependentUpon>Form1.cs</DependentUpon>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resources.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ </Compile>
+ <EmbeddedResource Include="aust.bin" />
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
+ <Visible>False</Visible>
+ <ProductName>Microsoft .NET Framework 4 Client Profile %28x86 and x64%29</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+ <Visible>False</Visible>
+ <ProductName>Windows Installer 3.1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\clipper_library\clipper_library.csproj">
+ <Project>{9B062971-A88E-4A3D-B3C9-12B78D15FA66}</Project>
+ <Name>clipper_library</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
diff --git a/C#/GuiDemo/GuiDemo/Program.cs b/C#/GuiDemo/GuiDemo/Program.cs
new file mode 100644
index 0000000..cc4a2c7
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace WindowsFormsApplication1
+{
+ static class Program
+ {
+ /// <summary>
+ /// The main entry point for the application.
+ /// </summary>
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/C#/GuiDemo/GuiDemo/Properties/AssemblyInfo.cs b/C#/GuiDemo/GuiDemo/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..eb3329d
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/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("ClipperCSharpDemo1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Angus Johnson")]
+[assembly: AssemblyProduct("ClipperCSharpDemo1")]
+[assembly: AssemblyCopyright("Copyright © Angus Johnson 2010-14")]
+[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("7eac3b0b-6f6f-4b48-b26b-c7acb1f769f5")]
+
+// 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/C#/GuiDemo/GuiDemo/Properties/Resources.Designer.cs b/C#/GuiDemo/GuiDemo/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..84210c0
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Properties/Resources.Designer.cs
@@ -0,0 +1,70 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.235
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace GuiDemo.Properties {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GuiDemo.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ internal static byte[] aust {
+ get {
+ object obj = ResourceManager.GetObject("aust", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+ }
+}
diff --git a/C#/GuiDemo/GuiDemo/Properties/Resources.resx b/C#/GuiDemo/GuiDemo/Properties/Resources.resx
new file mode 100644
index 0000000..4ab87d2
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Properties/Resources.resx
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+ <data name="aust" type="System.Resources.ResXFileRef, System.Windows.Forms">
+ <value>..\aust.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </data>
+</root>
\ No newline at end of file
diff --git a/C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs b/C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..e31c5b7
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.235
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace GuiDemo.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.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/C#/GuiDemo/GuiDemo/Properties/Settings.settings b/C#/GuiDemo/GuiDemo/Properties/Settings.settings
new file mode 100644
index 0000000..abf36c5
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile>
diff --git a/C#/GuiDemo/GuiDemo/Settings.cs b/C#/GuiDemo/GuiDemo/Settings.cs
new file mode 100644
index 0000000..dfa1e2a
--- /dev/null
+++ b/C#/GuiDemo/GuiDemo/Settings.cs
@@ -0,0 +1,28 @@
+namespace WindowsFormsApplication1.Properties {
+
+
+ // This class allows you to handle specific events on the settings class:
+ // The SettingChanging event is raised before a setting's value is changed.
+ // The PropertyChanged event is raised after a setting's value is changed.
+ // The SettingsLoaded event is raised after the setting values are loaded.
+ // The SettingsSaving event is raised before the setting values are saved.
+ internal sealed partial class Settings {
+
+ public Settings() {
+ // // To add event handlers for saving and changing settings, uncomment the lines below:
+ //
+ // this.SettingChanging += this.SettingChangingEventHandler;
+ //
+ // this.SettingsSaving += this.SettingsSavingEventHandler;
+ //
+ }
+
+ private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
+ // Add code to handle the SettingChangingEvent event here.
+ }
+
+ private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
+ // Add code to handle the SettingsSaving event here.
+ }
+ }
+}
diff --git a/C#/GuiDemo/GuiDemo/aust.bin b/C#/GuiDemo/GuiDemo/aust.bin
new file mode 100644
index 0000000..9d3bd08
Binary files /dev/null and b/C#/GuiDemo/GuiDemo/aust.bin differ
diff --git a/C#/clipper_library/Properties/AssemblyInfo.cs b/C#/clipper_library/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..6586d04
--- /dev/null
+++ b/C#/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("Angus Johnson")]
+[assembly: AssemblyProduct("clipper_library")]
+[assembly: AssemblyCopyright("Copyright © Angus Johnson 2010-14")]
+[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/C#/clipper_library/clipper.cs b/C#/clipper_library/clipper.cs
new file mode 100644
index 0000000..e3613ff
--- /dev/null
+++ b/C#/clipper_library/clipper.cs
@@ -0,0 +1,4839 @@
+/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 6.2.9 *
+* Date : 16 February 2015 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2015 *
+* *
+* 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 open path clipping. Adds a very minor cost to performance.
+#define use_lines
+
+
+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<IntPoint>;
+ using Paths = List<List<IntPoint>>;
+
+ 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<PolyNode> m_AllPolys = new List<PolyNode>();
+
+ //The GC probably handles this cleanup more efficiently ...
+ //~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
+ {
+ int result = m_AllPolys.Count;
+ //with negative offsets, ignore the hidden outer polygon ...
+ if (result > 0 && m_Childs[0] != m_AllPolys[0]) result--;
+ return result;
+ }
+ }
+
+ }
+
+ 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<PolyNode> m_Childs = new List<PolyNode>();
+
+ 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<PolyNode> 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);
+ }
+
+ public static explicit operator double(Int128 val)
+ {
+ const double shift64 = 18446744073709551616.0; //2^64
+ if (val.hi < 0)
+ {
+ if (val.lo == 0)
+ return (double)val.hi * shift64;
+ else
+ return -(double)(~val.lo + ~val.hi * shift64);
+ }
+ else
+ return (double)(val.lo + val.hi * shift64);
+ }
+
+ //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 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();
+ }
+
+ }// end struct IntPoint
+
+ 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 };
+
+ 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<IntersectNode>
+ {
+ public int Compare(IntersectNode node1, IntersectNode node2)
+ {
+ cInt i = node2.Pt.Y - node1.Pt.Y;
+ if (i > 0) return 1;
+ else if (i < 0) return -1;
+ else return 0;
+ }
+ }
+
+ 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 Maxima
+ {
+ internal cInt X;
+ internal Maxima Next;
+ internal Maxima Prev;
+ };
+
+ 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
+ public const cInt loRange = 0x7FFF;
+ public const cInt hiRange = 0x7FFF;
+#else
+ public const cInt loRange = 0x3FFFFFFF;
+ public const cInt hiRange = 0x3FFFFFFFFFFFFFFFL;
+#endif
+
+ internal LocalMinima m_MinimaList;
+ internal LocalMinima m_CurrentLM;
+ internal List<List<TEdge>> m_edges = new List<List<TEdge>>();
+ internal bool m_UseFullRange;
+ internal bool m_HasOpenPaths;
+
+ //------------------------------------------------------------------------------
+
+ public bool PreserveCollinear
+ {
+ get;
+ set;
+ }
+ //------------------------------------------------------------------------------
+
+ public void Swap(ref cInt val1, ref cInt val2)
+ {
+ cInt tmp = val1;
+ val1 = val2;
+ val2 = tmp;
+ }
+ //------------------------------------------------------------------------------
+
+ 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 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 LeftBoundIsForward)
+ {
+ TEdge EStart, Result = E;
+ TEdge Horz;
+
+ if (Result.OutIdx == Skip)
+ {
+ //check if there are edges beyond the skip edge in the bound and if so
+ //create another LocMin and calling ProcessBound once more ...
+ E = Result;
+ if (LeftBoundIsForward)
+ {
+ while (E.Top.Y == E.Next.Bot.Y) E = E.Next;
+ 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 (LeftBoundIsForward) Result = E.Next;
+ else Result = E.Prev;
+ }
+ else
+ {
+ //there are more edges in the bound beyond result starting with E
+ if (LeftBoundIsForward)
+ 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;
+ E.WindDelta = 0;
+ Result = ProcessBound(E, LeftBoundIsForward);
+ InsertLocalMinima(locMin);
+ }
+ return Result;
+ }
+
+ if (E.Dx == horizontal)
+ {
+ //We need to be careful with open paths because this may not be a
+ //true local minima (ie E may be following a skip edge).
+ //Also, consecutive horz. edges may start heading left before going right.
+ if (LeftBoundIsForward) EStart = E.Prev;
+ else EStart = E.Next;
+ if (EStart.Dx == horizontal) //ie an adjoining horizontal skip edge
+ {
+ if (EStart.Bot.X != E.Bot.X && EStart.Top.X != E.Bot.X)
+ ReverseHorizontal(E);
+ }
+ else if (EStart.Bot.X != E.Bot.X)
+ ReverseHorizontal(E);
+ }
+
+ EStart = E;
+ if (LeftBoundIsForward)
+ {
+ 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) 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 ||
+ 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
+ }
+ 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<TEdge> edges = new List<TEdge>(highI+1);
+ for (int i = 0; i <= highI; i++) edges.Add(new TEdge());
+
+ bool IsFlat = true;
+
+ //1. Basic (first) edge initialization ...
+ 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]);
+ }
+ TEdge eStart = edges[0];
+
+ //2. Remove duplicate vertices, and (when closed) collinear edges ...
+ TEdge E = eStart, eLoopStop = eStart;
+ for (;;)
+ {
+ //nb: allows matching start and end points when not Closed ...
+ if (E.Curr == E.Next.Curr && (Closed || E.Next != eStart))
+ {
+ 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) || (!Closed && E.Next == eStart)) break;
+ }
+
+ if ((!Closed && (E == E.Next)) || (Closed && (E.Prev == E.Next)))
+ return false;
+
+ if (!Closed)
+ {
+ m_HasOpenPaths = true;
+ eStart.Prev.OutIdx = Skip;
+ }
+
+ //3. Do second stage of edge initialization ...
+ 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;
+ 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;
+ for ( ; ; )
+ {
+ if (E.Bot.X != E.Prev.Top.X) ReverseHorizontal(E);
+ if (E.Next.OutIdx == Skip) break;
+ E.NextInLML = E.Next;
+ E = E.Next;
+ }
+ InsertLocalMinima(locMin);
+ m_edges.Add(edges);
+ return true;
+ }
+
+ m_edges.Add(edges);
+ bool leftBoundIsForward;
+ TEdge EMin = null;
+
+ //workaround to avoid an endless loop in the while loop below when
+ //open paths have matching start and end points ...
+ if (E.Prev.Bot == E.Prev.Top) E = E.Next;
+
+ 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;
+ leftBoundIsForward = false; //Q.nextInLML = Q.prev
+ } else
+ {
+ locMin.LeftBound = E;
+ locMin.RightBound = E.Prev;
+ leftBoundIsForward = 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, leftBoundIsForward);
+ if (E.OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward);
+
+ TEdge E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
+ if (E2.OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward);
+
+ if (locMin.LeftBound.OutIdx == Skip)
+ locMin.LeftBound = null;
+ else if (locMin.RightBound.OutIdx == Skip)
+ locMin.RightBound = null;
+ InsertLocalMinima(locMin);
+ if (!leftBoundIsForward) 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.]
+ Swap(ref e.Top.X, ref e.Bot.X);
+#if use_xyz
+ Swap(ref e.Top.Z, ref e.Bot.Z);
+#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<OutRec> m_PolyOuts;
+ private ClipType m_ClipType;
+ private Scanbeam m_Scanbeam;
+ private Maxima m_Maxima;
+ private TEdge m_ActiveEdges;
+ private TEdge m_SortedEdges;
+ private List<IntersectNode> m_IntersectList;
+ IComparer<IntersectNode> m_IntersectNodeComparer;
+ private bool m_ExecuteLocked;
+ private PolyFillType m_ClipFillType;
+ private PolyFillType m_SubjFillType;
+ private List<Join> m_Joins;
+ private List<Join> m_GhostJoins;
+ private bool m_UsingPolyTree;
+#if use_xyz
+ public delegate void ZFillCallback(IntPoint bot1, IntPoint top1,
+ IntPoint bot2, IntPoint top2, ref IntPoint pt);
+ public ZFillCallback ZFillFunction { get; set; }
+#endif
+ public Clipper(int InitOptions = 0): base() //constructor
+ {
+ m_Scanbeam = null;
+ m_Maxima = null;
+ m_ActiveEdges = null;
+ m_SortedEdges = null;
+ m_IntersectList = new List<IntersectNode>();
+ m_IntersectNodeComparer = new MyIntersectNodeSort();
+ m_ExecuteLocked = false;
+ m_UsingPolyTree = false;
+ m_PolyOuts = new List<OutRec>();
+ m_Joins = new List<Join>();
+ m_GhostJoins = new List<Join>();
+ ReverseSolution = (ioReverseSolution & InitOptions) != 0;
+ StrictlySimple = (ioStrictlySimple & InitOptions) != 0;
+ PreserveCollinear = (ioPreserveCollinear & InitOptions) != 0;
+#if use_xyz
+ ZFillFunction = null;
+#endif
+ }
+ //------------------------------------------------------------------------------
+
+ private void InsertScanbeam(cInt Y)
+ {
+ //single-linked list: sorted descending, ignoring dups.
+ 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;
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ private void InsertMaxima(cInt X)
+ {
+ //double-linked list: sorted ascending, ignoring dups.
+ Maxima newMax = new Maxima();
+ newMax.X = X;
+ if (m_Maxima == null)
+ {
+ m_Maxima = newMax;
+ m_Maxima.Next = null;
+ m_Maxima.Prev = null;
+ }
+ else if (X < m_Maxima.X)
+ {
+ newMax.Next = m_Maxima;
+ newMax.Prev = null;
+ m_Maxima = newMax;
+ }
+ else
+ {
+ Maxima m = m_Maxima;
+ while (m.Next != null && (X >= m.Next.X)) m = m.Next;
+ if (X == m.X) return; //ie ignores duplicates (& CG to clean up newMax)
+ //insert newMax between m and m.Next ...
+ newMax.Next = m.Next;
+ newMax.Prev = m;
+ if (m.Next != null) m.Next.Prev = newMax;
+ m.Next = newMax;
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ protected override void Reset()
+ {
+ base.Reset();
+ m_Scanbeam = null;
+ m_Maxima = null;
+ m_ActiveEdges = null;
+ m_SortedEdges = null;
+ LocalMinima lm = m_MinimaList;
+ while (lm != null)
+ {
+ InsertScanbeam(lm.Y);
+ lm = lm.Next;
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ public bool ReverseSolution
+ {
+ get;
+ set;
+ }
+ //------------------------------------------------------------------------------
+
+ public bool StrictlySimple
+ {
+ get;
+ set;
+ }
+ //------------------------------------------------------------------------------
+
+ public bool Execute(ClipType clipType, Paths solution,
+ PolyFillType FillType = PolyFillType.pftEvenOdd)
+ {
+ return Execute(clipType, solution, FillType, FillType);
+ }
+ //------------------------------------------------------------------------------
+
+ public bool Execute(ClipType clipType, PolyTree polytree,
+ PolyFillType FillType = PolyFillType.pftEvenOdd)
+ {
+ return Execute(clipType, polytree, FillType, FillType);
+ }
+ //------------------------------------------------------------------------------
+
+ 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 needed for open path clipping.");
+
+ m_ExecuteLocked = true;
+ solution.Clear();
+ m_SubjFillType = subjFillType;
+ m_ClipFillType = clipFillType;
+ m_ClipType = clipType;
+ m_UsingPolyTree = false;
+ bool succeeded;
+ try
+ {
+ succeeded = ExecuteInternal();
+ //build the return polygons ...
+ if (succeeded) BuildResult(solution);
+ }
+ finally
+ {
+ DisposeAllPolyPts();
+ 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;
+ try
+ {
+ succeeded = ExecuteInternal();
+ //build the return polygons ...
+ if (succeeded) BuildResult2(polytree);
+ }
+ finally
+ {
+ DisposeAllPolyPts();
+ m_ExecuteLocked = false;
+ }
+ return succeeded;
+ }
+ //------------------------------------------------------------------------------
+
+ 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);
+ ProcessHorizontals();
+ m_GhostJoins.Clear();
+ if (m_Scanbeam == null) break;
+ cInt topY = PopScanbeam();
+ if (!ProcessIntersections(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)
+ continue;
+ else if (outRec.IsOpen)
+ FixupOutPolyline(outRec);
+ else
+ 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;
+ m_Scanbeam = m_Scanbeam.Next;
+ 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];
+ outRec.Pts = null;
+ outRec = null;
+ m_PolyOuts[index] = 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 e1, TEdge e2)
+ {
+ if (pt.Z != 0 || ZFillFunction == null) return;
+ else if (pt == e1.Bot) pt.Z = e1.Bot.Z;
+ else if (pt == e1.Top) pt.Z = e1.Top.Z;
+ else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
+ else if (pt == e2.Top) pt.Z = e2.Top.Z;
+ else ZFillFunction(e1.Bot, e1.Top, e2.Bot, e2.Top, ref pt);
+ }
+ //------------------------------------------------------------------------------
+#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.X, j.OffPt.X, rb.Bot.X, rb.Top.X))
+ 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)
+ {
+ 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);
+ 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;
+ bool ToFront = (e.Side == EdgeSide.esLeft);
+ 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;
+ return newOp;
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ private OutPt GetLastOutPt(TEdge e)
+ {
+ OutRec outRec = m_PolyOuts[e.OutIdx];
+ if (e.Side == EdgeSide.esLeft)
+ return outRec.Pts;
+ else
+ return outRec.Pts.Prev;
+ }
+ //------------------------------------------------------------------------------
+
+ internal void SwapPoints(ref IntPoint pt1, ref IntPoint pt2)
+ {
+ IntPoint tmp = new IntPoint(pt1);
+ pt1 = pt2;
+ pt2 = tmp;
+ }
+ //------------------------------------------------------------------------------
+
+ private bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b)
+ {
+ if (seg1a > seg1b) Swap(ref seg1a, ref seg1b);
+ if (seg2a > seg2b) Swap(ref seg2a, ref seg2b);
+ return (seg1a < seg2b) && (seg2a < seg1b);
+ }
+ //------------------------------------------------------------------------------
+
+ 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)
+ {
+ //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 e1Contributing = (e1.OutIdx >= 0);
+ bool e2Contributing = (e2.OutIdx >= 0);
+
+#if use_xyz
+ SetZ(ref pt, e1, e2);
+#endif
+
+#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) return;
+ //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;
+ }
+ }
+ 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 ((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))
+ {
+ //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);
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ 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()
+ {
+ TEdge horzEdge = m_SortedEdges;
+ while (horzEdge != null)
+ {
+ DeleteFromSEL(horzEdge);
+ ProcessHorizontal(horzEdge);
+ 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;
+ }
+ }
+ //------------------------------------------------------------------------
+
+ private void ProcessHorizontal(TEdge horzEdge)
+ {
+ Direction dir;
+ cInt horzLeft, horzRight;
+ bool IsOpen = horzEdge.OutIdx >= 0 && m_PolyOuts[horzEdge.OutIdx].IsOpen;
+
+ 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);
+
+ Maxima currMax = m_Maxima;
+ if (currMax != null)
+ {
+ //get the first maxima in range (X) ...
+ if (dir == Direction.dLeftToRight)
+ {
+ while (currMax != null && currMax.X <= horzEdge.Bot.X)
+ currMax = currMax.Next;
+ if (currMax != null && currMax.X >= eLastHorz.Top.X)
+ currMax = null;
+ }
+ else
+ {
+ while (currMax.Next != null && currMax.Next.X < horzEdge.Bot.X)
+ currMax = currMax.Next;
+ if (currMax.X <= eLastHorz.Top.X) currMax = null;
+ }
+ }
+
+ OutPt op1 = null;
+ for (;;) //loop through consec. horizontal edges
+ {
+ bool IsLastHorz = (horzEdge == eLastHorz);
+ TEdge e = GetNextInAEL(horzEdge, dir);
+ while(e != null)
+ {
+
+ //this code block inserts extra coords into horizontal edges (in output
+ //polygons) whereever maxima touch these horizontal edges. This helps
+ //'simplifying' polygons (ie if the Simplify property is set).
+ if (currMax != null)
+ {
+ if (dir == Direction.dLeftToRight)
+ {
+ while (currMax != null && currMax.X < e.Curr.X)
+ {
+ if (horzEdge.OutIdx >= 0 && !IsOpen)
+ AddOutPt(horzEdge, new IntPoint(currMax.X, horzEdge.Bot.Y));
+ currMax = currMax.Next;
+ }
+ }
+ else
+ {
+ while (currMax != null && currMax.X > e.Curr.X)
+ {
+ if (horzEdge.OutIdx >= 0 && !IsOpen)
+ AddOutPt(horzEdge, new IntPoint(currMax.X, horzEdge.Bot.Y));
+ currMax = currMax.Prev;
+ }
+ }
+ };
+
+ if ((dir == Direction.dLeftToRight && e.Curr.X > horzRight) ||
+ (dir == Direction.dRightToLeft && e.Curr.X < horzLeft)) break;
+
+ //Also 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;
+
+ if (horzEdge.OutIdx >= 0 && !IsOpen) //note: may be done multiple times
+ {
+ op1 = AddOutPt(horzEdge, e.Curr);
+ TEdge eNextHorz = m_SortedEdges;
+ while (eNextHorz != null)
+ {
+ if (eNextHorz.OutIdx >= 0 &&
+ HorzSegmentsOverlap(horzEdge.Bot.X,
+ horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X))
+ {
+ OutPt op2 = GetLastOutPt(eNextHorz);
+ AddJoin(op2, op1, eNextHorz.Top);
+ }
+ eNextHorz = eNextHorz.NextInSEL;
+ }
+ AddGhostJoin(op1, horzEdge.Bot);
+ }
+
+ //OK, 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 (horzEdge.OutIdx >= 0)
+ AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge.Top);
+ DeleteFromAEL(horzEdge);
+ DeleteFromAEL(eMaxPair);
+ return;
+ }
+
+ if(dir == Direction.dLeftToRight)
+ {
+ IntPoint Pt = new IntPoint(e.Curr.X, horzEdge.Curr.Y);
+ IntersectEdges(horzEdge, e, Pt);
+ }
+ else
+ {
+ IntPoint Pt = new IntPoint(e.Curr.X, horzEdge.Curr.Y);
+ IntersectEdges(e, horzEdge, Pt);
+ }
+ TEdge eNext = GetNextInAEL(e, dir);
+ SwapPositionsInAEL(horzEdge, e);
+ e = eNext;
+ } //end while(e != null)
+
+ //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+ if (horzEdge.NextInLML == null || !IsHorizontal(horzEdge.NextInLML)) break;
+
+ UpdateEdgeIntoAEL(ref horzEdge);
+ if (horzEdge.OutIdx >= 0) AddOutPt(horzEdge, horzEdge.Bot);
+ GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight);
+
+ } //end for (;;)
+
+ if (horzEdge.OutIdx >= 0 && op1 == null)
+ {
+ op1 = GetLastOutPt(horzEdge);
+ TEdge eNextHorz = m_SortedEdges;
+ while (eNextHorz != null)
+ {
+ if (eNextHorz.OutIdx >= 0 &&
+ HorzSegmentsOverlap(horzEdge.Bot.X,
+ horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X))
+ {
+ OutPt op2 = GetLastOutPt(eNextHorz);
+ AddJoin(op2, op1, eNextHorz.Top);
+ }
+ eNextHorz = eNextHorz.NextInSEL;
+ }
+ AddGhostJoin(op1, horzEdge.Top);
+ }
+
+ if (horzEdge.NextInLML != null)
+ {
+ if(horzEdge.OutIdx >= 0)
+ {
+ 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 (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 topY)
+ {
+ if( m_ActiveEdges == null ) return true;
+ try {
+ BuildIntersectList(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 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)
+ {
+ IntersectPoint(e, eNext, out pt);
+ 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);
+ 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 void 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 (edge1.Dx == edge2.Dx)
+ {
+ ip.Y = edge1.Curr.Y;
+ ip.X = TopX(edge1, ip.Y);
+ return;
+ }
+
+ 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);
+ }
+ //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
+ if (ip.Y > edge1.Curr.Y)
+ {
+ ip.Y = edge1.Curr.Y;
+ //better to use the more vertical edge to derive X ...
+ if (Math.Abs(edge1.Dx) > Math.Abs(edge2.Dx))
+ ip.X = TopX(edge2, ip.Y);
+ else
+ ip.X = TopX(edge1, ip.Y);
+ }
+ }
+ //------------------------------------------------------------------------------
+
+ 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)
+ {
+ if (StrictlySimple) InsertMaxima(e.Top.X);
+ 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;
+ }
+
+ //When StrictlySimple and 'e' is being touched by another edge, then
+ //make sure both edges have a vertex here ...
+ 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))
+ {
+ IntPoint ip = new IntPoint(e.Curr);
+#if use_xyz
+ SetZ(ref ip, ePrev, e);
+#endif
+ OutPt op = AddOutPt(ePrev, ip);
+ OutPt op2 = AddOutPt(e, ip);
+ AddJoin(op, op2, ip); //StrictlySimple (type-3) join
+ }
+ }
+
+ e = e.NextInAEL;
+ }
+ }
+
+ //3. Process horizontals at the Top of the scanbeam ...
+ ProcessHorizontals();
+ m_Maxima = null;
+
+ //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);
+ 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 )
+ {
+ if (e.OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e.Top);
+ DeleteFromAEL(e);
+ DeleteFromAEL(eMaxPair);
+ }
+#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 FixupOutPolyline(OutRec outrec)
+ {
+ OutPt pp = outrec.Pts;
+ OutPt lastPP = pp.Prev;
+ while (pp != lastPP)
+ {
+ pp = pp.Next;
+ if (pp.Pt == pp.Prev.Pt)
+ {
+ if (pp == lastPP) lastPP = pp.Prev;
+ OutPt tmpPP = pp.Prev;
+ tmpPP.Next = pp.Next;
+ pp.Next.Prev = tmpPP;
+ pp = tmpPP;
+ }
+ }
+ if (pp == pp.Prev) outrec.Pts = null;
+ }
+ //------------------------------------------------------------------------------
+
+ 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;
+ bool preserveCol = PreserveCollinear || StrictlySimple;
+ for (;;)
+ {
+ if (pp.Prev == pp || pp.Prev == pp.Next)
+ {
+ 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) &&
+ (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp.Prev.Pt, pp.Pt, pp.Next.Pt))))
+ {
+ lastOK = null;
+ pp.Prev.Next = pp.Next;
+ pp.Next.Prev = pp.Prev;
+ pp = pp.Prev;
+ }
+ 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 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 ...
+ if (outRec1 != outRec2) return false;
+ 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;
+ }
+ }
+ }
+ //----------------------------------------------------------------------
+
+ public static int PointInPolygon(IntPoint pt, Path path)
+ {
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+ //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+ int result = 0, cnt = path.Count;
+ if (cnt < 3) return 0;
+ IntPoint ip = path[0];
+ for (int i = 1; i <= cnt; ++i)
+ {
+ IntPoint ipNext = (i == cnt ? path[0] : path[i]);
+ if (ipNext.Y == pt.Y)
+ {
+ if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
+ ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1;
+ }
+ if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y))
+ {
+ if (ip.X >= pt.X)
+ {
+ if (ipNext.X > pt.X) result = 1 - result;
+ else
+ {
+ double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+ (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+ if (d == 0) return -1;
+ else if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result;
+ }
+ }
+ else
+ {
+ if (ipNext.X > pt.X)
+ {
+ double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+ (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+ if (d == 0) return -1;
+ else if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result;
+ }
+ }
+ }
+ ip = ipNext;
+ }
+ return result;
+ }
+ //------------------------------------------------------------------------------
+
+ //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+ //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+ private static int PointInPolygon(IntPoint pt, OutPt op)
+ {
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ int result = 0;
+ OutPt startOp = op;
+ cInt ptx = pt.X, pty = pt.Y;
+ cInt poly0x = op.Pt.X, poly0y = op.Pt.Y;
+ do
+ {
+ op = op.Next;
+ cInt poly1x = op.Pt.X, poly1y = op.Pt.Y;
+
+ if (poly1y == pty)
+ {
+ if ((poly1x == ptx) || (poly0y == pty &&
+ ((poly1x > ptx) == (poly0x < ptx)))) return -1;
+ }
+ if ((poly0y < pty) != (poly1y < pty))
+ {
+ if (poly0x >= ptx)
+ {
+ if (poly1x > ptx) result = 1 - result;
+ else
+ {
+ double d = (double)(poly0x - ptx) * (poly1y - pty) -
+ (double)(poly1x - ptx) * (poly0y - pty);
+ if (d == 0) return -1;
+ if ((d > 0) == (poly1y > poly0y)) result = 1 - result;
+ }
+ }
+ else
+ {
+ if (poly1x > ptx)
+ {
+ double d = (double)(poly0x - ptx) * (poly1y - pty) -
+ (double)(poly1x - ptx) * (poly0y - pty);
+ if (d == 0) return -1;
+ if ((d > 0) == (poly1y > poly0y)) result = 1 - result;
+ }
+ }
+ }
+ poly0x = poly1x; poly0y = poly1y;
+ } while (startOp != op);
+ return result;
+ }
+ //------------------------------------------------------------------------------
+
+ private static bool Poly2ContainsPoly1(OutPt outPt1, OutPt outPt2)
+ {
+ OutPt op = outPt1;
+ do
+ {
+ //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+ 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 == null) continue;
+ OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft);
+ if (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;
+ if (outRec1.IsOpen || outRec2.IsOpen) 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 || outrec.IsOpen) 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;
+ if (m_UsingPolyTree) FixupFirstLefts2(outrec2, 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;
+ if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2);
+ }
+ else
+ {
+ //the 2 polygons are separate ...
+ outrec2.IsHole = outrec.IsHole;
+ outrec2.FirstLeft = outrec.FirstLeft;
+ if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2);
+ }
+ 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;
+ }
+
+ //------------------------------------------------------------------------------
+ // 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)
+ {
+ //this function is more accurate when the point that's GEOMETRICALLY
+ //between the other 2 points is the one that's tested for distance.
+ //nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts
+ if (Math.Abs(pt1.X - pt2.X) > Math.Abs(pt1.Y - pt2.Y))
+ {
+ if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
+ return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+ else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
+ return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+ else
+ return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+ }
+ else
+ {
+ if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
+ return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+ else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
+ return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+ else
+ return DistanceFromLineSqrd(pt3, pt1, pt2) < 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 pattern, Path path, bool IsSum, bool IsClosed)
+ {
+ int delta = (IsClosed ? 1 : 0);
+ int polyCnt = pattern.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 pattern)
+ 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 pattern)
+ 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 - 1 + delta; i++)
+ for (int j = 0; j < polyCnt; 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);
+ }
+ return quads;
+ }
+ //------------------------------------------------------------------------------
+
+ public static Paths MinkowskiSum(Path pattern, Path path, bool pathIsClosed)
+ {
+ Paths paths = Minkowski(pattern, path, true, pathIsClosed);
+ Clipper c = new Clipper();
+ c.AddPaths(paths, PolyType.ptSubject, true);
+ c.Execute(ClipType.ctUnion, paths, PolyFillType.pftNonZero, PolyFillType.pftNonZero);
+ return paths;
+ }
+ //------------------------------------------------------------------------------
+
+ private static Path TranslatePath(Path path, IntPoint delta)
+ {
+ Path outPath = new Path(path.Count);
+ for (int i = 0; i < path.Count; i++)
+ outPath.Add(new IntPoint(path[i].X + delta.X, path[i].Y + delta.Y));
+ return outPath;
+ }
+ //------------------------------------------------------------------------------
+
+ public static Paths MinkowskiSum(Path pattern, Paths paths, bool pathIsClosed)
+ {
+ Paths solution = new Paths();
+ Clipper c = new Clipper();
+ for (int i = 0; i < paths.Count; ++i)
+ {
+ Paths tmp = Minkowski(pattern, paths[i], true, pathIsClosed);
+ c.AddPaths(tmp, PolyType.ptSubject, true);
+ if (pathIsClosed)
+ {
+ Path path = TranslatePath(paths[i], pattern[0]);
+ c.AddPath(path, PolyType.ptClip, true);
+ }
+ }
+ c.Execute(ClipType.ctUnion, solution,
+ PolyFillType.pftNonZero, PolyFillType.pftNonZero);
+ return solution;
+ }
+ //------------------------------------------------------------------------------
+
+ public static Paths MinkowskiDiff(Path poly1, Path poly2)
+ {
+ Paths paths = Minkowski(poly1, poly2, false, true);
+ Clipper c = new Clipper();
+ c.AddPaths(paths, PolyType.ptSubject, true);
+ c.Execute(ClipType.ctUnion, paths, PolyFillType.pftNonZero, PolyFillType.pftNonZero);
+ return paths;
+ }
+ //------------------------------------------------------------------------------
+
+ 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<DoublePoint> m_normals = new List<DoublePoint>();
+ 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) 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(m_polyNodes.ChildCount - 1, 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];
+ solution.Childs[0].m_Parent = solution;
+ 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)
+ {
+ //cross product ...
+ m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
+
+ if (Math.Abs(m_sinA * m_delta) < 1.0)
+ {
+ //dot product ...
+ double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
+ if (cosA > 0) // angle ==> 0 degrees
+ {
+ 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)));
+ return;
+ }
+ //else angle ==> 180 degrees
+ }
+ 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 = Math.Max((int)Round(m_StepsPerRad * Math.Abs(a)),1);
+
+ 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/C#/clipper_library/clipper_library.csproj b/C#/clipper_library/clipper_library.csproj
new file mode 100644
index 0000000..7810468
--- /dev/null
+++ b/C#/clipper_library/clipper_library.csproj
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{9B062971-A88E-4A3D-B3C9-12B78D15FA66}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>ClipperLib</RootNamespace>
+ <AssemblyName>clipper_library</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="clipper.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
diff --git a/Delphi/cairo demo/Cairo Resources.txt b/Delphi/cairo demo/Cairo Resources.txt
new file mode 100644
index 0000000..95e2e83
--- /dev/null
+++ b/Delphi/cairo demo/Cairo Resources.txt
@@ -0,0 +1,12 @@
+http://cairographics.org/
+
+The Windows dynamic linked libraries necessary to run Cairo can be downloaded from
+http://www.gtk.org/download/win32.php
+All the dlls listed under the heading "Required third party dependencies" are
+required except gettext-runtime.dll.
+
+A Delphi library that interfaces with the Cairo dlls is
+"Cairo Graphics Delphi Bindings" available at
+http://www.rodi.dk/programming_cairo.php
+Direct link to the download -
+http://www.rodi.dk/download1.php?cairo-delphi-bindings-1.0.zip
diff --git a/Delphi/cairo demo/CairoClipperDemo1.dpr b/Delphi/cairo demo/CairoClipperDemo1.dpr
new file mode 100644
index 0000000..78fa035
--- /dev/null
+++ b/Delphi/cairo demo/CairoClipperDemo1.dpr
@@ -0,0 +1,267 @@
+{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N+,O+,P+,Q+,R+,S-,T-,U-,V+,W-,X+,Y+,Z1}
+{$MINSTACKSIZE $00004000}
+{$MAXSTACKSIZE $00100000}
+{$IMAGEBASE $00400000}
+{$APPTYPE GUI}
+{$WARN SYMBOL_DEPRECATED ON}
+{$WARN SYMBOL_LIBRARY ON}
+{$WARN SYMBOL_PLATFORM ON}
+{$WARN UNIT_LIBRARY ON}
+{$WARN UNIT_PLATFORM ON}
+{$WARN UNIT_DEPRECATED ON}
+{$WARN HRESULT_COMPAT ON}
+{$WARN HIDING_MEMBER ON}
+{$WARN HIDDEN_VIRTUAL ON}
+{$WARN GARBAGE ON}
+{$WARN BOUNDS_ERROR ON}
+{$WARN ZERO_NIL_COMPAT ON}
+{$WARN STRING_CONST_TRUNCED ON}
+{$WARN FOR_LOOP_VAR_VARPAR ON}
+{$WARN TYPED_CONST_VARPAR ON}
+{$WARN ASG_TO_TYPED_CONST ON}
+{$WARN CASE_LABEL_RANGE ON}
+{$WARN FOR_VARIABLE ON}
+{$WARN CONSTRUCTING_ABSTRACT ON}
+{$WARN COMPARISON_FALSE ON}
+{$WARN COMPARISON_TRUE ON}
+{$WARN COMPARING_SIGNED_UNSIGNED ON}
+{$WARN COMBINING_SIGNED_UNSIGNED ON}
+{$WARN UNSUPPORTED_CONSTRUCT ON}
+{$WARN FILE_OPEN ON}
+{$WARN FILE_OPEN_UNITSRC ON}
+{$WARN BAD_GLOBAL_SYMBOL ON}
+{$WARN DUPLICATE_CTOR_DTOR ON}
+{$WARN INVALID_DIRECTIVE ON}
+{$WARN PACKAGE_NO_LINK ON}
+{$WARN PACKAGED_THREADVAR ON}
+{$WARN IMPLICIT_IMPORT ON}
+{$WARN HPPEMIT_IGNORED ON}
+{$WARN NO_RETVAL ON}
+{$WARN USE_BEFORE_DEF ON}
+{$WARN FOR_LOOP_VAR_UNDEF ON}
+{$WARN UNIT_NAME_MISMATCH ON}
+{$WARN NO_CFG_FILE_FOUND ON}
+{$WARN MESSAGE_DIRECTIVE ON}
+{$WARN IMPLICIT_VARIANTS ON}
+{$WARN UNICODE_TO_LOCALE ON}
+{$WARN LOCALE_TO_UNICODE ON}
+{$WARN IMAGEBASE_MULTIPLE ON}
+{$WARN SUSPICIOUS_TYPECAST ON}
+{$WARN PRIVATE_PROPACCESSOR ON}
+{$WARN UNSAFE_TYPE OFF}
+{$WARN UNSAFE_CODE OFF}
+{$WARN UNSAFE_CAST OFF}
+program CairoClipperDemo1;
+
+uses
+ Windows,
+ sysutils,
+ Messages,
+ Graphics,
+ Math,
+ Cairo in 'cairo.pas',
+ CairoWin32 in 'cairowin32.pas',
+ cairo_clipper in 'cairo_clipper.pas',
+ clipper in '..\..\clipper.pas';
+
+{$R *.res}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+var
+ offsetVal: integer = 0;
+ bmp: graphics.TBitmap; //just to buffer drawing and minimize flicker
+
+procedure PaintBitmap;
+var
+ surface: Pcairo_surface_t;
+ cr: Pcairo_t;
+ extent: cairo_text_extents_t;
+ clipper: TClipper;
+ ppa: TPaths;
+ rec: TRect;
+ text: string;
+const
+ scaling = 2; //because Clipper now only accepts integer coordinates
+begin
+ //create a cairo context for the bitmap surface ...
+ surface := cairo_win32_surface_create(bmp.canvas.handle);
+ cr := cairo_create(surface);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
+
+ clipper := TClipper.Create;
+
+ //fill the context background with white ...
+ cairo_rectangle(cr, 0, 0, bmp.Width, bmp.Height);
+ cairo_set_source_rgba(cr, 1, 1, 1, 1);
+ cairo_fill(cr);
+
+ //create a circular pattern, add the path to clipper and then draw it ...
+ cairo_arc(cr, 165,110,70,0,2*3.1415926);
+ cairo_close_path(cr); //important because we can only clip polygons
+ CairoToPointArray(cr, ppa, scaling);
+ clipper.AddPaths(ppa, ptSubject, true);
+ cairo_set_line_width(cr, 2.0);
+ cairo_set_source_rgba(cr, 0, 0, 1, 0.25);
+ cairo_fill_preserve(cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.5);
+ cairo_stroke(cr);
+ cairo_new_path(cr);
+
+ //create a star pattern, add the path to clipper and then draw it ...
+ cairo_move_to(cr, 60,110);
+ cairo_line_to(cr, 240,70);
+ cairo_line_to(cr, 110,210);
+ cairo_line_to(cr, 140,25);
+ cairo_line_to(cr, 230,200);
+ cairo_close_path(cr);
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, 185,50,20,0,2*3.1415926);
+ cairo_close_path(cr);
+ CairoToPointArray(cr, ppa, scaling);
+ clipper.AddPaths(ppa, ptClip, true);
+ cairo_set_source_rgba(cr, 1, 0, 0, 0.25);
+ cairo_fill_preserve(cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.5);
+ cairo_stroke(cr);
+
+ //now clip and draw the paths previously added to clipper ....
+ clipper.Execute(ctIntersection, ppa, pftNonZero, pftNonZero);
+
+ cairo_set_line_width(cr, 2.0);
+ if offsetVal <> 0 then
+ begin
+ with TClipperOffset.Create() do
+ try
+ AddPaths(ppa, jtRound, etClosedPolygon);
+ Execute(ppa, offsetVal*power(10,scaling));
+ finally
+ Free;
+ end;
+ end;
+ PointArrayToCairo(ppa, cr, scaling);
+ cairo_set_source_rgba(cr, 1, 1, 0, 1);
+ cairo_fill_preserve(cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_stroke(cr);
+
+ GetClientRect(GetActiveWindow, rec);
+ cairo_set_font_size(cr,11);
+ text := 'Polygon offset = '+ inttostr(offsetVal) + '. (Adjust with arrow keys)';
+ cairo_text_extents(cr, pchar(text), @extent);
+ cairo_move_to(cr, 10, rec.Bottom - extent.height);
+ cairo_show_text(cr, pchar(text));
+
+ //clean up ...
+ cairo_surface_finish(surface);
+end;
+//------------------------------------------------------------------------------
+
+function WndProc(Wnd : HWND; message : UINT;
+ wParam : Integer; lParam: Integer) : Integer; stdcall;
+var
+ dc: HDC;
+ ps: PAINTSTRUCT;
+begin
+ case message of
+ WM_PAINT:
+ begin
+ dc := BeginPaint(Wnd, ps);
+ with bmp do BitBlt(dc,0,0,Width,Height,canvas.Handle,0,0,SRCCOPY);
+ EndPaint(Wnd, ps);
+ result := 0;
+ end;
+
+ WM_CREATE:
+ begin
+ bmp := graphics.TBitmap.Create;
+ result := DefWindowProc(Wnd, message, wParam, lParam);
+ end;
+ WM_DESTROY:
+ begin
+ bmp.Free;
+ PostQuitMessage(0);
+ result := 0;
+ end;
+
+ WM_SIZE:
+ begin
+ bmp.Width := loword(lparam);
+ bmp.Height := hiword(lparam);
+ PaintBitmap;
+ result := DefWindowProc(Wnd, message, wParam, lParam);
+ end;
+
+ WM_KEYDOWN:
+ case wParam of
+ VK_ESCAPE:
+ begin
+ PostQuitMessage(0);
+ result := 0;
+ end;
+ VK_RIGHT, VK_UP:
+ begin
+ if offsetVal < 20 then inc(offsetVal);
+ PaintBitmap;
+ InvalidateRect(0, nil, false);
+ result := 0;
+ end;
+ VK_LEFT, VK_DOWN:
+ begin
+ if offsetVal > -20 then dec(offsetVal);
+ PaintBitmap;
+ InvalidateRect(0, nil, false);
+ result := 0;
+ end;
+ else
+ result := DefWindowProc(Wnd, message, wParam, lParam);
+ end;
+
+ else
+ result := DefWindowProc(Wnd, message, wParam, lParam);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+var
+ hWnd : THandle;
+ Msg : TMsg;
+ wndClass : TWndClass;
+begin
+ wndClass.style := CS_HREDRAW or CS_VREDRAW;
+ wndClass.lpfnWndProc := @WndProc;
+ wndClass.cbClsExtra := 0;
+ wndClass.cbWndExtra := 0;
+ wndClass.hInstance := hInstance;
+ wndClass.hIcon := LoadIcon(0, IDI_APPLICATION);
+ wndClass.hCursor := LoadCursor(0, IDC_ARROW);
+ wndClass.hbrBackground := HBRUSH(GetStockObject(WHITE_BRUSH));
+ wndClass.lpszMenuName := nil;
+ wndClass.lpszClassName := 'CairoClipper';
+
+ RegisterClass(wndClass);
+
+ hWnd := CreateWindow(
+ 'CairoClipper', // window class name
+ 'Cairo-Clipper Demo', // window caption
+ WS_OVERLAPPEDWINDOW, // window style
+ Integer(CW_USEDEFAULT), // initial x position
+ Integer(CW_USEDEFAULT), // initial y position
+ 400, // initial x size
+ 300, // initial y size
+ 0, // parent window handle
+ 0, // window menu handle
+ hInstance, // program instance handle
+ nil); // creation parameters
+
+ ShowWindow(hWnd, SW_SHOW);
+ UpdateWindow(hWnd);
+
+ while(GetMessage(msg, 0, 0, 0)) do
+ begin
+ TranslateMessage(msg);
+ DispatchMessage(msg);
+ end;
+end.
+
diff --git a/Delphi/cairo demo/CairoClipperDemo1.res b/Delphi/cairo demo/CairoClipperDemo1.res
new file mode 100644
index 0000000..d8a5528
Binary files /dev/null and b/Delphi/cairo demo/CairoClipperDemo1.res differ
diff --git a/Delphi/cairo demo/cairo_clipper.pas b/Delphi/cairo demo/cairo_clipper.pas
new file mode 100644
index 0000000..6276606
--- /dev/null
+++ b/Delphi/cairo demo/cairo_clipper.pas
@@ -0,0 +1,121 @@
+unit cairo_clipper;
+
+(*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 1.2 *
+* Date : 29 September 2011 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2011 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+*******************************************************************************)
+
+interface
+
+uses
+ SysUtils, Classes, Cairo, types, math, clipper;
+
+//nb: Since Clipper only accepts integer coordinates, fractional values have to
+//be scaled up and down when being passed to and from Clipper. This is easily
+//accomplished by setting the scaling factor (10^x) in the following functions.
+//When scaling, remember that on most platforms, integer is only a 32bit value.
+function PointArrayToCairo(const polys: TPaths;
+ cairo: Pcairo_t; scaling_factor: integer = 2): boolean;
+function CairoToPointArray(cairo: Pcairo_t;
+ out polys: TPaths; scaling_factor: integer = 2): boolean;
+
+implementation
+
+type
+ PCairoPathDataArray = ^TCairoPathDataArray;
+ TCairoPathDataArray =
+ array [0.. MAXINT div sizeof(cairo_path_data_t) -1] of cairo_path_data_t;
+
+function PointArrayToCairo(const polys: TPaths;
+ cairo: Pcairo_t; scaling_factor: integer = 2): boolean;
+var
+ i,j: integer;
+ scaling: double;
+begin
+ result := assigned(cairo);
+ if not result then exit;
+ if abs(scaling_factor) > 6 then
+ raise Exception.Create('PointArrayToCairo: invalid scaling factor');
+ scaling := power(10, scaling_factor);
+ for i := 0 to high(polys) do
+ begin
+ cairo_new_sub_path(cairo);
+ for j := 0 to high(polys[i]) do
+ with polys[i][j] do cairo_line_to(cairo,X/scaling,Y/scaling);
+ cairo_close_path(cairo);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function CairoToPointArray(cairo: Pcairo_t;
+ out polys: TPaths; scaling_factor: integer = 2): boolean;
+const
+ buffLen1: integer = 32;
+ buffLen2: integer = 128;
+var
+ i,currLen1, currLen2: integer;
+ pdHdr: cairo_path_data_t;
+ path: Pcairo_path_t;
+ currPos: TIntPoint;
+ scaling: double;
+begin
+ if abs(scaling_factor) > 6 then
+ raise Exception.Create('PointArrayToCairo: invalid scaling factor');
+ scaling := power(10, scaling_factor);
+ result := false;
+ setlength(polys, buffLen1);
+ currLen1 := 1;
+ currLen2 := 0;
+ currPos := IntPoint(0,0);
+ i := 0;
+ path := cairo_copy_path_flat(cairo);
+ try
+ while i < path.num_data do
+ begin
+ pdHdr := PCairoPathDataArray(path.data)[i];
+ case pdHdr.header._type of
+ CAIRO_PATH_CLOSE_PATH:
+ begin
+ if currLen2 > 1 then //ie: ignore if < 3 points (not a polygon)
+ begin
+ setlength(polys[currLen1-1], currLen2);
+ setlength(polys[currLen1], buffLen2);
+ inc(currLen1);
+ end;
+ currLen2 := 0;
+ currPos := IntPoint(0,0);
+ result := true;
+ end;
+ CAIRO_PATH_MOVE_TO, CAIRO_PATH_LINE_TO:
+ begin
+ result := false;
+ if (pdHdr.header._type = CAIRO_PATH_MOVE_TO) and
+ (currLen2 > 0) then break; //ie enforce ClosePath for polygons
+ if (currLen2 mod buffLen2 = 0) then
+ SetLength(polys[currLen1-1], currLen2 + buffLen2);
+ with PCairoPathDataArray(path.data)[i+1].point do
+ currPos := IntPoint(Round(x*scaling),Round(y*scaling));
+ polys[currLen1-1][currLen2] := currPos;
+ inc(currLen2);
+ end;
+ end;
+ inc(i, pdHdr.header.length);
+ end;
+ finally
+ cairo_path_destroy(path);
+ end;
+ dec(currLen1); //ie enforces a ClosePath
+ setlength(polys, currLen1);
+end;
+//------------------------------------------------------------------------------
+
+end.
diff --git a/Delphi/clipper.pas b/Delphi/clipper.pas
new file mode 100644
index 0000000..859a208
--- /dev/null
+++ b/Delphi/clipper.pas
@@ -0,0 +1,5514 @@
+unit clipper;
+
+(*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 6.2.9 *
+* Date : 16 February 2015 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2015 *
+* *
+* 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 *
+* *
+*******************************************************************************)
+
+//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 (with only a minor cost to performance)
+//{$DEFINE use_xyz}
+
+//use_lines: Enables open path clipping (with a very minor cost to performance)
+{$DEFINE use_lines}
+
+{$IFDEF FPC}
+ {$DEFINE INLINING}
+ {$DEFINE UInt64Support}
+{$ELSE}
+ // enable LEGACYIFEND for Delphi XE4+
+ {$IF CompilerVersion >= 25.0}
+ {$LEGACYIFEND ON}
+ {$IFEND}
+
+ // use generic lists for NextGen compiler
+ {$IFDEF NEXTGEN}
+ {$DEFINE USEGENERICS}
+ {$ENDIF}
+
+ {$IFDEF ConditionalExpressions}
+ {$IF CompilerVersion >= 15} //Delphi 7
+ {$DEFINE UInt64Support} //nb: Delphi7 only marginally supports UInt64.
+ {$IFEND}
+ {$IF CompilerVersion >= 18} //Delphi 2007
+ //Inline has been supported since D2005.
+ //However D2005 and D2006 have an Inline codegen bug (QC41166).
+ //http://www.thedelphigeek.com/2007/02/nasty-inline-codegen-bug-in-bds-2006.html
+ {$DEFINE INLINING}
+ {$IFEND}
+ {$ENDIF}
+{$ENDIF}
+
+interface
+
+uses
+ SysUtils, Types, Classes,
+ {$IFDEF USEGENERICS}
+ Generics.Collections, Generics.Defaults,
+ {$ENDIF}
+ Math;
+
+const
+ def_arc_tolerance = 0.25;
+
+type
+{$IFDEF use_int32}
+{$IF CompilerVersion < 20} //Delphi 2009
+ cInt = Integer; //Int32 supported since D2009.
+{$ELSE}
+ cInt = Int32;
+{$IFEND}
+{$ELSE}
+ cInt = Int64;
+{$ENDIF}
+
+ PIntPoint = ^TIntPoint;
+{$IFDEF use_xyz}
+ TIntPoint = record X, Y, Z: cInt; end;
+{$ELSE}
+ TIntPoint = record X, Y: cInt; end;
+{$ENDIF}
+
+ TIntRect = record Left, Top, Right, Bottom: cInt; end;
+
+ TDoublePoint = record X, Y: Double; end;
+ TArrayOfDoublePoint = array of TDoublePoint;
+
+{$IFDEF use_xyz}
+ TZFillCallback =
+ procedure (const E1Bot, E1Top, E2Bot, E2Top: TIntPoint; var Pt: TIntPoint);
+{$ENDIF}
+
+ TInitOption = (ioReverseSolution, ioStrictlySimple, ioPreserveCollinear);
+ TInitOptions = set of TInitOption;
+
+ TClipType = (ctIntersection, ctUnion, ctDifference, ctXor);
+ TPolyType = (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
+ TPolyFillType = (pftEvenOdd, pftNonZero, pftPositive, pftNegative);
+
+ //TJoinType & TEndType are used by OffsetPaths()
+ TJoinType = (jtSquare, jtRound, jtMiter);
+ TEndType = (etClosedPolygon, etClosedLine,
+ etOpenButt, etOpenSquare, etOpenRound); //and etSingle still to come
+
+ TPath = array of TIntPoint;
+ TPaths = array of TPath;
+
+ TPolyNode = class;
+ TArrayOfPolyNode = array of TPolyNode;
+
+ TPolyNode = class
+ private
+ FPath : TPath;
+ FParent : TPolyNode;
+ FIndex : Integer;
+ FCount : Integer;
+ FBuffLen : Integer;
+ FIsOpen : Boolean;
+ FChilds : TArrayOfPolyNode;
+ FJoinType: TJoinType; //used by ClipperOffset only
+ FEndType : TEndType; //used by ClipperOffset only
+ function GetChild(Index: Integer): TPolyNode;
+ function IsHoleNode: boolean;
+ procedure AddChild(PolyNode: TPolyNode);
+ function GetNextSiblingUp: TPolyNode;
+ public
+ function GetNext: TPolyNode;
+ property ChildCount: Integer read FCount;
+ property Childs[index: Integer]: TPolyNode read GetChild;
+ property Parent: TPolyNode read FParent;
+ property IsHole: Boolean read IsHoleNode;
+ property IsOpen: Boolean read FIsOpen;
+ property Contour: TPath read FPath;
+ end;
+
+ TPolyTree = class(TPolyNode)
+ private
+ FAllNodes: TArrayOfPolyNode; //container for ALL PolyNodes
+ function GetTotal: Integer;
+ public
+ procedure Clear;
+ function GetFirst: TPolyNode;
+ destructor Destroy; override;
+ property Total: Integer read GetTotal;
+ end;
+
+ //the definitions below are used internally ...
+ TEdgeSide = (esLeft, esRight);
+ TDirection = (dRightToLeft, dLeftToRight);
+
+ POutPt = ^TOutPt;
+
+ PEdge = ^TEdge;
+ TEdge = record
+ Bot : TIntPoint; //bottom
+ Curr : TIntPoint; //current (ie relative to bottom of current scanbeam)
+ Top : TIntPoint; //top
+ Delta: TIntPoint;
+ Dx : Double; //inverse of slope
+ PolyType : TPolyType;
+ Side : TEdgeSide;
+ WindDelta: Integer; //1 or -1 depending on winding direction
+ WindCnt : Integer;
+ WindCnt2 : Integer; //winding count of the opposite PolyType
+ OutIdx : Integer;
+ Next : PEdge;
+ Prev : PEdge;
+ NextInLML: PEdge;
+ PrevInAEL: PEdge;
+ NextInAEL: PEdge;
+ PrevInSEL: PEdge;
+ NextInSEL: PEdge;
+ end;
+
+ PEdgeArray = ^TEdgeArray;
+ TEdgeArray = array[0.. MaxInt div sizeof(TEdge) -1] of TEdge;
+
+ PScanbeam = ^TScanbeam;
+ TScanbeam = record
+ Y : cInt;
+ Next : PScanbeam;
+ end;
+
+ PMaxima = ^TMaxima;
+ TMaxima = record
+ X : cInt;
+ Next : PMaxima;
+ Prev : PMaxima;
+ end;
+
+ PIntersectNode = ^TIntersectNode;
+ TIntersectNode = record
+ Edge1: PEdge;
+ Edge2: PEdge;
+ Pt : TIntPoint;
+ end;
+
+ PLocalMinimum = ^TLocalMinimum;
+ TLocalMinimum = record
+ Y : cInt;
+ LeftBound : PEdge;
+ RightBound: PEdge;
+ end;
+
+ POutRec = ^TOutRec;
+ TOutRec = record
+ Idx : Integer;
+ BottomPt : POutPt;
+ IsHole : Boolean;
+ IsOpen : Boolean;
+ //The 'FirstLeft' field points to the OutRec representing the polygon
+ //immediately to the left of the current OutRec's polygon. When a polygon is
+ //contained within another polygon, the polygon immediately to its left will
+ //either be its outer polygon or a sibling also contained by the same outer
+ //polygon. By storing this field, it's easy to sort polygons into a tree
+ //structure which reflects the parent/child relationships of all polygons.
+ FirstLeft : POutRec;
+ Pts : POutPt;
+ PolyNode : TPolyNode;
+ end;
+
+ TOutPt = record
+ Idx : Integer;
+ Pt : TIntPoint;
+ Next : POutPt;
+ Prev : POutPt;
+ end;
+
+ PJoin = ^TJoin;
+ TJoin = record
+ OutPt1 : POutPt;
+ OutPt2 : POutPt;
+ OffPt : TIntPoint; //offset point (provides slope of common edges)
+ end;
+
+ {$IFDEF USEGENERICS}
+ TEgdeList = TList<PEdgeArray>;
+ TLocMinList = TList<PLocalMinimum>;
+ {$ELSE}
+ TEgdeList = TList;
+ TLocMinList = TList;
+ {$ENDIF}
+
+ TClipperBase = class
+ private
+ FEdgeList : TEgdeList;
+ FUse64BitRange : Boolean; //see LoRange and HiRange consts notes below
+ FHasOpenPaths : Boolean;
+ procedure DisposeLocalMinimaList;
+ procedure DisposePolyPts(PP: POutPt);
+ function ProcessBound(E: PEdge; NextIsForward: Boolean): PEdge;
+ protected
+ FLocMinList : TLocMinList;
+ FCurrentLocMinIdx : Integer;
+ FPreserveCollinear : Boolean;
+ procedure Reset; virtual;
+ property HasOpenPaths: Boolean read FHasOpenPaths;
+ public
+ constructor Create; virtual;
+ destructor Destroy; override;
+ procedure Clear; virtual;
+
+ function AddPath(const Path: TPath; PolyType: TPolyType; Closed: Boolean): Boolean;
+ function AddPaths(const Paths: TPaths; PolyType: TPolyType; Closed: Boolean): Boolean;
+ //PreserveCollinear: Prevents removal of 'inner' vertices when three or
+ //more vertices are collinear in solution polygons.
+ property PreserveCollinear: Boolean
+ read FPreserveCollinear write FPreserveCollinear;
+ end;
+
+
+ {$IFDEF USEGENERICS}
+ TPolyOutList = TList<POutRec>;
+ TJoinList = TList<PJoin>;
+ TIntersecList = TList<PIntersectNode>;
+ {$ELSE}
+ TPolyOutList = TList;
+ TJoinList = TList;
+ TIntersecList = TList;
+ {$ENDIF}
+
+ TClipper = class(TClipperBase)
+ private
+ FPolyOutList : TPolyOutList;
+ FJoinList : TJoinList;
+ FGhostJoinList : TJoinList;
+ FIntersectList : TIntersecList;
+ FClipType : TClipType;
+ FScanbeam : PScanbeam; //scanbeam list
+ FMaxima : PMaxima; //maxima XPos list
+ FActiveEdges : PEdge; //active Edge list
+ FSortedEdges : PEdge; //used for temporary sorting
+ FClipFillType : TPolyFillType;
+ FSubjFillType : TPolyFillType;
+ FExecuteLocked : Boolean;
+ FReverseOutput : Boolean;
+ FStrictSimple : Boolean;
+ FUsingPolyTree : Boolean;
+{$IFDEF use_xyz}
+ FZFillCallback : TZFillCallback;
+{$ENDIF}
+ procedure DisposeScanbeamList;
+ procedure InsertScanbeam(const Y: cInt);
+ function PopScanbeam: cInt;
+ procedure InsertMaxima(const X: cInt);
+ procedure DisposeMaximaList;
+ procedure SetWindingCount(Edge: PEdge);
+ function IsEvenOddFillType(Edge: PEdge): Boolean;
+ function IsEvenOddAltFillType(Edge: PEdge): Boolean;
+ procedure AddEdgeToSEL(Edge: PEdge);
+ procedure CopyAELToSEL;
+ procedure InsertLocalMinimaIntoAEL(const BotY: cInt);
+ procedure SwapPositionsInAEL(E1, E2: PEdge);
+ procedure SwapPositionsInSEL(E1, E2: PEdge);
+ procedure ProcessHorizontal(HorzEdge: PEdge);
+ procedure ProcessHorizontals;
+ function ProcessIntersections(const TopY: cInt): Boolean;
+ procedure BuildIntersectList(const TopY: cInt);
+ procedure ProcessIntersectList;
+ procedure DeleteFromAEL(E: PEdge);
+ procedure DeleteFromSEL(E: PEdge);
+ procedure IntersectEdges(E1,E2: PEdge; Pt: TIntPoint);
+ procedure DoMaxima(E: PEdge);
+ procedure UpdateEdgeIntoAEL(var E: PEdge);
+ function FixupIntersectionOrder: Boolean;
+ procedure ProcessEdgesAtTopOfScanbeam(const TopY: cInt);
+ function IsContributing(Edge: PEdge): Boolean;
+ function CreateOutRec: POutRec;
+ function AddOutPt(E: PEdge; const Pt: TIntPoint): POutPt;
+ function GetLastOutPt(E: PEdge): POutPt;
+ procedure AddLocalMaxPoly(E1, E2: PEdge; const Pt: TIntPoint);
+ function AddLocalMinPoly(E1, E2: PEdge; const Pt: TIntPoint): POutPt;
+ function GetOutRec(Idx: integer): POutRec;
+ procedure AppendPolygon(E1, E2: PEdge);
+ procedure DisposeAllOutRecs;
+ procedure DisposeOutRec(Index: Integer);
+ procedure DisposeIntersectNodes;
+ function BuildResult: TPaths;
+ function BuildResult2(PolyTree: TPolyTree): Boolean;
+ procedure FixupOutPolygon(OutRec: POutRec);
+ procedure FixupOutPolyline(OutRec: POutRec);
+ procedure SetHoleState(E: PEdge; OutRec: POutRec);
+ procedure AddJoin(Op1, Op2: POutPt; const OffPt: TIntPoint);
+ procedure ClearJoins;
+ procedure AddGhostJoin(OutPt: POutPt; const OffPt: TIntPoint);
+ procedure ClearGhostJoins;
+ function JoinPoints(Jr: PJoin; OutRec1, OutRec2: POutRec): Boolean;
+ procedure FixupFirstLefts1(OldOutRec, NewOutRec: POutRec);
+ procedure FixupFirstLefts2(OldOutRec, NewOutRec: POutRec);
+ procedure DoSimplePolygons;
+ procedure JoinCommonEdges;
+ procedure FixHoleLinkage(OutRec: POutRec);
+ protected
+ procedure Reset; override;
+ function ExecuteInternal: Boolean; virtual;
+ public
+ function Execute(clipType: TClipType;
+ out solution: TPaths;
+ FillType: TPolyFillType = pftEvenOdd): Boolean; overload;
+ function Execute(clipType: TClipType;
+ out solution: TPaths;
+ subjFillType: TPolyFillType;
+ clipFillType: TPolyFillType): Boolean; overload;
+ function Execute(clipType: TClipType;
+ out PolyTree: TPolyTree;
+ FillType: TPolyFillType = pftEvenOdd): Boolean; overload;
+ function Execute(clipType: TClipType;
+ out PolyTree: TPolyTree;
+ subjFillType: TPolyFillType;
+ clipFillType: TPolyFillType): Boolean; overload;
+ constructor Create(InitOptions: TInitOptions = []); reintroduce; overload;
+ destructor Destroy; override;
+ //ReverseSolution: reverses the default orientation
+ property ReverseSolution: Boolean read FReverseOutput write FReverseOutput;
+ //StrictlySimple: when false (the default) solutions are 'weakly' simple
+ property StrictlySimple: Boolean read FStrictSimple write FStrictSimple;
+{$IFDEF use_xyz}
+ property ZFillFunction: TZFillCallback read FZFillCallback write FZFillCallback;
+{$ENDIF}
+ end;
+
+ TClipperOffset = class
+ private
+ FDelta: Double;
+ FSinA, FSin, FCos: Extended;
+ FMiterLim, FStepsPerRad: Double;
+ FNorms: TArrayOfDoublePoint;
+ FSolution: TPaths;
+ FOutPos: Integer;
+ FInP: TPath;
+ FOutP: TPath;
+
+ FLowest: TIntPoint; //X = Path index, Y = Path offset (to lowest point)
+ FPolyNodes: TPolyNode;
+ FMiterLimit: Double;
+ FArcTolerance: Double;
+
+ procedure AddPoint(const Pt: TIntPoint);
+ procedure DoSquare(J, K: Integer);
+ procedure DoMiter(J, K: Integer; R: Double);
+ procedure DoRound(J, K: Integer);
+ procedure OffsetPoint(J: Integer;
+ var K: Integer; JoinType: TJoinType);
+
+ procedure FixOrientations;
+ procedure DoOffset(Delta: Double);
+ public
+ constructor Create(MiterLimit: Double = 2; ArcTolerance: Double = def_arc_tolerance);
+ destructor Destroy; override;
+ procedure AddPath(const Path: TPath; JoinType: TJoinType; EndType: TEndType);
+ procedure AddPaths(const Paths: TPaths; JoinType: TJoinType; EndType: TEndType);
+ procedure Clear;
+ procedure Execute(out solution: TPaths; Delta: Double); overload;
+ procedure Execute(out solution: TPolyTree; Delta: Double); overload;
+ property MiterLimit: double read FMiterLimit write FMiterLimit;
+ property ArcTolerance: double read FArcTolerance write FArcTolerance;
+
+ end;
+
+function Orientation(const Pts: TPath): Boolean; overload;
+function Area(const Pts: TPath): Double; overload;
+function PointInPolygon (const pt: TIntPoint; const poly: TPath): Integer; overload;
+function GetBounds(const polys: TPaths): TIntRect;
+
+{$IFDEF use_xyz}
+function IntPoint(const X, Y: Int64; Z: Int64 = 0): TIntPoint; overload;
+function IntPoint(const X, Y: Double; Z: Double = 0): TIntPoint; overload;
+{$ELSE}
+function IntPoint(const X, Y: cInt): TIntPoint; overload;
+function IntPoint(const X, Y: Double): TIntPoint; overload;
+{$ENDIF}
+
+function DoublePoint(const X, Y: Double): TDoublePoint; overload;
+function DoublePoint(const Ip: TIntPoint): TDoublePoint; overload;
+
+function ReversePath(const Pts: TPath): TPath;
+function ReversePaths(const Pts: TPaths): TPaths;
+
+//SimplifyPolygon converts a self-intersecting polygon into a simple polygon.
+function SimplifyPolygon(const Poly: TPath; FillType: TPolyFillType = pftEvenOdd): TPaths;
+function SimplifyPolygons(const Polys: TPaths; FillType: TPolyFillType = pftEvenOdd): TPaths;
+
+//CleanPolygon removes adjacent vertices closer than the specified distance.
+function CleanPolygon(const Poly: TPath; Distance: double = 1.415): TPath;
+function CleanPolygons(const Polys: TPaths; Distance: double = 1.415): TPaths;
+
+function MinkowskiSum(const Pattern, Path: TPath; PathIsClosed: Boolean): TPaths; overload;
+function MinkowskiSum(const Pattern: TPath; const Paths: TPaths;
+ PathFillType: TPolyFillType; PathIsClosed: Boolean): TPaths; overload;
+function MinkowskiDiff(const Poly1, Poly2: TPath): TPaths;
+
+function PolyTreeToPaths(PolyTree: TPolyTree): TPaths;
+function ClosedPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
+function OpenPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
+
+const
+ //The SlopesEqual function places the most limits on coordinate values
+ //So, to avoid overflow errors, they must not exceed the following values...
+ //Also, if all coordinates are within +/-LoRange, then calculations will be
+ //faster. Otherwise using Int128 math will render the library ~10-15% slower.
+{$IFDEF use_int32}
+ LoRange: cInt = 46340;
+ HiRange: cInt = 46340;
+{$ELSE}
+ LoRange: cInt = $B504F333; //3.0e+9
+ HiRange: cInt = $3FFFFFFFFFFFFFFF; //9.2e+18
+{$ENDIF}
+
+implementation
+
+//NOTE: The Clipper library has been developed with software that uses an
+//inverted Y axis display. Therefore 'above' and 'below' in the code's comments
+//will reflect this. For example: given coord A (0,20) and coord B (0,10),
+//A.Y would be considered BELOW B.Y to correctly understand the comments.
+
+const
+ Horizontal: Double = -3.4e+38;
+
+ Unassigned : Integer = -1;
+ Skip : Integer = -2; //flag for the edge that closes an open path
+ Tolerance : double = 1.0E-15;
+ Two_Pi : double = 2 * PI;
+
+resourcestring
+ rsDoMaxima = 'DoMaxima error';
+ rsUpdateEdgeIntoAEL = 'UpdateEdgeIntoAEL error';
+ rsHorizontal = 'ProcessHorizontal error';
+ rsInvalidInt = 'Coordinate exceeds range bounds';
+ rsIntersect = 'Intersection error';
+ rsOpenPath = 'AddPath: Open paths must be subject.';
+ rsOpenPath2 = 'AddPath: Open paths have been disabled.';
+ rsOpenPath3 = 'Error: TPolyTree struct is needed for open path clipping.';
+ rsPolylines = 'Error intersecting polylines';
+ rsClipperOffset = 'Error: No PolyTree assigned';
+
+//------------------------------------------------------------------------------
+// TPolyNode methods ...
+//------------------------------------------------------------------------------
+
+function TPolyNode.GetChild(Index: Integer): TPolyNode;
+begin
+ if (Index < 0) or (Index >= FCount) then
+ raise Exception.Create('TPolyNode range error: ' + inttostr(Index));
+ Result := FChilds[Index];
+end;
+//------------------------------------------------------------------------------
+
+procedure TPolyNode.AddChild(PolyNode: TPolyNode);
+begin
+ if FCount = FBuffLen then
+ begin
+ Inc(FBuffLen, 16);
+ SetLength(FChilds, FBuffLen);
+ end;
+ PolyNode.FParent := self;
+ PolyNode.FIndex := FCount;
+ FChilds[FCount] := PolyNode;
+ Inc(FCount);
+end;
+//------------------------------------------------------------------------------
+
+function TPolyNode.IsHoleNode: boolean;
+var
+ Node: TPolyNode;
+begin
+ Result := True;
+ Node := FParent;
+ while Assigned(Node) do
+ begin
+ Result := not Result;
+ Node := Node.FParent;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TPolyNode.GetNext: TPolyNode;
+begin
+ if FCount > 0 then
+ Result := FChilds[0] else
+ Result := GetNextSiblingUp;
+end;
+//------------------------------------------------------------------------------
+
+function TPolyNode.GetNextSiblingUp: TPolyNode;
+begin
+ if not Assigned(FParent) then //protects against TPolyTree.GetNextSiblingUp()
+ Result := nil
+ else if FIndex = FParent.FCount -1 then
+ Result := FParent.GetNextSiblingUp
+ else
+ Result := FParent.Childs[FIndex +1];
+end;
+
+//------------------------------------------------------------------------------
+// TPolyTree methods ...
+//------------------------------------------------------------------------------
+
+destructor TPolyTree.Destroy;
+begin
+ Clear;
+ inherited;
+end;
+//------------------------------------------------------------------------------
+
+procedure TPolyTree.Clear;
+var
+ I: Integer;
+begin
+ for I := 0 to high(FAllNodes) do FAllNodes[I].Free;
+ FAllNodes := nil;
+ FBuffLen := 16;
+ SetLength(FChilds, FBuffLen);
+ FCount := 0;
+end;
+//------------------------------------------------------------------------------
+
+function TPolyTree.GetFirst: TPolyNode;
+begin
+ if FCount > 0 then
+ Result := FChilds[0] else
+ Result := nil;
+end;
+//------------------------------------------------------------------------------
+
+function TPolyTree.GetTotal: Integer;
+begin
+ Result := length(FAllNodes);
+ //with negative offsets, ignore the hidden outer polygon ...
+ if (Result > 0) and (FAllNodes[0] <> FChilds[0]) then dec(Result);
+end;
+
+{$IFNDEF use_int32}
+
+//------------------------------------------------------------------------------
+// UInt64 math support for Delphi 6
+//------------------------------------------------------------------------------
+
+{$OVERFLOWCHECKS OFF}
+{$IFNDEF UInt64Support}
+function CompareUInt64(const i, j: Int64): Integer;
+begin
+ if Int64Rec(i).Hi < Int64Rec(j).Hi then
+ Result := -1
+ else if Int64Rec(i).Hi > Int64Rec(j).Hi then
+ Result := 1
+ else if Int64Rec(i).Lo < Int64Rec(j).Lo then
+ Result := -1
+ else if Int64Rec(i).Lo > Int64Rec(j).Lo then
+ Result := 1
+ else
+ Result := 0;
+end;
+{$ENDIF}
+
+function UInt64LT(const i, j: Int64): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+{$IFDEF UInt64Support}
+ Result := UInt64(i) < UInt64(j);
+{$ELSE}
+ Result := CompareUInt64(i, j) = -1;
+{$ENDIF}
+end;
+
+function UInt64GT(const i, j: Int64): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+{$IFDEF UInt64Support}
+ Result := UInt64(i) > UInt64(j);
+{$ELSE}
+ Result := CompareUInt64(i, j) = 1;
+{$ENDIF}
+end;
+{$OVERFLOWCHECKS ON}
+
+//------------------------------------------------------------------------------
+// Int128 Functions ...
+//------------------------------------------------------------------------------
+
+const
+ Mask32Bits = $FFFFFFFF;
+
+type
+
+ //nb: TInt128.Lo is typed Int64 instead of UInt64 to provide Delphi 7
+ //compatability. However while UInt64 isn't a recognised type in
+ //Delphi 7, it can still be used in typecasts.
+ TInt128 = record
+ Hi : Int64;
+ Lo : Int64;
+ end;
+
+{$OVERFLOWCHECKS OFF}
+procedure Int128Negate(var Val: TInt128);
+begin
+ if Val.Lo = 0 then
+ begin
+ Val.Hi := -Val.Hi;
+ end else
+ begin
+ Val.Lo := -Val.Lo;
+ Val.Hi := not Val.Hi;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function Int128(const val: Int64): TInt128; overload;
+begin
+ Result.Lo := val;
+ if val < 0 then
+ Result.Hi := -1 else
+ Result.Hi := 0;
+end;
+//------------------------------------------------------------------------------
+
+function Int128Equal(const Int1, Int2: TInt128): Boolean;
+begin
+ Result := (Int1.Lo = Int2.Lo) and (Int1.Hi = Int2.Hi);
+end;
+//------------------------------------------------------------------------------
+
+function Int128LessThan(const Int1, Int2: TInt128): Boolean;
+begin
+ if (Int1.Hi <> Int2.Hi) then Result := Int1.Hi < Int2.Hi
+ else Result := UInt64LT(Int1.Lo, Int2.Lo);
+end;
+//------------------------------------------------------------------------------
+
+function Int128Add(const Int1, Int2: TInt128): TInt128;
+begin
+ Result.Lo := Int1.Lo + Int2.Lo;
+ Result.Hi := Int1.Hi + Int2.Hi;
+ if UInt64LT(Result.Lo, Int1.Lo) then Inc(Result.Hi);
+end;
+//------------------------------------------------------------------------------
+
+function Int128Sub(const Int1, Int2: TInt128): TInt128;
+begin
+ Result.Hi := Int1.Hi - Int2.Hi;
+ Result.Lo := Int1.Lo - Int2.Lo;
+ if UInt64GT(Result.Lo, Int1.Lo) then Dec(Result.Hi);
+end;
+//------------------------------------------------------------------------------
+
+function Int128Mul(Int1, Int2: Int64): TInt128;
+var
+ A, B, C: Int64;
+ Int1Hi, Int1Lo, Int2Hi, Int2Lo: Int64;
+ Negate: Boolean;
+begin
+ //save the Result's sign before clearing both sign bits ...
+ Negate := (Int1 < 0) <> (Int2 < 0);
+ if Int1 < 0 then Int1 := -Int1;
+ if Int2 < 0 then Int2 := -Int2;
+
+ Int1Hi := Int1 shr 32;
+ Int1Lo := Int1 and Mask32Bits;
+ Int2Hi := Int2 shr 32;
+ Int2Lo := Int2 and Mask32Bits;
+
+ A := Int1Hi * Int2Hi;
+ B := Int1Lo * Int2Lo;
+ //because the high (sign) bits in both int1Hi & int2Hi have been zeroed,
+ //there's no risk of 64 bit overflow in the following assignment
+ //(ie: $7FFFFFFF*$FFFFFFFF + $7FFFFFFF*$FFFFFFFF < 64bits)
+ C := Int1Hi*Int2Lo + Int2Hi*Int1Lo;
+ //Result = A shl 64 + C shl 32 + B ...
+ Result.Hi := A + (C shr 32);
+ A := C shl 32;
+
+ Result.Lo := A + B;
+ if UInt64LT(Result.Lo, A) then
+ Inc(Result.Hi);
+
+ if Negate then Int128Negate(Result);
+end;
+//------------------------------------------------------------------------------
+
+function Int128Div(Dividend, Divisor: TInt128{; out Remainder: TInt128}): TInt128;
+var
+ Cntr: TInt128;
+ Negate: Boolean;
+begin
+ if (Divisor.Lo = 0) and (Divisor.Hi = 0) then
+ raise Exception.create('int128Div error: divide by zero');
+
+ Negate := (Divisor.Hi < 0) <> (Dividend.Hi < 0);
+ if Dividend.Hi < 0 then Int128Negate(Dividend);
+ if Divisor.Hi < 0 then Int128Negate(Divisor);
+
+ if Int128LessThan(Divisor, Dividend) then
+ begin
+ Result.Hi := 0;
+ Result.Lo := 0;
+ Cntr.Lo := 1;
+ Cntr.Hi := 0;
+ //while (Dividend >= Divisor) do
+ while not Int128LessThan(Dividend, Divisor) do
+ begin
+ //divisor := divisor shl 1;
+ Divisor.Hi := Divisor.Hi shl 1;
+ if Divisor.Lo < 0 then Inc(Divisor.Hi);
+ Divisor.Lo := Divisor.Lo shl 1;
+
+ //Cntr := Cntr shl 1;
+ Cntr.Hi := Cntr.Hi shl 1;
+ if Cntr.Lo < 0 then Inc(Cntr.Hi);
+ Cntr.Lo := Cntr.Lo shl 1;
+ end;
+ //Divisor := Divisor shr 1;
+ Divisor.Lo := Divisor.Lo shr 1;
+ if Divisor.Hi and $1 = $1 then
+ Int64Rec(Divisor.Lo).Hi := Cardinal(Int64Rec(Divisor.Lo).Hi) or $80000000;
+ Divisor.Hi := Divisor.Hi shr 1;
+
+ //Cntr := Cntr shr 1;
+ Cntr.Lo := Cntr.Lo shr 1;
+ if Cntr.Hi and $1 = $1 then
+ Int64Rec(Cntr.Lo).Hi := Cardinal(Int64Rec(Cntr.Lo).Hi) or $80000000;
+ Cntr.Hi := Cntr.Hi shr 1;
+
+ //while (Cntr > 0) do
+ while not ((Cntr.Hi = 0) and (Cntr.Lo = 0)) do
+ begin
+ //if ( Dividend >= Divisor) then
+ if not Int128LessThan(Dividend, Divisor) then
+ begin
+ //Dividend := Dividend - Divisor;
+ Dividend := Int128Sub(Dividend, Divisor);
+
+ //Result := Result or Cntr;
+ Result.Hi := Result.Hi or Cntr.Hi;
+ Result.Lo := Result.Lo or Cntr.Lo;
+ end;
+ //Divisor := Divisor shr 1;
+ Divisor.Lo := Divisor.Lo shr 1;
+ if Divisor.Hi and $1 = $1 then
+ Int64Rec(Divisor.Lo).Hi := Cardinal(Int64Rec(Divisor.Lo).Hi) or $80000000;
+ Divisor.Hi := Divisor.Hi shr 1;
+
+ //Cntr := Cntr shr 1;
+ Cntr.Lo := Cntr.Lo shr 1;
+ if Cntr.Hi and $1 = $1 then
+ Int64Rec(Cntr.Lo).Hi := Cardinal(Int64Rec(Cntr.Lo).Hi) or $80000000;
+ Cntr.Hi := Cntr.Hi shr 1;
+ end;
+ if Negate then Int128Negate(Result);
+ //Remainder := Dividend;
+ end
+ else if (Divisor.Hi = Dividend.Hi) and (Divisor.Lo = Dividend.Lo) then
+ begin
+ if Negate then Result := Int128(-1) else Result := Int128(1);
+ end else
+ begin
+ Result := Int128(0);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function Int128AsDouble(val: TInt128): Double;
+const
+ shift64: Double = 18446744073709551616.0;
+var
+ lo: Int64;
+begin
+ if (val.Hi < 0) then
+ begin
+ lo := -val.Lo;
+ if lo = 0 then
+ Result := val.Hi * shift64 else
+ Result := -(not val.Hi * shift64 + UInt64(lo));
+ end else
+ Result := val.Hi * shift64 + UInt64(val.Lo);
+end;
+//------------------------------------------------------------------------------
+
+{$OVERFLOWCHECKS ON}
+
+{$ENDIF}
+
+//------------------------------------------------------------------------------
+// Miscellaneous Functions ...
+//------------------------------------------------------------------------------
+
+function PointCount(Pts: POutPt): Integer;
+var
+ P: POutPt;
+begin
+ Result := 0;
+ if not Assigned(Pts) then Exit;
+ P := Pts;
+ repeat
+ Inc(Result);
+ P := P.Next;
+ until P = Pts;
+end;
+//------------------------------------------------------------------------------
+
+function PointsEqual(const P1, P2: TIntPoint): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ Result := (P1.X = P2.X) and (P1.Y = P2.Y);
+end;
+//------------------------------------------------------------------------------
+
+{$IFDEF use_xyz}
+function IntPoint(const X, Y: Int64; Z: Int64 = 0): TIntPoint;
+begin
+ Result.X := X;
+ Result.Y := Y;
+ Result.Z := Z;
+end;
+//------------------------------------------------------------------------------
+
+function IntPoint(const X, Y: Double; Z: Double = 0): TIntPoint;
+begin
+ Result.X := Round(X);
+ Result.Y := Round(Y);
+ Result.Z := Round(Z);
+end;
+//------------------------------------------------------------------------------
+
+{$ELSE}
+
+function IntPoint(const X, Y: cInt): TIntPoint;
+begin
+ Result.X := X;
+ Result.Y := Y;
+end;
+//------------------------------------------------------------------------------
+
+function IntPoint(const X, Y: Double): TIntPoint;
+begin
+ Result.X := Round(X);
+ Result.Y := Round(Y);
+end;
+
+{$ENDIF}
+//------------------------------------------------------------------------------
+
+function DoublePoint(const X, Y: Double): TDoublePoint;
+begin
+ Result.X := X;
+ Result.Y := Y;
+end;
+//------------------------------------------------------------------------------
+
+function DoublePoint(const Ip: TIntPoint): TDoublePoint;
+begin
+ Result.X := Ip.X;
+ Result.Y := Ip.Y;
+end;
+//------------------------------------------------------------------------------
+
+function Area(const Pts: TPath): Double;
+var
+ I, J, Cnt: Integer;
+ D: Double;
+begin
+ Result := 0.0;
+ Cnt := Length(Pts);
+ if (Cnt < 3) then Exit;
+ J := cnt - 1;
+ for I := 0 to Cnt -1 do
+ begin
+ D := (Pts[j].X + Pts[i].X);
+ Result := Result + D * (Pts[j].Y - Pts[i].Y);
+ J := I;
+ end;
+ Result := -Result * 0.5;
+end;
+//------------------------------------------------------------------------------
+
+function Area(OutRec: POutRec): Double; overload;
+var
+ Op: POutPt;
+ D, D2: Double;
+begin
+ D := 0;
+ Op := OutRec.Pts;
+ if Assigned(Op) then
+ repeat
+ D2 := Op.Prev.Pt.X + Op.Pt.X;
+ D := D + D2 * (Op.Prev.Pt.Y - Op.Pt.Y);
+ Op := Op.Next;
+ until Op = OutRec.Pts;
+ Result := D * 0.5;
+end;
+//------------------------------------------------------------------------------
+
+function Orientation(const Pts: TPath): Boolean;
+begin
+ Result := Area(Pts) >= 0;
+end;
+//------------------------------------------------------------------------------
+
+function ReversePath(const Pts: TPath): TPath;
+var
+ I, HighI: Integer;
+begin
+ HighI := high(Pts);
+ SetLength(Result, HighI +1);
+ for I := 0 to HighI do
+ Result[I] := Pts[HighI - I];
+end;
+//------------------------------------------------------------------------------
+
+function ReversePaths(const Pts: TPaths): TPaths;
+var
+ I, J, highJ: Integer;
+begin
+ I := length(Pts);
+ SetLength(Result, I);
+ for I := 0 to I -1 do
+ begin
+ highJ := high(Pts[I]);
+ SetLength(Result[I], highJ+1);
+ for J := 0 to highJ do
+ Result[I][J] := Pts[I][highJ - J];
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function GetBounds(const polys: TPaths): TIntRect;
+var
+ I,J,Len: Integer;
+begin
+ Len := Length(polys);
+ I := 0;
+ while (I < Len) and (Length(polys[I]) = 0) do inc(I);
+ if (I = Len) then
+ begin
+ with Result do begin Left := 0; Top := 0; Right := 0; Bottom := 0; end;
+ Exit;
+ end;
+ Result.Left := polys[I][0].X;
+ Result.Right := Result.Left;
+ Result.Top := polys[I][0].Y;
+ Result.Bottom := Result.Top;
+ for I := I to Len -1 do
+ for J := 0 to High(polys[I]) do
+ begin
+ if polys[I][J].X < Result.Left then Result.Left := polys[I][J].X
+ else if polys[I][J].X > Result.Right then Result.Right := polys[I][J].X;
+ if polys[I][J].Y < Result.Top then Result.Top := polys[I][J].Y
+ else if polys[I][J].Y > Result.Bottom then Result.Bottom := polys[I][J].Y;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function PointInPolygon (const pt: TIntPoint; const poly: TPath): Integer;
+var
+ i, cnt: Integer;
+ d, d2, d3: double; //use cInt ???
+ ip, ipNext: TIntPoint;
+begin
+ //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
+ //nb: if poly bounds are known, test them first before calling this function.
+ result := 0;
+ cnt := Length(poly);
+ if cnt < 3 then Exit;
+ ip := poly[0];
+ for i := 1 to cnt do
+ begin
+ if i < cnt then ipNext := poly[i]
+ else ipNext := poly[0];
+
+ if (ipNext.Y = pt.Y) then
+ begin
+ if (ipNext.X = pt.X) or ((ip.Y = pt.Y) and
+ ((ipNext.X > pt.X) = (ip.X < pt.X))) then
+ begin
+ result := -1;
+ Exit;
+ end;
+ end;
+
+ if ((ip.Y < pt.Y) <> (ipNext.Y < pt.Y)) then
+ begin
+ if (ip.X >= pt.X) then
+ begin
+ if (ipNext.X > pt.X) then
+ result := 1 - result
+ else
+ begin
+ d2 := (ip.X - pt.X);
+ d3 := (ipNext.X - pt.X);
+ d := d2 * (ipNext.Y - pt.Y) - d3 * (ip.Y - pt.Y);
+ if (d = 0) then begin result := -1; Exit; end;
+ if ((d > 0) = (ipNext.Y > ip.Y)) then
+ result := 1 - result;
+ end;
+ end else
+ begin
+ if (ipNext.X > pt.X) then
+ begin
+ d2 := (ip.X - pt.X);
+ d3 := (ipNext.X - pt.X);
+ d := d2 * (ipNext.Y - pt.Y) - d3 * (ip.Y - pt.Y);
+ if (d = 0) then begin result := -1; Exit; end;
+ if ((d > 0) = (ipNext.Y > ip.Y)) then
+ result := 1 - result;
+ end;
+ end;
+ end;
+ ip := ipNext;
+ end;
+end;
+//---------------------------------------------------------------------------
+
+//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+function PointInPolygon (const pt: TIntPoint; ops: POutPt): Integer; overload;
+var
+ d, d2, d3: double; //nb: double not cInt avoids potential overflow errors
+ opStart: POutPt;
+ pt1, ptN: TIntPoint;
+begin
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ result := 0;
+ opStart := ops;
+ pt1.X := ops.Pt.X; pt1.Y := ops.Pt.Y;
+ repeat
+ ops := ops.Next;
+ ptN.X := ops.Pt.X; ptN.Y := ops.Pt.Y;
+
+ if (ptN.Y = pt.Y) then
+ begin
+ if (ptN.X = pt.X) or ((pt1.Y = pt.Y) and
+ ((ptN.X > pt.X) = (pt1.X < pt.X))) then
+ begin
+ result := -1;
+ Exit;
+ end;
+ end;
+
+ if ((pt1.Y < pt.Y) <> (ptN.Y < pt.Y)) then
+ begin
+ if (pt1.X >= pt.X) then
+ begin
+ if (ptN.X > pt.X) then
+ result := 1 - result
+ else
+ begin
+ d2 := (pt1.X - pt.X);
+ d3 := (ptN.X - pt.X);
+ d := d2 * (ptN.Y - pt.Y) - d3 * (pt1.Y - pt.Y);
+ if (d = 0) then begin result := -1; Exit; end;
+ if ((d > 0) = (ptN.Y > pt1.Y)) then
+ result := 1 - result;
+ end;
+ end else
+ begin
+ if (ptN.X > pt.X) then
+ begin
+ d2 := (pt1.X - pt.X);
+ d3 := (ptN.X - pt.X);
+ d := d2 * (ptN.Y - pt.Y) - d3 * (pt1.Y - pt.Y);
+ if (d = 0) then begin result := -1; Exit; end;
+ if ((d > 0) = (ptN.Y > pt1.Y)) then
+ result := 1 - result;
+ end;
+ end;
+ end;
+ pt1 := ptN;
+ until ops = opStart;
+end;
+//---------------------------------------------------------------------------
+
+function Poly2ContainsPoly1(OutPt1, OutPt2: POutPt): Boolean;
+var
+ res: integer;
+ op: POutPt;
+begin
+ op := OutPt1;
+ repeat
+ //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+ res := PointInPolygon(op.Pt, OutPt2);
+ if (res >= 0) then
+ begin
+ Result := res > 0;
+ Exit;
+ end;
+ op := op.Next;
+ until op = OutPt1;
+ Result := true; //all points on line => result = true
+end;
+//---------------------------------------------------------------------------
+
+function SlopesEqual(E1, E2: PEdge;
+ UseFullInt64Range: Boolean): Boolean; overload;
+begin
+{$IFNDEF use_int32}
+ if UseFullInt64Range then
+ Result := Int128Equal(Int128Mul(E1.Delta.Y, E2.Delta.X),
+ Int128Mul(E1.Delta.X, E2.Delta.Y))
+ else
+{$ENDIF}
+ Result := E1.Delta.Y * E2.Delta.X = E1.Delta.X * E2.Delta.Y;
+end;
+//---------------------------------------------------------------------------
+
+function SlopesEqual(const Pt1, Pt2, Pt3: TIntPoint;
+ UseFullInt64Range: Boolean): Boolean; overload;
+begin
+{$IFNDEF use_int32}
+ if UseFullInt64Range then
+ Result := Int128Equal(
+ Int128Mul(Pt1.Y-Pt2.Y, Pt2.X-Pt3.X), Int128Mul(Pt1.X-Pt2.X, Pt2.Y-Pt3.Y))
+ else
+{$ENDIF}
+ Result := (Pt1.Y-Pt2.Y)*(Pt2.X-Pt3.X) = (Pt1.X-Pt2.X)*(Pt2.Y-Pt3.Y);
+end;
+//---------------------------------------------------------------------------
+
+(*****************************************************************************
+* Dx: 0(90�) Slope: 0 = Dx: -inf *
+* | Slope: 0.5 = Dx: -2 *
+* +inf (180�) <--- o ---> -inf (0�) Slope: 2.0 = Dx: -0.5 *
+* Slope: inf = Dx: 0 *
+*****************************************************************************)
+
+function GetDx(const Pt1, Pt2: TIntPoint): Double;
+begin
+ if (Pt1.Y = Pt2.Y) then Result := Horizontal
+ else Result := (Pt2.X - Pt1.X)/(Pt2.Y - Pt1.Y);
+end;
+//---------------------------------------------------------------------------
+
+procedure SetDx(E: PEdge); {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ E.Delta.X := (E.Top.X - E.Bot.X);
+ E.Delta.Y := (E.Top.Y - E.Bot.Y);
+ if E.Delta.Y = 0 then E.Dx := Horizontal
+ else E.Dx := E.Delta.X/E.Delta.Y;
+end;
+//---------------------------------------------------------------------------
+
+procedure Swap(var val1, val2: cInt); {$IFDEF INLINING} inline; {$ENDIF}
+var
+ tmp: cInt;
+begin
+ tmp := val1;
+ val1 := val2;
+ val2 := tmp;
+end;
+//---------------------------------------------------------------------------
+
+procedure SwapSides(Edge1, Edge2: PEdge);
+var
+ Side: TEdgeSide;
+begin
+ Side := Edge1.Side;
+ Edge1.Side := Edge2.Side;
+ Edge2.Side := Side;
+end;
+//------------------------------------------------------------------------------
+
+procedure SwapPolyIndexes(Edge1, Edge2: PEdge);
+var
+ OutIdx: Integer;
+begin
+ OutIdx := Edge1.OutIdx;
+ Edge1.OutIdx := Edge2.OutIdx;
+ Edge2.OutIdx := OutIdx;
+end;
+//------------------------------------------------------------------------------
+
+function TopX(Edge: PEdge; const currentY: cInt): cInt;
+begin
+ if currentY = Edge.Top.Y then Result := Edge.Top.X
+ else if Edge.Top.X = Edge.Bot.X then Result := Edge.Bot.X
+ else Result := Edge.Bot.X + round(Edge.Dx*(currentY - Edge.Bot.Y));
+end;
+//------------------------------------------------------------------------------
+
+{$IFDEF use_xyz}
+Procedure SetZ(var Pt: TIntPoint; E1, E2: PEdge; ZFillFunc: TZFillCallback);
+begin
+ if (Pt.Z <> 0) or not assigned(ZFillFunc) then Exit
+ else if PointsEqual(Pt, E1.Bot) then Pt.Z := E1.Bot.Z
+ else if PointsEqual(Pt, E2.Bot) then Pt.Z := E2.Bot.Z
+ else if PointsEqual(Pt, E1.Top) then Pt.Z := E1.Top.Z
+ else if PointsEqual(Pt, E2.Top) then Pt.Z := E2.Top.Z
+ else ZFillFunc(E1.Bot, E1.Top, E2.Bot, E2.Top, Pt);
+end;
+//------------------------------------------------------------------------------
+{$ENDIF}
+
+procedure IntersectPoint(Edge1, Edge2: PEdge; out ip: TIntPoint);
+var
+ B1,B2,M: Double;
+begin
+{$IFDEF use_xyz}
+ ip.Z := 0;
+{$ENDIF}
+ if (edge1.Dx = edge2.Dx) then
+ begin
+ ip.Y := edge1.Curr.Y;
+ ip.X := TopX(edge1, ip.Y);
+ Exit;
+ end;
+
+ if Edge1.Delta.X = 0 then
+ begin
+ ip.X := Edge1.Bot.X;
+ if Edge2.Dx = Horizontal then
+ ip.Y := Edge2.Bot.Y
+ else
+ begin
+ with Edge2^ do B2 := Bot.Y - (Bot.X/Dx);
+ ip.Y := round(ip.X/Edge2.Dx + B2);
+ end;
+ end
+ else if Edge2.Delta.X = 0 then
+ begin
+ ip.X := Edge2.Bot.X;
+ if Edge1.Dx = Horizontal then
+ ip.Y := Edge1.Bot.Y
+ else
+ begin
+ with Edge1^ do B1 := Bot.Y - (Bot.X/Dx);
+ ip.Y := round(ip.X/Edge1.Dx + B1);
+ end;
+ end else
+ begin
+ with Edge1^ do B1 := Bot.X - Bot.Y * Dx;
+ with Edge2^ do B2 := Bot.X - Bot.Y * Dx;
+ M := (B2-B1)/(Edge1.Dx - Edge2.Dx);
+ ip.Y := round(M);
+ if Abs(Edge1.Dx) < Abs(Edge2.Dx) then
+ ip.X := round(Edge1.Dx * M + B1)
+ else
+ ip.X := round(Edge2.Dx * M + B2);
+ end;
+
+ //The precondition - E.Curr.X > eNext.Curr.X - indicates that the two edges do
+ //intersect below TopY (and hence below the tops of either Edge). However,
+ //when edges are almost parallel, rounding errors may cause False positives -
+ //indicating intersections when there really aren't any. Also, floating point
+ //imprecision can incorrectly place an intersect point beyond/above an Edge.
+ //Therfore, further adjustment to IP is warranted ...
+ if (ip.Y < Edge1.Top.Y) or (ip.Y < Edge2.Top.Y) then
+ begin
+ //Find the lower top of the two edges and compare X's at this Y.
+ //If Edge1's X is greater than Edge2's X then it's fair to assume an
+ //intersection really has occurred...
+ if (Edge1.Top.Y > Edge2.Top.Y) then
+ ip.Y := edge1.Top.Y else
+ ip.Y := edge2.Top.Y;
+ if Abs(Edge1.Dx) < Abs(Edge2.Dx) then
+ ip.X := TopX(Edge1, ip.Y) else
+ ip.X := TopX(Edge2, ip.Y);
+ end;
+ //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
+ if (ip.Y > Edge1.Curr.Y) then
+ begin
+ ip.Y := Edge1.Curr.Y;
+ if (abs(Edge1.Dx) > abs(Edge2.Dx)) then //ie use more vertical edge
+ ip.X := TopX(Edge2, ip.Y) else
+ ip.X := TopX(Edge1, ip.Y);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure ReversePolyPtLinks(PP: POutPt);
+var
+ Pp1,Pp2: POutPt;
+begin
+ if not Assigned(PP) then Exit;
+ Pp1 := PP;
+ repeat
+ Pp2:= Pp1.Next;
+ Pp1.Next := Pp1.Prev;
+ Pp1.Prev := Pp2;
+ Pp1 := Pp2;
+ until Pp1 = PP;
+end;
+//------------------------------------------------------------------------------
+
+function Pt2IsBetweenPt1AndPt3(const Pt1, Pt2, Pt3: TIntPoint): Boolean;
+begin
+ //nb: assumes collinearity.
+ if PointsEqual(Pt1, Pt3) or PointsEqual(Pt1, Pt2) or PointsEqual(Pt3, Pt2) then
+ Result := False
+ else if (Pt1.X <> Pt3.X) then
+ Result := (Pt2.X > Pt1.X) = (Pt2.X < Pt3.X)
+ else
+ Result := (Pt2.Y > Pt1.Y) = (Pt2.Y < Pt3.Y);
+end;
+//------------------------------------------------------------------------------
+
+function GetOverlap(const A1, A2, B1, B2: cInt; out Left, Right: cInt): Boolean;
+begin
+ if (A1 < A2) then
+ begin
+ if (B1 < B2) then begin Left := Max(A1,B1); Right := Min(A2,B2); end
+ else begin Left := Max(A1,B2); Right := Min(A2,B1); end;
+ end else
+ begin
+ if (B1 < B2) then begin Left := Max(A2,B1); Right := Min(A1,B2); end
+ else begin Left := Max(A2,B2); Right := Min(A1,B1); end
+ end;
+ Result := Left < Right;
+end;
+//------------------------------------------------------------------------------
+
+procedure UpdateOutPtIdxs(OutRec: POutRec);
+var
+ op: POutPt;
+begin
+ op := OutRec.Pts;
+ repeat
+ op.Idx := OutRec.Idx;
+ op := op.Prev;
+ until op = OutRec.Pts;
+end;
+//------------------------------------------------------------------------------
+
+procedure RangeTest(const Pt: TIntPoint; var Use64BitRange: Boolean);
+begin
+ if Use64BitRange then
+ begin
+ if (Pt.X > HiRange) or (Pt.Y > HiRange) or
+ (-Pt.X > HiRange) or (-Pt.Y > HiRange) then
+ raise exception.Create(rsInvalidInt);
+ end
+ else if (Pt.X > LoRange) or (Pt.Y > LoRange) or
+ (-Pt.X > LoRange) or (-Pt.Y > LoRange) then
+ begin
+ Use64BitRange := true;
+ RangeTest(Pt, Use64BitRange);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure ReverseHorizontal(E: PEdge);
+begin
+ //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.]
+ Swap(E.Top.X, E.Bot.X);
+{$IFDEF use_xyz}
+ Swap(E.Top.Z, E.Bot.Z);
+{$ENDIF}
+end;
+//------------------------------------------------------------------------------
+
+procedure InitEdge(E, Next, Prev: PEdge;
+ const Pt: TIntPoint); {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ E.Curr := Pt;
+ E.Next := Next;
+ E.Prev := Prev;
+ E.OutIdx := -1;
+end;
+//------------------------------------------------------------------------------
+
+procedure InitEdge2(E: PEdge; PolyType: TPolyType); {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ if E.Curr.Y >= E.Next.Curr.Y then
+ begin
+ E.Bot := E.Curr;
+ E.Top := E.Next.Curr;
+ end else
+ begin
+ E.Top := E.Curr;
+ E.Bot := E.Next.Curr;
+ end;
+ SetDx(E);
+ E.PolyType := PolyType;
+end;
+//------------------------------------------------------------------------------
+
+function RemoveEdge(E: PEdge): PEdge; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ //removes E from double_linked_list (but without disposing from memory)
+ E.Prev.Next := E.Next;
+ E.Next.Prev := E.Prev;
+ Result := E.Next;
+ E.Prev := nil; //flag as removed (see ClipperBase.Clear)
+end;
+//------------------------------------------------------------------------------
+
+function FindNextLocMin(E: PEdge): PEdge; {$IFDEF INLINING} inline; {$ENDIF}
+var
+ E2: PEdge;
+begin
+ while True do
+ begin
+ while not PointsEqual(E.Bot, E.Prev.Bot) or
+ PointsEqual(E.Curr, E.Top) do E := E.Next;
+ if (E.Dx <> Horizontal) and (E.Prev.Dx <> Horizontal) then break;
+ while (E.Prev.Dx = Horizontal) do E := E.Prev;
+ E2 := E; //E2 == first horizontal
+ while (E.Dx = Horizontal) do E := E.Next;
+ if (E.Top.Y = E.Prev.Bot.Y) then Continue; //ie just an intermediate horz.
+ //E == first edge past horizontals
+ if E2.Prev.Bot.X < E.Bot.X then E := E2;
+ //E is first horizontal when CW and first past horizontals when CCW
+ break;
+ end;
+ Result := E;
+end;
+
+//------------------------------------------------------------------------------
+// TClipperBase methods ...
+//------------------------------------------------------------------------------
+
+constructor TClipperBase.Create;
+begin
+ inherited;
+ FEdgeList := TEgdeList.Create;
+ FLocMinList := TLocMinList.Create;
+ FCurrentLocMinIdx := 0;
+ FUse64BitRange := False; //ie default is False
+end;
+//------------------------------------------------------------------------------
+
+destructor TClipperBase.Destroy;
+begin
+ Clear;
+ FEdgeList.Free;
+ FLocMinList.Free;
+ inherited;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.ProcessBound(E: PEdge; NextIsForward: Boolean): PEdge;
+var
+ EStart, Horz: PEdge;
+ locMin: PLocalMinimum;
+begin
+ Result := E;
+ if (E.OutIdx = Skip) then
+ begin
+ //check if there are edges beyond the skip edge in the bound and if so
+ //create another LocMin and calling ProcessBound once more ...
+ if NextIsForward then
+ begin
+ while (E.Top.Y = E.Next.Bot.Y) do
+ E := E.Next;
+ //don't include top horizontals here ...
+ while (E <> Result) and (E.Dx = Horizontal) do E := E.Prev;
+ end else
+ begin
+ while (E.Top.Y = E.Prev.Bot.Y) do E := E.Prev;
+ while (E <> Result) and (E.Dx = Horizontal) do E := E.Next;
+ end;
+ if E = Result then
+ begin
+ if NextIsForward then Result := E.Next
+ else Result := E.Prev;
+ end else
+ begin
+ if NextIsForward then
+ E := Result.Next else
+ E := Result.Prev;
+ new(locMin);
+ locMin.Y := E.Bot.Y;
+ locMin.LeftBound := nil;
+ locMin.RightBound := E;
+ E.WindDelta := 0;
+ Result := ProcessBound(E, NextIsForward);
+ FLocMinList.Add(locMin);
+ end;
+ Exit;
+ end;
+
+ if (E.Dx = Horizontal) then
+ begin
+ //We need to be careful with open paths because this may not be a
+ //true local minima (ie E may be following a skip edge).
+ //Also, consecutive horz. edges may start heading left before going right.
+ if NextIsForward then EStart := E.Prev
+ else EStart := E.Next;
+ if (EStart.Dx = Horizontal) then
+ begin
+ if (EStart.Bot.X <> E.Bot.X) and (EStart.Top.X <> E.Bot.X) then
+ ReverseHorizontal(E);
+ end
+ else if (EStart.Bot.X <> E.Bot.X) then
+ ReverseHorizontal(E);
+ end;
+
+ EStart := E;
+ if NextIsForward then
+ begin
+ while (Result.Top.Y = Result.Next.Bot.Y) and (Result.Next.OutIdx <> Skip) do
+ Result := Result.Next;
+ if (Result.Dx = Horizontal) and (Result.Next.OutIdx <> Skip) then
+ begin
+ //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) do Horz := Horz.Prev;
+ if (Horz.Prev.Top.X > Result.Next.Top.X) then Result := Horz.Prev;
+ end;
+ while (E <> Result) do
+ begin
+ e.NextInLML := e.Next;
+ if (E.Dx = Horizontal) and (e <> EStart) and
+ (E.Bot.X <> E.Prev.Top.X) then ReverseHorizontal(E);
+ E := E.Next;
+ end;
+ if (e <> EStart) and (E.Dx = Horizontal) and (E.Bot.X <> E.Prev.Top.X) then
+ ReverseHorizontal(E);
+ Result := Result.Next; //move to the edge just beyond current bound
+ end else
+ begin
+ while (Result.Top.Y = Result.Prev.Bot.Y) and (Result.Prev.OutIdx <> Skip) do
+ Result := Result.Prev;
+ if (Result.Dx = Horizontal) and (Result.Prev.OutIdx <> Skip) then
+ begin
+ Horz := Result;
+ while (Horz.Next.Dx = Horizontal) do Horz := Horz.Next;
+ if (Horz.Next.Top.X = Result.Prev.Top.X) or
+ (Horz.Next.Top.X > Result.Prev.Top.X) then Result := Horz.Next;
+ end;
+ while (E <> Result) do
+ begin
+ e.NextInLML := e.Prev;
+ if (e.Dx = Horizontal) and (e <> EStart) and
+ (E.Bot.X <> E.Next.Top.X) then ReverseHorizontal(E);
+ E := E.Prev;
+ end;
+ if (e <> EStart) and (E.Dx = Horizontal) and (E.Bot.X <> E.Next.Top.X) then
+ ReverseHorizontal(E);
+ Result := Result.Prev; //move to the edge just beyond current bound
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.AddPath(const Path: TPath;
+ PolyType: TPolyType; Closed: Boolean): Boolean;
+var
+ I, HighI: Integer;
+ Edges: PEdgeArray;
+ E, E2, EMin, EStart, ELoopStop: PEdge;
+ IsFlat, leftBoundIsForward: Boolean;
+ locMin: PLocalMinimum;
+begin
+{$IFDEF use_lines}
+ if not Closed and (polyType = ptClip) then
+ raise exception.Create(rsOpenPath);
+{$ELSE}
+ if not Closed then raise exception.Create(rsOpenPath2);
+{$ENDIF}
+
+ Result := false;
+ IsFlat := true;
+
+ //1. Basic (first) edge initialization ...
+ HighI := High(Path);
+ if Closed then
+ while (HighI > 0) and PointsEqual(Path[HighI],Path[0]) do Dec(HighI);
+ while (HighI > 0) and PointsEqual(Path[HighI],Path[HighI -1]) do Dec(HighI);
+ if (Closed and (HighI < 2)) or (not Closed and (HighI < 1)) then Exit;
+
+ GetMem(Edges, sizeof(TEdge)*(HighI +1));
+ try
+ FillChar(Edges^, sizeof(TEdge)*(HighI +1), 0);
+ Edges[1].Curr := Path[1];
+ RangeTest(Path[0], FUse64BitRange);
+ RangeTest(Path[HighI], FUse64BitRange);
+ InitEdge(@Edges[0], @Edges[1], @Edges[HighI], Path[0]);
+ InitEdge(@Edges[HighI], @Edges[0], @Edges[HighI-1], Path[HighI]);
+ for I := HighI - 1 downto 1 do
+ begin
+ RangeTest(Path[I], FUse64BitRange);
+ InitEdge(@Edges[I], @Edges[I+1], @Edges[I-1], Path[I]);
+ end;
+ except
+ FreeMem(Edges);
+ raise; //Range test fails
+ end;
+ EStart := @Edges[0];
+
+ //2. Remove duplicate vertices, and (when closed) collinear edges ...
+ E := EStart;
+ ELoopStop := EStart;
+ while (E <> E.Next) do //ie in case loop reduces to a single vertex
+ begin
+ //allow matching start and end points when not Closed ...
+ if PointsEqual(E.Curr, E.Next.Curr) and
+ (Closed or (E.Next <> EStart)) then
+ begin
+ if E = EStart then EStart := E.Next;
+ E := RemoveEdge(E);
+ ELoopStop := E;
+ Continue;
+ end;
+ if (E.Prev = E.Next) then
+ Break //only two vertices
+ else if Closed and
+ SlopesEqual(E.Prev.Curr, E.Curr, E.Next.Curr, FUse64BitRange) and
+ (not FPreserveCollinear or
+ not Pt2IsBetweenPt1AndPt3(E.Prev.Curr, E.Curr, E.Next.Curr)) then
+ begin
+ //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 then EStart := E.Next;
+ E := RemoveEdge(E);
+ E := E.Prev;
+ ELoopStop := E;
+ Continue;
+ end;
+ E := E.Next;
+ //todo - manage open paths which start and end at same point
+ if (E = eLoopStop) then Break;
+ if E = ELoopStop then Break;
+ end;
+
+ if (not Closed and (E = E.Next)) or (Closed and (E.Prev = E.Next)) then
+ begin
+ FreeMem(Edges);
+ Exit;
+ end;
+
+ if not Closed then
+ begin
+ FHasOpenPaths := true;
+ EStart.Prev.OutIdx := Skip;
+ end;
+
+ //3. Do second stage of edge initialization ...
+ E := EStart;
+ repeat
+ InitEdge2(E, polyType);
+ E := E.Next;
+ if IsFlat and (E.Curr.Y <> EStart.Curr.Y) then IsFlat := false;
+ until 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) then
+ begin
+ if Closed then
+ begin
+ FreeMem(Edges);
+ Exit;
+ end;
+ new(locMin);
+ locMin.Y := E.Bot.Y;
+ locMin.LeftBound := nil;
+ locMin.RightBound := E;
+ locMin.RightBound.Side := esRight;
+ locMin.RightBound.WindDelta := 0;
+ while true do
+ begin
+ if E.Bot.X <> E.Prev.Top.X then ReverseHorizontal(E);
+ if E.Next.OutIdx = Skip then break;
+ E.NextInLML := E.Next;
+ E := E.Next;
+ end;
+ FLocMinList.Add(locMin);
+ Result := true;
+ FEdgeList.Add(Edges);
+ Exit;
+ end;
+
+ Result := true;
+ FEdgeList.Add(Edges);
+ EMin := nil;
+
+ //workaround to avoid an endless loop in the while loop below when
+ //open paths have matching start and end points ...
+ if PointsEqual(E.Prev.Bot, E.Prev.Top) then E := E.Next;
+
+ while true do
+ begin
+ E := FindNextLocMin(E);
+ if (E = EMin) then break
+ else if (EMin = nil) then EMin := E;
+
+ //E and E.Prev now share a local minima (left aligned if horizontal).
+ //Compare their slopes to find which starts which bound ...
+ new(locMin);
+ locMin.Y := E.Bot.Y;
+ if (E.Dx < E.Prev.Dx) then
+ begin
+ locMin.LeftBound := E.Prev;
+ locMin.RightBound := E; //can be horz when CW
+ leftBoundIsForward := false; //Q.nextInLML = Q.prev
+ end else
+ begin
+ locMin.LeftBound := E;
+ locMin.RightBound := E.Prev; //can be horz when CCW
+ leftBoundIsForward := true; //Q.nextInLML = Q.next
+ end;
+ locMin.LeftBound.Side := esLeft;
+ locMin.RightBound.Side := esRight;
+
+ if not Closed then locMin.LeftBound.WindDelta := 0
+ else if (locMin.LeftBound.Next = locMin.RightBound) then
+ locMin.LeftBound.WindDelta := -1
+ else locMin.LeftBound.WindDelta := 1;
+ locMin.RightBound.WindDelta := -locMin.LeftBound.WindDelta;
+
+ E := ProcessBound(locMin.LeftBound, leftBoundIsForward);
+ if E.OutIdx = Skip then E := ProcessBound(E, leftBoundIsForward);
+
+ E2 := ProcessBound(locMin.RightBound, not leftBoundIsForward);
+ if E2.OutIdx = Skip then E2 := ProcessBound(E2, not leftBoundIsForward);
+
+ if (locMin.LeftBound.OutIdx = Skip) then locMin.LeftBound := nil
+ else if (locMin.RightBound.OutIdx = Skip) then locMin.RightBound := nil;
+ FLocMinList.Add(locMin);
+
+ if not leftBoundIsForward then E := E2;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipperBase.AddPaths(const Paths: TPaths;
+ PolyType: TPolyType; Closed: Boolean): Boolean;
+var
+ I: Integer;
+begin
+ Result := False;
+ for I := 0 to high(Paths) do
+ if AddPath(Paths[I], PolyType, Closed) then Result := True;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.Clear;
+var
+ I: Integer;
+begin
+ DisposeLocalMinimaList;
+ //dispose of Edges ...
+ for I := 0 to FEdgeList.Count -1 do
+ FreeMem(PEdgeArray(fEdgeList[I]));
+ FEdgeList.Clear;
+
+ FUse64BitRange := False;
+ FHasOpenPaths := False;
+end;
+//------------------------------------------------------------------------------
+
+{$IFNDEF USEGENERICS}
+function LocMinListSort(item1, item2:Pointer): Integer;
+var
+ y: cInt;
+begin
+ y := PLocalMinimum(item2).Y - PLocalMinimum(item1).Y;
+ if y < 0 then result := -1
+ else if y > 0 then result := 1
+ else result := 0;
+end;
+{$ENDIF}
+
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.Reset;
+var
+ i: Integer;
+ Lm: PLocalMinimum;
+begin
+ //Reset() allows various clipping operations to be executed
+ //multiple times on the same polygon sets.
+{$IFDEF USEGENERICS}
+ FLocMinList.Sort(TComparer<PLocalMinimum>.Construct(
+ function (const Item1, Item2 : PLocalMinimum) : integer
+ var
+ y: cInt;
+ begin
+ y := PLocalMinimum(item2).Y - PLocalMinimum(item1).Y;
+ if y < 0 then result := -1
+ else if y > 0 then result := 1
+ else result := 0;
+ end
+ ));
+{$ELSE}
+ FLocMinList.Sort(LocMinListSort);
+{$ENDIF}
+ for i := 0 to FLocMinList.Count -1 do
+ begin
+ Lm := PLocalMinimum(FLocMinList[i]);
+ //resets just the two (L & R) edges attached to each Local Minima ...
+ if assigned(Lm.LeftBound) then
+ with Lm.LeftBound^ do
+ begin
+ Curr := Bot;
+ Side := esLeft;
+ OutIdx := Unassigned;
+ end;
+ if assigned(Lm.RightBound) then
+ with Lm.RightBound^ do
+ begin
+ Curr := Bot;
+ Side := esRight;
+ OutIdx := Unassigned;
+ end;
+ end;
+ FCurrentLocMinIdx := 0;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DisposePolyPts(PP: POutPt);
+var
+ TmpPp: POutPt;
+begin
+ PP.Prev.Next := nil;
+ while Assigned(PP) do
+ begin
+ TmpPp := PP;
+ PP := PP.Next;
+ dispose(TmpPp);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperBase.DisposeLocalMinimaList;
+var
+ i: Integer;
+begin
+ for i := 0 to FLocMinList.Count -1 do
+ Dispose(PLocalMinimum(FLocMinList[i]));
+ FLocMinList.Clear;
+ FCurrentLocMinIdx := 0;
+end;
+
+//------------------------------------------------------------------------------
+// TClipper methods ...
+//------------------------------------------------------------------------------
+
+constructor TClipper.Create(InitOptions: TInitOptions = []);
+begin
+ inherited Create;
+ FJoinList := TJoinList.Create;
+ FGhostJoinList := TJoinList.Create;
+ FPolyOutList := TPolyOutList.Create;
+ FIntersectList := TIntersecList.Create;
+ if ioReverseSolution in InitOptions then
+ FReverseOutput := true;
+ if ioStrictlySimple in InitOptions then
+ FStrictSimple := true;
+ if ioPreserveCollinear in InitOptions then
+ FPreserveCollinear := true;
+end;
+//------------------------------------------------------------------------------
+
+destructor TClipper.Destroy;
+begin
+ inherited; //this must be first since inherited Destroy calls Clear.
+ DisposeScanbeamList;
+ FJoinList.Free;
+ FGhostJoinList.Free;
+ FPolyOutList.Free;
+ FIntersectList.Free;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DisposeScanbeamList;
+var
+ SB: PScanbeam;
+begin
+ while Assigned(fScanbeam) do
+ begin
+ SB := FScanbeam.Next;
+ Dispose(fScanbeam);
+ FScanbeam := SB;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.Reset;
+var
+ i: Integer;
+begin
+ inherited Reset;
+ FScanbeam := nil;
+ for i := 0 to FLocMinList.Count -1 do
+ InsertScanbeam(PLocalMinimum(FLocMinList[i]).Y);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.Execute(clipType: TClipType;
+ out solution: TPaths;
+ FillType: TPolyFillType = pftEvenOdd): Boolean;
+begin
+ Result := Execute(clipType, solution, FillType, FillType);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.Execute(clipType: TClipType;
+ out solution: TPaths;
+ subjFillType: TPolyFillType; clipFillType: TPolyFillType): Boolean;
+begin
+ Result := False;
+ solution := nil;
+ if FExecuteLocked then Exit;
+ //nb: Open paths can only be returned via the PolyTree structure ...
+ if HasOpenPaths then raise Exception.Create(rsOpenPath3);
+ try try
+ FExecuteLocked := True;
+ FSubjFillType := subjFillType;
+ FClipFillType := clipFillType;
+ FClipType := clipType;
+ FUsingPolyTree := False;
+ Result := ExecuteInternal;
+ solution := BuildResult;
+ except
+ solution := nil;
+ Result := False;
+ end;
+ finally
+ DisposeAllOutRecs;
+ FExecuteLocked := False;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.Execute(clipType: TClipType;
+ out PolyTree: TPolyTree;
+ FillType: TPolyFillType = pftEvenOdd): Boolean;
+begin
+ Result := Execute(clipType, PolyTree, FillType, FillType);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.Execute(clipType: TClipType;
+ out PolyTree: TPolyTree;
+ subjFillType: TPolyFillType;
+ clipFillType: TPolyFillType): Boolean;
+begin
+ Result := False;
+ if FExecuteLocked or not Assigned(PolyTree) then Exit;
+ try try
+ FExecuteLocked := True;
+ FSubjFillType := subjFillType;
+ FClipFillType := clipFillType;
+ FClipType := clipType;
+ FUsingPolyTree := True;
+ Result := ExecuteInternal and BuildResult2(PolyTree);
+ except
+ Result := False;
+ end;
+ finally
+ DisposeAllOutRecs;
+ FExecuteLocked := False;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.FixHoleLinkage(OutRec: POutRec);
+var
+ orfl: POutRec;
+begin
+ //skip if it's an outermost polygon or if FirstLeft
+ //already points to the outer/owner polygon ...
+ if not Assigned(OutRec.FirstLeft) or
+ ((OutRec.IsHole <> OutRec.FirstLeft.IsHole) and
+ Assigned(OutRec.FirstLeft.Pts)) then Exit;
+ orfl := OutRec.FirstLeft;
+ while Assigned(orfl) and
+ ((orfl.IsHole = OutRec.IsHole) or not Assigned(orfl.Pts)) do
+ orfl := orfl.FirstLeft;
+ OutRec.FirstLeft := orfl;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.ExecuteInternal: Boolean;
+var
+ I: Integer;
+ OutRec: POutRec;
+ BotY, TopY: cInt;
+begin
+ try
+ Reset;
+ Result := Assigned(fScanbeam);
+ if not Result then Exit;
+
+ BotY := PopScanbeam;
+ repeat
+ InsertLocalMinimaIntoAEL(BotY);
+ ProcessHorizontals;
+ ClearGhostJoins;
+ if not assigned(FScanbeam) then Break;
+ TopY := PopScanbeam;
+ if not ProcessIntersections(TopY) then Exit;
+ ProcessEdgesAtTopOfScanbeam(TopY);
+ BotY := TopY;
+ until not assigned(FScanbeam) and (FCurrentLocMinIdx >= FLocMinList.Count);
+
+ //fix orientations ...
+ for I := 0 to FPolyOutList.Count -1 do
+ begin
+ OutRec := FPolyOutList[I];
+ if Assigned(OutRec.Pts) and not OutRec.IsOpen and
+ ((OutRec.IsHole xor FReverseOutput) = (Area(OutRec) > 0)) then
+ ReversePolyPtLinks(OutRec.Pts);
+ end;
+
+ if FJoinList.count > 0 then JoinCommonEdges;
+
+ //unfortunately FixupOutPolygon() must be done after JoinCommonEdges ...
+ for I := 0 to FPolyOutList.Count -1 do
+ begin
+ OutRec := FPolyOutList[I];
+ if not Assigned(OutRec.Pts) then continue;
+ if OutRec.IsOpen then
+ FixupOutPolyline(OutRec)
+ else
+ FixupOutPolygon(OutRec);
+ end;
+
+ if FStrictSimple then DoSimplePolygons;
+ Result := True;
+ finally
+ ClearJoins;
+ ClearGhostJoins;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.InsertScanbeam(const Y: cInt);
+var
+ newSb, sb: PScanbeam;
+begin
+ //single-linked list: sorted descending, ignoring dups.
+ new(newSb);
+ newSb.Y := Y;
+ if not Assigned(fScanbeam) then
+ begin
+ FScanbeam := newSb;
+ newSb.Next := nil;
+ end else if Y > FScanbeam.Y then
+ begin
+ newSb.Next := FScanbeam;
+ FScanbeam := newSb;
+ end else
+ begin
+ sb := FScanbeam;
+ while Assigned(sb.Next) and (Y <= sb.Next.Y) do sb := sb.Next;
+ if Y <> sb.Y then
+ begin
+ newSb.Next := sb.Next;
+ sb.Next := newSb;
+ end
+ else dispose(newSb);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.PopScanbeam: cInt;
+var
+ Sb: PScanbeam;
+begin
+ Result := FScanbeam.Y;
+ Sb := FScanbeam;
+ FScanbeam := FScanbeam.Next;
+ dispose(Sb);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.InsertMaxima(const X: cInt);
+var
+ newMax, m: PMaxima;
+begin
+ //double-linked list: sorted ascending, ignoring dups.
+ new(newMax);
+ newMax.X := X;
+ if not Assigned(FMaxima) then
+ begin
+ FMaxima := newMax;
+ newMax.Next := nil;
+ newMax.Prev := nil;
+ end else if X < FMaxima.X then
+ begin
+ newMax.Next := FMaxima;
+ newMax.Prev := nil;
+ FMaxima.Prev := newMax;
+ FMaxima := newMax;
+ end else
+ begin
+ m := FMaxima;
+ while Assigned(m.Next) and (X >= m.Next.X) do m := m.Next;
+ if X <> m.X then
+ begin
+ //insert m1 between m2 and m2.Next ...
+ newMax.Next := m.Next;
+ newMax.Prev := m;
+ if assigned(m.Next) then m.Next.Prev := newMax;
+ m.Next := newMax;
+ end
+ else dispose(newMax);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DisposeMaximaList;
+var
+ m: PMaxima;
+begin
+ while Assigned(FMaxima) do
+ begin
+ m := FMaxima.Next;
+ Dispose(FMaxima);
+ FMaxima := m;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DisposeAllOutRecs;
+var
+ I: Integer;
+begin
+ for I := 0 to FPolyOutList.Count -1 do DisposeOutRec(I);
+ FPolyOutList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DisposeOutRec(Index: Integer);
+var
+ OutRec: POutRec;
+begin
+ OutRec := FPolyOutList[Index];
+ if Assigned(OutRec.Pts) then DisposePolyPts(OutRec.Pts);
+ Dispose(OutRec);
+ FPolyOutList[Index] := nil;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.SetWindingCount(Edge: PEdge);
+var
+ E, E2: PEdge;
+ Inside: Boolean;
+begin
+ E := Edge.PrevInAEL;
+ //find the Edge of the same PolyType that immediately preceeds 'Edge' in AEL
+ while Assigned(E) and ((E.PolyType <> Edge.PolyType) or (E.WindDelta = 0)) do
+ E := E.PrevInAEL;
+ if not Assigned(E) then
+ begin
+ if Edge.WindDelta = 0 then Edge.WindCnt := 1
+ else Edge.WindCnt := Edge.WindDelta;
+ Edge.WindCnt2 := 0;
+ E := FActiveEdges; //ie get ready to calc WindCnt2
+ end
+ else if (Edge.WindDelta = 0) and (FClipType <> ctUnion) then
+ begin
+ Edge.WindCnt := 1;
+ Edge.WindCnt2 := E.WindCnt2;
+ E := E.NextInAEL; //ie get ready to calc WindCnt2
+ end
+ else if IsEvenOddFillType(Edge) then
+ begin
+ //even-odd filling ...
+ if (Edge.WindDelta = 0) then //if edge is part of a line
+ begin
+ //are we inside a subj polygon ...
+ Inside := true;
+ E2 := E.PrevInAEL;
+ while assigned(E2) do
+ begin
+ if (E2.PolyType = E.PolyType) and (E2.WindDelta <> 0) then
+ Inside := not Inside;
+ E2 := E2.PrevInAEL;
+ end;
+ if Inside then Edge.WindCnt := 0
+ else Edge.WindCnt := 1;
+ end
+ else //else a polygon
+ begin
+ Edge.WindCnt := Edge.WindDelta;
+ end;
+ Edge.WindCnt2 := E.WindCnt2;
+ E := E.NextInAEL; //ie get ready to calc WindCnt2
+ end else
+ begin
+ //NonZero, Positive, or Negative filling ...
+ if (E.WindCnt * E.WindDelta < 0) then
+ begin
+ //prev edge is 'decreasing' WindCount (WC) toward zero
+ //so we're outside the previous polygon ...
+ if (Abs(E.WindCnt) > 1) then
+ begin
+ //outside prev poly but still inside another.
+ //when reversing direction of prev poly use the same WC
+ if (E.WindDelta * Edge.WindDelta < 0) then
+ Edge.WindCnt := E.WindCnt
+ //otherwise continue to 'decrease' WC ...
+ else Edge.WindCnt := E.WindCnt + Edge.WindDelta;
+ end
+ else
+ //now outside all polys of same polytype so set own WC ...
+ if Edge.WindDelta = 0 then Edge.WindCnt := 1
+ else Edge.WindCnt := Edge.WindDelta;
+ end else
+ begin
+ //prev edge is 'increasing' WindCount (WC) away from zero
+ //so we're inside the previous polygon ...
+ if (Edge.WindDelta = 0) then
+ begin
+ if (E.WindCnt < 0) then Edge.WindCnt := E.WindCnt -1
+ else Edge.WindCnt := E.WindCnt +1;
+ end
+ //if wind direction is reversing prev then use same WC
+ else if (E.WindDelta * Edge.WindDelta < 0) then
+ Edge.WindCnt := E.WindCnt
+ //otherwise add to WC ...
+ else Edge.WindCnt := E.WindCnt + Edge.WindDelta;
+ end;
+ Edge.WindCnt2 := E.WindCnt2;
+ E := E.NextInAEL; //ie get ready to calc WindCnt2
+ end;
+
+ //update WindCnt2 ...
+ if IsEvenOddAltFillType(Edge) then
+ begin
+ //even-odd filling ...
+ while (E <> Edge) do
+ begin
+ if E.WindDelta = 0 then //do nothing (ie ignore lines)
+ else if Edge.WindCnt2 = 0 then Edge.WindCnt2 := 1
+ else Edge.WindCnt2 := 0;
+ E := E.NextInAEL;
+ end;
+ end else
+ begin
+ //NonZero, Positive, or Negative filling ...
+ while (E <> Edge) do
+ begin
+ Inc(Edge.WindCnt2, E.WindDelta);
+ E := E.NextInAEL;
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.IsEvenOddFillType(Edge: PEdge): Boolean;
+begin
+ if Edge.PolyType = ptSubject then
+ Result := FSubjFillType = pftEvenOdd else
+ Result := FClipFillType = pftEvenOdd;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.IsEvenOddAltFillType(Edge: PEdge): Boolean;
+begin
+ if Edge.PolyType = ptSubject then
+ Result := FClipFillType = pftEvenOdd else
+ Result := FSubjFillType = pftEvenOdd;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.IsContributing(Edge: PEdge): Boolean;
+var
+ Pft, Pft2: TPolyFillType;
+begin
+ if Edge.PolyType = ptSubject then
+ begin
+ Pft := FSubjFillType;
+ Pft2 := FClipFillType;
+ end else
+ begin
+ Pft := FClipFillType;
+ Pft2 := FSubjFillType
+ end;
+
+ case Pft of
+ pftEvenOdd: Result := (Edge.WindDelta <> 0) or (Edge.WindCnt = 1);
+ pftNonZero: Result := abs(Edge.WindCnt) = 1;
+ pftPositive: Result := (Edge.WindCnt = 1);
+ else Result := (Edge.WindCnt = -1);
+ end;
+ if not Result then Exit;
+
+ case FClipType of
+ ctIntersection:
+ case Pft2 of
+ pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 <> 0);
+ pftPositive: Result := (Edge.WindCnt2 > 0);
+ pftNegative: Result := (Edge.WindCnt2 < 0);
+ end;
+ ctUnion:
+ case Pft2 of
+ pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0);
+ pftPositive: Result := (Edge.WindCnt2 <= 0);
+ pftNegative: Result := (Edge.WindCnt2 >= 0);
+ end;
+ ctDifference:
+ if Edge.PolyType = ptSubject then
+ case Pft2 of
+ pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0);
+ pftPositive: Result := (Edge.WindCnt2 <= 0);
+ pftNegative: Result := (Edge.WindCnt2 >= 0);
+ end
+ else
+ case Pft2 of
+ pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 <> 0);
+ pftPositive: Result := (Edge.WindCnt2 > 0);
+ pftNegative: Result := (Edge.WindCnt2 < 0);
+ end;
+ ctXor:
+ if Edge.WindDelta = 0 then //XOr always contributing unless open
+ case Pft2 of
+ pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0);
+ pftPositive: Result := (Edge.WindCnt2 <= 0);
+ pftNegative: Result := (Edge.WindCnt2 >= 0);
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.AddLocalMinPoly(E1, E2: PEdge; const Pt: TIntPoint): POutPt;
+var
+ E, prevE: PEdge;
+ OutPt: POutPt;
+begin
+ if (E2.Dx = Horizontal) or (E1.Dx > E2.Dx) then
+ begin
+ Result := AddOutPt(E1, Pt);
+ E2.OutIdx := E1.OutIdx;
+ E1.Side := esLeft;
+ E2.Side := esRight;
+ E := E1;
+ if E.PrevInAEL = E2 then
+ prevE := E2.PrevInAEL
+ else
+ prevE := E.PrevInAEL;
+ end else
+ begin
+ Result := AddOutPt(E2, Pt);
+ E1.OutIdx := E2.OutIdx;
+ E1.Side := esRight;
+ E2.Side := esLeft;
+
+ E := E2;
+ if E.PrevInAEL = E1 then
+ prevE := E1.PrevInAEL
+ else
+ prevE := E.PrevInAEL;
+ end;
+
+ if Assigned(prevE) and (prevE.OutIdx >= 0) and
+ (TopX(prevE, Pt.Y) = TopX(E, Pt.Y)) and
+ SlopesEqual(E, prevE, FUse64BitRange) and
+ (E.WindDelta <> 0) and (prevE.WindDelta <> 0) then
+ begin
+ OutPt := AddOutPt(prevE, Pt);
+ AddJoin(Result, OutPt, E.Top);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.AddLocalMaxPoly(E1, E2: PEdge; const Pt: TIntPoint);
+begin
+ AddOutPt(E1, Pt);
+ if E2.WindDelta = 0 then AddOutPt(E2, Pt);
+ if (E1.OutIdx = E2.OutIdx) then
+ begin
+ E1.OutIdx := Unassigned;
+ E2.OutIdx := Unassigned;
+ end
+ else if E1.OutIdx < E2.OutIdx then
+ AppendPolygon(E1, E2)
+ else
+ AppendPolygon(E2, E1);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.AddEdgeToSEL(Edge: PEdge);
+begin
+ //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 not Assigned(FSortedEdges) then
+ begin
+ FSortedEdges := Edge;
+ Edge.PrevInSEL := nil;
+ Edge.NextInSEL := nil;
+ end else
+ begin
+ Edge.NextInSEL := FSortedEdges;
+ Edge.PrevInSEL := nil;
+ FSortedEdges.PrevInSEL := Edge;
+ FSortedEdges := Edge;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.CopyAELToSEL;
+var
+ E: PEdge;
+begin
+ E := FActiveEdges;
+ FSortedEdges := E;
+ while Assigned(E) do
+ begin
+ E.PrevInSEL := E.PrevInAEL;
+ E.NextInSEL := E.NextInAEL;
+ E := E.NextInAEL;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.AddJoin(Op1, Op2: POutPt; const OffPt: TIntPoint);
+var
+ Jr: PJoin;
+begin
+ new(Jr);
+ Jr.OutPt1 := Op1;
+ Jr.OutPt2 := Op2;
+ Jr.OffPt := OffPt;
+ FJoinList.add(Jr);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.ClearJoins;
+var
+ I: Integer;
+begin
+ for I := 0 to FJoinList.count -1 do
+ Dispose(PJoin(fJoinList[I]));
+ FJoinList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.AddGhostJoin(OutPt: POutPt; const OffPt: TIntPoint);
+var
+ Jr: PJoin;
+begin
+ //Ghost joins are used to find horizontal edges at the top of one scanbeam
+ //that coincide with horizontal edges at the bottom of the next. Ghost joins
+ //are converted to real joins when match ups occur.
+ new(Jr);
+ Jr.OutPt1 := OutPt;
+ Jr.OffPt := OffPt;
+ FGhostJoinList.Add(Jr);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.ClearGhostJoins;
+var
+ I: Integer;
+begin
+ for I := 0 to FGhostJoinList.Count -1 do
+ Dispose(PJoin(FGhostJoinList[I]));
+ FGhostJoinList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure SwapPoints(var Pt1, Pt2: TIntPoint);
+var
+ Tmp: TIntPoint;
+begin
+ Tmp := Pt1;
+ Pt1 := Pt2;
+ Pt2 := Tmp;
+end;
+//------------------------------------------------------------------------------
+
+function HorzSegmentsOverlap(seg1a, seg1b, seg2a, seg2b: cInt): Boolean;
+begin
+ if (seg1a > seg1b) then Swap(seg1a, seg1b);
+ if (seg2a > seg2b) then Swap(seg2a, seg2b);
+ Result := (seg1a < seg2b) and (seg2a < seg1b);
+end;
+//------------------------------------------------------------------------------
+
+function E2InsertsBeforeE1(E1, E2: PEdge): Boolean;
+ {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ if E2.Curr.X = E1.Curr.X then
+ begin
+ if E2.Top.Y > E1.Top.Y then
+ Result := E2.Top.X < TopX(E1, E2.Top.Y) else
+ Result := E1.Top.X > TopX(E2, E1.Top.Y);
+ end else
+ Result := E2.Curr.X < E1.Curr.X;
+end;
+//----------------------------------------------------------------------
+
+procedure TClipper.InsertLocalMinimaIntoAEL(const BotY: cInt);
+
+ procedure InsertEdgeIntoAEL(Edge, StartEdge: PEdge);
+ begin
+ if not Assigned(FActiveEdges) then
+ begin
+ Edge.PrevInAEL := nil;
+ Edge.NextInAEL := nil;
+ FActiveEdges := Edge;
+ end
+ else if not Assigned(StartEdge) and
+ E2InsertsBeforeE1(FActiveEdges, Edge) then
+ begin
+ Edge.PrevInAEL := nil;
+ Edge.NextInAEL := FActiveEdges;
+ FActiveEdges.PrevInAEL := Edge;
+ FActiveEdges := Edge;
+ end else
+ begin
+ if not Assigned(StartEdge) then StartEdge := FActiveEdges;
+ while Assigned(StartEdge.NextInAEL) and
+ not E2InsertsBeforeE1(StartEdge.NextInAEL, Edge) do
+ StartEdge := StartEdge.NextInAEL;
+ Edge.NextInAEL := StartEdge.NextInAEL;
+ if Assigned(StartEdge.NextInAEL) then
+ StartEdge.NextInAEL.PrevInAEL := Edge;
+ Edge.PrevInAEL := StartEdge;
+ StartEdge.NextInAEL := Edge;
+ end;
+ end;
+ //----------------------------------------------------------------------
+
+var
+ I: Integer;
+ E: PEdge;
+ Lb, Rb: PEdge;
+ Jr: PJoin;
+ Op1, Op2: POutPt;
+ LocMin: PLocalMinimum;
+begin
+
+ while (FCurrentLocMinIdx < FLocMinList.Count) do
+ begin
+ LocMin := PLocalMinimum(FLocMinList[FCurrentLocMinIdx]);
+ if (LocMin.Y <> BotY) then break;
+ inc(FCurrentLocMinIdx);
+
+ Lb := LocMin.LeftBound;
+ Rb := LocMin.RightBound;
+ Op1 := nil;
+ if not assigned(Lb) then
+ begin
+ InsertEdgeIntoAEL(Rb, nil);
+ SetWindingCount(Rb);
+ if IsContributing(Rb) then
+ Op1 := AddOutPt(Rb, Rb.Bot);
+ end
+ else if not assigned(Rb) then
+ begin
+ InsertEdgeIntoAEL(Lb, nil);
+ SetWindingCount(Lb);
+ if IsContributing(Lb) then
+ Op1 := AddOutPt(Lb, Lb.Bot);
+ InsertScanbeam(Lb.Top.Y);
+ end else
+ begin
+ InsertEdgeIntoAEL(Lb, nil);
+ InsertEdgeIntoAEL(Rb, Lb);
+ SetWindingCount(Lb);
+ Rb.WindCnt := Lb.WindCnt;
+ Rb.WindCnt2 := Lb.WindCnt2;
+ if IsContributing(Lb) then
+ Op1 := AddLocalMinPoly(Lb, Rb, Lb.Bot);
+ InsertScanbeam(Lb.Top.Y);
+ end;
+
+ if Assigned(Rb) then
+ begin
+ if (Rb.Dx = Horizontal) then
+ AddEdgeToSEL(Rb) else
+ InsertScanbeam(Rb.Top.Y);
+ end;
+
+ if not assigned(Lb) or not assigned(Rb) then Continue;
+
+ //if output polygons share an Edge with rb, they'll need joining later ...
+ if assigned(Op1) and (Rb.Dx = Horizontal) and
+ (FGhostJoinList.Count > 0) and (Rb.WindDelta <> 0) then
+ begin
+ for I := 0 to FGhostJoinList.Count -1 do
+ begin
+ //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
+ //the 'ghost' join to a real join ready for later ...
+ Jr := PJoin(FGhostJoinList[I]);
+ if HorzSegmentsOverlap(Jr.OutPt1.Pt.X, Jr.OffPt.X,
+ Rb.Bot.X, Rb.Top.X) then
+ AddJoin(Jr.OutPt1, Op1, Jr.OffPt);
+ end;
+ end;
+
+ if (Lb.OutIdx >= 0) and assigned(Lb.PrevInAEL) and
+ (Lb.PrevInAEL.Curr.X = Lb.Bot.X) and
+ (Lb.PrevInAEL.OutIdx >= 0) and
+ SlopesEqual(Lb.PrevInAEL, Lb, FUse64BitRange) and
+ (Lb.WindDelta <> 0) and (Lb.PrevInAEL.WindDelta <> 0) then
+ begin
+ Op2 := AddOutPt(Lb.PrevInAEL, Lb.Bot);
+ AddJoin(Op1, Op2, Lb.Top);
+ end;
+
+ if (Lb.NextInAEL <> Rb) then
+ begin
+ if (Rb.OutIdx >= 0) and (Rb.PrevInAEL.OutIdx >= 0) and
+ SlopesEqual(Rb.PrevInAEL, Rb, FUse64BitRange) and
+ (Rb.WindDelta <> 0) and (Rb.PrevInAEL.WindDelta <> 0) then
+ begin
+ Op2 := AddOutPt(Rb.PrevInAEL, Rb.Bot);
+ AddJoin(Op1, Op2, Rb.Top);
+ end;
+
+ E := Lb.NextInAEL;
+ if Assigned(E) then
+ while (E <> Rb) do
+ begin
+ //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);
+ E := E.NextInAEL;
+ end;
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DeleteFromAEL(E: PEdge);
+var
+ AelPrev, AelNext: PEdge;
+begin
+ AelPrev := E.PrevInAEL;
+ AelNext := E.NextInAEL;
+ if not Assigned(AelPrev) and not Assigned(AelNext) and
+ (E <> FActiveEdges) then Exit; //already deleted
+ if Assigned(AelPrev) then AelPrev.NextInAEL := AelNext
+ else FActiveEdges := AelNext;
+ if Assigned(AelNext) then AelNext.PrevInAEL := AelPrev;
+ E.NextInAEL := nil;
+ E.PrevInAEL := nil;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DeleteFromSEL(E: PEdge);
+var
+ SelPrev, SelNext: PEdge;
+begin
+ SelPrev := E.PrevInSEL;
+ SelNext := E.NextInSEL;
+ if not Assigned(SelPrev) and not Assigned(SelNext) and
+ (E <> FSortedEdges) then Exit; //already deleted
+ if Assigned(SelPrev) then SelPrev.NextInSEL := SelNext
+ else FSortedEdges := SelNext;
+ if Assigned(SelNext) then SelNext.PrevInSEL := SelPrev;
+ E.NextInSEL := nil;
+ E.PrevInSEL := nil;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.IntersectEdges(E1,E2: PEdge; Pt: TIntPoint);
+var
+ E1Contributing, E2contributing: Boolean;
+ E1FillType, E2FillType, E1FillType2, E2FillType2: TPolyFillType;
+ E1Wc, E2Wc, E1Wc2, E2Wc2: Integer;
+begin
+ {IntersectEdges}
+ //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 ...
+
+ E1Contributing := (E1.OutIdx >= 0);
+ E2contributing := (E2.OutIdx >= 0);
+
+{$IFDEF use_xyz}
+ SetZ(Pt, E1, E2, FZFillCallback);
+{$ENDIF}
+
+{$IFDEF use_lines}
+ //if either edge is on an OPEN path ...
+ if (E1.WindDelta = 0) or (E2.WindDelta = 0) then
+ begin
+ //ignore subject-subject open path intersections ...
+ if (E1.WindDelta = 0) AND (E2.WindDelta = 0) then Exit
+ //if intersecting a subj line with a subj poly ...
+ else if (E1.PolyType = E2.PolyType) and
+ (E1.WindDelta <> E2.WindDelta) and (FClipType = ctUnion) then
+ begin
+ if (E1.WindDelta = 0) then
+ begin
+ if (E2Contributing) then
+ begin
+ AddOutPt(E1, pt);
+ if (E1Contributing) then E1.OutIdx := Unassigned;
+ end;
+ end else
+ begin
+ if (E1Contributing) then
+ begin
+ AddOutPt(E2, pt);
+ if (E2Contributing) then E2.OutIdx := Unassigned;
+ end;
+ end;
+ end
+ else if (E1.PolyType <> E2.PolyType) then
+ begin
+ //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) = 1 ...
+ if (E1.WindDelta = 0) and (Abs(E2.WindCnt) = 1) and
+ ((FClipType <> ctUnion) or (E2.WindCnt2 = 0)) then
+ begin
+ AddOutPt(E1, Pt);
+ if E1Contributing then E1.OutIdx := Unassigned;
+ end
+ else if (E2.WindDelta = 0) and (Abs(E1.WindCnt) = 1) and
+ ((FClipType <> ctUnion) or (E1.WindCnt2 = 0)) then
+ begin
+ AddOutPt(E2, Pt);
+ if E2Contributing then E2.OutIdx := Unassigned;
+ end
+ end;
+ Exit;
+ end;
+{$ENDIF}
+
+ //update winding counts...
+ //assumes that E1 will be to the right of E2 ABOVE the intersection
+ if E1.PolyType = E2.PolyType then
+ begin
+ if IsEvenOddFillType(E1) then
+ begin
+ E1Wc := E1.WindCnt;
+ E1.WindCnt := E2.WindCnt;
+ E2.WindCnt := E1Wc;
+ end else
+ begin
+ if E1.WindCnt + E2.WindDelta = 0 then
+ E1.WindCnt := -E1.WindCnt else
+ Inc(E1.WindCnt, E2.WindDelta);
+ if E2.WindCnt - E1.WindDelta = 0 then
+ E2.WindCnt := -E2.WindCnt else
+ Dec(E2.WindCnt, E1.WindDelta);
+ end;
+ end else
+ begin
+ if not IsEvenOddFillType(E2) then Inc(E1.WindCnt2, E2.WindDelta)
+ else if E1.WindCnt2 = 0 then E1.WindCnt2 := 1
+ else E1.WindCnt2 := 0;
+
+ if not IsEvenOddFillType(E1) then Dec(E2.WindCnt2, E1.WindDelta)
+ else if E2.WindCnt2 = 0 then E2.WindCnt2 := 1
+ else E2.WindCnt2 := 0;
+ end;
+
+ if E1.PolyType = ptSubject then
+ begin
+ E1FillType := FSubjFillType;
+ E1FillType2 := FClipFillType;
+ end else
+ begin
+ E1FillType := FClipFillType;
+ E1FillType2 := FSubjFillType;
+ end;
+ if E2.PolyType = ptSubject then
+ begin
+ E2FillType := FSubjFillType;
+ E2FillType2 := FClipFillType;
+ end else
+ begin
+ E2FillType := FClipFillType;
+ E2FillType2 := FSubjFillType;
+ end;
+
+ case E1FillType of
+ pftPositive: E1Wc := E1.WindCnt;
+ pftNegative : E1Wc := -E1.WindCnt;
+ else E1Wc := abs(E1.WindCnt);
+ end;
+ case E2FillType of
+ pftPositive: E2Wc := E2.WindCnt;
+ pftNegative : E2Wc := -E2.WindCnt;
+ else E2Wc := abs(E2.WindCnt);
+ end;
+
+ if E1Contributing and E2contributing then
+ begin
+ if not (E1Wc in [0,1]) or not (E2Wc in [0,1]) or
+ ((E1.PolyType <> E2.PolyType) and (fClipType <> ctXor)) then
+ begin
+ AddLocalMaxPoly(E1, E2, Pt);
+ end else
+ begin
+ AddOutPt(E1, Pt);
+ AddOutPt(E2, Pt);
+ SwapSides(E1, E2);
+ SwapPolyIndexes(E1, E2);
+ end;
+ end else if E1Contributing then
+ begin
+ if (E2Wc = 0) or (E2Wc = 1) then
+ begin
+ AddOutPt(E1, Pt);
+ SwapSides(E1, E2);
+ SwapPolyIndexes(E1, E2);
+ end;
+ end
+ else if E2contributing then
+ begin
+ if (E1Wc = 0) or (E1Wc = 1) then
+ begin
+ AddOutPt(E2, Pt);
+ SwapSides(E1, E2);
+ SwapPolyIndexes(E1, E2);
+ end;
+ end
+ else if ((E1Wc = 0) or (E1Wc = 1)) and ((E2Wc = 0) or (E2Wc = 1)) then
+ begin
+ //neither Edge is currently contributing ...
+
+ case E1FillType2 of
+ pftPositive: E1Wc2 := E1.WindCnt2;
+ pftNegative : E1Wc2 := -E1.WindCnt2;
+ else E1Wc2 := abs(E1.WindCnt2);
+ end;
+ case E2FillType2 of
+ pftPositive: E2Wc2 := E2.WindCnt2;
+ pftNegative : E2Wc2 := -E2.WindCnt2;
+ else E2Wc2 := abs(E2.WindCnt2);
+ end;
+
+ if (E1.PolyType <> E2.PolyType) then
+ begin
+ AddLocalMinPoly(E1, E2, Pt);
+ end
+ else if (E1Wc = 1) and (E2Wc = 1) then
+ case FClipType of
+ ctIntersection:
+ if (E1Wc2 > 0) and (E2Wc2 > 0) then
+ AddLocalMinPoly(E1, E2, Pt);
+ ctUnion:
+ if (E1Wc2 <= 0) and (E2Wc2 <= 0) then
+ AddLocalMinPoly(E1, E2, Pt);
+ ctDifference:
+ if ((E1.PolyType = ptClip) and (E1Wc2 > 0) and (E2Wc2 > 0)) or
+ ((E1.PolyType = ptSubject) and (E1Wc2 <= 0) and (E2Wc2 <= 0)) then
+ AddLocalMinPoly(E1, E2, Pt);
+ ctXor:
+ AddLocalMinPoly(E1, E2, Pt);
+ end
+ else
+ swapsides(E1,E2);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function FirstParamIsBottomPt(btmPt1, btmPt2: POutPt): Boolean;
+var
+ Dx1n, Dx1p, Dx2n, Dx2p: Double;
+ P: POutPt;
+begin
+ //Precondition: bottom-points share the same vertex.
+ //Use inverse slopes of adjacent edges (ie dx/dy) to determine the outer
+ //polygon and hence the 'real' bottompoint.
+ //nb: Slope is vertical when dx == 0. If the greater abs(dx) of param1
+ //is greater than or equal both abs(dx) in param2 then param1 is outer.
+ P := btmPt1.Prev;
+ while PointsEqual(P.Pt, btmPt1.Pt) and (P <> btmPt1) do P := P.Prev;
+ Dx1p := abs(GetDx(btmPt1.Pt, P.Pt));
+ P := btmPt1.Next;
+ while PointsEqual(P.Pt, btmPt1.Pt) and (P <> btmPt1) do P := P.Next;
+ Dx1n := abs(GetDx(btmPt1.Pt, P.Pt));
+
+ P := btmPt2.Prev;
+ while PointsEqual(P.Pt, btmPt2.Pt) and (P <> btmPt2) do P := P.Prev;
+ Dx2p := abs(GetDx(btmPt2.Pt, P.Pt));
+ P := btmPt2.Next;
+ while PointsEqual(P.Pt, btmPt2.Pt) and (P <> btmPt2) do P := P.Next;
+ Dx2n := abs(GetDx(btmPt2.Pt, P.Pt));
+ Result := ((Dx1p >= Dx2p) and (Dx1p >= Dx2n)) or
+ ((Dx1n >= Dx2p) and (Dx1n >= Dx2n));
+end;
+//------------------------------------------------------------------------------
+
+function GetBottomPt(PP: POutPt): POutPt;
+var
+ P, Dups: POutPt;
+begin
+ Dups := nil;
+ P := PP.Next;
+ while P <> PP do
+ begin
+ if P.Pt.Y > PP.Pt.Y then
+ begin
+ PP := P;
+ Dups := nil;
+ end
+ else if (P.Pt.Y = PP.Pt.Y) and (P.Pt.X <= PP.Pt.X) then
+ begin
+ if (P.Pt.X < PP.Pt.X) then
+ begin
+ Dups := nil;
+ PP := P;
+ end else
+ begin
+ if (P.Next <> PP) and (P.Prev <> PP) then Dups := P;
+ end;
+ end;
+ P := P.Next;
+ end;
+ if Assigned(Dups) then
+ begin
+ //there appears to be at least 2 vertices at bottom-most point so ...
+ while Dups <> P do
+ begin
+ if not FirstParamIsBottomPt(P, Dups) then PP := Dups;
+ Dups := Dups.Next;
+ while not PointsEqual(Dups.Pt, PP.Pt) do Dups := Dups.Next;
+ end;
+ end;
+ Result := PP;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.SetHoleState(E: PEdge; OutRec: POutRec);
+var
+ E2: PEdge;
+ IsHole: Boolean;
+begin
+ IsHole := False;
+ E2 := E.PrevInAEL;
+ while Assigned(E2) do
+ begin
+ if (E2.OutIdx >= 0) and (E2.WindDelta <> 0) then
+ begin
+ IsHole := not IsHole;
+ if not Assigned(OutRec.FirstLeft) then
+ OutRec.FirstLeft := POutRec(fPolyOutList[E2.OutIdx]);
+ end;
+ E2 := E2.PrevInAEL;
+ end;
+ if IsHole then
+ OutRec.IsHole := True;
+end;
+//------------------------------------------------------------------------------
+
+function GetLowermostRec(OutRec1, OutRec2: POutRec): POutRec;
+var
+ OutPt1, OutPt2: POutPt;
+begin
+ if not assigned(OutRec1.BottomPt) then
+ OutRec1.BottomPt := GetBottomPt(OutRec1.Pts);
+ if not assigned(OutRec2.BottomPt) then
+ OutRec2.BottomPt := GetBottomPt(OutRec2.Pts);
+ OutPt1 := OutRec1.BottomPt;
+ OutPt2 := OutRec2.BottomPt;
+ if (OutPt1.Pt.Y > OutPt2.Pt.Y) then Result := OutRec1
+ else if (OutPt1.Pt.Y < OutPt2.Pt.Y) then Result := OutRec2
+ else if (OutPt1.Pt.X < OutPt2.Pt.X) then Result := OutRec1
+ else if (OutPt1.Pt.X > OutPt2.Pt.X) then Result := OutRec2
+ else if (OutPt1.Next = OutPt1) then Result := OutRec2
+ else if (OutPt2.Next = OutPt2) then Result := OutRec1
+ else if FirstParamIsBottomPt(OutPt1, OutPt2) then Result := OutRec1
+ else Result := OutRec2;
+end;
+//------------------------------------------------------------------------------
+
+function Param1RightOfParam2(OutRec1, OutRec2: POutRec): Boolean;
+begin
+ Result := True;
+ repeat
+ OutRec1 := OutRec1.FirstLeft;
+ if OutRec1 = OutRec2 then Exit;
+ until not Assigned(OutRec1);
+ Result := False;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.GetOutRec(Idx: integer): POutRec;
+begin
+ Result := FPolyOutList[Idx];
+ while Result <> FPolyOutList[Result.Idx] do
+ Result := FPolyOutList[Result.Idx];
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.AppendPolygon(E1, E2: PEdge);
+var
+ HoleStateRec, OutRec1, OutRec2: POutRec;
+ P1_lft, P1_rt, P2_lft, P2_rt: POutPt;
+ NewSide: TEdgeSide;
+ OKIdx, ObsoleteIdx: Integer;
+ E: PEdge;
+begin
+ OutRec1 := FPolyOutList[E1.OutIdx];
+ OutRec2 := FPolyOutList[E2.OutIdx];
+
+ //First work out which polygon fragment has the correct hole state.
+ //Since we're working from the bottom upward and left to right, the left most
+ //and lowermost polygon is outermost and must have the correct hole state ...
+ if Param1RightOfParam2(OutRec1, OutRec2) then HoleStateRec := OutRec2
+ else if Param1RightOfParam2(OutRec2, OutRec1) then HoleStateRec := OutRec1
+ else HoleStateRec := GetLowermostRec(OutRec1, OutRec2);
+
+ //get the start and ends of both output polygons and
+ //join E2 poly onto E1 poly and delete pointers to E2 ...
+
+ P1_lft := OutRec1.Pts;
+ P2_lft := OutRec2.Pts;
+ P1_rt := P1_lft.Prev;
+ P2_rt := P2_lft.Prev;
+
+ if E1.Side = esLeft then
+ begin
+ if E2.Side = esLeft then
+ begin
+ //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;
+ end else
+ begin
+ //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;
+ end;
+ NewSide := esLeft;
+ end else
+ begin
+ if E2.Side = esRight then
+ begin
+ //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;
+ end else
+ begin
+ //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;
+ end;
+ NewSide := esRight;
+ end;
+
+ OutRec1.BottomPt := nil;
+ if HoleStateRec = OutRec2 then
+ begin
+ if OutRec2.FirstLeft <> OutRec1 then
+ OutRec1.FirstLeft := OutRec2.FirstLeft;
+ OutRec1.IsHole := OutRec2.IsHole;
+ end;
+
+ OutRec2.Pts := nil;
+ OutRec2.BottomPt := nil;
+ OutRec2.FirstLeft := OutRec1;
+
+ OKIdx := OutRec1.Idx;
+ ObsoleteIdx := OutRec2.Idx;
+
+ E1.OutIdx := Unassigned; //safe because we only get here via AddLocalMaxPoly
+ E2.OutIdx := Unassigned;
+
+ E := FActiveEdges;
+ while Assigned(E) do
+ begin
+ if (E.OutIdx = ObsoleteIdx) then
+ begin
+ E.OutIdx := OKIdx;
+ E.Side := NewSide;
+ Break;
+ end;
+ E := E.NextInAEL;
+ end;
+
+ OutRec2.Idx := OutRec1.Idx;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.CreateOutRec: POutRec;
+begin
+ new(Result);
+ Result.IsHole := False;
+ Result.IsOpen := False;
+ Result.FirstLeft := nil;
+ Result.Pts := nil;
+ Result.BottomPt := nil;
+ Result.PolyNode := nil;
+ Result.Idx := FPolyOutList.Add(Result);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.AddOutPt(E: PEdge; const Pt: TIntPoint): POutPt;
+var
+ OutRec: POutRec;
+ PrevOp, Op: POutPt;
+ ToFront: Boolean;
+begin
+ if E.OutIdx < 0 then
+ begin
+ OutRec := CreateOutRec;
+ OutRec.IsOpen := (E.WindDelta = 0);
+ new(Result);
+ OutRec.Pts := Result;
+ Result.Pt := Pt;
+ Result.Next := Result;
+ Result.Prev := Result;
+ Result.Idx := OutRec.Idx;
+ if not OutRec.IsOpen then
+ SetHoleState(E, OutRec);
+ E.OutIdx := OutRec.Idx;
+ end else
+ begin
+ ToFront := E.Side = esLeft;
+ OutRec := FPolyOutList[E.OutIdx];
+ //OutRec.Pts is the 'left-most' point & OutRec.Pts.Prev is the 'right-most'
+ Op := OutRec.Pts;
+ if ToFront then PrevOp := Op else PrevOp := Op.Prev;
+ if PointsEqual(Pt, PrevOp.Pt) then
+ begin
+ Result := PrevOp;
+ Exit;
+ end;
+ new(Result);
+ Result.Pt := Pt;
+ Result.Idx := OutRec.Idx;
+ Result.Next := Op;
+ Result.Prev := Op.Prev;
+ Op.Prev.Next := Result;
+ Op.Prev := Result;
+ if ToFront then OutRec.Pts := Result;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.GetLastOutPt(E: PEdge): POutPt;
+var
+ OutRec: POutRec;
+begin
+ OutRec := FPolyOutList[E.OutIdx];
+ if E.Side = esLeft then
+ Result := OutRec.Pts else
+ Result := OutRec.Pts.Prev;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.ProcessHorizontals;
+var
+ E: PEdge;
+begin
+ while Assigned(fSortedEdges) do
+ begin
+ E := FSortedEdges;
+ DeleteFromSEL(E);
+ ProcessHorizontal(E);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function IsMinima(E: PEdge): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ Result := Assigned(E) and (E.Prev.NextInLML <> E) and (E.Next.NextInLML <> E);
+end;
+//------------------------------------------------------------------------------
+
+function IsMaxima(E: PEdge; const Y: cInt): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ Result := Assigned(E) and (E.Top.Y = Y) and not Assigned(E.NextInLML);
+end;
+//------------------------------------------------------------------------------
+
+function IsIntermediate(E: PEdge; const Y: cInt): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ Result := (E.Top.Y = Y) and Assigned(E.NextInLML);
+end;
+//------------------------------------------------------------------------------
+
+function GetMaximaPair(E: PEdge): PEdge;
+begin
+ if PointsEqual(E.Next.Top, E.Top) and not assigned(E.Next.NextInLML) then
+ Result := E.Next
+ else if PointsEqual(E.Prev.Top, E.Top) and not assigned(E.Prev.NextInLML) then
+ Result := E.Prev
+ else
+ Result := nil;
+ if assigned(Result) and ((Result.OutIdx = Skip) or
+ //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ...
+ ((Result.NextInAEL = Result.PrevInAEL) and (Result.Dx <> Horizontal))) then
+ Result := nil;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.SwapPositionsInAEL(E1, E2: PEdge);
+var
+ Prev,Next: PEdge;
+begin
+ //check that one or other edge hasn't already been removed from AEL ...
+ if (E1.NextInAEL = E1.PrevInAEL) or (E2.NextInAEL = E2.PrevInAEL) then
+ Exit;
+
+ if E1.NextInAEL = E2 then
+ begin
+ Next := E2.NextInAEL;
+ if Assigned(Next) then Next.PrevInAEL := E1;
+ Prev := E1.PrevInAEL;
+ if Assigned(Prev) then Prev.NextInAEL := E2;
+ E2.PrevInAEL := Prev;
+ E2.NextInAEL := E1;
+ E1.PrevInAEL := E2;
+ E1.NextInAEL := Next;
+ end
+ else if E2.NextInAEL = E1 then
+ begin
+ Next := E1.NextInAEL;
+ if Assigned(Next) then Next.PrevInAEL := E2;
+ Prev := E2.PrevInAEL;
+ if Assigned(Prev) then Prev.NextInAEL := E1;
+ E1.PrevInAEL := Prev;
+ E1.NextInAEL := E2;
+ E2.PrevInAEL := E1;
+ E2.NextInAEL := Next;
+ end else
+ begin
+ Next := E1.NextInAEL;
+ Prev := E1.PrevInAEL;
+ E1.NextInAEL := E2.NextInAEL;
+ if Assigned(E1.NextInAEL) then E1.NextInAEL.PrevInAEL := E1;
+ E1.PrevInAEL := E2.PrevInAEL;
+ if Assigned(E1.PrevInAEL) then E1.PrevInAEL.NextInAEL := E1;
+ E2.NextInAEL := Next;
+ if Assigned(E2.NextInAEL) then E2.NextInAEL.PrevInAEL := E2;
+ E2.PrevInAEL := Prev;
+ if Assigned(E2.PrevInAEL) then E2.PrevInAEL.NextInAEL := E2;
+ end;
+ if not Assigned(E1.PrevInAEL) then FActiveEdges := E1
+ else if not Assigned(E2.PrevInAEL) then FActiveEdges := E2;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.SwapPositionsInSEL(E1, E2: PEdge);
+var
+ Prev,Next: PEdge;
+begin
+ if E1.NextInSEL = E2 then
+ begin
+ Next := E2.NextInSEL;
+ if Assigned(Next) then Next.PrevInSEL := E1;
+ Prev := E1.PrevInSEL;
+ if Assigned(Prev) then Prev.NextInSEL := E2;
+ E2.PrevInSEL := Prev;
+ E2.NextInSEL := E1;
+ E1.PrevInSEL := E2;
+ E1.NextInSEL := Next;
+ end
+ else if E2.NextInSEL = E1 then
+ begin
+ Next := E1.NextInSEL;
+ if Assigned(Next) then Next.PrevInSEL := E2;
+ Prev := E2.PrevInSEL;
+ if Assigned(Prev) then Prev.NextInSEL := E1;
+ E1.PrevInSEL := Prev;
+ E1.NextInSEL := E2;
+ E2.PrevInSEL := E1;
+ E2.NextInSEL := Next;
+ end else
+ begin
+ Next := E1.NextInSEL;
+ Prev := E1.PrevInSEL;
+ E1.NextInSEL := E2.NextInSEL;
+ if Assigned(E1.NextInSEL) then E1.NextInSEL.PrevInSEL := E1;
+ E1.PrevInSEL := E2.PrevInSEL;
+ if Assigned(E1.PrevInSEL) then E1.PrevInSEL.NextInSEL := E1;
+ E2.NextInSEL := Next;
+ if Assigned(E2.NextInSEL) then E2.NextInSEL.PrevInSEL := E2;
+ E2.PrevInSEL := Prev;
+ if Assigned(E2.PrevInSEL) then E2.PrevInSEL.NextInSEL := E2;
+ end;
+ if not Assigned(E1.PrevInSEL) then FSortedEdges := E1
+ else if not Assigned(E2.PrevInSEL) then FSortedEdges := E2;
+end;
+//------------------------------------------------------------------------------
+
+function GetNextInAEL(E: PEdge; Direction: TDirection): PEdge;
+ {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ if Direction = dLeftToRight then
+ Result := E.NextInAEL else
+ Result := E.PrevInAEL;
+end;
+//------------------------------------------------------------------------
+
+procedure GetHorzDirection(HorzEdge: PEdge; out Dir: TDirection;
+ out Left, Right: cInt); {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ if HorzEdge.Bot.X < HorzEdge.Top.X then
+ begin
+ Left := HorzEdge.Bot.X;
+ Right := HorzEdge.Top.X;
+ Dir := dLeftToRight;
+ end else
+ begin
+ Left := HorzEdge.Top.X;
+ Right := HorzEdge.Bot.X;
+ Dir := dRightToLeft;
+ end;
+end;
+//------------------------------------------------------------------------
+
+procedure TClipper.ProcessHorizontal(HorzEdge: PEdge);
+var
+ E, eNext, eNextHorz, ePrev, eMaxPair, eLastHorz: PEdge;
+ HorzLeft, HorzRight: cInt;
+ Direction: TDirection;
+ Pt: TIntPoint;
+ Op1, Op2: POutPt;
+ IsLastHorz, IsOpen: Boolean;
+ currMax: PMaxima;
+begin
+(*******************************************************************************
+* Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or *
+* bottom of a scanbeam) are processed as if layered. The order in which HEs *
+* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] *
+* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), *
+* and with other non-horizontal edges [*]. Once these intersections are *
+* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into *
+* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
+*******************************************************************************)
+
+(*******************************************************************************
+* \ nb: HE processing order doesn't matter / / *
+* \ / / *
+* { -------- \ ------------------- / \ - (3) o==========%==========o - } *
+* { o==========o (2) / \ . . } *
+* { . / \ . . } *
+* { ---- o===============#========*========*=====#==========o (1) ------- } *
+* / \ / \ / *
+*******************************************************************************)
+
+ GetHorzDirection(HorzEdge, Direction, HorzLeft, HorzRight);
+ IsOpen := (HorzEdge.OutIdx >= 0) and
+ POutRec(FPolyOutList[HorzEdge.OutIdx]).IsOpen;
+
+ eLastHorz := HorzEdge;
+ while Assigned(eLastHorz.NextInLML) and
+ (eLastHorz.NextInLML.Dx = Horizontal) do
+ eLastHorz := eLastHorz.NextInLML;
+ if Assigned(eLastHorz.NextInLML) then
+ eMaxPair := nil else
+ eMaxPair := GetMaximaPair(eLastHorz);
+
+ Op1 := nil;
+ currMax := FMaxima;
+ //nb: FMaxima will only be assigned when the Simplify property is set true.
+
+ if assigned(currMax) then
+ begin
+ //get the first useful Maxima ...
+ if (Direction = dLeftToRight) then
+ begin
+ while Assigned(currMax) and (currMax.X <= HorzEdge.Bot.X) do
+ currMax := currMax.Next;
+ if Assigned(currMax) and (currMax.X >= eLastHorz.Top.X) then
+ currMax := nil;
+ end else
+ begin
+ while Assigned(currMax.Next) and (currMax.Next.X < HorzEdge.Bot.X) do
+ currMax := currMax.Next;
+ if (currMax.X <= eLastHorz.Top.X) then currMax := nil;
+ end;
+ end;
+
+ while true do //loops through consec. horizontal edges
+ begin
+ IsLastHorz := (HorzEdge = eLastHorz);
+ E := GetNextInAEL(HorzEdge, Direction);
+ while Assigned(E) do
+ begin
+
+ //this code block inserts extra coords into horizontal edges (in output
+ //polygons) whereever maxima touch these horizontal edges. This helps
+ //'simplifying' polygons (ie if the Simplify property is set).
+ if assigned(currMax) then
+ begin
+ if (Direction = dLeftToRight) then
+ begin
+ while assigned(currMax) and (currMax.X < E.Curr.X) do
+ begin
+ if (HorzEdge.OutIdx >= 0) and not IsOpen then
+ AddOutPt(HorzEdge, IntPoint(currMax.X, HorzEdge.Bot.Y));
+ currMax := currMax.Next;
+ end;
+ end else
+ begin
+ while assigned(currMax) and (currMax.X > E.Curr.X) do
+ begin
+ if (HorzEdge.OutIdx >= 0) and not IsOpen then
+ AddOutPt(HorzEdge, IntPoint(currMax.X, HorzEdge.Bot.Y));
+ currMax := currMax.Prev;
+ end;
+ end;
+ end;
+
+ if ((Direction = dLeftToRight) and (E.Curr.X > HorzRight)) or
+ ((Direction = dRightToLeft) and (E.Curr.X < HorzLeft)) then
+ Break;
+
+ //also 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) and
+ Assigned(HorzEdge.NextInLML) and (E.Dx < HorzEdge.NextInLML.Dx) then
+ Break;
+
+ if (HorzEdge.OutIdx >= 0) and not IsOpen then //may be done multiple times
+ begin
+ Op1 := AddOutPt(HorzEdge, E.Curr);
+ eNextHorz := FSortedEdges;
+ while Assigned(eNextHorz) do
+ begin
+ if (eNextHorz.OutIdx >= 0) and
+ HorzSegmentsOverlap(HorzEdge.Bot.X,
+ HorzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X) then
+ begin
+ Op2 := GetLastOutPt(eNextHorz);
+ AddJoin(Op2, Op1, eNextHorz.Top);
+ end;
+ eNextHorz := eNextHorz.NextInSEL;
+ end;
+ AddGhostJoin(Op1, HorzEdge.Bot);
+ end;
+
+ //OK, 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) and IsLastHorz then
+ begin
+ if HorzEdge.OutIdx >= 0 then
+ AddLocalMaxPoly(HorzEdge, eMaxPair, HorzEdge.Top);
+ deleteFromAEL(HorzEdge);
+ deleteFromAEL(eMaxPair);
+ Exit;
+ end;
+
+ if (Direction = dLeftToRight) then
+ begin
+ Pt := IntPoint(E.Curr.X, HorzEdge.Curr.Y);
+ IntersectEdges(HorzEdge, E, Pt);
+ end else
+ begin
+ Pt := IntPoint(E.Curr.X, HorzEdge.Curr.Y);
+ IntersectEdges(E, HorzEdge, Pt);
+ end;
+ eNext := GetNextInAEL(E, Direction);
+ SwapPositionsInAEL(HorzEdge, E);
+ E := eNext;
+ end;
+
+ //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+ if not Assigned(HorzEdge.NextInLML) or
+ (HorzEdge.NextInLML.Dx <> Horizontal) then Break;
+
+ UpdateEdgeIntoAEL(HorzEdge);
+ if (HorzEdge.OutIdx >= 0) then AddOutPt(HorzEdge, HorzEdge.Bot);
+ GetHorzDirection(HorzEdge, Direction, HorzLeft, HorzRight);
+ end;
+
+ if (HorzEdge.OutIdx >= 0) and not Assigned(Op1) then
+ begin
+ Op1 := GetLastOutPt(HorzEdge);
+ eNextHorz := FSortedEdges;
+ while Assigned(eNextHorz) do
+ begin
+ if (eNextHorz.OutIdx >= 0) and
+ HorzSegmentsOverlap(HorzEdge.Bot.X,
+ HorzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X) then
+ begin
+ Op2 := GetLastOutPt(eNextHorz);
+ AddJoin(Op2, Op1, eNextHorz.Top);
+ end;
+ eNextHorz := eNextHorz.NextInSEL;
+ end;
+ AddGhostJoin(Op1, HorzEdge.Top);
+ end;
+
+ if Assigned(HorzEdge.NextInLML) then
+ begin
+ if (HorzEdge.OutIdx >= 0) then
+ begin
+ Op1 := AddOutPt(HorzEdge, HorzEdge.Top);
+
+ UpdateEdgeIntoAEL(HorzEdge);
+ if (HorzEdge.WindDelta = 0) then Exit;
+ //nb: HorzEdge is no longer horizontal here
+ ePrev := HorzEdge.PrevInAEL;
+ eNext := HorzEdge.NextInAEL;
+ if Assigned(ePrev) and (ePrev.Curr.X = HorzEdge.Bot.X) and
+ (ePrev.Curr.Y = HorzEdge.Bot.Y) and (ePrev.WindDelta <> 0) and
+ (ePrev.OutIdx >= 0) and (ePrev.Curr.Y > ePrev.Top.Y) and
+ SlopesEqual(HorzEdge, ePrev, FUse64BitRange) then
+ begin
+ Op2 := AddOutPt(ePrev, HorzEdge.Bot);
+ AddJoin(Op1, Op2, HorzEdge.Top);
+ end
+ else if Assigned(eNext) and (eNext.Curr.X = HorzEdge.Bot.X) and
+ (eNext.Curr.Y = HorzEdge.Bot.Y) and (eNext.WindDelta <> 0) and
+ (eNext.OutIdx >= 0) and (eNext.Curr.Y > eNext.Top.Y) and
+ SlopesEqual(HorzEdge, eNext, FUse64BitRange) then
+ begin
+ Op2 := AddOutPt(eNext, HorzEdge.Bot);
+ AddJoin(Op1, Op2, HorzEdge.Top);
+ end;
+ end else
+ UpdateEdgeIntoAEL(HorzEdge);
+ end else
+ begin
+ if (HorzEdge.OutIdx >= 0) then AddOutPt(HorzEdge, HorzEdge.Top);
+ DeleteFromAEL(HorzEdge);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.UpdateEdgeIntoAEL(var E: PEdge);
+var
+ AelPrev, AelNext: PEdge;
+begin
+ //return true when AddOutPt() call needed too
+ if not Assigned(E.NextInLML) then
+ raise exception.Create(rsUpdateEdgeIntoAEL);
+
+ E.NextInLML.OutIdx := E.OutIdx;
+
+ AelPrev := E.PrevInAEL;
+ AelNext := E.NextInAEL;
+ if Assigned(AelPrev) then
+ AelPrev.NextInAEL := E.NextInLML else
+ FActiveEdges := E.NextInLML;
+ if Assigned(AelNext) then
+ 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 E.Dx <> Horizontal then
+ InsertScanbeam(E.Top.Y);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.ProcessIntersections(const TopY: cInt): Boolean;
+begin
+ Result := True;
+ try
+ BuildIntersectList(TopY);
+ if (FIntersectList.Count = 0) then
+ Exit
+ else if FixupIntersectionOrder then
+ ProcessIntersectList()
+ else
+ Result := False;
+ finally
+ DisposeIntersectNodes; //clean up if there's been an error
+ FSortedEdges := nil;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DisposeIntersectNodes;
+var
+ I: Integer;
+begin
+ for I := 0 to FIntersectList.Count - 1 do
+ Dispose(PIntersectNode(FIntersectList[I]));
+ FIntersectList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.BuildIntersectList(const TopY: cInt);
+var
+ E, eNext: PEdge;
+ Pt: TIntPoint;
+ IsModified: Boolean;
+ NewNode: PIntersectNode;
+begin
+ if not Assigned(fActiveEdges) then Exit;
+
+ //prepare for sorting ...
+ E := FActiveEdges;
+ FSortedEdges := E;
+ while Assigned(E) do
+ begin
+ E.PrevInSEL := E.PrevInAEL;
+ E.NextInSEL := E.NextInAEL;
+ E.Curr.X := TopX(E, TopY);
+ E := E.NextInAEL;
+ end;
+
+ //bubblesort (because adjacent swaps are required) ...
+ repeat
+ IsModified := False;
+ E := FSortedEdges;
+ while Assigned(E.NextInSEL) do
+ begin
+ eNext := E.NextInSEL;
+ if (E.Curr.X > eNext.Curr.X) then
+ begin
+ IntersectPoint(E, eNext, Pt);
+ new(NewNode);
+ NewNode.Edge1 := E;
+ NewNode.Edge2 := eNext;
+ NewNode.Pt := Pt;
+ FIntersectList.Add(NewNode);
+
+ SwapPositionsInSEL(E, eNext);
+ IsModified := True;
+ end else
+ E := eNext;
+ end;
+ if Assigned(E.PrevInSEL) then
+ E.PrevInSEL.NextInSEL := nil
+ else Break;
+ until not IsModified;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.ProcessIntersectList;
+var
+ I: Integer;
+begin
+ for I := 0 to FIntersectList.Count - 1 do
+ begin
+ with PIntersectNode(FIntersectList[I])^ do
+ begin
+ IntersectEdges(Edge1, Edge2, Pt);
+ SwapPositionsInAEL(Edge1, Edge2);
+ end;
+ dispose(PIntersectNode(FIntersectList[I]));
+ end;
+ FIntersectList.Clear;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DoMaxima(E: PEdge);
+var
+ ENext, EMaxPair: PEdge;
+begin
+ EMaxPair := GetMaximaPair(E);
+ if not assigned(EMaxPair) then
+ begin
+ if E.OutIdx >= 0 then
+ AddOutPt(E, E.Top);
+ DeleteFromAEL(E);
+ Exit;
+ end;
+
+ ENext := E.NextInAEL;
+ //rarely, with overlapping collinear edges (in open paths) ENext can be nil
+ while Assigned(ENext) and (ENext <> EMaxPair) do
+ begin
+ IntersectEdges(E, ENext, E.Top);
+ SwapPositionsInAEL(E, ENext);
+ ENext := E.NextInAEL;
+ end;
+
+ if (E.OutIdx = Unassigned) and (EMaxPair.OutIdx = Unassigned) then
+ begin
+ DeleteFromAEL(E);
+ DeleteFromAEL(EMaxPair);
+ end
+ else if (E.OutIdx >= 0) and (EMaxPair.OutIdx >= 0) then
+ begin
+ if E.OutIdx >= 0 then
+ AddLocalMaxPoly(E, EMaxPair, E.Top);
+ deleteFromAEL(E);
+ deleteFromAEL(eMaxPair);
+ end
+{$IFDEF use_lines}
+ else if E.WindDelta = 0 then
+ begin
+ if (E.OutIdx >= 0) then
+ begin
+ AddOutPt(E, E.Top);
+ E.OutIdx := Unassigned;
+ end;
+ DeleteFromAEL(E);
+
+ if (EMaxPair.OutIdx >= 0) then
+ begin
+ AddOutPt(EMaxPair, E.Top);
+ EMaxPair.OutIdx := Unassigned;
+ end;
+ DeleteFromAEL(EMaxPair);
+ end
+{$ENDIF}
+ else
+ raise exception.Create(rsDoMaxima);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.ProcessEdgesAtTopOfScanbeam(const TopY: cInt);
+var
+ E, EMaxPair, ePrev, eNext: PEdge;
+ Op, Op2: POutPt;
+ IsMaximaEdge: Boolean;
+ Pt: TIntPoint;
+begin
+(*******************************************************************************
+* Notes: Processing edges at scanline intersections (ie at the top or bottom *
+* of a scanbeam) needs to be done in multiple stages and in the correct order. *
+* Firstly, edges forming a 'maxima' need to be processed and then removed. *
+* Next, 'intermediate' and 'maxima' horizontal edges are processed. Then edges *
+* that intersect exactly at the top of the scanbeam are processed [%]. *
+* Finally, new minima are added and any intersects they create are processed. *
+*******************************************************************************)
+
+(*******************************************************************************
+* \ / / \ / *
+* \ Horizontal minima / / \ / *
+* { -- o======================#====o -------- . ------------------- } *
+* { Horizontal maxima . % scanline intersect } *
+* { -- o=======================#===================#========o ---------- } *
+* | / / \ \ *
+* + maxima intersect / / \ \ *
+* /|\ / / \ \ *
+* / | \ / / \ \ *
+*******************************************************************************)
+
+ E := FActiveEdges;
+ while Assigned(E) do
+ begin
+ //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.
+ IsMaximaEdge := IsMaxima(E, TopY);
+ if IsMaximaEdge then
+ begin
+ EMaxPair := GetMaximaPair(E);
+ IsMaximaEdge := not assigned(EMaxPair) or (EMaxPair.Dx <> Horizontal);
+ end;
+
+ if IsMaximaEdge then
+ begin
+ if FStrictSimple then
+ InsertMaxima(E.Top.X);
+ //'E' might be removed from AEL, as may any following edges so ...
+ ePrev := E.PrevInAEL;
+ DoMaxima(E);
+ if not Assigned(ePrev) then
+ E := FActiveEdges else
+ E := ePrev.NextInAEL;
+ end else
+ begin
+ //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
+ if IsIntermediate(E, TopY) and (E.NextInLML.Dx = Horizontal) then
+ begin
+ UpdateEdgeIntoAEL(E);
+ if (E.OutIdx >= 0) then
+ AddOutPt(E, E.Bot);
+ AddEdgeToSEL(E);
+ end else
+ begin
+ E.Curr.X := TopX(E, TopY);
+ E.Curr.Y := TopY;
+ end;
+
+ //When StrictlySimple and 'e' is being touched by another edge, then
+ //make sure both edges have a vertex here ...
+ if FStrictSimple then
+ begin
+ ePrev := E.PrevInAEL;
+ if (E.OutIdx >= 0) and (E.WindDelta <> 0) and
+ Assigned(ePrev) and (ePrev.Curr.X = E.Curr.X) and
+ (ePrev.OutIdx >= 0) and (ePrev.WindDelta <> 0) then
+ begin
+ Pt := E.Curr;
+{$IFDEF use_xyz}
+ SetZ(Pt, ePrev, E, FZFillCallback);
+{$ENDIF}
+ Op := AddOutPt(ePrev, Pt);
+ Op2 := AddOutPt(E, Pt);
+ AddJoin(Op, Op2, Pt); //strictly-simple (type-3) 'join'
+ end;
+ end;
+
+ E := E.NextInAEL;
+ end;
+ end;
+
+ //3. Process horizontals at the top of the scanbeam ...
+ ProcessHorizontals;
+ if FStrictSimple then DisposeMaximaList;
+
+ //4. Promote intermediate vertices ...
+ E := FActiveEdges;
+ while Assigned(E) do
+ begin
+ if IsIntermediate(E, TopY) then
+ begin
+ if (E.OutIdx >= 0) then
+ Op := AddOutPt(E, E.Top) else
+ Op := nil;
+ UpdateEdgeIntoAEL(E);
+
+ //if output polygons share an Edge, they'll need joining later ...
+ ePrev := E.PrevInAEL;
+ eNext := E.NextInAEL;
+ if Assigned(ePrev) and (ePrev.Curr.X = E.Bot.X) and
+ (ePrev.Curr.Y = E.Bot.Y) and assigned(Op) and
+ (ePrev.OutIdx >= 0) and (ePrev.Curr.Y > ePrev.Top.Y) and
+ SlopesEqual(E, ePrev, FUse64BitRange) and
+ (E.WindDelta <> 0) and (ePrev.WindDelta <> 0) then
+ begin
+ Op2 := AddOutPt(ePrev, E.Bot);
+ AddJoin(Op, Op2, E.Top);
+ end
+ else if Assigned(eNext) and (eNext.Curr.X = E.Bot.X) and
+ (eNext.Curr.Y = E.Bot.Y) and assigned(Op) and
+ (eNext.OutIdx >= 0) and (eNext.Curr.Y > eNext.Top.Y) and
+ SlopesEqual(E, eNext, FUse64BitRange) and
+ (E.WindDelta <> 0) and (eNext.WindDelta <> 0) then
+ begin
+ Op2 := AddOutPt(eNext, E.Bot);
+ AddJoin(Op, Op2, E.Top);
+ end;
+ end;
+ E := E.NextInAEL;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.BuildResult: TPaths;
+var
+ I, J, K, Cnt: Integer;
+ OutRec: POutRec;
+ Op: POutPt;
+begin
+ J := 0;
+ SetLength(Result, FPolyOutList.Count);
+ for I := 0 to FPolyOutList.Count -1 do
+ if Assigned(fPolyOutList[I]) then
+ begin
+ OutRec := FPolyOutList[I];
+ if not assigned(OutRec.Pts) then Continue;
+
+ Op := OutRec.Pts.Prev;
+ Cnt := PointCount(Op);
+ if (Cnt < 2) then Continue;
+ SetLength(Result[J], Cnt);
+ for K := 0 to Cnt -1 do
+ begin
+ Result[J][K] := Op.Pt;
+ Op := Op.Prev;
+ end;
+ Inc(J);
+ end;
+ SetLength(Result, J);
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.BuildResult2(PolyTree: TPolyTree): Boolean;
+var
+ I, J, Cnt, CntAll: Integer;
+ Op: POutPt;
+ OutRec: POutRec;
+ PolyNode: TPolyNode;
+begin
+ try
+ PolyTree.Clear;
+ SetLength(PolyTree.FAllNodes, FPolyOutList.Count);
+
+ //add PolyTree ...
+ CntAll := 0;
+ for I := 0 to FPolyOutList.Count -1 do
+ begin
+ OutRec := fPolyOutList[I];
+ Cnt := PointCount(OutRec.Pts);
+ if (OutRec.IsOpen and (cnt < 2)) or
+ (not outRec.IsOpen and (cnt < 3)) then Continue;
+ FixHoleLinkage(OutRec);
+
+ PolyNode := TPolyNode.Create;
+ PolyTree.FAllNodes[CntAll] := PolyNode;
+ OutRec.PolyNode := PolyNode;
+ Inc(CntAll);
+ SetLength(PolyNode.FPath, Cnt);
+ Op := OutRec.Pts.Prev;
+ for J := 0 to Cnt -1 do
+ begin
+ PolyNode.FPath[J] := Op.Pt;
+ Op := Op.Prev;
+ end;
+ end;
+
+ //fix Poly links ...
+ SetLength(PolyTree.FAllNodes, CntAll);
+ SetLength(PolyTree.FChilds, CntAll);
+ for I := 0 to FPolyOutList.Count -1 do
+ begin
+ OutRec := fPolyOutList[I];
+ if Assigned(OutRec.PolyNode) then
+ begin
+ if OutRec.IsOpen then
+ begin
+ OutRec.PolyNode.FIsOpen := true;
+ PolyTree.AddChild(OutRec.PolyNode);
+ end
+ else if Assigned(OutRec.FirstLeft) and
+ assigned(OutRec.FirstLeft.PolyNode)then
+ OutRec.FirstLeft.PolyNode.AddChild(OutRec.PolyNode)
+ else
+ PolyTree.AddChild(OutRec.PolyNode);
+ end;
+ end;
+ SetLength(PolyTree.FChilds, PolyTree.FCount);
+ Result := True;
+ except
+ Result := False;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.FixupOutPolyline(OutRec: POutRec);
+var
+ PP, LastPP, TmpPP: POutPt;
+begin
+ //remove duplicate points ...
+ PP := OutRec.Pts;
+ LastPP := PP.Prev;
+ while (PP <> LastPP) do
+ begin
+ PP := PP.Next;
+ //strip duplicate points ...
+ if PointsEqual(PP.Pt, PP.Prev.Pt) then
+ begin
+ if PP = LastPP then LastPP := PP.Prev;
+ TmpPP := PP.Prev;
+ TmpPP.Next := PP.Next;
+ PP.Next.Prev := TmpPP;
+ dispose(PP);
+ PP := TmpPP;
+ end;
+ end;
+
+ if (PP = PP.Prev) then
+ begin
+ Dispose(PP);
+ OutRec.Pts := nil;
+ Exit;
+ end;
+
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.FixupOutPolygon(OutRec: POutRec);
+var
+ PP, Tmp, LastOK: POutPt;
+ PreserveCol: Boolean;
+begin
+ //remove duplicate points and collinear edges
+ LastOK := nil;
+ OutRec.BottomPt := nil; //flag as stale
+ PP := OutRec.Pts;
+ PreserveCol := FPreserveCollinear or FStrictSimple;
+ while True do
+ begin
+ if (PP = PP.Prev) or (PP.Next = PP.Prev) then
+ begin
+ Dispose(PP);
+ OutRec.Pts := nil;
+ Exit;
+ end;
+
+ //test for duplicate points and collinear edges ...
+ if PointsEqual(PP.Pt, PP.Next.Pt) or PointsEqual(PP.Pt, PP.Prev.Pt) or
+ (SlopesEqual(PP.Prev.Pt, PP.Pt, PP.Next.Pt, FUse64BitRange) and
+ (not PreserveCol or
+ not Pt2IsBetweenPt1AndPt3(PP.Prev.Pt, PP.Pt, PP.Next.Pt))) then
+ begin
+ //OK, we need to delete a point ...
+ LastOK := nil;
+ Tmp := PP;
+ PP.Prev.Next := PP.Next;
+ PP.Next.Prev := PP.Prev;
+ PP := PP.Prev;
+ dispose(Tmp);
+ end
+ else if PP = LastOK then Break
+ else
+ begin
+ if not Assigned(LastOK) then LastOK := PP;
+ PP := PP.Next;
+ end;
+ end;
+ OutRec.Pts := PP;
+end;
+//------------------------------------------------------------------------------
+
+function EdgesAdjacent(Inode: PIntersectNode): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
+begin
+ Result := (Inode.Edge1.NextInSEL = Inode.Edge2) or
+ (Inode.Edge1.PrevInSEL = Inode.Edge2);
+end;
+//------------------------------------------------------------------------------
+
+function IntersectListSort(Node1, Node2: Pointer): Integer;
+var
+ i: cInt;
+begin
+ i := PIntersectNode(Node2).Pt.Y - PIntersectNode(Node1).Pt.Y;
+ if i < 0 then Result := -1
+ else if i > 0 then Result := 1
+ else Result := 0;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.FixupIntersectionOrder: Boolean;
+var
+ I, J, Cnt: Integer;
+ Node: PIntersectNode;
+begin
+ //pre-condition: intersections are sorted bottom-most first.
+ //Now it's crucial that intersections are made only between adjacent edges,
+ //and to ensure this the order of intersections may need adjusting ...
+ Result := True;
+ Cnt := FIntersectList.Count;
+ if Cnt < 2 then exit;
+
+ CopyAELToSEL;
+ {$IFDEF USEGENERICS}
+ FIntersectList.Sort(TComparer<PIntersectNode>.Construct(
+ function (const Node1, Node2 : PIntersectNode) : integer
+ var
+ i: cInt;
+ begin
+ i := PIntersectNode(Node2).Pt.Y - PIntersectNode(Node1).Pt.Y;
+ if i < 0 then Result := -1
+ else if i > 0 then Result := 1
+ else Result := 0;
+ end
+ ));
+ {$ELSE}
+ FIntersectList.Sort(IntersectListSort);
+ {$ENDIF}
+ for I := 0 to Cnt - 1 do
+ begin
+ if not EdgesAdjacent(FIntersectList[I]) then
+ begin
+ J := I + 1;
+ while (J < Cnt) and not EdgesAdjacent(FIntersectList[J]) do inc(J);
+ if J = Cnt then
+ begin
+ Result := False;
+ Exit; //error!!
+ end;
+ //Swap IntersectNodes ...
+ Node := FIntersectList[I];
+ FIntersectList[I] := FIntersectList[J];
+ FIntersectList[J] := Node;
+ end;
+ with PIntersectNode(FIntersectList[I])^ do
+ SwapPositionsInSEL(Edge1, Edge2);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function DupOutPt(OutPt: POutPt; InsertAfter: Boolean = true): POutPt;
+begin
+ new(Result);
+ Result.Pt := OutPt.Pt;
+ Result.Idx := OutPt.Idx;
+ if InsertAfter then
+ begin
+ Result.Next := OutPt.Next;
+ Result.Prev := OutPt;
+ OutPt.Next.Prev := Result;
+ OutPt.Next := Result;
+ end else
+ begin
+ Result.Prev := OutPt.Prev;
+ Result.Next := OutPt;
+ OutPt.Prev.Next := Result;
+ OutPt.Prev := Result;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function JoinHorz(Op1, Op1b, Op2, Op2b: POutPt;
+ const Pt: TIntPoint; DiscardLeft: Boolean): Boolean;
+var
+ Dir1, Dir2: TDirection;
+begin
+ if Op1.Pt.X > Op1b.Pt.X then Dir1 := dRightToLeft else Dir1 := dLeftToRight;
+ if Op2.Pt.X > Op2b.Pt.X then Dir2 := dRightToLeft else Dir2 := dLeftToRight;
+ Result := Dir1 <> Dir2;
+ if not Result then Exit;
+
+ //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.)
+ //To facilitate this while inserting Op1b & Op2b when DiscardLeft == true,
+ //make sure we're either AT or RIGHT OF Pt before adding Op1b, otherwise
+ //make sure we're AT or LEFT OF Pt. (Likewise with Op2b.)
+ if Dir1 = dLeftToRight then
+ begin
+ while (Op1.Next.Pt.X <= Pt.X) and
+ (Op1.Next.Pt.X >= Op1.Pt.X) and (Op1.Next.Pt.Y = Pt.Y) do
+ Op1 := Op1.Next;
+ if DiscardLeft and (Op1.Pt.X <> Pt.X) then Op1 := Op1.Next;
+ Op1b := DupOutPt(Op1, not DiscardLeft);
+ if not PointsEqual(Op1b.Pt, Pt) then
+ begin
+ Op1 := Op1b;
+ Op1.Pt := Pt;
+ Op1b := DupOutPt(Op1, not DiscardLeft);
+ end;
+ end else
+ begin
+ while (Op1.Next.Pt.X >= Pt.X) and
+ (Op1.Next.Pt.X <= Op1.Pt.X) and (Op1.Next.Pt.Y = Pt.Y) do
+ Op1 := Op1.Next;
+ if not DiscardLeft and (Op1.Pt.X <> Pt.X) then Op1 := Op1.Next;
+ Op1b := DupOutPt(Op1, DiscardLeft);
+ if not PointsEqual(Op1b.Pt, Pt) then
+ begin
+ Op1 := Op1b;
+ Op1.Pt := Pt;
+ Op1b := DupOutPt(Op1, DiscardLeft);
+ end;
+ end;
+
+ if Dir2 = dLeftToRight then
+ begin
+ while (Op2.Next.Pt.X <= Pt.X) and
+ (Op2.Next.Pt.X >= Op2.Pt.X) and (Op2.Next.Pt.Y = Pt.Y) do
+ Op2 := Op2.Next;
+ if DiscardLeft and (Op2.Pt.X <> Pt.X) then Op2 := Op2.Next;
+ Op2b := DupOutPt(Op2, not DiscardLeft);
+ if not PointsEqual(Op2b.Pt, Pt) then
+ begin
+ Op2 := Op2b;
+ Op2.Pt := Pt;
+ Op2b := DupOutPt(Op2, not DiscardLeft);
+ end;
+ end else
+ begin
+ while (Op2.Next.Pt.X >= Pt.X) and
+ (Op2.Next.Pt.X <= Op2.Pt.X) and (Op2.Next.Pt.Y = Pt.Y) do
+ Op2 := Op2.Next;
+ if not DiscardLeft and (Op2.Pt.X <> Pt.X) then Op2 := Op2.Next;
+ Op2b := DupOutPt(Op2, DiscardLeft);
+ if not PointsEqual(Op2b.Pt, Pt) then
+ begin
+ Op2 := Op2b;
+ Op2.Pt := Pt;
+ Op2b := DupOutPt(Op2, DiscardLeft);
+ end;
+ end;
+
+ if (Dir1 = dLeftToRight) = DiscardLeft then
+ begin
+ Op1.Prev := Op2;
+ Op2.Next := Op1;
+ Op1b.Next := Op2b;
+ Op2b.Prev := Op1b;
+ end
+ else
+ begin
+ Op1.Next := Op2;
+ Op2.Prev := Op1;
+ Op1b.Prev := Op2b;
+ Op2b.Next := Op1b;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TClipper.JoinPoints(Jr: PJoin; OutRec1, OutRec2: POutRec): Boolean;
+var
+ Op1, Op1b, Op2, Op2b: POutPt;
+ Pt: TIntPoint;
+ Reverse1, Reverse2, DiscardLeftSide: Boolean;
+ IsHorizontal: Boolean;
+ Left, Right: cInt;
+begin
+ Result := False;
+ Op1 := Jr.OutPt1;
+ Op2 := Jr.OutPt2;
+
+ //There are 3 kinds of joins for output polygons ...
+ //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are 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.
+ IsHorizontal := (Jr.OutPt1.Pt.Y = Jr.OffPt.Y);
+
+ if IsHorizontal and PointsEqual(Jr.OffPt, Jr.OutPt1.Pt) and
+ PointsEqual(Jr.OffPt, Jr.OutPt2.Pt) then
+ begin
+ //Strictly Simple join ...
+ if (OutRec1 <> OutRec2) then
+ Exit;
+
+ Op1b := Jr.OutPt1.Next;
+ while (Op1b <> Op1) and
+ PointsEqual(Op1b.Pt, Jr.OffPt) do Op1b := Op1b.Next;
+ Reverse1 := (Op1b.Pt.Y > Jr.OffPt.Y);
+ Op2b := Jr.OutPt2.Next;
+ while (Op2b <> Op2) and
+ PointsEqual(Op2b.Pt, Jr.OffPt) do Op2b := Op2b.Next;
+ Reverse2 := (Op2b.Pt.Y > Jr.OffPt.Y);
+ if (Reverse1 = Reverse2) then Exit;
+
+ if Reverse1 then
+ begin
+ Op1b := DupOutPt(Op1, False);
+ Op2b := DupOutPt(Op2, True);
+ Op1.Prev := Op2;
+ Op2.Next := Op1;
+ Op1b.Next := Op2b;
+ Op2b.Prev := Op1b;
+ Jr.OutPt1 := Op1;
+ Jr.OutPt2 := Op1b;
+ Result := True;
+ end else
+ begin
+ Op1b := DupOutPt(Op1, True);
+ Op2b := DupOutPt(Op2, False);
+ Op1.Next := Op2;
+ Op2.Prev := Op1;
+ Op1b.Prev := Op2b;
+ Op2b.Next := Op1b;
+ Jr.OutPt1 := Op1;
+ Jr.OutPt2 := Op1b;
+ Result := True;
+ end;
+ end
+ else if IsHorizontal then
+ begin
+ op1b := op1;
+ while (op1.Prev.Pt.Y = op1.Pt.Y) and
+ (op1.Prev <> Op1b) and (op1.Prev <> op2) do
+ op1 := op1.Prev;
+ while (op1b.Next.Pt.Y = op1b.Pt.Y) and
+ (op1b.Next <> Op1) and (op1b.Next <> op2) do
+ op1b := op1b.Next;
+ if (op1b.Next = Op1) or (op1b.Next = op2) then Exit; //a flat 'polygon'
+
+ op2b := op2;
+ while (op2.Prev.Pt.Y = op2.Pt.Y) and
+ (op2.Prev <> Op2b) and (op2.Prev <> op1b) do
+ op2 := op2.Prev;
+ while (op2b.Next.Pt.Y = op2b.Pt.Y) and
+ (op2b.Next <> Op2) and (op2b.Next <> op1) do
+ op2b := op2b.Next;
+ if (op2b.Next = Op2) or (op2b.Next = op1) then Exit; //a flat 'polygon'
+
+ //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
+ if not GetOverlap(Op1.Pt.X, Op1b.Pt.X, Op2.Pt.X, Op2b.Pt.X, Left, Right) then
+ Exit;
+
+ //DiscardLeftSide: when joining overlapping edges, a spike will be 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 ...
+ if (Op1.Pt.X >= Left) and (Op1.Pt.X <= Right) then
+ begin
+ Pt := Op1.Pt; DiscardLeftSide := Op1.Pt.X > Op1b.Pt.X;
+ end else if (Op2.Pt.X >= Left) and (Op2.Pt.X <= Right) then
+ begin
+ Pt := Op2.Pt; DiscardLeftSide := Op2.Pt.X > Op2b.Pt.X;
+ end else if (Op1b.Pt.X >= Left) and (Op1b.Pt.X <= Right) then
+ begin
+ Pt := Op1b.Pt; DiscardLeftSide := Op1b.Pt.X > Op1.Pt.X;
+ end else
+ begin
+ Pt := Op2b.Pt; DiscardLeftSide := Op2b.Pt.X > Op2.Pt.X;
+ end;
+
+ Result := JoinHorz(Op1, Op1b, Op2, Op2b, Pt, DiscardLeftSide);
+ if not Result then Exit;
+ Jr.OutPt1 := Op1;
+ Jr.OutPt2 := Op2;
+ end else
+ begin
+ //make sure the polygons are correctly oriented ...
+ Op1b := Op1.Next;
+ while PointsEqual(Op1b.Pt, Op1.Pt) and (Op1b <> Op1) do Op1b := Op1b.Next;
+ Reverse1 := (Op1b.Pt.Y > Op1.Pt.Y) or
+ not SlopesEqual(Op1.Pt, Op1b.Pt, Jr.OffPt, FUse64BitRange);
+ if Reverse1 then
+ begin
+ Op1b := Op1.Prev;
+ while PointsEqual(Op1b.Pt, Op1.Pt) and (Op1b <> Op1) do Op1b := Op1b.Prev;
+ if (Op1b.Pt.Y > Op1.Pt.Y) or
+ not SlopesEqual(Op1.Pt, Op1b.Pt, Jr.OffPt, FUse64BitRange) then Exit;
+ end;
+ Op2b := Op2.Next;
+ while PointsEqual(Op2b.Pt, Op2.Pt) and (Op2b <> Op2) do Op2b := Op2b.Next;
+ Reverse2 := (Op2b.Pt.Y > Op2.Pt.Y) or
+ not SlopesEqual(Op2.Pt, Op2b.Pt, Jr.OffPt, FUse64BitRange);
+ if Reverse2 then
+ begin
+ Op2b := Op2.Prev;
+ while PointsEqual(Op2b.Pt, Op2.Pt) and (Op2b <> Op2) do Op2b := Op2b.Prev;
+ if (Op2b.Pt.Y > Op2.Pt.Y) or
+ not SlopesEqual(Op2.Pt, Op2b.Pt, Jr.OffPt, FUse64BitRange) then Exit;
+ end;
+
+ if (Op1b = Op1) or (Op2b = Op2) or (Op1b = Op2b) or
+ ((OutRec1 = OutRec2) and (Reverse1 = Reverse2)) then Exit;
+
+ if Reverse1 then
+ begin
+ Op1b := DupOutPt(Op1, False);
+ Op2b := DupOutPt(Op2, True);
+ Op1.Prev := Op2;
+ Op2.Next := Op1;
+ Op1b.Next := Op2b;
+ Op2b.Prev := Op1b;
+ Jr.OutPt1 := Op1;
+ Jr.OutPt2 := Op1b;
+ Result := True;
+ end else
+ begin
+ Op1b := DupOutPt(Op1, True);
+ Op2b := DupOutPt(Op2, False);
+ Op1.Next := Op2;
+ Op2.Prev := Op1;
+ Op1b.Prev := Op2b;
+ Op2b.Next := Op1b;
+ Jr.OutPt1 := Op1;
+ Jr.OutPt2 := Op1b;
+ Result := True;
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function ParseFirstLeft(FirstLeft: POutRec): POutRec;
+begin
+ while Assigned(FirstLeft) and not Assigned(FirstLeft.Pts) do
+ FirstLeft := FirstLeft.FirstLeft;
+ Result := FirstLeft;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.FixupFirstLefts1(OldOutRec, NewOutRec: POutRec);
+var
+ I: Integer;
+ OutRec: POutRec;
+ firstLeft: POutRec;
+begin
+ //tests if NewOutRec contains the polygon before reassigning FirstLeft
+ for I := 0 to FPolyOutList.Count -1 do
+ begin
+ OutRec := fPolyOutList[I];
+ if not Assigned(OutRec.Pts) or not Assigned(OutRec.FirstLeft) then continue;
+ firstLeft := ParseFirstLeft(OutRec.FirstLeft);
+ if (firstLeft = OldOutRec) then
+ begin
+ if Poly2ContainsPoly1(OutRec.Pts, NewOutRec.Pts) then
+ OutRec.FirstLeft := NewOutRec;
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.FixupFirstLefts2(OldOutRec, NewOutRec: POutRec);
+var
+ I: Integer;
+begin
+ //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
+ for I := 0 to FPolyOutList.Count -1 do
+ with POutRec(fPolyOutList[I])^ do
+ if (FirstLeft = OldOutRec) then FirstLeft := NewOutRec;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.JoinCommonEdges;
+var
+ I, J: Integer;
+ Jr: PJoin;
+ OutRec1, OutRec2, HoleStateRec, oRec: POutRec;
+begin
+ for I := 0 to FJoinList.count -1 do
+ begin
+ Jr := FJoinList[I];
+
+ OutRec1 := GetOutRec(Jr.OutPt1.Idx);
+ OutRec2 := GetOutRec(Jr.OutPt2.Idx);
+
+ if not Assigned(OutRec1.Pts) or not Assigned(OutRec2.Pts) then Continue;
+ if OutRec1.IsOpen or OutRec2.IsOpen then Continue;
+
+ //get the polygon fragment with the correct hole state (FirstLeft)
+ //before calling JoinPoints() ...
+ if OutRec1 = OutRec2 then HoleStateRec := OutRec1
+ else if Param1RightOfParam2(OutRec1, OutRec2) then HoleStateRec := OutRec2
+ else if Param1RightOfParam2(OutRec2, OutRec1) then HoleStateRec := OutRec1
+ else HoleStateRec := GetLowermostRec(OutRec1, OutRec2);
+
+ if not JoinPoints(Jr, OutRec1, OutRec2) then Continue;
+
+ if (OutRec1 = OutRec2) then
+ begin
+ //instead of joining two polygons, we've just created a new one by
+ //splitting one polygon into two.
+ OutRec1.Pts := Jr.OutPt1;
+ OutRec1.BottomPt := nil;
+ OutRec2 := CreateOutRec;
+ OutRec2.Pts := Jr.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 FUsingPolyTree then
+ for J := 0 to FPolyOutList.Count - 2 do
+ begin
+ oRec := POutRec(FPolyOutList[J]);
+ if not Assigned(oRec.Pts) or
+ (ParseFirstLeft(oRec.FirstLeft) <> OutRec1) or
+ (oRec.IsHole = OutRec1.IsHole) then Continue;
+ if Poly2ContainsPoly1(oRec.Pts, Jr.OutPt2) then
+ oRec.FirstLeft := OutRec2;
+ end;
+
+ //sort out the hole states of both polygon ...
+ if Poly2ContainsPoly1(OutRec2.Pts, OutRec1.Pts) then
+ begin
+ //OutRec2 is contained by OutRec1 ...
+ OutRec2.IsHole := not OutRec1.IsHole;
+ OutRec2.FirstLeft := OutRec1;
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec1
+ if FUsingPolyTree then FixupFirstLefts2(OutRec2, OutRec1);
+
+ if (OutRec2.IsHole xor FReverseOutput) = (Area(OutRec2) > 0) then
+ ReversePolyPtLinks(OutRec2.Pts);
+ end else if Poly2ContainsPoly1(OutRec1.Pts, OutRec2.Pts) then
+ begin
+ //OutRec1 is contained by OutRec2 ...
+ OutRec2.IsHole := OutRec1.IsHole;
+ OutRec1.IsHole := not OutRec2.IsHole;
+ OutRec2.FirstLeft := OutRec1.FirstLeft;
+ OutRec1.FirstLeft := OutRec2;
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec1
+ if FUsingPolyTree then FixupFirstLefts2(OutRec1, OutRec2);
+
+ if (OutRec1.IsHole xor FReverseOutput) = (Area(OutRec1) > 0) then
+ ReversePolyPtLinks(OutRec1.Pts);
+ end else
+ begin
+ //the 2 polygons are completely separate ...
+ OutRec2.IsHole := OutRec1.IsHole;
+ OutRec2.FirstLeft := OutRec1.FirstLeft;
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec2
+ if FUsingPolyTree then FixupFirstLefts1(OutRec1, OutRec2);
+ end;
+ end else
+ begin
+ //joined 2 polygons together ...
+
+ //delete the obsolete pointer ...
+ OutRec2.Pts := nil;
+ OutRec2.BottomPt := nil;
+ OutRec2.Idx := OutRec1.Idx;
+
+ OutRec1.IsHole := HoleStateRec.IsHole;
+ if HoleStateRec = OutRec2 then
+ OutRec1.FirstLeft := OutRec2.FirstLeft;
+ OutRec2.FirstLeft := OutRec1;
+
+ //fixup FirstLeft pointers that may need reassigning to OutRec1
+ if FUsingPolyTree then FixupFirstLefts2(OutRec2, OutRec1);
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipper.DoSimplePolygons;
+var
+ I: Integer;
+ OutRec1, OutRec2: POutRec;
+ Op, Op2, Op3, Op4: POutPt;
+begin
+ I := 0;
+ while I < FPolyOutList.Count do
+ begin
+ OutRec1 := POutRec(fPolyOutList[I]);
+ inc(I);
+ Op := OutRec1.Pts;
+ if not assigned(Op) or OutRec1.IsOpen then Continue;
+ repeat //for each Pt in Path until duplicate found do ...
+ Op2 := Op.Next;
+ while (Op2 <> OutRec1.Pts) do
+ begin
+ if (PointsEqual(Op.Pt, Op2.Pt) and
+ (Op2.Next <> Op) and (Op2.Prev <> Op)) then
+ begin
+ //split the polygon into two ...
+ Op3 := Op.Prev;
+ Op4 := Op2.Prev;
+ Op.Prev := Op4;
+ Op4.Next := Op;
+ Op2.Prev := Op3;
+ Op3.Next := Op2;
+
+ OutRec1.Pts := Op;
+
+ OutRec2 := CreateOutRec;
+ OutRec2.Pts := Op2;
+ UpdateOutPtIdxs(OutRec2);
+ if Poly2ContainsPoly1(OutRec2.Pts, OutRec1.Pts) then
+ begin
+ //OutRec2 is contained by OutRec1 ...
+ OutRec2.IsHole := not OutRec1.IsHole;
+ OutRec2.FirstLeft := OutRec1;
+ if FUsingPolyTree then FixupFirstLefts2(OutRec2, OutRec1);
+ end
+ else
+ if Poly2ContainsPoly1(OutRec1.Pts, OutRec2.Pts) then
+ begin
+ //OutRec1 is contained by OutRec2 ...
+ OutRec2.IsHole := OutRec1.IsHole;
+ OutRec1.IsHole := not OutRec2.IsHole;
+ OutRec2.FirstLeft := OutRec1.FirstLeft;
+ OutRec1.FirstLeft := OutRec2;
+ if FUsingPolyTree then FixupFirstLefts2(OutRec1, OutRec2);
+ end else
+ begin
+ //the 2 polygons are separate ...
+ OutRec2.IsHole := OutRec1.IsHole;
+ OutRec2.FirstLeft := OutRec1.FirstLeft;
+ if FUsingPolyTree then FixupFirstLefts1(OutRec1, OutRec2);
+ end;
+ Op2 := Op; //ie get ready for the next iteration
+ end;
+ Op2 := Op2.Next;
+ end;
+ Op := Op.Next;
+ until (Op = OutRec1.Pts);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function GetUnitNormal(const Pt1, Pt2: TIntPoint): TDoublePoint;
+var
+ Dx, Dy, F: Single;
+begin
+ if (Pt2.X = Pt1.X) and (Pt2.Y = Pt1.Y) then
+ begin
+ Result.X := 0;
+ Result.Y := 0;
+ Exit;
+ end;
+
+ Dx := (Pt2.X - Pt1.X);
+ Dy := (Pt2.Y - Pt1.Y);
+ F := 1 / Hypot(Dx, Dy);
+ Dx := Dx * F;
+ Dy := Dy * F;
+ Result.X := Dy;
+ Result.Y := -Dx
+end;
+
+//------------------------------------------------------------------------------
+// TClipperOffset
+//------------------------------------------------------------------------------
+
+constructor TClipperOffset.Create(
+ MiterLimit: Double = 2;
+ ArcTolerance: Double = def_arc_tolerance);
+begin
+ inherited Create;
+ FPolyNodes := TPolyNode.Create;
+ FLowest.X := -1;
+ FMiterLimit := MiterLimit;
+ FArcTolerance := ArcTolerance;
+end;
+//------------------------------------------------------------------------------
+
+destructor TClipperOffset.Destroy;
+begin
+ Clear;
+ FPolyNodes.Free;
+ inherited;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.Clear;
+var
+ I: Integer;
+ PolyNode: TPolyNode;
+begin
+ for I := 0 to FPolyNodes.ChildCount -1 do
+ begin
+ PolyNode:= FPolyNodes.Childs[I];
+ PolyNode.Free;
+ end;
+ FPolyNodes.FCount := 0;
+ FPolyNodes.FBuffLen := 16;
+ SetLength(FPolyNodes.FChilds, 16);
+ FLowest.X := -1;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.AddPath(const Path: TPath;
+ JoinType: TJoinType; EndType: TEndType);
+var
+ I, J, K, HighI: Integer;
+ NewNode: TPolyNode;
+ ip: TIntPoint;
+begin
+ HighI := High(Path);
+ if HighI < 0 then Exit;
+ NewNode := TPolyNode.Create;
+ NewNode.FJoinType := JoinType;
+ NewNode.FEndType := EndType;
+
+ //strip duplicate points from path and also get index to the lowest point ...
+ if EndType in [etClosedLine, etClosedPolygon] then
+ while (HighI > 0) and PointsEqual(Path[0], Path[HighI]) do dec(HighI);
+ SetLength(NewNode.FPath, HighI +1);
+ NewNode.FPath[0] := Path[0];
+ J := 0; K := 0;
+ for I := 1 to HighI do
+ if not PointsEqual(NewNode.FPath[J], Path[I]) then
+ begin
+ inc(J);
+ NewNode.FPath[J] := Path[I];
+ if (NewNode.FPath[K].Y < Path[I].Y) or
+ ((NewNode.FPath[K].Y = Path[I].Y) and
+ (NewNode.FPath[K].X > Path[I].X)) then
+ K := J;
+ end;
+ inc(J);
+ if J < HighI +1 then
+ SetLength(NewNode.FPath, J);
+ if (EndType = etClosedPolygon) and (J < 3) then
+ begin
+ NewNode.free;
+ Exit;
+ end;
+ FPolyNodes.AddChild(NewNode);
+
+ if EndType <> etClosedPolygon then Exit;
+ //if this path's lowest pt is lower than all the others then update FLowest
+ if (FLowest.X < 0) then
+ begin
+ FLowest := IntPoint(FPolyNodes.ChildCount -1, K);
+ end else
+ begin
+ ip := FPolyNodes.Childs[FLowest.X].FPath[FLowest.Y];
+ if (NewNode.FPath[K].Y > ip.Y) or
+ ((NewNode.FPath[K].Y = ip.Y) and
+ (NewNode.FPath[K].X < ip.X)) then
+ FLowest := IntPoint(FPolyNodes.ChildCount -1, K);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.AddPaths(const Paths: TPaths;
+ JoinType: TJoinType; EndType: TEndType);
+var
+ I: Integer;
+begin
+ for I := 0 to High(Paths) do AddPath(Paths[I], JoinType, EndType);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.FixOrientations;
+var
+ I: Integer;
+begin
+ //fixup orientations of all closed paths if the orientation of the
+ //closed path with the lowermost vertex is wrong ...
+ if (FLowest.X >= 0) and
+ not Orientation(FPolyNodes.Childs[FLowest.X].FPath) then
+ begin
+ for I := 0 to FPolyNodes.ChildCount -1 do
+ if FPolyNodes.Childs[I].FEndType = etClosedPolygon then
+ FPolyNodes.Childs[I].FPath := ReversePath(FPolyNodes.Childs[I].FPath)
+ else if (FPolyNodes.Childs[I].FEndType = etClosedLine) and
+ Orientation(FPolyNodes.Childs[I].FPath) then
+ FPolyNodes.Childs[I].FPath := ReversePath(FPolyNodes.Childs[I].FPath);
+ end else
+ begin
+ for I := 0 to FPolyNodes.ChildCount -1 do
+ if (FPolyNodes.Childs[I].FEndType = etClosedLine) and
+ not Orientation(FPolyNodes.Childs[I].FPath) then
+ FPolyNodes.Childs[I].FPath := ReversePath(FPolyNodes.Childs[I].FPath);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.DoOffset(Delta: Double);
+var
+ I, J, K, Len, solCount: Integer;
+ X, X2, Y, Steps, AbsDelta: Double;
+ Node: TPolyNode;
+ N: TDoublePoint;
+begin
+ FSolution := nil;
+ FDelta := Delta;
+ AbsDelta := Abs(Delta);
+
+ //if Zero offset, just copy any CLOSED polygons to FSolution and return ...
+ if AbsDelta < Tolerance then
+ begin
+ solCount := 0;
+ SetLength(FSolution, FPolyNodes.ChildCount);
+ for I := 0 to FPolyNodes.ChildCount -1 do
+ if FPolyNodes.Childs[I].FEndType = etClosedPolygon then
+ begin
+ FSolution[solCount] := FPolyNodes.Childs[I].FPath;
+ inc(solCount);
+ end;
+ SetLength(FSolution, solCount);
+ Exit;
+ end;
+
+ //FMiterLimit: see offset_triginometry3.svg in the documentation folder ...
+ if FMiterLimit > 2 then FMiterLim := 2/(sqr(FMiterLimit))
+ else FMiterLim := 0.5;
+
+ if (FArcTolerance <= 0) then Y := def_arc_tolerance
+ else if FArcTolerance > AbsDelta * def_arc_tolerance then
+ Y := AbsDelta * def_arc_tolerance
+ else Y := FArcTolerance;
+
+ //see offset_triginometry2.svg in the documentation folder ...
+ Steps := PI / ArcCos(1 - Y / AbsDelta); //steps per 360 degrees
+ if (Steps > AbsDelta * Pi) then
+ Steps := AbsDelta * Pi; //ie excessive precision check
+
+ Math.SinCos(Two_Pi / Steps, FSin, FCos); //sin & cos per step
+ if Delta < 0 then FSin := -FSin;
+ FStepsPerRad := Steps / Two_Pi;
+
+ SetLength(FSolution, FPolyNodes.ChildCount * 2);
+ solCount := 0;
+ for I := 0 to FPolyNodes.ChildCount -1 do
+ begin
+ Node := FPolyNodes.Childs[I];
+ FInP := Node.FPath;
+ Len := length(FInP);
+
+ if (Len = 0) or
+ ((Delta <= 0) and ((Len < 3) or (Node.FEndType <> etClosedPolygon))) then
+ Continue;
+
+ FOutPos := 0;
+ FOutP := nil;
+
+ //if a single vertex then build circle or a square ...
+ if (Len = 1) then
+ begin
+ if Node.FJoinType = jtRound then
+ begin
+ X := 1; Y := 0;
+ for J := 1 to Round(Steps) do
+ begin
+ AddPoint(IntPoint(
+ Round(FInP[0].X + X * FDelta),
+ Round(FInP[0].Y + Y * FDelta)));
+ X2 := X;
+ X := X * FCos - FSin * Y;
+ Y := X2 * FSin + Y * FCos;
+ end
+ end else
+ begin
+ X := -1; Y := -1;
+ for J := 1 to 4 do
+ begin
+ AddPoint(IntPoint( Round(FInP[0].X + X * FDelta),
+ Round(FInP[0].Y + Y * FDelta)));
+ if X < 0 then X := 1
+ else if Y < 0 then Y := 1
+ else X := -1;
+ end;
+ end;
+ SetLength(FOutP, FOutPos);
+ FSolution[solCount] := FOutP;
+ Inc(solCount);
+ Continue;
+ end;
+
+ //build Normals ...
+ SetLength(FNorms, Len);
+ for J := 0 to Len-2 do
+ FNorms[J] := GetUnitNormal(FInP[J], FInP[J+1]);
+ if not (Node.FEndType in [etClosedLine, etClosedPolygon]) then
+ FNorms[Len-1] := FNorms[Len-2] else
+ FNorms[Len-1] := GetUnitNormal(FInP[Len-1], FInP[0]);
+
+ if Node.FEndType = etClosedPolygon then
+ begin
+ K := Len -1;
+ for J := 0 to Len-1 do
+ OffsetPoint(J, K, Node.FJoinType);
+ SetLength(FOutP, FOutPos);
+ FSolution[solCount] := FOutP;
+ Inc(solCount);
+ end
+ else if (Node.FEndType = etClosedLine) then
+ begin
+ K := Len -1;
+ for J := 0 to Len-1 do
+ OffsetPoint(J, K, Node.FJoinType);
+ SetLength(FOutP, FOutPos);
+ FSolution[solCount] := FOutP;
+ Inc(solCount);
+
+ FOutPos := 0;
+ FOutP := nil;
+
+ //re-build Normals ...
+ N := FNorms[Len - 1];
+ for J := Len-1 downto 1 do
+ begin
+ FNorms[J].X := -FNorms[J-1].X;
+ FNorms[J].Y := -FNorms[J-1].Y;
+ end;
+ FNorms[0].X := -N.X;
+ FNorms[0].Y := -N.Y;
+
+ K := 0;
+ for J := Len-1 downto 0 do
+ OffsetPoint(J, K, Node.FJoinType);
+ SetLength(FOutP, FOutPos);
+
+ FSolution[solCount] := FOutP;
+ Inc(solCount);
+ end else
+ begin
+ //offset the polyline going forward ...
+ K := 0;
+ for J := 1 to Len-2 do
+ OffsetPoint(J, K, Node.FJoinType);
+
+ //handle the end (butt, round or square) ...
+ if Node.FEndType = etOpenButt then
+ begin
+ J := Len - 1;
+ AddPoint(IntPoint(round(FInP[J].X + FNorms[J].X *FDelta),
+ round(FInP[J].Y + FNorms[J].Y * FDelta)));
+ AddPoint(IntPoint(round(FInP[J].X - FNorms[J].X *FDelta),
+ round(FInP[J].Y - FNorms[J].Y * FDelta)));
+ end else
+ begin
+ J := Len - 1;
+ K := Len - 2;
+ FNorms[J].X := -FNorms[J].X;
+ FNorms[J].Y := -FNorms[J].Y;
+ FSinA := 0;
+ if Node.FEndType = etOpenSquare then
+ DoSquare(J, K) else
+ DoRound(J, K);
+ end;
+
+ //re-build Normals ...
+ for J := Len-1 downto 1 do
+ begin
+ FNorms[J].X := -FNorms[J-1].X;
+ FNorms[J].Y := -FNorms[J-1].Y;
+ end;
+ FNorms[0].X := -FNorms[1].X;
+ FNorms[0].Y := -FNorms[1].Y;
+
+ //offset the polyline going backward ...
+ K := Len -1;
+ for J := Len -2 downto 1 do
+ OffsetPoint(J, K, Node.FJoinType);
+
+ //finally handle the start (butt, round or square) ...
+ if Node.FEndType = etOpenButt then
+ begin
+ AddPoint(IntPoint(round(FInP[0].X - FNorms[0].X *FDelta),
+ round(FInP[0].Y - FNorms[0].Y * FDelta)));
+ AddPoint(IntPoint(round(FInP[0].X + FNorms[0].X *FDelta),
+ round(FInP[0].Y + FNorms[0].Y * FDelta)));
+ end else
+ begin
+ FSinA := 0;
+ if Node.FEndType = etOpenSquare then
+ DoSquare(0, 1) else
+ DoRound(0, 1);
+ end;
+ SetLength(FOutP, FOutPos);
+ FSolution[solCount] := FOutP;
+ Inc(solCount);
+ end;
+ end;
+ SetLength(FSolution, solCount);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.Execute(out solution: TPaths; Delta: Double);
+var
+ I, Len: Integer;
+ Outer: TPath;
+ Bounds: TIntRect;
+begin
+ FixOrientations;
+ DoOffset(Delta);
+ //now clean up 'corners' ...
+ with TClipper.Create do
+ try
+ AddPaths(FSolution, ptSubject, True);
+ if Delta > 0 then
+ begin
+ Execute(ctUnion, solution, pftPositive, pftPositive);
+ end else
+ begin
+ Bounds := GetBounds(FSolution);
+ SetLength(Outer, 4);
+ Outer[0] := IntPoint(Bounds.left-10, Bounds.bottom+10);
+ Outer[1] := IntPoint(Bounds.right+10, Bounds.bottom+10);
+ Outer[2] := IntPoint(Bounds.right+10, Bounds.top-10);
+ Outer[3] := IntPoint(Bounds.left-10, Bounds.top-10);
+ AddPath(Outer, ptSubject, True);
+ ReverseSolution := True;
+ Execute(ctUnion, solution, pftNegative, pftNegative);
+ //delete the outer rectangle ...
+ Len := length(solution);
+ for I := 1 to Len -1 do solution[I-1] := solution[I];
+ if Len > 0 then SetLength(solution, Len -1);
+ end;
+ finally
+ free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.Execute(out solution: TPolyTree; Delta: Double);
+var
+ I: Integer;
+ Outer: TPath;
+ Bounds: TIntRect;
+ OuterNode: TPolyNode;
+begin
+ if not assigned(solution) then
+ raise exception.Create(rsClipperOffset);
+ solution.Clear;
+
+ FixOrientations;
+ DoOffset(Delta);
+
+ //now clean up 'corners' ...
+ with TClipper.Create do
+ try
+ AddPaths(FSolution, ptSubject, True);
+ if Delta > 0 then
+ begin
+ Execute(ctUnion, solution, pftPositive, pftPositive);
+ end else
+ begin
+ Bounds := GetBounds(FSolution);
+ SetLength(Outer, 4);
+ Outer[0] := IntPoint(Bounds.left-10, Bounds.bottom+10);
+ Outer[1] := IntPoint(Bounds.right+10, Bounds.bottom+10);
+ Outer[2] := IntPoint(Bounds.right+10, Bounds.top-10);
+ Outer[3] := IntPoint(Bounds.left-10, Bounds.top-10);
+ AddPath(Outer, ptSubject, True);
+ ReverseSolution := True;
+ Execute(ctUnion, solution, pftNegative, pftNegative);
+ //remove the outer PolyNode rectangle ...
+ if (solution.ChildCount = 1) and (solution.Childs[0].ChildCount > 0) then
+ begin
+ OuterNode := solution.Childs[0];
+ SetLength(solution.FChilds, OuterNode.ChildCount);
+ solution.FChilds[0] := OuterNode.Childs[0];
+ solution.FChilds[0].FParent := solution;
+ for I := 1 to OuterNode.ChildCount -1 do
+ solution.AddChild(OuterNode.Childs[I]);
+ end else
+ solution.Clear;
+ end;
+ finally
+ free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.AddPoint(const Pt: TIntPoint);
+const
+ BuffLength = 32;
+begin
+ if FOutPos = length(FOutP) then
+ SetLength(FOutP, FOutPos + BuffLength);
+ FOutP[FOutPos] := Pt;
+ Inc(FOutPos);
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.DoSquare(J, K: Integer);
+var
+ A, Dx: Double;
+begin
+ //see offset_triginometry.svg in the documentation folder ...
+ A := ArcTan2(FSinA, FNorms[K].X * FNorms[J].X + FNorms[K].Y * FNorms[J].Y);
+ Dx := tan(A/4);
+ AddPoint(IntPoint(
+ round(FInP[J].X + FDelta * (FNorms[K].X - FNorms[K].Y *Dx)),
+ round(FInP[J].Y + FDelta * (FNorms[K].Y + FNorms[K].X *Dx))));
+ AddPoint(IntPoint(
+ round(FInP[J].X + FDelta * (FNorms[J].X + FNorms[J].Y *Dx)),
+ round(FInP[J].Y + FDelta * (FNorms[J].Y - FNorms[J].X *Dx))));
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.DoMiter(J, K: Integer; R: Double);
+var
+ Q: Double;
+begin
+ Q := FDelta / R;
+ AddPoint(IntPoint(round(FInP[J].X + (FNorms[K].X + FNorms[J].X)*Q),
+ round(FInP[J].Y + (FNorms[K].Y + FNorms[J].Y)*Q)));
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.DoRound(J, K: Integer);
+var
+ I, Steps: Integer;
+ A, X, X2, Y: Double;
+begin
+ A := ArcTan2(FSinA, FNorms[K].X * FNorms[J].X + FNorms[K].Y * FNorms[J].Y);
+ Steps := Max(Round(FStepsPerRad * Abs(A)), 1);
+
+ X := FNorms[K].X;
+ Y := FNorms[K].Y;
+ for I := 1 to Steps do
+ begin
+ AddPoint(IntPoint(
+ round(FInP[J].X + X * FDelta),
+ round(FInP[J].Y + Y * FDelta)));
+ X2 := X;
+ X := X * FCos - FSin * Y;
+ Y := X2 * FSin + Y * FCos;
+ end;
+ AddPoint(IntPoint(
+ round(FInP[J].X + FNorms[J].X * FDelta),
+ round(FInP[J].Y + FNorms[J].Y * FDelta)));
+end;
+//------------------------------------------------------------------------------
+
+procedure TClipperOffset.OffsetPoint(J: Integer;
+ var K: Integer; JoinType: TJoinType);
+var
+ R, cosA: Double;
+begin
+ //cross product ...
+ FSinA := (FNorms[K].X * FNorms[J].Y - FNorms[J].X * FNorms[K].Y);
+ if (Abs(FSinA * FDelta) < 1.0) then
+ begin
+ //very nearly collinear edges can occasionally cause tiny self-intersections
+ //due to rounding so offset with a single vertex here. (nb: The two offset
+ //vertices that would otherwise have been used would be < 1 unit apart.)
+ //dot product ...
+ cosA := (FNorms[K].X * FNorms[J].X + FNorms[J].Y * FNorms[K].Y );
+ if (cosA > 0) then // angle => 0 deg.
+ begin
+ AddPoint(IntPoint(round(FInP[J].X + FNorms[K].X * FDelta),
+ round(FInP[J].Y + FNorms[K].Y * FDelta)));
+ Exit;
+ end
+ //else angle => 180 deg.
+ end
+ else if (FSinA > 1.0) then FSinA := 1.0
+ else if (FSinA < -1.0) then FSinA := -1.0;
+
+ if FSinA * FDelta < 0 then
+ begin
+ AddPoint(IntPoint(round(FInP[J].X + FNorms[K].X * FDelta),
+ round(FInP[J].Y + FNorms[K].Y * FDelta)));
+ AddPoint(FInP[J]);
+ AddPoint(IntPoint(round(FInP[J].X + FNorms[J].X * FDelta),
+ round(FInP[J].Y + FNorms[J].Y * FDelta)));
+ end
+ else
+ case JoinType of
+ jtMiter:
+ begin
+ R := 1 + (FNorms[J].X * FNorms[K].X + FNorms[J].Y * FNorms[K].Y);
+ if (R >= FMiterLim) then DoMiter(J, K, R)
+ else DoSquare(J, K);
+ end;
+ jtSquare: DoSquare(J, K);
+ jtRound: DoRound(J, K);
+ end;
+ K := J;
+end;
+//------------------------------------------------------------------------------
+
+function SimplifyPolygon(const Poly: TPath; FillType: TPolyFillType = pftEvenOdd): TPaths;
+begin
+ with TClipper.Create do
+ try
+ StrictlySimple := True;
+ AddPath(Poly, ptSubject, True);
+ Execute(ctUnion, Result, FillType, FillType);
+ finally
+ free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function SimplifyPolygons(const Polys: TPaths; FillType: TPolyFillType = pftEvenOdd): TPaths;
+begin
+ with TClipper.Create do
+ try
+ StrictlySimple := True;
+ AddPaths(Polys, ptSubject, True);
+ Execute(ctUnion, Result, FillType, FillType);
+ finally
+ free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function DistanceSqrd(const Pt1, Pt2: TIntPoint): Double; {$IFDEF INLINING} inline; {$ENDIF}
+var
+ dx, dy: Double;
+begin
+ dx := (Pt1.X - Pt2.X);
+ dy := (Pt1.Y - Pt2.Y);
+ result := (dx*dx + dy*dy);
+end;
+//------------------------------------------------------------------------------
+
+function DistanceFromLineSqrd(const pt, ln1, ln2: TIntPoint): double;
+var
+ A, B, C: double;
+begin
+ //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
+ A := ln1.Y - ln2.Y;
+ B := ln2.X - ln1.X;
+ C := A * ln1.X + B * ln1.Y;
+ C := A * pt.X + B * pt.Y - C;
+ Result := (C * C) / (A * A + B * B);
+end;
+//---------------------------------------------------------------------------
+
+function SlopesNearCollinear(const Pt1, Pt2, Pt3: TIntPoint;
+ DistSqrd: Double): Boolean;
+begin
+ //this function is more accurate when the point that's geometrically
+ //between the other 2 points is the one that's tested for distance.
+ //ie makes it more likely to pick up 'spikes' ...
+ if Abs(Pt1.X - Pt2.X) > Abs(Pt1.Y - Pt2.Y) then
+ begin
+ if (Pt1.X > Pt2.X) = (Pt1.X < Pt3.X) then
+ result := DistanceFromLineSqrd(Pt1, Pt2, Pt3) < DistSqrd
+ else if (Pt2.X > Pt1.X) = (Pt2.X < Pt3.X) then
+ result := DistanceFromLineSqrd(Pt2, Pt1, Pt3) < DistSqrd
+ else
+ result := DistanceFromLineSqrd(Pt3, Pt1, Pt2) < DistSqrd;
+ end else
+ begin
+ if (Pt1.Y > Pt2.Y) = (Pt1.Y < Pt3.Y) then
+ result := DistanceFromLineSqrd(Pt1, Pt2, Pt3) < DistSqrd
+ else if (Pt2.Y > Pt1.Y) = (Pt2.Y < Pt3.Y) then
+ result := DistanceFromLineSqrd(Pt2, Pt1, Pt3) < DistSqrd
+ else
+ result := DistanceFromLineSqrd(Pt3, Pt1, Pt2) < DistSqrd;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function PointsAreClose(const Pt1, Pt2: TIntPoint;
+ DistSqrd: Double): Boolean;
+begin
+ result := DistanceSqrd(Pt1, Pt2) <= DistSqrd;
+end;
+//------------------------------------------------------------------------------
+
+function CleanPolygon(const Poly: TPath; Distance: Double = 1.415): TPath;
+var
+ I, Len: Integer;
+ DistSqrd: double;
+ OutPts: array of TOutPt;
+ op: POutPt;
+
+ function ExcludeOp(op: POutPt): POutPt;
+ begin
+ Result := op.Prev;
+ Result.Next := op.Next;
+ op.Next.Prev := Result;
+ Result.Idx := 0;
+ end;
+
+begin
+ //Distance = proximity in units/pixels below which vertices
+ //will be stripped. Default ~= sqrt(2) so when adjacent
+ //vertices have both x & y coords within 1 unit, then
+ //the second vertex will be stripped.
+ DistSqrd := Round(Distance * Distance);
+ Result := nil;
+ Len := Length(Poly);
+ if Len = 0 then Exit;
+
+ SetLength(OutPts, Len);
+ for I := 0 to Len -1 do
+ begin
+ OutPts[I].Pt := Poly[I];
+ OutPts[I].Next := @OutPts[(I + 1) mod Len];
+ OutPts[I].Next.Prev := @OutPts[I];
+ OutPts[I].Idx := 0;
+ end;
+
+ op := @OutPts[0];
+ while (op.Idx = 0) and (op.Next <> op.Prev) do
+ begin
+ if PointsAreClose(op.Pt, op.Prev.Pt, DistSqrd) then
+ begin
+ op := ExcludeOp(op);
+ Dec(Len);
+ end else if PointsAreClose(op.Prev.Pt, op.Next.Pt, DistSqrd) then
+ begin
+ ExcludeOp(op.Next);
+ op := ExcludeOp(op);
+ Dec(Len, 2);
+ end
+ else if SlopesNearCollinear(op.Prev.Pt, op.Pt, op.Next.Pt, DistSqrd) then
+ begin
+ op := ExcludeOp(op);
+ Dec(Len);
+ end
+ else
+ begin
+ op.Idx := 1;
+ op := op.Next;
+ end;
+ end;
+
+ if Len < 3 then Len := 0;
+ SetLength(Result, Len);
+ for I := 0 to Len -1 do
+ begin
+ Result[I] := op.Pt;
+ op := op.Next;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function CleanPolygons(const Polys: TPaths; Distance: double = 1.415): TPaths;
+var
+ I, Len: Integer;
+begin
+ Len := Length(Polys);
+ SetLength(Result, Len);
+ for I := 0 to Len - 1 do
+ Result[I] := CleanPolygon(Polys[I], Distance);
+end;
+//------------------------------------------------------------------------------
+
+function Minkowski(const Base, Path: TPath;
+ IsSum: Boolean; IsClosed: Boolean): TPaths;
+var
+ i, j, delta, baseLen, pathLen: integer;
+ quad: TPath;
+ tmp: TPaths;
+begin
+ if IsClosed then delta := 1 else delta := 0;
+
+ baseLen := Length(Base);
+ pathLen := Length(Path);
+ setLength(tmp, pathLen);
+ if IsSum then
+ for i := 0 to pathLen -1 do
+ begin
+ setLength(tmp[i], baseLen);
+ for j := 0 to baseLen -1 do
+ begin
+ tmp[i][j].X := Path[i].X + Base[j].X;
+ tmp[i][j].Y := Path[i].Y + Base[j].Y;
+ end;
+ end
+ else
+ for i := 0 to pathLen -1 do
+ begin
+ setLength(tmp[i], baseLen);
+ for j := 0 to baseLen -1 do
+ begin
+ tmp[i][j].X := Path[i].X - Base[j].X;
+ tmp[i][j].Y := Path[i].Y - Base[j].Y;
+ end;
+ end;
+
+ SetLength(quad, 4);
+ SetLength(Result, (pathLen + delta) * (baseLen + 1));
+ for i := 0 to pathLen - 2 + delta do
+ begin
+ for j := 0 to baseLen - 1 do
+ begin
+ quad[0] := tmp[i mod pathLen][j mod baseLen];
+ quad[1] := tmp[(i+1) mod pathLen][j mod baseLen];
+ quad[2] := tmp[(i+1) mod pathLen][(j+1) mod baseLen];
+ quad[3] := tmp[i mod pathLen][(j+1) mod baseLen];
+ if not Orientation(quad) then quad := ReversePath(quad);
+ Result[i*baseLen + j] := copy(quad, 0, 4);
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function TranslatePath(const Path: TPath; const Delta: TIntPoint): TPath;
+var
+ i, len: Integer;
+begin
+ len := Length(Path);
+ SetLength(Result, len);
+ for i := 0 to High(Path) do
+ begin
+ Result[i].X := Path[i].X + Delta.X;
+ Result[i].Y := Path[i].Y + Delta.Y;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiSum(const Pattern, Path: TPath; PathIsClosed: Boolean): TPaths;
+begin
+ Result := Minkowski(Pattern, Path, true, PathIsClosed);
+ with TClipper.Create() do
+ try
+ AddPaths(Result, ptSubject, True);
+ Execute(ctUnion, Result, pftNonZero);
+ finally
+ Free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiSum(const Pattern: TPath; const Paths: TPaths;
+ PathFillType: TPolyFillType; PathIsClosed: Boolean): TPaths;
+var
+ I, Cnt: Integer;
+ Paths2: TPaths;
+ Path: TPath;
+begin
+ Result := nil;
+ if Length(Pattern) = 0 then Exit;
+ Cnt := Length(Paths);
+ with TClipper.Create() do
+ try
+ for I := 0 to Cnt -1 do
+ begin
+ Paths2 := Minkowski(Pattern, Paths[I], true, PathIsClosed);
+ AddPaths( Paths2, ptSubject, true);
+ if PathIsClosed then
+ begin
+ Path := TranslatePath(Paths[I], Pattern[0]);
+ AddPath(Path, ptClip, true);
+ end;
+ end;
+ Execute(ctUnion, Result, PathFillType, PathFillType);
+ finally
+ Free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function MinkowskiDiff(const Poly1, Poly2: TPath): TPaths;
+begin
+ Result := Minkowski(Poly1, Poly2, false, true);
+ with TClipper.Create() do
+ try
+ AddPaths(Result, ptSubject, True);
+ Execute(ctUnion, Result, pftNonZero);
+ finally
+ Free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+type
+ TNodeType = (ntAny, ntOpen, ntClosed);
+
+procedure AddPolyNodeToPaths(PolyNode: TPolyNode;
+ NodeType: TNodeType; var Paths: TPaths);
+var
+ I: Integer;
+ Match: Boolean;
+begin
+ case NodeType of
+ ntAny: Match := True;
+ ntClosed: Match := not PolyNode.IsOpen;
+ else Exit;
+ end;
+
+ if (Length(PolyNode.Contour) > 0) and Match then
+ begin
+ I := Length(Paths);
+ SetLength(Paths, I +1);
+ Paths[I] := PolyNode.Contour;
+ end;
+ for I := 0 to PolyNode.ChildCount - 1 do
+ AddPolyNodeToPaths(PolyNode.Childs[I], NodeType, Paths);
+end;
+//------------------------------------------------------------------------------
+
+function PolyTreeToPaths(PolyTree: TPolyTree): TPaths;
+begin
+ Result := nil;
+ AddPolyNodeToPaths(PolyTree, ntAny, Result);
+end;
+//------------------------------------------------------------------------------
+
+function ClosedPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
+begin
+ Result := nil;
+ AddPolyNodeToPaths(PolyTree, ntClosed, Result);
+end;
+//------------------------------------------------------------------------------
+
+function OpenPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
+var
+ I, J: Integer;
+begin
+ Result := nil;
+ //Open polys are top level only, so ...
+ for I := 0 to PolyTree.ChildCount - 1 do
+ if PolyTree.Childs[I].IsOpen then
+ begin
+ J := Length(Result);
+ SetLength(Result, J +1);
+ Result[J] := PolyTree.Childs[I].Contour;
+ end;
+end;
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+end.
diff --git a/Delphi/main demo/GR32_Misc.pas b/Delphi/main demo/GR32_Misc.pas
new file mode 100644
index 0000000..9e9f42f
--- /dev/null
+++ b/Delphi/main demo/GR32_Misc.pas
@@ -0,0 +1,295 @@
+unit GR32_Misc;
+
+(*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+*******************************************************************************)
+
+interface
+
+{$WARN UNSAFE_CODE OFF}
+
+uses
+ Windows, Types,
+ Classes, SysUtils, Math, GR32, GR32_LowLevel, GR32_Blend, GR32_Transforms,
+ //requires Graphics32 (revision 2180 or later) ...
+ //https://sourceforge.net/p/graphics32/code/HEAD/tree/trunk/Source/
+ Graphics, GR32_Math, GR32_Polygons, GR32_VPR;
+
+type
+ TArrayOfArrayOfArrayOfFixedPoint = array of TArrayOfArrayOfFixedPoint;
+
+function CreateMaskFromPolygon(bitmap: TBitmap32;
+ const polygons: TArrayOfArrayOfFloatPoint;
+ fillMode: TPolyFillMode = pfAlternate): TBitmap32; overload;
+procedure ApplyMask(modifiedBmp, originalBmp, maskBmp: TBitmap32; invertMask: boolean = false);
+procedure Simple3D(bitmap: TBitmap32; const pts: TArrayOfArrayOfFloatPoint;
+ dx,dy,fadeRate: integer; topLeftColor, bottomRightColor: TColor32;
+ fillMode: TPolyFillMode = pfAlternate);
+function GetEllipsePoints(const ellipseRect: TFloatRect): TArrayOfFloatPoint;
+
+const
+ MAXIMUM_SHADOW_FADE = 0;
+ MEDIUM_SHADOW_FADE = 5;
+ MINIMUM_SHADOW_FADE = 10;
+ NO_SHADOW_FADE = 11; //anything > 10
+
+implementation
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+procedure OffsetPoints(var pts: TArrayOfFloatPoint; dx, dy: single);
+var
+ i: integer;
+begin
+ for i := 0 to high(pts) do
+ with pts[i] do
+ begin
+ X := X + dx;
+ Y := Y + dy;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function CreateMaskFromPolygon(bitmap: TBitmap32;
+ const polygons: TArrayOfArrayOfFloatPoint;
+ fillMode: TPolyFillMode = pfAlternate): TBitmap32;
+var
+ highI: integer;
+begin
+ result := TBitmap32.create;
+ with bitmap do result.SetSize(width,height);
+ highI := high(polygons);
+ if highI < 0 then exit;
+ PolyPolygonFS(result, polygons, clWhite32, fillMode);
+ PolyPolyLineFS(result, polygons, clBlack32, true);
+end;
+//------------------------------------------------------------------------------
+
+procedure ApplyMask(modifiedBmp, originalBmp, maskBmp: TBitmap32; invertMask: boolean = false);
+var
+ i: integer;
+ origClr, modClr, mskClr: PColor32Entry;
+begin
+ if not assigned(originalBmp) or not assigned(maskBmp) or
+ (originalBmp.Width <> modifiedBmp.Width) or
+ (originalBmp.Height <> modifiedBmp.Height) or
+ (originalBmp.Height <> maskBmp.Height) or
+ (originalBmp.Height <> maskBmp.Height) then exit;
+
+ origClr := @originalBmp.Bits[0];
+ modClr := @modifiedBmp.Bits[0];
+ mskClr := @maskBmp.Bits[0];
+ for i := 1 to originalBmp.Width * originalBmp.Height do
+ begin
+ //black pixel in mask -> replace modified color with original color
+ //white pixel in mask -> keep modified color
+ if invertMask then
+ MergeMemEx(origClr.ARGB, modClr.ARGB, 255- mskClr.B) else
+ MergeMemEx(origClr.ARGB, modClr.ARGB, mskClr.B);
+ inc(origClr);
+ inc(modClr);
+ inc(mskClr);
+ end;
+ EMMS;
+end;
+//------------------------------------------------------------------------------
+
+procedure SimpleShadow(bitmap: TBitmap32; const pts: TArrayOfArrayOfFloatPoint;
+ dx, dy, fadeRate: integer; shadowColor: TColor32;
+ closed: boolean = false; NoInteriorBleed: boolean = false;
+ fillMode: TPolyFillMode = pfAlternate);
+var
+ i, j, maxD: integer;
+ sx,sy, a, alpha, alphaLinear, alphaExp, dRate: single;
+ p: TArrayOfFloatPoint;
+ originalBitmap, maskBitmap: TBitmap32;
+ sc: TColor32;
+begin
+ if ((dx = 0) and (dy = 0)) or (length(pts) = 0) then exit;
+
+ if abs(dy) > abs(dx) then
+ begin
+ maxD := abs(dy);
+ sy := sign(dy);
+ sx := dx/maxD;
+ end else
+ begin
+ maxD := abs(dx);
+ sx := sign(dx);
+ sy := dy/maxD;
+ end;
+
+ if fadeRate <= MAXIMUM_SHADOW_FADE then dRate := 0.05
+ else if fadeRate >= MINIMUM_SHADOW_FADE then dRate := 0.95
+ else dRate := fadeRate/10;
+ alpha := AlphaComponent(shadowColor);
+ alphaLinear := alpha*dRate/maxD;
+ alphaExp := exp(ln(dRate)/maxD);
+
+ NoInteriorBleed := NoInteriorBleed and closed;
+ if NoInteriorBleed then
+ begin
+ originalBitmap := TBitmap32.Create;
+ originalBitmap.Assign(bitmap);
+ maskBitmap := CreateMaskFromPolygon(bitmap,pts, fillMode);
+ end else
+ begin
+ originalBitmap := nil;
+ maskBitmap := nil;
+ end;
+
+ try
+ a := alpha;
+ sc := shadowColor;
+ for j := 0 to high(pts) do
+ begin
+ alpha := a;
+ shadowColor := sc;
+ p := copy(pts[j], 0, length(pts[j]));
+ for i := 1 to maxD do
+ begin
+ PolyLineFS(bitmap, p, shadowColor, closed);
+ alpha := alpha * alphaExp;
+ if fadeRate < NO_SHADOW_FADE then
+ shadowColor := SetAlpha(shadowColor, round(alpha - i*alphaLinear));
+ OffsetPoints(p, sx, sy);
+ end;
+ end;
+ if assigned(originalBitmap) then
+ ApplyMask(bitmap, originalBitmap, maskBitmap);
+ finally
+ FreeAndNil(originalBitmap);
+ FreeAndNil(maskBitmap);
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure Simple3D(bitmap: TBitmap32; const pts: TArrayOfArrayOfFloatPoint;
+ dx,dy,fadeRate: integer; topLeftColor, bottomRightColor: TColor32;
+ fillMode: TPolyFillMode = pfAlternate); overload;
+var
+ mask, orig: TBitmap32;
+begin
+ orig := TBitmap32.Create;
+ mask := CreateMaskFromPolygon(bitmap,pts, fillMode);
+ try
+ orig.Assign(bitmap);
+ SimpleShadow(bitmap, pts, -dx, -dy, fadeRate, bottomRightColor, true);
+ SimpleShadow(bitmap, pts, dx, dy, fadeRate, topLeftColor, true);
+ ApplyMask(bitmap, orig, mask, true);
+ finally
+ orig.Free;
+ mask.Free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function GetCBezierPoints(const control_points: array of TFloatPoint): TArrayOfFloatPoint;
+var
+ i, j, arrayLen, resultCnt: integer;
+ ctrlPts: array [ 0..3] of TFloatPoint;
+const
+ cbezier_tolerance = 0.5;
+ half = 0.5;
+
+ procedure RecursiveCBezier(const p1, p2, p3, p4: TFloatPoint);
+ var
+ p12, p23, p34, p123, p234, p1234: TFloatPoint;
+ begin
+ //assess flatness of curve ...
+ //http://groups.google.com/group/comp.graphics.algorithms/tree/browse_frm/thread/d85ca902fdbd746e
+ if abs(p1.x + p3.x - 2*p2.x) + abs(p2.x + p4.x - 2*p3.x) +
+ abs(p1.y + p3.y - 2*p2.y) + abs(p2.y + p4.y - 2*p3.y) < cbezier_tolerance then
+ begin
+ if resultCnt = length(result) then
+ setLength(result, length(result) +128);
+ result[resultCnt] := p4;
+ inc(resultCnt);
+ end else
+ begin
+ p12.X := (p1.X + p2.X) *half;
+ p12.Y := (p1.Y + p2.Y) *half;
+ p23.X := (p2.X + p3.X) *half;
+ p23.Y := (p2.Y + p3.Y) *half;
+ p34.X := (p3.X + p4.X) *half;
+ p34.Y := (p3.Y + p4.Y) *half;
+ p123.X := (p12.X + p23.X) *half;
+ p123.Y := (p12.Y + p23.Y) *half;
+ p234.X := (p23.X + p34.X) *half;
+ p234.Y := (p23.Y + p34.Y) *half;
+ p1234.X := (p123.X + p234.X) *half;
+ p1234.Y := (p123.Y + p234.Y) *half;
+ RecursiveCBezier(p1, p12, p123, p1234);
+ RecursiveCBezier(p1234, p234, p34, p4);
+ end;
+ end;
+
+begin
+ //first check that the 'control_points' count is valid ...
+ arrayLen := length(control_points);
+ if (arrayLen < 4) or ((arrayLen -1) mod 3 <> 0) then exit;
+
+ setLength(result, 128);
+ result[0] := control_points[0];
+ resultCnt := 1;
+ for i := 0 to (arrayLen div 3)-1 do
+ begin
+ for j := 0 to 3 do
+ ctrlPts[j] := control_points[i*3 +j];
+ RecursiveCBezier(ctrlPts[0], ctrlPts[1], ctrlPts[2], ctrlPts[3]);
+ end;
+ SetLength(result,resultCnt);
+end;
+//------------------------------------------------------------------------------
+
+function GetEllipsePoints(const ellipseRect: TFloatRect): TArrayOfFloatPoint;
+const
+ //Magic constant =
+ // 2/3*(1-cos(90deg/2))/sin(90deg/2) = 2/3*(sqrt(2)-1) = 0.276142375 ...
+ offset: single = 0.276142375;
+var
+ midx, midy, offx, offy: single;
+ pts: array [0..12] of TFloatPoint;
+begin
+ with ellipseRect do
+ begin
+ if (abs(Left - Right) <= 0.5) and (abs(Top - Bottom) <= 0.5) then
+ begin
+ setlength(result,1);
+ result[0] := FloatPoint(Left,Top);
+ exit;
+ end;
+
+ midx := (right + left)/2;
+ midy := (bottom + top)/2;
+ offx := (right - left) * offset;
+ offy := (bottom - top) * offset;
+ //draws an ellipse starting at angle 0 and moving anti-clockwise ...
+ pts[0] := FloatPoint(right, midy);
+ pts[1] := FloatPoint(right, midy - offy);
+ pts[2] := FloatPoint(midx + offx, top);
+ pts[3] := FloatPoint(midx, top);
+ pts[4] := FloatPoint(midx - offx, top);
+ pts[5] := FloatPoint(left, midy - offy);
+ pts[6] := FloatPoint(left, midy);
+ pts[7] := FloatPoint(left, midy + offy);
+ pts[8] := FloatPoint(midx - offx, bottom);
+ pts[9] := FloatPoint(midx, bottom);
+ pts[10] := FloatPoint(midx + offx, bottom);
+ pts[11] := FloatPoint(right, midy + offy);
+ pts[12] := pts[0];
+ end;
+ result := GetCBezierPoints(pts);
+end;
+//------------------------------------------------------------------------------
+
+end.
diff --git a/Delphi/main demo/clipper_demo.dpr b/Delphi/main demo/clipper_demo.dpr
new file mode 100644
index 0000000..8f851b1
--- /dev/null
+++ b/Delphi/main demo/clipper_demo.dpr
@@ -0,0 +1,15 @@
+program clipper_demo;
+
+uses
+ Forms,
+ main in 'main.pas' {MainForm},
+ GR32_Misc in 'GR32_Misc.pas',
+ clipper in '..\clipper.pas';
+
+{$R *.res}
+
+begin
+ Application.Initialize;
+ Application.CreateForm(TMainForm, MainForm);
+ Application.Run;
+end.
diff --git a/Delphi/main demo/clipper_demo.res b/Delphi/main demo/clipper_demo.res
new file mode 100644
index 0000000..7da8359
Binary files /dev/null and b/Delphi/main demo/clipper_demo.res differ
diff --git a/Delphi/main demo/main.dfm b/Delphi/main demo/main.dfm
new file mode 100644
index 0000000..9d09efa
--- /dev/null
+++ b/Delphi/main demo/main.dfm
@@ -0,0 +1,394 @@
+object MainForm: TMainForm
+ Left = 235
+ Top = 110
+ Caption = 'Clipper Delphi Demo'
+ ClientHeight = 728
+ ClientWidth = 1012
+ Color = clBtnFace
+ Font.Charset = ARABIC_CHARSET
+ Font.Color = clWindowText
+ Font.Height = -18
+ Font.Name = 'Arial'
+ Font.Style = []
+ KeyPreview = True
+ OldCreateOrder = False
+ Position = poDesktopCenter
+ OnCreate = FormCreate
+ OnKeyPress = FormKeyPress
+ OnMouseWheel = FormMouseWheel
+ OnResize = FormResize
+ PixelsPerInch = 144
+ TextHeight = 21
+ object Panel1: TPanel
+ Left = 0
+ Top = 0
+ Width = 256
+ Height = 709
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Align = alLeft
+ TabOrder = 0
+ object lblClipOpacity: TLabel
+ Left = 24
+ Top = 571
+ Width = 149
+ Height = 21
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Clip Opacity (255):'
+ FocusControl = tbClipOpacity
+ end
+ object lblSubjOpacity: TLabel
+ Left = 24
+ Top = 511
+ Width = 154
+ Height = 21
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Subj &Opacity (255):'
+ FocusControl = tbSubjOpacity
+ end
+ object GroupBox1: TGroupBox
+ Left = 18
+ Top = 11
+ Width = 223
+ Height = 161
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Clipping Oper&ation'
+ TabOrder = 0
+ object rbIntersection: TRadioButton
+ Left = 20
+ Top = 53
+ Width = 158
+ Height = 24
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Intersection'
+ Checked = True
+ TabOrder = 1
+ TabStop = True
+ OnClick = rbIntersectionClick
+ end
+ object rbUnion: TRadioButton
+ Left = 20
+ Top = 78
+ Width = 158
+ Height = 24
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Union'
+ TabOrder = 2
+ OnClick = rbIntersectionClick
+ end
+ object rbDifference: TRadioButton
+ Left = 20
+ Top = 104
+ Width = 158
+ Height = 23
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Difference'
+ TabOrder = 3
+ OnClick = rbIntersectionClick
+ end
+ object rbXOR: TRadioButton
+ Left = 20
+ Top = 129
+ Width = 158
+ Height = 24
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'XOR'
+ TabOrder = 4
+ OnClick = rbIntersectionClick
+ end
+ object rbNone: TRadioButton
+ Left = 20
+ Top = 28
+ Width = 158
+ Height = 24
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'None'
+ TabOrder = 0
+ OnClick = rbIntersectionClick
+ end
+ end
+ object rbStatic: TRadioButton
+ Left = 22
+ Top = 181
+ Width = 161
+ Height = 23
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = '&Static Polygons'
+ Checked = True
+ TabOrder = 1
+ TabStop = True
+ OnClick = rbStaticClick
+ end
+ object bExit: TButton
+ Left = 153
+ Top = 634
+ Width = 72
+ Height = 35
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Cancel = True
+ Caption = 'E&xit'
+ TabOrder = 7
+ OnClick = bExitClick
+ end
+ object gbRandom: TGroupBox
+ Left = 15
+ Top = 258
+ Width = 223
+ Height = 236
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ TabOrder = 4
+ object lblSubjCount: TLabel
+ Left = 6
+ Top = 56
+ Width = 189
+ Height = 21
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'No. Subject edges: (20)'
+ Enabled = False
+ FocusControl = tbSubj
+ end
+ object lblClipCount: TLabel
+ Left = 6
+ Top = 122
+ Width = 160
+ Height = 21
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'No. Clip edges (20):'
+ Enabled = False
+ FocusControl = tbClip
+ end
+ object tbSubj: TTrackBar
+ Left = 7
+ Top = 81
+ Width = 203
+ Height = 39
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Enabled = False
+ Max = 100
+ Min = 3
+ Position = 20
+ TabOrder = 2
+ ThumbLength = 16
+ TickStyle = tsNone
+ OnChange = tbSubjChange
+ end
+ object tbClip: TTrackBar
+ Left = 7
+ Top = 148
+ Width = 203
+ Height = 40
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Enabled = False
+ Max = 100
+ Min = 3
+ Position = 20
+ TabOrder = 3
+ ThumbLength = 16
+ TickStyle = tsNone
+ OnChange = tbSubjChange
+ end
+ object bNext: TButton
+ Left = 14
+ Top = 185
+ Width = 188
+ Height = 35
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = '&New Polygons'
+ TabOrder = 4
+ OnClick = bNextClick
+ end
+ object rbEvenOdd: TRadioButton
+ Left = 7
+ Top = 20
+ Width = 102
+ Height = 23
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'E&venOdd'
+ Checked = True
+ Enabled = False
+ TabOrder = 0
+ TabStop = True
+ OnClick = rbEvenOddClick
+ end
+ object rbNonZero: TRadioButton
+ Left = 115
+ Top = 20
+ Width = 96
+ Height = 23
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Non&Zero'
+ Enabled = False
+ TabOrder = 1
+ OnClick = rbEvenOddClick
+ end
+ end
+ object rbRandom1: TRadioButton
+ Left = 22
+ Top = 204
+ Width = 205
+ Height = 24
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Random Polygons &1'
+ TabOrder = 2
+ OnClick = rbStaticClick
+ end
+ object tbClipOpacity: TTrackBar
+ Left = 17
+ Top = 595
+ Width = 221
+ Height = 39
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Max = 255
+ Position = 255
+ TabOrder = 6
+ ThumbLength = 16
+ TickStyle = tsNone
+ OnChange = tbClipOpacityChange
+ end
+ object tbSubjOpacity: TTrackBar
+ Left = 17
+ Top = 535
+ Width = 221
+ Height = 39
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Max = 255
+ Position = 255
+ TabOrder = 5
+ ThumbLength = 16
+ TickStyle = tsNone
+ OnChange = tbSubjOpacityChange
+ end
+ object rbRandom2: TRadioButton
+ Left = 22
+ Top = 230
+ Width = 205
+ Height = 23
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Caption = 'Random Polygons &2'
+ TabOrder = 3
+ OnClick = rbStaticClick
+ end
+ object bSaveSvg: TButton
+ Left = 27
+ Top = 634
+ Width = 114
+ Height = 35
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Cancel = True
+ Caption = 'Save S&VG ...'
+ TabOrder = 8
+ OnClick = bSaveSvgClick
+ end
+ end
+ object StatusBar1: TStatusBar
+ Left = 0
+ Top = 709
+ Width = 1012
+ Height = 19
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Panels = <>
+ SimplePanel = True
+ end
+ object ImgView321: TImgView32
+ Left = 256
+ Top = 0
+ Width = 756
+ Height = 709
+ Margins.Left = 4
+ Margins.Top = 4
+ Margins.Right = 4
+ Margins.Bottom = 4
+ Align = alClient
+ Bitmap.ResamplerClassName = 'TNearestResampler'
+ BitmapAlign = baCustom
+ Scale = 1.000000000000000000
+ ScaleMode = smScale
+ ScrollBars.ShowHandleGrip = True
+ ScrollBars.Style = rbsDefault
+ ScrollBars.Size = 16
+ OverSize = 0
+ TabOrder = 2
+ OnDblClick = bNextClick
+ OnResize = ImgView321Resize
+ end
+ object SaveDialog1: TSaveDialog
+ DefaultExt = 'svg'
+ Filter = 'SVG Files (*.svg)|*.svg'
+ Left = 239
+ Top = 32
+ end
+end
diff --git a/Delphi/main demo/main.pas b/Delphi/main demo/main.pas
new file mode 100644
index 0000000..d9978a0
--- /dev/null
+++ b/Delphi/main demo/main.pas
@@ -0,0 +1,797 @@
+unit main;
+
+(*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2011 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+*******************************************************************************)
+
+interface
+
+uses
+ Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
+ Dialogs, StdCtrls, ComCtrls, ExtCtrls, Math,
+ //requires Graphics32 (revision 2180 or later) ...
+ //https://sourceforge.net/p/graphics32/code/HEAD/tree/trunk/Source/
+ GR32, GR32_Image, GR32_Polygons,
+ GR32_Misc, clipper;
+
+type
+
+ //SVG Builder structures ////////////////////////////////////////
+
+ TStyleInfo = record
+ pft: TPolyFillType;
+ brushClr: TColor32;
+ penClr: TColor32;
+ dashArray: TArrayOfInteger;
+ penWidth: double;
+ showCoords: boolean;
+ end;
+
+ TPolyInfo = record
+ paths: TPaths;
+ si: TStyleInfo;
+ end;
+ TPolyInfos = array of TPolyInfo;
+
+ TTextInfo = record
+ text: string;
+ x,y: integer;
+ fontName: string;
+ fontSize: integer;
+ fontColor: string;
+ end;
+ TTextInfos = array of TTextInfo;
+
+ /////////////////////////////////////////////////////////////////
+
+ TSvgBuilder = class
+ private
+ polyList: TPolyInfos;
+ textList: TTextInfos;
+ function Color32ToHtml(clr: TColor32): string;
+ public
+ fontName: string;
+ fontSize: integer;
+ fontColor: TColor32;
+ style: TStyleInfo;
+ constructor Create;
+ procedure Clear;
+ procedure AddPaths(const poly: TPaths);
+ procedure AddText(const text: string; X,Y: integer);
+ function SaveToFile(filename: string;
+ scale: double = 1.0; margin: integer = 10): boolean;
+ end;
+
+ TMainForm = class(TForm)
+ Panel1: TPanel;
+ StatusBar1: TStatusBar;
+ ImgView321: TImgView32;
+ GroupBox1: TGroupBox;
+ rbIntersection: TRadioButton;
+ rbUnion: TRadioButton;
+ rbDifference: TRadioButton;
+ rbXOR: TRadioButton;
+ rbStatic: TRadioButton;
+ bExit: TButton;
+ rbNone: TRadioButton;
+ gbRandom: TGroupBox;
+ lblSubjCount: TLabel;
+ lblClipCount: TLabel;
+ tbSubj: TTrackBar;
+ tbClip: TTrackBar;
+ rbRandom1: TRadioButton;
+ bNext: TButton;
+ tbClipOpacity: TTrackBar;
+ lblClipOpacity: TLabel;
+ lblSubjOpacity: TLabel;
+ tbSubjOpacity: TTrackBar;
+ rbRandom2: TRadioButton;
+ rbEvenOdd: TRadioButton;
+ rbNonZero: TRadioButton;
+ bSaveSvg: TButton;
+ SaveDialog1: TSaveDialog;
+ procedure FormCreate(Sender: TObject);
+ procedure ImgView321Resize(Sender: TObject);
+ procedure rbIntersectionClick(Sender: TObject);
+ procedure FormResize(Sender: TObject);
+ procedure tbSubjChange(Sender: TObject);
+ procedure bNextClick(Sender: TObject);
+ procedure bExitClick(Sender: TObject);
+ procedure tbClipOpacityChange(Sender: TObject);
+ procedure rbStaticClick(Sender: TObject);
+ procedure tbSubjOpacityChange(Sender: TObject);
+ procedure rbEvenOddClick(Sender: TObject);
+ procedure FormMouseWheel(Sender: TObject; Shift: TShiftState;
+ WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
+ procedure FormKeyPress(Sender: TObject; var Key: Char);
+ procedure bSaveSvgClick(Sender: TObject);
+ private
+ offsetMul2: integer; //accommodates 0.5 steps
+ function GetFillTypeI: TPolyFillType;
+ function GetOpTypeI: TClipType;
+ procedure ShowStaticPolys;
+ procedure ShowRandomPolys1(newPoly: boolean);
+ procedure ShowRandomPolys2(newPoly: boolean);
+ procedure RePaintBitmapI;
+ public
+ { Public declarations }
+ end;
+
+var
+ MainForm: TMainForm;
+
+implementation
+
+//------------------------------------------------------------------------------
+// TSvgBuilder
+//------------------------------------------------------------------------------
+
+constructor TSvgBuilder.Create;
+begin
+ fontName := 'Verdana';
+ fontSize := 12;
+ fontColor := clBlack32;
+ style.brushClr := clWhite32;
+ style.penClr := clBlack32;
+ style.penWidth := 1.5;
+end;
+//------------------------------------------------------------------------------
+
+procedure TSvgBuilder.Clear;
+begin
+ polyList := nil;
+ textList := nil;
+end;
+//------------------------------------------------------------------------------
+
+function TSvgBuilder.Color32ToHtml(clr: TColor32): string;
+begin
+ with TColor32Entry(clr) do
+ result := format('#%.2x%.2x%.2x', [R, G, B]) ;
+end;
+//------------------------------------------------------------------------------
+
+function IntArrayToStr(a: TArrayOfInteger; scale: double = 1.0;
+ dx: integer = 0; dy: integer = 0): string;
+var
+ i: integer;
+begin
+ result := format('%1.1n',[a[0] * scale + dx]);
+ for i := 1 to high(a) do
+ if odd(i) then
+ result := result + format(', %1.1n',[a[i] * scale + dy]) else
+ result := result + format(', %1.1n',[a[i] * scale + dx]);
+end;
+//------------------------------------------------------------------------------
+
+procedure TSvgBuilder.AddPaths(const poly: TPaths);
+var
+ i, len: integer;
+begin
+ i := length(poly);
+ if i = 0 then Exit;
+ len := length(polyList);
+ setlength(polyList, len+1);
+ setlength(polyList[len].paths, i);
+ for i := 0 to i-1 do
+ polyList[len].paths[i] := Copy(poly[i], 0, MaxInt);
+ polyList[len].si.pft := style.pft;
+ polyList[len].si.brushClr := style.brushClr;
+ polyList[len].si.penClr := style.penClr;
+ polyList[len].si.dashArray := copy(style.dashArray, 0, maxint);
+ polyList[len].si.penWidth := style.penWidth;
+ polyList[len].si.showCoords := style.showCoords;
+end;
+//------------------------------------------------------------------------------
+
+procedure TSvgBuilder.AddText(const text: string; X,Y: integer);
+var
+ len: integer;
+begin
+ len := length(textList);
+ setlength(textList, len +1);
+ textList[len].text := text;
+ textList[len].x := X;
+ textList[len].y := Y;
+ textList[len].fontName := fontName;
+ textList[len].fontSize := fontSize;
+ textList[len].fontColor := Color32ToHtml(fontColor);
+end;
+//------------------------------------------------------------------------------
+
+function TSvgBuilder.SaveToFile(filename: string;
+ scale: double = 1.0; margin: integer = 10): boolean;
+const
+ pft_string: array[TPolyFillType] of string = ('evenodd', 'nonzero', 'positive', 'negative');
+ svg_xml_start: array [0..1] of string =
+ ('<?xml version="1.0" standalone="no"?>'+#10+
+ '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"'+#10+
+ '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'+#10+#10+'<svg ',
+ 'version="1.1" xmlns="http://www.w3.org/2000/svg">'+#10+#10);
+ poly_start: string = ' <path d="';
+ svg_xml_end: string = '</svg>'+#10;
+var
+ i,j,k,len,len2: integer;
+ offsetX, offsetY: Int64;
+ rec: TIntRect;
+ ds: char;
+ ss: TStringStream;
+ dashArrayStr: string;
+begin
+ result := false;
+ len := length(polyList);
+ if len = 0 then Exit;
+
+ if (scale = 0) then scale := 1.0;
+ if (margin < 0) then margin := 0;
+ i := 0; j := 0;
+ //calculate the bounding rect (skipping empty polygons) ...
+ while i < len do
+ begin
+ len2 := length(polyList[i].paths);
+ j := 0;
+ while (j < len2) and (length(polyList[i].paths[j]) < 3) do inc(j);
+ if j < len2 then Break;
+ inc(i);
+ end;
+ if i = len then Exit;
+ rec.left := polyList[i].paths[j][0].X;
+ rec.right := rec.left;
+ rec.top := polyList[i].paths[j][0].Y;
+ rec.bottom := rec.top;
+ for i := i to len -1 do
+ for j := 0 to length(polyList[i].paths) -1 do
+ for k := 0 to length(polyList[i].paths[j]) -1 do
+ with polyList[i].paths[j][k] do
+ begin
+ if X < rec.left then rec.left := X
+ else if X > rec.right then rec.right := X;
+ if Y < rec.top then rec.top := Y
+ else if Y > rec.bottom then rec.bottom := Y;
+ end;
+ rec.left := round(rec.left * scale);
+ rec.top := round(rec.top * scale);
+ rec.right := round(rec.right * scale);
+ rec.bottom := round(rec.bottom * scale);
+ offsetX := -rec.left + margin;
+ offsetY := -rec.top + margin;
+
+ ds := DecimalSeparator;
+ DecimalSeparator := '.';
+ ss := TStringStream.Create('');
+ try
+ ss.WriteString(
+ format('%s width="%dpx" height="%dpx" viewBox="0 0 %d %d" %s',
+ [svg_xml_start[0],
+ (rec.right - rec.left) + margin*2,
+ (rec.bottom - rec.top) + margin*2,
+ (rec.right - rec.left) + margin*2,
+ (rec.bottom - rec.top) + margin*2,
+ svg_xml_start[1]]));
+
+ for i := 0 to len -1 do
+ if assigned(polyList[i].paths) then
+ begin
+ ss.WriteString(poly_start);
+ for j := 0 to high(polyList[i].paths) do
+ begin
+ if (length(polyList[i].paths[j]) < 3) then continue;
+ with polyList[i].paths[j][0] do
+ ss.WriteString( format(' M %1.2f %1.2f',
+ [X * scale + offsetX, Y * scale + offsetY]));
+ for k := 1 to high(polyList[i].paths[j]) do
+ with polyList[i].paths[j][k] do
+ ss.WriteString( format(' L %1.2f %1.2f',
+ [X * scale + offsetX, Y * scale + offsetY]));
+ ss.WriteString(' z');
+ end;
+
+ with polyList[i].si do
+ begin
+ if length(dashArray) > 1 then
+ dashArrayStr := 'stroke-dasharray:'+ IntArrayToStr(dashArray) +';'
+ else
+ dashArrayStr := '';
+ ss.WriteString(format('"'#10+
+ ' style="fill:%s; fill-opacity:%1.2n; fill-rule:%s;'#10+
+ ' stroke:%s; stroke-opacity:%1.2n; %s stroke-width:%1.2n;"/>'#10#10,
+ [Color32ToHtml(brushClr), AlphaComponent(brushClr)/255,
+ pft_string[pft], Color32ToHtml(penClr), AlphaComponent(penClr)/255,
+ dashArrayStr, penWidth]));
+ end;
+
+ if polyList[i].si.showCoords then
+ begin
+ ss.WriteString('<g font-family="Verdana" font-size="11" fill="black">'#10#10);
+ for j := 0 to high(polyList[i].paths) do
+ begin
+ if (length(polyList[i].paths[j]) < 3) then continue;
+ for k := 0 to high(polyList[i].paths[j]) do
+ with polyList[i].paths[j][k] do
+ ss.WriteString(format('<text x="%1.0n" y="%1.0n">%1.0n,%1.0n</text>'#10,
+ [X * scale + offsetX, Y * scale + offsetY, X, Y]));
+ ss.WriteString(#10);
+ end;
+ ss.WriteString('</g>'#10);
+ end;
+ end;
+
+ for i := 0 to high(textList) do
+ with textList[i] do
+ begin
+ if fontSize < 7 then fontSize := 7 else if fontSize > 30 then fontSize := 30;
+ ss.WriteString(format('<g font-family="%s" font-size="%d" fill="%s">'#10,
+ [fontName, fontSize, fontColor]));
+ ss.WriteString(format('<text x="%1.0n" y="%1.0n">%s</text>'#10'</g>'#10#10,
+ [X * scale + offsetX, Y * scale + offsetY, text]));
+ end;
+
+ ss.WriteString(svg_xml_end);
+ //finally write to file ...
+ with TFileStream.Create(filename, fmCreate) do
+ try CopyFrom(ss, 0); finally free; end;
+ finally
+ ss.Free;
+ DecimalSeparator := ds;
+ end;
+
+end;
+
+//------------------------------------------------------------------------------
+// Miscellaneous functions ...
+//------------------------------------------------------------------------------
+
+const
+ subjPenColor: TColor32 = $60C3C9CF;
+ subjBrushColor: TColor32 = $00DDDDF0;
+ clipPenColor: TColor32 = $30F9BEA6;
+ clipBrushColor: TColor32 = $00FFE0E0;
+ solPenColor: TColor32 = $7F003300;
+ solBrushColor: TColor32 = $8066EF7F;
+
+var
+ scale: integer = 1; //scale bitmap to 10 decimal places
+ subj: TArrayOfArrayOfFloatPoint = nil;
+ clip: TArrayOfArrayOfFloatPoint = nil;
+ subjI: TPaths = nil;
+ clipI: TPaths = nil;
+ solution: TArrayOfArrayOfFloatPoint = nil;
+ solutionI: TPaths = nil;
+ subjOpacity: cardinal = $FF000000;
+ clipOpacity: cardinal = $FF000000;
+
+{$R *.dfm}
+{$R polygons.res}
+
+//------------------------------------------------------------------------------
+
+function AAFloatPoint2AAPoint(const a: TArrayOfArrayOfFloatPoint;
+ decimals: integer = 0): TPaths;
+var
+ i,j,decScale: integer;
+begin
+ decScale := round(power(10,decimals));
+ setlength(result, length(a));
+ for i := 0 to high(a) do
+ begin
+ setlength(result[i], length(a[i]));
+ for j := 0 to high(a[i]) do
+ begin
+ result[i][j].X := round(a[i][j].X *decScale);
+ result[i][j].Y := round(a[i][j].Y *decScale);
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+function AAPoint2AAFloatPoint(const a: TPaths;
+ decimals: integer = 0): TArrayOfArrayOfFloatPoint;
+var
+ i,j,decScale: integer;
+begin
+ decScale := round(power(10,decimals));
+ setlength(result, length(a));
+ for i := 0 to high(a) do
+ begin
+ setlength(result[i], length(a[i]));
+ for j := 0 to high(a[i]) do
+ begin
+ result[i][j].X := a[i][j].X /decScale;
+ result[i][j].Y := a[i][j].Y /decScale;
+ end;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure LoadBinaryStreamToArrayOfArrayOfFloatPoint(stream: TStream;
+ out fpa: TArrayOfArrayOfFloatPoint);
+var
+ i,j: integer;
+begin
+ try
+ stream.Read(i, sizeof(i));
+ setlength(fpa, i);
+ for i := 0 to i-1 do
+ begin
+ stream.Read(j, sizeof(j));
+ setlength(fpa[i], j);
+ for j := 0 to j-1 do
+ stream.Read(fpa[i][j], sizeof(TFloatPoint));
+ end;
+ except
+ fpa := nil;
+ end;
+end;
+
+//------------------------------------------------------------------------------
+// TMainForm methods
+//------------------------------------------------------------------------------
+
+procedure TMainForm.FormCreate(Sender: TObject);
+begin
+ tbSubjOpacity.Position := 156;
+ tbClipOpacity.Position := 156;
+ Randomize;
+ StatusBar1.SimpleText :=
+ ' Use the mouse wheel (or +,- & 0) to adjust the clipped region''s offset.';
+ ImgView321.Bitmap.Font.Style := [fsBold];
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.bExitClick(Sender: TObject);
+begin
+ close;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.ImgView321Resize(Sender: TObject);
+begin
+ ImgView321.SetupBitmap(true, clWhite32);
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.RepaintBitmapI;
+var
+ pfm: TPolyFillMode;
+ sol: TArrayOfArrayOfFloatPoint;
+ solI: TPaths;
+ scaling: single;
+begin
+ ImgView321.Bitmap.Clear(clWhite32);
+
+ if rbEvenOdd.Checked then pfm := pfAlternate else pfm := pfWinding;
+ PolyPolygonFS(ImgView321.Bitmap, subj, subjBrushColor or subjOpacity, pfm);
+ PolyPolylineFS(ImgView321.Bitmap, subj, subjPenColor or subjOpacity, true);
+ PolyPolygonFS(ImgView321.Bitmap, clip, clipBrushColor or clipOpacity, pfm);
+ PolyPolylineFS(ImgView321.Bitmap, clip, clipPenColor or clipOpacity, true);
+ if assigned(solutionI) and not rbNone.Checked then
+ begin
+ if offsetMul2 = 0 then
+ begin
+ sol := AAPoint2AAFloatPoint(solutionI, scale);
+ end else
+ begin
+ //do offsetting ...
+ sol := AAPoint2AAFloatPoint(solutionI, scale);
+ PolyPolylineFS(ImgView321.Bitmap, sol, clGray32, true);
+ scaling := power(10, scale);
+ with TClipperOffset.Create() do
+ try
+ AddPaths(solutionI, jtRound, etClosedPolygon);
+ Execute(solI, offsetMul2/2 *scaling);
+ finally
+ Free;
+ end;
+ sol := AAPoint2AAFloatPoint(solI, scale);
+ end;
+ PolyPolygonFS(ImgView321.Bitmap, sol, solBrushColor);
+
+ //now add a 3D effect to the solution to make it stand out ...
+ Simple3D(ImgView321.Bitmap, sol, 3, 3, MAXIMUM_SHADOW_FADE, clWhite32, clBlack32);
+ PolyPolylineFS(ImgView321.Bitmap, sol, solPenColor, true);
+ end;
+ with ImgView321.Bitmap do
+ begin
+ Textout(10, height-20, format('Offset = %1.1n pixels',[offsetMul2/2]));
+ end;
+ ImgView321.Repaint;
+end;
+//------------------------------------------------------------------------------
+
+function TMainForm.GetFillTypeI: TPolyFillType;
+begin
+ if rbEvenOdd.checked then
+ result := pftEvenOdd else
+ result := pftNonZero;
+end;
+//------------------------------------------------------------------------------
+
+function TMainForm.GetOpTypeI: TClipType;
+begin
+ if rbIntersection.Checked then result := ctIntersection
+ else if rbUnion.Checked then result := ctUnion
+ else if rbDifference.Checked then result := ctDifference
+ else result := ctXor;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.tbSubjOpacityChange(Sender: TObject);
+begin
+ lblSubjOpacity.Caption := format('Subj &Opacity (%d):',[tbSubjOpacity.Position]);
+ subjOpacity := cardinal(tbSubjOpacity.Position) shl 24;
+ RePaintBitmapI;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.tbClipOpacityChange(Sender: TObject);
+begin
+ lblClipOpacity.Caption := format('Clip &Opacity (%d):',[tbClipOpacity.Position]);
+ clipOpacity := cardinal(tbClipOpacity.Position) shl 24;
+ RePaintBitmapI;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.rbStaticClick(Sender: TObject);
+begin
+ if rbStatic.Checked then
+ ShowStaticPolys
+ else if rbRandom1.Checked then
+ ShowRandomPolys1(true)
+ else
+ ShowRandomPolys2(true);
+
+ rbNonZero.Enabled := not rbStatic.Checked;
+ rbEvenOdd.Enabled := not rbStatic.Checked;
+ lblSubjCount.Enabled := rbRandom1.Checked;
+ tbSubj.Enabled := rbRandom1.Checked;
+ tbClip.Enabled := not rbStatic.Checked;
+ lblClipCount.Enabled := not rbStatic.Checked;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.rbIntersectionClick(Sender: TObject);
+begin
+ if rbStatic.Checked then ShowStaticPolys
+ else if rbRandom1.Checked then ShowRandomPolys1(false)
+ else ShowRandomPolys2(false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.bNextClick(Sender: TObject);
+begin
+ if not bNext.Enabled then exit;
+ if rbRandom1.Checked then ShowRandomPolys1(true)
+ else ShowRandomPolys2(true);
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.FormResize(Sender: TObject);
+begin
+ if visible then rbIntersectionClick(nil);
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.tbSubjChange(Sender: TObject);
+begin
+ lblSubjCount.Caption := format('Random Subj Count (%d):',[tbSubj.Position]);
+ lblClipCount.Caption := format('Random Clip Count (%d):',[tbClip.Position]);
+ if not bNext.Enabled then exit;
+ //only update random polygons once the mouse has been released ...
+ if (GetAsyncKeyState(VK_LBUTTON) < 0) then exit;
+ if rbRandom1.Checked then ShowRandomPolys1(true)
+ else ShowRandomPolys2(true);
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.rbEvenOddClick(Sender: TObject);
+begin
+ if rbRandom1.Checked then ShowRandomPolys1(false)
+ else ShowRandomPolys2(false);
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.ShowStaticPolys;
+var
+ rs: TResourceStream;
+begin
+ solution := nil;
+ rs := TResourceStream.Create(HInstance, 'POLYGON', RT_RCDATA);
+ LoadBinaryStreamToArrayOfArrayOfFloatPoint(rs, subj);
+ rs.Free;
+
+ rs := TResourceStream.Create(HInstance, 'CLIP', RT_RCDATA);
+ LoadBinaryStreamToArrayOfArrayOfFloatPoint(rs, clip);
+ rs.Free;
+
+ subjI := AAFloatPoint2AAPoint(subj, scale);
+ clipI := AAFloatPoint2AAPoint(clip, scale);
+
+ if not rbNone.Checked then
+ with TClipper.Create do
+ try
+ AddPaths(subjI, ptSubject, true);
+ AddPaths(clipI, ptClip, true);
+ Execute(GetOpTypeI, solutionI, pftNonZero, pftNonZero);
+ finally
+ free;
+ end;
+ RepaintBitmapI;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.ShowRandomPolys1(newPoly: boolean);
+var
+ i,highI,w,h: integer;
+ fillType: TPolyFillType;
+begin
+ w := (ImgView321.ClientWidth -30);
+ h := (ImgView321.ClientHeight -30);
+ fillType := GetFillTypeI;
+
+ if newPoly then
+ begin
+ solution := nil;
+ //nb: although for this demo I chose to display just one random subject
+ //and one random clip polygon, it would be very easy to make multiple
+ //subject and clip polygons here. Clipper would handle them just as easily
+ //(as is demonstrated in ShowStaticPolys).
+ setLength(subj, 1);
+ highI := tbSubj.Position -1;
+ setLength(subj[0], highI+1);
+ for i := 0 to highI do
+ subj[0][i] := FloatPoint(10+round(random*w), 10+round(random*h));
+ setLength(clip, 1);
+ highI := tbClip.Position - 1;
+ setLength(clip[0], highI+1);
+ for i := 0 to highI do
+ clip[0][i] := FloatPoint(10+round(random*w), 10+round(random*h));
+ end;
+
+ subjI := AAFloatPoint2AAPoint(subj, scale);
+ clipI := AAFloatPoint2AAPoint(clip, scale);
+
+ if not rbNone.Checked then
+ with TClipper.Create do
+ try
+ AddPaths(subjI, ptSubject, true);
+ AddPaths(clipI, ptClip, true);
+ Execute(GetOpTypeI, solutionI, fillType, fillType);
+ finally
+ free;
+ end;
+ RepaintBitmapI;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.ShowRandomPolys2(newPoly: boolean);
+var
+ i,j,w,h: integer;
+ pt: TFloatPoint;
+ rec: TFloatRect;
+ fillType: TPolyFillType;
+ rs: TResourceStream;
+begin
+
+ w := (ImgView321.ClientWidth -30);
+ h := (ImgView321.ClientHeight -30);
+ fillType := GetFillTypeI;
+
+ if newPoly then
+ begin
+ solution := nil;
+
+ rs := TResourceStream.Create(HInstance, 'AUSTRALIA', RT_RCDATA);
+ LoadBinaryStreamToArrayOfArrayOfFloatPoint(rs, subj);
+ rs.Free;
+
+ //make bubbles for clip ...
+ setlength(clip, tbClip.Position);
+ for i := 0 to high(clip) do
+ begin
+ pt := FloatPoint(random*(w-100) +50, random*(h-100) +50);
+ j := round(random*45) + 5;
+ rec := FloatRect(pt.X -j, pt.Y - j, pt.X +j, pt.Y + j);
+ clip[i] := GetEllipsePoints(rec);
+ end;
+ end;
+
+ subjI := AAFloatPoint2AAPoint(subj, scale);
+ clipI := AAFloatPoint2AAPoint(clip, scale);
+
+ if not rbNone.Checked then
+ with TClipper.Create do
+ try
+ AddPaths(subjI, ptSubject, true);
+ AddPaths(clipI, ptClip, true);
+ Execute(GetOpTypeI, solutionI, fillType, fillType);
+ finally
+ free;
+ end;
+ RepaintBitmapI;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.FormMouseWheel(Sender: TObject; Shift: TShiftState;
+ WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
+begin
+ if WheelDelta > 0 then
+ begin
+ if offsetMul2 = 20 then exit;
+ inc(offsetMul2);
+ RePaintBitmapI;
+ end
+ else if WheelDelta < 0 then
+ begin
+ if offsetMul2 = -20 then exit;
+ dec(offsetMul2);
+ RePaintBitmapI;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
+begin
+ case Key of
+ '0',')': offsetMul2 := 0;
+ '=','+': if offsetMul2 = 20 then exit else inc(offsetMul2);
+ '-','_': if offsetMul2 = -20 then exit else dec(offsetMul2);
+ else exit;
+ end;
+ RePaintBitmapI;
+end;
+//------------------------------------------------------------------------------
+
+function MakeArrayOfIntPoint(const pts: array of integer): TPath;
+var
+ i, len: integer;
+begin
+ result := nil;
+ len := length(pts) div 2;
+ if len < 1 then exit;
+ setlength(result, len);
+ for i := 0 to len -1 do
+ begin
+ result[i].X := pts[i*2];
+ result[i].Y := pts[i*2 +1];
+ end;
+end;
+//------------------------------------------------------------------------------
+
+procedure TMainForm.bSaveSvgClick(Sender: TObject);
+var
+ invScale: single;
+begin
+ if not SaveDialog1.Execute then exit;
+ invScale := 1/ power(10, scale);
+ with TSvgBuilder.Create do
+ try
+ style.penWidth := 0.8;
+
+ style.brushClr := $0F0000FF;
+ style.penClr := $800099FF;
+ AddPaths(subjI);
+
+ style.brushClr := $0FFFFF00;
+ style.penClr := $80FF9900;
+ AddPaths(clipI);
+
+ style.brushClr := $2000FF00;
+ style.penClr := $FF006600;
+ AddPaths(solutionI);
+
+ SaveToFile(SaveDialog1.FileName, invScale);
+ finally
+ free;
+ end;
+end;
+//------------------------------------------------------------------------------
+
+end.
diff --git a/Delphi/main demo/polygons.res b/Delphi/main demo/polygons.res
new file mode 100644
index 0000000..272463a
Binary files /dev/null and b/Delphi/main demo/polygons.res differ
diff --git a/Documentation/offset_triginometry.svg b/Documentation/offset_triginometry.svg
new file mode 100644
index 0000000..67e8bd3
--- /dev/null
+++ b/Documentation/offset_triginometry.svg
@@ -0,0 +1,42 @@
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="600" width="800" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <g transform="translate(0,-452.4)">
+ <g stroke="#000" fill="none">
+ <path style="color:#000000" stroke-width="1.961" stroke-dasharray="3.9224, 3.9224" d="m193.9 735.7-26.4 46.4c7.4 4.6 16.2 7.3 25.6 7.3 10.4 0 20.2-3.3 28.1-8.9l-27.3-44.8z"/>
+ <path style="color:#000000" stroke-width="1.8" stroke-dasharray="3.6, 3.6" d="m194.5 733.4 57.2-98.9"/>
+ <path style="color:#000000" stroke-width="1.8" d="m10.81 854.2 183.3-314.5 190.3 308.5"/>
+ </g>
+ <g stroke="#000" fill="#0f0">
+ <g stroke-width="2.138">
+ <path style="color:#000000" d="m194.2 635.6 15 21.2h-10v56.4h10l-15 21.2-15-21.2h10v-56.4h-10z"/>
+ <path style="color:#000000" d="m77.71 740.9 25.79 2.7-6.59 7.5 42.49 37.2 6.6-7.5 6 25.3-25.8-2.7 6.6-7.5-42.49-37.3-6.6 7.5z"/>
+ <path style="color:#000000" d="m194.2 635.6 15 21.2h-10v56.4h10l-15 21.2-15-21.2h10v-56.4h-10z"/>
+ <path style="color:#000000" d="m194.2 635.6 15 21.2h-10v56.4h10l-15 21.2-15-21.2h10v-56.4h-10z"/>
+ <path style="color:#000000" d="m194.2 635.6 15 21.2h-10v56.4h10l-15 21.2-15-21.2h10v-56.4h-10z"/>
+ </g>
+ <path style="color:#000000" d="m138.4 633.3h110" fill-rule="evenodd" stroke-dasharray="7.2, 7.2" stroke-width="1.8"/>
+ </g>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" font-size="20px" line-height="125%" y="543.2041" x="216.81096" font-family="Tahoma" fill="#000000"><tspan y="543.2041" x="216.81096" font-weight="bold">ß</tspan></text>
+ <path style="color:#000000" d="m195.2 737.7 88-52.5" stroke="#000" stroke-dasharray="7.2, 7.2" stroke-width="1.8" fill="none"/>
+ <path style="color:#000000" d="m272.9 633.2 20.5 2.7-8.1 5.9 22.2 30.7-8.2 5.8-22.2-30.7-8.1 5.9z" stroke="#000" stroke-width="1.752" fill="#ff0"/>
+ <g font-family="Tahoma">
+ <g font-size="10px">
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="756.90411" x="110.61097"><tspan y="756.90411" x="110.61097" font-size="14px">delta</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="687.75928" x="153.25862"><tspan y="687.75928" x="153.25862" font-size="14px">delta</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="687.40411" x="288.61096"><tspan y="687.40411" x="288.61096" font-size="14px">c</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="633.0722" x="255.56584"><tspan y="633.0722" x="255.56584" font-size="14px">d</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="738.10455" x="180.68153"><tspan y="738.10455" x="180.68153" font-size="14px">a</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="628.76648" x="190.6815"><tspan y="628.76648" x="190.6815" font-size="14px">b</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="534.92474" x="186.17192"><tspan y="534.92474" x="186.17192" font-size="14px">e</tspan></text>
+ </g>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" font-size="14px" line-height="125%" y="491.55411" x="434.41095"><tspan y="491.55411" x="434.41095">To square a join at exactly delta offset, such</tspan><tspan y="509.05411" x="434.41095">that (b) is delta distance from (a), and given</tspan><tspan y="526.55414" x="434.41095">that (c) is a perpendicular offset of (a) which</tspan><tspan y="544.054 [...]
+ </g>
+ <path style="color:#000000" d="m272.4 667.3-17.1 10.3 10.3 17.1" stroke="#000" stroke-dasharray="3.6, 3.6" stroke-width="1.8" fill="none"/>
+ <g transform="translate(-9.589,458.9)" stroke="#000" stroke-dasharray="3.6, 3.6" stroke-width="1.8" fill="none">
+ <path style="color:#000000" d="m206 78.44 35.1-60.24"/>
+ <path style="color:#000000" d="m230.1 122.7c14.4-8.7 24.1-24.46 24.1-42.51 0-18.38-10.1-34.42-24.9-42.97"/>
+ </g>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" font-size="14px" line-height="125%" y="577.60413" x="187.21097" font-family="Tahoma" fill="#000000"><tspan y="577.60413" x="187.21097" font-size="20px" font-weight="bold">Ø</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" font-size="14px" line-height="125%" y="773.54077" x="186.64101" font-family="Tahoma" fill="#000000"><tspan y="773.54077" x="186.64101" font-size="20px" font-weight="bold">Ø</tspan></text>
+ <path style="color:#000000" d="m109 884.2 85.9-147.8 73.6 119.9" stroke="#000" stroke-width="3.6" fill="none"/>
+ </g>
+</svg>
diff --git a/Documentation/offset_triginometry2.svg b/Documentation/offset_triginometry2.svg
new file mode 100644
index 0000000..9a03cba
--- /dev/null
+++ b/Documentation/offset_triginometry2.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 400 600">
+ <g stroke="#000" fill="none">
+ <path style="color:#000000" d="m513.5 810.4a185.3 203.1 0 1 1 -370.6 0 185.3 203.1 0 1 1 370.6 0z" transform="matrix(1.074 0 0 .98 -426.9 -543.2)" stroke-width="3.509"/>
+ <g>
+ <path transform="translate(-295.0,-530.9)" d="m280 972.4-222.13-74.3 1.96-234.1 223.27-70.5 136.1 190.6z"/>
+ <path style="color:#000000" d="m-30 230 120-90" stroke-dasharray="4, 4"/>
+ <path style="color:#000000" d="m-10 62.08-55.1 189.92l190.8 1" stroke-dasharray="4, 4"/>
+ </g>
+ </g>
+ <g font-size="14px" font-family="Tahoma" fill="#000000">
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="264.27399" x="-75.70369"><tspan x="-75.70369" y="264.27399">c</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="54.873951" x="-9.90369"><tspan x="-9.90369" y="54.873951">a</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="254.97398" x="133.90369"><tspan x="133.90369" y="254.97398">b</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="131.47398" x="93.00369"><tspan x="93.00369" y="131.47398">d</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="173.41545" x="52.1037"><tspan x="52.1037" y="173.41545">q</tspan></text>
+ </g>
+ <g>
+ <path style="color:#000000" d="m-20 251.2v-0.7c0-20.4-14.3-37.6-33.6-42.5" stroke="#000" fill="none"/>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" font-family="Tahoma" xml:space="preserve" font-size="20px" line-height="125%" y="245.65685" x="-54.47578" fill="#000000"><tspan y="245.65685" x="-54.47578" font-weight="bold">ß</tspan></text>
+ <path style="color:#000000" d="m44.1 142.9-16.2 11.7 11.7 16.2" stroke="#000" stroke-dasharray="3.6, 3.6" stroke-width="1.8" fill="none"/>
+ </g>
+ <g font-size="14px" font-family="Tahoma" fill="#000000">
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="91.773972" x="154.80371"><tspan x="154.80371" y="91.773972">Calculate the number of steps (S) needed to </tspan><tspan x="154.80371" y="109.27397">construct a flattened path approximating a</tspan><tspan x="154.80371" y="126.77397">circle with radius (R) where the maximum </tspan><tspan x="154.80371" y="144.27397">imprecision [...]
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="159.97398" x="73.6037"><tspan y="159.97398" x="73.6037" font-weight="bold">L</tspan></text>
+ <text style="letter-spacing:0px;block-progression:tb;text-indent:0;word-spacing:0px;color:#000000;text-transform:none" xml:space="preserve" line-height="125%" y="265.32526" x="28.05185"><tspan y="265.32526" x="28.05185" font-weight="bold">R</tspan></text>
+ </g>
+</svg>
diff --git a/Documentation/offset_triginometry3.svg b/Documentation/offset_triginometry3.svg
new file mode 100644
index 0000000..52eff70
--- /dev/null
+++ b/Documentation/offset_triginometry3.svg
@@ -0,0 +1,391 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="800"
+ height="600"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="offset_triginometry3.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.64"
+ inkscape:cx="-24.27"
+ inkscape:cy="218.1"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1280"
+ inkscape:window-height="753"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4"
+ inkscape:window-maximized="1"
+ showborder="true"
+ inkscape:showpageshadow="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3779"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-407.6,90.95)">
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:4, 4;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 604.8,209.6 85.8,-55.7"
+ id="path3907"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 420.4,326.1 603.7,10.08 757.6,263.6"
+ id="path2985"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ xml:space="preserve"
+ style="font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="624.09735"
+ y="215.06464"
+ id="text3861"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3863"
+ x="624.09735"
+ y="215.06464"
+ style="font-weight:bold;-inkscape-font-specification:Tahoma Bold">ß</tspan></text>
+ <path
+ sodipodi:nodetypes="cccccccc"
+ inkscape:connector-curvature="0"
+ id="path3857-2"
+ d="m 603,29.9 15,15.8 -10,0 0,155.1 -10,0 0,-155.1 -10,0 z"
+ style="color:#000000;fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.776;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ inkscape:transform-center-x="-20.96"
+ inkscape:transform-center-y="-1.652" />
+ <text
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="682.20001"
+ y="261.375"
+ id="text3952"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3954"
+ x="682.20001"
+ y="261.375"
+ style="font-size:14px">delta</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="698.20001"
+ y="151.875"
+ id="text3977"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3979"
+ x="698.20001"
+ y="151.875"
+ style="font-size:14px">c</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="599.76099"
+ y="3.3956299"
+ id="text3977-5-1-2"
+ sodipodi:linespacing="125%"
+ inkscape:transform-center-x="-52.52"
+ inkscape:transform-center-y="54.68"><tspan
+ sodipodi:role="line"
+ id="tspan3979-1-5-7"
+ x="599.76099"
+ y="3.3956299"
+ style="font-size:14px">b</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="766"
+ y="-49.349998"
+ id="text4077"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ x="766"
+ y="-49.349998"
+ id="tspan4087">When 'mitering' offset polygons, the maximum distance </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="-31.849998"
+ id="tspan3063">point 'b' can be from point 'a' is set by 'limit' where limit </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="-14.349998"
+ id="tspan3065">is a multiple of delta. Therefore, for any given angle</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="3.1500015"
+ id="tspan3067">we need to know if length(ab) > limit * delta.</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="20.650002"
+ id="tspan4083" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="38.150002"
+ id="tspan3890">Find the largest angle ß (or smallest Ø since Ø = pi - ß) </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="55.650002"
+ id="tspan3904">for a given limit, expressing ß as sin(ß) or cos(ß) since</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="73.150002"
+ id="tspan3908">these can easily be derived from cross or dot products </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="90.650002"
+ id="tspan3916">respectively.</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="98.598343"
+ id="tspan3884"
+ style="font-size:4px"> </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="113.15"
+ id="tspan3888">angle(abc) = Ø/2 </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="130.64999"
+ id="tspan3083">length(ab) = limit * delta</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="148.14999"
+ id="tspan3087">length(ac) = delta</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="165.64999"
+ id="tspan3089">sin(Ø/2) = delta / (limit * delta) = 1 / limit</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="183.14999"
+ id="tspan3157">Given that sin(Ø/2) = sqrt((1-cos(Ø))/2) **</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="200.64999"
+ id="tspan3151">1 / limit = sqrt((1-cos(Ø))/2)</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="218.14999"
+ id="tspan3161">limit = sqrt(2 / (1-cos(Ø)))</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="235.64999"
+ id="tspan3815"
+ style="font-weight:normal">1-cos(Ø) = 2 / sqr(limit) </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="253.14999"
+ id="tspan3043">Since Ø = pi - ß ...</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="270.64999"
+ id="tspan3868">1-cos(pi - ß) = 2 / sqr(limit)</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="288.14999"
+ id="tspan3047">and given cos(pi-ß) = -cos(ß) ** ... </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="296.09836"
+ style="font-size:4px;font-weight:normal"
+ id="tspan3823"> </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="310.64999"
+ style="font-weight:bold"
+ id="tspan3122">1+cos(ß) = 2 / sqr(limit) </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="318.59836"
+ style="font-size:4px;font-weight:normal"
+ id="tspan3118"> </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="333.14999"
+ style="font-weight:normal"
+ id="tspan3114">cos(ß) = 2 / sqr(limit) - 1</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="350.64999"
+ style="font-weight:bold"
+ id="tspan3051" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="368.14999"
+ style="font-weight:normal"
+ id="tspan3066">Example: if miter limit = 2 (ie 2 times delta) then </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="385.64999"
+ style="font-weight:normal"
+ id="tspan3070">cos(ß) = (2 / 4) -1 = -0.5 and so ß = 120 degrees. </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="403.14999"
+ style="font-weight:normal"
+ id="tspan3072">Therefore, when ß > 120 deg. (or Ø < 60 deg.), the</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="420.64999"
+ style="font-weight:normal"
+ id="tspan3090">distance point 'b' would be from point 'a' would exceed </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="438.14999"
+ style="font-weight:normal"
+ id="tspan3094">the limit.</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="446.09836"
+ id="tspan3171"
+ style="font-size:4px;font-weight:bold"> </tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="451.09836"
+ style="font-size:4px;font-weight:bold"
+ id="tspan3049" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="462.78452"
+ id="tspan3163"
+ style="font-size:11px">** see http://en.wikipedia.org/wiki/List_of_trigonometric_identities</tspan><tspan
+ sodipodi:role="line"
+ x="766"
+ y="476.53452"
+ id="tspan3153" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="490.28452"
+ id="tspan3145" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="504.03452"
+ id="tspan4124"
+ style="font-weight:bold" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="517.78448"
+ id="tspan4116" /><tspan
+ sodipodi:role="line"
+ x="766"
+ y="531.53448"
+ id="tspan4095" /></text>
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:2, 2;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 684.7,144.1 -10.6,5.9 5.4,10.1"
+ id="path4091"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="588.19745"
+ y="208.67169"
+ id="text3977-1"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3979-7"
+ x="588.19745"
+ y="208.67169"
+ style="font-size:14px">a</tspan></text>
+ <g
+ id="g3841"
+ transform="translate(400,-20.7)">
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3026"
+ d="M 175.6,276.8 233.7,175.7"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:7.2, 7.2;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3809"
+ d="m 227.7,188.7 c 27.7,17.9 31.6,57.6 -0.5,76.7"
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:1.81;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:7.24, 7.24;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ </g>
+ <path
+ style="color:#000000;fill:#00ff00;fill-opacity:1;stroke:#000000;stroke-width:2.1383;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 724.8,212.4 -9.2,24.2 -5.6,-8.3 -46.8,31.5 5.4,8.3 -26,-0.8 9.4,-24.3 5.5,8.3 47,-31.3 -5.6,-8.3 z"
+ id="path3777-9"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccc"
+ inkscape:transform-center-x="-96.45"
+ inkscape:transform-center-y="-137.3" />
+ <text
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ x="595.66949"
+ y="246.41019"
+ id="text3845"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3847"
+ x="595.66949"
+ y="246.41019"
+ style="font-size:20px;font-weight:bold;-inkscape-font-specification:Tahoma Bold">Ø</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text3849"
+ y="246.41019"
+ x="595.66949"
+ style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-backgr [...]
+ xml:space="preserve"><tspan
+ style="font-size:20px;font-weight:bold;-inkscape-font-specification:Tahoma Bold"
+ y="246.41019"
+ x="595.66949"
+ id="tspan3851"
+ sodipodi:role="line">Ø</tspan></text>
+ <path
+ style="color:#000000;fill:none;stroke:#000000;stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="M 530.8,333.3 603,210 677.5,330.2"
+ id="path2985-1-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+</svg>
diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..3d94797
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,24 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+http://www.boost.org/LICENSE_1_0.txt
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..c5d2ef9
--- /dev/null
+++ b/README
@@ -0,0 +1,407 @@
+=====================================================================
+Clipper Change Log
+=====================================================================
+v6.2.1 (31 October 2014) Rev 482
+* Bugfix in ClipperOffset.Execute where the Polytree.IsHole property
+ was returning incorrect values with negative offsets
+* Very minor improvement to join rounding in ClipperOffset
+* Fixed CPP OpenGL demo.
+
+v6.2.0 (17 October 2014) Rev 477
+* Numerous minor bugfixes, too many to list.
+ (See revisions 454-475 in Sourceforge Repository)
+* The ZFillFunction (custom callback function) has had its parameters
+ changed.
+* Curves demo removed (temporarily).
+* Deprecated functions have been removed.
+
+v6.1.5 (26 February 2014) Rev 460
+* Improved the joining of output polygons sharing a common edge
+ when those common edges are horizontal.
+* Fixed a bug in ClipperOffset.AddPath() which would produce
+ incorrect solutions when open paths were added before closed paths.
+* Minor code tidy and performance improvement
+
+v6.1.4 (6 February 2014)
+* Fixed bugs in MinkowskiSum
+* Fixed minor bug when using Clipper.ForceSimplify.
+* Modified use_xyz callback so that all 4 vertices around an
+ intersection point are now passed to the callback function.
+
+v6.1.3a (22 January 2014) Rev 453
+* Fixed buggy PointInPolygon function (C++ and C# only).
+ Note this bug only affected the newly exported function, the
+ internal PointInPolygon function used by Clipper was OK.
+
+v6.1.3 (19 January 2014) Rev 452
+* Fixed potential endless loop condition when adding open
+ paths to Clipper.
+* Fixed missing implementation of SimplifyPolygon function
+ in C++ code.
+* Fixed incorrect upper range constant for polygon coordinates
+ in Delphi code.
+* Added PointInPolygon function.
+* Overloaded MinkowskiSum function to accommodate multi-contour
+ paths.
+
+v6.1.2 (15 December 2013) Rev 444
+* Fixed broken C++ header file.
+* Minor improvement to joining polygons.
+
+v6.1.1 (13 December 2013) Rev 441
+* Fixed a couple of bugs affecting open paths that could
+ raise unhandled exceptions.
+
+v6.1.0 (12 December 2013)
+* Deleted: Previously deprecated code has been removed.
+* Modified: The OffsetPaths function is now deprecated as it has
+ been replaced by the ClipperOffset class which is much more
+ flexible.
+* Bugfixes: Several minor bugs have been fixed including
+ occasionally an incorrect nesting within the PolyTree structure.
+
+v6.0.0 (30 October 2013)
+* Added: Open path (polyline) clipping. A new 'Curves' demo
+ application showcases this (see the 'Curves' directory).
+* Update: Major improvement in the merging of
+ shared/collinear edges in clip solutions (see Execute).
+* Added: The IntPoint structure now has an optional 'Z' member.
+ (See the precompiler directive use_xyz.)
+* Added: Users can now force Clipper to use 32bit integers
+ (via the precompiler directive use_int32) instead of using
+ 64bit integers.
+* Modified: To accommodate open paths, the Polygon and Polygons
+ structures have been renamed Path and Paths respectively. The
+ AddPolygon and AddPolygons methods of the ClipperBase class
+ have been renamed AddPath and AddPaths respectively. Several
+ other functions have been similarly renamed.
+* Modified: The PolyNode Class has a new IsOpen property.
+* Modified: The Clipper class has a new ZFillFunction property.
+* Added: MinkowskiSum and MinkowskiDiff functions added.
+* Added: Several other new functions have been added including
+ PolyTreeToPaths, OpenPathsFromPolyTree and ClosedPathsFromPolyTree.
+* Added: The Clipper constructor now accepts an optional InitOptions
+ parameter to simplify setting properties.
+* Bugfixes: Numerous minor bugs have been fixed.
+* Deprecated: Version 6 is a major upgrade from previous versions
+ and quite a number of changes have been made to exposed structures
+ and functions. To minimize inconvenience to existing library users,
+ some code has been retained and some added to maintain backward
+ compatibility. However, because this code will be removed in a
+ future update, it has been marked as deprecated and a precompiler
+ directive use_deprecated has been defined.
+
+v5.1.6 (23 May 2013)
+* BugFix: CleanPolygon function was buggy.
+* Changed: The behaviour of the 'miter' JoinType has been
+ changed so that when squaring occurs, it's no longer
+ extended up to the miter limit but is squared off at
+ exactly 'delta' units. (This improves the look of mitering
+ with larger limits at acute angles.)
+* Added: New OffsetPolyLines function
+* Update: Minor code refactoring and optimisations
+
+v5.1.5 (5 May 2013)
+* Added: ForceSimple property to Clipper class
+* Update: Improved documentation
+
+v5.1.4 (24 March 2013)
+* Update: CleanPolygon function enhanced.
+* Update: Documentation improved.
+
+v5.1.3 (14 March 2013)
+* Bugfix: Minor bugfixes.
+* Update: Documentation significantly improved.
+
+v5.1.2 (26 February 2013)
+* Bugfix: PolyNode class was missing a constructor.
+* Update: The MiterLimit parameter in the OffsetPolygons
+ function has been renamed Limit and can now also be used to
+ limit the number of vertices used to construct arcs when
+ JoinType is set to jtRound.
+
+v5.1.0 (17 February 2013)
+* Update: ExPolygons has been replaced with the PolyTree &
+ PolyNode classes to more fully represent the parent-child
+ relationships of the polygons returned by Clipper.
+* Added: New CleanPolygon and CleanPolygons functions.
+* Bugfix: Another orientation bug fixed.
+
+v5.0.2 - 30 December 2012
+* Bugfix: Significant fixes in and tidy of the internal
+ Int128 class (which is used only when polygon coordinate
+ values are greater than ±0x3FFFFFFF (~1.07e9)).
+* Update: The Area algorithm has been updated and is faster.
+* Update: Documentation updates. The newish but undocumented
+ 'CheckInputs' parameter of the OffsetPolygons function has been
+ renamed 'AutoFix' and documented too. The comments on rounding
+ have also been improved (ie clearer and expanded).
+
+v4.10.0 - 25 December 2012
+* Bugfix: Orientation bugs should now be resolved (finally!).
+* Bugfix: Bug in Int128 class
+
+v4.9.8 - 2 December 2012
+* Bugfix: Further fixes to rare Orientation bug.
+
+v4.9.7 - 29 November 2012
+* Bugfix: Bug that very rarely returned the wrong polygon
+ orientation.
+* Bugfix: Obscure bug affecting OffsetPolygons when using
+ jtRound for the JoinType parameter and when polygons also
+ contain very large coordinate values (> +/-100000000000).
+
+v4.9.6 - 9 November 2012
+* Bugfix: Another obscure bug related to joining polygons.
+
+v4.9.4 - 2 November 2012
+* Bugfix: Bugs in Int128 class occasionally causing
+ wrong orientations.
+* Bugfix: Further fixes related to joining polygons.
+
+v4.9.0 - 9 October 2012
+* Bugfix: Obscure bug related to joining polygons.
+
+v4.8.9 - 25 September 2012
+* Bugfix: Obscure bug related to precision of intersections.
+
+v4.8.8 - 30 August 2012
+* Bugfix: Fixed bug in OffsetPolygons function introduced in
+ version 4.8.5.
+
+v4.8.7 - 24 August 2012
+* Bugfix: ReversePolygon function in C++ translation was broken.
+* Bugfix: Two obscure bugs affecting orientation fixed too.
+
+v4.8.6 - 11 August 2012
+* Bugfix: Potential for memory overflow errors when using
+ ExPolygons structure.
+* Bugfix: The polygon coordinate range has been reduced to
+ +/- 0x3FFFFFFFFFFFFFFF (4.6e18).
+* Update: ReversePolygons function was misnamed ReversePoints in C++.
+* Update: SimplifyPolygon function now takes a PolyFillType parameter.
+
+v4.8.5 - 15 July 2012
+* Bugfix: Potential for memory overflow errors in OffsetPolygons().
+
+v4.8.4 - 1 June 2012
+* Bugfix: Another obscure bug affecting ExPolygons structure.
+
+v4.8.3 - 27 May 2012
+* Bugfix: Obscure bug causing incorrect removal of a vertex.
+
+v4.8.2 - 21 May 2012
+* Bugfix: Obscure bug could cause an exception when using
+ ExPolygon structure.
+
+v4.8.1 - 12 May 2012
+* Update: Cody tidy and minor bug fixes.
+
+v4.8.0 - 30 April 2012
+* Bugfix: Occasional errors in orientation fixed.
+* Update: Added notes on rounding to the documentation.
+
+v4.7.6 - 11 April 2012
+* Fixed a bug in Orientation function (affecting C# translations only).
+* Minor documentation update.
+
+v4.7.5 - 28 March 2012
+* Bugfix: Fixed a recently introduced bug that occasionally caused an
+ unhandled exception in C++ and C# translations.
+
+v4.7.4 - 15 March 2012
+* Bugfix: Another minor bugfix.
+
+v4.7.2 - 4 March 2012
+* Bugfix: Fixed bug introduced in ver 4.7 which sometimes caused
+ an exception if ExPolygon structure was passed to Clipper's
+ Execute method.
+
+v4.7.1 - 3 March 2012
+* Bugfix: Rare crash when JoinCommonEdges joined polygons that
+ 'cancelled' each other.
+* Bugfix: Clipper's internal Orientation method occasionally
+ returned wrong result.
+* Update: Improved C# code (thanks to numerous excellent suggestions
+ from David Piepgrass)
+
+v4.7 - 10 February 2012
+* Improved the joining of output polygons sharing a common edge.
+
+v4.6.6 - 3 February 2012
+* Bugfix: Another obscure bug occasionally causing incorrect
+ polygon orientation.
+
+v4.6.5 - 17 January 2012
+* Bugfix: Obscure bug occasionally causing incorrect hole
+ assignment in ExPolygon structure.
+
+v4.6.4 - 8 November 2011
+* Added: SimplifyPolygon and SimplifyPolygons functions.
+
+v4.6.3 - 11 November 2011
+* Bugfix: Fixed another minor mitering bug in OffsetPolygons.
+
+v4.6.2 - 10 November 2011
+* Bugfix: Fixed a rare bug in the orientation of polygons
+ returned by Clipper's Execute() method.
+* Bugfix: Previous update introduced a mitering bug in the
+ OffsetPolygons function.
+
+v4.6 - 29 October 2011
+* Added: Support for Positive and Negative polygon fill
+ types (in addition to the EvenOdd and NonZero fill types).
+* Bugfix: The OffsetPolygons function was generating the
+ occasional artefact when 'shrinking' polygons.
+
+v4.5.5 - 8 October 2011
+* Bugfix: Fixed an obscure bug in Clipper's JoinCommonEdges
+ method.
+* Update: Replaced IsClockwise function with Orientation
+ function. The orientation issues affecting OffsetPolygons
+ should now be finally resolved.
+* Change: The Area function once again returns a signed value.
+
+v4.5.1 - 28 September 2011
+* Deleted: The UseFullCoordinateRange property has been
+ deleted since integer range is now managed implicitly.
+* BugFix: Minor bug in OffsetPolygon mitering.
+* Change: C# JoinType enum moved from Clipper class to
+ ClipperLib namespace.
+* Change: The Area function now returns the absolute area
+ (irrespective of orientation).
+* Change: The IsClockwise function now requires a second
+ parameter - YAxisPositiveUpward - to accommodate displays
+ with Y-axis oriented in either direction
+
+v4.4.4 - 10 September 2011
+* Change: Deleted jtButt from JoinType (used by the
+ OffsetPolygons function).
+* BugFix: Fixed another minor bug in OffsetPolygons function.
+* Update: Further improvements to the help file
+
+v4.4.3 - 29 August 2011
+* BugFix: fixed a minor rounding issue in OffsetPolygons
+ function (affected C++ & C# translations).
+* BugFix: fixed a minor bug in OffsetPolygons' function
+ declaration (affected C++ translation only).
+* Change: 'clipper' namespace changed to 'ClipperLib'
+ namespace in both C++ and C# code to remove the ambiguity
+ between the Clipper class and the namespace. (This also
+ required numerous updates to the accompanying demos.)
+
+v4.4.2 - 26 August 2011
+* BugFix: minor bugfixes in Clipper.
+* Update: the OffsetPolygons function has been significantly
+ improved by offering 4 different join styles.
+
+v4.4.0 - 6 August 2011
+* BugFix: A number of minor bugs have been fixed that mostly
+ affected the new ExPolygons structure.
+
+v4.3.0 - 17 June 2011
+* New: ExPolygons structure that explicitly associates 'hole'
+ polygons with their 'outer' container polygons.
+* New: Execute method overloaded so the solution parameter
+ can now be either Polygons or ExPolygons.
+* BugFix: Fixed a rare bug in solution polygons orientation.
+
+v4.2.8 - 21 May 2011
+* Update: JoinCommonEdges() improved once more.
+* BugFix: Several minor bugs fixed.
+
+v4.2.6 - 1 May 2011
+* Bugfix: minor bug in SlopesEqual function.
+* Update: Merging of output polygons sharing common edges
+ has been significantly inproved
+
+v4.2.4 - 26 April 2011
+ Input polygon coordinates can now contain the full range of
+ signed 64bit integers (ie +/-9,223,372,036,854,775,807). This
+ means that floating point values can be converted to and from
+ Clipper's 64bit integer coordinates structure (IntPoint) and
+ still retain a precision of up to 18 decimal places. However,
+ since the large-integer math that supports this expanded range
+ imposes a small cost on performance (~15%), a new property
+ UseFullCoordinateRange has been added to the Clipper class to
+ allow users the choice of whether or not to use this expanded
+ coordinate range. If this property is disabled, coordinate values
+ are restricted to +/-1,500,000,000.
+
+v4.2 - 12 April 2011
+ JoinCommonEdges() code significantly improved plus other minor
+ improvements.
+
+v4.1.2 - 9 April 2011
+* Update: Minor code tidy.
+* Bugfix: Possible endless loop in JoinCommonEdges() in clipper.pas.
+
+v4.1.1 - 8 April 2011
+* Update: All polygon coordinates are now stored as 64bit integers
+ (though they're still restricted to range -1.5e9 to +1.5e9 pending
+ the inclusion of code supporting 64bit math).
+* Change: AddPolygon and AddPolygons methods now return boolean
+ values.
+* Bugfix: Bug in JoinCommonEdges() caused potential endless loop.
+* Bugfix: Bug in IsClockwise(). (C++ code only)
+
+v4.0 - 5 April 2011
+* Clipper 4 is a major rewrite of earlier versions. The biggest
+ change is that floating point values are no longer used,
+ except for the storing of edge slope values. The main benefit
+ of this is the issue of numerical robustness has been
+ addressed. Due to other major code improvements Clipper v4
+ is approximately 40% faster than Clipper v3.
+* The AddPolyPolygon method has been renamed to AddPolygons.
+* The IgnoreOrientation property has been removed.
+* The clipper_misc library has been merged back into the
+ main clipper library.
+
+v3.1.0 - 17 February 2011
+* Bugfix: Obscure bug in TClipperBase.SetDx method that caused
+ problems with very small edges ( edges <1/1000th pixel in size).
+
+v3.0.3 - 9 February 2011
+* Bugfix: Significant bug, but only in C# code.
+* Update: Minor refactoring.
+
+v3.0 - 31 January 2011
+* Update: Major rewrite of the portion of code that calculates
+ the output polygons' orientation.
+* Update: Help file significantly improved.
+* Change: Renamed ForceOrientation property to IgnoreOrientation.
+ If the orientation of output polygons is not important, or can
+ be managed separately, clipping routines can be sped up by about
+ 60% by setting IgnoreOrientation to true. Defaults to false.
+* Change: The OffsetPolygon and Area functions have been moved to
+ the new unit - clipper_misc.
+
+2.99 - 15 January 2011
+* Bugfix: Obscure bug in AddPolygon method could cause an endless loop.
+
+2.8 - 20 November 2010
+* Updated: Output polygons which previously shared a common
+ edge are now merged.
+* Changed: The orientation of outer polygons is now clockwise
+ when the display's Y axis is positive downwards (as is
+ typical for most Windows applications). Inner polygons
+ (holes) have the opposite orientation.
+* Added: Support module for Cairo Graphics Library (with demo).
+* Updated: C# and C++ demos.
+
+2.522 - 15 October 2010
+* Added C# translation (thanks to Olivier Lejeune) and
+ a link to Ruby bindings (thanks to Mike Owens).
+
+2.0 - 30 July 2010
+* Clipper now clips using both the Even-Odd (alternate) and
+ Non-Zero (winding) polygon filling rules. (Previously Clipper
+ assumed the Even-Odd rule for polygon filling.)
+
+1.4c - 16 June 2010
+* Added C++ support for AGG graphics library
+
+1.2s - 2 June 2010
+* Added C++ translation of clipper.pas
+
+1.0 - 9 May 2010
diff --git a/Third Party/Flash/AS3_flash_readme.txt b/Third Party/Flash/AS3_flash_readme.txt
new file mode 100644
index 0000000..da54dfd
--- /dev/null
+++ b/Third Party/Flash/AS3_flash_readme.txt
@@ -0,0 +1,2 @@
+An AS3 Flash port written by Chris Denham:
+https://github.com/ChrisDenham/PolygonClipper.AS3
diff --git a/Third Party/Go/Go_readme.txt b/Third Party/Go/Go_readme.txt
new file mode 100644
index 0000000..a5a085a
--- /dev/null
+++ b/Third Party/Go/Go_readme.txt
@@ -0,0 +1,2 @@
+An AS3 Flash port written by Chris Tessum:
+https://github.com/ctessum/go.clipper
diff --git a/Third Party/Haskell/Haskell_readme.txt b/Third Party/Haskell/Haskell_readme.txt
new file mode 100644
index 0000000..a341b6d
--- /dev/null
+++ b/Third Party/Haskell/Haskell_readme.txt
@@ -0,0 +1,4 @@
+
+An Haskell module written by Chetan Taralekar <chetant at gmail.com>
+that wraps the Clipper library can be downloaded from:
+http://hackage.haskell.org/package/clipper
diff --git a/Third Party/LuaJIT/LuaJIT_readme.txt b/Third Party/LuaJIT/LuaJIT_readme.txt
new file mode 100644
index 0000000..14ae1b8
--- /dev/null
+++ b/Third Party/LuaJIT/LuaJIT_readme.txt
@@ -0,0 +1 @@
+http://www.freelists.org/post/luajit/binding-for-clipper-polygon-clipping-library
\ No newline at end of file
diff --git a/Third Party/Matlab/matlab_readme.txt b/Third Party/Matlab/matlab_readme.txt
new file mode 100644
index 0000000..aadd821
--- /dev/null
+++ b/Third Party/Matlab/matlab_readme.txt
@@ -0,0 +1,3 @@
+To compile your own mexfile run the command:
+
+mex clipper.cpp mexclipper.cpp
diff --git a/Third Party/Matlab/mexclipper.cpp b/Third Party/Matlab/mexclipper.cpp
new file mode 100644
index 0000000..d524524
--- /dev/null
+++ b/Third Party/Matlab/mexclipper.cpp
@@ -0,0 +1,283 @@
+// mex clipper.cpp mexclipper.cpp
+//
+// Modified 2014-10-24, Jari Repo (JR), University West, jari.repo at hv.se
+// replaced "Polygons" to "Paths"
+// replaced "AddPolygons" by "AddPaths"
+// replaced the call to "OffsetPolygons" by use of the "ClipperOffset" class
+
+#include "mex.h"
+#include "clipper.hpp"
+
+using namespace ClipperLib;
+
+//void read_polygons_MATLAB(const mxArray *prhs, Polygons &poly)
+void read_polygons_MATLAB(const mxArray *prhs, Paths &poly)
+{
+ int id_x, id_y;
+ int num_contours;
+ int nx, ny;
+ long64 *x, *y;
+ const mxArray *x_in, *y_in;
+
+ /* Checking if input is non empty Matlab-structure */
+ if (!mxIsStruct(prhs))
+ mexErrMsgTxt("Input needs to be structure.");
+ if (!mxGetM(prhs) || !mxGetN(prhs))
+ mexErrMsgTxt("Empty structure.");
+
+ /* Checking field names and data type */
+ id_x = mxGetFieldNumber(prhs,"x");
+ if (id_x==-1)
+ mexErrMsgTxt("Input structure must contain a field 'x'.");
+
+ x_in = mxGetFieldByNumber(prhs, 0, id_x);
+ if (!mxIsInt64(x_in))
+ mexErrMsgTxt("Structure field 'x' must be of type INT64.");
+
+ id_y = mxGetFieldNumber(prhs,"y");
+ if (id_y==-1)
+ mexErrMsgTxt("Input structure must contain a field 'y'.");
+ y_in = mxGetFieldByNumber(prhs, 0, id_y);
+ if (!mxIsInt64(y_in))
+ mexErrMsgTxt("Structure field 'y' must be of type INT64.");
+
+ num_contours = mxGetNumberOfElements(prhs);
+ poly.resize(num_contours);
+ for (unsigned i = 0; i < num_contours; i++){
+ x_in = mxGetFieldByNumber(prhs, i, id_x);
+ y_in = mxGetFieldByNumber(prhs, i, id_y);
+
+ nx = mxGetNumberOfElements(x_in);
+ ny = mxGetNumberOfElements(y_in);
+ if (nx!=ny)
+ mexErrMsgTxt("Structure fields x and y must be the same length.");
+
+ poly[i].resize(nx);
+
+ x = (long64*)mxGetData(x_in);
+ y = (long64*)mxGetData(y_in);
+ for (unsigned j = 0; j < nx; j++){
+ poly[i][j].X = x[j];
+ poly[i][j].Y = y[j];
+ }
+ }
+}
+
+//void write_polygons_MATLAB(mxArray *plhs, Polygons &solution)
+void write_polygons_MATLAB(mxArray *plhs, Paths &solution)
+{
+ mxArray *x_out, *y_out;
+
+ for (unsigned i = 0; i < solution.size(); ++i)
+ {
+ x_out = mxCreateDoubleMatrix(solution[i].size(),1,mxREAL);
+ y_out = mxCreateDoubleMatrix(solution[i].size(),1,mxREAL);
+ for (unsigned j = 0; j < solution[i].size(); ++j)
+ {
+ ((double*)mxGetPr(x_out))[j]=solution[i][j].X;
+ ((double*)mxGetPr(y_out))[j]=solution[i][j].Y;
+ }
+ mxSetFieldByNumber(plhs,i,0,x_out);
+ mxSetFieldByNumber(plhs,i,1,y_out);
+ }
+}
+
+
+void mexFunction(int nlhs, mxArray *plhs[],
+ int nrhs, const mxArray *prhs[])
+{
+ //Polygons subj, clip, solution;
+ Paths subj, clip, solution;
+ const char *field_names[] = {"x","y"};
+ int dims[2];
+
+ if (nrhs == 0) {
+ mexPrintf("OutPol = clipper(RefPol, ClipPol, Method, [RefF], [ClipF]);\n");
+ mexPrintf(" Clips polygons by Method:\n");
+ mexPrintf(" 0 - Difference (RefPol - ClipPol)\n");
+ mexPrintf(" 1 - Intersection\n");
+ mexPrintf(" 2 - Xor\n");
+ mexPrintf(" 3 - Union\n");
+ mexPrintf(" Optionally specifying Fill Types for the polygons:\n");
+ mexPrintf(" 0 - Even-Odd (default)\n");
+ mexPrintf(" 1 - Non-Zero\n");
+ mexPrintf(" 2 - Positive\n");
+ mexPrintf(" 3 - Negative\n\n");
+ mexPrintf("Or:\n\n");
+ mexPrintf("OutPol = clipper(RefPol, Delta, MiterLimit);\n");
+ mexPrintf(" Offsets RefPol by Delta (+: outset, -: inset).\n");
+ mexPrintf(" MiterLimit * Delta = max distance of new vertex.\n");
+ mexPrintf(" MiterLimit = 1 for square corners.\n");
+ mexPrintf(" MiterLimit = 0 for round corners.\n\n");
+ mexPrintf("Or:\n\n");
+ mexPrintf("Orientation = clipper(RefPol);\n");
+ mexPrintf(" Returns boolean orientations of polygons.\n\n");
+ mexPrintf("All polygons are structures with the fields ...\n");
+ mexPrintf(" .x: x-coordinates of contour\n");
+ mexPrintf(" .y: y-coordinates of contour\n");
+ mexPrintf("All polygons may contain several contours.\n");
+ mexPrintf("\nPolygon Clipping Routine based on clipper v4.7.5.\n");
+ mexPrintf(" Credit goes to Angus Johnson\n");
+ mexPrintf(" http://www.angusj.com/delphi/clipper.php\n\n");
+ return;}
+
+ /* Check number of arguments */
+
+ if (nlhs != 1)
+ mexErrMsgTxt("One output required.");
+
+ if (nrhs == 1)
+ {
+ // Find the orientation of input polygons
+ bool orient;
+ read_polygons_MATLAB(prhs[0], subj);
+ plhs[0] = mxCreateDoubleMatrix(subj.size(), 1, mxREAL);
+ for (unsigned i = 0; i < subj.size(); ++i)
+ {
+ orient = Orientation(subj[i]);
+ ((double*)mxGetPr(plhs[0]))[i] = (double)orient;
+ }
+ }
+ else if (nrhs >= 3)
+ {
+ if (!mxIsDouble(prhs[2]) || mxGetM(prhs[2])!=1 || mxGetN(prhs[2])!=1)
+ mexErrMsgTxt("Third input must be scalar.");
+ if (mxIsStruct(prhs[1]))
+ {
+ // Clip two input polygons
+ int ct;
+ ClipType CT;
+ PolyFillType SFT, CFT;
+ ct=mxGetScalar(prhs[2]);
+ switch (ct){
+ case 0:
+ CT=ctDifference;
+ break;
+ case 1:
+ CT=ctIntersection;
+ break;
+ case 2:
+ CT=ctXor;
+ break;
+ case 3:
+ CT=ctUnion;
+ break;
+ default:
+ mexErrMsgTxt("Third input must be 0, 1, 2, or 3.");
+ }
+
+ if (nrhs >= 4)
+ {
+ if (!mxIsDouble(prhs[3]) || mxGetM(prhs[3])!=1 || mxGetN(prhs[3])!=1)
+ mexErrMsgTxt("Fourth input must be scalar if specified.");
+
+ int sft;
+
+ sft=mxGetScalar(prhs[3]);
+ switch (sft){
+ case 0:
+ SFT = pftEvenOdd;
+ break;
+ case 1:
+ SFT = pftNonZero;
+ break;
+ case 2:
+ SFT = pftPositive;
+ break;
+ case 3:
+ SFT = pftNegative;
+ break;
+ default:
+ mexErrMsgTxt("Fourth input must be 0, 1, 2, or 3.");
+ }
+
+ }
+ else
+ SFT=pftEvenOdd;
+
+ if (nrhs >= 5)
+ {
+ if (!mxIsDouble(prhs[4]) || mxGetM(prhs[4])!=1 || mxGetN(prhs[4])!=1)
+ mexErrMsgTxt("Fifth input must be scalar if specified.");
+
+ int cft;
+
+ cft=mxGetScalar(prhs[4]);
+ switch (cft){
+ case 0:
+ CFT = pftEvenOdd;
+ break;
+ case 1:
+ CFT = pftNonZero;
+ break;
+ case 2:
+ CFT = pftPositive;
+ break;
+ case 3:
+ CFT = pftNegative;
+ break;
+ default:
+ mexErrMsgTxt("Fifth input must be 0, 1, 2, or 3.");
+ }
+
+ }
+ else
+ CFT=pftEvenOdd;
+
+ /* Import polygons to structures */
+ read_polygons_MATLAB(prhs[0], subj);
+ read_polygons_MATLAB(prhs[1], clip);
+
+ Clipper c;
+ c.AddPaths(subj, ptSubject, true); //assume closed polygons
+ c.AddPaths(clip, ptClip, true);
+
+ if (c.Execute(CT, solution, SFT, CFT)){
+ dims[0] = 1;
+ dims[1] = solution.size();
+ plhs[0] = mxCreateStructArray(2, dims, 2, field_names);
+ write_polygons_MATLAB(plhs[0], solution);
+ } else
+ mexErrMsgTxt("Clipper Error.");
+ }
+ else
+ {
+ // Offset single input polygon
+ if (!mxIsDouble(prhs[1]) || mxGetM(prhs[1])!=1 || mxGetN(prhs[1])!=1)
+ mexErrMsgTxt("Second input must be either a structure or a scalar double.");
+
+ if (nrhs > 3)
+ mexPrintf("Ignoring fill type arguments for offsetting.\n");
+
+ /* Import polygons to structures */
+ read_polygons_MATLAB(prhs[0], subj);
+
+ JoinType jt;
+ double delta, ml;
+
+ delta = mxGetScalar(prhs[1]);
+ ml = mxGetScalar(prhs[2]);
+
+ if (ml==0)
+ jt = jtRound;
+ else if (ml==1)
+ jt = jtSquare;
+ else
+ jt = jtMiter;
+
+ ClipperOffset offs( ml );
+ //enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
+ offs.AddPaths(subj,jt,etClosedPolygon);
+ offs.Execute(solution, delta);
+
+ dims[0] = 1;
+ dims[1] = solution.size();
+ plhs[0] = mxCreateStructArray(2, dims, 2, field_names);
+ write_polygons_MATLAB(plhs[0], solution);
+
+ offs.Clear();
+ }
+ }
+ else
+ mexErrMsgTxt("One or three inputs required.");
+}
diff --git a/Third Party/ObjectiveC/objectivec_readme.txt b/Third Party/ObjectiveC/objectivec_readme.txt
new file mode 100644
index 0000000..548a026
--- /dev/null
+++ b/Third Party/ObjectiveC/objectivec_readme.txt
@@ -0,0 +1,4 @@
+
+An Objective C wrapper for Clipper by John Swensen (http://www.swengames.com/) can be downloaded from:
+
+https://github.com/jpswensen/ClipperCocoa
\ No newline at end of file
diff --git a/Third Party/perl/perl_readme.txt b/Third Party/perl/perl_readme.txt
new file mode 100644
index 0000000..abcfa41
--- /dev/null
+++ b/Third Party/perl/perl_readme.txt
@@ -0,0 +1,5 @@
+
+A Perl module written by Steffen M�ller < smueller at cpan.org >
+that wraps the Clipper library can be downloaded from:
+
+https://metacpan.org/module/Math::Clipper
\ No newline at end of file
diff --git a/Third Party/python/clipper.py b/Third Party/python/clipper.py
new file mode 100644
index 0000000..8fb137b
--- /dev/null
+++ b/Third Party/python/clipper.py
@@ -0,0 +1,2259 @@
+#===============================================================================
+# #
+# Author : Angus Johnson #
+# Version : 5.1.6(b) #
+# Date : 1 June 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 #
+# #
+#===============================================================================
+
+import math
+from collections import namedtuple
+
+horizontal = float('-inf')
+
+class ClipType: (Intersection, Union, Difference, Xor) = range(4)
+class PolyType: (Subject, Clip) = range(2)
+class PolyFillType: (EvenOdd, NonZero, Positive, Negative) = range(4)
+class JoinType: (Square, Round, Miter) = range(3)
+class EndType: (Closed, Butt, Square, Round) = range(4)
+class EdgeSide: (Left, Right) = range(2)
+class Protects: (Neither, Left, Right, Both) = range(4)
+class Direction: (LeftToRight, RightToLeft) = range(2)
+
+Point = namedtuple('Point', 'x y')
+FloatPoint = namedtuple('FloatPoint', 'x y')
+Rect = namedtuple('FloatPoint', 'left top right bottom')
+
+class LocalMinima(object):
+ leftBound = rightBound = nextLm = None
+ def __init__(self, y, leftBound, rightBound):
+ self.y = y
+ self.leftBound = leftBound
+ self.rightBound = rightBound
+
+class Scanbeam(object):
+ __slots__ = ('y','nextSb')
+ def __init__(self, y, nextSb = None):
+ self.y = y
+ self.nextSb = nextSb
+ def __repr__(self):
+ s = 'None'
+ if self.nextSb is not None: s = '<obj>'
+ return "(y:%i, nextSb:%s)" % (self.y, s)
+
+class IntersectNode(object):
+ __slots__ = ('e1','e2','pt','nextIn')
+ def __init__(self, e1, e2, pt):
+ self.e1 = e1
+ self.e2 = e2
+ self.pt = pt
+ self.nextIn = None
+
+class OutPt(object):
+ __slots__ = ('idx','pt','prevOp','nextOp')
+ def __init__(self, idx, pt):
+ self.idx = idx
+ self.pt = pt
+ self.prevOp = None
+ self.nextOp = None
+
+class OutRec(object):
+ __slots__ = ('idx','bottomPt','isHole','FirstLeft', 'pts','PolyNode')
+ def __init__(self, idx):
+ self.idx = idx
+ self.bottomPt = None
+ self.isHole = False
+ self.FirstLeft = None
+ self.pts = None
+ self.PolyNode = None
+
+class JoinRec(object):
+ __slots__ = ('pt1a','pt1b','poly1Idx','pt2a', 'pt2b','poly2Idx')
+
+class HorzJoin(object):
+ edge = None
+ savedIdx = 0
+ prevHj = None
+ nextHj = None
+ def __init__(self, edge, idx):
+ self.edge = edge
+ self.savedIdx = idx
+
+#===============================================================================
+# Unit global functions ...
+#===============================================================================
+def IntsToPoints(ints):
+ result = []
+ for i in range(0, len(ints), 2):
+ result.append(Point(ints[i], ints[i+1]))
+ return result
+
+def Area(polygon):
+ # see http://www.mathopenref.com/coordpolygonarea2.html
+ highI = len(polygon) - 1
+ A = (polygon[highI].x + polygon[0].x) * (polygon[0].y - polygon[highI].y)
+ for i in range(highI):
+ A += (polygon[i].x + polygon[i+1].x) * (polygon[i+1].y - polygon[i].y)
+ return float(A) / 2
+
+def Orientation(polygon):
+ return Area(polygon) > 0.0
+
+#===============================================================================
+# PolyNode & PolyTree classes (+ ancilliary functions)
+#===============================================================================
+class PolyNode(object):
+ """Node of PolyTree"""
+
+ def __init__(self):
+ self.Contour = []
+ self.Childs = []
+ self.Parent = None
+ self.Index = 0
+ self.ChildCount = 0
+
+ def IsHole(self):
+ result = True
+ while (self.Parent is not None):
+ result = not result
+ self.Parent = self.Parent.Parent
+ return result
+
+ def GetNext(self):
+ if (self.ChildCount > 0):
+ return self.Childs[0]
+ else:
+ return self._GetNextSiblingUp()
+
+ def _AddChild(self, node):
+ self.Childs.append(node)
+ node.Index = self.ChildCount
+ node.Parent = self
+ self.ChildCount += 1
+
+ def _GetNextSiblingUp(self):
+ if (self.Parent is None):
+ return None
+ elif (self.Index == self.Parent.ChildCount - 1):
+ return self.Parent._GetNextSiblingUp()
+ else:
+ return self.Parent.Childs[self.Index +1]
+
+class PolyTree(PolyNode):
+ """Container for PolyNodes"""
+
+ def __init__(self):
+ PolyNode.__init__(self)
+ self._AllNodes = []
+
+ def Clear(self):
+ self._AllNodes = []
+ self.Childs = []
+ self.ChildCount = 0
+
+ def GetFirst(self):
+ if (self.ChildCount > 0):
+ return self.Childs[0]
+ else:
+ return None
+
+ def Total(self):
+ return len(self._AllNodes)
+
+def _AddPolyNodeToPolygons(polynode, polygons):
+ """Internal function for PolyTreeToPolygons()"""
+ if (len(polynode.Contour) > 0):
+ polygons.append(polynode.Contour)
+ for i in range(polynode.ChildCount):
+ _AddPolyNodeToPolygons(polynode.Childs[i], polygons)
+
+def PolyTreeToPolygons(polyTree):
+ result = []
+ _AddPolyNodeToPolygons(polyTree, result)
+ return result
+
+#===============================================================================
+# Edge class
+#===============================================================================
+
+class Edge(object):
+
+ def __init__(self):
+ self.Bot = Point(0,0)
+ self.Curr = Point(0,0)
+ self.Top = Point(0,0)
+ self.Delta = Point(0,0)
+ self.dx = float(0.0)
+ self.polyType = PolyType.Subject
+ self.side = EdgeSide.Left
+ self.windDelta, self.windCnt, self.windCnt2 = 0, 0, 0
+ self.outIdx = -1
+ self.nextE, self.prevE, self.nextInLML = None, None, None
+ self.prevInAEL, self.nextInAEL, self.prevInSEL, self.nextInSEL = None, None, None, None
+
+ def __repr__(self):
+ return "(%i,%i . %i,%i {dx:%0.2f} %i {%x})" % \
+ (self.Bot.x, self.Bot.y, self.Top.x, self.Top.y, self.dx, self.outIdx, id(self))
+
+#===============================================================================
+# ClipperBase class (+ data structs & ancilliary functions)
+#===============================================================================
+
+def _PointsEqual(pt1, pt2):
+ return (pt1.x == pt2.x) and (pt1.y == pt2.y)
+
+def _SlopesEqual(pt1, pt2, pt3, pt4 = None):
+ if pt4 is None:
+ return (pt1.y-pt2.y)*(pt2.x-pt3.x) == (pt1.x-pt2.x)*(pt2.y-pt3.y)
+ else:
+ return (pt1.y-pt2.y)*(pt3.x-pt4.x) == (pt1.x-pt2.x)*(pt3.y-pt4.y)
+
+def _SlopesEqual2(e1, e2):
+ return e1.Delta.y * e2.Delta.x == e1.Delta.x * e2.Delta.y
+
+def _SetDx(e):
+ e.Delta = Point(e.Top.x - e.Bot.x, e.Top.y - e.Bot.y)
+ if e.Delta.y == 0: e.dx = horizontal
+ else: e.dx = float(e.Delta.x)/float(e.Delta.y)
+
+def _SwapSides(e1, e2):
+ side = e1.side
+ e1.side = e2.side
+ e2.side = side
+
+def _SwapPolyIndexes(e1, e2):
+ idx = e1.outIdx
+ e1.outIdx = e2.outIdx
+ e2.outIdx = idx
+
+def _InitEdge(e, eNext, ePrev, pt, polyType):
+ e.nextE = eNext
+ e.prevE = ePrev
+ e.Curr = pt
+ if e.Curr.y >= e.nextE.Curr.y:
+ e.Bot = e.Curr
+ e.Top = e.nextE.Curr
+ e.windDelta = 1
+ else:
+ e.Top = e.Curr
+ e.Bot = e.nextE.Curr
+ e.windDelta = -1
+ _SetDx(e)
+ e.outIdx = -1
+ e.PolyType = polyType
+
+def _SwapX(e):
+ e.Curr = Point(e.Top.x, e.Curr.y)
+ e.Top = Point(e.Bot.x, e.Top.y)
+ e.Bot = Point(e.Curr.x, e.Bot.y)
+
+class ClipperBase(object):
+
+ def __init__(self):
+ self._EdgeList = [] # 2D array
+ self._LocalMinList = None # single-linked list of LocalMinima
+ self._CurrentLocMin = None
+
+ def _InsertLocalMinima(self, lm):
+ if self._LocalMinList is None:
+ self._LocalMinList = lm
+ elif lm.y >= self._LocalMinList.y:
+ lm.nextLm = self._LocalMinList
+ self._LocalMinList = lm
+ else:
+ tmp = self._LocalMinList
+ while tmp.nextLm is not None and lm.y < tmp.nextLm.y:
+ tmp = tmp.nextLm
+ lm.nextLm = tmp.nextLm
+ tmp.nextLm = lm
+
+ def _AddBoundsToLML(self, e):
+ e.nextInLML = None
+ e = e.nextE
+ while True:
+ if e.dx == horizontal:
+ if (e.nextE.Top.y < e.Top.y) and (e.nextE.Bot.x > e.prevE.Bot.x): break
+ if (e.Top.x != e.prevE.Bot.x): _SwapX(e)
+ e.nextInLML = e.prevE
+ elif e.Bot.y == e.prevE.Bot.y: break
+ else: e.nextInLML = e.prevE
+ e = e.nextE
+
+ if e.dx == horizontal:
+ if (e.Bot.x != e.prevE.Bot.x): _SwapX(e)
+ lm = LocalMinima(e.prevE.Bot.y, e.prevE, e)
+ elif (e.dx < e.prevE.dx):
+ lm = LocalMinima(e.prevE.Bot.y, e.prevE, e)
+ else:
+ lm = LocalMinima(e.prevE.Bot.y, e, e.prevE)
+ lm.leftBound.side = EdgeSide.Left
+ lm.rightBound.side = EdgeSide.Right
+ self._InsertLocalMinima(lm)
+ while True:
+ if e.nextE.Top.y == e.Top.y and e.nextE.dx != horizontal: break
+ e.nextInLML = e.nextE
+ e = e.nextE
+ if e.dx == horizontal and e.Bot.x != e.prevE.Top.x: _SwapX(e)
+ return e.nextE
+
+ def _Reset(self):
+ lm = self._LocalMinList
+ if lm is not None: self._CurrentLocMin = lm
+ while lm is not None:
+ e = lm.leftBound
+ while e is not None:
+ e.Curr = e.Bot
+ e.side = EdgeSide.Left
+ e.outIdx = -1
+ e = e.nextInLML
+ e = lm.rightBound
+ while e is not None:
+ e.Curr = e.Bot
+ e.side = EdgeSide.Right
+ e.outIdx = -1
+ e = e.nextInLML
+ lm = lm.nextLm
+
+ def AddPolygon(self, polygon, polyType):
+ ln = len(polygon)
+ if ln < 3: return False
+ pg = polygon[:]
+ j = 0
+ # remove duplicate points and co-linear points
+ for i in range(1, len(polygon)):
+ if _PointsEqual(pg[j], polygon[i]):
+ continue
+ elif (j > 0) and _SlopesEqual(pg[j-1], pg[j], polygon[i]):
+ if _PointsEqual(pg[j-1], polygon[i]): j -= 1
+ else: j += 1
+ pg[j] = polygon[i]
+ if (j < 2): return False
+ # remove duplicate points and co-linear edges at the loop around
+ # of the start and end coordinates ...
+ ln = j +1
+ while (ln > 2):
+ if _PointsEqual(pg[j], pg[0]): j -= 1
+ elif _PointsEqual(pg[0], pg[1]) or _SlopesEqual(pg[j], pg[0], pg[1]):
+ pg[0] = pg[j]
+ j -= 1
+ elif _SlopesEqual(pg[j-1], pg[j], pg[0]): j -= 1
+ elif _SlopesEqual(pg[0], pg[1], pg[2]):
+ for i in range(2, j +1): pg[i-1] = pg[i]
+ j -= 1
+ else: break
+ ln -= 1
+ if ln < 3: return False
+ edges = []
+ for i in range(ln):
+ edges.append(Edge())
+ edges[0].Curr = pg[0]
+ _InitEdge(edges[ln-1], edges[0], edges[ln-2], pg[ln-1], polyType)
+ for i in range(ln-2, 0, -1):
+ _InitEdge(edges[i], edges[i+1], edges[i-1], pg[i], polyType)
+ _InitEdge(edges[0], edges[1], edges[ln-1], pg[0], polyType)
+ e = edges[0]
+ eHighest = e
+ while True:
+ e.Curr = e.Bot
+ if e.Top.y < eHighest.Top.y: eHighest = e
+ e = e.nextE
+ if e == edges[0]: break
+ # make sure eHighest is positioned so the following loop works safely ...
+ if eHighest.windDelta > 0: eHighest = eHighest.nextE
+ if eHighest.dx == horizontal: eHighest = eHighest.nextE
+ # finally insert each local minima ...
+ e = eHighest
+ while True:
+ e = self._AddBoundsToLML(e)
+ if e == eHighest: break
+ self._EdgeList.append(edges)
+
+ def AddPolygons(self, polygons, polyType):
+ result = False
+ for p in polygons:
+ if self.AddPolygon(p, polyType): result = True
+ return result
+
+ def Clear(self):
+ self._EdgeList = []
+ self._LocalMinList = None
+ self._CurrentLocMin = None
+
+ def _PopLocalMinima(self):
+ if self._CurrentLocMin is not None:
+ self._CurrentLocMin = self._CurrentLocMin.nextLm
+
+#===============================================================================
+# Clipper class (+ data structs & ancilliary functions)
+#===============================================================================
+def _IntersectPoint(edge1, edge2):
+ if _SlopesEqual2(edge1, edge2):
+ if (edge2.Bot.y > edge1.Bot.y): y = edge2.Bot.y
+ else: y = edge1.Bot.y
+ return Point(0, y), False
+ if edge1.dx == 0:
+ x = edge1.Bot.x
+ if edge2.dx == horizontal:
+ y = edge2.Bot.y
+ else:
+ b2 = edge2.Bot.y - float(edge2.Bot.x)/edge2.dx
+ y = round(float(x)/edge2.dx + b2)
+ elif edge2.dx == 0:
+ x = edge2.Bot.x
+ if edge1.dx == horizontal:
+ y = edge1.Bot.y
+ else:
+ b1 = edge1.Bot.y - float(edge1.Bot.x)/edge1.dx
+ y = round(float(x)/edge1.dx + b1)
+ else:
+ b1 = float(edge1.Bot.x) - float(edge1.Bot.y) * edge1.dx
+ b2 = float(edge2.Bot.x) - float(edge2.Bot.y) * edge2.dx
+ m = (b2-b1)/(edge1.dx - edge2.dx)
+ y = round(m)
+ if math.fabs(edge1.dx) < math.fabs(edge2.dx):
+ x = round(edge1.dx * m + b1)
+ else:
+ x = round(edge2.dx * m + b2)
+ if (y < edge1.Top.y) or (y < edge2.Top.y):
+ if (edge1.Top.y > edge2.Top.y):
+ return edge1.Top, _TopX(edge2, edge1.Top.y) < edge1.Top.x
+ else:
+ return edge2.Top, _TopX(edge1, edge2.Top.y) > edge2.Top.x
+ else:
+ return Point(x,y), True
+
+def _TopX(e, currentY):
+ if currentY == e.Top.y: return e.Top.x
+ elif e.Top.x == e.Bot.x: return e.Bot.x
+ else: return e.Bot.x + round(e.dx * float(currentY - e.Bot.y))
+
+def _E2InsertsBeforeE1(e1,e2):
+ if (e2.Curr.x == e1.Curr.x):
+ if (e2.Top.y > e1.Top.y):
+ return e2.Top.x < _TopX(e1, e2.Top.y)
+ return e1.Top.x > _TopX(e2, e1.Top.y)
+ else:
+ return e2.Curr.x < e1.Curr.x
+
+def _IsMinima(e):
+ return e is not None and e.prevE.nextInLML != e and e.nextE.nextInLML != e
+
+def _IsMaxima(e, y):
+ return e is not None and e.Top.y == y and e.nextInLML is None
+
+def _IsIntermediate(e, y):
+ return e.Top.y == y and e.nextInLML is not None
+
+def _GetMaximaPair(e):
+ if not _IsMaxima(e.nextE, e.Top.y) or e.nextE.Top.x != e.Top.x:
+ return e.prevE
+ else:
+ return e.nextE
+
+def _GetnextInAEL(e, direction):
+ if direction == Direction.LeftToRight: return e.nextInAEL
+ else: return e.prevInAEL
+
+def _ProtectLeft(val):
+ if val: return Protects.Both
+ else: return Protects.Right
+
+def _ProtectRight(val):
+ if val: return Protects.Both
+ else: return Protects.Left
+
+def _GetDx(pt1, pt2):
+ if (pt1.y == pt2.y): return horizontal
+ else: return float(pt2.x - pt1.x)/float(pt2.y - pt1.y)
+
+def _Param1RightOfParam2(outRec1, outRec2):
+ while outRec1 is not None:
+ outRec1 = outRec1.FirstLeft
+ if outRec1 == outRec2: return True
+ return False
+
+def _FirstParamIsbottomPt(btmPt1, btmPt2):
+ p = btmPt1.prevOp
+ while _PointsEqual(p.pt, btmPt1.pt) and (p != btmPt1): p = p.prevOp
+ dx1p = abs(_GetDx(btmPt1.pt, p.pt))
+ p = btmPt1.nextOp
+ while _PointsEqual(p.pt, btmPt1.pt) and (p != btmPt1): p = p.nextOp
+ dx1n = abs(_GetDx(btmPt1.pt, p.pt))
+
+ p = btmPt2.prevOp
+ while _PointsEqual(p.pt, btmPt2.pt) and (p != btmPt2): p = p.prevOp
+ dx2p = abs(_GetDx(btmPt2.pt, p.pt))
+ p = btmPt2.nextOp
+ while _PointsEqual(p.pt, btmPt2.pt) and (p != btmPt2): p = p.nextOp
+ dx2n = abs(_GetDx(btmPt2.pt, p.pt))
+ return (dx1p >= dx2p and dx1p >= dx2n) or (dx1n >= dx2p and dx1n >= dx2n)
+
+def _GetBottomPt(pp):
+ dups = None
+ p = pp.nextOp
+ while p != pp:
+ if p.pt.y > pp.pt.y:
+ pp = p
+ dups = None
+ elif p.pt.y == pp.pt.y and p.pt.x <= pp.pt.x:
+ if p.pt.x < pp.pt.x:
+ dups = None
+ pp = p
+ else:
+ if p.nextOp != pp and p.prevOp != pp: dups = p
+ p = p.nextOp
+ if dups is not None:
+ while dups != p:
+ if not _FirstParamIsbottomPt(p, dups): pp = dups
+ dups = dups.nextOp
+ while not _PointsEqual(dups.pt, pp.pt): dups = dups.nextOp
+ return pp
+
+def _GetLowermostRec(outRec1, outRec2):
+ if (outRec1.bottomPt is None):
+ outPt1 = _GetBottomPt(outRec1.pts)
+ else: outPt1 = outRec1.bottomPt
+ if (outRec2.bottomPt is None):
+ outPt2 = _GetBottomPt(outRec2.pts)
+ else: outPt2 = outRec2.bottomPt
+ if (outPt1.pt.y > outPt2.pt.y): return outRec1
+ elif (outPt1.pt.y < outPt2.pt.y): return outRec2
+ elif (outPt1.pt.x < outPt2.pt.x): return outRec1
+ elif (outPt1.pt.x > outPt2.pt.x): return outRec2
+ elif (outPt1.nextOp == outPt1): return outRec2
+ elif (outPt2.nextOp == outPt2): return outRec1
+ elif _FirstParamIsbottomPt(outPt1, outPt2): return outRec1
+ else: return outRec2
+
+def _SetHoleState(e, outRec, polyOutList):
+ isHole = False
+ e2 = e.prevInAEL
+ while e2 is not None:
+ if e2.outIdx >= 0:
+ isHole = not isHole
+ if outRec.FirstLeft is None:
+ outRec.FirstLeft = polyOutList[e2.outIdx]
+ e2 = e2.prevInAEL
+ outRec.isHole = isHole
+
+def _PointCount(pts):
+ if pts is None: return 0
+ p = pts
+ result = 0
+ while True:
+ result += 1
+ p = p.nextOp
+ if p == pts: break
+ return result
+
+def _PointIsVertex(pt, outPts):
+ op = outPts
+ while True:
+ if _PointsEqual(op.pt, pt): return True
+ op = op.nextOp
+ if op == outPts: break
+ return False
+
+def _ReversePolyPtLinks(pp):
+ if pp is None: return
+ pp1 = pp
+ while True:
+ pp2 = pp1.nextOp
+ pp1.nextOp = pp1.prevOp
+ pp1.prevOp = pp2;
+ pp1 = pp2
+ if pp1 == pp: break
+
+def _FixupOutPolygon(outRec):
+ lastOK = None
+ outRec.bottomPt = None
+ pp = outRec.pts
+ while True:
+ if pp.prevOp == pp or pp.nextOp == pp.prevOp:
+ outRec.pts = None
+ return
+ if _PointsEqual(pp.pt, pp.nextOp.pt) or \
+ _SlopesEqual(pp.prevOp.pt, pp.pt, pp.nextOp.pt):
+ lastOK = None
+ pp.prevOp.nextOp = pp.nextOp
+ pp.nextOp.prevOp = pp.prevOp
+ pp = pp.prevOp
+ elif pp == lastOK: break
+ else:
+ if lastOK is None: lastOK = pp
+ pp = pp.nextOp
+ outRec.pts = pp
+
+def _FixHoleLinkage(outRec):
+ if outRec.FirstLeft is None or \
+ (outRec.isHole != outRec.FirstLeft.isHole and \
+ outRec.FirstLeft.pts is not None): return
+ orfl = outRec.FirstLeft
+ while orfl is not None and \
+ (orfl.isHole == outRec.isHole or orfl.pts is None):
+ orfl = orfl.FirstLeft
+ outRec.FirstLeft = orfl
+
+def _GetOverlapSegment(pt1a, pt1b, pt2a, pt2b):
+ # precondition: segments are co-linear
+ if abs(pt1a.x - pt1b.x) > abs(pt1a.y - pt1b.y):
+ if pt1a.x > pt1b.x: tmp = pt1a; pt1a = pt1b; pt1b = tmp
+ if pt2a.x > pt2b.x: tmp = pt2a; pt2a = pt2b; pt2b = tmp
+ if (pt1a.x > pt2a.x): pt1 = pt1a
+ else: pt1 = pt2a
+ if (pt1b.x < pt2b.x): pt2 = pt1b
+ else: pt2 = pt2b
+ return pt1, pt2, pt1.x < pt2.x
+ else:
+ if pt1a.y < pt1b.y: tmp = pt1a; pt1a = pt1b; pt1b = tmp
+ if pt2a.y < pt2b.y: tmp = pt2a; pt2a = pt2b; pt2b = tmp
+ if (pt1a.y < pt2a.y): pt1 = pt1a
+ else: pt1 = pt2a
+ if (pt1b.y > pt2b.y): pt2 = pt1b
+ else: pt2 = pt2b
+ return pt1, pt2, pt1.y > pt2.y
+
+
+def _FindSegment(outPt, pt1, pt2):
+ if outPt is None: return outPt, pt1, pt2, False
+ pt1a = pt1; pt2a = pt2
+ outPt2 = outPt
+ while True:
+ if _SlopesEqual(pt1a, pt2a, outPt.pt, outPt.prevOp.pt) and _SlopesEqual(pt1a, pt2a, outPt.pt):
+ pt1, pt2, overlap = _GetOverlapSegment(pt1a, pt2a, outPt.pt, outPt.prevOp.pt)
+ if overlap: return outPt, pt1, pt2, True
+ outPt = outPt.nextOp
+ if outPt == outPt2: return outPt, pt1, pt2, False
+
+def _Pt3IsBetweenPt1AndPt2(pt1, pt2, pt3):
+ if _PointsEqual(pt1, pt3) or _PointsEqual(pt2, pt3): return True
+ elif pt1.x != pt2.x: return (pt1.x < pt3.x) == (pt3.x < pt2.x)
+ else: return (pt1.y < pt3.y) == (pt3.y < pt2.y)
+
+def _InsertPolyPtBetween(outPt1, outPt2, pt):
+ if outPt1 == outPt2: raise Exception("JoinError")
+ result = OutPt(outPt1.idx, pt)
+ if outPt2 == outPt1.nextOp:
+ outPt1.nextOp = result
+ outPt2.prevOp = result
+ result.nextOp = outPt2
+ result.prevOp = outPt1
+ else:
+ outPt2.nextOp = result
+ outPt1.prevOp = result
+ result.nextOp = outPt1
+ result.prevOp = outPt2
+ return result
+
+def _PointOnLineSegment(pt, linePt1, linePt2):
+ return ((pt.x == linePt1.x) and (pt.y == linePt1.y)) or \
+ ((pt.x == linePt2.x) and (pt.y == linePt2.y)) or \
+ (((pt.x > linePt1.x) == (pt.x < linePt2.x)) and \
+ ((pt.y > linePt1.y) == (pt.y < linePt2.y)) and \
+ ((pt.x - linePt1.x) * (linePt2.y - linePt1.y) == \
+ (linePt2.x - linePt1.x) * (pt.y - linePt1.y)))
+
+def _PointOnPolygon(pt, pp):
+ pp2 = pp;
+ while True:
+ if (_PointOnLineSegment(pt, pp2.pt, pp2.nextOp.pt)):
+ return True
+ pp2 = pp2.nextOp
+ if (pp2 == pp): return False
+
+def _PointInPolygon(pt, outPt):
+ result = False
+ outPt2 = outPt
+ while True:
+ if ((((outPt2.pt.y <= pt.y) and (pt.y < outPt2.prevOp.pt.y)) or \
+ ((outPt2.prevOp.pt.y <= pt.y) and (pt.y < outPt2.pt.y))) and \
+ (pt.x < (outPt2.prevOp.pt.x - outPt2.pt.x) * (pt.y - outPt2.pt.y) / \
+ (outPt2.prevOp.pt.y - outPt2.pt.y) + outPt2.pt.x)): result = not result
+ outPt2 = outPt2.nextOp
+ if outPt2 == outPt: break
+
+def _Poly2ContainsPoly1(outPt1, outPt2):
+ pt = outPt1
+ if (_PointOnPolygon(pt.pt, outPt2)):
+ pt = pt.nextOp
+ while (pt != outPt1 and _PointOnPolygon(pt.pt, outPt2)):
+ pt = pt.nextOp
+ if (pt == outPt1): return True
+ return _PointInPolygon(pt.pt, outPt2)
+
+def _EdgesAdjacent(inode):
+ return (inode.e1.nextInSEL == inode.e2) or \
+ (inode.e1.prevInSEL == inode.e2)
+
+def _UpdateOutPtIdxs(outrec):
+ op = outrec.pts
+ while True:
+ op.idx = outrec.idx
+ op = op.prevOp
+ if (op == outrec.pts): break
+
+class Clipper(ClipperBase):
+
+ def __init__(self):
+ ClipperBase.__init__(self)
+
+ self.ReverseSolution = False
+ self.ForceSimple = False
+
+ self._PolyOutList = []
+ self._ClipType = ClipType.Intersection
+ self._Scanbeam = None
+ self._ActiveEdges = None
+ self._SortedEdges = None
+ self._IntersectNodes = None
+ self._ClipFillType = PolyFillType.EvenOdd
+ self._SubjFillType = PolyFillType.EvenOdd
+ self._ExecuteLocked = False
+ self._UsingPolyTree = False
+ self._JoinList = None
+ self._HorzJoins = None
+
+ def _Reset(self):
+ ClipperBase._Reset(self)
+ self._Scanbeam = None
+ self._PolyOutList = []
+ lm = self._LocalMinList
+ while lm is not None:
+ self._InsertScanbeam(lm.y)
+ lm = lm.nextLm
+
+ def Clear(self):
+ self._PolyOutList = []
+ ClipperBase.Clear(self)
+
+ def _InsertScanbeam(self, y):
+ if self._Scanbeam is None:
+ self._Scanbeam = Scanbeam(y)
+ elif y > self._Scanbeam.y:
+ self._Scanbeam = Scanbeam(y, self._Scanbeam)
+ else:
+ sb = self._Scanbeam
+ while sb.nextSb is not None and y <= sb.nextSb.y:
+ sb = sb.nextSb
+ if y == sb.y: return
+ newSb = Scanbeam(y, sb.nextSb)
+ sb.nextSb = newSb
+
+ def _PopScanbeam(self):
+ result = self._Scanbeam.y
+ self._Scanbeam = self._Scanbeam.nextSb
+ return result
+
+ def _SetWindingCount(self, edge):
+ e = edge.prevInAEL
+ while e is not None and e.PolyType != edge.PolyType:
+ e = e.prevInAEL
+ if e is None:
+ edge.windCnt = edge.windDelta
+ edge.windCnt2 = 0
+ e = self._ActiveEdges
+ elif self._IsEvenOddFillType(edge):
+ edge.windCnt = 1
+ edge.windCnt2 = e.windCnt2
+ e = e.nextInAEL
+ else:
+ if e.windCnt * e.windDelta < 0:
+ if (abs(e.windCnt) > 1):
+ if (e.windDelta * edge.windDelta < 0): edge.windCnt = e.windCnt
+ else: edge.windCnt = e.windCnt + edge.windDelta
+ else:
+ edge.windCnt = e.windCnt + e.windDelta + edge.windDelta
+ elif (abs(e.windCnt) > 1) and (e.windDelta * edge.windDelta < 0):
+ edge.windCnt = e.windCnt
+ elif e.windCnt + edge.windDelta == 0:
+ edge.windCnt = e.windCnt
+ else:
+ edge.windCnt = e.windCnt + edge.windDelta
+ edge.windCnt2 = e.windCnt2
+ e = e.nextInAEL
+ # update windCnt2 ...
+ if self._IsEvenOddAltFillType(edge):
+ while (e != edge):
+ if edge.windCnt2 == 0: edge.windCnt2 = 1
+ else: edge.windCnt2 = 0
+ e = e.nextInAEL
+ else:
+ while (e != edge):
+ edge.windCnt2 += e.windDelta
+ e = e.nextInAEL
+
+ def _IsEvenOddFillType(self, edge):
+ if edge.PolyType == PolyType.Subject:
+ return self._SubjFillType == PolyFillType.EvenOdd
+ else:
+ return self._ClipFillType == PolyFillType.EvenOdd
+
+ def _IsEvenOddAltFillType(self, edge):
+ if edge.PolyType == PolyType.Subject:
+ return self._ClipFillType == PolyFillType.EvenOdd
+ else:
+ return self._SubjFillType == PolyFillType.EvenOdd
+
+ def _IsContributing(self, edge):
+ if edge.PolyType == PolyType.Subject:
+ pft = self._SubjFillType
+ pft2 = self._ClipFillType
+ else:
+ pft = self._ClipFillType
+ pft2 = self._SubjFillType
+ if pft == PolyFillType.EvenOdd or pft == PolyFillType.NonZero:
+ if abs(edge.windCnt) != 1: return False
+ elif pft == PolyFillType.Positive:
+ if edge.windCnt != 1: return False
+ elif pft == PolyFillType.Negative:
+ if edge.windCnt != -1: return False
+
+ if self._ClipType == ClipType.Intersection: ###########
+ if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero:
+ return edge.windCnt2 != 0
+ elif pft2 == PolyFillType.Positive:
+ return edge.windCnt2 > 0
+ else:
+ return edge.windCnt2 < 0 # Negative
+ elif self._ClipType == ClipType.Union: ###########
+ if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero:
+ return edge.windCnt2 == 0
+ elif pft2 == PolyFillType.Positive:
+ return edge.windCnt2 <= 0
+ else: return edge.windCnt2 >= 0 # Negative
+ elif self._ClipType == ClipType.Difference: ###########
+ if edge.PolyType == PolyType.Subject:
+ if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero:
+ return edge.windCnt2 == 0
+ elif edge.PolyType == PolyFillType.Positive:
+ return edge.windCnt2 <= 0
+ else:
+ return edge.windCnt2 >= 0
+ else:
+ if pft2 == PolyFillType.EvenOdd or pft2 == PolyFillType.NonZero:
+ return edge.windCnt2 != 0
+ elif pft2 == PolyFillType.Positive:
+ return edge.windCnt2 > 0
+ else:
+ return edge.windCnt2 < 0
+ else: # self._ClipType == ClipType.XOR: ###########
+ return True
+
+ def _AddEdgeToSEL(self, edge):
+ if self._SortedEdges is None:
+ self._SortedEdges = edge
+ edge.prevInSEL = None
+ edge.nextInSEL = None
+ else:
+ # add edge to front of list ...
+ edge.nextInSEL = self._SortedEdges
+ edge.prevInSEL = None
+ self._SortedEdges.prevInSEL = edge
+ self._SortedEdges = edge
+
+ def _CopyAELToSEL(self):
+ e = self._ActiveEdges
+ self._SortedEdges = e
+ while e is not None:
+ e.prevInSEL = e.prevInAEL
+ e.nextInSEL = e.nextInAEL
+ e = e.nextInAEL
+
+ def _InsertEdgeIntoAEL(self, edge):
+ edge.prevInAEL = None
+ edge.nextInAEL = None
+ if self._ActiveEdges is None:
+ self._ActiveEdges = edge
+ elif _E2InsertsBeforeE1(self._ActiveEdges, edge):
+ edge.nextInAEL = self._ActiveEdges
+ self._ActiveEdges.prevInAEL = edge
+ self._ActiveEdges = edge
+ else:
+ e = self._ActiveEdges
+ while e.nextInAEL is not None and \
+ not _E2InsertsBeforeE1(e.nextInAEL, edge):
+ e = e.nextInAEL
+ edge.nextInAEL = e.nextInAEL
+ if e.nextInAEL is not None: e.nextInAEL.prevInAEL = edge
+ edge.prevInAEL = e
+ e.nextInAEL = edge
+
+ def _InsertLocalMinimaIntoAEL(self, botY):
+ while self._CurrentLocMin is not None and \
+ self._CurrentLocMin.y == botY:
+ lb = self._CurrentLocMin.leftBound
+ rb = self._CurrentLocMin.rightBound
+ self._InsertEdgeIntoAEL(lb)
+ self._InsertScanbeam(lb.Top.y)
+ self._InsertEdgeIntoAEL(rb)
+ if self._IsEvenOddFillType(lb):
+ lb.windDelta = 1
+ rb.windDelta = 1
+ else:
+ rb.windDelta = -lb.windDelta
+ self._SetWindingCount(lb)
+ rb.windCnt = lb.windCnt
+ rb.windCnt2 = lb.windCnt2
+ if rb.dx == horizontal:
+ self._AddEdgeToSEL(rb)
+ self._InsertScanbeam(rb.nextInLML.Top.y)
+ else:
+ self._InsertScanbeam(rb.Top.y)
+ if self._IsContributing(lb):
+ self._AddLocalMinPoly(lb, rb, Point(lb.Curr.x, self._CurrentLocMin.y))
+
+ if rb.outIdx >= 0 and rb.dx == horizontal and self._HorzJoins is not None:
+ hj = self._HorzJoins
+ while True:
+ dummy1, dummy2, overlap = _GetOverlapSegment(hj.edge.Bot, hj.edge.Top, rb.Bot, rb.Top)
+ if overlap:
+ self._AddJoin(hj.edge, rb, hj.savedIdx)
+ hj = hj.nextHj
+ if hj == self._HorzJoins: break
+
+ if (lb.nextInAEL != rb):
+
+ if rb.outIdx >= 0 and rb.prevInAEL.outIdx >= 0 and _SlopesEqual2(rb.prevInAEL, rb):
+ self._AddJoin(rb, rb.prevInAEL)
+
+ e = lb.nextInAEL
+ pt = lb.Curr
+ while e != rb:
+ self._IntersectEdges(rb, e, pt)
+ e = e.nextInAEL
+ self._PopLocalMinima()
+
+ def _SwapPositionsInAEL(self, e1, e2):
+ if e1.nextInAEL == e2:
+ nextE = e2.nextInAEL
+ if nextE is not None: nextE.prevInAEL = e1
+ prevE = e1.prevInAEL
+ if prevE is not None: prevE.nextInAEL = e2
+ e2.prevInAEL = prevE
+ e2.nextInAEL = e1
+ e1.prevInAEL = e2
+ e1.nextInAEL = nextE
+ elif e2.nextInAEL == e1:
+ nextE = e1.nextInAEL
+ if nextE is not None: nextE.prevInAEL = e2
+ prevE = e2.prevInAEL
+ if prevE is not None: prevE.nextInAEL = e1
+ e1.prevInAEL = prevE
+ e1.nextInAEL = e2
+ e2.prevInAEL = e1
+ e2.nextInAEL = nextE
+ else:
+ nextE = e1.nextInAEL
+ prevE = e1.prevInAEL
+ e1.nextInAEL = e2.nextInAEL
+ if e1.nextInAEL is not None: e1.nextInAEL.prevInAEL = e1
+ e1.prevInAEL = e2.prevInAEL
+ if e1.prevInAEL is not None: e1.prevInAEL.nextInAEL = e1
+ e2.nextInAEL = nextE
+ if e2.nextInAEL is not None: e2.nextInAEL.prevInAEL = e2
+ e2.prevInAEL = prevE
+ if e2.prevInAEL is not None: e2.prevInAEL.nextInAEL = e2
+ if e1.prevInAEL is None: self._ActiveEdges = e1
+ elif e2.prevInAEL is None: self._ActiveEdges = e2
+
+ def _SwapPositionsInSEL(self, e1, e2):
+ if e1.nextInSEL == e2:
+ nextE = e2.nextInSEL
+ if nextE is not None: nextE.prevInSEL = e1
+ prevE = e1.prevInSEL
+ if prevE is not None: prevE.nextInSEL = e2
+ e2.prevInSEL = prevE
+ e2.nextInSEL = e1
+ e1.prevInSEL = e2
+ e1.nextInSEL = nextE
+ elif e2.nextInSEL == e1:
+ nextE = e1.nextInSEL
+ if nextE is not None: nextE.prevInSEL = e2
+ prevE = e2.prevInSEL
+ if prevE is not None: prevE.nextInSEL = e1
+ e1.prevInSEL = prevE
+ e1.nextInSEL = e2
+ e2.prevInSEL = e1
+ e2.nextInSEL = nextE
+ else:
+ nextE = e1.nextInSEL
+ prevE = e1.prevInSEL
+ e1.nextInSEL = e2.nextInSEL
+ e1.nextInSEL = e2.nextInSEL
+ if e1.nextInSEL is not None: e1.nextInSEL.prevInSEL = e1
+ e1.prevInSEL = e2.prevInSEL
+ if e1.prevInSEL is not None: e1.prevInSEL.nextInSEL = e1
+ e2.nextInSEL = nextE
+ if e2.nextInSEL is not None: e2.nextInSEL.prevInSEL = e2
+ e2.prevInSEL = prevE
+ if e2.prevInSEL is not None: e2.prevInSEL.nextInSEL = e2
+ if e1.prevInSEL is None: self._SortedEdges = e1
+ elif e2.prevInSEL is None: self._SortedEdges = e2
+
+ def _IsTopHorz(self, xPos):
+ e = self._SortedEdges
+ while e is not None:
+ if (xPos >= min(e.Curr.x,e.Top.x)) and (xPos <= max(e.Curr.x,e.Top.x)):
+ return False
+ e = e.nextInSEL
+ return True
+
+ def _ProcessHorizontal(self, horzEdge):
+ if horzEdge.Curr.x < horzEdge.Top.x:
+ horzLeft = horzEdge.Curr.x
+ horzRight = horzEdge.Top.x
+ direction = Direction.LeftToRight
+ else:
+ horzLeft = horzEdge.Top.x
+ horzRight = horzEdge.Curr.x
+ direction = Direction.RightToLeft
+ eMaxPair = None
+ if horzEdge.nextInLML is None:
+ eMaxPair = _GetMaximaPair(horzEdge)
+ e = _GetnextInAEL(horzEdge, direction)
+ while e is not None:
+ if (e.Curr.x == horzEdge.Top.x) and eMaxPair is None:
+ if _SlopesEqual2(e, horzEdge.nextInLML):
+ if horzEdge.outIdx >= 0 and e.outIdx >= 0:
+ self._AddJoin(horzEdge.nextInLML, e, horzEdge.outIdx)
+ break
+ elif e.dx < horzEdge.nextInLML.dx: break
+ eNext = _GetnextInAEL(e, direction)
+ if eMaxPair is not None or \
+ ((direction == Direction.LeftToRight) and (e.Curr.x < horzRight)) or \
+ ((direction == Direction.RightToLeft) and (e.Curr.x > horzLeft)):
+ if e == eMaxPair:
+ if direction == Direction.LeftToRight:
+ self._IntersectEdges(horzEdge, e, Point(e.Curr.x, horzEdge.Curr.y))
+ else:
+ self._IntersectEdges(e, horzEdge, Point(e.Curr.x, horzEdge.Curr.y))
+ return
+ elif e.dx == horizontal and not _IsMinima(e) and e.Curr.x <= e.Top.x:
+ if direction == Direction.LeftToRight:
+ self._IntersectEdges(horzEdge, e, Point(e.Curr.x, horzEdge.Curr.y),
+ _ProtectRight(not self._IsTopHorz(e.Curr.x)))
+ else:
+ self._IntersectEdges(e, horzEdge, Point(e.Curr.x, horzEdge.Curr.y),
+ _ProtectLeft(not self._IsTopHorz(e.Curr.x)))
+ elif (direction == Direction.LeftToRight):
+ self._IntersectEdges(horzEdge, e, Point(e.Curr.x, horzEdge.Curr.y),
+ _ProtectRight(not self._IsTopHorz(e.Curr.x)))
+ else:
+ self._IntersectEdges(e, horzEdge, Point(e.Curr.x, horzEdge.Curr.y),
+ _ProtectLeft(not self._IsTopHorz(e.Curr.x)))
+ self._SwapPositionsInAEL(horzEdge, e)
+ elif ((direction == Direction.LeftToRight and e.Curr.x >= horzRight) or \
+ (direction == Direction.RightToLeft and e.Curr.x <= horzLeft)): break
+ e = eNext
+ if horzEdge.nextInLML is not None:
+ if horzEdge.outIdx >= 0:
+ self._AddOutPt(horzEdge, horzEdge.Top)
+ self._UpdateEdgeIntoAEL(horzEdge)
+ else:
+ if horzEdge.outIdx >= 0:
+ self._IntersectEdges(horzEdge, eMaxPair, \
+ Point(horzEdge.Top.x, horzEdge.Curr.y), Protects.Both)
+ if eMaxPair.outIdx >= 0: raise Exception("Clipper: Horizontal Error")
+ self._DeleteFromAEL(eMaxPair)
+ self._DeleteFromAEL(horzEdge)
+
+ def _ProcessHorizontals(self):
+ while self._SortedEdges is not None:
+ e = self._SortedEdges
+ self._DeleteFromSEL(e)
+ self._ProcessHorizontal(e)
+
+ def _AddJoin(self, e1, e2, e1OutIdx = -1, e2OutIdx = -1):
+ jr = JoinRec()
+ if e1OutIdx >= 0: jr.poly1Idx = e1OutIdx
+ else: jr.poly1Idx = e1.outIdx
+ jr.pt1a = e1.Curr
+ jr.pt1b = e1.Top
+ if e2OutIdx >= 0: jr.poly2Idx = e2OutIdx
+ else: jr.poly2Idx = e2.outIdx
+ jr.pt2a = e2.Curr
+ jr.pt2b = e2.Top
+ if self._JoinList is None:
+ self._JoinList = []
+ self._JoinList.append(jr)
+
+ def _FixupJoinRecs(self, jr, outPt, startIdx):
+ for i in range(startIdx, len(self._JoinList)):
+ jr2 = self._JoinList[i]
+ if jr2.poly1Idx == jr.poly1Idx and _PointIsVertex(jr2.pt1a, outPt):
+ jr2.poly1Idx = jr.poly2Idx
+ if jr2.poly2Idx == jr.poly1Idx and _PointIsVertex(jr2.pt2a, outPt):
+ jr2.poly2Idx = jr.poly2Idx
+
+ def _AddHorzJoin(self, e, idx):
+ hj = HorzJoin(e, idx)
+ if self._HorzJoins == None:
+ self._HorzJoins = hj
+ hj.nextHj = hj
+ hj.prevHj = hj
+ else:
+ hj.nextHj = self._HorzJoins
+ hj.prevHj = self._HorzJoins.prevHj
+ self._HorzJoins.prevHj.nextHj = hj
+ self._HorzJoins.prevHj = hj
+
+ def _InsertIntersectNode(self, e1, e2, pt):
+ newNode = IntersectNode(e1, e2, pt)
+ if self._IntersectNodes is None:
+ self._IntersectNodes = newNode
+ elif newNode.pt.y > self._IntersectNodes.pt.y:
+ newNode.nextIn = self._IntersectNodes
+ self._IntersectNodes = newNode
+ else:
+ node = self._IntersectNodes
+ while node.nextIn is not None and \
+ newNode.pt.y < node.nextIn.pt.y:
+ node = node.nextIn
+ newNode.nextIn = node.nextIn
+ node.nextIn = newNode
+
+ def _ProcessIntersections(self, botY, topY):
+ try:
+ self._BuildIntersectList(botY, topY)
+ if self._IntersectNodes is None: return True
+ if self._IntersectNodes.nextIn is not None and \
+ not self._FixupIntersectionOrder(): return False
+ self._ProcessIntersectList()
+ return True
+ finally:
+ self._IntersectNodes = None
+ self._SortedEdges = None
+
+ def _BuildIntersectList(self, botY, topY):
+ e = self._ActiveEdges
+ if e is None: return
+ self._SortedEdges = e
+ while e is not None:
+ e.prevInSEL = e.prevInAEL
+ e.nextInSEL = e.nextInAEL
+ e.Curr = Point(_TopX(e, topY), e.Curr.y)
+ e = e.nextInAEL
+ while True:
+ isModified = False
+ e = self._SortedEdges
+ while e.nextInSEL is not None:
+ eNext = e.nextInSEL
+ if e.Curr.x <= eNext.Curr.x:
+ e = eNext
+ continue
+ pt, intersected = _IntersectPoint(e, eNext)
+ if not intersected and e.Curr.x > eNext.Curr.x +1:
+ raise Exception("Intersect Error")
+ if pt.y > botY:
+ pt = Point(_TopX(e, botY), botY)
+ self._InsertIntersectNode(e, eNext, pt)
+ self._SwapPositionsInSEL(e, eNext)
+ isModified = True
+ if e.prevInSEL is not None:
+ e.prevInSEL.nextInSEL = None
+ else:
+ break
+ if not isModified: break
+ self._SortedEdges = None
+ return
+
+ def _ProcessIntersectList(self):
+ while self._IntersectNodes is not None:
+ node = self._IntersectNodes
+ self._IntersectEdges(node.e1, node.e2, node.pt, Protects.Both)
+ self._SwapPositionsInAEL(node.e1, node.e2)
+ self._IntersectNodes = node.nextIn
+
+ def _DeleteFromAEL(self, e):
+ aelPrev = e.prevInAEL
+ aelNext = e.nextInAEL
+ if aelPrev is None and aelNext is None and e != self._ActiveEdges:
+ return
+ if aelPrev is not None:
+ aelPrev.nextInAEL = aelNext
+ else:
+ self._ActiveEdges = aelNext
+ if aelNext is not None:
+ aelNext.prevInAEL = aelPrev
+ e.nextInAEL = None
+ e.prevInAEL = None
+
+ def _DeleteFromSEL(self, e):
+ SELPrev = e.prevInSEL
+ SELNext = e.nextInSEL
+ if SELPrev is None and SELNext is None and e != self._SortedEdges:
+ return
+ if SELPrev is not None:
+ SELPrev.nextInSEL = SELNext
+ else:
+ self._SortedEdges = SELNext
+ if SELNext is not None:
+ SELNext.prevInSEL = SELPrev
+ e.nextInSEL = None
+ e.prevInSEL = None
+
+ def _IntersectEdges(self, e1, e2, pt, protects = Protects.Neither):
+ e1stops = protects & Protects.Left == 0 and \
+ e1.nextInLML is None and \
+ e1.Top.x == pt.x and e1.Top.y == pt.y
+ e2stops = protects & Protects.Right == 0 and \
+ e2.nextInLML is None and \
+ e2.Top.x == pt.x and e2.Top.y == pt.y
+ e1Contributing = e1.outIdx >= 0
+ e2contributing = e2.outIdx >= 0
+
+ if e1.PolyType == e2.PolyType:
+ if self._IsEvenOddFillType(e1):
+ e1Wc = e1.windCnt
+ e1.windCnt = e2.windCnt
+ e2.windCnt = e1Wc
+ 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 not self._IsEvenOddFillType(e2): e1.windCnt2 += e2.windDelta
+ elif e1.windCnt2 == 0: e1.windCnt2 = 1
+ else: e1.windCnt2 = 0
+ if not self._IsEvenOddFillType(e1): e2.windCnt2 -= e1.windDelta
+ elif e2.windCnt2 == 0: e2.windCnt2 = 1
+ else: e2.windCnt2 = 0
+
+ if e1.PolyType == PolyType.Subject:
+ e1FillType = self._SubjFillType
+ e1FillType2 = self._ClipFillType
+ else:
+ e1FillType = self._ClipFillType
+ e1FillType2 = self._SubjFillType
+
+ if e2.PolyType == PolyType.Subject:
+ e2FillType = self._SubjFillType
+ e2FillType2 = self._ClipFillType
+ else:
+ e2FillType = self._ClipFillType
+ e2FillType2 = self._SubjFillType
+
+ if e1FillType == PolyFillType.Positive: e1Wc = e1.windCnt
+ elif e1FillType == PolyFillType.Negative: e1Wc = -e1.windCnt
+ else: e1Wc = abs(e1.windCnt)
+
+ if e2FillType == PolyFillType.Positive: e2Wc = e2.windCnt
+ elif e2FillType == PolyFillType.Negative: e2Wc = -e2.windCnt
+ else: e2Wc = abs(e2.windCnt)
+
+ if e1Contributing and e2contributing:
+ if e1stops or e2stops or \
+ (e1Wc != 0 and e1Wc != 1) or (e2Wc != 0 and e2Wc != 1) or \
+ (e1.PolyType != e2.PolyType and self._ClipType != ClipType.Xor):
+ self._AddLocalMaxPoly(e1, e2, pt)
+ else:
+ self._AddOutPt(e1, pt)
+ self._AddOutPt(e2, pt)
+ _SwapSides(e1, e2)
+ _SwapPolyIndexes(e1, e2)
+ elif e1Contributing:
+ if (e2Wc == 0 or e2Wc == 1):
+ self._AddOutPt(e1, pt)
+ _SwapSides(e1, e2)
+ _SwapPolyIndexes(e1, e2)
+ elif e2contributing:
+ if (e1Wc == 0 or e1Wc == 1):
+ self._AddOutPt(e2, pt)
+ _SwapSides(e1, e2)
+ _SwapPolyIndexes(e1, e2)
+
+ elif (e1Wc == 0 or e1Wc == 1) and (e2Wc == 0 or e2Wc == 1) and \
+ not e1stops and not e2stops:
+
+ e1FillType2 = e2FillType2 = PolyFillType.EvenOdd
+ if e1FillType2 == PolyFillType.Positive: e1Wc2 = e1.windCnt2
+ elif e1FillType2 == PolyFillType.Negative: e1Wc2 = -e1.windCnt2
+ else: e1Wc2 = abs(e1.windCnt2)
+ if e2FillType2 == PolyFillType.Positive: e2Wc2 = e2.windCnt2
+ elif e2FillType2 == PolyFillType.Negative: e2Wc2 = -e2.windCnt2
+ else: e2Wc2 = abs(e2.windCnt2)
+
+ if e1.PolyType != e2.PolyType:
+ self._AddLocalMinPoly(e1, e2, pt)
+ elif e1Wc == 1 and e2Wc == 1:
+ if self._ClipType == ClipType.Intersection:
+ if e1Wc2 > 0 and e2Wc2 > 0:
+ self._AddLocalMinPoly(e1, e2, pt)
+ elif self._ClipType == ClipType.Union:
+ if e1Wc2 <= 0 and e2Wc2 <= 0:
+ self._AddLocalMinPoly(e1, e2, pt)
+ elif self._ClipType == ClipType.Difference:
+ if (e1.PolyType == PolyType.Clip and e1Wc2 > 0 and e2Wc2 > 0) or \
+ (e1.PolyType == PolyType.Subject and e1Wc2 <= 0 and e2Wc2 <= 0):
+ self._AddLocalMinPoly(e1, e2, pt)
+ else:
+ self._AddLocalMinPoly(e1, e2, pt)
+ else:
+ _SwapSides(e1, e2, self._PolyOutList)
+
+ if e1stops != e2stops and \
+ ((e1stops and e1.outIdx >= 0) or (e2stops and e2.outIdx >= 0)):
+ _SwapSides(e1, e2, self._PolyOutList)
+ _SwapPolyIndexes(e1, e2)
+ if e1stops: self._DeleteFromAEL(e1)
+ if e2stops: self._DeleteFromAEL(e2)
+
+ def _DoMaxima(self, e, topY):
+ eMaxPair = _GetMaximaPair(e)
+ x = e.Top.x
+ eNext = e.nextInAEL
+ while eNext != eMaxPair:
+ if eNext is None: raise Exception("DoMaxima error")
+ self._IntersectEdges(e, eNext, Point(x, topY), Protects.Both)
+ self._SwapPositionsInAEL(e, eNext)
+ eNext = e.nextInAEL
+ if e.outIdx < 0 and eMaxPair.outIdx < 0:
+ self._DeleteFromAEL(e)
+ self._DeleteFromAEL(eMaxPair)
+ elif e.outIdx >= 0 and eMaxPair.outIdx >= 0:
+ self._IntersectEdges(e, eMaxPair, Point(x, topY))
+ else:
+ raise Exception("DoMaxima error")
+
+ def _UpdateEdgeIntoAEL(self, e):
+ if e.nextInLML is None:
+ raise Exception("UpdateEdgeIntoAEL error")
+ aelPrev = e.prevInAEL
+ aelNext = e.nextInAEL
+ e.nextInLML.outIdx = e.outIdx
+ if aelPrev is not None:
+ aelPrev.nextInAEL = e.nextInLML
+ else:
+ self._ActiveEdges = e.nextInLML
+ if aelNext is not None:
+ 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.prevInAEL = aelPrev
+ e.nextInAEL = aelNext
+ if e.dx != horizontal:
+ self._InsertScanbeam(e.Top.y)
+ return e
+
+ def _AddLocalMinPoly(self, e1, e2, pt):
+ if e2.dx == horizontal or e1.dx > e2.dx:
+ self._AddOutPt(e1, pt)
+ e2.outIdx = e1.outIdx
+ e1.side = EdgeSide.Left
+ e2.side = EdgeSide.Right
+ e = e1
+ if e.prevInAEL == e2: prevE = e2.prevInAEL
+ else: prevE = e1.prevInAEL
+ else:
+ self._AddOutPt(e2, pt)
+ e1.outIdx = e2.outIdx
+ e1.side = EdgeSide.Right
+ e2.side = EdgeSide.Left
+ e = e2
+ if e.prevInAEL == e1: prevE = e1.prevInAEL
+ else: prevE = e.prevInAEL
+
+ if prevE is not None and prevE.outIdx >= 0 and \
+ _TopX(prevE, pt.y) == _TopX(e, pt.y) and \
+ _SlopesEqual2(e, prevE):
+ self._AddJoin(e, prevE)
+ return
+
+ def _AddLocalMaxPoly(self, e1, e2, pt):
+ self._AddOutPt(e1, pt)
+ if e1.outIdx == e2.outIdx:
+ e1.outIdx = -1
+ e2.outIdx = -1
+ elif e1.outIdx < e2.outIdx:
+ self._AppendPolygon(e1, e2)
+ else:
+ self._AppendPolygon(e2, e1)
+
+ def _CreateOutRec(self):
+ outRec = OutRec(len(self._PolyOutList))
+ self._PolyOutList.append(outRec)
+ return outRec
+
+ def _AddOutPt(self, e, pt):
+ toFront = e.side == EdgeSide.Left
+ if e.outIdx < 0:
+ outRec = self._CreateOutRec();
+ e.outIdx = outRec.idx
+ op = OutPt(outRec.idx, pt)
+ op.nextOp = op
+ op.prevOp = op
+ outRec.pts = op
+ _SetHoleState(e, outRec, self._PolyOutList)
+ else:
+ outRec = self._PolyOutList[e.outIdx]
+ op = outRec.pts
+ if (toFront and _PointsEqual(pt, op.pt)) or \
+ (not toFront and _PointsEqual(pt, op.prevOp.pt)): return
+ op2 = OutPt(outRec.idx, pt)
+ op2.nextOp = op
+ op2.prevOp = op.prevOp
+ op.prevOp.nextOp = op2
+ op.prevOp = op2
+ if toFront: outRec.pts = op2
+
+ def _AppendPolygon(self, e1, e2):
+ outRec1 = self._PolyOutList[e1.outIdx]
+ outRec2 = self._PolyOutList[e2.outIdx]
+ holeStateRec = None
+ if _Param1RightOfParam2(outRec1, outRec2): holeStateRec = outRec2
+ elif _Param1RightOfParam2(outRec2, outRec1): holeStateRec = outRec1
+ else: holeStateRec = _GetLowermostRec(outRec1, outRec2)
+
+ p1_lft = outRec1.pts
+ p2_lft = outRec2.pts
+ p1_rt = p1_lft.prevOp
+ p2_rt = p2_lft.prevOp
+ newSide = EdgeSide.Left
+
+ if e1.side == EdgeSide.Left:
+ if e2.side == EdgeSide.Left:
+ # z y x a b c
+ _ReversePolyPtLinks(p2_lft)
+ p2_lft.nextOp = p1_lft
+ p1_lft.prevOp = p2_lft
+ p1_rt.nextOp = p2_rt
+ p2_rt.prevOp = p1_rt
+ outRec1.pts = p2_rt
+ else:
+ # x y z a b c
+ p2_rt.nextOp = p1_lft
+ p1_lft.prevOp = p2_rt
+ p2_lft.prevOp = p1_rt
+ p1_rt.nextOp = p2_lft
+ outRec1.pts = p2_lft
+ else:
+ newSide = EdgeSide.Right
+ if e2.side == EdgeSide.Right:
+ # a b c z y x
+ _ReversePolyPtLinks(p2_lft)
+ p1_rt.nextOp = p2_rt
+ p2_rt.prevOp = p1_rt
+ p2_lft.nextOp = p1_lft
+ p1_lft.prevOp = p2_lft
+ else:
+ # a b c x y z
+ p1_rt.nextOp = p2_lft
+ p2_lft.prevOp = p1_rt
+ p1_lft.prevOp = p2_rt
+ p2_rt.nextOp = p1_lft
+
+ outRec1.bottomPt = None
+ if holeStateRec == outRec2:
+ if outRec2.FirstLeft != outRec1:
+ outRec1.FirstLeft = outRec2.FirstLeft
+ outRec1.isHole = outRec2.isHole
+ outRec2.pts = None
+ outRec2.bottomPt = None
+ outRec2.FirstLeft = outRec1
+ OKIdx = outRec1.idx
+ ObsoleteIdx = outRec2.idx
+
+ e1.outIdx = -1
+ e2.outIdx = -1
+
+ e = self._ActiveEdges
+ while e is not None:
+ if e.outIdx == ObsoleteIdx:
+ e.outIdx = OKIdx
+ e.side = newSide
+ break
+ e = e.nextInAEL
+ outRec2.idx = outRec1.idx
+
+ def _FixupIntersectionOrder(self):
+ self._CopyAELToSEL()
+ inode = self._IntersectNodes
+ while inode is not None:
+ if (not _EdgesAdjacent(inode)):
+ nextNode = inode.nextIn
+ while (nextNode and not _EdgesAdjacent(nextNode)):
+ nextNode = nextNode.nextIn
+ if (nextNode is None): return False
+ e1 = inode.e1
+ e2 = inode.e2
+ p = inode.pt
+ inode.e1 = nextNode.e1
+ inode.e2 = nextNode.e2
+ inode.pt = nextNode.pt
+ nextNode.e1 = e1
+ nextNode.e2 = e2
+ nextNode.pt = p
+
+ self._SwapPositionsInSEL(inode.e1, inode.e2);
+ inode = inode.nextIn
+ return True
+
+ def _ProcessEdgesAtTopOfScanbeam(self, topY):
+ e = self._ActiveEdges
+ while e is not None:
+ if _IsMaxima(e, topY) and _GetMaximaPair(e).dx != horizontal:
+ ePrev = e.prevInAEL
+ self._DoMaxima(e, topY)
+ if ePrev is None: e = self._ActiveEdges
+ else: e = ePrev.nextInAEL
+ else:
+ intermediateVert = _IsIntermediate(e, topY)
+ if intermediateVert and e.nextInLML.dx == horizontal:
+ if e.outIdx >= 0:
+ self._AddOutPt(e, e.Top)
+ hj = self._HorzJoins
+ if hj is not None:
+ while True:
+ _1, _2, overlap = _GetOverlapSegment(
+ hj.edge.Bot, hj.edge.Top, e.nextInLML.Bot, e.nextInLML.Top)
+ if overlap: self._AddJoin(hj.edge, e.nextInLML, hj.savedIdx, e.outIdx)
+ hj = hj.nextHj
+ if hj == self._HorzJoins: break
+ self._AddHorzJoin(e.nextInLML, e.outIdx)
+
+ e = self._UpdateEdgeIntoAEL(e)
+ self._AddEdgeToSEL(e)
+ else:
+ e.Curr = Point(_TopX(e, topY), topY)
+ if (self.ForceSimple and e.prevInAEL is not None and
+ e.prevInAEL.Curr.x == e.Curr.x and
+ e.outIdx >= 0 and e.prevInAEL.outIdx >= 0):
+ if (intermediateVert):
+ self._AddOutPt(e.prevInAEL, Point(e.Curr.x, topY));
+ else:
+ self._AddOutPt(e, Point(e.Curr.x, topY))
+ e = e.nextInAEL
+
+ self._ProcessHorizontals()
+
+ e = self._ActiveEdges
+ while e is not None:
+ if _IsIntermediate(e, topY):
+ if (e.outIdx >= 0) :
+ self._AddOutPt(e, e.Top)
+ e = self._UpdateEdgeIntoAEL(e)
+
+ ePrev = e.prevInAEL
+ eNext = e.nextInAEL
+ if ePrev is not None and ePrev.Curr.x == e.Bot.x and \
+ (ePrev.Curr.y == e.Bot.y) and (e.outIdx >= 0) and \
+ (ePrev.outIdx >= 0) and (ePrev.Curr.y > ePrev.Top.y) and \
+ _SlopesEqual2(e, ePrev):
+ self._AddOutPt(ePrev, e.Bot)
+ self._AddJoin(e, ePrev)
+ elif eNext is not None and (eNext.Curr.x == e.Bot.x) and \
+ (eNext.Curr.y == e.Bot.y) and (e.outIdx >= 0) and \
+ (eNext.outIdx >= 0) and (eNext.Curr.y > eNext.Top.y) and \
+ _SlopesEqual2(e, eNext):
+ self._AddOutPt(eNext, e.Bot)
+ self._AddJoin(e, eNext)
+
+ e = e.nextInAEL
+
+ def _Area(self, pts):
+ # see http://www.mathopenref.com/coordpolygonarea2.html
+ result = 0.0
+ p = pts
+ while True:
+ result += (p.pt.x + p.prevOp.pt.x) * (p.prevOp.pt.y - p.pt.y)
+ p = p.nextOp
+ if p == pts: break
+ return result / 2
+
+ def _JoinPoints(self, jr):
+ p1, p2 = None, None
+ outRec1 = self._PolyOutList[jr.poly1Idx]
+ outRec2 = self._PolyOutList[jr.poly2Idx]
+ if outRec1 is None or outRec2 is None: return p1, p2, False
+ pp1a = outRec1.pts; pp2a = outRec2.pts
+ pt1 = jr.pt2a; pt2 = jr.pt2b
+ pt3 = jr.pt1a; pt4 = jr.pt1b
+ pp1a, pt1, pt2, result = _FindSegment(pp1a, pt1, pt2)
+ if not result: return p1, p2, False
+ if (outRec1 == outRec2):
+ pp2a = pp1a.nextOp
+ pp2a, pt3, pt4, result = _FindSegment(pp2a, pt3, pt4)
+ if not result or pp2a == pp1a: return p1, p2, False
+ else:
+ pp2a, pt3, pt4, result = _FindSegment(pp2a, pt3, pt4)
+ if not result: return p1, p2, False
+ pt1, pt2, result = _GetOverlapSegment(pt1, pt2, pt3, pt4)
+ if not result: return p1, p2, False
+
+ prevOp = pp1a.prevOp
+ if _PointsEqual(pp1a.pt, pt1): p1 = pp1a
+ elif _PointsEqual(prevOp.pt, pt1): p1 = prevOp
+ else: p1 = _InsertPolyPtBetween(pp1a, prevOp, pt1)
+
+ if _PointsEqual(pp1a.pt, pt2): p2 = pp1a
+ elif _PointsEqual(prevOp.pt, pt2): p2 = prevOp
+ elif (p1 == pp1a) or (p1 == prevOp):
+ p2 = _InsertPolyPtBetween(pp1a, prevOp, pt2)
+ elif _Pt3IsBetweenPt1AndPt2(pp1a.pt, p1.pt, pt2):
+ p2 = _InsertPolyPtBetween(pp1a, p1, pt2)
+ else: p2 = _InsertPolyPtBetween(p1, prevOp, pt2)
+
+ prevOp = pp2a.prevOp
+ if _PointsEqual(pp2a.pt, pt1): p3 = pp2a
+ elif _PointsEqual(prevOp.pt, pt1): p3 = prevOp
+ else: p3 = _InsertPolyPtBetween(pp2a, prevOp, pt1)
+ if _PointsEqual(pp2a.pt, pt2): p4 = pp2a
+ elif _PointsEqual(prevOp.pt, pt2): p4 = prevOp
+ elif (p3 == pp2a) or (p3 == prevOp):
+ p4 = _InsertPolyPtBetween(pp2a, prevOp, pt2)
+ elif _Pt3IsBetweenPt1AndPt2(pp2a.pt, p3.pt, pt2):
+ p4 = _InsertPolyPtBetween(pp2a, p3, pt2)
+ else: p4 = _InsertPolyPtBetween(p3, prevOp, pt2)
+
+ if p1.nextOp == p2 and p3.prevOp == p4:
+ p1.nextOp = p3
+ p3.prevOp = p1
+ p2.prevOp = p4
+ p4.nextOp = p2
+ return p1, p2, True
+ elif p1.prevOp == p2 and p3.nextOp == p4:
+ p1.prevOp = p3
+ p3.nextOp = p1
+ p2.nextOp = p4
+ p4.prevOp = p2
+ return p1, p2, True
+ return p1, p2, False
+
+ def _FixupFirstLefts1(self, oldOutRec, newOutRec):
+ for outRec in self._PolyOutList:
+ if outRec.pts is not None and outRec.FirstLeft == oldOutRec:
+ if _Poly2ContainsPoly1(outRec.pts, newOutRec.pts):
+ outRec.FirstLeft = newOutRec
+
+ def _FixupFirstLefts2(self, oldOutRec, newOutRec):
+ for outRec in self._PolyOutList:
+ if outRec.FirstLeft == oldOutRec: outRec.FirstLeft = newOutRec
+
+ def _GetOutRec(self, idx):
+ outrec = self._PolyOutList[idx]
+ while (outrec != self._PolyOutList[outrec.idx]):
+ outrec = self._PolyOutList[outrec.idx]
+ return outrec
+
+ def _JoinCommonEdges(self):
+ for i in range(len(self._JoinList)):
+ jr = self._JoinList[i]
+ outRec1 = self._GetOutRec(jr.poly1Idx)
+ outRec2 = self._GetOutRec(jr.poly2Idx)
+ if outRec1.pts is None or outRec2.pts is None: continue
+
+ if outRec1 == outRec2: holeStateRec = outRec1
+ elif _Param1RightOfParam2(outRec1, outRec2): holeStateRec = outRec2
+ elif _Param1RightOfParam2(outRec2, outRec1): holeStateRec = outRec1
+ else: holeStateRec = _GetLowermostRec(outRec1, outRec2)
+
+ p1, p2, result = self._JoinPoints(jr)
+ if not result: continue
+
+ if outRec1 == outRec2:
+ outRec1.pts = p1
+ outRec1.bottomPt = None
+ outRec2 = self._CreateOutRec()
+ outRec2.pts = p2
+ jr.poly2Idx = outRec2.idx
+
+ if _Poly2ContainsPoly1(outRec2.pts, outRec1.pts):
+ outRec2.isHole = not outRec1.isHole
+ outRec2.FirstLeft = outRec1
+
+ self._FixupJoinRecs(jr, p2, i + 1)
+
+ if self._UsingPolyTree: self._FixupFirstLefts2(outRec2, outRec1)
+
+ _FixupOutPolygon(outRec1)
+ _FixupOutPolygon(outRec2)
+
+ if (outRec2.isHole ^ self.ReverseSolution) == self._Area(outRec2) > 0.0:
+ _ReversePolyPtLinks(outRec2.pts)
+
+ elif _Poly2ContainsPoly1(outRec1.pts, outRec2.pts):
+ outRec2.isHole = outRec1.isHole
+ outRec1.isHole = not outRec2.isHole
+ outRec2.FirstLeft = outRec1.FirstLeft
+ outRec1.FirstLeft = outRec2
+
+ self._FixupJoinRecs(jr, p2, i + 1)
+
+ if self._UsingPolyTree: self._FixupFirstLefts2(outRec1, outRec2)
+
+ _FixupOutPolygon(outRec1)
+ _FixupOutPolygon(outRec2)
+
+ if (outRec1.isHole ^ self.ReverseSolution) == self._Area(outRec1) > 0.0:
+ _ReversePolyPtLinks(outRec1.pts)
+ else:
+ outRec2.isHole = outRec1.isHole
+ outRec2.FirstLeft = outRec1.FirstLeft
+
+ self._FixupJoinRecs(jr, p2, i + 1)
+ if self._UsingPolyTree: self._FixupFirstLefts1(outRec1, outRec2)
+
+ _FixupOutPolygon(outRec1)
+ _FixupOutPolygon(outRec2)
+ else:
+ _FixupOutPolygon(outRec1)
+ outRec2.pts = None
+ outRec2.bottomPt = None
+ outRec2.idx = outRec1.idx
+
+ outRec1.isHole = holeStateRec.isHole
+ if holeStateRec == outRec2:
+ outRec1.FirstLeft = outRec2.FirstLeft
+ outRec2.FirstLeft = outRec1
+
+ if self._UsingPolyTree: self._FixupFirstLefts2(outRec2, outRec1)
+ return
+
+ def _DoSimplePolygons(self):
+ i = 0;
+ while i < len(self._PolyOutList):
+ outrec = self._PolyOutList[i]
+ i +=1
+ op = outrec.pts
+ if (op is None): continue
+ while True:
+ op2 = op.nextOp
+ while (op2 != outrec.pts):
+ if (_PointsEqual(op.pt, op2.pt) and op2.nextOp != op and op2.prevOp != op):
+ #split the polygon into two ...
+ op3 = op.prevOp
+ op4 = op2.prevOp
+ op.prevOp = op4
+ op4.nextOp = op
+ op2.prevOp = op3
+ op3.nextOp = op2
+
+ outrec.pts = op
+ outrec2 = self._CreateOutRec();
+ outrec2.pts = op2;
+ _UpdateOutPtIdxs(outrec2)
+ if (_Poly2ContainsPoly1(outrec2.pts, outrec.pts)):
+ #OutRec2 is contained by OutRec1 ...
+ outrec2.isHole = not outrec.isHole
+ outrec2.FirstLeft = outrec
+
+ elif (_Poly2ContainsPoly1(outrec.pts, outrec2.pts)):
+ #OutRec1 is contained by OutRec2 ...
+ outrec2.isHole = outrec.isHole
+ outrec.isHole = not 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.nextOp
+ op = op.nextOp
+ if op == outrec.pts: break
+ return
+
+ def _ExecuteInternal(self):
+# try:
+ try:
+ self._Reset()
+ if self._Scanbeam is None: return True
+ botY = self._PopScanbeam()
+ while True:
+ self._InsertLocalMinimaIntoAEL(botY)
+ self._HorzJoins = None
+ self._ProcessHorizontals()
+ topY = self._PopScanbeam()
+ if not self._ProcessIntersections(botY, topY): return False
+ self._ProcessEdgesAtTopOfScanbeam(topY)
+ botY = topY
+ if self._Scanbeam is None and self._CurrentLocMin is None: break
+
+ for outRec in self._PolyOutList:
+ if outRec.pts is None: continue
+ _FixupOutPolygon(outRec)
+ if outRec.pts is None: continue
+ if ((outRec.isHole ^ self.ReverseSolution) == (self._Area(outRec.pts) > 0.0)):
+ _ReversePolyPtLinks(outRec.pts)
+
+ if self._JoinList is not None: self._JoinCommonEdges()
+ if self.ForceSimple: self._DoSimplePolygons()
+
+ return True
+ finally:
+ self._JoinList = None
+ self._HorzJoins = None
+# except:
+# return False
+
+ def Execute(
+ self,
+ clipType,
+ solution,
+ subjFillType = PolyFillType.EvenOdd,
+ clipFillType = PolyFillType.EvenOdd):
+ if self._ExecuteLocked: return False
+ try:
+ self._ExecuteLocked = True
+ self._UsingPolyTree = True
+ del solution[:]
+ self._SubjFillType = subjFillType
+ self._ClipFillType = clipFillType
+ self._ClipType = clipType
+ result = self._ExecuteInternal()
+ if result: self._BuildResult(solution)
+ finally:
+ self._ExecuteLocked = False
+ self._UsingPolyTree = False
+ return result
+
+ def Execute2(
+ self,
+ clipType,
+ solutionTree,
+ subjFillType = PolyFillType.EvenOdd,
+ clipFillType = PolyFillType.EvenOdd):
+ if self._ExecuteLocked: return False
+ try:
+ self._ExecuteLocked = True
+ self._UsingPolyTree = True
+ solutionTree.Clear()
+ self._SubjFillType = subjFillType
+ self._ClipFillType = clipFillType
+ self._ClipType = clipType
+ result = self._ExecuteInternal()
+ if result: self._BuildResult2(solutionTree)
+ finally:
+ self._ExecuteLocked = False
+ self._UsingPolyTree = False
+ return result
+
+ def _BuildResult(self, polygons):
+ for outRec in self._PolyOutList:
+ if outRec is None: continue
+ cnt = _PointCount(outRec.pts)
+ if (cnt < 3): continue
+ poly = []
+ op = outRec.pts
+ for _ in range(cnt):
+ poly.append(op.pt)
+ op = op.prevOp
+ polygons.append(poly)
+ return
+
+ def _BuildResult2(self, polyTree):
+ for outRec in self._PolyOutList:
+ if outRec is None: continue
+ cnt = _PointCount(outRec.pts)
+ if (cnt < 3): continue
+ _FixHoleLinkage(outRec)
+
+ # add nodes to _AllNodes list ...
+ polyNode = PolyNode()
+ polyTree._AllNodes.append(polyNode)
+ outRec.PolyNode = polyNode
+ op = outRec.pts
+ while True:
+ polyNode.Contour.append(op.pt)
+ op = op.prevOp
+ if op == outRec.pts: break
+ # build the tree ...
+ for outRec in self._PolyOutList:
+ if outRec.PolyNode is None: continue
+ if outRec.FirstLeft is None:
+ polyTree._AddChild(outRec.PolyNode)
+ else:
+ outRec.FirstLeft.PolyNode._AddChild(outRec.PolyNode)
+ return
+
+#===============================================================================
+# OffsetPolygons (+ ancilliary functions)
+#===============================================================================
+
+def _GetUnitNormal(pt1, pt2):
+ if pt2.x == pt1.x and pt2.y == pt1.y:
+ return FloatPoint(0.0, 0.0)
+ dx = float(pt2.x - pt1.x)
+ dy = float(pt2.y - pt1.y)
+ f = 1.0 / math.hypot(dx, dy)
+ dx = float(dx) * f
+ dy = float(dy) * f
+ return FloatPoint(dy, -dx)
+
+def _GetBounds(pts):
+ left = None
+ for poly in pts:
+ for pt in poly:
+ left = pt.x
+ top = pt.y
+ right = pt.x
+ bottom = pt.y
+ break
+ break
+
+ for poly in pts:
+ for pt in poly:
+ if pt.x < left: left = pt.x
+ if pt.x > right: right = pt.x
+ if pt.y < top: top = pt.y
+ if pt.y > bottom: bottom = pt.y
+ if left is None: return Rect(0, 0, 0, 0)
+ else: return Rect(left, top, right, bottom)
+
+def _GetLowestPt(poly):
+ # precondition: poly must not be empty
+ result = poly[0]
+ for pt in poly:
+ if pt.y > result.y or (pt.y == result.y and pt.x < result.x):
+ result = pt
+ return result
+
+def _StripDupPts(poly):
+ if poly == []: return poly
+ for i in range(1, len(poly)):
+ if _PointsEqual(poly[i-1], poly[i]): poly.pop(i)
+ i = len(poly) -1
+ while i > 0 and _PointsEqual(poly[i], poly[0]):
+ poly.pop(i)
+ i -= 1
+ return poly
+
+def _OffsetInternal(polys, isPolygon, delta, jointype = JoinType.Square, endtype = EndType.Square, limit = 0.0):
+
+ def _DoSquare(pt):
+ # see offset_triginometry.svg in the documentation folder ...
+ dx = math.tan(math.atan2(sinA,
+ Normals[k].x * Normals[j].x + Normals[k].y * Normals[j].y)/4)
+ result.append(Point(
+ round(pt.x + delta * (Normals[k].x - Normals[k].y *dx)),
+ round(pt.y + delta * (Normals[k].y + Normals[k].x *dx))))
+ result.append(Point(
+ round(pt.x + delta * (Normals[j].x + Normals[j].y *dx)),
+ round(pt.y + delta * (Normals[j].y - Normals[j].x *dx))))
+ return
+
+ def _DoMiter(pt, r):
+ q = delta / r
+ result.append(Point(
+ round(pt.x + (Normals[k].x + Normals[j].x) * q),
+ round(pt.y + (Normals[k].y + Normals[j].y) * q)))
+ return
+
+
+ def _DoRound(pt):
+ a = math.atan2(sinA,
+ Normals[k].x * Normals[j].x + Normals[k].y * Normals[j].y)
+ steps = round(step360 * abs(a));
+ X,Y = Normals[k].x, Normals[k].y
+ for _ in range(steps):
+ result.append(Point(
+ round(pt.x + X * delta), round(pt.y + Y * delta)))
+ X2 = X
+ X = X * mcos - msin * Y
+ Y = X2 * msin + Y * mcos
+ result.append(Point(round(pt.x + Normals[j].x * delta),
+ round(pt.y + Normals[j].y * delta)));
+ return
+
+ def GetSin():
+ result = (Normals[k].x * Normals[j].y - Normals[j].x * Normals[k].y)
+ if (result > 1.0): result = 1.0
+ elif (result < -1.0): result = -1.0
+ return result
+
+ def _OffsetPoint(jointype):
+ if (sinA * delta < 0):
+ result.append(Point(round(pts[j].x + Normals[k].x * delta),
+ round(pts[j].y + Normals[k].y * delta)))
+ result.append(pts[j])
+ result.append(Point(round(pts[j].x + Normals[j].x * delta),
+ round(pts[j].y + Normals[j].y * delta)))
+ elif jointype == JoinType.Miter:
+ r = 1.0 + (Normals[j].x * Normals[k].x + Normals[j].y * Normals[k].y)
+ if (r >= miterLim): _DoMiter(pts[j], r)
+ else: _DoSquare(pts[j])
+ elif jointype == JoinType.Square: _DoSquare(pts[j])
+ else: _DoRound(pts[j])
+ return j
+
+ if delta == 0: return polys
+ if not isPolygon and delta < 0: delta = -delta
+
+ if jointype == JoinType.Miter:
+ # miterLim: see offset_triginometry3.svg in the documentation folder ...
+ if limit > 2: miterLim = 2 / (limit * limit)
+ else: miterLim = 0.5
+ if endtype == EndType.Round: limit = 0.25
+
+ if jointype == JoinType.Round or endtype == EndType.Round:
+ if limit <= 0: limit = 0.25
+ elif limit > abs(delta)*0.25: limit = abs(delta)*0.25
+ # step360: see offset_triginometry2.svg in the documentation folder ...
+ step360 = math.pi / math.acos(1 - limit / abs(delta))
+ msin = math.sin(2 * math.pi / step360)
+ mcos = math.cos(2 * math.pi / step360)
+ step360 /= math.pi * 2
+ if delta < 0: msin = -msin
+
+ res = []
+ ppts = polys[:]
+ for pts in ppts:
+ Normals = []
+ result = []
+ cnt = len(pts)
+
+ if (cnt == 0 or cnt < 3 and delta <= 0): continue
+
+ if (cnt == 1):
+ if jointype == JoinType.Round:
+ X,Y = 1.0, 0.0
+ for _ in range(round(step360 * 2 * math.pi)):
+ result.append(Point(round(pts[0].x + X * delta),
+ round(pts[0].y + Y * delta)))
+ X2 = X
+ X = X * mcos - msin * Y
+ Y = X2 * msin + Y * mcos
+ else:
+ X,Y = -1.0, -1.0
+ for _ in range(4):
+ result.append(Point(round(pts[0].x + X * delta),
+ round(pts[0].y + Y * delta)))
+ if X < 0: X = 1
+ elif Y < 0: Y = 1
+ else: X = -1
+ continue
+
+ forceClose = _PointsEqual(pts[0], pts[cnt - 1])
+ if (forceClose): cnt -=1
+
+ for j in range(cnt -1):
+ Normals.append(_GetUnitNormal(pts[j], pts[j+1]))
+ if isPolygon or forceClose:
+ Normals.append(_GetUnitNormal(pts[cnt-1], pts[0]))
+ else:
+ Normals.append(Normals[cnt-2])
+
+
+ if (isPolygon or forceClose):
+ k = cnt - 1
+ for j in range(cnt):
+ sinA = GetSin()
+ k = _OffsetPoint(jointype)
+ res.append(result)
+
+ if not isPolygon:
+ result = []
+ delta = -delta
+ k = cnt - 1
+ for j in range(cnt):
+ sinA = GetSin()
+ k = _OffsetPoint(jointype)
+ delta = -delta
+ res.append(result[::-1])
+
+ else:
+ # offset the polyline going forward ...
+ k = 0;
+ for j in range(1, cnt-1):
+ sinA = GetSin()
+ k = _OffsetPoint(jointype)
+
+ # handle the end (butt, round or square) ...
+ if (endtype == EndType.Butt):
+ j = cnt - 1
+ pt1 = Point(round(float(pts[j].x) + Normals[j].x * delta), \
+ round(float(pts[j].y) + Normals[j].y * delta))
+ result.append(pt1)
+ pt1 = Point(round(float(pts[j].x) - Normals[j].x * delta), \
+ round(float(pts[j].y) - Normals[j].y * delta))
+ result.append(pt1)
+ else:
+ j = cnt - 1;
+ k = cnt - 2;
+ Normals[j] = FloatPoint(-Normals[j].x, -Normals[j].y)
+ if (endtype == EndType.Square): _DoSquare(pts[j])
+ else: _DoRound(pts[j])
+
+ # re-build Normals ...
+ for j in range(cnt -1, 0, -1):
+ Normals[j] = FloatPoint(-Normals[j -1].x, -Normals[j -1].y)
+ Normals[0] = FloatPoint(-Normals[1].x, -Normals[1].y)
+
+ # offset the polyline going backward ...
+ k = cnt -1;
+ for j in range(cnt -2, 0, -1):
+ sinA = GetSin()
+ k = _OffsetPoint(jointype)
+
+ # finally handle the start (butt, round or square) ...
+ if (endtype == EndType.Butt):
+ pt1 = Point(round(float(pts[0].x) - Normals[0].x * delta), \
+ round(float(pts[0].y) - Normals[0].y * delta))
+ result.append(pt1)
+ pt1 = Point(round(float(pts[0].x) + Normals[0].x * delta), \
+ round(float(pts[0].y) + Normals[0].y * delta))
+ result.append(pt1)
+ else:
+ j = 0
+ k = 1
+ if (endtype == EndType.Square): _DoSquare(pts[0])
+ else: _DoRound(pts[0])
+ res.append(result)
+
+
+ c = Clipper()
+ c.AddPolygons(res, PolyType.Subject)
+ if delta > 0:
+ c.Execute(ClipType.Union, res, PolyFillType.Positive, PolyFillType.Positive)
+ else:
+ bounds = _GetBounds(res)
+ outer = []
+ outer.append(Point(bounds.left-10, bounds.bottom+10))
+ outer.append(Point(bounds.right+10, bounds.bottom+10))
+ outer.append(Point(bounds.right+10, bounds.top-10))
+ outer.append(Point(bounds.left-10, bounds.top-10))
+ c.AddPolygon(outer, PolyType.Subject)
+ c.ReverseSolution = True
+ c.Execute(ClipType.Union, res, PolyFillType.Negative, PolyFillType.Negative)
+ if len(res) > 0: res.pop(0)
+ return res
+
+def OffsetPolygons(polys, delta, jointype = JoinType.Square, limit = 0.0, autoFix = True):
+ if not autoFix:
+ return _OffsetInternal(polys, True, delta, jointype, EndType.Butt, limit)
+ pts = polys[:]
+ botPoly = None
+ botPt = None
+ for poly in pts:
+ poly = _StripDupPts(poly)
+ if len(poly) < 3: continue
+ bot = _GetLowestPt(poly)
+ if botPt is None or (bot.y > botPt.y) or \
+ (bot.y == botPt.y and bot.x < botPt.x):
+ botPt = bot
+ botPoly = poly
+ if botPt is None: return []
+ # if the outermost polygon has the wrong orientation,
+ # reverse the orientation of all the polygons ...
+ if Area(botPoly) < 0.0:
+ for i in range(len(pts)):
+ pts[i] = pts[i][::-1]
+ return _OffsetInternal(pts, True, delta, jointype, EndType.Butt, limit)
+
+def OffsetPolyLines(polys, delta, jointype = JoinType.Square, endtype = EndType.Square, limit = 0.0):
+ polys2 = polys[:]
+ for p in polys2:
+ if p == []: continue
+ for i in range(1, len(p)):
+ if _PointsEqual(p[i-1], p[i]): p.pop(i)
+
+ if endtype == EndType.Closed:
+ for i in range(len(polys2)):
+ polys2.append(polys2[i][::-1])
+ return _OffsetInternal(polys2, True, delta, jointype, EndType.Butt, limit)
+ else:
+ return _OffsetInternal(polys2, False, delta, jointype, endtype, limit)
+
+def _DistanceSqrd(pt1, pt2):
+ dx = (pt1.x - pt2.x)
+ dy = (pt1.y - pt2.y)
+ return (dx*dx + dy*dy)
+
+def _ClosestPointOnLine(pt, linePt1, linePt2):
+ dx = linePt2.x - linePt1.x
+ dy = linePt2.y - linePt1.y
+ if (dx == 0 and dy == 0):
+ return FloatPoint(linePt1.x, linePt1.y)
+ q = ((pt.x-linePt1.x)*dx + (pt.Y-linePt1.Y)*dy) / (dx*dx + dy*dy)
+ return FloatPoint(
+ (1-q)*linePt1.X + q*linePt2.X,
+ (1-q)*linePt1.Y + q*linePt2.Y)
+
+def _SlopesNearColinear(pt1, pt2, pt3, distSqrd):
+ if _DistanceSqrd(pt1, pt2) > _DistanceSqrd(pt1, pt3): return False
+ cpol = _ClosestPointOnLine(pt2, pt1, pt3);
+ dx = pt2.x - cpol.x
+ dy = pt2.y - cpol.y
+ return (dx*dx + dy*dy) < distSqrd
+
+def _PointsAreClose(pt1, pt2, distSqrd):
+ dx = pt1.x - pt2.x
+ dy = pt1.y - pt2.y
+ return (dx * dx) + (dy * dy) <= distSqrd
+
+def CleanPolygon(poly, distance = 1.415):
+ distSqrd = distance * distance
+ highI = len(poly) -1
+ while (highI > 0 and _PointsEqual(poly[highI], poly[0])): highI -= 1
+ if (highI < 2): return []
+ pt = poly[highI]
+ result = []
+ i = 0
+ while True:
+ while (i < highI and _PointsAreClose(pt, poly[i+1], distSqrd)): i +=2
+ i2 = i
+ while (i < highI and (_PointsAreClose(poly[i], poly[i+1], distSqrd) or \
+ _SlopesNearColinear(pt, poly[i], poly[i+1], distSqrd))): i +=1
+ if i >= highI: break
+ elif i != i2: continue
+ pt = poly[i]
+ i +=1
+ result.append(pt)
+
+ if (i <= highI): result.append(poly[i])
+ j = len(result)
+ if (j > 2 and _SlopesNearColinear(result[j-2], result[j-1], result[0], distSqrd)):
+ del result[j-1:]
+ if len(result) < 3: return []
+ else: return result
+
+def CleanPolygons(polys, distance = 1.415):
+ result = []
+ for poly in polys:
+ result.append(CleanPolygon(poly, distance = 1.415))
+ return result
+
+def SimplifyPolygon(poly, fillType):
+ result = []
+ c = Clipper();
+ c.ForceSimple = True
+ c.AddPolygon(poly, PolyType.Subject);
+ c.Execute(ClipType.Union, result, fillType, fillType)
+ return result
+
+def SimplifyPolygons(polys, fillType):
+ result = []
+ c = Clipper();
+ c.ForceSimple = True
+ c.AddPolygons(polys, PolyType.Subject);
+ c.Execute(ClipType.Union, result, fillType, fillType)
+ return result
diff --git a/Third Party/python/clipper_demo.py b/Third Party/python/clipper_demo.py
new file mode 100644
index 0000000..14696aa
--- /dev/null
+++ b/Third Party/python/clipper_demo.py
@@ -0,0 +1,267 @@
+# from clipper import Area, Clipper, Point, ClipType, PolyType, PolyFillType
+from clipper import *
+import math
+import re
+from random import randint
+# from subprocess import call
+import os
+
+#===============================================================================
+#===============================================================================
+
+def LoadFile1(lines):
+ # File type 1: first line is total polygons count and subsequent lines
+ # contain the polygon vertex count followed by its coords
+ try:
+ polygons = []
+ poly = []
+ for l in lines:
+ vals = re.split(' |, |,', l.strip())
+ if len(vals) < 2:
+ if (len(poly) > 2):
+ polygons.append(poly)
+ poly = []
+ else:
+ poly.append(Point(int(vals[0]), int(vals[1])))
+ if (len(poly) > 2):
+ polygons.append(poly)
+ return polygons
+ except:
+ return None
+#===============================================================================
+
+def LoadFile2(lines):
+ # File type 2: vertex coords on consecutive lines for each polygon
+ # where each polygon is separated by an empty line
+ try:
+ polygons = []
+ poly = []
+ for l in lines:
+ l = l.strip()
+ if (l == ''):
+ if (len(poly) > 2):
+ polygons.append(poly)
+ poly = []
+ else:
+ vals = re.split(' |, |,', l)
+ poly.append(Point(int(vals[0]), int(vals[1])))
+ if (len(poly) > 2):
+ polygons.append(poly)
+ return polygons
+ except:
+ return None
+#===============================================================================
+
+def LoadFile(filename):
+ try:
+ f = open(filename, 'r')
+ try:
+ lines = f.readlines()
+ finally:
+ f.close()
+ # pick file type from format of first line ...
+ if len(lines) == 0: return []
+ elif not ',' in lines[0]: return LoadFile1(lines)
+ else: return LoadFile2(lines)
+ except:
+ return None
+#===============================================================================
+
+def SaveToFile(filename, polys, scale = 1.0):
+ invScale = 1.0 / scale
+ try:
+ f = open(filename, 'w')
+ try:
+ if invScale == 1:
+ for poly in polys:
+ for pt in poly:
+ f.write("{0}, {1}\n".format(pt.x, pt.y))
+ f.write("\n")
+ else:
+ for poly in polys:
+ for pt in poly:
+ f.write("{0:.4f}, {1:.4f}\n".format(pt.x * invScale, pt.y * invScale))
+ f.write("\n")
+ finally:
+ f.close()
+ except:
+ return
+#===============================================================================
+
+def RandomPoly(maxWidth, maxHeight, vertCnt):
+ result = []
+ for _ in range(vertCnt):
+ result.append(Point(randint(0, maxWidth), randint(0, maxHeight)))
+ return result
+
+#===============================================================================
+# SVGBuilder
+#===============================================================================
+class SVGBuilder(object):
+
+ def HtmlColor(self, val):
+ return "#{0:06x}".format(val & 0xFFFFFF)
+
+ def AlphaClr(self, val):
+ return "{0:.2f}".format(float(val >> 24)/255)
+
+ class StyleInfo(object):
+ def __init__(self):
+ self.fillType = PolyFillType.EvenOdd
+ self.brushClr = 0
+ self.penClr = 0
+ self.penWidth = 0.8
+ self.showCoords = False
+
+ class StyleInfoPlus(StyleInfo):
+
+ def __init__(self):
+ SVGBuilder.StyleInfo.__init__(self)
+ self.polygons = []
+ self.textlines = []
+
+ def __init__(self):
+ self.GlobalStyle = SVGBuilder.StyleInfo()
+ self.PolyInfoList = []
+ self.PathHeader = " <path d=\""
+ self.PathFooter = "\"\n style=\"fill:{0}; fill-opacity:{1}; fill-rule:{2}; stroke:{3}; stroke-opacity:{4}; stroke-width:{5:.2f};\" filter=\"url(#Gamma)\"/>\n\n"
+ self.Header = """<?xml version=\"1.0\" standalone=\"no\"?>
+<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"
+\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">
+\n<svg width=\"{0}px\" height=\"{1}px\" viewBox=\"0 0 {0} {1}\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">
+ <defs>
+ <filter id="Gamma">
+ <feComponentTransfer>
+ <feFuncR type="gamma" amplitude="1" exponent="0.3" offset="0" />
+ <feFuncG type="gamma" amplitude="1" exponent="0.3" offset="0" />
+ <feFuncB type="gamma" amplitude="1" exponent="0.3" offset="0" />
+ </feComponentTransfer>
+ </filter>
+ </defs>\n\n"""
+
+ def AddPolygon(self, poly, brushColor, penColor):
+ if poly is None or len(poly) == 0: return
+ pi = self.StyleInfoPlus()
+ pi.penWidth = self.GlobalStyle.penWidth
+ pi.fillType = self.GlobalStyle.fillType
+ pi.showCoords = self.GlobalStyle.showCoords
+ pi.brushClr = brushColor
+ pi.penClr = penColor
+ pi.polygons.append(poly)
+ self.PolyInfoList.append(pi)
+
+ def AddPolygons(self, polys, brushColor, penColor):
+ if polys is None or len(polys) == 0: return
+ pi = self.StyleInfoPlus()
+ pi.penWidth = self.GlobalStyle.penWidth
+ pi.fillType = self.GlobalStyle.fillType
+ pi.showCoords = self.GlobalStyle.showCoords
+ pi.brushClr = brushColor
+ pi.penClr = penColor
+ pi.polygons = polys
+ self.PolyInfoList.append(pi)
+
+ def SaveToFile(self, filename, invScale = 1.0, margin = 10):
+ if len(self.PolyInfoList) == 0: return False
+ if invScale == 0: invScale = 1.0
+ if margin < 0: margin = 0
+ pi = self.PolyInfoList[0]
+ # get bounding rect ...
+ left = right = pi.polygons[0][0].x
+ top = bottom = pi.polygons[0][0].y
+ for pi in self.PolyInfoList:
+ for p in pi.polygons:
+ for ip in p:
+ if ip.x < left: left = ip.x
+ if ip.x > right: right = ip.x
+ if ip.y < top: top = ip.y
+ if ip.y > bottom: bottom = ip.y
+ left *= invScale
+ top *= invScale
+ right *= invScale
+ bottom *= invScale
+ offsetX = -left + margin
+ offsetY = -top + margin
+
+ f = open(filename, 'w')
+ m2 = margin * 2
+ f.write(self.Header.format(right - left + m2, bottom - top + m2))
+ for pi in self.PolyInfoList:
+ f.write(self.PathHeader)
+ for p in pi.polygons:
+ cnt = len(p)
+ if cnt < 3: continue
+ f.write(" M {0:.2f} {1:.2f}".format(p[0].x * invScale + offsetX, p[0].y * invScale + offsetY))
+ for i in range(1,cnt):
+ f.write(" L {0:.2f} {1:.2f}".format(p[i].x * invScale + offsetX, p[i].y * invScale + offsetY))
+ f.write(" z")
+ fillRule = "evenodd"
+ if pi.fillType != PolyFillType.EvenOdd: fillRule = "nonzero"
+ f.write(self.PathFooter.format(self.HtmlColor(pi.brushClr),
+ self.AlphaClr(pi.brushClr), fillRule,
+ self.HtmlColor(pi.penClr), self.AlphaClr(pi.penClr), pi.penWidth))
+
+ if (pi.showCoords):
+ f.write("<g font-family=\"Verdana\" font-size=\"11\" fill=\"black\">\n\n")
+ for p in pi.polygons:
+ cnt = len(p)
+ if cnt < 3: continue
+ for pt in p:
+ x = pt.x * invScale + offsetX
+ y = pt.y * invScale + offsetY
+ f.write("<text x=\"{0}\" y=\"{1}\">{2},{3}</text>\n".format(x, y, pt.x, pt.y))
+ f.write("\n")
+ f.write("</g>\n")
+
+ f.write("</svg>\n")
+ f.close()
+ return True
+
+#===============================================================================
+# Main entry ...
+#===============================================================================
+
+scaleExp = 0
+scale = math.pow(10, scaleExp)
+invScale = 1.0 / scale
+
+subj, clip = [], []
+#load saved subject and clip polygons ...
+#subj = LoadFile('./subj.txt')
+#clip = LoadFile('./clip.txt')
+
+# Generate random subject and clip polygons ...
+subj.append(RandomPoly(640 * scale, 480 * scale, 100))
+clip.append(RandomPoly(640 * scale, 480 * scale, 100))
+#SaveToFile('./subj2.txt', subj, scale)
+#SaveToFile('./clip2.txt', clip, scale)
+
+# Load the polygons into Clipper and execute the boolean clip op ...
+c = Clipper()
+solution = []
+pft = PolyFillType.EvenOdd
+
+c.AddPolygons(subj, PolyType.Subject)
+c.AddPolygons(clip, PolyType.Clip)
+result = c.Execute(ClipType.Intersection, solution, pft, pft)
+
+SaveToFile('./solution2.txt', solution, scale)
+
+# Create an SVG file to display what's happened ...
+svgBuilder = SVGBuilder()
+#svgBuilder.GlobalStyle.showCoords = True
+svgBuilder.GlobalStyle.fillType = pft
+svgBuilder.AddPolygons(subj, 0x402020FF, 0x802020FF)
+#svgBuilder.GlobalStyle.showCoords = False
+svgBuilder.AddPolygons(clip, 0x40FFFF20, 0x80FF2020)
+svgBuilder.GlobalStyle.penWidth = 0.6
+svgBuilder.AddPolygons(solution, 0x60138013, 0xFF003300)
+
+holes = []
+for poly in solution:
+ if Area(poly) < 0: holes.append(poly)
+svgBuilder.AddPolygons(holes, 0x0, 0xFFFF0000)
+svgBuilder.SaveToFile('./test.svg', invScale, 100)
+
+if result: os.startfile('test.svg') # call(('open', 'test.svg')) # print("finished") #
+else: print("failed")
\ No newline at end of file
diff --git a/Third Party/python/python_readme.txt b/Third Party/python/python_readme.txt
new file mode 100644
index 0000000..2fe1d3d
--- /dev/null
+++ b/Third Party/python/python_readme.txt
@@ -0,0 +1,10 @@
+
+The clipper.py file included in this distribution contains a very old version of Clipper.
+
+I found it too onerous maintaining 4 parallel translations of Clipper, and I only wrote clipper.py to teach myself Python.
+Besides, the Python code is about 100 times slower than compiled versions of Clipper so there are better alternatives.
+
+Maxime Chalon <maxime.chalon at gmail.com> has written a Python extension module for Clipper:
+https://sites.google.com/site/maxelsbackyard/home/pyclipper
+This module provides a Python interface to the C++ compiled Clipper Library.
+
diff --git a/Third Party/ruby/ruby_readme.txt b/Third Party/ruby/ruby_readme.txt
new file mode 100644
index 0000000..c71c1d8
--- /dev/null
+++ b/Third Party/ruby/ruby_readme.txt
@@ -0,0 +1,4 @@
+A Ruby module written by Mike Owens <http://mike.filespanker.com>
+that wraps the Clipper library can be downloaded from:
+
+http://github.com/mieko/rbclipper
\ No newline at end of file
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
new file mode 100644
index 0000000..652221f
--- /dev/null
+++ b/cpp/CMakeLists.txt
@@ -0,0 +1,21 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0)
+PROJECT(polyclipping)
+
+SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "Release type")
+# The header name clipper.hpp is too generic, so install in a subdirectory
+SET(CMAKE_INSTALL_INCDIR "${CMAKE_INSTALL_PREFIX}/include/polyclipping")
+SET(CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}")
+SET(CMAKE_INSTALL_PKGCONFIGDIR "${CMAKE_INSTALL_PREFIX}/share/pkgconfig")
+SET(PCFILE "${CMAKE_CURRENT_BINARY_DIR}/polyclipping.pc")
+
+SET(BUILD_SHARED_LIBS ON CACHE BOOL
+ "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)")
+ADD_LIBRARY(polyclipping clipper.cpp)
+
+CONFIGURE_FILE (polyclipping.pc.cmakein "${PCFILE}" @ONLY)
+
+INSTALL (FILES clipper.hpp DESTINATION "${CMAKE_INSTALL_INCDIR}")
+INSTALL (TARGETS polyclipping LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+INSTALL (FILES "${PCFILE}" DESTINATION "${CMAKE_INSTALL_PKGCONFIGDIR}")
+
+SET_TARGET_PROPERTIES(polyclipping PROPERTIES VERSION 21.0.0 SOVERSION 21 )
diff --git a/cpp/clipper.cpp b/cpp/clipper.cpp
new file mode 100644
index 0000000..d932da0
--- /dev/null
+++ b/cpp/clipper.cpp
@@ -0,0 +1,4577 @@
+/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 6.2.9 *
+* Date : 16 February 2015 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2015 *
+* *
+* 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. *
+* *
+*******************************************************************************/
+
+#include "clipper.hpp"
+#include <cmath>
+#include <vector>
+#include <algorithm>
+#include <stdexcept>
+#include <cstring>
+#include <cstdlib>
+#include <ostream>
+#include <functional>
+#include <sstream>
+
+namespace ClipperLib {
+
+static double const pi = 3.141592653589793238;
+static double const two_pi = pi *2;
+static double const def_arc_tolerance = 0.25;
+
+enum Direction { dRightToLeft, dLeftToRight };
+
+static int const Unassigned = -1; //edge not currently 'owning' a solution
+static int const Skip = -2; //edge that would otherwise close a path
+
+#define HORIZONTAL (-1.0E+40)
+#define TOLERANCE (1.0e-20)
+#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
+
+struct TEdge {
+ IntPoint Bot;
+ IntPoint Curr;
+ IntPoint Top;
+ IntPoint Delta;
+ double Dx;
+ PolyType PolyTyp;
+ EdgeSide Side;
+ int WindDelta; //1 or -1 depending on winding direction
+ int WindCnt;
+ int WindCnt2; //winding count of the opposite polytype
+ int OutIdx;
+ TEdge *Next;
+ TEdge *Prev;
+ TEdge *NextInLML;
+ TEdge *NextInAEL;
+ TEdge *PrevInAEL;
+ TEdge *NextInSEL;
+ TEdge *PrevInSEL;
+};
+
+struct IntersectNode {
+ TEdge *Edge1;
+ TEdge *Edge2;
+ IntPoint Pt;
+};
+
+struct LocalMinimum {
+ cInt Y;
+ TEdge *LeftBound;
+ TEdge *RightBound;
+};
+
+struct OutPt;
+
+struct OutRec {
+ int Idx;
+ bool IsHole;
+ bool IsOpen;
+ OutRec *FirstLeft; //see comments in clipper.pas
+ PolyNode *PolyNd;
+ OutPt *Pts;
+ OutPt *BottomPt;
+};
+
+struct OutPt {
+ int Idx;
+ IntPoint Pt;
+ OutPt *Next;
+ OutPt *Prev;
+};
+
+struct Join {
+ OutPt *OutPt1;
+ OutPt *OutPt2;
+ IntPoint OffPt;
+};
+
+struct LocMinSorter
+{
+ inline bool operator()(const LocalMinimum& locMin1, const LocalMinimum& locMin2)
+ {
+ return locMin2.Y < locMin1.Y;
+ }
+};
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+inline cInt Round(double val)
+{
+ if ((val < 0)) return static_cast<cInt>(val - 0.5);
+ else return static_cast<cInt>(val + 0.5);
+}
+//------------------------------------------------------------------------------
+
+inline cInt Abs(cInt val)
+{
+ return val < 0 ? -val : val;
+}
+
+//------------------------------------------------------------------------------
+// PolyTree methods ...
+//------------------------------------------------------------------------------
+
+void PolyTree::Clear()
+{
+ for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
+ delete AllNodes[i];
+ AllNodes.resize(0);
+ Childs.resize(0);
+}
+//------------------------------------------------------------------------------
+
+PolyNode* PolyTree::GetFirst() const
+{
+ if (!Childs.empty())
+ return Childs[0];
+ else
+ return 0;
+}
+//------------------------------------------------------------------------------
+
+int PolyTree::Total() const
+{
+ int result = (int)AllNodes.size();
+ //with negative offsets, ignore the hidden outer polygon ...
+ if (result > 0 && Childs[0] != AllNodes[0]) result--;
+ return result;
+}
+
+//------------------------------------------------------------------------------
+// PolyNode methods ...
+//------------------------------------------------------------------------------
+
+PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false)
+{
+}
+//------------------------------------------------------------------------------
+
+int PolyNode::ChildCount() const
+{
+ return (int)Childs.size();
+}
+//------------------------------------------------------------------------------
+
+void PolyNode::AddChild(PolyNode& child)
+{
+ unsigned cnt = (unsigned)Childs.size();
+ Childs.push_back(&child);
+ child.Parent = this;
+ child.Index = cnt;
+}
+//------------------------------------------------------------------------------
+
+PolyNode* PolyNode::GetNext() const
+{
+ if (!Childs.empty())
+ return Childs[0];
+ else
+ return GetNextSiblingUp();
+}
+//------------------------------------------------------------------------------
+
+PolyNode* PolyNode::GetNextSiblingUp() const
+{
+ if (!Parent) //protects against PolyTree.GetNextSiblingUp()
+ return 0;
+ else if (Index == Parent->Childs.size() - 1)
+ return Parent->GetNextSiblingUp();
+ else
+ return Parent->Childs[Index + 1];
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsHole() const
+{
+ bool result = true;
+ PolyNode* node = Parent;
+ while (node)
+ {
+ result = !result;
+ node = node->Parent;
+ }
+ return result;
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsOpen() const
+{
+ return m_IsOpen;
+}
+//------------------------------------------------------------------------------
+
+#ifndef use_int32
+
+//------------------------------------------------------------------------------
+// Int128 class (enables safe math on signed 64bit integers)
+// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
+// Int128 val2((long64)9223372036854775807);
+// Int128 val3 = val1 * val2;
+// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
+//------------------------------------------------------------------------------
+
+class Int128
+{
+ public:
+ ulong64 lo;
+ long64 hi;
+
+ Int128(long64 _lo = 0)
+ {
+ lo = (ulong64)_lo;
+ if (_lo < 0) hi = -1; else hi = 0;
+ }
+
+
+ Int128(const Int128 &val): lo(val.lo), hi(val.hi){}
+
+ Int128(const long64& _hi, const ulong64& _lo): lo(_lo), hi(_hi){}
+
+ Int128& operator = (const long64 &val)
+ {
+ lo = (ulong64)val;
+ if (val < 0) hi = -1; else hi = 0;
+ return *this;
+ }
+
+ bool operator == (const Int128 &val) const
+ {return (hi == val.hi && lo == val.lo);}
+
+ bool operator != (const Int128 &val) const
+ { return !(*this == val);}
+
+ bool operator > (const Int128 &val) const
+ {
+ if (hi != val.hi)
+ return hi > val.hi;
+ else
+ return lo > val.lo;
+ }
+
+ bool operator < (const Int128 &val) const
+ {
+ if (hi != val.hi)
+ return hi < val.hi;
+ else
+ return lo < val.lo;
+ }
+
+ bool operator >= (const Int128 &val) const
+ { return !(*this < val);}
+
+ bool operator <= (const Int128 &val) const
+ { return !(*this > val);}
+
+ Int128& operator += (const Int128 &rhs)
+ {
+ hi += rhs.hi;
+ lo += rhs.lo;
+ if (lo < rhs.lo) hi++;
+ return *this;
+ }
+
+ Int128 operator + (const Int128 &rhs) const
+ {
+ Int128 result(*this);
+ result+= rhs;
+ return result;
+ }
+
+ Int128& operator -= (const Int128 &rhs)
+ {
+ *this += -rhs;
+ return *this;
+ }
+
+ Int128 operator - (const Int128 &rhs) const
+ {
+ Int128 result(*this);
+ result -= rhs;
+ return result;
+ }
+
+ Int128 operator-() const //unary negation
+ {
+ if (lo == 0)
+ return Int128(-hi, 0);
+ else
+ return Int128(~hi, ~lo + 1);
+ }
+
+ operator double() const
+ {
+ const double shift64 = 18446744073709551616.0; //2^64
+ if (hi < 0)
+ {
+ if (lo == 0) return (double)hi * shift64;
+ else return -(double)(~lo + ~hi * shift64);
+ }
+ else
+ return (double)(lo + hi * shift64);
+ }
+
+};
+//------------------------------------------------------------------------------
+
+Int128 Int128Mul (long64 lhs, long64 rhs)
+{
+ bool negate = (lhs < 0) != (rhs < 0);
+
+ if (lhs < 0) lhs = -lhs;
+ ulong64 int1Hi = ulong64(lhs) >> 32;
+ ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
+
+ if (rhs < 0) rhs = -rhs;
+ ulong64 int2Hi = ulong64(rhs) >> 32;
+ ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
+
+ //nb: see comments in clipper.pas
+ ulong64 a = int1Hi * int2Hi;
+ ulong64 b = int1Lo * int2Lo;
+ ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
+
+ Int128 tmp;
+ tmp.hi = long64(a + (c >> 32));
+ tmp.lo = long64(c << 32);
+ tmp.lo += long64(b);
+ if (tmp.lo < b) tmp.hi++;
+ if (negate) tmp = -tmp;
+ return tmp;
+};
+#endif
+
+//------------------------------------------------------------------------------
+// Miscellaneous global functions
+//------------------------------------------------------------------------------
+
+bool Orientation(const Path &poly)
+{
+ return Area(poly) >= 0;
+}
+//------------------------------------------------------------------------------
+
+double Area(const Path &poly)
+{
+ int size = (int)poly.size();
+ if (size < 3) return 0;
+
+ double a = 0;
+ for (int i = 0, j = size -1; i < size; ++i)
+ {
+ a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y);
+ j = i;
+ }
+ return -a * 0.5;
+}
+//------------------------------------------------------------------------------
+
+double Area(const OutRec &outRec)
+{
+ OutPt *op = outRec.Pts;
+ if (!op) return 0;
+ double a = 0;
+ do {
+ 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;
+}
+//------------------------------------------------------------------------------
+
+bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
+{
+ OutPt *pp2 = pp;
+ do
+ {
+ if (pp2->Pt == Pt) return true;
+ pp2 = pp2->Next;
+ }
+ while (pp2 != pp);
+ return false;
+}
+//------------------------------------------------------------------------------
+
+//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+int PointInPolygon(const IntPoint &pt, const Path &path)
+{
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ int result = 0;
+ size_t cnt = path.size();
+ if (cnt < 3) return 0;
+ IntPoint ip = path[0];
+ for(size_t i = 1; i <= cnt; ++i)
+ {
+ IntPoint ipNext = (i == cnt ? path[0] : path[i]);
+ if (ipNext.Y == pt.Y)
+ {
+ if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
+ ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1;
+ }
+ if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y))
+ {
+ if (ip.X >= pt.X)
+ {
+ if (ipNext.X > pt.X) result = 1 - result;
+ else
+ {
+ double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+ (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+ if (!d) return -1;
+ if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result;
+ }
+ } else
+ {
+ if (ipNext.X > pt.X)
+ {
+ double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+ (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+ if (!d) return -1;
+ if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result;
+ }
+ }
+ }
+ ip = ipNext;
+ }
+ return result;
+}
+//------------------------------------------------------------------------------
+
+int PointInPolygon (const IntPoint &pt, OutPt *op)
+{
+ //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ int result = 0;
+ OutPt* startOp = op;
+ for(;;)
+ {
+ if (op->Next->Pt.Y == pt.Y)
+ {
+ if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y &&
+ ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1;
+ }
+ if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y))
+ {
+ if (op->Pt.X >= pt.X)
+ {
+ if (op->Next->Pt.X > pt.X) result = 1 - result;
+ else
+ {
+ double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+ (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+ if (!d) return -1;
+ if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result;
+ }
+ } else
+ {
+ if (op->Next->Pt.X > pt.X)
+ {
+ double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+ (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+ if (!d) return -1;
+ if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result;
+ }
+ }
+ }
+ op = op->Next;
+ if (startOp == op) break;
+ }
+ return result;
+}
+//------------------------------------------------------------------------------
+
+bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2)
+{
+ OutPt* op = OutPt1;
+ do
+ {
+ //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+ int res = PointInPolygon(op->Pt, OutPt2);
+ if (res >= 0) return res > 0;
+ op = op->Next;
+ }
+ while (op != OutPt1);
+ return true;
+}
+//----------------------------------------------------------------------
+
+bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range)
+{
+#ifndef use_int32
+ if (UseFullInt64Range)
+ return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y);
+ else
+#endif
+ return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y;
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
+ const IntPoint pt3, bool UseFullInt64Range)
+{
+#ifndef use_int32
+ if (UseFullInt64Range)
+ return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y);
+ else
+#endif
+ return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2,
+ const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range)
+{
+#ifndef use_int32
+ if (UseFullInt64Range)
+ return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y);
+ else
+#endif
+ return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsHorizontal(TEdge &e)
+{
+ return e.Delta.Y == 0;
+}
+//------------------------------------------------------------------------------
+
+inline double GetDx(const IntPoint pt1, const IntPoint pt2)
+{
+ return (pt1.Y == pt2.Y) ?
+ HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
+}
+//---------------------------------------------------------------------------
+
+inline 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;
+}
+//---------------------------------------------------------------------------
+
+inline void SwapSides(TEdge &Edge1, TEdge &Edge2)
+{
+ EdgeSide Side = Edge1.Side;
+ Edge1.Side = Edge2.Side;
+ Edge2.Side = Side;
+}
+//------------------------------------------------------------------------------
+
+inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2)
+{
+ int OutIdx = Edge1.OutIdx;
+ Edge1.OutIdx = Edge2.OutIdx;
+ Edge2.OutIdx = OutIdx;
+}
+//------------------------------------------------------------------------------
+
+inline cInt TopX(TEdge &edge, const cInt currentY)
+{
+ return ( currentY == edge.Top.Y ) ?
+ edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y));
+}
+//------------------------------------------------------------------------------
+
+void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip)
+{
+#ifdef use_xyz
+ ip.Z = 0;
+#endif
+
+ double b1, b2;
+ if (Edge1.Dx == Edge2.Dx)
+ {
+ ip.Y = Edge1.Curr.Y;
+ ip.X = TopX(Edge1, ip.Y);
+ return;
+ }
+ 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 (std::fabs(Edge1.Dx) < std::fabs(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 (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
+ ip.X = TopX(Edge1, ip.Y);
+ else
+ ip.X = TopX(Edge2, ip.Y);
+ }
+ //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
+ if (ip.Y > Edge1.Curr.Y)
+ {
+ ip.Y = Edge1.Curr.Y;
+ //use the more vertical edge to derive X ...
+ if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
+ ip.X = TopX(Edge2, ip.Y); else
+ ip.X = TopX(Edge1, ip.Y);
+ }
+}
+//------------------------------------------------------------------------------
+
+void ReversePolyPtLinks(OutPt *pp)
+{
+ if (!pp) return;
+ OutPt *pp1, *pp2;
+ pp1 = pp;
+ do {
+ pp2 = pp1->Next;
+ pp1->Next = pp1->Prev;
+ pp1->Prev = pp2;
+ pp1 = pp2;
+ } while( pp1 != pp );
+}
+//------------------------------------------------------------------------------
+
+void DisposeOutPts(OutPt*& pp)
+{
+ if (pp == 0) return;
+ pp->Prev->Next = 0;
+ while( pp )
+ {
+ OutPt *tmpPp = pp;
+ pp = pp->Next;
+ delete tmpPp;
+ }
+}
+//------------------------------------------------------------------------------
+
+inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt)
+{
+ std::memset(e, 0, sizeof(TEdge));
+ e->Next = eNext;
+ e->Prev = ePrev;
+ e->Curr = Pt;
+ e->OutIdx = Unassigned;
+}
+//------------------------------------------------------------------------------
+
+void InitEdge2(TEdge& e, PolyType Pt)
+{
+ 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 = Pt;
+}
+//------------------------------------------------------------------------------
+
+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 = 0; //flag as removed (see ClipperBase.Clear)
+ return result;
+}
+//------------------------------------------------------------------------------
+
+inline 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.]
+ std::swap(e.Top.X, e.Bot.X);
+#ifdef use_xyz
+ std::swap(e.Top.Z, e.Bot.Z);
+#endif
+}
+//------------------------------------------------------------------------------
+
+void SwapPoints(IntPoint &pt1, IntPoint &pt2)
+{
+ IntPoint tmp = pt1;
+ pt1 = pt2;
+ pt2 = tmp;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
+ IntPoint pt2b, IntPoint &pt1, IntPoint &pt2)
+{
+ //precondition: segments are Collinear.
+ if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y))
+ {
+ if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b);
+ if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b);
+ if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a;
+ if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b;
+ return pt1.X < pt2.X;
+ } else
+ {
+ if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b);
+ if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b);
+ if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a;
+ if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b;
+ return pt1.Y > pt2.Y;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
+{
+ OutPt *p = btmPt1->Prev;
+ while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev;
+ double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
+ p = btmPt1->Next;
+ while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next;
+ double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
+
+ p = btmPt2->Prev;
+ while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev;
+ double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
+ p = btmPt2->Next;
+ while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next;
+ double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
+ return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
+}
+//------------------------------------------------------------------------------
+
+OutPt* GetBottomPt(OutPt *pp)
+{
+ OutPt* dups = 0;
+ OutPt* p = pp->Next;
+ while (p != pp)
+ {
+ if (p->Pt.Y > pp->Pt.Y)
+ {
+ pp = p;
+ dups = 0;
+ }
+ else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X)
+ {
+ if (p->Pt.X < pp->Pt.X)
+ {
+ dups = 0;
+ pp = p;
+ } else
+ {
+ if (p->Next != pp && p->Prev != pp) dups = p;
+ }
+ }
+ p = p->Next;
+ }
+ if (dups)
+ {
+ //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;
+}
+//------------------------------------------------------------------------------
+
+bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1,
+ const IntPoint pt2, const 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);
+}
+//------------------------------------------------------------------------------
+
+bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b)
+{
+ if (seg1a > seg1b) std::swap(seg1a, seg1b);
+ if (seg2a > seg2b) std::swap(seg2a, seg2b);
+ return (seg1a < seg2b) && (seg2a < seg1b);
+}
+
+//------------------------------------------------------------------------------
+// ClipperBase class methods ...
+//------------------------------------------------------------------------------
+
+ClipperBase::ClipperBase() //constructor
+{
+ m_CurrentLM = m_MinimaList.begin(); //begin() == end() here
+ m_UseFullRange = false;
+}
+//------------------------------------------------------------------------------
+
+ClipperBase::~ClipperBase() //destructor
+{
+ Clear();
+}
+//------------------------------------------------------------------------------
+
+void RangeTest(const IntPoint& Pt, bool& useFullRange)
+{
+ if (useFullRange)
+ {
+ if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
+ {
+ std::stringstream s;
+ s << "Coordinate outside allowed range: ";
+ s << std::fixed << Pt.X << " " << Pt.Y << " " << -Pt.X << " " << -Pt.Y;
+ throw clipperException(s.str().c_str());
+ }
+ }
+ else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange)
+ {
+ useFullRange = true;
+ RangeTest(Pt, useFullRange);
+ }
+}
+//------------------------------------------------------------------------------
+
+TEdge* FindNextLocMin(TEdge* E)
+{
+ for (;;)
+ {
+ while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next;
+ if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break;
+ while (IsHorizontal(*E->Prev)) E = E->Prev;
+ TEdge* E2 = E;
+ while (IsHorizontal(*E)) 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;
+}
+//------------------------------------------------------------------------------
+
+TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
+{
+ TEdge *Result = E;
+ TEdge *Horz = 0;
+
+ if (E->OutIdx == Skip)
+ {
+ //if edges still remain in the current bound beyond the skip edge then
+ //create another LocMin and call ProcessBound once more
+ if (NextIsForward)
+ {
+ 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 && IsHorizontal(*E)) E = E->Prev;
+ }
+ else
+ {
+ while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev;
+ while (E != Result && IsHorizontal(*E)) E = E->Next;
+ }
+
+ if (E == Result)
+ {
+ if (NextIsForward) Result = E->Next;
+ else Result = E->Prev;
+ }
+ else
+ {
+ //there are more edges in the bound beyond result starting with E
+ if (NextIsForward)
+ E = Result->Next;
+ else
+ E = Result->Prev;
+ MinimaList::value_type locMin;
+ locMin.Y = E->Bot.Y;
+ locMin.LeftBound = 0;
+ locMin.RightBound = E;
+ E->WindDelta = 0;
+ Result = ProcessBound(E, NextIsForward);
+ m_MinimaList.push_back(locMin);
+ }
+ return Result;
+ }
+
+ TEdge *EStart;
+
+ if (IsHorizontal(*E))
+ {
+ //We need to be careful with open paths because this may not be a
+ //true local minima (ie E may be following a skip edge).
+ //Also, consecutive horz. edges may start heading left before going right.
+ if (NextIsForward)
+ EStart = E->Prev;
+ else
+ EStart = E->Next;
+ if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge
+ {
+ if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
+ ReverseHorizontal(*E);
+ }
+ else if (EStart->Bot.X != E->Bot.X)
+ ReverseHorizontal(*E);
+ }
+
+ EStart = E;
+ if (NextIsForward)
+ {
+ while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
+ Result = Result->Next;
+ if (IsHorizontal(*Result) && 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 (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev;
+ if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev;
+ }
+ while (E != Result)
+ {
+ E->NextInLML = E->Next;
+ if (IsHorizontal(*E) && E != EStart &&
+ E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E);
+ E = E->Next;
+ }
+ if (IsHorizontal(*E) && 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 (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip)
+ {
+ Horz = Result;
+ while (IsHorizontal(*Horz->Next)) Horz = Horz->Next;
+ if (Horz->Next->Top.X == Result->Prev->Top.X ||
+ Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next;
+ }
+
+ while (E != Result)
+ {
+ E->NextInLML = E->Prev;
+ if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+ ReverseHorizontal(*E);
+ E = E->Prev;
+ }
+ if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+ ReverseHorizontal(*E);
+ Result = Result->Prev; //move to the edge just beyond current bound
+ }
+
+ return Result;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
+{
+#ifdef use_lines
+ if (!Closed && PolyTyp == ptClip)
+ throw clipperException("AddPath: Open paths must be subject.");
+#else
+ if (!Closed)
+ throw clipperException("AddPath: Open paths have been disabled.");
+#endif
+
+ int highI = (int)pg.size() -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 ...
+ TEdge *edges = new TEdge [highI +1];
+
+ bool IsFlat = true;
+ //1. Basic (first) edge initialization ...
+ try
+ {
+ edges[1].Curr = pg[1];
+ RangeTest(pg[0], m_UseFullRange);
+ RangeTest(pg[highI], 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], m_UseFullRange);
+ InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]);
+ }
+ }
+ catch(std::exception const&)
+ {
+ delete [] edges;
+ throw; //range test fails
+ }
+ TEdge *eStart = &edges[0];
+
+ //2. Remove duplicate vertices, and (when closed) collinear edges ...
+ TEdge *E = eStart, *eLoopStop = eStart;
+ for (;;)
+ {
+ //nb: allows matching start and end points when not Closed ...
+ if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart))
+ {
+ 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) &&
+ (!m_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) || (!Closed && E->Next == eStart)) break;
+ }
+
+ if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next)))
+ {
+ delete [] edges;
+ return false;
+ }
+
+ if (!Closed)
+ {
+ m_HasOpenPaths = true;
+ eStart->Prev->OutIdx = Skip;
+ }
+
+ //3. Do second stage of edge initialization ...
+ E = eStart;
+ do
+ {
+ InitEdge2(*E, PolyTyp);
+ 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)
+ {
+ delete [] edges;
+ return false;
+ }
+ E->Prev->OutIdx = Skip;
+ MinimaList::value_type locMin;
+ locMin.Y = E->Bot.Y;
+ locMin.LeftBound = 0;
+ locMin.RightBound = E;
+ locMin.RightBound->Side = esRight;
+ locMin.RightBound->WindDelta = 0;
+ for (;;)
+ {
+ if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E);
+ if (E->Next->OutIdx == Skip) break;
+ E->NextInLML = E->Next;
+ E = E->Next;
+ }
+ m_MinimaList.push_back(locMin);
+ m_edges.push_back(edges);
+ return true;
+ }
+
+ m_edges.push_back(edges);
+ bool leftBoundIsForward;
+ TEdge* EMin = 0;
+
+ //workaround to avoid an endless loop in the while loop below when
+ //open paths have matching start and end points ...
+ if (E->Prev->Bot == E->Prev->Top) E = E->Next;
+
+ for (;;)
+ {
+ E = FindNextLocMin(E);
+ if (E == EMin) break;
+ else if (!EMin) EMin = E;
+
+ //E and E.Prev now share a local minima (left aligned if horizontal).
+ //Compare their slopes to find which starts which bound ...
+ MinimaList::value_type locMin;
+ locMin.Y = E->Bot.Y;
+ if (E->Dx < E->Prev->Dx)
+ {
+ locMin.LeftBound = E->Prev;
+ locMin.RightBound = E;
+ leftBoundIsForward = false; //Q.nextInLML = Q.prev
+ } else
+ {
+ locMin.LeftBound = E;
+ locMin.RightBound = E->Prev;
+ leftBoundIsForward = true; //Q.nextInLML = Q.next
+ }
+ locMin.LeftBound->Side = esLeft;
+ locMin.RightBound->Side = 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, leftBoundIsForward);
+ if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward);
+
+ TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
+ if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward);
+
+ if (locMin.LeftBound->OutIdx == Skip)
+ locMin.LeftBound = 0;
+ else if (locMin.RightBound->OutIdx == Skip)
+ locMin.RightBound = 0;
+ m_MinimaList.push_back(locMin);
+ if (!leftBoundIsForward) E = E2;
+ }
+ return true;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed)
+{
+ bool result = false;
+ for (Paths::size_type i = 0; i < ppg.size(); ++i)
+ if (AddPath(ppg[i], PolyTyp, Closed)) result = true;
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Clear()
+{
+ DisposeLocalMinimaList();
+ for (EdgeList::size_type i = 0; i < m_edges.size(); ++i)
+ {
+ //for each edge array in turn, find the first used edge and
+ //check for and remove any hiddenPts in each edge in the array.
+ TEdge* edges = m_edges[i];
+ delete [] edges;
+ }
+ m_edges.clear();
+ m_UseFullRange = false;
+ m_HasOpenPaths = false;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Reset()
+{
+ m_CurrentLM = m_MinimaList.begin();
+ if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process
+ std::stable_sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
+
+ //reset all edges ...
+ for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm)
+ {
+ TEdge* e = lm->LeftBound;
+ if (e)
+ {
+ e->Curr = e->Bot;
+ e->Side = esLeft;
+ e->OutIdx = Unassigned;
+ }
+
+ e = lm->RightBound;
+ if (e)
+ {
+ e->Curr = e->Bot;
+ e->Side = esRight;
+ e->OutIdx = Unassigned;
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeLocalMinimaList()
+{
+ m_MinimaList.clear();
+ m_CurrentLM = m_MinimaList.begin();
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::PopLocalMinima()
+{
+ if (m_CurrentLM == m_MinimaList.end()) return;
+ ++m_CurrentLM;
+}
+//------------------------------------------------------------------------------
+
+IntRect ClipperBase::GetBounds()
+{
+ IntRect result;
+ MinimaList::iterator lm = m_MinimaList.begin();
+ if (lm == m_MinimaList.end())
+ {
+ result.left = result.top = result.right = result.bottom = 0;
+ return result;
+ }
+ result.left = lm->LeftBound->Bot.X;
+ result.top = lm->LeftBound->Bot.Y;
+ result.right = lm->LeftBound->Bot.X;
+ result.bottom = lm->LeftBound->Bot.Y;
+ while (lm != m_MinimaList.end())
+ {
+ result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
+ TEdge* e = lm->LeftBound;
+ for (;;) {
+ TEdge* bottomE = e;
+ while (e->NextInLML)
+ {
+ if (e->Bot.X < result.left) result.left = e->Bot.X;
+ if (e->Bot.X > result.right) result.right = e->Bot.X;
+ e = e->NextInLML;
+ }
+ result.left = std::min(result.left, e->Bot.X);
+ result.right = std::max(result.right, e->Bot.X);
+ result.left = std::min(result.left, e->Top.X);
+ result.right = std::max(result.right, e->Top.X);
+ result.top = std::min(result.top, e->Top.Y);
+ if (bottomE == lm->LeftBound) e = lm->RightBound;
+ else break;
+ }
+ ++lm;
+ }
+ return result;
+}
+
+//------------------------------------------------------------------------------
+// TClipper methods ...
+//------------------------------------------------------------------------------
+
+Clipper::Clipper(int initOptions) : ClipperBase() //constructor
+{
+ m_ActiveEdges = 0;
+ m_SortedEdges = 0;
+ m_ExecuteLocked = false;
+ m_UseFullRange = false;
+ m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
+ m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
+ m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
+ m_HasOpenPaths = false;
+#ifdef use_xyz
+ m_ZFill = 0;
+#endif
+}
+//------------------------------------------------------------------------------
+
+Clipper::~Clipper() //destructor
+{
+ Clear();
+}
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+void Clipper::ZFillFunction(ZFillCallback zFillFunc)
+{
+ m_ZFill = zFillFunc;
+}
+//------------------------------------------------------------------------------
+#endif
+
+void Clipper::Reset()
+{
+ ClipperBase::Reset();
+ m_Scanbeam = ScanbeamList();
+ m_Maxima = MaximaList();
+ m_ActiveEdges = 0;
+ m_SortedEdges = 0;
+ for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm)
+ InsertScanbeam(lm->Y);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType)
+{
+ return Execute(clipType, solution, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType)
+{
+ return Execute(clipType, polytree, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, Paths &solution,
+ PolyFillType subjFillType, PolyFillType clipFillType)
+{
+ if( m_ExecuteLocked ) return false;
+ if (m_HasOpenPaths)
+ throw clipperException("Error: PolyTree struct is needed for open path clipping.");
+ m_ExecuteLocked = true;
+ solution.resize(0);
+ m_SubjFillType = subjFillType;
+ m_ClipFillType = clipFillType;
+ m_ClipType = clipType;
+ m_UsingPolyTree = false;
+ bool succeeded = ExecuteInternal();
+ if (succeeded) BuildResult(solution);
+ DisposeAllOutRecs();
+ m_ExecuteLocked = false;
+ return succeeded;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::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();
+ if (succeeded) BuildResult2(polytree);
+ DisposeAllOutRecs();
+ m_ExecuteLocked = false;
+ return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixHoleLinkage(OutRec &outrec)
+{
+ //skip OutRecs that (a) contain outermost polygons or
+ //(b) already have the correct owner/child linkage ...
+ if (!outrec.FirstLeft ||
+ (outrec.IsHole != outrec.FirstLeft->IsHole &&
+ outrec.FirstLeft->Pts)) return;
+
+ OutRec* orfl = outrec.FirstLeft;
+ while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
+ orfl = orfl->FirstLeft;
+ outrec.FirstLeft = orfl;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ExecuteInternal()
+{
+ bool succeeded = true;
+ try {
+ Reset();
+ if (m_CurrentLM == m_MinimaList.end()) return true;
+ cInt botY = PopScanbeam();
+ do {
+ InsertLocalMinimaIntoAEL(botY);
+ ProcessHorizontals();
+ ClearGhostJoins();
+ if (m_Scanbeam.empty()) break;
+ cInt topY = PopScanbeam();
+ succeeded = ProcessIntersections(topY);
+ if (!succeeded) break;
+ ProcessEdgesAtTopOfScanbeam(topY);
+ botY = topY;
+ } while (!m_Scanbeam.empty() || m_CurrentLM != m_MinimaList.end());
+ }
+ catch(std::exception const&)
+ {
+ succeeded = false;
+ }
+
+ if (succeeded)
+ {
+ //fix orientations ...
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec *outRec = m_PolyOuts[i];
+ if (!outRec->Pts || outRec->IsOpen) continue;
+ if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
+ ReversePolyPtLinks(outRec->Pts);
+ }
+
+ if (!m_Joins.empty()) JoinCommonEdges();
+
+ //unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec *outRec = m_PolyOuts[i];
+ if (!outRec->Pts) continue;
+ if (outRec->IsOpen)
+ FixupOutPolyline(*outRec);
+ else
+ FixupOutPolygon(*outRec);
+ }
+
+ if (m_StrictSimple) DoSimplePolygons();
+ }
+
+ ClearJoins();
+ ClearGhostJoins();
+ return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertScanbeam(const cInt Y)
+{
+ m_Scanbeam.push(Y);
+}
+//------------------------------------------------------------------------------
+
+cInt Clipper::PopScanbeam()
+{
+ const cInt Y = m_Scanbeam.top();
+ m_Scanbeam.pop();
+ while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates.
+ return Y;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeAllOutRecs(){
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ DisposeOutRec(i);
+ m_PolyOuts.clear();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeOutRec(PolyOutList::size_type index)
+{
+ OutRec *outRec = m_PolyOuts[index];
+ if (outRec->Pts) DisposeOutPts(outRec->Pts);
+ delete outRec;
+ m_PolyOuts[index] = 0;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetWindingCount(TEdge &edge)
+{
+ TEdge *e = edge.PrevInAEL;
+ //find the edge of the same polytype that immediately preceeds 'edge' in AEL
+ while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL;
+ if (!e)
+ {
+ 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 != 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)
+ {
+ 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 (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;
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddFillType(const TEdge& edge) const
+{
+ if (edge.PolyTyp == ptSubject)
+ return m_SubjFillType == pftEvenOdd; else
+ return m_ClipFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const
+{
+ if (edge.PolyTyp == ptSubject)
+ return m_ClipFillType == pftEvenOdd; else
+ return m_SubjFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsContributing(const TEdge& edge) const
+{
+ PolyFillType pft, pft2;
+ if (edge.PolyTyp == ptSubject)
+ {
+ pft = m_SubjFillType;
+ pft2 = m_ClipFillType;
+ } else
+ {
+ pft = m_ClipFillType;
+ pft2 = m_SubjFillType;
+ }
+
+ switch(pft)
+ {
+ case 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 pftNonZero:
+ if (Abs(edge.WindCnt) != 1) return false;
+ break;
+ case pftPositive:
+ if (edge.WindCnt != 1) return false;
+ break;
+ default: //pftNegative
+ if (edge.WindCnt != -1) return false;
+ }
+
+ switch(m_ClipType)
+ {
+ case ctIntersection:
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.WindCnt2 != 0);
+ case pftPositive:
+ return (edge.WindCnt2 > 0);
+ default:
+ return (edge.WindCnt2 < 0);
+ }
+ break;
+ case ctUnion:
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.WindCnt2 == 0);
+ case pftPositive:
+ return (edge.WindCnt2 <= 0);
+ default:
+ return (edge.WindCnt2 >= 0);
+ }
+ break;
+ case ctDifference:
+ if (edge.PolyTyp == ptSubject)
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.WindCnt2 == 0);
+ case pftPositive:
+ return (edge.WindCnt2 <= 0);
+ default:
+ return (edge.WindCnt2 >= 0);
+ }
+ else
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.WindCnt2 != 0);
+ case pftPositive:
+ return (edge.WindCnt2 > 0);
+ default:
+ return (edge.WindCnt2 < 0);
+ }
+ break;
+ case ctXor:
+ if (edge.WindDelta == 0) //XOr always contributing unless open
+ switch(pft2)
+ {
+ case pftEvenOdd:
+ case pftNonZero:
+ return (edge.WindCnt2 == 0);
+ case pftPositive:
+ return (edge.WindCnt2 <= 0);
+ default:
+ return (edge.WindCnt2 >= 0);
+ }
+ else
+ return true;
+ break;
+ default:
+ return true;
+ }
+}
+//------------------------------------------------------------------------------
+
+OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt)
+{
+ OutPt* result;
+ TEdge *e, *prevE;
+ if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx ))
+ {
+ result = AddOutPt(e1, Pt);
+ e2->OutIdx = e1->OutIdx;
+ e1->Side = esLeft;
+ e2->Side = 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 = esRight;
+ e2->Side = esLeft;
+ e = e2;
+ if (e->PrevInAEL == e1)
+ prevE = e1->PrevInAEL;
+ else
+ prevE = e->PrevInAEL;
+ }
+
+ if (prevE && 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;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const 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);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::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 )
+ {
+ m_SortedEdges = edge;
+ edge->PrevInSEL = 0;
+ edge->NextInSEL = 0;
+ }
+ else
+ {
+ edge->NextInSEL = m_SortedEdges;
+ edge->PrevInSEL = 0;
+ m_SortedEdges->PrevInSEL = edge;
+ m_SortedEdges = edge;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::CopyAELToSEL()
+{
+ TEdge* e = m_ActiveEdges;
+ m_SortedEdges = e;
+ while ( e )
+ {
+ e->PrevInSEL = e->PrevInAEL;
+ e->NextInSEL = e->NextInAEL;
+ e = e->NextInAEL;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt)
+{
+ Join* j = new Join;
+ j->OutPt1 = op1;
+ j->OutPt2 = op2;
+ j->OffPt = OffPt;
+ m_Joins.push_back(j);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearJoins()
+{
+ for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+ delete m_Joins[i];
+ m_Joins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearGhostJoins()
+{
+ for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
+ delete m_GhostJoins[i];
+ m_GhostJoins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt)
+{
+ Join* j = new Join;
+ j->OutPt1 = op;
+ j->OutPt2 = 0;
+ j->OffPt = OffPt;
+ m_GhostJoins.push_back(j);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertLocalMinimaIntoAEL(const cInt botY)
+{
+ while (m_CurrentLM != m_MinimaList.end() && (m_CurrentLM->Y == botY))
+ {
+ TEdge* lb = m_CurrentLM->LeftBound;
+ TEdge* rb = m_CurrentLM->RightBound;
+ PopLocalMinima();
+ OutPt *Op1 = 0;
+ if (!lb)
+ {
+ //nb: don't insert LB into either AEL or SEL
+ InsertEdgeIntoAEL(rb, 0);
+ SetWindingCount(*rb);
+ if (IsContributing(*rb))
+ Op1 = AddOutPt(rb, rb->Bot);
+ }
+ else if (!rb)
+ {
+ InsertEdgeIntoAEL(lb, 0);
+ SetWindingCount(*lb);
+ if (IsContributing(*lb))
+ Op1 = AddOutPt(lb, lb->Bot);
+ InsertScanbeam(lb->Top.Y);
+ }
+ else
+ {
+ InsertEdgeIntoAEL(lb, 0);
+ 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)
+ {
+ if(IsHorizontal(*rb)) AddEdgeToSEL(rb);
+ else InsertScanbeam( rb->Top.Y );
+ }
+
+ if (!lb || !rb) continue;
+
+ //if any output polygons share an edge, they'll need joining later ...
+ if (Op1 && IsHorizontal(*rb) &&
+ !m_GhostJoins.empty() && (rb->WindDelta != 0))
+ {
+ for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i)
+ {
+ Join* jr = m_GhostJoins[i];
+ //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
+ //the 'ghost' join to a real join ready for later ...
+ if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, rb->Top.X))
+ AddJoin(jr->OutPt1, Op1, jr->OffPt);
+ }
+ }
+
+ if (lb->OutIdx >= 0 && lb->PrevInAEL &&
+ 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)
+ {
+ 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;
+ }
+ }
+ }
+
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DeleteFromAEL(TEdge *e)
+{
+ TEdge* AelPrev = e->PrevInAEL;
+ TEdge* AelNext = e->NextInAEL;
+ if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted
+ if( AelPrev ) AelPrev->NextInAEL = AelNext;
+ else m_ActiveEdges = AelNext;
+ if( AelNext ) AelNext->PrevInAEL = AelPrev;
+ e->NextInAEL = 0;
+ e->PrevInAEL = 0;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DeleteFromSEL(TEdge *e)
+{
+ TEdge* SelPrev = e->PrevInSEL;
+ TEdge* SelNext = e->NextInSEL;
+ if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted
+ if( SelPrev ) SelPrev->NextInSEL = SelNext;
+ else m_SortedEdges = SelNext;
+ if( SelNext ) SelNext->PrevInSEL = SelPrev;
+ e->NextInSEL = 0;
+ e->PrevInSEL = 0;
+}
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2)
+{
+ if (pt.Z != 0 || !m_ZFill) return;
+ else if (pt == e1.Bot) pt.Z = e1.Bot.Z;
+ else if (pt == e1.Top) pt.Z = e1.Top.Z;
+ else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
+ else if (pt == e2.Top) pt.Z = e2.Top.Z;
+ else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
+}
+//------------------------------------------------------------------------------
+#endif
+
+void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt)
+{
+ bool e1Contributing = ( e1->OutIdx >= 0 );
+ bool e2Contributing = ( e2->OutIdx >= 0 );
+
+#ifdef use_xyz
+ SetZ(Pt, *e1, *e2);
+#endif
+
+#ifdef 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) return;
+
+ //if intersecting a subj line with a subj poly ...
+ else if (e1->PolyTyp == e2->PolyTyp &&
+ e1->WindDelta != e2->WindDelta && m_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)
+ {
+ //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
+ if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
+ (m_ClipType != ctUnion || e2->WindCnt2 == 0))
+ {
+ AddOutPt(e1, Pt);
+ if (e1Contributing) e1->OutIdx = Unassigned;
+ }
+ else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
+ (m_ClipType != ctUnion || e1->WindCnt2 == 0))
+ {
+ AddOutPt(e2, Pt);
+ if (e2Contributing) e2->OutIdx = Unassigned;
+ }
+ }
+ 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 == ptSubject)
+ {
+ e1FillType = m_SubjFillType;
+ e1FillType2 = m_ClipFillType;
+ } else
+ {
+ e1FillType = m_ClipFillType;
+ e1FillType2 = m_SubjFillType;
+ }
+ if (e2->PolyTyp == ptSubject)
+ {
+ e2FillType = m_SubjFillType;
+ e2FillType2 = m_ClipFillType;
+ } else
+ {
+ e2FillType = m_ClipFillType;
+ e2FillType2 = m_SubjFillType;
+ }
+
+ cInt e1Wc, e2Wc;
+ switch (e1FillType)
+ {
+ case pftPositive: e1Wc = e1->WindCnt; break;
+ case pftNegative: e1Wc = -e1->WindCnt; break;
+ default: e1Wc = Abs(e1->WindCnt);
+ }
+ switch(e2FillType)
+ {
+ case pftPositive: e2Wc = e2->WindCnt; break;
+ case pftNegative: e2Wc = -e2->WindCnt; break;
+ default: e2Wc = Abs(e2->WindCnt);
+ }
+
+ if ( e1Contributing && e2Contributing )
+ {
+ if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
+ (e1->PolyTyp != e2->PolyTyp && m_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))
+ {
+ //neither edge is currently contributing ...
+
+ cInt e1Wc2, e2Wc2;
+ switch (e1FillType2)
+ {
+ case pftPositive: e1Wc2 = e1->WindCnt2; break;
+ case pftNegative : e1Wc2 = -e1->WindCnt2; break;
+ default: e1Wc2 = Abs(e1->WindCnt2);
+ }
+ switch (e2FillType2)
+ {
+ case pftPositive: e2Wc2 = e2->WindCnt2; break;
+ case pftNegative: e2Wc2 = -e2->WindCnt2; break;
+ default: e2Wc2 = Abs(e2->WindCnt2);
+ }
+
+ if (e1->PolyTyp != e2->PolyTyp)
+ {
+ AddLocalMinPoly(e1, e2, Pt);
+ }
+ else if (e1Wc == 1 && e2Wc == 1)
+ switch( m_ClipType ) {
+ case ctIntersection:
+ if (e1Wc2 > 0 && e2Wc2 > 0)
+ AddLocalMinPoly(e1, e2, Pt);
+ break;
+ case ctUnion:
+ if ( e1Wc2 <= 0 && e2Wc2 <= 0 )
+ AddLocalMinPoly(e1, e2, Pt);
+ break;
+ case ctDifference:
+ if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+ ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+ AddLocalMinPoly(e1, e2, Pt);
+ break;
+ case ctXor:
+ AddLocalMinPoly(e1, e2, Pt);
+ }
+ else
+ SwapSides( *e1, *e2 );
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetHoleState(TEdge *e, OutRec *outrec)
+{
+ bool IsHole = false;
+ TEdge *e2 = e->PrevInAEL;
+ while (e2)
+ {
+ if (e2->OutIdx >= 0 && e2->WindDelta != 0)
+ {
+ IsHole = !IsHole;
+ if (! outrec->FirstLeft)
+ outrec->FirstLeft = m_PolyOuts[e2->OutIdx];
+ }
+ e2 = e2->PrevInAEL;
+ }
+ if (IsHole) outrec->IsHole = true;
+}
+//------------------------------------------------------------------------------
+
+OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2)
+{
+ //work out which polygon fragment has the correct hole state ...
+ if (!outRec1->BottomPt)
+ outRec1->BottomPt = GetBottomPt(outRec1->Pts);
+ if (!outRec2->BottomPt)
+ outRec2->BottomPt = GetBottomPt(outRec2->Pts);
+ OutPt *OutPt1 = outRec1->BottomPt;
+ OutPt *OutPt2 = outRec2->BottomPt;
+ if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1;
+ else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2;
+ else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1;
+ else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2;
+ else if (OutPt1->Next == OutPt1) return outRec2;
+ else if (OutPt2->Next == OutPt2) return outRec1;
+ else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1;
+ else return outRec2;
+}
+//------------------------------------------------------------------------------
+
+bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2)
+{
+ do
+ {
+ outRec1 = outRec1->FirstLeft;
+ if (outRec1 == outRec2) return true;
+ } while (outRec1);
+ return false;
+}
+//------------------------------------------------------------------------------
+
+OutRec* Clipper::GetOutRec(int Idx)
+{
+ OutRec* outrec = m_PolyOuts[Idx];
+ while (outrec != m_PolyOuts[outrec->Idx])
+ outrec = m_PolyOuts[outrec->Idx];
+ return outrec;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::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);
+
+ //get the start and ends of both output polygons and
+ //join e2 poly onto e1 poly and delete pointers to e2 ...
+
+ 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 == esLeft )
+ {
+ if( e2->Side == 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 = esLeft;
+ } else
+ {
+ if( e2->Side == 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 = esRight;
+ }
+
+ outRec1->BottomPt = 0;
+ if (holeStateRec == outRec2)
+ {
+ if (outRec2->FirstLeft != outRec1)
+ outRec1->FirstLeft = outRec2->FirstLeft;
+ outRec1->IsHole = outRec2->IsHole;
+ }
+ outRec2->Pts = 0;
+ outRec2->BottomPt = 0;
+ 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 )
+ {
+ if( e->OutIdx == ObsoleteIdx )
+ {
+ e->OutIdx = OKIdx;
+ e->Side = Side;
+ break;
+ }
+ e = e->NextInAEL;
+ }
+
+ outRec2->Idx = outRec1->Idx;
+}
+//------------------------------------------------------------------------------
+
+OutRec* Clipper::CreateOutRec()
+{
+ OutRec* result = new OutRec;
+ result->IsHole = false;
+ result->IsOpen = false;
+ result->FirstLeft = 0;
+ result->Pts = 0;
+ result->BottomPt = 0;
+ result->PolyNd = 0;
+ m_PolyOuts.push_back(result);
+ result->Idx = (int)m_PolyOuts.size()-1;
+ return result;
+}
+//------------------------------------------------------------------------------
+
+OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt)
+{
+ 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);
+ e->OutIdx = outRec->Idx;
+ 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;
+
+ bool ToFront = (e->Side == esLeft);
+ 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;
+ return newOp;
+ }
+}
+//------------------------------------------------------------------------------
+
+OutPt* Clipper::GetLastOutPt(TEdge *e)
+{
+ OutRec *outRec = m_PolyOuts[e->OutIdx];
+ if (e->Side == esLeft)
+ return outRec->Pts;
+ else
+ return outRec->Pts->Prev;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessHorizontals()
+{
+ TEdge* horzEdge = m_SortedEdges;
+ while(horzEdge)
+ {
+ DeleteFromSEL(horzEdge);
+ ProcessHorizontal(horzEdge);
+ horzEdge = m_SortedEdges;
+ }
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMinima(TEdge *e)
+{
+ return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMaxima(TEdge *e, const cInt Y)
+{
+ return e && e->Top.Y == Y && !e->NextInLML;
+}
+//------------------------------------------------------------------------------
+
+inline bool IsIntermediate(TEdge *e, const cInt Y)
+{
+ return e->Top.Y == Y && e->NextInLML;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetMaximaPair(TEdge *e)
+{
+ TEdge* result = 0;
+ if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
+ result = e->Next;
+ else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
+ result = e->Prev;
+
+ if (result && (result->OutIdx == Skip ||
+ //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ...
+ (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
+ return 0;
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::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 ) Next->PrevInAEL = Edge1;
+ TEdge* Prev = Edge1->PrevInAEL;
+ if( Prev ) 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 ) Next->PrevInAEL = Edge2;
+ TEdge* Prev = Edge2->PrevInAEL;
+ if( Prev ) 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 ) Edge1->NextInAEL->PrevInAEL = Edge1;
+ Edge1->PrevInAEL = Edge2->PrevInAEL;
+ if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1;
+ Edge2->NextInAEL = Next;
+ if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2;
+ Edge2->PrevInAEL = Prev;
+ if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2;
+ }
+
+ if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1;
+ else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2)
+{
+ if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return;
+ if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return;
+
+ if( Edge1->NextInSEL == Edge2 )
+ {
+ TEdge* Next = Edge2->NextInSEL;
+ if( Next ) Next->PrevInSEL = Edge1;
+ TEdge* Prev = Edge1->PrevInSEL;
+ if( Prev ) 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 ) Next->PrevInSEL = Edge2;
+ TEdge* Prev = Edge2->PrevInSEL;
+ if( Prev ) 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 ) Edge1->NextInSEL->PrevInSEL = Edge1;
+ Edge1->PrevInSEL = Edge2->PrevInSEL;
+ if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1;
+ Edge2->NextInSEL = Next;
+ if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2;
+ Edge2->PrevInSEL = Prev;
+ if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2;
+ }
+
+ if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1;
+ else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2;
+}
+//------------------------------------------------------------------------------
+
+TEdge* GetNextInAEL(TEdge *e, Direction dir)
+{
+ return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
+}
+//------------------------------------------------------------------------------
+
+void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right)
+{
+ if (HorzEdge.Bot.X < HorzEdge.Top.X)
+ {
+ Left = HorzEdge.Bot.X;
+ Right = HorzEdge.Top.X;
+ Dir = dLeftToRight;
+ } else
+ {
+ Left = HorzEdge.Top.X;
+ Right = HorzEdge.Bot.X;
+ Dir = dRightToLeft;
+ }
+}
+//------------------------------------------------------------------------
+
+/*******************************************************************************
+* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or *
+* Bottom of a scanbeam) are processed as if layered. The order in which HEs *
+* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] *
+* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), *
+* and with other non-horizontal edges [*]. Once these intersections are *
+* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into *
+* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
+*******************************************************************************/
+
+void Clipper::ProcessHorizontal(TEdge *horzEdge)
+{
+ Direction dir;
+ cInt horzLeft, horzRight;
+ bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen);
+
+ GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+ TEdge* eLastHorz = horzEdge, *eMaxPair = 0;
+ while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
+ eLastHorz = eLastHorz->NextInLML;
+ if (!eLastHorz->NextInLML)
+ eMaxPair = GetMaximaPair(eLastHorz);
+
+ MaximaList::const_iterator maxIt;
+ MaximaList::const_reverse_iterator maxRit;
+ if (!m_Maxima.empty())
+ {
+ //get the first maxima in range (X) ...
+ if (dir == dLeftToRight)
+ {
+ maxIt = m_Maxima.begin();
+ while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++;
+ if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
+ maxIt = m_Maxima.end();
+ }
+ else
+ {
+ maxRit = m_Maxima.rbegin();
+ while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++;
+ if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
+ maxRit = m_Maxima.rend();
+ }
+ }
+
+ OutPt* op1 = 0;
+
+ for (;;) //loop through consec. horizontal edges
+ {
+
+ bool IsLastHorz = (horzEdge == eLastHorz);
+ TEdge* e = GetNextInAEL(horzEdge, dir);
+ while(e)
+ {
+
+ //this code block inserts extra coords into horizontal edges (in output
+ //polygons) whereever maxima touch these horizontal edges. This helps
+ //'simplifying' polygons (ie if the Simplify property is set).
+ if (!m_Maxima.empty())
+ {
+ if (dir == dLeftToRight)
+ {
+ while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X)
+ {
+ if (horzEdge->OutIdx >= 0 && !IsOpen)
+ AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
+ maxIt++;
+ }
+ }
+ else
+ {
+ while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X)
+ {
+ if (horzEdge->OutIdx >= 0 && !IsOpen)
+ AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
+ maxRit++;
+ }
+ }
+ };
+
+ if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
+ (dir == dRightToLeft && e->Curr.X < horzLeft)) break;
+
+ //Also 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 &&
+ e->Dx < horzEdge->NextInLML->Dx) break;
+
+ if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times
+ {
+ op1 = AddOutPt(horzEdge, e->Curr);
+ TEdge* eNextHorz = m_SortedEdges;
+ while (eNextHorz)
+ {
+ if (eNextHorz->OutIdx >= 0 &&
+ HorzSegmentsOverlap(horzEdge->Bot.X,
+ horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
+ {
+ OutPt* op2 = GetLastOutPt(eNextHorz);
+ AddJoin(op2, op1, eNextHorz->Top);
+ }
+ eNextHorz = eNextHorz->NextInSEL;
+ }
+ AddGhostJoin(op1, horzEdge->Bot);
+ }
+
+ //OK, 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 (horzEdge->OutIdx >= 0)
+ AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
+ DeleteFromAEL(horzEdge);
+ DeleteFromAEL(eMaxPair);
+ return;
+ }
+
+ if(dir == dLeftToRight)
+ {
+ IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+ IntersectEdges(horzEdge, e, Pt);
+ }
+ else
+ {
+ IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+ IntersectEdges( e, horzEdge, Pt);
+ }
+ TEdge* eNext = GetNextInAEL(e, dir);
+ SwapPositionsInAEL( horzEdge, e );
+ e = eNext;
+ } //end while(e)
+
+ //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+ if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break;
+
+ UpdateEdgeIntoAEL(horzEdge);
+ if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot);
+ GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+ } //end for (;;)
+
+ if (horzEdge->OutIdx >= 0 && !op1)
+ {
+ op1 = GetLastOutPt(horzEdge);
+ TEdge* eNextHorz = m_SortedEdges;
+ while (eNextHorz)
+ {
+ if (eNextHorz->OutIdx >= 0 &&
+ HorzSegmentsOverlap(horzEdge->Bot.X,
+ horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X))
+ {
+ OutPt* op2 = GetLastOutPt(eNextHorz);
+ AddJoin(op2, op1, eNextHorz->Top);
+ }
+ eNextHorz = eNextHorz->NextInSEL;
+ }
+ AddGhostJoin(op1, horzEdge->Top);
+ }
+
+ if (horzEdge->NextInLML)
+ {
+ if(horzEdge->OutIdx >= 0)
+ {
+ op1 = AddOutPt( horzEdge, horzEdge->Top);
+ UpdateEdgeIntoAEL(horzEdge);
+ if (horzEdge->WindDelta == 0) return;
+ //nb: HorzEdge is no longer horizontal here
+ TEdge* ePrev = horzEdge->PrevInAEL;
+ TEdge* eNext = horzEdge->NextInAEL;
+ if (ePrev && 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 && 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(horzEdge);
+ }
+ else
+ {
+ if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top);
+ DeleteFromAEL(horzEdge);
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::UpdateEdgeIntoAEL(TEdge *&e)
+{
+ if( !e->NextInLML )
+ throw clipperException("UpdateEdgeIntoAEL: invalid call");
+
+ e->NextInLML->OutIdx = e->OutIdx;
+ TEdge* AelPrev = e->PrevInAEL;
+ TEdge* AelNext = e->NextInAEL;
+ if (AelPrev) AelPrev->NextInAEL = e->NextInLML;
+ else m_ActiveEdges = e->NextInLML;
+ if (AelNext) 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);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ProcessIntersections(const cInt topY)
+{
+ if( !m_ActiveEdges ) return true;
+ try {
+ BuildIntersectList(topY);
+ size_t IlSize = m_IntersectList.size();
+ if (IlSize == 0) return true;
+ if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList();
+ else return false;
+ }
+ catch(std::exception const& ex)
+ {
+ m_SortedEdges = 0;
+ DisposeIntersectNodes();
+ throw clipperException((std::string("ProcessIntersections error ") + ex.what()).c_str());
+ }
+ m_SortedEdges = 0;
+ return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeIntersectNodes()
+{
+ for (size_t i = 0; i < m_IntersectList.size(); ++i )
+ delete m_IntersectList[i];
+ m_IntersectList.clear();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildIntersectList(const cInt topY)
+{
+ if ( !m_ActiveEdges ) return;
+
+ //prepare for sorting ...
+ TEdge* e = m_ActiveEdges;
+ m_SortedEdges = e;
+ while( e )
+ {
+ e->PrevInSEL = e->PrevInAEL;
+ e->NextInSEL = e->NextInAEL;
+ e->Curr.X = TopX( *e, topY );
+ e = e->NextInAEL;
+ }
+
+ //bubblesort ...
+ bool isModified;
+ do
+ {
+ isModified = false;
+ e = m_SortedEdges;
+ while( e->NextInSEL )
+ {
+ TEdge *eNext = e->NextInSEL;
+ IntPoint Pt;
+ if(e->Curr.X > eNext->Curr.X)
+ {
+ IntersectPoint(*e, *eNext, Pt);
+ IntersectNode * newNode = new IntersectNode;
+ newNode->Edge1 = e;
+ newNode->Edge2 = eNext;
+ newNode->Pt = Pt;
+ m_IntersectList.push_back(newNode);
+
+ SwapPositionsInSEL(e, eNext);
+ isModified = true;
+ }
+ else
+ e = eNext;
+ }
+ if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0;
+ else break;
+ }
+ while ( isModified );
+ m_SortedEdges = 0; //important
+}
+//------------------------------------------------------------------------------
+
+
+void Clipper::ProcessIntersectList()
+{
+ for (size_t i = 0; i < m_IntersectList.size(); ++i)
+ {
+ IntersectNode* iNode = m_IntersectList[i];
+ {
+ IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt);
+ SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 );
+ }
+ delete iNode;
+ }
+ m_IntersectList.clear();
+}
+//------------------------------------------------------------------------------
+
+bool IntersectListSort(IntersectNode* node1, IntersectNode* node2)
+{
+ return node2->Pt.Y < node1->Pt.Y;
+}
+//------------------------------------------------------------------------------
+
+inline bool EdgesAdjacent(const IntersectNode &inode)
+{
+ return (inode.Edge1->NextInSEL == inode.Edge2) ||
+ (inode.Edge1->PrevInSEL == inode.Edge2);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::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 ...
+ CopyAELToSEL();
+ std::stable_sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
+ size_t cnt = m_IntersectList.size();
+ for (size_t i = 0; i < cnt; ++i)
+ {
+ if (!EdgesAdjacent(*m_IntersectList[i]))
+ {
+ size_t j = i + 1;
+ while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++;
+ if (j == cnt) return false;
+ std::swap(m_IntersectList[i], m_IntersectList[j]);
+ }
+ SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
+ }
+ return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DoMaxima(TEdge *e)
+{
+ TEdge* eMaxPair = GetMaximaPair(e);
+ if (!eMaxPair)
+ {
+ if (e->OutIdx >= 0)
+ AddOutPt(e, e->Top);
+ DeleteFromAEL(e);
+ return;
+ }
+
+ TEdge* eNext = e->NextInAEL;
+ while(eNext && eNext != eMaxPair)
+ {
+ IntersectEdges(e, eNext, e->Top);
+ 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 )
+ {
+ if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top);
+ DeleteFromAEL(e);
+ DeleteFromAEL(eMaxPair);
+ }
+#ifdef 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 clipperException("DoMaxima error");
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
+{
+ TEdge* e = m_ActiveEdges;
+ while( e )
+ {
+ //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 || !IsHorizontal(*eMaxPair));
+ }
+
+ if(IsMaximaEdge)
+ {
+ if (m_StrictSimple) m_Maxima.push_back(e->Top.X);
+ TEdge* ePrev = e->PrevInAEL;
+ DoMaxima(e);
+ if( !ePrev ) 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(e);
+ if (e->OutIdx >= 0)
+ AddOutPt(e, e->Bot);
+ AddEdgeToSEL(e);
+ }
+ else
+ {
+ e->Curr.X = TopX( *e, topY );
+ e->Curr.Y = topY;
+ }
+
+ //When StrictlySimple and 'e' is being touched by another edge, then
+ //make sure both edges have a vertex here ...
+ if (m_StrictSimple)
+ {
+ TEdge* ePrev = e->PrevInAEL;
+ if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) &&
+ (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0))
+ {
+ IntPoint pt = e->Curr;
+#ifdef use_xyz
+ SetZ(pt, *ePrev, *e);
+#endif
+ OutPt* op = AddOutPt(ePrev, pt);
+ OutPt* op2 = AddOutPt(e, pt);
+ AddJoin(op, op2, pt); //StrictlySimple (type-3) join
+ }
+ }
+
+ e = e->NextInAEL;
+ }
+ }
+
+ //3. Process horizontals at the Top of the scanbeam ...
+ m_Maxima.sort();
+ ProcessHorizontals();
+ m_Maxima.clear();
+
+ //4. Promote intermediate vertices ...
+ e = m_ActiveEdges;
+ while(e)
+ {
+ if(IsIntermediate(e, topY))
+ {
+ OutPt* op = 0;
+ if( e->OutIdx >= 0 )
+ op = AddOutPt(e, e->Top);
+ UpdateEdgeIntoAEL(e);
+
+ //if output polygons share an edge, they'll need joining later ...
+ TEdge* ePrev = e->PrevInAEL;
+ TEdge* eNext = e->NextInAEL;
+ if (ePrev && ePrev->Curr.X == e->Bot.X &&
+ ePrev->Curr.Y == e->Bot.Y && op &&
+ 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 && eNext->Curr.X == e->Bot.X &&
+ eNext->Curr.Y == e->Bot.Y && op &&
+ 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;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolyline(OutRec &outrec)
+{
+ OutPt *pp = outrec.Pts;
+ OutPt *lastPP = pp->Prev;
+ while (pp != lastPP)
+ {
+ pp = pp->Next;
+ if (pp->Pt == pp->Prev->Pt)
+ {
+ if (pp == lastPP) lastPP = pp->Prev;
+ OutPt *tmpPP = pp->Prev;
+ tmpPP->Next = pp->Next;
+ pp->Next->Prev = tmpPP;
+ delete pp;
+ pp = tmpPP;
+ }
+ }
+
+ if (pp == pp->Prev)
+ {
+ DisposeOutPts(pp);
+ outrec.Pts = 0;
+ return;
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolygon(OutRec &outrec)
+{
+ //FixupOutPolygon() - removes duplicate points and simplifies consecutive
+ //parallel edges by removing the middle vertex.
+ OutPt *lastOK = 0;
+ outrec.BottomPt = 0;
+ OutPt *pp = outrec.Pts;
+ bool preserveCol = m_PreserveCollinear || m_StrictSimple;
+
+ for (;;)
+ {
+ if (pp->Prev == pp || pp->Prev == pp->Next)
+ {
+ DisposeOutPts(pp);
+ outrec.Pts = 0;
+ 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) &&
+ (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt))))
+ {
+ lastOK = 0;
+ OutPt *tmp = pp;
+ pp->Prev->Next = pp->Next;
+ pp->Next->Prev = pp->Prev;
+ pp = pp->Prev;
+ delete tmp;
+ }
+ else if (pp == lastOK) break;
+ else
+ {
+ if (!lastOK) lastOK = pp;
+ pp = pp->Next;
+ }
+ }
+ outrec.Pts = pp;
+}
+//------------------------------------------------------------------------------
+
+int PointCount(OutPt *Pts)
+{
+ if (!Pts) return 0;
+ int result = 0;
+ OutPt* p = Pts;
+ do
+ {
+ result++;
+ p = p->Next;
+ }
+ while (p != Pts);
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult(Paths &polys)
+{
+ polys.reserve(m_PolyOuts.size());
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ if (!m_PolyOuts[i]->Pts) continue;
+ Path pg;
+ OutPt* p = m_PolyOuts[i]->Pts->Prev;
+ int cnt = PointCount(p);
+ if (cnt < 2) continue;
+ pg.reserve(cnt);
+ for (int j = 0; j < cnt; ++j)
+ {
+ pg.push_back(p->Pt);
+ p = p->Prev;
+ }
+ polys.push_back(pg);
+ }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult2(PolyTree& polytree)
+{
+ polytree.Clear();
+ polytree.AllNodes.reserve(m_PolyOuts.size());
+ //add each output polygon/contour to polytree ...
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); 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();
+ //nb: polytree takes ownership of all the PolyNodes
+ polytree.AllNodes.push_back(pn);
+ outRec->PolyNd = pn;
+ pn->Parent = 0;
+ pn->Index = 0;
+ pn->Contour.reserve(cnt);
+ OutPt *op = outRec->Pts->Prev;
+ for (int j = 0; j < cnt; j++)
+ {
+ pn->Contour.push_back(op->Pt);
+ op = op->Prev;
+ }
+ }
+
+ //fixup PolyNode links etc ...
+ polytree.Childs.reserve(m_PolyOuts.size());
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ if (!outRec->PolyNd) continue;
+ if (outRec->IsOpen)
+ {
+ outRec->PolyNd->m_IsOpen = true;
+ polytree.AddChild(*outRec->PolyNd);
+ }
+ else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
+ outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
+ else
+ polytree.AddChild(*outRec->PolyNd);
+ }
+}
+//------------------------------------------------------------------------------
+
+void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2)
+{
+ //just swap the contents (because fIntersectNodes is a single-linked-list)
+ IntersectNode inode = int1; //gets a copy of Int1
+ int1.Edge1 = int2.Edge1;
+ int1.Edge2 = int2.Edge2;
+ int1.Pt = int2.Pt;
+ int2.Edge1 = inode.Edge1;
+ int2.Edge2 = inode.Edge2;
+ int2.Pt = inode.Pt;
+}
+//------------------------------------------------------------------------------
+
+inline 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;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
+ cInt& Left, cInt& Right)
+{
+ if (a1 < a2)
+ {
+ if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);}
+ else {Left = std::max(a1,b2); Right = std::min(a2,b1);}
+ }
+ else
+ {
+ if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);}
+ else {Left = std::max(a2,b2); Right = std::min(a1,b1);}
+ }
+ return Left < Right;
+}
+//------------------------------------------------------------------------------
+
+inline void UpdateOutPtIdxs(OutRec& outrec)
+{
+ OutPt* op = outrec.Pts;
+ do
+ {
+ op->Idx = outrec.Idx;
+ op = op->Prev;
+ }
+ while(op != outrec.Pts);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge)
+{
+ if(!m_ActiveEdges)
+ {
+ edge->PrevInAEL = 0;
+ edge->NextInAEL = 0;
+ m_ActiveEdges = edge;
+ }
+ else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge))
+ {
+ edge->PrevInAEL = 0;
+ edge->NextInAEL = m_ActiveEdges;
+ m_ActiveEdges->PrevInAEL = edge;
+ m_ActiveEdges = edge;
+ }
+ else
+ {
+ if(!startEdge) startEdge = m_ActiveEdges;
+ while(startEdge->NextInAEL &&
+ !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge))
+ startEdge = startEdge->NextInAEL;
+ edge->NextInAEL = startEdge->NextInAEL;
+ if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge;
+ edge->PrevInAEL = startEdge;
+ startEdge->NextInAEL = edge;
+ }
+}
+//----------------------------------------------------------------------
+
+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 JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b,
+ const IntPoint Pt, bool DiscardLeft)
+{
+ Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
+ Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : 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 == 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 == 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 == 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;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::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 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. StrictSimple 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 ...
+ if (outRec1 != outRec2) return false;
+ 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, Left, 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;
+ }
+ }
+}
+//----------------------------------------------------------------------
+
+static OutRec* ParseFirstLeft(OutRec* FirstLeft)
+{
+ while (FirstLeft && !FirstLeft->Pts)
+ FirstLeft = FirstLeft->FirstLeft;
+ return FirstLeft;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec)
+{
+ //tests if NewOutRec contains the polygon before reassigning FirstLeft
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ if (!outRec->Pts || !outRec->FirstLeft) continue;
+ OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft);
+ if (firstLeft == OldOutRec)
+ {
+ if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
+ outRec->FirstLeft = NewOutRec;
+ }
+ }
+}
+//----------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec)
+{
+ //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
+ for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+ {
+ OutRec* outRec = m_PolyOuts[i];
+ if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec;
+ }
+}
+//----------------------------------------------------------------------
+
+void Clipper::JoinCommonEdges()
+{
+ for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+ {
+ Join* join = m_Joins[i];
+
+ OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
+ OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
+
+ if (!outRec1->Pts || !outRec2->Pts) continue;
+ if (outRec1->IsOpen || outRec2->IsOpen) 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 = 0;
+ 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 (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++)
+ {
+ OutRec* oRec = m_PolyOuts[j];
+ if (!oRec->Pts || 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 ^ m_ReverseOutput) == (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 ^ m_ReverseOutput) == (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 = 0;
+ outRec2->BottomPt = 0;
+ 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);
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset support functions ...
+//------------------------------------------------------------------------------
+
+DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2)
+{
+ if(pt2.X == pt1.X && pt2.Y == pt1.Y)
+ return DoublePoint(0, 0);
+
+ double Dx = (double)(pt2.X - pt1.X);
+ double dy = (double)(pt2.Y - pt1.Y);
+ double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy );
+ Dx *= f;
+ dy *= f;
+ return DoublePoint(dy, -Dx);
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset class
+//------------------------------------------------------------------------------
+
+ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance)
+{
+ this->MiterLimit = miterLimit;
+ this->ArcTolerance = arcTolerance;
+ m_lowest.X = -1;
+}
+//------------------------------------------------------------------------------
+
+ClipperOffset::~ClipperOffset()
+{
+ Clear();
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Clear()
+{
+ for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
+ delete m_polyNodes.Childs[i];
+ m_polyNodes.Childs.clear();
+ m_lowest.X = -1;
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType)
+{
+ int highI = (int)path.size() - 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 == etClosedLine || endType == etClosedPolygon)
+ while (highI > 0 && path[0] == path[highI]) highI--;
+ newNode->Contour.reserve(highI + 1);
+ newNode->Contour.push_back(path[0]);
+ int j = 0, k = 0;
+ for (int i = 1; i <= highI; i++)
+ if (newNode->Contour[j] != path[i])
+ {
+ j++;
+ newNode->Contour.push_back(path[i]);
+ if (path[i].Y > newNode->Contour[k].Y ||
+ (path[i].Y == newNode->Contour[k].Y &&
+ path[i].X < newNode->Contour[k].X)) k = j;
+ }
+ if (endType == etClosedPolygon && j < 2)
+ {
+ delete newNode;
+ return;
+ }
+ m_polyNodes.AddChild(*newNode);
+
+ //if this path's lowest pt is lower than all the others then update m_lowest
+ if (endType != etClosedPolygon) return;
+ if (m_lowest.X < 0)
+ m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+ else
+ {
+ IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y];
+ if (newNode->Contour[k].Y > ip.Y ||
+ (newNode->Contour[k].Y == ip.Y &&
+ newNode->Contour[k].X < ip.X))
+ m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType)
+{
+ for (Paths::size_type i = 0; i < paths.size(); ++i)
+ AddPath(paths[i], joinType, endType);
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::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 &&
+ !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour))
+ {
+ for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
+ {
+ PolyNode& node = *m_polyNodes.Childs[i];
+ if (node.m_endtype == etClosedPolygon ||
+ (node.m_endtype == etClosedLine && Orientation(node.Contour)))
+ ReversePath(node.Contour);
+ }
+ } else
+ {
+ for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
+ {
+ PolyNode& node = *m_polyNodes.Childs[i];
+ if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
+ ReversePath(node.Contour);
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Execute(Paths& solution, double delta)
+{
+ solution.clear();
+ FixOrientations();
+ DoOffset(delta);
+
+ //now clean up 'corners' ...
+ Clipper clpr;
+ clpr.AddPaths(m_destPolys, ptSubject, true);
+ if (delta > 0)
+ {
+ clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+ }
+ else
+ {
+ IntRect r = clpr.GetBounds();
+ Path outer(4);
+ outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+ outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+ outer[2] = IntPoint(r.right + 10, r.top - 10);
+ outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+ clpr.AddPath(outer, ptSubject, true);
+ clpr.ReverseSolution(true);
+ clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+ if (!solution.empty()) solution.erase(solution.begin());
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Execute(PolyTree& solution, double delta)
+{
+ solution.Clear();
+ FixOrientations();
+ DoOffset(delta);
+
+ //now clean up 'corners' ...
+ Clipper clpr;
+ clpr.AddPaths(m_destPolys, ptSubject, true);
+ if (delta > 0)
+ {
+ clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+ }
+ else
+ {
+ IntRect r = clpr.GetBounds();
+ Path outer(4);
+ outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+ outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+ outer[2] = IntPoint(r.right + 10, r.top - 10);
+ outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+ clpr.AddPath(outer, ptSubject, true);
+ clpr.ReverseSolution(true);
+ clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+ //remove the outer PolyNode rectangle ...
+ if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0)
+ {
+ PolyNode* outerNode = solution.Childs[0];
+ solution.Childs.reserve(outerNode->ChildCount());
+ solution.Childs[0] = outerNode->Childs[0];
+ solution.Childs[0]->Parent = outerNode->Parent;
+ for (int i = 1; i < outerNode->ChildCount(); ++i)
+ solution.AddChild(*outerNode->Childs[i]);
+ }
+ else
+ solution.Clear();
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoOffset(double delta)
+{
+ m_destPolys.clear();
+ m_delta = delta;
+
+ //if Zero offset, just copy any CLOSED polygons to m_p and return ...
+ if (NEAR_ZERO(delta))
+ {
+ m_destPolys.reserve(m_polyNodes.ChildCount());
+ for (int i = 0; i < m_polyNodes.ChildCount(); i++)
+ {
+ PolyNode& node = *m_polyNodes.Childs[i];
+ if (node.m_endtype == etClosedPolygon)
+ m_destPolys.push_back(node.Contour);
+ }
+ 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 > std::fabs(delta) * def_arc_tolerance)
+ y = std::fabs(delta) * def_arc_tolerance;
+ else y = ArcTolerance;
+ //see offset_triginometry2.svg in the documentation folder ...
+ double steps = pi / std::acos(1 - y / std::fabs(delta));
+ if (steps > std::fabs(delta) * pi)
+ steps = std::fabs(delta) * pi; //ie excessive precision check
+ m_sin = std::sin(two_pi / steps);
+ m_cos = std::cos(two_pi / steps);
+ m_StepsPerRad = steps / two_pi;
+ if (delta < 0.0) m_sin = -m_sin;
+
+ m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
+ for (int i = 0; i < m_polyNodes.ChildCount(); i++)
+ {
+ PolyNode& node = *m_polyNodes.Childs[i];
+ m_srcPoly = node.Contour;
+
+ int len = (int)m_srcPoly.size();
+ if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
+ continue;
+
+ m_destPoly.clear();
+ if (len == 1)
+ {
+ if (node.m_jointype == jtRound)
+ {
+ double X = 1.0, Y = 0.0;
+ for (cInt j = 1; j <= steps; j++)
+ {
+ m_destPoly.push_back(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.push_back(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.push_back(m_destPoly);
+ continue;
+ }
+ //build m_normals ...
+ m_normals.clear();
+ m_normals.reserve(len);
+ for (int j = 0; j < len - 1; ++j)
+ m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
+ if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
+ m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
+ else
+ m_normals.push_back(DoublePoint(m_normals[len - 2]));
+
+ if (node.m_endtype == etClosedPolygon)
+ {
+ int k = len - 1;
+ for (int j = 0; j < len; ++j)
+ OffsetPoint(j, k, node.m_jointype);
+ m_destPolys.push_back(m_destPoly);
+ }
+ else if (node.m_endtype == etClosedLine)
+ {
+ int k = len - 1;
+ for (int j = 0; j < len; ++j)
+ OffsetPoint(j, k, node.m_jointype);
+ m_destPolys.push_back(m_destPoly);
+ m_destPoly.clear();
+ //re-build m_normals ...
+ DoublePoint n = m_normals[len -1];
+ for (int j = len - 1; j > 0; j--)
+ m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+ m_normals[0] = DoublePoint(-n.X, -n.Y);
+ k = 0;
+ for (int j = len - 1; j >= 0; j--)
+ OffsetPoint(j, k, node.m_jointype);
+ m_destPolys.push_back(m_destPoly);
+ }
+ else
+ {
+ int k = 0;
+ for (int j = 1; j < len - 1; ++j)
+ OffsetPoint(j, k, node.m_jointype);
+
+ IntPoint pt1;
+ if (node.m_endtype == etOpenButt)
+ {
+ int j = len - 1;
+ pt1 = 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.push_back(pt1);
+ pt1 = 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.push_back(pt1);
+ }
+ else
+ {
+ int j = len - 1;
+ k = len - 2;
+ m_sinA = 0;
+ m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
+ if (node.m_endtype == etOpenSquare)
+ DoSquare(j, k);
+ else
+ DoRound(j, k);
+ }
+
+ //re-build m_normals ...
+ for (int j = len - 1; j > 0; j--)
+ m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+ m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
+
+ k = len - 1;
+ for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype);
+
+ if (node.m_endtype == etOpenButt)
+ {
+ pt1 = 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.push_back(pt1);
+ pt1 = 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.push_back(pt1);
+ }
+ else
+ {
+ k = 1;
+ m_sinA = 0;
+ if (node.m_endtype == etOpenSquare)
+ DoSquare(0, 1);
+ else
+ DoRound(0, 1);
+ }
+ m_destPolys.push_back(m_destPoly);
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype)
+{
+ //cross product ...
+ m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
+ if (std::fabs(m_sinA * m_delta) < 1.0)
+ {
+ //dot product ...
+ double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y );
+ if (cosA > 0) // angle => 0 degrees
+ {
+ m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+ Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+ return;
+ }
+ //else angle => 180 degrees
+ }
+ 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.push_back(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.push_back(m_srcPoly[j]);
+ m_destPoly.push_back(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 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 jtSquare: DoSquare(j, k); break;
+ case jtRound: DoRound(j, k); break;
+ }
+ k = j;
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoSquare(int j, int k)
+{
+ double dx = std::tan(std::atan2(m_sinA,
+ m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4);
+ m_destPoly.push_back(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.push_back(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))));
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoMiter(int j, int k, double r)
+{
+ double q = m_delta / r;
+ m_destPoly.push_back(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)));
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoRound(int j, int k)
+{
+ double a = std::atan2(m_sinA,
+ m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y);
+ int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1);
+
+ double X = m_normals[k].X, Y = m_normals[k].Y, X2;
+ for (int i = 0; i < steps; ++i)
+ {
+ m_destPoly.push_back(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.push_back(IntPoint(
+ Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+ Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+}
+
+//------------------------------------------------------------------------------
+// Miscellaneous public functions
+//------------------------------------------------------------------------------
+
+void Clipper::DoSimplePolygons()
+{
+ PolyOutList::size_type i = 0;
+ while (i < m_PolyOuts.size())
+ {
+ OutRec* outrec = m_PolyOuts[i++];
+ OutPt* op = outrec->Pts;
+ if (!op || outrec->IsOpen) 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;
+ if (m_UsingPolyTree) FixupFirstLefts2(outrec2, 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;
+ if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2);
+ }
+ else
+ {
+ //the 2 polygons are separate ...
+ outrec2->IsHole = outrec->IsHole;
+ outrec2->FirstLeft = outrec->FirstLeft;
+ if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2);
+ }
+ op2 = op; //ie get ready for the Next iteration
+ }
+ op2 = op2->Next;
+ }
+ op = op->Next;
+ }
+ while (op != outrec->Pts);
+ }
+}
+//------------------------------------------------------------------------------
+
+void ReversePath(Path& p)
+{
+ std::reverse(p.begin(), p.end());
+}
+//------------------------------------------------------------------------------
+
+void ReversePaths(Paths& p)
+{
+ for (Paths::size_type i = 0; i < p.size(); ++i)
+ ReversePath(p[i]);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType)
+{
+ Clipper c;
+ c.StrictlySimple(true);
+ c.AddPath(in_poly, ptSubject, true);
+ c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType)
+{
+ Clipper c;
+ c.StrictlySimple(true);
+ c.AddPaths(in_polys, ptSubject, true);
+ c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(Paths &polys, PolyFillType fillType)
+{
+ SimplifyPolygons(polys, polys, fillType);
+}
+//------------------------------------------------------------------------------
+
+inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2)
+{
+ double Dx = ((double)pt1.X - pt2.X);
+ double dy = ((double)pt1.Y - pt2.Y);
+ return (Dx*Dx + dy*dy);
+}
+//------------------------------------------------------------------------------
+
+double DistanceFromLineSqrd(
+ const IntPoint& pt, const IntPoint& ln1, const 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 = double(ln1.Y - ln2.Y);
+ double B = double(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);
+}
+//---------------------------------------------------------------------------
+
+bool SlopesNearCollinear(const IntPoint& pt1,
+ const IntPoint& pt2, const IntPoint& pt3, double distSqrd)
+{
+ //this function is more accurate when the point that's geometrically
+ //between the other 2 points is the one that's tested for distance.
+ //ie makes it more likely to pick up 'spikes' ...
+ if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y))
+ {
+ if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
+ return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+ else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
+ return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+ else
+ return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+ }
+ else
+ {
+ if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
+ return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+ else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
+ return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+ else
+ return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+ }
+}
+//------------------------------------------------------------------------------
+
+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);
+}
+//------------------------------------------------------------------------------
+
+OutPt* ExcludeOp(OutPt* op)
+{
+ OutPt* result = op->Prev;
+ result->Next = op->Next;
+ op->Next->Prev = result;
+ result->Idx = 0;
+ return result;
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(const Path& in_poly, Path& out_poly, double distance)
+{
+ //distance = proximity in units/pixels below which vertices
+ //will be stripped. Default ~= sqrt(2).
+
+ size_t size = in_poly.size();
+
+ if (size == 0)
+ {
+ out_poly.clear();
+ return;
+ }
+
+ OutPt* outPts = new OutPt[size];
+ for (size_t i = 0; i < size; ++i)
+ {
+ outPts[i].Pt = in_poly[i];
+ outPts[i].Next = &outPts[(i + 1) % size];
+ 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);
+ size--;
+ }
+ else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd))
+ {
+ ExcludeOp(op->Next);
+ op = ExcludeOp(op);
+ size -= 2;
+ }
+ else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd))
+ {
+ op = ExcludeOp(op);
+ size--;
+ }
+ else
+ {
+ op->Idx = 1;
+ op = op->Next;
+ }
+ }
+
+ if (size < 3) size = 0;
+ out_poly.resize(size);
+ for (size_t i = 0; i < size; ++i)
+ {
+ out_poly[i] = op->Pt;
+ op = op->Next;
+ }
+ delete [] outPts;
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(Path& poly, double distance)
+{
+ CleanPolygon(poly, poly, distance);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance)
+{
+ for (Paths::size_type i = 0; i < in_polys.size(); ++i)
+ CleanPolygon(in_polys[i], out_polys[i], distance);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(Paths& polys, double distance)
+{
+ CleanPolygons(polys, polys, distance);
+}
+//------------------------------------------------------------------------------
+
+void Minkowski(const Path& poly, const Path& path,
+ Paths& solution, bool isSum, bool isClosed)
+{
+ int delta = (isClosed ? 1 : 0);
+ size_t polyCnt = poly.size();
+ size_t pathCnt = path.size();
+ Paths pp;
+ pp.reserve(pathCnt);
+ if (isSum)
+ for (size_t i = 0; i < pathCnt; ++i)
+ {
+ Path p;
+ p.reserve(polyCnt);
+ for (size_t j = 0; j < poly.size(); ++j)
+ p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
+ pp.push_back(p);
+ }
+ else
+ for (size_t i = 0; i < pathCnt; ++i)
+ {
+ Path p;
+ p.reserve(polyCnt);
+ for (size_t j = 0; j < poly.size(); ++j)
+ p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
+ pp.push_back(p);
+ }
+
+ solution.clear();
+ solution.reserve((pathCnt + delta) * (polyCnt + 1));
+ for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
+ for (size_t j = 0; j < polyCnt; ++j)
+ {
+ Path quad;
+ quad.reserve(4);
+ quad.push_back(pp[i % pathCnt][j % polyCnt]);
+ quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
+ quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
+ quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
+ if (!Orientation(quad)) ReversePath(quad);
+ solution.push_back(quad);
+ }
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed)
+{
+ Minkowski(pattern, path, solution, true, pathIsClosed);
+ Clipper c;
+ c.AddPaths(solution, ptSubject, true);
+ c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+void TranslatePath(const Path& input, Path& output, const IntPoint delta)
+{
+ //precondition: input != output
+ output.resize(input.size());
+ for (size_t i = 0; i < input.size(); ++i)
+ output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed)
+{
+ Clipper c;
+ for (size_t i = 0; i < paths.size(); ++i)
+ {
+ Paths tmp;
+ Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
+ c.AddPaths(tmp, ptSubject, true);
+ if (pathIsClosed)
+ {
+ Path tmp2;
+ TranslatePath(paths[i], tmp2, pattern[0]);
+ c.AddPath(tmp2, ptClip, true);
+ }
+ }
+ c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution)
+{
+ Minkowski(poly1, poly2, solution, false, true);
+ Clipper c;
+ c.AddPaths(solution, ptSubject, true);
+ c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+enum NodeType {ntAny, ntOpen, ntClosed};
+
+void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths)
+{
+ bool match = true;
+ if (nodetype == ntClosed) match = !polynode.IsOpen();
+ else if (nodetype == ntOpen) return;
+
+ if (!polynode.Contour.empty() && match)
+ paths.push_back(polynode.Contour);
+ for (int i = 0; i < polynode.ChildCount(); ++i)
+ AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
+}
+//------------------------------------------------------------------------------
+
+void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
+{
+ paths.resize(0);
+ paths.reserve(polytree.Total());
+ AddPolyNodeToPaths(polytree, ntAny, paths);
+}
+//------------------------------------------------------------------------------
+
+void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
+{
+ paths.resize(0);
+ paths.reserve(polytree.Total());
+ AddPolyNodeToPaths(polytree, ntClosed, paths);
+}
+//------------------------------------------------------------------------------
+
+void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
+{
+ paths.resize(0);
+ paths.reserve(polytree.Total());
+ //Open paths are top level only, so ...
+ for (int i = 0; i < polytree.ChildCount(); ++i)
+ if (polytree.Childs[i]->IsOpen())
+ paths.push_back(polytree.Childs[i]->Contour);
+}
+//------------------------------------------------------------------------------
+
+std::ostream& operator <<(std::ostream &s, const IntPoint &p)
+{
+ s << "(" << p.X << "," << p.Y << ")";
+ return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream& operator <<(std::ostream &s, const Path &p)
+{
+ if (p.empty()) return s;
+ Path::size_type last = p.size() -1;
+ for (Path::size_type i = 0; i < last; i++)
+ s << "(" << p[i].X << "," << p[i].Y << "), ";
+ s << "(" << p[last].X << "," << p[last].Y << ")\n";
+ return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream& operator <<(std::ostream &s, const Paths &p)
+{
+ for (Paths::size_type i = 0; i < p.size(); i++)
+ s << p[i];
+ s << "\n";
+ return s;
+}
+//------------------------------------------------------------------------------
+
+} //ClipperLib namespace
diff --git a/cpp/clipper.hpp b/cpp/clipper.hpp
new file mode 100644
index 0000000..73cc06c
--- /dev/null
+++ b/cpp/clipper.hpp
@@ -0,0 +1,435 @@
+/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 6.2.9 *
+* Date : 16 February 2015 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2015 *
+* *
+* 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 *
+* *
+*******************************************************************************/
+
+#ifndef clipper_hpp
+#define clipper_hpp
+
+#define CLIPPER_VERSION "6.2.6"
+
+//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 perfomance.
+//#define use_xyz
+
+//use_lines: Enables line clipping. Adds a very minor cost to performance.
+//#define use_lines
+
+//use_deprecated: Enables temporary support for the obsolete functions
+//#define use_deprecated
+
+#include <vector>
+#include <list>
+#include <set>
+#include <stdexcept>
+#include <cstring>
+#include <cstdlib>
+#include <ostream>
+#include <functional>
+#include <queue>
+#if defined(CLIPPER_IMPL_INCLUDE)
+#include CLIPPER_IMPL_INCLUDE
+#endif
+
+namespace ClipperLib {
+
+enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
+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
+enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
+
+#ifdef use_int32
+ typedef int cInt;
+ static cInt const loRange = 0x7FFF;
+ static cInt const hiRange = 0x7FFF;
+#else
+ typedef std::int64_t cInt;
+ static cInt const loRange = 0x3FFFFFFF;
+ static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
+ typedef signed long long long64; //used by Int128 class
+ typedef unsigned long long ulong64;
+
+#endif
+
+#if defined(CLIPPER_INTPOINT_IMPL)
+
+typedef CLIPPER_INTPOINT_IMPL IntPoint;
+
+#else
+
+struct IntPoint {
+ cInt X;
+ cInt Y;
+#ifdef use_xyz
+ cInt Z;
+ IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
+#else
+ IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {};
+#endif
+
+ friend inline bool operator== (const IntPoint& a, const IntPoint& b)
+ {
+ return a.X == b.X && a.Y == b.Y;
+ }
+ friend inline bool operator!= (const IntPoint& a, const IntPoint& b)
+ {
+ return a.X != b.X || a.Y != b.Y;
+ }
+};
+#endif
+
+//------------------------------------------------------------------------------
+
+#if defined(CLIPPER_PATH_IMPL)
+
+typedef CLIPPER_PATH_IMPL Path;
+
+#else
+
+typedef std::vector< IntPoint > Path;
+
+#endif
+
+
+#if defined(CLIPPER_PATHS_IMPL)
+
+typedef CLIPPER_PATHS_IMPL Paths;
+
+#else
+
+typedef std::vector< Path > Paths;
+
+#endif
+
+
+
+inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
+inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
+
+std::ostream& operator <<(std::ostream &s, const IntPoint &p);
+std::ostream& operator <<(std::ostream &s, const Path &p);
+std::ostream& operator <<(std::ostream &s, const Paths &p);
+
+struct DoublePoint
+{
+ double X;
+ double Y;
+ DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
+ DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {}
+};
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
+#endif
+
+enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
+enum JoinType {jtSquare, jtRound, jtMiter};
+enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
+
+class PolyNode;
+typedef std::vector< PolyNode* > PolyNodes;
+
+class PolyNode
+{
+public:
+ PolyNode();
+ virtual ~PolyNode(){};
+ Path Contour;
+ PolyNodes Childs;
+ PolyNode* Parent;
+ PolyNode* GetNext() const;
+ bool IsHole() const;
+ bool IsOpen() const;
+ int ChildCount() const;
+private:
+ unsigned Index; //node index in Parent.Childs
+ bool m_IsOpen;
+ JoinType m_jointype;
+ EndType m_endtype;
+ PolyNode* GetNextSiblingUp() const;
+ void AddChild(PolyNode& child);
+ friend class Clipper; //to access Index
+ friend class ClipperOffset;
+};
+
+class PolyTree: public PolyNode
+{
+public:
+ ~PolyTree(){Clear();};
+ PolyNode* GetFirst() const;
+ void Clear();
+ int Total() const;
+private:
+ PolyNodes AllNodes;
+ friend class Clipper; //to access AllNodes
+};
+
+bool Orientation(const Path &poly);
+double Area(const Path &poly);
+int PointInPolygon(const IntPoint &pt, const Path &path);
+
+void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
+
+void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
+void CleanPolygon(Path& poly, double distance = 1.415);
+void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
+void CleanPolygons(Paths& polys, double distance = 1.415);
+
+void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
+void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
+void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
+
+void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
+void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
+void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
+
+void ReversePath(Path& p);
+void ReversePaths(Paths& p);
+
+struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
+
+//enums that are used internally ...
+enum EdgeSide { esLeft = 1, esRight = 2};
+
+//forward declarations (for stuff used internally) ...
+struct TEdge;
+struct IntersectNode;
+struct LocalMinimum;
+struct OutPt;
+struct OutRec;
+struct Join;
+
+typedef std::vector < OutRec* > PolyOutList;
+typedef std::vector < TEdge* > EdgeList;
+typedef std::vector < Join* > JoinList;
+typedef std::vector < IntersectNode* > IntersectList;
+
+//------------------------------------------------------------------------------
+
+//ClipperBase is the ancestor to the Clipper class. It should not be
+//instantiated directly. This class simply abstracts the conversion of sets of
+//polygon coordinates into edge objects that are stored in a LocalMinima list.
+class ClipperBase
+{
+public:
+ ClipperBase();
+ virtual ~ClipperBase();
+ bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
+ bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
+ virtual void Clear();
+ IntRect GetBounds();
+ bool PreserveCollinear() {return m_PreserveCollinear;};
+ void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
+protected:
+ void DisposeLocalMinimaList();
+ TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
+ void PopLocalMinima();
+ virtual void Reset();
+ TEdge* ProcessBound(TEdge* E, bool IsClockwise);
+ TEdge* DescendToMin(TEdge *&E);
+ void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
+
+ typedef std::vector<LocalMinimum> MinimaList;
+ MinimaList::iterator m_CurrentLM;
+ MinimaList m_MinimaList;
+
+ bool m_UseFullRange;
+ EdgeList m_edges;
+ bool m_PreserveCollinear;
+ bool m_HasOpenPaths;
+};
+//------------------------------------------------------------------------------
+
+class Clipper : public virtual ClipperBase
+{
+public:
+ Clipper(int initOptions = 0);
+ ~Clipper();
+ bool Execute(ClipType clipType,
+ Paths &solution,
+ PolyFillType fillType = pftEvenOdd);
+ bool Execute(ClipType clipType,
+ Paths &solution,
+ PolyFillType subjFillType,
+ PolyFillType clipFillType);
+ bool Execute(ClipType clipType,
+ PolyTree &polytree,
+ PolyFillType fillType = pftEvenOdd);
+ bool Execute(ClipType clipType,
+ PolyTree &polytree,
+ PolyFillType subjFillType,
+ PolyFillType clipFillType);
+ bool ReverseSolution() { return m_ReverseOutput; };
+ void ReverseSolution(bool value) {m_ReverseOutput = value;};
+ bool StrictlySimple() {return m_StrictSimple;};
+ void StrictlySimple(bool value) {m_StrictSimple = value;};
+ //set the callback function for z value filling on intersections (otherwise Z is 0)
+#ifdef use_xyz
+ void ZFillFunction(ZFillCallback zFillFunc);
+#endif
+protected:
+ void Reset();
+ virtual bool ExecuteInternal();
+private:
+ PolyOutList m_PolyOuts;
+ JoinList m_Joins;
+ JoinList m_GhostJoins;
+ IntersectList m_IntersectList;
+ ClipType m_ClipType;
+ typedef std::priority_queue<cInt> ScanbeamList;
+ ScanbeamList m_Scanbeam;
+ typedef std::list<cInt> MaximaList;
+ MaximaList m_Maxima;
+ TEdge *m_ActiveEdges;
+ TEdge *m_SortedEdges;
+ bool m_ExecuteLocked;
+ PolyFillType m_ClipFillType;
+ PolyFillType m_SubjFillType;
+ bool m_ReverseOutput;
+ bool m_UsingPolyTree;
+ bool m_StrictSimple;
+#ifdef use_xyz
+ ZFillCallback m_ZFill; //custom callback
+#endif
+ void SetWindingCount(TEdge& edge);
+ bool IsEvenOddFillType(const TEdge& edge) const;
+ bool IsEvenOddAltFillType(const TEdge& edge) const;
+ void InsertScanbeam(const cInt Y);
+ cInt PopScanbeam();
+ void InsertLocalMinimaIntoAEL(const cInt botY);
+ void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
+ void AddEdgeToSEL(TEdge *edge);
+ void CopyAELToSEL();
+ void DeleteFromSEL(TEdge *e);
+ void DeleteFromAEL(TEdge *e);
+ void UpdateEdgeIntoAEL(TEdge *&e);
+ void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
+ bool IsContributing(const TEdge& edge) const;
+ bool IsTopHorz(const cInt XPos);
+ void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
+ void DoMaxima(TEdge *e);
+ void ProcessHorizontals();
+ void ProcessHorizontal(TEdge *horzEdge);
+ void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+ OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+ OutRec* GetOutRec(int idx);
+ void AppendPolygon(TEdge *e1, TEdge *e2);
+ void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
+ OutRec* CreateOutRec();
+ OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
+ OutPt* GetLastOutPt(TEdge *e);
+ void DisposeAllOutRecs();
+ void DisposeOutRec(PolyOutList::size_type index);
+ bool ProcessIntersections(const cInt topY);
+ void BuildIntersectList(const cInt topY);
+ void ProcessIntersectList();
+ void ProcessEdgesAtTopOfScanbeam(const cInt topY);
+ void BuildResult(Paths& polys);
+ void BuildResult2(PolyTree& polytree);
+ void SetHoleState(TEdge *e, OutRec *outrec);
+ void DisposeIntersectNodes();
+ bool FixupIntersectionOrder();
+ void FixupOutPolygon(OutRec &outrec);
+ void FixupOutPolyline(OutRec &outrec);
+ bool IsHole(TEdge *e);
+ bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
+ void FixHoleLinkage(OutRec &outrec);
+ void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
+ void ClearJoins();
+ void ClearGhostJoins();
+ void AddGhostJoin(OutPt *op, const IntPoint offPt);
+ bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
+ void JoinCommonEdges();
+ void DoSimplePolygons();
+ void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
+ void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
+#ifdef use_xyz
+ void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
+#endif
+};
+//------------------------------------------------------------------------------
+
+class ClipperOffset
+{
+public:
+ ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
+ ~ClipperOffset();
+ void AddPath(const Path& path, JoinType joinType, EndType endType);
+ void AddPaths(const Paths& paths, JoinType joinType, EndType endType);
+ void Execute(Paths& solution, double delta);
+ void Execute(PolyTree& solution, double delta);
+ void Clear();
+ double MiterLimit;
+ double ArcTolerance;
+private:
+ Paths m_destPolys;
+ Path m_srcPoly;
+ Path m_destPoly;
+ std::vector<DoublePoint> m_normals;
+ double m_delta, m_sinA, m_sin, m_cos;
+ double m_miterLim, m_StepsPerRad;
+ IntPoint m_lowest;
+ PolyNode m_polyNodes;
+
+ void FixOrientations();
+ void DoOffset(double delta);
+ void OffsetPoint(int j, int& k, JoinType jointype);
+ void DoSquare(int j, int k);
+ void DoMiter(int j, int k, double r);
+ void DoRound(int j, int k);
+};
+//------------------------------------------------------------------------------
+
+class clipperException : public std::exception
+{
+ public:
+ clipperException(const char* description): m_descr(description) {}
+ virtual ~clipperException() throw() {}
+ virtual const char* what() const throw() {return m_descr.c_str();}
+ private:
+ std::string m_descr;
+};
+//------------------------------------------------------------------------------
+
+} //ClipperLib namespace
+
+#endif //clipper_hpp
+
+
diff --git a/cpp/cpp_agg/agg_conv_clipper.h b/cpp/cpp_agg/agg_conv_clipper.h
new file mode 100644
index 0000000..15f66f9
--- /dev/null
+++ b/cpp/cpp_agg/agg_conv_clipper.h
@@ -0,0 +1,295 @@
+/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 1.1 *
+* Date : 4 April 2011 *
+* Website : http://www.angusj.com *
+* Copyright : Angus Johnson 2010-2011 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+*******************************************************************************/
+
+#ifndef AGG_CONV_CLIPPER_INCLUDED
+#define AGG_CONV_CLIPPER_INCLUDED
+
+#include <cmath>
+#include "agg_basics.h"
+#include "agg_array.h"
+#include "../clipper.hpp"
+
+namespace agg
+{
+ enum clipper_op_e { clipper_or,
+ clipper_and, clipper_xor, clipper_a_minus_b, clipper_b_minus_a };
+ enum clipper_PolyFillType {clipper_even_odd, clipper_non_zero, clipper_positive, clipper_negative};
+
+ template<class VSA, class VSB> class conv_clipper
+ {
+ enum status { status_move_to, status_line_to, status_stop };
+ typedef VSA source_a_type;
+ typedef VSB source_b_type;
+ typedef conv_clipper<source_a_type, source_b_type> self_type;
+
+ private:
+ source_a_type* m_src_a;
+ source_b_type* m_src_b;
+ status m_status;
+ int m_vertex;
+ int m_contour;
+ int m_scaling_factor;
+ clipper_op_e m_operation;
+ pod_bvector<ClipperLib::IntPoint, 8> m_vertex_accumulator;
+ ClipperLib::Paths m_poly_a;
+ ClipperLib::Paths m_poly_b;
+ ClipperLib::Paths m_result;
+ ClipperLib::Clipper m_clipper;
+ clipper_PolyFillType m_subjFillType;
+ clipper_PolyFillType m_clipFillType;
+
+ int Round(double val)
+ {
+ if ((val < 0)) return (int)(val - 0.5); else return (int)(val + 0.5);
+ }
+
+ public:
+ conv_clipper(source_a_type &a, source_b_type &b,
+ clipper_op_e op = clipper_or,
+ clipper_PolyFillType subjFillType = clipper_even_odd,
+ clipper_PolyFillType clipFillType = clipper_even_odd,
+ int scaling_factor = 2) :
+ m_src_a(&a),
+ m_src_b(&b),
+ m_status(status_move_to),
+ m_vertex(-1),
+ m_contour(-1),
+ m_operation(op),
+ m_subjFillType(subjFillType),
+ m_clipFillType(clipFillType)
+ {
+ m_scaling_factor = std::max(std::min(scaling_factor, 6),0);
+ m_scaling_factor = Round(std::pow((double)10, m_scaling_factor));
+ }
+
+ ~conv_clipper()
+ {
+ }
+
+ void attach1(VSA &source, clipper_PolyFillType subjFillType = clipper_even_odd)
+ { m_src_a = &source; m_subjFillType = subjFillType; }
+ void attach2(VSB &source, clipper_PolyFillType clipFillType = clipper_even_odd)
+ { m_src_b = &source; m_clipFillType = clipFillType; }
+
+ void operation(clipper_op_e v) { m_operation = v; }
+
+ void rewind(unsigned path_id);
+ unsigned vertex(double* x, double* y);
+
+ bool next_contour();
+ bool next_vertex(double* x, double* y);
+ void start_extracting();
+ void add_vertex_(double &x, double &y);
+ void end_contour(ClipperLib::Paths &p);
+
+ template<class VS> void add(VS &src, ClipperLib::Paths &p){
+ unsigned cmd;
+ double x; double y; double start_x; double start_y;
+ bool starting_first_line;
+
+ start_x = 0.0;
+ start_y = 0.0;
+ starting_first_line = true;
+ p.resize(0);
+
+ cmd = src->vertex( &x , &y );
+ while(!is_stop(cmd))
+ {
+ if(is_vertex(cmd))
+ {
+ if(is_move_to(cmd))
+ {
+ if(!starting_first_line ) end_contour(p);
+ start_x = x;
+ start_y = y;
+ }
+ add_vertex_( x, y );
+ starting_first_line = false;
+ }
+ else if(is_end_poly(cmd))
+ {
+ if(!starting_first_line && is_closed(cmd))
+ add_vertex_( start_x, start_y );
+ }
+ cmd = src->vertex( &x, &y );
+ }
+ end_contour(p);
+ }
+ };
+
+ //------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ void conv_clipper<VSA, VSB>::start_extracting()
+ {
+ m_status = status_move_to;
+ m_contour = -1;
+ m_vertex = -1;
+ }
+ //------------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ void conv_clipper<VSA, VSB>::rewind(unsigned path_id)
+ {
+ m_src_a->rewind( path_id );
+ m_src_b->rewind( path_id );
+
+ add( m_src_a , m_poly_a );
+ add( m_src_b , m_poly_b );
+ m_result.resize(0);
+
+ ClipperLib::PolyFillType pftSubj, pftClip;
+ switch (m_subjFillType)
+ {
+ case clipper_even_odd: pftSubj = ClipperLib::pftEvenOdd; break;
+ case clipper_non_zero: pftSubj = ClipperLib::pftNonZero; break;
+ case clipper_positive: pftSubj = ClipperLib::pftPositive; break;
+ default: pftSubj = ClipperLib::pftNegative;
+ }
+ switch (m_clipFillType)
+ {
+ case clipper_even_odd: pftClip = ClipperLib::pftEvenOdd; break;
+ case clipper_non_zero: pftClip = ClipperLib::pftNonZero; break;
+ case clipper_positive: pftClip = ClipperLib::pftPositive; break;
+ default: pftClip = ClipperLib::pftNegative;
+ }
+
+ m_clipper.Clear();
+ switch( m_operation ) {
+ case clipper_or:
+ {
+ m_clipper.AddPaths( m_poly_a , ClipperLib::ptSubject, true );
+ m_clipper.AddPaths( m_poly_b , ClipperLib::ptClip, true );
+ m_clipper.Execute( ClipperLib::ctUnion , m_result , pftSubj, pftClip);
+ break;
+ }
+ case clipper_and:
+ {
+ m_clipper.AddPaths( m_poly_a , ClipperLib::ptSubject, true );
+ m_clipper.AddPaths( m_poly_b , ClipperLib::ptClip, true );
+ m_clipper.Execute( ClipperLib::ctIntersection , m_result, pftSubj, pftClip );
+ break;
+ }
+ case clipper_xor:
+ {
+ m_clipper.AddPaths( m_poly_a , ClipperLib::ptSubject, true );
+ m_clipper.AddPaths( m_poly_b , ClipperLib::ptClip, true );
+ m_clipper.Execute( ClipperLib::ctXor , m_result, pftSubj, pftClip );
+ break;
+ }
+ case clipper_a_minus_b:
+ {
+ m_clipper.AddPaths( m_poly_a , ClipperLib::ptSubject, true );
+ m_clipper.AddPaths( m_poly_b , ClipperLib::ptClip, true );
+ m_clipper.Execute( ClipperLib::ctDifference , m_result, pftSubj, pftClip );
+ break;
+ }
+ case clipper_b_minus_a:
+ {
+ m_clipper.AddPaths( m_poly_b , ClipperLib::ptSubject, true );
+ m_clipper.AddPaths( m_poly_a , ClipperLib::ptClip, true );
+ m_clipper.Execute( ClipperLib::ctDifference , m_result, pftSubj, pftClip );
+ break;
+ }
+ }
+ start_extracting();
+ }
+ //------------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ void conv_clipper<VSA, VSB>::end_contour( ClipperLib::Paths &p)
+ {
+ unsigned i, len;
+
+ if( m_vertex_accumulator.size() < 3 ) return;
+ len = p.size();
+ p.resize(len+1);
+ p[len].resize(m_vertex_accumulator.size());
+ for( i = 0 ; i < m_vertex_accumulator.size() ; i++ )
+ p[len][i] = m_vertex_accumulator[i];
+ m_vertex_accumulator.remove_all();
+ }
+ //------------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ void conv_clipper<VSA, VSB>::add_vertex_(double &x, double &y)
+ {
+ ClipperLib::IntPoint v;
+
+ v.X = Round(x * m_scaling_factor);
+ v.Y = Round(y * m_scaling_factor);
+ m_vertex_accumulator.add( v );
+ }
+ //------------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ bool conv_clipper<VSA, VSB>::next_contour()
+ {
+ m_contour++;
+ if(m_contour >= (int)m_result.size()) return false;
+ m_vertex =-1;
+ return true;
+}
+//------------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ bool conv_clipper<VSA, VSB>::next_vertex(double *x, double *y)
+ {
+ m_vertex++;
+ if(m_vertex >= (int)m_result[m_contour].size()) return false;
+ *x = (double)m_result[ m_contour ][ m_vertex ].X / m_scaling_factor;
+ *y = (double)m_result[ m_contour ][ m_vertex ].Y / m_scaling_factor;
+ return true;
+ }
+ //------------------------------------------------------------------------------
+
+ template<class VSA, class VSB>
+ unsigned conv_clipper<VSA, VSB>::vertex(double *x, double *y)
+{
+ if( m_status == status_move_to )
+ {
+ if( next_contour() )
+ {
+ if( next_vertex( x, y ) )
+ {
+ m_status =status_line_to;
+ return path_cmd_move_to;
+ }
+ else
+ {
+ m_status = status_stop;
+ return path_cmd_end_poly | path_flags_close;
+ }
+ }
+ else
+ return path_cmd_stop;
+ }
+ else
+ {
+ if( next_vertex( x, y ) )
+ {
+ return path_cmd_line_to;
+ }
+ else
+ {
+ m_status = status_move_to;
+ return path_cmd_end_poly | path_flags_close;
+ }
+ }
+}
+//------------------------------------------------------------------------------
+
+
+} //namespace agg
+#endif //AGG_CONV_CLIPPER_INCLUDED
diff --git a/cpp/cpp_agg/clipper_test.cpp b/cpp/cpp_agg/clipper_test.cpp
new file mode 100644
index 0000000..ffbaa64
--- /dev/null
+++ b/cpp/cpp_agg/clipper_test.cpp
@@ -0,0 +1,574 @@
+#include <stdio.h>
+#include "agg_basics.h"
+#include "agg_rendering_buffer.h"
+#include "agg_rasterizer_scanline_aa.h"
+#include "agg_scanline_u.h"
+#include "agg_scanline_p.h"
+#include "agg_renderer_scanline.h"
+#include "agg_renderer_primitives.h"
+#include "agg_conv_curve.h"
+#include "agg_conv_stroke.h"
+#include "agg_conv_clip_polygon.h"
+#include "agg_gsv_text.h"
+#include "agg_pixfmt_rgb.h"
+#include "agg_platform_support.h"
+
+#include "agg_slider_ctrl.h"
+#include "agg_cbox_ctrl.h"
+#include "agg_rbox_ctrl.h"
+
+#include "agg_conv_clipper.h"
+#include "windows.h"
+
+enum flip_y_e { flip_y = true };
+
+
+class spiral
+{
+public:
+ spiral(double x, double y, double r1, double r2, double step, double start_angle=0) :
+ m_x(x),
+ m_y(y),
+ m_r1(r1),
+ m_r2(r2),
+ m_step(step),
+ m_start_angle(start_angle),
+ m_angle(start_angle),
+ m_da(agg::deg2rad(4.0)),
+ m_dr(m_step / 90.0)
+ {
+ }
+
+ void rewind(unsigned)
+ {
+ m_angle = m_start_angle;
+ m_curr_r = m_r1;
+ m_start = true;
+ }
+
+ unsigned vertex(double* x, double* y)
+ {
+ if(m_curr_r > m_r2) return agg::path_cmd_stop;
+
+ *x = m_x + cos(m_angle) * m_curr_r;
+ *y = m_y + sin(m_angle) * m_curr_r;
+ m_curr_r += m_dr;
+ m_angle += m_da;
+ if(m_start)
+ {
+ m_start = false;
+ return agg::path_cmd_move_to;
+ }
+ return agg::path_cmd_line_to;
+ }
+
+private:
+ double m_x;
+ double m_y;
+ double m_r1;
+ double m_r2;
+ double m_step;
+ double m_start_angle;
+
+ double m_angle;
+ double m_curr_r;
+ double m_da;
+ double m_dr;
+ bool m_start;
+};
+
+
+
+namespace agg
+{
+ // A simple counter of points and contours
+ template<class Src> struct conv_poly_counter
+ {
+ unsigned m_contours;
+ unsigned m_points;
+
+ conv_poly_counter(Src& src) : m_src(&src), m_contours(0), m_points(0) {}
+
+ void rewind(unsigned path_id)
+ {
+ m_contours = 0;
+ m_points = 0;
+ m_src->rewind(path_id);
+ }
+
+ unsigned vertex(double* x, double* y)
+ {
+ unsigned cmd = m_src->vertex(x, y);
+ if(is_vertex(cmd)) ++m_points;
+ if(is_move_to(cmd)) ++m_contours;
+ return cmd;
+ }
+
+ private:
+ Src* m_src;
+ };
+}
+
+
+void make_gb_poly(agg::path_storage& ps);
+void make_arrows(agg::path_storage& ps);
+
+
+class the_application : public agg::platform_support
+{
+ agg::rbox_ctrl<agg::rgba8> m_polygons;
+ agg::rbox_ctrl<agg::rgba8> m_operation;
+ double m_x;
+ double m_y;
+
+ virtual void on_key(int x, int y, unsigned key, unsigned flags)
+ {
+ if(key == agg::key_escape) exit(0);
+
+ }
+
+public:
+ the_application(agg::pix_format_e format, bool flip_y) :
+ agg::platform_support(format, flip_y),
+ m_polygons (5.0, 5.0, 5.0+205.0, 110.0, !flip_y),
+ m_operation(555.0, 5.0, 555.0+80.0, 130.0, !flip_y)
+ {
+ m_operation.add_item("None");
+ m_operation.add_item("OR");
+ m_operation.add_item("AND");
+ m_operation.add_item("XOR");
+ m_operation.add_item("A-B");
+ m_operation.add_item("B-A");
+ m_operation.cur_item(2);
+ add_ctrl(m_operation);
+
+ m_polygons.add_item("Two Simple Paths");
+ m_polygons.add_item("Closed Stroke");
+ m_polygons.add_item("Great Britain and Arrows");
+ m_polygons.add_item("Great Britain and Spiral");
+ m_polygons.add_item("Spiral and Glyph");
+ m_polygons.cur_item(3);
+ add_ctrl(m_polygons);
+ }
+
+
+ template<class Scanline, class Ras, class Ren, class Clp>
+ void perform_rendering(Scanline &sl, Ras &ras, Ren &ren, Clp &clp)
+ {
+ if(m_operation.cur_item() > 0)
+ {
+ ras.reset();
+ switch(m_operation.cur_item())
+ {
+ case 1: clp.operation(agg::clipper_or); break;
+ case 2: clp.operation(agg::clipper_and); break;
+ case 3: clp.operation(agg::clipper_xor); break;
+ case 4: clp.operation(agg::clipper_a_minus_b); break;
+ case 5: clp.operation(agg::clipper_b_minus_a); break;
+ }
+ agg::conv_poly_counter<Clp> counter(clp);
+
+
+ start_timer();
+ counter.rewind(0);
+ double t1 = elapsed_time();
+
+ agg::path_storage ps;
+ double x;
+ double y;
+ unsigned cmd;
+ start_timer();
+ while(!agg::is_stop(cmd = counter.vertex(&x, &y)))
+ {
+ if(agg::is_move_to(cmd))
+ ps.move_to(x, y);
+ else if(agg::is_line_to(cmd))
+ ps.line_to(x, y);
+ else if(agg::is_close(cmd))
+ ps.close_polygon();
+ }
+ ras.add_path(ps);
+ ren.color(agg::rgba(0.25, 0.9, 0.25, 0.65));
+ agg::render_scanlines(ras, sl, ren);
+ double t2 = elapsed_time();
+
+ agg::conv_stroke<agg::path_storage> stroke(ps);
+ stroke.width(0.4);
+ ras.add_path(stroke);
+ ren.color(agg::rgba(0, 0, 0));
+ agg::render_scanlines(ras, sl, ren);
+
+ char buf[100];
+ sprintf_s(buf, "Contours: %d Points: %d", counter.m_contours, counter.m_points);
+ agg::gsv_text txt;
+ agg::conv_stroke<agg::gsv_text> txt_stroke(txt);
+ txt_stroke.width(1.5);
+ txt_stroke.line_cap(agg::round_cap);
+ txt.size(10.0);
+ txt.start_point(250, 5);
+ txt.text(buf);
+ ras.add_path(txt_stroke);
+ ren.color(agg::rgba(0.0, 0.0, 0.0));
+ agg::render_scanlines(ras, sl, ren);
+
+ sprintf_s(buf, "Clipper=%.3fms Render=%.3fms", t1, t2);
+ txt.start_point(250, 20);
+ txt.text(buf);
+ ras.add_path(txt_stroke);
+ ren.color(agg::rgba(0.0, 0.0, 0.0));
+ agg::render_scanlines(ras, sl, ren);
+ }
+ }
+
+
+ template<class Scanline, class Ras>
+ unsigned render_clipper(Scanline& sl, Ras& ras)
+ {
+ agg::pixfmt_bgr24 pf(rbuf_window());
+ agg::renderer_base<agg::pixfmt_bgr24> rb(pf);
+ agg::renderer_scanline_aa_solid<agg::renderer_base<agg::pixfmt_bgr24> > ren(rb);
+
+
+ switch(m_polygons.cur_item())
+ {
+ case 0:
+ {
+ //------------------------------------
+ // Two simple paths
+ //
+ agg::path_storage ps1;
+ agg::path_storage ps2;
+
+ agg::conv_clipper<agg::path_storage, agg::path_storage> clp(ps1, ps2, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero);
+
+ double x = m_x - initial_width()/2 + 100;
+ double y = m_y - initial_height()/2 + 100;
+ ps1.move_to(x+140, y+145);
+ ps1.line_to(x+225, y+44);
+ ps1.line_to(x+296, y+219);
+ ps1.close_polygon();
+
+ ps1.line_to(x+226, y+289);
+ ps1.line_to(x+82, y+292);
+
+ ps1.move_to(x+220, y+222);
+ ps1.line_to(x+363, y+249);
+ ps1.line_to(x+265, y+331);
+
+ ps1.move_to(x+242, y+243);
+ ps1.line_to(x+268, y+309);
+ ps1.line_to(x+325, y+261);
+
+ ps1.move_to(x+259, y+259);
+ ps1.line_to(x+273, y+288);
+ ps1.line_to(x+298, y+266);
+
+ ps2.move_to(100+32, 100+77);
+ ps2.line_to(100+473, 100+263);
+ ps2.line_to(100+351, 100+290);
+ ps2.line_to(100+354, 100+374);
+
+ ras.reset();
+ ras.add_path(ps1);
+ ren.color(agg::rgba(0, 0, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ ras.reset();
+ ras.add_path(ps2);
+ ren.color(agg::rgba(0, 0.6, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ perform_rendering(sl, ras, ren, clp);
+ }
+ break;
+
+ case 1:
+ {
+ //------------------------------------
+ // Closed stroke
+ //
+ agg::path_storage ps1;
+ agg::path_storage ps2;
+ agg::conv_stroke<agg::path_storage> stroke(ps2);
+ stroke.width(10.0);
+
+ agg::conv_clipper<agg::path_storage,
+ agg::conv_stroke<agg::path_storage> > clp(ps1, stroke, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero);
+
+
+ double x = m_x - initial_width()/2 + 100;
+ double y = m_y - initial_height()/2 + 100;
+ ps1.move_to(x+140, y+145);
+ ps1.line_to(x+225, y+44);
+ ps1.line_to(x+296, y+219);
+ ps1.close_polygon();
+
+ ps1.line_to(x+226, y+289);
+ ps1.line_to(x+82, y+292);
+
+ ps1.move_to(x+220-50, y+222);
+ ps1.line_to(x+265-50, y+331);
+ ps1.line_to(x+363-50, y+249);
+ ps1.close_polygon(agg::path_flags_ccw);
+
+ ps2.move_to(100+32, 100+77);
+ ps2.line_to(100+473, 100+263);
+ ps2.line_to(100+351, 100+290);
+ ps2.line_to(100+354, 100+374);
+ ps2.close_polygon();
+
+ ras.reset();
+ ras.add_path(ps1);
+ ren.color(agg::rgba(0, 0, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ ras.reset();
+ ras.add_path(stroke);
+ ren.color(agg::rgba(0, 0.6, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ perform_rendering(sl, ras, ren, clp);
+ }
+ break;
+
+
+ case 2:
+ {
+ //------------------------------------
+ // Great Britain and Arrows
+ //
+ agg::path_storage gb_poly;
+ agg::path_storage arrows;
+ make_gb_poly(gb_poly);
+ make_arrows(arrows);
+
+ agg::trans_affine mtx1;
+ agg::trans_affine mtx2;
+ mtx1 *= agg::trans_affine_translation(-1150, -1150);
+ mtx1 *= agg::trans_affine_scaling(2.0);
+
+ mtx2 = mtx1;
+ mtx2 *= agg::trans_affine_translation(m_x - initial_width()/2,
+ m_y - initial_height()/2);
+
+ agg::conv_transform<agg::path_storage> trans_gb_poly(gb_poly, mtx1);
+ agg::conv_transform<agg::path_storage> trans_arrows(arrows, mtx2);
+
+ agg::conv_clipper<agg::conv_transform<agg::path_storage>,
+ agg::conv_transform<agg::path_storage> > clp(trans_gb_poly, trans_arrows, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero);
+
+ ras.add_path(trans_gb_poly);
+ ren.color(agg::rgba(0.5, 0.5, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ agg::conv_stroke<agg::conv_transform<agg::path_storage> > stroke_gb_poly(trans_gb_poly);
+ stroke_gb_poly.width(0.1);
+ ras.add_path(stroke_gb_poly);
+ ren.color(agg::rgba(0, 0, 0));
+ agg::render_scanlines(ras, sl, ren);
+
+ ras.add_path(trans_arrows);
+ ren.color(agg::rgba(0.0, 0.5, 0.5, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ perform_rendering(sl, ras, ren, clp);
+ }
+ break;
+
+
+ case 3:
+ {
+ //------------------------------------
+ // Great Britain and a Spiral
+ //
+ spiral sp(m_x, m_y, 10, 150, 30, 0.0);
+ agg::conv_stroke<spiral> stroke(sp);
+ stroke.width(15.0);
+
+ agg::path_storage gb_poly;
+ make_gb_poly(gb_poly);
+
+ agg::trans_affine mtx;
+ mtx *= agg::trans_affine_translation(-1150, -1150);
+ mtx *= agg::trans_affine_scaling(2.0);
+
+ agg::conv_transform<agg::path_storage> trans_gb_poly(gb_poly, mtx);
+
+ agg::conv_clipper<agg::conv_transform<agg::path_storage>,
+ agg::conv_stroke<spiral> > clp(trans_gb_poly, stroke, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero);
+
+ ras.add_path(trans_gb_poly);
+ ren.color(agg::rgba(0.5, 0.5, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ agg::conv_stroke<agg::conv_transform<agg::path_storage> > stroke_gb_poly(trans_gb_poly);
+ stroke_gb_poly.width(0.1);
+ ras.add_path(stroke_gb_poly);
+ ren.color(agg::rgba(0, 0, 0));
+ agg::render_scanlines(ras, sl, ren);
+
+ ras.add_path(stroke);
+ ren.color(agg::rgba(0.0, 0.5, 0.5, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ perform_rendering(sl, ras, ren, clp);
+ }
+ break;
+
+
+ case 4:
+ {
+ //------------------------------------
+ // Spiral and glyph
+ //
+ spiral sp(m_x, m_y, 10, 150, 30, 0.0);
+ agg::conv_stroke<spiral> stroke(sp);
+ stroke.width(15.0);
+
+ agg::path_storage glyph;
+ glyph.move_to(28.47, 6.45);
+ glyph.curve3(21.58, 1.12, 19.82, 0.29);
+ glyph.curve3(17.19, -0.93, 14.21, -0.93);
+ glyph.curve3(9.57, -0.93, 6.57, 2.25);
+ glyph.curve3(3.56, 5.42, 3.56, 10.60);
+ glyph.curve3(3.56, 13.87, 5.03, 16.26);
+ glyph.curve3(7.03, 19.58, 11.99, 22.51);
+ glyph.curve3(16.94, 25.44, 28.47, 29.64);
+ glyph.line_to(28.47, 31.40);
+ glyph.curve3(28.47, 38.09, 26.34, 40.58);
+ glyph.curve3(24.22, 43.07, 20.17, 43.07);
+ glyph.curve3(17.09, 43.07, 15.28, 41.41);
+ glyph.curve3(13.43, 39.75, 13.43, 37.60);
+ glyph.line_to(13.53, 34.77);
+ glyph.curve3(13.53, 32.52, 12.38, 31.30);
+ glyph.curve3(11.23, 30.08, 9.38, 30.08);
+ glyph.curve3(7.57, 30.08, 6.42, 31.35);
+ glyph.curve3(5.27, 32.62, 5.27, 34.81);
+ glyph.curve3(5.27, 39.01, 9.57, 42.53);
+ glyph.curve3(13.87, 46.04, 21.63, 46.04);
+ glyph.curve3(27.59, 46.04, 31.40, 44.04);
+ glyph.curve3(34.28, 42.53, 35.64, 39.31);
+ glyph.curve3(36.52, 37.21, 36.52, 30.71);
+ glyph.line_to(36.52, 15.53);
+ glyph.curve3(36.52, 9.13, 36.77, 7.69);
+ glyph.curve3(37.01, 6.25, 37.57, 5.76);
+ glyph.curve3(38.13, 5.27, 38.87, 5.27);
+ glyph.curve3(39.65, 5.27, 40.23, 5.62);
+ glyph.curve3(41.26, 6.25, 44.19, 9.18);
+ glyph.line_to(44.19, 6.45);
+ glyph.curve3(38.72, -0.88, 33.74, -0.88);
+ glyph.curve3(31.35, -0.88, 29.93, 0.78);
+ glyph.curve3(28.52, 2.44, 28.47, 6.45);
+ glyph.close_polygon();
+
+ glyph.move_to(28.47, 9.62);
+ glyph.line_to(28.47, 26.66);
+ glyph.curve3(21.09, 23.73, 18.95, 22.51);
+ glyph.curve3(15.09, 20.36, 13.43, 18.02);
+ glyph.curve3(11.77, 15.67, 11.77, 12.89);
+ glyph.curve3(11.77, 9.38, 13.87, 7.06);
+ glyph.curve3(15.97, 4.74, 18.70, 4.74);
+ glyph.curve3(22.41, 4.74, 28.47, 9.62);
+ glyph.close_polygon();
+
+ agg::trans_affine mtx;
+ mtx *= agg::trans_affine_scaling(4.0);
+ mtx *= agg::trans_affine_translation(220, 200);
+ agg::conv_transform<agg::path_storage> trans(glyph, mtx);
+ agg::conv_curve<agg::conv_transform<agg::path_storage> > curve(trans);
+
+ agg::conv_clipper<agg::conv_stroke<spiral>,
+ agg::conv_curve<
+ agg::conv_transform<
+ agg::path_storage> > > clp(stroke, curve, agg::clipper_or, agg::clipper_non_zero, agg::clipper_non_zero);
+
+ ras.reset();
+ ras.add_path(stroke);
+ ren.color(agg::rgba(0, 0, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ ras.reset();
+ ras.add_path(curve);
+ ren.color(agg::rgba(0, 0.6, 0, 0.1));
+ agg::render_scanlines(ras, sl, ren);
+
+ perform_rendering(sl, ras, ren, clp);
+ }
+ break;
+ }
+
+ return 0;
+ }
+
+
+ virtual void on_init()
+ {
+ m_x = width() / 2.0;
+ m_y = height() / 2.0;
+ }
+
+ virtual void on_draw()
+ {
+ typedef agg::renderer_base<agg::pixfmt_bgr24> base_ren_type;
+
+ agg::pixfmt_bgr24 pf(rbuf_window());
+ base_ren_type ren_base(pf);
+ ren_base.clear(agg::rgba(1,1,1));
+
+ agg::scanline_u8 sl;
+ agg::rasterizer_scanline_aa<> ras;
+
+ render_clipper(sl, ras);
+
+ agg::render_ctrl(ras, sl, ren_base, m_polygons);
+ agg::render_ctrl(ras, sl, ren_base, m_operation);
+ }
+
+ virtual void on_mouse_button_down(int x, int y, unsigned flags)
+ {
+ if(flags & agg::mouse_left)
+ {
+ m_x = x;
+ m_y = y;
+ force_redraw();
+ }
+ }
+
+
+ virtual void on_mouse_move(int x, int y, unsigned flags)
+ {
+ if(flags & agg::mouse_left)
+ {
+ m_x = x;
+ m_y = y;
+ force_redraw();
+ }
+ }
+
+
+
+};
+
+
+int agg_main(int argc, char* argv[])
+{
+ the_application app(agg::pix_format_bgr24, flip_y);
+ app.caption("AGG Example. Clipper");
+
+ if(app.init(640, 520, agg::window_resize))
+ {
+ //replace the main window icon with Resource Icon #1 ...
+ HWND w = GetActiveWindow();
+ HMODULE m = GetModuleHandle(0); //hInstance
+ HANDLE small_ico = LoadImage(m, MAKEINTRESOURCE(1), IMAGE_ICON, 16, 16, 0);
+ HANDLE big_ico = LoadImage(m, MAKEINTRESOURCE(1), IMAGE_ICON, 32, 32, 0);
+ SendMessage(w, WM_SETICON, ICON_SMALL, (LPARAM)small_ico);
+ SendMessage(w, WM_SETICON, ICON_BIG, (LPARAM)big_ico);
+
+ //main message loop ...
+ return app.run();
+ }
+ return 0;
+}
+
+
diff --git a/cpp/cpp_agg/clipper_test.sln b/cpp/cpp_agg/clipper_test.sln
new file mode 100644
index 0000000..2c46499
--- /dev/null
+++ b/cpp/cpp_agg/clipper_test.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "clipper_test", "clipper_test.vcxproj", "{4DF13A7A-6137-E76C-8C62-F591D0E4B69F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Debug|Win32.Build.0 = Debug|Win32
+ {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Release|Win32.ActiveCfg = Release|Win32
+ {4DF13A7A-6137-E76C-8C62-F591D0E4B69F}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/cpp/cpp_agg/clipper_test.vcxproj b/cpp/cpp_agg/clipper_test.vcxproj
new file mode 100644
index 0000000..9f4aaf2
--- /dev/null
+++ b/cpp/cpp_agg/clipper_test.vcxproj
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <Keyword>Win32Proj</Keyword>
+ <ProjectName>clipper_test</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>C:\Program Files (x86)\Borland\agg-2.5\include;C:\Program Files (x86)\Borland\agg-2.5\include\platform;C:\Program Files (x86)\Borland\agg-2.5\include\platform\win32;C:\Program Files (x86)\Borland\agg-2.5\include\ctrl;C:\Program Files (x86)\Borland\agg-2.5\include\util;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>C:\Program Files (x86)\Borland\agg-2.5\include;C:\Program Files (x86)\Borland\agg-2.5\include\platform;C:\Program Files (x86)\Borland\agg-2.5\include\platform\win32;C:\Program Files (x86)\Borland\agg-2.5\include\ctrl;C:\Program Files (x86)\Borland\agg-2.5\include\util;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\agg_src\examples\make_arrows.cpp" />
+ <ClCompile Include="..\agg_src\examples\make_gb_poly.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_arc.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_arrowhead.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_bezier_arc.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_bspline.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_curves.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_embedded_raster_fonts.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_gsv_text.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_image_filters.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_line_aa_basics.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_line_profile_aa.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_rounded_rect.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_sqrt_tables.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_trans_affine.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_trans_double_path.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_trans_single_path.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_trans_warp_magnifier.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vcgen_bspline.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vcgen_contour.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vcgen_dash.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vcgen_markers_term.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vcgen_smooth_poly1.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vcgen_stroke.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vpgen_clip_polygon.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vpgen_clip_polyline.cpp" />
+ <ClCompile Include="..\agg_src\src\agg_vpgen_segmentator.cpp" />
+ <ClCompile Include="..\agg_src\src\ctrl\agg_rbox_ctrl.cpp" />
+ <ClCompile Include="..\agg_src\src\platform\win32\agg_platform_support.cpp" />
+ <ClCompile Include="..\agg_src\src\platform\win32\agg_win32_bmp.cpp" />
+ <ClCompile Include="..\clipper.cpp" />
+ <ClCompile Include="clipper_test.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\clipper.hpp" />
+ <ClInclude Include="agg_conv_clipper.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/cpp/cpp_agg/clipper_test.vcxproj.filters b/cpp/cpp_agg/clipper_test.vcxproj.filters
new file mode 100644
index 0000000..6a12557
--- /dev/null
+++ b/cpp/cpp_agg/clipper_test.vcxproj.filters
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="clipper_test.cpp" />
+ <ClCompile Include="..\clipper.cpp">
+ <Filter>ClipperLib</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_arc.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_arrowhead.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_bezier_arc.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_bspline.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_curves.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_embedded_raster_fonts.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_gsv_text.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_image_filters.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_line_aa_basics.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_line_profile_aa.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_rounded_rect.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_sqrt_tables.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_trans_affine.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_trans_double_path.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_trans_single_path.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_trans_warp_magnifier.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vcgen_bspline.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vcgen_contour.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vcgen_dash.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vcgen_markers_term.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vcgen_smooth_poly1.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vcgen_stroke.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vpgen_clip_polygon.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vpgen_clip_polyline.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\agg_vpgen_segmentator.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\ctrl\agg_rbox_ctrl.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\platform\win32\agg_platform_support.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\src\platform\win32\agg_win32_bmp.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\examples\make_arrows.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\agg_src\examples\make_gb_poly.cpp">
+ <Filter>agg_src</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="agg_src">
+ <UniqueIdentifier>{31703eea-b46a-4b7c-93fe-7b3889404c6f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="ClipperLib">
+ <UniqueIdentifier>{0d7823fd-c31f-4f5d-b1f5-3f3c91751bf3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="agg_clipper">
+ <UniqueIdentifier>{0ea31d0f-7e25-4f22-964b-8c74b5ced2fd}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\clipper.hpp">
+ <Filter>ClipperLib</Filter>
+ </ClInclude>
+ <ClInclude Include="agg_conv_clipper.h">
+ <Filter>agg_clipper</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/cpp/cpp_agg/icon.res b/cpp/cpp_agg/icon.res
new file mode 100644
index 0000000..a4fc1a7
Binary files /dev/null and b/cpp/cpp_agg/icon.res differ
diff --git a/cpp/cpp_cairo/Cairo Resources.txt b/cpp/cpp_cairo/Cairo Resources.txt
new file mode 100644
index 0000000..e868eec
--- /dev/null
+++ b/cpp/cpp_cairo/Cairo Resources.txt
@@ -0,0 +1,6 @@
+http://cairographics.org/
+
+The Windows dynamic linked libraries necessary to run Cairo can be downloaded from
+http://www.gtk.org/download/win32.php
+All the dlls listed under the heading "Required third party dependencies" are
+required except gettext-runtime.dll.
diff --git a/cpp/cpp_cairo/cairo.sln b/cpp/cpp_cairo/cairo.sln
new file mode 100644
index 0000000..651e81a
--- /dev/null
+++ b/cpp/cpp_cairo/cairo.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cairo", "cairo.vcxproj", "{6AFBCA2B-9262-6D28-7506-E13747347388}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6AFBCA2B-9262-6D28-7506-E13747347388}.Debug|Win32.ActiveCfg = Debug|Win32
+ {6AFBCA2B-9262-6D28-7506-E13747347388}.Debug|Win32.Build.0 = Debug|Win32
+ {6AFBCA2B-9262-6D28-7506-E13747347388}.Release|Win32.ActiveCfg = Release|Win32
+ {6AFBCA2B-9262-6D28-7506-E13747347388}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/cpp/cpp_cairo/cairo.vcxproj b/cpp/cpp_cairo/cairo.vcxproj
new file mode 100644
index 0000000..9816ce1
--- /dev/null
+++ b/cpp/cpp_cairo/cairo.vcxproj
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>C:\Program Files %28x86%29\Borland\Clipper\PreReleaseTesting\cpp\cairo_src;$(IncludePath)</IncludePath>
+ <SourcePath>C:\Program Files %28x86%29\Borland\Clipper\PreReleaseTesting\cpp\cairo_src;C:\Program Files %28x86%29\Borland\graphics32\Examples\Vcl\Drawing\Clipper\PreReleaseTesting\cpp\cairo_src;$(SourcePath)</SourcePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>C:\Program Files (x86)\Borland\graphics32\Examples\Vcl\Drawing\Clipper\PreReleaseTesting\cpp\cairo_src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ <UseUnicodeForAssemblerListing>
+ </UseUnicodeForAssemblerListing>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>C:\Program Files (x86)\Borland\graphics32\Examples\Vcl\Drawing\Clipper\PreReleaseTesting\cpp\cairo_src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\clipper.cpp" />
+ <ClCompile Include="cairo_clipper.cpp" />
+ <ClCompile Include="main.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\cairo_src\cairo.h" />
+ <ClInclude Include="..\clipper.hpp" />
+ <ClInclude Include="cairo_clipper.hpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Library Include="libcairo-2.lib" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/cpp/cpp_cairo/cairo_clipper.cpp b/cpp/cpp_cairo/cairo_clipper.cpp
new file mode 100644
index 0000000..5767b9d
--- /dev/null
+++ b/cpp/cpp_cairo/cairo_clipper.cpp
@@ -0,0 +1,134 @@
+/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 1.1 *
+* Date : 4 April 2011 *
+* Copyright : Angus Johnson 2010-2011 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+* Modified by Mike Owens to support coordinate transformation *
+*******************************************************************************/
+
+#include <stdexcept>
+#include <cmath>
+#include <cairo.h>
+#include "clipper.hpp"
+#include "cairo_clipper.hpp"
+
+namespace ClipperLib {
+ namespace cairo {
+
+ namespace {
+
+ inline cInt Round(double val)
+ {
+ if ((val < 0)) return (cInt)(val - 0.5); else return (cInt)(val + 0.5);
+ }
+
+ void transform_point(cairo_t* pen, Transform transform, cInt* x, cInt* y)
+ {
+ double _x = (double)*x, _y = (double)*y;
+ switch (transform)
+ {
+ case tDeviceToUser:
+ cairo_device_to_user(pen, &_x, &_y);
+ break;
+ case tUserToDevice:
+ cairo_user_to_device(pen, &_x, &_y);
+ break;
+ default:
+ ;
+ }
+ *x = Round(_x); *y = Round(_y);
+ }
+ }
+
+ void cairo_to_clipper(cairo_t* cr,
+ Paths &pg,
+ int scaling_factor,
+ Transform transform)
+ {
+ if (scaling_factor > 8 || scaling_factor < 0)
+ throw clipperCairoException("cairo_to_clipper: invalid scaling factor");
+ double scaling = std::pow((double)10, scaling_factor);
+
+ pg.clear();
+ cairo_path_t *path = cairo_copy_path_flat(cr);
+
+ int poly_count = 0;
+ for (int i = 0; i < path->num_data; i += path->data[i].header.length) {
+ if( path->data[i].header.type == CAIRO_PATH_CLOSE_PATH) poly_count++;
+ }
+
+ pg.resize(poly_count);
+ int i = 0, pc = 0;
+ while (pc < poly_count)
+ {
+ int vert_count = 1;
+ int j = i;
+ while(j < path->num_data &&
+ path->data[j].header.type != CAIRO_PATH_CLOSE_PATH)
+ {
+ if (path->data[j].header.type == CAIRO_PATH_LINE_TO)
+ vert_count++;
+ j += path->data[j].header.length;
+ }
+ pg[pc].resize(vert_count);
+ if (path->data[i].header.type != CAIRO_PATH_MOVE_TO) {
+ pg.resize(pc);
+ break;
+ }
+ pg[pc][0].X = Round(path->data[i+1].point.x *scaling);
+ pg[pc][0].Y = Round(path->data[i+1].point.y *scaling);
+ if (transform != tNone)
+ transform_point(cr, transform, &pg[pc][0].X, &pg[pc][0].Y);
+
+ i += path->data[i].header.length;
+
+ j = 1;
+ while (j < vert_count && i < path->num_data &&
+ path->data[i].header.type == CAIRO_PATH_LINE_TO) {
+ pg[pc][j].X = Round(path->data[i+1].point.x *scaling);
+ pg[pc][j].Y = Round(path->data[i+1].point.y *scaling);
+ if (transform != tNone)
+ transform_point(cr, transform, &pg[pc][j].X, &pg[pc][j].Y);
+ j++;
+ i += path->data[i].header.length;
+ }
+ pc++;
+ i += path->data[i].header.length;
+ }
+ cairo_path_destroy(path);
+ }
+ //--------------------------------------------------------------------------
+
+ void clipper_to_cairo(const Paths &pg,
+ cairo_t* cr,
+ int scaling_factor,
+ Transform transform)
+ {
+ if (scaling_factor > 8 || scaling_factor < 0)
+ throw clipperCairoException("clipper_to_cairo: invalid scaling factor");
+ double scaling = std::pow((double)10, scaling_factor);
+ for (size_t i = 0; i < pg.size(); ++i)
+ {
+ size_t sz = pg[i].size();
+ if (sz < 3)
+ continue;
+ cairo_new_sub_path(cr);
+ for (size_t j = 0; j < sz; ++j) {
+ cInt x = pg[i][j].X, y = pg[i][j].Y;
+ if (transform != tNone)
+ transform_point(cr, transform, &x, &y);
+ cairo_line_to(cr, (double)x / scaling, (double)y / scaling);
+ }
+ cairo_close_path(cr);
+ }
+ }
+ //--------------------------------------------------------------------------
+
+ }
+}
diff --git a/cpp/cpp_cairo/cairo_clipper.hpp b/cpp/cpp_cairo/cairo_clipper.hpp
new file mode 100644
index 0000000..7c52dfd
--- /dev/null
+++ b/cpp/cpp_cairo/cairo_clipper.hpp
@@ -0,0 +1,59 @@
+/*******************************************************************************
+* *
+* Author : Angus Johnson *
+* Version : 1.1 *
+* Date : 4 April 2011 *
+* Copyright : Angus Johnson 2010-2011 *
+* *
+* License: *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt *
+* *
+* Modified by Mike Owens to support coordinate transformation *
+*******************************************************************************/
+
+#ifndef CLIPPER_CAIRO_CLIPPER_HPP
+#define CLIPPER_CAIRO_CLIPPER_HPP
+
+#include "clipper.hpp"
+
+typedef struct _cairo cairo_t;
+
+namespace ClipperLib {
+ namespace cairo {
+
+ enum Transform {
+ tNone,
+ tUserToDevice,
+ tDeviceToUser
+ };
+
+//nb: Since Clipper only accepts integer coordinates, fractional values have to
+//be scaled up and down when being passed to and from Clipper. This is easily
+//accomplished by setting the scaling factor (10^x) in the following functions.
+//When scaling, remember that on most platforms, integer is only a 32bit value.
+ void cairo_to_clipper(cairo_t* cr,
+ ClipperLib::Paths &pg,
+ int scaling_factor = 0,
+ Transform transform = tNone);
+
+ void clipper_to_cairo(const ClipperLib::Paths &pg,
+ cairo_t* cr,
+ int scaling_factor = 0,
+ Transform transform = tNone);
+ }
+
+ class clipperCairoException : public std::exception
+ {
+ public:
+ clipperCairoException(const char* description)
+ throw(): std::exception(), m_description (description) {}
+ virtual ~clipperCairoException() throw() {}
+ virtual const char* what() const throw() {return m_description.c_str();}
+ private:
+ std::string m_description;
+ };
+}
+
+#endif
+
diff --git a/cpp/cpp_cairo/libcairo-2.lib b/cpp/cpp_cairo/libcairo-2.lib
new file mode 100644
index 0000000..9be9bc1
Binary files /dev/null and b/cpp/cpp_cairo/libcairo-2.lib differ
diff --git a/cpp/cpp_cairo/main.cpp b/cpp/cpp_cairo/main.cpp
new file mode 100644
index 0000000..b196a22
--- /dev/null
+++ b/cpp/cpp_cairo/main.cpp
@@ -0,0 +1,182 @@
+//---------------------------------------------------------------------------
+
+#include <windows.h>
+#include <cstring>
+#include <cmath>
+#include <sstream>
+#pragma hdrstop
+
+#include "clipper.hpp"
+#include "cairo.h"
+#include "cairo-win32.h"
+#include "cairo_clipper.hpp"
+//---------------------------------------------------------------------------
+
+int offsetVal;
+
+LRESULT CALLBACK WndProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+int CALLBACK wWinMain(HINSTANCE hInstance,
+ HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
+{
+ WCHAR* ClsName = L"CairoApp";
+ WCHAR* WndName = L"A Simple Cairo Clipper Demo";
+ offsetVal = 0;
+
+ MSG Msg;
+ HWND hWnd;
+ WNDCLASSEX WndClsEx;
+
+ // Create the application window
+ WndClsEx.cbSize = sizeof(WNDCLASSEX);
+ WndClsEx.style = CS_HREDRAW | CS_VREDRAW;
+ WndClsEx.lpfnWndProc = WndProcedure;
+ WndClsEx.cbClsExtra = 0;
+ WndClsEx.cbWndExtra = 0;
+ WndClsEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ WndClsEx.hCursor = LoadCursor(NULL, IDC_ARROW);
+ WndClsEx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
+ WndClsEx.lpszMenuName = NULL;
+ WndClsEx.lpszClassName = ClsName;
+ WndClsEx.hInstance = hInstance;
+ WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
+
+ // Register the application
+ RegisterClassEx(&WndClsEx);
+
+ // Create the window object
+ hWnd = CreateWindow(ClsName, WndName, WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
+ NULL, NULL, hInstance, NULL);
+ if( !hWnd ) return 0;
+
+ ShowWindow(hWnd, SW_SHOWNORMAL);
+ UpdateWindow(hWnd);
+
+ while( GetMessage(&Msg, NULL, 0, 0) )
+ {
+ TranslateMessage(&Msg);
+ DispatchMessage(&Msg);
+ }
+ return Msg.wParam;
+}
+//------------------------------------------------------------------------------
+
+
+void OnPaint(HWND hWnd, HDC dc)
+{
+ RECT rec;
+ GetClientRect(hWnd, &rec);
+ cairo_surface_t* surface = cairo_win32_surface_create(dc);
+ cairo_t* cr = cairo_create(surface);
+
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
+ cairo_set_line_width (cr, 2.0);
+
+ //fill background with white ...
+ cairo_rectangle(cr, 0, 0, rec.right, rec.bottom);
+ cairo_set_source_rgba(cr, 1, 1, 1, 1);
+ cairo_fill(cr);
+
+ using namespace ClipperLib;
+
+ const int scaling = 2;
+
+ Clipper clpr; //clipper class
+ Paths pg; //std::vector for polygon(s) storage
+
+ //create a circular pattern, add the path to clipper and then draw it ...
+ cairo_arc(cr, 170,110,70,0,2*3.1415926);
+ cairo_close_path(cr);
+ cairo::cairo_to_clipper(cr, pg, scaling);
+ clpr.AddPaths(pg, ptSubject, true);
+ cairo_set_source_rgba(cr, 0, 0, 1, 0.25);
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.5);
+ cairo_stroke (cr);
+
+ //draw a star and another circle, add them to clipper and draw ...
+ cairo_move_to(cr, 60,110);
+ cairo_line_to (cr, 240,70);
+ cairo_line_to (cr, 110,210);
+ cairo_line_to (cr, 140,25);
+ cairo_line_to (cr, 230,200);
+ cairo_close_path(cr);
+ cairo_new_sub_path(cr);
+ cairo_arc(cr, 190,50,20,0,2*3.1415926);
+ cairo_close_path(cr);
+ cairo::cairo_to_clipper(cr, pg, scaling);
+ clpr.AddPaths(pg, ptClip, true);
+ cairo_set_source_rgba(cr, 1, 0, 0, 0.25);
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.5);
+ cairo_stroke (cr);
+
+ clpr.Execute(ctIntersection, pg, pftNonZero, pftNonZero);
+ //now do something fancy with the returned polygons ...
+ OffsetPaths(pg, pg, offsetVal * std::pow((double)10,scaling), jtMiter, etClosed);
+
+ //finally copy the clipped path back to the cairo context and draw it ...
+ cairo::clipper_to_cairo(pg, cr, scaling);
+ cairo_set_source_rgba(cr, 1, 1, 0, 1);
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_stroke (cr);
+
+ cairo_text_extents_t extent;
+ cairo_set_font_size(cr,11);
+ std::stringstream ss;
+ ss << "Polygon offset = " << offsetVal << ". (Adjust with arrow keys)";
+ std::string s = ss.str();
+ cairo_text_extents(cr, s.c_str(), &extent);
+ cairo_move_to(cr, 10, rec.bottom - extent.height);
+ cairo_show_text(cr, s.c_str());
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+}
+//------------------------------------------------------------------------------
+
+LRESULT CALLBACK WndProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
+{
+ PAINTSTRUCT ps;
+ HDC Handle;
+
+ switch(Msg)
+ {
+ case WM_DESTROY:
+ PostQuitMessage(WM_QUIT);
+ return 0;
+
+ case WM_PAINT:
+ Handle = BeginPaint(hWnd, &ps);
+ OnPaint(hWnd, Handle);
+ EndPaint(hWnd, &ps);
+ return 0;
+
+ case WM_KEYDOWN:
+ switch(wParam)
+ {
+ case VK_ESCAPE:
+ PostQuitMessage(0);
+ return 0;
+ case VK_RIGHT:
+ case VK_UP:
+ if (offsetVal < 20) offsetVal++;
+ InvalidateRect(hWnd, 0, false);
+ return 0;
+ case VK_LEFT:
+ case VK_DOWN:
+ if (offsetVal > -20) offsetVal--;
+ InvalidateRect(hWnd, 0, false);
+ return 0;
+ default:
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+ }
+
+ default:
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+ }
+}
+//---------------------------------------------------------------------------
+
diff --git a/cpp/cpp_console/clipper_console_demo.cpp b/cpp/cpp_console/clipper_console_demo.cpp
new file mode 100644
index 0000000..a63b78c
--- /dev/null
+++ b/cpp/cpp_console/clipper_console_demo.cpp
@@ -0,0 +1,461 @@
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <cmath>
+#include <algorithm>
+#include <ctime>
+#include <cstdlib>
+#include <cstdio>
+#include <vector>
+#include <iomanip>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include "clipper.hpp"
+
+using namespace std;
+using namespace ClipperLib;
+
+//---------------------------------------------------------------------------
+// SVGBuilder class
+// a very simple class that creates an SVG image file
+//---------------------------------------------------------------------------
+
+class SVGBuilder
+{
+ static string ColorToHtml(unsigned clr)
+ {
+ stringstream ss;
+ ss << '#' << hex << std::setfill('0') << setw(6) << (clr & 0xFFFFFF);
+ return ss.str();
+ }
+ //------------------------------------------------------------------------------
+
+ static float GetAlphaAsFrac(unsigned clr)
+ {
+ return ((float)(clr >> 24) / 255);
+ }
+ //------------------------------------------------------------------------------
+
+ class StyleInfo
+ {
+ public:
+ PolyFillType pft;
+ unsigned brushClr;
+ unsigned penClr;
+ double penWidth;
+ bool showCoords;
+
+ StyleInfo()
+ {
+ pft = pftNonZero;
+ brushClr = 0xFFFFFFCC;
+ penClr = 0xFF000000;
+ penWidth = 0.8;
+ showCoords = false;
+ }
+ };
+
+ class PolyInfo
+ {
+ public:
+ Paths paths;
+ StyleInfo si;
+
+ PolyInfo(Paths paths, StyleInfo style)
+ {
+ this->paths = paths;
+ this->si = style;
+ }
+ };
+
+ typedef std::vector<PolyInfo> PolyInfoList;
+
+private:
+ PolyInfoList polyInfos;
+ static const std::string svg_xml_start[];
+ static const std::string poly_end[];
+
+public:
+ StyleInfo style;
+
+ void AddPaths(Paths& poly)
+ {
+ if (poly.size() == 0) return;
+ polyInfos.push_back(PolyInfo(poly, style));
+ }
+
+ bool SaveToFile(const string& filename, double scale = 1.0, int margin = 10)
+ {
+ //calculate the bounding rect ...
+ PolyInfoList::size_type i = 0;
+ Paths::size_type j;
+ while (i < polyInfos.size())
+ {
+ j = 0;
+ while (j < polyInfos[i].paths.size() &&
+ polyInfos[i].paths[j].size() == 0) j++;
+ if (j < polyInfos[i].paths.size()) break;
+ i++;
+ }
+ if (i == polyInfos.size()) return false;
+
+ IntRect rec;
+ rec.left = polyInfos[i].paths[j][0].X;
+ rec.right = rec.left;
+ rec.top = polyInfos[i].paths[j][0].Y;
+ rec.bottom = rec.top;
+ for ( ; i < polyInfos.size(); ++i)
+ for (Paths::size_type j = 0; j < polyInfos[i].paths.size(); ++j)
+ for (Path::size_type k = 0; k < polyInfos[i].paths[j].size(); ++k)
+ {
+ IntPoint ip = polyInfos[i].paths[j][k];
+ if (ip.X < rec.left) rec.left = ip.X;
+ else if (ip.X > rec.right) rec.right = ip.X;
+ if (ip.Y < rec.top) rec.top = ip.Y;
+ else if (ip.Y > rec.bottom) rec.bottom = ip.Y;
+ }
+
+ if (scale == 0) scale = 1.0;
+ if (margin < 0) margin = 0;
+ rec.left = (cInt)((double)rec.left * scale);
+ rec.top = (cInt)((double)rec.top * scale);
+ rec.right = (cInt)((double)rec.right * scale);
+ rec.bottom = (cInt)((double)rec.bottom * scale);
+ cInt offsetX = -rec.left + margin;
+ cInt offsetY = -rec.top + margin;
+
+ ofstream file;
+ file.open(filename);
+ if (!file.is_open()) return false;
+ file.setf(ios::fixed);
+ file.precision(0);
+ file << svg_xml_start[0] <<
+ ((rec.right - rec.left) + margin*2) << "px" << svg_xml_start[1] <<
+ ((rec.bottom - rec.top) + margin*2) << "px" << svg_xml_start[2] <<
+ ((rec.right - rec.left) + margin*2) << " " <<
+ ((rec.bottom - rec.top) + margin*2) << svg_xml_start[3];
+ setlocale(LC_NUMERIC, "C");
+ file.precision(2);
+
+ for (PolyInfoList::size_type i = 0; i < polyInfos.size(); ++i)
+ {
+ file << " <path d=\"";
+ for (Paths::size_type j = 0; j < polyInfos[i].paths.size(); ++j)
+ {
+ if (polyInfos[i].paths[j].size() < 3) continue;
+ file << " M " << ((double)polyInfos[i].paths[j][0].X * scale + offsetX) <<
+ " " << ((double)polyInfos[i].paths[j][0].Y * scale + offsetY);
+ for (Path::size_type k = 1; k < polyInfos[i].paths[j].size(); ++k)
+ {
+ IntPoint ip = polyInfos[i].paths[j][k];
+ double x = (double)ip.X * scale;
+ double y = (double)ip.Y * scale;
+ file << " L " << (x + offsetX) << " " << (y + offsetY);
+ }
+ file << " z";
+ }
+ file << poly_end[0] << ColorToHtml(polyInfos[i].si.brushClr) <<
+ poly_end[1] << GetAlphaAsFrac(polyInfos[i].si.brushClr) <<
+ poly_end[2] <<
+ (polyInfos[i].si.pft == pftEvenOdd ? "evenodd" : "nonzero") <<
+ poly_end[3] << ColorToHtml(polyInfos[i].si.penClr) <<
+ poly_end[4] << GetAlphaAsFrac(polyInfos[i].si.penClr) <<
+ poly_end[5] << polyInfos[i].si.penWidth << poly_end[6];
+
+ if (polyInfos[i].si.showCoords)
+ {
+ file << "<g font-family=\"Verdana\" font-size=\"11\" fill=\"black\">\n\n";
+ for (Paths::size_type j = 0; j < polyInfos[i].paths.size(); ++j)
+ {
+ if (polyInfos[i].paths[j].size() < 3) continue;
+ for (Path::size_type k = 0; k < polyInfos[i].paths[j].size(); ++k)
+ {
+ IntPoint ip = polyInfos[i].paths[j][k];
+ file << "<text x=\"" << (int)(ip.X * scale + offsetX) <<
+ "\" y=\"" << (int)(ip.Y * scale + offsetY) << "\">" <<
+ ip.X << "," << ip.Y << "</text>\n";
+ file << "\n";
+ }
+ }
+ file << "</g>\n";
+ }
+ }
+ file << "</svg>\n";
+ file.close();
+ setlocale(LC_NUMERIC, "");
+ return true;
+ }
+}; //SVGBuilder
+//------------------------------------------------------------------------------
+
+const std::string SVGBuilder::svg_xml_start [] =
+ {"<?xml version=\"1.0\" standalone=\"no\"?>\n"
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"
+ "\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n\n"
+ "<svg width=\"",
+ "\" height=\"",
+ "\" viewBox=\"0 0 ",
+ "\" version=\"1.0\" xmlns=\"http://www.w3.org/2000/svg\">\n\n"
+ };
+const std::string SVGBuilder::poly_end [] =
+ {"\"\n style=\"fill:",
+ "; fill-opacity:",
+ "; fill-rule:",
+ "; stroke:",
+ "; stroke-opacity:",
+ "; stroke-width:",
+ ";\"/>\n\n"
+ };
+
+//------------------------------------------------------------------------------
+// Miscellaneous function ...
+//------------------------------------------------------------------------------
+
+bool SaveToFile(const string& filename, Paths &ppg, double scale = 1.0, unsigned decimal_places = 0)
+{
+ ofstream ofs(filename);
+ if (!ofs) return false;
+
+ if (decimal_places > 8) decimal_places = 8;
+ ofs << setprecision(decimal_places) << std::fixed;
+
+ Path pg;
+ for (size_t i = 0; i < ppg.size(); ++i)
+ {
+ for (size_t j = 0; j < ppg[i].size(); ++j)
+ ofs << ppg[i][j].X / scale << ", " << ppg[i][j].Y / scale << "," << std::endl;
+ ofs << std::endl;
+ }
+ ofs.close();
+ return true;
+}
+//------------------------------------------------------------------------------
+
+bool LoadFromFile(Paths &ppg, const string& filename, double scale)
+{
+ //file format assumes:
+ // 1. path coordinates (x,y) are comma separated (+/- spaces) and
+ // each coordinate is on a separate line
+ // 2. each path is separated by one or more blank lines
+
+ ppg.clear();
+ ifstream ifs(filename);
+ if (!ifs) return false;
+ string line;
+ Path pg;
+ while (std::getline(ifs, line))
+ {
+ stringstream ss(line);
+ double X = 0.0, Y = 0.0;
+ if (!(ss >> X))
+ {
+ //ie blank lines => flag start of next polygon
+ if (pg.size() > 0) ppg.push_back(pg);
+ pg.clear();
+ continue;
+ }
+ char c = ss.peek();
+ while (c == ' ') {ss.read(&c, 1); c = ss.peek();} //gobble spaces before comma
+ if (c == ',') {ss.read(&c, 1); c = ss.peek();} //gobble comma
+ while (c == ' ') {ss.read(&c, 1); c = ss.peek();} //gobble spaces after comma
+ if (!(ss >> Y)) break; //oops!
+ pg.push_back(IntPoint((cInt)(X * scale),(cInt)(Y * scale)));
+ }
+ if (pg.size() > 0) ppg.push_back(pg);
+ ifs.close();
+ return true;
+}
+//------------------------------------------------------------------------------
+
+void MakeRandomPoly(int edgeCount, int width, int height, Paths & poly)
+{
+ poly.resize(1);
+ poly[0].resize(edgeCount);
+ for (int i = 0; i < edgeCount; i++){
+ poly[0][i].X = rand() % width;
+ poly[0][i].Y = rand() % height;
+ }
+}
+//------------------------------------------------------------------------------
+
+bool ASCII_icompare(const char* str1, const char* str2)
+{
+ //case insensitive compare for ASCII chars only
+ while (*str1)
+ {
+ if (toupper(*str1) != toupper(*str2)) return false;
+ str1++;
+ str2++;
+ }
+ return (!*str2);
+}
+
+//------------------------------------------------------------------------------
+// Main entry point ...
+//------------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+ if (argc > 1 &&
+ (strcmp(argv[1], "-b") == 0 || strcmp(argv[1], "--benchmark") == 0))
+ {
+ //do a benchmark test that creates a subject and a clip polygon both with
+ //100 vertices randomly placed in a 400 * 400 space. Then perform an
+ //intersection operation based on even-odd filling. Repeat all this X times.
+ int loop_cnt = 1000;
+ char * dummy;
+ if (argc > 2) loop_cnt = strtol(argv[2], &dummy, 10);
+ if (loop_cnt == 0) loop_cnt = 1000;
+ cout << "\nPerforming " << loop_cnt << " random intersection operations ... ";
+ srand((int)time(0));
+ int error_cnt = 0;
+ Paths subject, clip, solution;
+ Clipper clpr;
+
+ time_t time_start = clock();
+ for (int i = 0; i < loop_cnt; i++) {
+ MakeRandomPoly(100, 400, 400, subject);
+ MakeRandomPoly(100, 400, 400, clip);
+ clpr.Clear();
+ clpr.AddPaths(subject, ptSubject, true);
+ clpr.AddPaths(clip, ptClip, true);
+ if (!clpr.Execute(ctIntersection, solution, pftEvenOdd, pftEvenOdd))
+ error_cnt++;
+ }
+ double time_elapsed = double(clock() - time_start)/CLOCKS_PER_SEC;
+
+ cout << "\nFinished in " << time_elapsed << " secs with ";
+ cout << error_cnt << " errors.\n\n";
+ //let's save the very last result ...
+ SaveToFile("Subject.txt", subject);
+ SaveToFile("Clip.txt", clip);
+ SaveToFile("Solution.txt", solution);
+
+ //and see the final clipping op as an image too ...
+ SVGBuilder svg;
+ svg.style.penWidth = 0.8;
+ svg.style.pft = pftEvenOdd;
+ svg.style.brushClr = 0x1200009C;
+ svg.style.penClr = 0xCCD3D3DA;
+ svg.AddPaths(subject);
+ svg.style.brushClr = 0x129C0000;
+ svg.style.penClr = 0xCCFFA07A;
+ svg.AddPaths(clip);
+ svg.style.brushClr = 0x6080ff9C;
+ svg.style.penClr = 0xFF003300;
+ svg.style.pft = pftNonZero;
+ svg.AddPaths(solution);
+ svg.SaveToFile("solution.svg");
+ return 0;
+ }
+
+ if (argc < 3)
+ {
+ cout << "\nUsage:\n"
+ << " clipper_console_demo S_FILE C_FILE CT [S_FILL C_FILL] [PRECISION] [SVG_SCALE]\n"
+ << "or\n"
+ << " clipper_console_demo --benchmark [LOOP_COUNT]\n\n"
+ << "Legend: [optional parameters in square braces]; {comments in curly braces}\n\n"
+ << "Parameters:\n"
+ << " S_FILE & C_FILE are the subject and clip input files (see format below)\n"
+ << " CT: cliptype, either INTERSECTION or UNION or DIFFERENCE or XOR\n"
+ << " SUBJECT_FILL & CLIP_FILL: either EVENODD or NONZERO. Default: NONZERO\n"
+ << " PRECISION (in decimal places) for input data. Default = 0\n"
+ << " SVG_SCALE: scale of the output svg image. Default = 1.0\n"
+ << " LOOP_COUNT is the number of random clipping operations. Default = 1000\n\n"
+ << "\nFile format for input and output files:\n"
+ << " X, Y[,] {first vertex of first path}\n"
+ << " X, Y[,] {next vertex of first path}\n"
+ << " {etc.}\n"
+ << " X, Y[,] {last vertex of first path}\n"
+ << " {blank line(s) between paths}\n"
+ << " X, Y[,] {first vertex of second path}\n"
+ << " X, Y[,] {next vertex of second path}\n"
+ << " {etc.}\n\n"
+ << "Examples:\n"
+ << " clipper_console_demo \"subj.txt\" \"clip.txt\" INTERSECTION EVENODD EVENODD\n"
+ << " clipper_console_demo --benchmark 1000\n";
+ return 1;
+ }
+
+ int scale_log10 = 0;
+ char* dummy;
+ if (argc > 6) scale_log10 = strtol(argv[6], &dummy, 10);
+ double scale = std::pow(double(10), scale_log10);
+
+ double svg_scale = 1.0;
+ if (argc > 7) svg_scale = strtod(argv[7], &dummy);
+ svg_scale /= scale;
+
+ Paths subject, clip;
+
+ if (!LoadFromFile(subject, argv[1], scale))
+ {
+ cerr << "\nCan't open the file " << argv[1]
+ << " or the file format is invalid.\n";
+ return 1;
+ }
+ if (!LoadFromFile(clip, argv[2], scale))
+ {
+ cerr << "\nCan't open the file " << argv[2]
+ << " or the file format is invalid.\n";
+ return 1;
+ }
+
+ ClipType clipType = ctIntersection;
+ const string sClipType[] = {"INTERSECTION", "UNION", "DIFFERENCE", "XOR"};
+
+ if (argc > 3)
+ {
+ if (ASCII_icompare(argv[3], "XOR")) clipType = ctXor;
+ else if (ASCII_icompare(argv[3], "UNION")) clipType = ctUnion;
+ else if (ASCII_icompare(argv[3], "DIFFERENCE")) clipType = ctDifference;
+ else clipType = ctIntersection;
+ }
+
+ PolyFillType subj_pft = pftNonZero, clip_pft = pftNonZero;
+ if (argc > 5)
+ {
+ if (ASCII_icompare(argv[4], "EVENODD")) subj_pft = pftEvenOdd;
+ if (ASCII_icompare(argv[5], "EVENODD")) clip_pft = pftEvenOdd;
+ }
+
+ Clipper c;
+ c.AddPaths(subject, ptSubject, true);
+ c.AddPaths(clip, ptClip, true);
+ Paths solution;
+
+ if (!c.Execute(clipType, solution, subj_pft, clip_pft))
+ {
+ cout << (sClipType[clipType] + " failed!\n\n");
+ return 1;
+ }
+
+ cout << "\nFinished!\n\n";
+ SaveToFile("solution.txt", solution, scale);
+
+ //let's see the result too ...
+ SVGBuilder svg;
+ svg.style.penWidth = 0.8;
+ svg.style.brushClr = 0x1200009C;
+ svg.style.penClr = 0xCCD3D3DA;
+ svg.style.pft = subj_pft;
+ svg.AddPaths(subject);
+ svg.style.brushClr = 0x129C0000;
+ svg.style.penClr = 0xCCFFA07A;
+ svg.style.pft = clip_pft;
+ svg.AddPaths(clip);
+ svg.style.brushClr = 0x6080ff9C;
+ svg.style.penClr = 0xFF003300;
+ svg.style.pft = pftNonZero;
+ svg.AddPaths(solution);
+ svg.SaveToFile("solution.svg", svg_scale);
+
+ //finally, show the svg image in the default viewing application
+ system("solution.svg");
+ return 0;
+}
+//---------------------------------------------------------------------------
diff --git a/cpp/cpp_console/clipper_console_demo.sln b/cpp/cpp_console/clipper_console_demo.sln
new file mode 100644
index 0000000..147a7b5
--- /dev/null
+++ b/cpp/cpp_console/clipper_console_demo.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "clipper_console_demo", "clipper_console_demo.vcxproj", "{0710EFB8-6D8B-4DF1-9D14-907AE3857FE4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0710EFB8-6D8B-4DF1-9D14-907AE3857FE4}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0710EFB8-6D8B-4DF1-9D14-907AE3857FE4}.Debug|Win32.Build.0 = Debug|Win32
+ {0710EFB8-6D8B-4DF1-9D14-907AE3857FE4}.Release|Win32.ActiveCfg = Release|Win32
+ {0710EFB8-6D8B-4DF1-9D14-907AE3857FE4}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/cpp/cpp_console/clipper_console_demo.vcxproj b/cpp/cpp_console/clipper_console_demo.vcxproj
new file mode 100644
index 0000000..0f0b5ee
--- /dev/null
+++ b/cpp/cpp_console/clipper_console_demo.vcxproj
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{0710EFB8-6D8B-4DF1-9D14-907AE3857FE4}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>clipper_console_demo</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderFile>
+ </PrecompiledHeaderFile>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderFile>
+ </PrecompiledHeaderFile>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="clipper.hpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="clipper.cpp" />
+ <ClCompile Include="clipper_console_demo.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ </PrecompiledHeaderFile>
+ </ClCompile>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/cpp/cpp_opengl/clipper_demo.sln b/cpp/cpp_opengl/clipper_demo.sln
new file mode 100644
index 0000000..56899d2
--- /dev/null
+++ b/cpp/cpp_opengl/clipper_demo.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "clipper_demo", "clipper_demo.vcxproj", "{4D48A928-F288-40F9-9FA0-4AC650602636}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4D48A928-F288-40F9-9FA0-4AC650602636}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D48A928-F288-40F9-9FA0-4AC650602636}.Debug|Win32.Build.0 = Debug|Win32
+ {4D48A928-F288-40F9-9FA0-4AC650602636}.Release|Win32.ActiveCfg = Release|Win32
+ {4D48A928-F288-40F9-9FA0-4AC650602636}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/cpp/cpp_opengl/clipper_demo.vcxproj b/cpp/cpp_opengl/clipper_demo.vcxproj
new file mode 100644
index 0000000..d697681
--- /dev/null
+++ b/cpp/cpp_opengl/clipper_demo.vcxproj
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{4D48A928-F288-40F9-9FA0-4AC650602636}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>opengl_app</RootNamespace>
+ <ProjectName>clipper_demo</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>v120</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ <PlatformToolset>v120</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <SourcePath>$(SourcePath)</SourcePath>
+ <IncludePath>$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>opengl32.lib;glu32.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level4</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalDependencies>opengl32.lib;glu32.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\clipper.cpp" />
+ <ClCompile Include="main.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\clipper.hpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="icon.res" />
+ <Resource Include="menu.res" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/cpp/cpp_opengl/icon.res b/cpp/cpp_opengl/icon.res
new file mode 100644
index 0000000..a4fc1a7
Binary files /dev/null and b/cpp/cpp_opengl/icon.res differ
diff --git a/cpp/cpp_opengl/main.cpp b/cpp/cpp_opengl/main.cpp
new file mode 100644
index 0000000..204716b
--- /dev/null
+++ b/cpp/cpp_opengl/main.cpp
@@ -0,0 +1,621 @@
+#include <windows.h>
+#include <commctrl.h>
+#include <gl/gl.h>
+#include <gl/glu.h>
+//#include <gl/glut.h>
+#include <ctime>
+#include <cmath>
+#include <sstream>
+#include <fstream>
+#include <iomanip>
+#include "../clipper.hpp"
+
+using namespace std;
+using namespace ClipperLib;
+
+enum poly_color_type { pctSubject, pctClip, pctSolution };
+
+//global vars ...
+HWND hWnd;
+HWND hStatus;
+HDC hDC;
+HGLRC hRC;
+ClipType ct = ctIntersection;
+PolyFillType pft = pftEvenOdd;
+JoinType jt = jtRound;
+bool show_clipping = true;
+Paths sub, clp, sol;
+int VertCount = 5;
+int scale = 10;
+double delta = 0.0;
+
+const LPCWSTR helpText =
+L"Clipper Demo tips...\n\n"
+L"I - for Intersection operations.\n"
+L"U - for Union operations.\n"
+L"D - for Difference operations.\n"
+L"X - for XOR operations.\n"
+L"------------------------------\n"
+L"Q - Toggle clipping on/off.\n"
+L"------------------------------\n"
+L"E - for EvenOdd fills.\n"
+L"Z - for NonZero fills.\n"
+L"P - for Positive fills.\n"
+L"N - for Negative fills.\n"
+L"------------------------------\n"
+L"nn<ENTER> - number of vertices (3..50).\n"
+L"------------------------------\n"
+L"UP arrow - Expand Solution.\n"
+L"DN arrow - Contract Solution.\n"
+L"LT or RT arrow - Reset Solution.\n"
+L"------------------------------\n"
+L"M - Miter OffsetPolygons.\n"
+L"S - Square OffsetPolygons.\n"
+L"R - Round OffsetPolygons.\n"
+L"------------------------------\n"
+L"SPACE, ENTER or click to refresh.\n"
+L"F1 - to see this help dialog again.\n"
+L"Esc - to quit.\n";
+
+typedef std::vector< GLdouble* > Vectors;
+Vectors vectors;
+
+//------------------------------------------------------------------------------
+// heap memory management for GLUtesselator ...
+//------------------------------------------------------------------------------
+
+GLdouble* NewVector(GLdouble x, GLdouble y)
+{
+ GLdouble *vert = new GLdouble[3];
+ vert[0] = x;
+ vert[1] = y;
+ vert[2] = 0;
+ vectors.push_back(vert);
+ return vert;
+}
+//------------------------------------------------------------------------------
+
+void ClearVectors()
+{
+ for (Vectors::size_type i = 0; i < vectors.size(); ++i)
+ delete[] vectors[i];
+ vectors.clear();
+}
+
+//------------------------------------------------------------------------------
+// GLUtesselator callback functions ...
+//------------------------------------------------------------------------------
+
+void CALLBACK BeginCallback(GLenum type)
+{
+ glBegin(type);
+}
+//------------------------------------------------------------------------------
+
+void CALLBACK EndCallback()
+{
+ glEnd();
+}
+//------------------------------------------------------------------------------
+
+void CALLBACK VertexCallback(GLvoid *vertex)
+{
+ glVertex3dv( (const double *)vertex );
+}
+//------------------------------------------------------------------------------
+
+void CALLBACK CombineCallback(GLdouble coords[3],
+ GLdouble*[4], GLfloat[4], GLdouble **dataOut )
+{
+ GLdouble *vert = NewVector(coords[0], coords[1]);
+ *dataOut = vert;
+}
+//------------------------------------------------------------------------------
+
+wstring str2wstr(const std::string &s) {
+ int slength = (int)s.length() + 1;
+ int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
+ wchar_t* buf = new wchar_t[len];
+ MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
+ std::wstring r(buf);
+ delete[] buf;
+ return r;
+}
+//------------------------------------------------------------------------------
+
+void CALLBACK ErrorCallback(GLenum errorCode)
+{
+ std::wstring s = str2wstr( (char *)gluErrorString(errorCode) );
+ SetWindowText(hWnd, s.c_str());
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+// Set up pixel format for graphics initialization
+void SetupPixelFormat()
+{
+ PIXELFORMATDESCRIPTOR pfd;
+ ZeroMemory( &pfd, sizeof(pfd));
+ pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.cColorBits = 32;
+ int pfIdx = ChoosePixelFormat(hDC, &pfd);
+ if (pfIdx != 0) SetPixelFormat(hDC, pfIdx, &pfd);
+}
+//------------------------------------------------------------------------------
+
+// Initialize OpenGL graphics
+void InitGraphics()
+{
+ hDC = GetDC(hWnd);
+ SetupPixelFormat();
+ hRC = wglCreateContext(hDC);
+ wglMakeCurrent(hDC, hRC);
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glTranslatef (0.375, 0.375, 0);
+}
+//------------------------------------------------------------------------------
+
+void MakeRandomPoly(Path &p, int width, int height, int edgeCount)
+{
+ p.resize(edgeCount);
+ for (int i = 0; i < edgeCount; i++)
+ {
+ p[i].X = (rand()%(width -20) +10)*scale;
+ p[i].Y = (rand()%(height -20) +10)*scale;
+ }
+}
+//------------------------------------------------------------------------------
+
+void ResizeGraphics(int width, int height)
+{
+ //setup 2D projection with origin at top-left corner ...
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, width, height, 0, 0, 1);
+ glViewport(0, 0, width, height);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+//------------------------------------------------------------------------------
+
+void DrawPolygon(Paths &pgs, poly_color_type pct)
+{
+ switch (pct)
+ {
+ case pctSubject: glColor4f(0.0f, 0.0f, 1.0f, 0.062f); break;
+ case pctClip: glColor4f(1.0f, 1.0f, 0.0f, 0.062f); break;
+ default: glColor4f(0.0f, 1.0f, 0.0f, 0.25f);
+ }
+
+ GLUtesselator* tess = gluNewTess();
+ gluTessCallback(tess, GLU_TESS_BEGIN, (void (CALLBACK*)())&BeginCallback);
+ gluTessCallback(tess, GLU_TESS_VERTEX, (void (CALLBACK*)())&VertexCallback);
+ gluTessCallback(tess, GLU_TESS_END, (void (CALLBACK*)())&EndCallback);
+ gluTessCallback(tess, GLU_TESS_COMBINE, (void (CALLBACK*)())&CombineCallback);
+ gluTessCallback(tess, GLU_TESS_ERROR, (void (CALLBACK*)())&ErrorCallback);
+ gluTessNormal(tess, 0.0, 0.0, 1.0);
+
+ switch (pft)
+ {
+ case pftEvenOdd:
+ gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
+ break;
+ case pftNonZero:
+ gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
+ break;
+ case pftPositive:
+ gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE);
+ break;
+ default: //case pftNegative
+ if (pct == pctSolution)
+ gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
+ else
+ gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE);
+ }
+
+ gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_FALSE); //GL_FALSE
+ gluTessBeginPolygon(tess, NULL);
+ for (Paths::size_type i = 0; i < pgs.size(); ++i)
+ {
+ gluTessBeginContour(tess);
+ for (Path::size_type j = 0; j < pgs[i].size(); ++j)
+ {
+ GLdouble *vert =
+ NewVector((GLdouble)pgs[i][j].X/scale, (GLdouble)pgs[i][j].Y/scale);
+ gluTessVertex(tess, vert, vert);
+ }
+ gluTessEndContour(tess);
+ }
+ gluTessEndPolygon(tess);
+ ClearVectors();
+
+ switch (pct)
+ {
+ case pctSubject:
+ glColor4f(0.0f, 0.6f, 1.0f, 0.5f);
+ break;
+ case pctClip:
+ glColor4f(1.0f, 0.6f, 0.0f, 0.5f);
+ break;
+ default:
+ glColor4f(0.0f, 0.4f, 0.0f, 1.0f);
+ }
+ if (pct == pctSolution) glLineWidth(1.0f); else glLineWidth(0.8f);
+
+ gluTessProperty(tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
+ gluTessProperty(tess, GLU_TESS_BOUNDARY_ONLY, GL_TRUE);
+ for (Paths::size_type i = 0; i < pgs.size(); ++i)
+ {
+ gluTessBeginPolygon(tess, NULL);
+ gluTessBeginContour(tess);
+ for (Path::size_type j = 0; j < pgs[i].size(); ++j)
+ {
+ GLdouble *vert =
+ NewVector((GLdouble)pgs[i][j].X/scale, (GLdouble)pgs[i][j].Y/scale);
+ gluTessVertex(tess, vert, vert);
+ }
+
+ switch (pct)
+ {
+ case pctSubject:
+ glColor4f(0.0f, 0.0f, 0.8f, 0.5f);
+ break;
+ case pctClip:
+ glColor4f(0.6f, 0.0f, 0.0f, 0.5f);
+ }
+ gluTessEndContour(tess);
+ gluTessEndPolygon(tess);
+ }
+
+ //final cleanup ...
+ gluDeleteTess(tess);
+ ClearVectors();
+}
+//------------------------------------------------------------------------------
+
+void DrawGraphics()
+{
+ //this can take a few moments ...
+ HCURSOR hWaitCursor = LoadCursor(NULL, IDC_WAIT);
+ SetCursor(hWaitCursor);
+ SetClassLong(hWnd, GCL_HCURSOR, (DWORD)hWaitCursor);
+
+ //fill background with a light off-gray color ...
+ glClearColor(1,1,1,1);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ //glRasterPos2f(110, 340);
+ //glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
+ //char * text = "Positive Fills";
+ //for (int i = 0; i < strlen(text); ++i)
+ // glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, text[i]);
+
+ DrawPolygon(sub, pctSubject);
+ DrawPolygon(clp, pctClip);
+ if (show_clipping)
+ DrawPolygon(sol, pctSolution);
+ wstringstream ss;
+ if (!show_clipping)
+ ss << L"Clipper Demo - NO CLIPPING";
+ else
+ switch (ct)
+ {
+ case ctUnion:
+ ss << L"Clipper Demo - UNION";
+ break;
+ case ctDifference:
+ ss << L"Clipper Demo - DIFFERENCE";
+ break;
+ case ctXor:
+ ss << L"Clipper Demo - XOR";
+ break;
+ default:
+ ss << L"Clipper Demo - INTERSECTION";
+ }
+
+ switch(pft)
+ {
+ case pftEvenOdd:
+ ss << L" (EvenOdd filled polygons with ";
+ break;
+ case pftNonZero:
+ ss << L" (NonZero filled polygons with ";
+ break;
+ case pftPositive:
+ ss << L" (Positive filled polygons with ";
+ break;
+ default:
+ ss << L" (Negative filled polygons with ";
+ }
+ ss << VertCount << " vertices each.)";
+ SetWindowText(hWnd, ss.str().c_str());
+
+ HCURSOR hArrowCursor = LoadCursor(NULL, IDC_ARROW);
+ SetCursor(hArrowCursor);
+ SetClassLong(hWnd, GCL_HCURSOR, (DWORD)hArrowCursor);
+}
+//------------------------------------------------------------------------------
+
+inline long64 Round(double val)
+{
+ if ((val < 0)) return (long64)(val - 0.5); else return (long64)(val + 0.5);
+}
+//------------------------------------------------------------------------------
+
+//bool LoadFromFile(Polygons &ppg, char * filename, double scale= 1,
+// int xOffset = 0, int yOffset = 0)
+//{
+// ppg.clear();
+// ifstream infile(filename);
+// if (!infile.is_open()) return false;
+// int polyCnt, vertCnt;
+// double X, Y;
+//
+// infile >> polyCnt;
+// infile.ignore(80, '\n');
+// if (infile.good() && polyCnt > 0)
+// {
+// ppg.resize(polyCnt);
+// for (int i = 0; i < polyCnt; i++)
+// {
+// infile >> vertCnt;
+// infile.ignore(80, '\n');
+// if (!infile.good() || vertCnt < 0) break;
+// ppg[i].resize(vertCnt);
+// for (int j = 0; infile.good() && j < vertCnt; j++)
+// {
+// infile >> X;
+// while (infile.peek() == ' ') infile.ignore();
+// if (infile.peek() == ',') infile.ignore();
+// while (infile.peek() == ' ') infile.ignore();
+// infile >> Y;
+// ppg[i][j].X = Round((X + xOffset) * scale);
+// ppg[i][j].Y = Round((Y + yOffset) * scale);
+// infile.ignore(80, '\n');
+// }
+// }
+// }
+// infile.close();
+// return true;
+//}
+//------------------------------------------------------------------------------
+
+void SaveToFile(const char *filename, Paths &pp, double scale = 1)
+{
+ ofstream of(filename);
+ if (!of.is_open()) return;
+ of << pp.size() << "\n";
+ for (Paths::size_type i = 0; i < pp.size(); ++i)
+ {
+ of << pp[i].size() << "\n";
+ if (scale > 1.01 || scale < 0.99)
+ of << fixed << setprecision(6);
+ for (Path::size_type j = 0; j < pp[i].size(); ++j)
+ of << (double)pp[i][j].X /scale << ", " << (double)pp[i][j].Y /scale << ",\n";
+ }
+ of.close();
+}
+//---------------------------------------------------------------------------
+
+void UpdatePolygons(bool updateSolutionOnly)
+{
+ if (VertCount < 0) VertCount = -VertCount;
+ if (VertCount > 50) VertCount = 50;
+ if (VertCount < 3) VertCount = 3;
+
+ Clipper c;
+ if (!updateSolutionOnly)
+ {
+ delta = 0.0;
+
+ RECT r;
+ GetWindowRect(hStatus, &r);
+ int statusHeight = r.bottom - r.top;
+ GetClientRect(hWnd, &r);
+
+ sub.resize(1);
+ clp.resize(1);
+
+
+ MakeRandomPoly(sub[0], r.right, r.bottom - statusHeight, VertCount);
+ MakeRandomPoly(clp[0], r.right, r.bottom - statusHeight, VertCount);
+
+ //SaveToFile("subj.txt", sub);
+ //SaveToFile("clip.txt", clp);
+ }
+
+ c.AddPaths(sub, ptSubject, true);
+ c.AddPaths(clp, ptClip, true);
+
+ c.Execute(ct, sol, pft, pft);
+ SaveToFile("solution.txt", sol);
+
+ if (delta != 0.0)
+ {
+ ClipperOffset co;
+ co.AddPaths(sol, jt, etClosedPolygon);
+ co.Execute(sol, delta);
+ }
+
+ InvalidateRect(hWnd, NULL, false);
+}
+//------------------------------------------------------------------------------
+
+void DoNumericKeyPress(int num)
+{
+ if (VertCount >= 0) VertCount = -num;
+ else if (VertCount > -10) VertCount = VertCount*10 - num;
+ else Beep(1000, 100);
+}
+//------------------------------------------------------------------------------
+
+LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ int clientwidth, clientheight;
+ switch (uMsg)
+ {
+
+ case WM_SIZE:
+ clientwidth = LOWORD(lParam);
+ clientheight = HIWORD(lParam);
+ ResizeGraphics(clientwidth, clientheight);
+ SetWindowPos(hStatus, NULL, 0,
+ clientheight, clientwidth, 0, SWP_NOACTIVATE | SWP_NOZORDER);
+ return 0;
+
+ case WM_PAINT:
+ HDC hdc;
+ PAINTSTRUCT ps;
+ hdc = BeginPaint(hWnd, &ps);
+ //do the drawing ...
+ DrawGraphics();
+ SwapBuffers(hdc);
+ EndPaint(hWnd, &ps);
+ return 0;
+
+ case WM_CLOSE:
+ DestroyWindow(hWnd);
+ return 0;
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ return 0;
+
+ case WM_HELP:
+ MessageBox(hWnd, helpText, L"Clipper Demo - Help", 0);
+ return 0;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case 1: case 27: PostQuitMessage(0); break; //escape
+ case 98: MessageBox(hWnd, helpText, L"Clipper Demo - Help", 0); break;
+ case 99: MessageBox(hWnd, L"After closing this dialog,\ntype the required number of vertices (3-50) then <Enter> ...", L"Clipper Demo", 0);
+ case 101: show_clipping = true; ct = ctIntersection; UpdatePolygons(true); break;
+ case 102: show_clipping = true; ct = ctUnion; UpdatePolygons(true); break;
+ case 103: show_clipping = true; ct = ctDifference; UpdatePolygons(true); break;
+ case 104: show_clipping = true; ct = ctXor; UpdatePolygons(true); break;
+ case 105: pft = pftEvenOdd; UpdatePolygons(true); break;
+ case 106: pft = pftNonZero; UpdatePolygons(true); break;
+ case 107: pft = pftPositive; UpdatePolygons(true); break;
+ case 108: pft = pftNegative; UpdatePolygons(true); break;
+ case 109: show_clipping = !show_clipping; UpdatePolygons(true); break;
+ case 110: case 111: case 112: case 113: case 114:
+ case 115: case 116: case 117: case 118: case 119:
+ DoNumericKeyPress(LOWORD(wParam) - 110);
+ break;
+ case 120: UpdatePolygons(false); break; //space, return
+ case 131: if (delta < 20*scale) {delta += scale; UpdatePolygons(true);} break;
+ case 132: if (delta > -20*scale) {delta -= scale; UpdatePolygons(true);} break;
+ case 133: if (delta != 0.0) {delta = 0.0; UpdatePolygons(true);} break;
+ case 141: {jt = jtMiter; if (delta != 0.0) UpdatePolygons(true);} break;
+ case 142: {jt = jtSquare; if (delta != 0.0) UpdatePolygons(true);} break;
+ case 143: {jt = jtRound; if (delta != 0.0) UpdatePolygons(true);} break;
+ default: return DefWindowProc (hWnd, uMsg, wParam, lParam);
+ }
+ return 0;
+
+ case WM_LBUTTONUP:
+ UpdatePolygons(false);
+ return 0;
+
+ // Default event handler
+ default: return DefWindowProc (hWnd, uMsg, wParam, lParam);
+ }
+}
+//------------------------------------------------------------------------------
+
+int WINAPI WinMain (HINSTANCE hInstance,
+ HINSTANCE, LPSTR, int nCmdShow)
+{
+
+ const LPCWSTR appname = TEXT("Clipper Demo");
+
+ WNDCLASS wndclass;
+ MSG msg;
+
+ // Define the window class
+ wndclass.style = 0;
+ wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = hInstance;
+ wndclass.hIcon = LoadIcon(hInstance, appname);
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
+ wndclass.lpszMenuName = appname;
+ wndclass.lpszClassName = appname;
+
+ // Register the window class
+ if (!RegisterClass(&wndclass)) return FALSE;
+
+ HMENU menu = LoadMenu(hInstance, MAKEINTRESOURCE(1));
+ HACCEL accel = LoadAccelerators(hInstance, MAKEINTRESOURCE(1));
+
+ int windowSizeX = 540, windowSizeY = 400;
+
+ int dx = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ int dy = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ dx += ((GetSystemMetrics(SM_CXSCREEN) -windowSizeX) /2);
+ dy += ((GetSystemMetrics(SM_CYSCREEN) -windowSizeY) /2);
+
+ // Create the window
+ hWnd = CreateWindow(
+ appname,
+ appname,
+ WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+ dx, dy, windowSizeX, windowSizeY,
+ NULL,
+ menu,
+ hInstance,
+ NULL);
+
+ if (!hWnd) return FALSE;
+
+ //replace the main window icon with Resource Icon #1 ...
+ HANDLE small_ico = LoadImage(hInstance, MAKEINTRESOURCE(1), IMAGE_ICON, 16, 16, 0);
+ HANDLE big_ico = LoadImage(hInstance, MAKEINTRESOURCE(1), IMAGE_ICON, 32, 32, 0);
+ SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)small_ico);
+ SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)big_ico);
+
+ InitCommonControls();
+ hStatus = CreateWindowEx(0, L"msctls_statusbar32", NULL, WS_CHILD | WS_VISIBLE,
+ 0, 0, 0, 0, hWnd, (HMENU)0, hInstance, NULL);
+ SetWindowText(hStatus, L" Copyright � Angus Johnson 2011");
+
+ // Initialize OpenGL
+ InitGraphics();
+
+ srand((unsigned)time(0));
+ UpdatePolygons(false);
+
+ // Display the window
+ ShowWindow(hWnd, nCmdShow);
+ UpdateWindow(hWnd);
+
+ // Event loop
+ for (;;)
+ {
+ if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == TRUE)
+ {
+ if (!GetMessage(&msg, NULL, 0, 0)) break;
+
+ if (!TranslateAccelerator(hWnd, accel, &msg))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+ }
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(hRC);
+ ReleaseDC(hWnd, hDC);
+ return TRUE;
+}
+//------------------------------------------------------------------------------
diff --git a/cpp/cpp_opengl/menu.res b/cpp/cpp_opengl/menu.res
new file mode 100644
index 0000000..9f8ff54
Binary files /dev/null and b/cpp/cpp_opengl/menu.res differ
diff --git a/cpp/fix_members.sh b/cpp/fix_members.sh
new file mode 100755
index 0000000..998d45f
--- /dev/null
+++ b/cpp/fix_members.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+export ROOTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+perl -i -p -e "s/\.X/\.x/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/\->X/\->x/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/cInt X;/cInt x;/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/double X;/double x;/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/X\(x\)/x\(_x\)/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/X\(\(/x\(\(/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/double x = 0/double _x = 0/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/cInt x = 0/cInt _x = 0/g;" ${ROOTDIR}/clipper.*
+
+perl -i -p -e "s/\.Y/\.y/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/\->Y/\->y/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/cInt Y;/cInt y;/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/double Y;/double y;/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/Y\(y\)/y\(_y\)/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/Y\(\(/y\(\(/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/cInt Y;/cInt y;/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/double y = 0/double _y = 0/g;" ${ROOTDIR}/clipper.*
+perl -i -p -e "s/cInt y = 0/cInt _y = 0/g;" ${ROOTDIR}/clipper.*
+
+
diff --git a/cpp/polyclipping.pc.cmakein b/cpp/polyclipping.pc.cmakein
new file mode 100644
index 0000000..7cf55f2
--- /dev/null
+++ b/cpp/polyclipping.pc.cmakein
@@ -0,0 +1,13 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+libdir=@CMAKE_INSTALL_LIBDIR@
+sharedlibdir=@CMAKE_INSTALL_LIBDIR@
+includedir=@CMAKE_INSTALL_INCDIR@
+
+Name: polyclipping
+Description: polygon clipping library
+Version: @VERSION@
+
+Requires:
+Libs: -L${libdir} -L${sharedlibdir} -lpolyclipping
+Cflags: -I${includedir}
diff --git a/mapnik-changes.md b/mapnik-changes.md
new file mode 100644
index 0000000..87114ec
--- /dev/null
+++ b/mapnik-changes.md
@@ -0,0 +1,24 @@
+Mapnik working copy of [clipper](http://sourceforge.net/projects/polyclipping/files/).
+
+Why this fork?
+
+ - We want to adapt Mapnik geometries to clipper algorithms in a zero-copy way.
+ - This fork reworks the clipper c++ code to be able to pass in specialized data structures
+ - This fork also provides a shell script to rename clipper `X` and `Y` variables lowercase so they can be the [same as Mapnik](https://github.com/mapnik/mapnik/blob/master/include/mapnik/geometry.hpp#L57-L58)
+ - Once we forked we found it useful to make a few other modifications like:
+ - Using `stable_sort` to ensure test results are stable across Linux and OS X: https://github.com/mapnik/node-mapnik/issues/442#issuecomment-110133213
+ - To avoid abort on uncatchable/invalid exception: https://github.com/mapnik/clipper/commit/7c73dedbed7abcbc3aa0acc3bfbd7a62ff75e5ec
+
+Currently developing and using the `r493-mapnik` branch in:
+
+ - https://github.com/mapbox/mapnik-vector-tile
+ - https://github.com/mapnik/node-mapnik
+
+Details:
+
+ - We are using r493 rather than r494 due this this bug introduced in r494: https://github.com/mapbox/mapnik-vector-tile/issues/115
+ - We have not tested with any commit greater than r494 (http://sourceforge.net/p/polyclipping/code/HEAD/tree/)
+ - See our changes: https://github.com/mapnik/clipper/compare/r493...r493-mapnik
+
+
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapnik-clipper.git
More information about the Pkg-grass-devel
mailing list