├── Documentation
├── Fusion1.png
├── Fusion2.png
├── Fusion3.png
├── Fusion4.png
├── Monotone1.png
├── Monotone2.png
├── Monotone3.png
├── SimpleHole.png
├── SingleSplit.png
├── BasicPolygon.png
├── FusionOrdering.png
├── SplitAfterJoin.png
├── AfterSingleSplit.png
├── FusionOrderingBad.png
├── FusionOrderingGood.png
├── SimpleHoleSplitted.png
├── SplittedMonotones.png
├── PolygonTrapezoidLines.png
├── SimpleHoleJoiningSplit.png
├── PolygonTrapezoidLinesFilled.png
├── PolygonTrapezoidLinesFilledSplits.png
├── Monotones.md
├── Polygon.md
└── Trapezoidation.md
├── .gitignore
├── .codacy.yaml
├── TriangulationTests
├── packages.config
├── Properties
│ └── AssemblyInfo.cs
├── TriangulationTests.csproj
└── UnitTest1.cs
├── PolygonDisplay
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── App.config
├── Program.cs
├── PolygonDisplay.csproj.user
├── ExceptionHelper.cs
├── PolygonDrawControl.Designer.cs
├── PolygonDisplay.csproj
├── app.manifest
├── PolygonController.cs
├── Form1.resx
├── PolygonDrawControl.resx
└── Form1.cs
├── Directory.Build.props
├── .editorconfig
├── PolygonTriangulation
├── Polygon.Extensions.cs
├── PolygonTriangulation.csproj
├── PlanePolygonBuilder.PlanePolygonData.cs
├── PolygonTriangulator.TriangleCollector.cs
├── Properties
│ └── AssemblyInfo.cs
├── PlanePolygonBuilder.TriangulatedPlanePolygon.cs
├── Trapezoidation.TrapezoidEdge.cs
├── Polygon.VertexChain.cs
├── PlanePolygonBuilder.ClusterVertexComparer.cs
├── PolygonTriangulator.cs
├── Polygon.NextChainEnumerable.cs
├── Polygon.VertexInfo.cs
├── PolygonTriangulator.ScanSplitByTrapezoidation.cs
├── PlanePolygonBuilder.cs
├── RedBlackTree.DumpEnumerator.cs
├── Polygon.Builder.cs
├── TriangulationException.cs
├── PlanePolygonBuilder.EdgesToPolygonBuilder.cs
├── PolygonTriangulator.MonotonePolygonTriangulator.cs
├── Trapezoidation.EdgeComparer.cs
├── Trapezoidation.Trapezoid.cs
├── PlanePolygonBuilder.PolygonLineDetector.cs
├── Trapezoidation.cs
└── RedBlackTree.Node.cs
├── LICENSE
├── .github
└── workflows
│ └── dotnet-core.yml
├── PolygonTriangulation.sln
└── Readme.md
/Documentation/Fusion1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Fusion1.png
--------------------------------------------------------------------------------
/Documentation/Fusion2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Fusion2.png
--------------------------------------------------------------------------------
/Documentation/Fusion3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Fusion3.png
--------------------------------------------------------------------------------
/Documentation/Fusion4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Fusion4.png
--------------------------------------------------------------------------------
/Documentation/Monotone1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Monotone1.png
--------------------------------------------------------------------------------
/Documentation/Monotone2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Monotone2.png
--------------------------------------------------------------------------------
/Documentation/Monotone3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/Monotone3.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | */obj
3 | */bin
4 | packages
5 | Debug
6 | Bin
7 | *Tests/coverage.json
8 | *Tests/coverage.opencover.xml
9 |
--------------------------------------------------------------------------------
/Documentation/SimpleHole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/SimpleHole.png
--------------------------------------------------------------------------------
/Documentation/SingleSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/SingleSplit.png
--------------------------------------------------------------------------------
/Documentation/BasicPolygon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/BasicPolygon.png
--------------------------------------------------------------------------------
/Documentation/FusionOrdering.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/FusionOrdering.png
--------------------------------------------------------------------------------
/Documentation/SplitAfterJoin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/SplitAfterJoin.png
--------------------------------------------------------------------------------
/Documentation/AfterSingleSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/AfterSingleSplit.png
--------------------------------------------------------------------------------
/Documentation/FusionOrderingBad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/FusionOrderingBad.png
--------------------------------------------------------------------------------
/Documentation/FusionOrderingGood.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/FusionOrderingGood.png
--------------------------------------------------------------------------------
/Documentation/SimpleHoleSplitted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/SimpleHoleSplitted.png
--------------------------------------------------------------------------------
/Documentation/SplittedMonotones.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/SplittedMonotones.png
--------------------------------------------------------------------------------
/.codacy.yaml:
--------------------------------------------------------------------------------
1 | engines:
2 | duplication:
3 | exclude_paths:
4 | - TriangulationTests/**
5 | - PolygonDisplay/PolygonSamples.cs
6 |
--------------------------------------------------------------------------------
/Documentation/PolygonTrapezoidLines.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/PolygonTrapezoidLines.png
--------------------------------------------------------------------------------
/Documentation/SimpleHoleJoiningSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/SimpleHoleJoiningSplit.png
--------------------------------------------------------------------------------
/Documentation/PolygonTrapezoidLinesFilled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/PolygonTrapezoidLinesFilled.png
--------------------------------------------------------------------------------
/Documentation/PolygonTrapezoidLinesFilledSplits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/git-ruttmann/PolygonTriangulation/HEAD/Documentation/PolygonTrapezoidLinesFilledSplits.png
--------------------------------------------------------------------------------
/TriangulationTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/PolygonDisplay/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | S1172,S3881
5 | ..\Bin\$(Configuration)
6 | ..\Bin\$(Configuration)\$(ProjectName).xml
7 |
8 |
9 |
--------------------------------------------------------------------------------
/PolygonDisplay/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/PolygonDisplay/Program.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonDisplay
2 | {
3 | using System;
4 | using System.Windows.Forms;
5 |
6 | ///
7 | /// the main program
8 | ///
9 | public static class Program
10 | {
11 | ///
12 | /// Der Haupteinstiegspunkt für die Anwendung.
13 | ///
14 | [STAThread]
15 | public static void Main()
16 | {
17 | Application.EnableVisualStyles();
18 | Application.SetCompatibleTextRenderingDefault(false);
19 | Application.Run(new Form1());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/PolygonDisplay/PolygonDisplay.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | publish\
5 |
6 |
7 |
8 |
9 |
10 | de-DE
11 | false
12 |
13 |
14 |
15 | Form
16 |
17 |
18 |
--------------------------------------------------------------------------------
/TriangulationTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("TriangulationTests")]
5 | [assembly: AssemblyDescription("")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("")]
8 | [assembly: AssemblyProduct("TriangulationTests")]
9 | [assembly: AssemblyCopyright("Copyright © 2020")]
10 | [assembly: AssemblyTrademark("")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 |
15 | [assembly: Guid("981fe2f3-4d3e-4f7f-8a64-32fd485330bb")]
16 |
17 | // [assembly: AssemblyVersion("1.0.*")]
18 | [assembly: AssemblyVersion("1.0.0.0")]
19 | [assembly: AssemblyFileVersion("1.0.0.0")]
20 |
--------------------------------------------------------------------------------
/PolygonDisplay/ExceptionHelper.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonDisplay
2 | {
3 | using System;
4 |
5 | ///
6 | /// exception handling helper
7 | ///
8 | public static class ExceptionHelper
9 | {
10 | ///
11 | /// Determines whether the exception can be swallowed.
12 | ///
13 | /// The exception.
14 | /// true if the exception is not too severe
15 | public static bool CanSwallow(Exception exception)
16 | {
17 | if (exception is OutOfMemoryException)
18 | {
19 | return false;
20 | }
21 |
22 | return true;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # IDE0065: Die using-Anweisung wurde falsch platziert.
4 | csharp_using_directive_placement = inside_namespace:silent
5 |
6 | # SA1629: Documentation text should end with a period
7 | dotnet_diagnostic.SA1629.severity = none
8 |
9 | # CA1303: Literale nicht als lokalisierte Parameter übergeben
10 | dotnet_diagnostic.CA1303.severity = none
11 |
12 | # SA1633: File should have header
13 | dotnet_diagnostic.SA1633.severity = none
14 |
15 | # CA1062: Check public parameters for null
16 | dotnet_diagnostic.CA1062.severity = none
17 |
18 | # SA1649: The first type should match the type name
19 | dotnet_diagnostic.SA1649.severity = none
20 |
21 | # CA1819: Properties may not return arrays
22 | dotnet_diagnostic.CA1819.severity = none
23 |
24 | # SA1300: object names
25 | dotnet_diagnostic.SA1300.severity = none
26 |
27 | # SA1300: type parameters
28 | dotnet_diagnostic.SA1619.severity = none
29 |
--------------------------------------------------------------------------------
/PolygonTriangulation/Polygon.Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System.Collections.Generic;
4 |
5 | #if UNITY_EDITOR || UNITY_STANDALONE
6 | using Vertex = UnityEngine.Vector2;
7 | #else
8 | using Vertex = System.Numerics.Vector2;
9 |
10 | #endif
11 |
12 | ///
13 | /// Extension methods for polygon
14 | ///
15 | public static class PolygonExtensions
16 | {
17 | ///
18 | /// Adds the vertices.
19 | ///
20 | /// The builder.
21 | /// The vertices.
22 | /// the same builder
23 | public static IPolygonBuilder AddVertices(this IPolygonBuilder builder, params int[] vertices)
24 | {
25 | return builder.AddVertices((IEnumerable)vertices);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PolygonTriangulation.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | Library
5 | false
6 |
7 |
8 | DEBUG;TRACE
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Matthias Ruttmann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PolygonDisplay/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace PolygonDisplay.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PlanePolygonBuilder.PlanePolygonData.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | #if UNITY_EDITOR || UNITY_STANDALONE
4 | using Vector3 = UnityEngine.Vector3;
5 | #else
6 | using Vector3 = System.Numerics.Vector3;
7 | #endif
8 |
9 | ///
10 | /// The polygon result after combining all edges
11 | ///
12 | public interface IPlanePolygon
13 | {
14 | ///
15 | /// Gets the 3D vertices.
16 | ///
17 | Vector3[] Vertices { get; }
18 |
19 | ///
20 | /// Gets the polygon. It contains the 2D vertices.
21 | ///
22 | Polygon Polygon { get; }
23 | }
24 |
25 | ///
26 | /// subclass container
27 | ///
28 | public partial class PlanePolygonBuilder
29 | {
30 | ///
31 | /// Result storage for polygon data
32 | ///
33 | private class PlanePolygonData : IPlanePolygon
34 | {
35 | public PlanePolygonData(Vector3[] vertices3D, Polygon polygon)
36 | {
37 | this.Vertices = vertices3D;
38 | this.Polygon = polygon;
39 | }
40 |
41 | ///
42 | public Vector3[] Vertices { get; }
43 |
44 | ///
45 | public Polygon Polygon { get; }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/TriangulationTests/TriangulationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | false
5 |
6 |
7 | DEBUG;TRACE
8 |
9 |
10 |
11 |
12 |
13 |
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 | all
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 | all
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/PolygonDisplay/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // Allgemeine Informationen über eine Assembly werden über die folgenden
5 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
6 | // die einer Assembly zugeordnet sind.
7 | [assembly: AssemblyTitle("PolygonDisplay")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("PolygonDisplay")]
12 | [assembly: AssemblyCopyright("Copyright © 2020")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
17 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
18 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
19 | [assembly: ComVisible(false)]
20 |
21 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
22 | [assembly: Guid("8898db4e-4452-4a83-b4f5-b518bbab5eb6")]
23 |
24 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
25 | //
26 | // Hauptversion
27 | // Nebenversion
28 | // Buildnummer
29 | // Revision
30 | //
31 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
32 | // indem Sie "*" wie unten gezeigt eingeben:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
36 |
--------------------------------------------------------------------------------
/PolygonDisplay/PolygonDrawControl.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonDisplay
2 | {
3 | partial class PolygonDrawControl
4 | {
5 | ///
6 | /// Erforderliche Designervariable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Verwendete Ressourcen bereinigen.
12 | ///
13 | /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Vom Komponenten-Designer generierter Code
24 |
25 | ///
26 | /// Erforderliche Methode für die Designerunterstützung.
27 | /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.SuspendLayout();
32 | //
33 | // PolygonDrawControl
34 | //
35 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
37 | this.BackColor = System.Drawing.Color.White;
38 | this.Name = "PolygonDrawControl";
39 | this.Size = new System.Drawing.Size(716, 551);
40 | this.ResumeLayout(false);
41 |
42 | }
43 |
44 | #endregion
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-core.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on:
4 | push:
5 | branches: [ master, develop ]
6 | pull_request:
7 | branches: [ master, develop ]
8 | workflow_dispatch:
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Setup .NET Core
18 | uses: actions/setup-dotnet@v1
19 | with:
20 | dotnet-version: 3.1.101
21 | - name: Install dependencies
22 | run: dotnet restore TriangulationTests
23 | - name: Build
24 | run: dotnet build --configuration Release --no-restore TriangulationTests
25 | - name: Test
26 | run: dotnet test --no-restore --verbosity normal TriangulationTests /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
27 |
28 | - name: Upload Coverage to Codacy
29 | env: # Set the secret as an input
30 | CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
31 | run: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r TriangulationTests/coverage.opencover.xml --commit-uuid $GITHUB_SHA
32 |
33 | - name: Setup .net core 5.0
34 | uses: actions/setup-dotnet@v1
35 | with:
36 | dotnet-version: 5.0.301
37 | - name: Install Coverage Tool
38 | # run: dotnet tool install --tool-path . coveralls.net
39 | run: dotnet tool install -g coveralls.net
40 | - name: Upload Coverage
41 | env: # Set the secret as an input
42 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
43 | run: csmacnz.Coveralls --opencover -i TriangulationTests/coverage.opencover.xml --useRelativePaths --commitId $GITHUB_SHA --commitBranch `echo $GITHUB_REF | sed -e 's#refs/heads/##'` --commitAuthor "$GITHUB_ACTOR" --commitMessage "$REPO_COMMIT_MESSAGE"
44 |
45 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PolygonTriangulator.TriangleCollector.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System.Collections.Generic;
4 |
5 | ///
6 | /// Receive triangles
7 | ///
8 | public interface ITriangleCollector
9 | {
10 | ///
11 | /// Add a triangle
12 | ///
13 | /// id of vertex 0
14 | /// id of vertex 1
15 | /// id of vertex 2
16 | void AddTriangle(int v0, int v1, int v2);
17 | }
18 |
19 | ///
20 | /// Receive triangles and provide the recieved triangles
21 | ///
22 | public interface IArrayTriangleCollector : ITriangleCollector
23 | {
24 | ///
25 | /// Gets the triangles
26 | ///
27 | int[] Triangles { get; }
28 | }
29 |
30 | ///
31 | /// subclass container for triangulator
32 | ///
33 | public partial class PolygonTriangulator
34 | {
35 | ///
36 | /// The triangle collector
37 | ///
38 | private class TriangleCollector : IArrayTriangleCollector
39 | {
40 | private readonly List triangles;
41 |
42 | public TriangleCollector()
43 | {
44 | this.triangles = new List();
45 | }
46 |
47 | public int[] Triangles => this.triangles.ToArray();
48 |
49 | public void AddTriangle(int v0, int v1, int v2)
50 | {
51 | this.triangles.Add(v0);
52 | this.triangles.Add(v1);
53 | this.triangles.Add(v2);
54 | }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/PolygonTriangulation/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // Allgemeine Informationen über eine Assembly werden über die folgenden
6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
7 | // die einer Assembly zugeordnet sind.
8 | [assembly: AssemblyTitle("PolygonTriangulation")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("PolygonTriangulation")]
13 | [assembly: AssemblyCopyright("Copyright © 2020")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
20 | [assembly: ComVisible(false)]
21 |
22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
23 | [assembly: Guid("250832d7-a812-4b2a-9ef7-f1791ec657ff")]
24 |
25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
26 | //
27 | // Hauptversion
28 | // Nebenversion
29 | // Buildnummer
30 | // Revision
31 | //
32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
33 | // indem Sie "*" wie unten gezeigt eingeben:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
38 | [assembly: InternalsVisibleTo("TriangulationTests")]
39 | [assembly: InternalsVisibleTo("PolygonDisplay")]
--------------------------------------------------------------------------------
/PolygonTriangulation/PlanePolygonBuilder.TriangulatedPlanePolygon.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System.Collections.Generic;
4 |
5 | #if UNITY_EDITOR || UNITY_STANDALONE
6 | using Vector3 = UnityEngine.Vector3;
7 | using Vertex = UnityEngine.Vector2;
8 | #else
9 | using Vector3 = System.Numerics.Vector3;
10 | using Vertex = System.Numerics.Vector2;
11 | #endif
12 |
13 | ///
14 | /// Resulting plane mesh with coordinates and triangles
15 | ///
16 | public interface ITriangulatedPlanePolygon
17 | {
18 | ///
19 | /// Gets the 3D vertices
20 | ///
21 | Vector3[] Vertices { get; }
22 |
23 | ///
24 | /// Gets the 2D vertices of the plane points
25 | ///
26 | IReadOnlyList Vertices2D { get; }
27 |
28 | ///
29 | /// Gets the triangles with vertex offset
30 | ///
31 | int[] Triangles { get; }
32 | }
33 |
34 | ///
35 | /// subclass container
36 | ///
37 | public partial class PlanePolygonBuilder
38 | {
39 | ///
40 | /// Result for the plane mesh
41 | ///
42 | private class TriangulatedPlanePolygon : ITriangulatedPlanePolygon
43 | {
44 | public TriangulatedPlanePolygon(Vector3[] vertices, IReadOnlyList vertices2D, int[] triangles)
45 | {
46 | this.Vertices = vertices;
47 | this.Triangles = triangles;
48 | this.Vertices2D = vertices2D;
49 | }
50 |
51 | ///
52 | public Vector3[] Vertices { get; }
53 |
54 | ///
55 | public int[] Triangles { get; }
56 |
57 | ///
58 | public IReadOnlyList Vertices2D { get; }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/PolygonTriangulation/Trapezoidation.TrapezoidEdge.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | ///
4 | /// subclass container for trapzoidation
5 | ///
6 | public partial class Trapezoidation
7 | {
8 | ///
9 | /// Internal representation of an edge
10 | ///
11 | private class TrapezoidEdge : ITestingTrapezoidEdge
12 | {
13 | public TrapezoidEdge(int left, int right, int rightUnique, bool isRightToLeft)
14 | {
15 | this.IsRightToLeft = isRightToLeft;
16 | this.Left = left;
17 | this.Right = right;
18 | this.RightUnique = rightUnique;
19 | }
20 |
21 | ///
22 | /// Gets a value indicating whether this instance is right to left.
23 | ///
24 | public bool IsRightToLeft { get; }
25 |
26 | ///
27 | /// Gets the left vertex id.
28 | ///
29 | public int Left { get; }
30 |
31 | ///
32 | /// Gets the right vertex id.
33 | ///
34 | public int Right { get; }
35 |
36 | ///
37 | /// Gets the unique id of the right vertex
38 | ///
39 | public int RightUnique { get; }
40 |
41 | ///
42 | /// Gets or sets the node in the red black tree
43 | ///
44 | public IOrderedNode TreeNode { get; set; }
45 |
46 | ///
47 | /// Gets or sets the current associated trapezoid.
48 | ///
49 | public Trapezoid Trapezoid { get; set; }
50 |
51 | ///
52 | public override string ToString()
53 | {
54 | return $"{this.Left}{(this.IsRightToLeft ? "<" : ">")}{this.Right}";
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/PolygonTriangulation/Polygon.VertexChain.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | ///
4 | /// subclass container for polygon
5 | ///
6 | public partial class Polygon
7 | {
8 | ///
9 | /// - each chain element belongs to exactly one polygon.
10 | /// - multiple polygons are stored in the chain. (avoids copy during split)
11 | /// - if a vertex belongs to multple polygons, it has multiple chain elements with the same VertexId
12 | /// the start of that chain is in the , the collision list is in SameVertexChain
13 | /// the combination of PolygonId/VertexId is distinct.
14 | /// during polygon triangulation, the maximum collision count is 3
15 | /// - a polygon has a specific chain element as start index
16 | /// - a polygon with holes has multiple chain start elements. They are joined via
17 | ///
18 | private struct VertexChain
19 | {
20 | ///
21 | /// the index in
22 | ///
23 | public int VertexId;
24 |
25 | ///
26 | /// The id of the polygon. Holes are a separate polygon.
27 | ///
28 | public int SubPolygonId;
29 |
30 | ///
31 | /// The next info with the same vertex id.
32 | ///
33 | public int SameVertexChain;
34 |
35 | ///
36 | /// Gets the previous vertex id (not chain index)
37 | ///
38 | public int Prev { get; private set; }
39 |
40 | ///
41 | /// Gets the next chain index in the polygon (same polygon id)
42 | ///
43 | public int Next { get; private set; }
44 |
45 | ///
46 | /// Chain two items
47 | ///
48 | /// the id of the current item
49 | /// the id of the next item
50 | /// the data of the next item
51 | public void SetNext(int current, int nextChain, ref VertexChain nextItem)
52 | {
53 | this.Next = nextChain;
54 | nextItem.Prev = current;
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/PolygonDisplay/PolygonDisplay.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp3.1
4 | WinExe
5 | publish\
6 | true
7 | Disk
8 | false
9 | Foreground
10 | 7
11 | Days
12 | false
13 | false
14 | true
15 | 0
16 | 1.0.0.%2a
17 | false
18 | false
19 | true
20 | false
21 | true
22 |
23 |
24 | app.manifest
25 |
26 |
27 |
28 | UserControl
29 |
30 |
31 |
32 |
33 | False
34 | Microsoft .NET Framework 4.7.2 %28x86 und x64%29
35 | true
36 |
37 |
38 | False
39 | .NET Framework 3.5 SP1
40 | false
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | all
49 | runtime; build; native; contentfiles; analyzers; buildtransitive
50 |
51 |
52 |
53 | all
54 | runtime; build; native; contentfiles; analyzers; buildtransitive
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PlanePolygonBuilder.ClusterVertexComparer.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | #if UNITY_EDITOR || UNITY_STANDALONE
7 | using Vertex = UnityEngine.Vector2;
8 | #else
9 | using Vertex = System.Numerics.Vector2;
10 | #endif
11 |
12 | ///
13 | /// subclass container
14 | ///
15 | public partial class PlanePolygonBuilder
16 | {
17 | ///
18 | /// Compare two vertices, very close vertices are considered equal.
19 | ///
20 | private class ClusterVertexComparer : IComparer
21 | {
22 | ///
23 | public int Compare(Vertex x, Vertex y)
24 | {
25 | #if UNITY_EDITOR || UNITY_STANDALONE
26 | var xdist = Math.Abs(x.x - y.x);
27 | if (xdist < epsilon)
28 | {
29 | var ydist = Math.Abs(x.y - y.y);
30 | if (ydist < epsilon)
31 | {
32 | return 0;
33 | }
34 |
35 | var xCompare = x.x.CompareTo(y.x);
36 | if (xCompare != 0)
37 | {
38 | return xCompare;
39 | }
40 |
41 | if (x.y < y.y)
42 | {
43 | return -1;
44 | }
45 | else
46 | {
47 | return 1;
48 | }
49 | }
50 | else if (x.x < y.x)
51 | {
52 | return -1;
53 | }
54 | else
55 | {
56 | return 1;
57 | }
58 | #else
59 | var xdist = Math.Abs(x.X - y.X);
60 | if (xdist < Epsilon)
61 | {
62 | var ydist = Math.Abs(x.Y - y.Y);
63 | if (ydist < Epsilon)
64 | {
65 | return 0;
66 | }
67 |
68 | var xCompare = x.X.CompareTo(y.X);
69 | if (xCompare != 0)
70 | {
71 | return xCompare;
72 | }
73 |
74 | if (x.Y < y.Y)
75 | {
76 | return -1;
77 | }
78 | else
79 | {
80 | return 1;
81 | }
82 | }
83 | else if (x.X < y.X)
84 | {
85 | return -1;
86 | }
87 | else
88 | {
89 | return 1;
90 | }
91 | #endif
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PolygonTriangulator.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | ///
7 | /// Build triangles for a polygon
8 | ///
9 | public partial class PolygonTriangulator
10 | {
11 | private readonly Polygon polygon;
12 |
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// the polygon to triangulate
17 | public PolygonTriangulator(Polygon polygon)
18 | {
19 | this.polygon = polygon;
20 | }
21 |
22 | ///
23 | /// Create a triangle collector
24 | ///
25 | /// a triangle collector
26 | public static IArrayTriangleCollector CreateTriangleCollector()
27 | {
28 | return new TriangleCollector();
29 | }
30 |
31 | ///
32 | /// Finally, build the triangles
33 | ///
34 | /// the triangles to represent the polygon
35 | public int[] BuildTriangles()
36 | {
37 | var collector = new TriangleCollector();
38 | this.BuildTriangles(collector);
39 | return collector.Triangles;
40 | }
41 |
42 | ///
43 | /// Finally, build the triangles
44 | ///
45 | /// the triangle collector
46 | public void BuildTriangles(ITriangleCollector collector)
47 | {
48 | var splits = ScanSplitByTrapezoidation.BuildSplits(this.polygon);
49 | var polygonWithMonotones = Polygon.Split(this.polygon, splits, collector);
50 | foreach (var subPolygonId in polygonWithMonotones.SubPolygonIds)
51 | {
52 | var triangluator = new MonotonePolygonTriangulator(polygonWithMonotones, subPolygonId);
53 | triangluator.Build(collector);
54 | }
55 | }
56 |
57 | ///
58 | /// Get the possible splits for the polygon
59 | ///
60 | /// the splits
61 | internal IEnumerable> GetSplits()
62 | {
63 | return ScanSplitByTrapezoidation.BuildSplits(this.polygon);
64 | }
65 |
66 | ///
67 | /// Iterates over the first n vertices and reports the active edges and the sort order after that step.
68 | ///
69 | /// The execution depth.
70 | /// sorted active edges
71 | internal IEnumerable GetEdgesAfterPartialTrapezoidation(int depth)
72 | {
73 | return ScanSplitByTrapezoidation.GetEdgesAfterPartialTrapezoidation(this.polygon, depth);
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/PolygonTriangulation.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30002.166
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolygonTriangulation", "PolygonTriangulation\PolygonTriangulation.csproj", "{250832D7-A812-4B2A-9EF7-F1791EC657FF}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TriangulationTests", "TriangulationTests\TriangulationTests.csproj", "{981FE2F3-4D3E-4F7F-8A64-32FD485330BB}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolygonDisplay", "PolygonDisplay\PolygonDisplay.csproj", "{8898DB4E-4452-4A83-B4F5-B518BBAB5EB6}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2EFAEA6D-3965-464E-A0AD-188667A80217}"
13 | ProjectSection(SolutionItems) = preProject
14 | .editorconfig = .editorconfig
15 | Directory.Build.props = Directory.Build.props
16 | EndProjectSection
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{B69883E5-A4DC-4811-89E6-B0A24BFA8865}"
19 | ProjectSection(SolutionItems) = preProject
20 | Documentation\Monotones.md = Documentation\Monotones.md
21 | Documentation\Polygon.md = Documentation\Polygon.md
22 | Readme.md = Readme.md
23 | Documentation\Trapezoidation.md = Documentation\Trapezoidation.md
24 | EndProjectSection
25 | EndProject
26 | Global
27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
28 | Debug|Any CPU = Debug|Any CPU
29 | Release|Any CPU = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {250832D7-A812-4B2A-9EF7-F1791EC657FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {250832D7-A812-4B2A-9EF7-F1791EC657FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {250832D7-A812-4B2A-9EF7-F1791EC657FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {250832D7-A812-4B2A-9EF7-F1791EC657FF}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {981FE2F3-4D3E-4F7F-8A64-32FD485330BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {981FE2F3-4D3E-4F7F-8A64-32FD485330BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {981FE2F3-4D3E-4F7F-8A64-32FD485330BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {981FE2F3-4D3E-4F7F-8A64-32FD485330BB}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {8898DB4E-4452-4A83-B4F5-B518BBAB5EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {8898DB4E-4452-4A83-B4F5-B518BBAB5EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {8898DB4E-4452-4A83-B4F5-B518BBAB5EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {8898DB4E-4452-4A83-B4F5-B518BBAB5EB6}.Release|Any CPU.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(NestedProjects) = preSolution
49 | {B69883E5-A4DC-4811-89E6-B0A24BFA8865} = {2EFAEA6D-3965-464E-A0AD-188667A80217}
50 | EndGlobalSection
51 | GlobalSection(ExtensibilityGlobals) = postSolution
52 | SolutionGuid = {00E046C1-DF26-4181-B779-F6383697110B}
53 | EndGlobalSection
54 | EndGlobal
55 |
--------------------------------------------------------------------------------
/PolygonDisplay/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Dieser Code wurde von einem Tool generiert.
4 | // Laufzeitversion: 4.0.30319.42000
5 | //
6 | // Änderungen an dieser Datei können fehlerhaftes Verhalten verursachen und gehen verloren, wenn
7 | // der Code neu generiert wird.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace PolygonDisplay.Properties
12 | {
13 |
14 |
15 | ///
16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
17 | ///
18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse
19 | // über ein Tool wie ResGen oder Visual Studio automatisch generiert.
20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
21 | // mit der Option /str erneut aus, oder erstellen Sie Ihr VS-Projekt neu.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PolygonDisplay.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
56 | /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Documentation/Monotones.md:
--------------------------------------------------------------------------------
1 | # Monotone Polygons
2 |
3 | A monotone [polygon](Polygon.md) is intersected by any vertical line at maxium 2 times.
4 |
5 | A mono monotone polygon has a single edge that connects the left-most and the right-most vertex.
6 | Call it the __base edge__.
7 | The other vertices are either all above or all below the base edge.
8 |
9 | After splitting a polygon via [Trapezoidation](Trapezoidation.md), the polygon consists of multiple mono monotone sub polygons.
10 |
11 |
12 |
13 | ## Triangulation of Mono Monotone Polygons
14 |
15 | `MonotonePolygonTriangulator` starts at the end vertex of base edge. That is vertex 7 of polygon A and vertex 5 of polygon B.
16 | * Take the first three vertices.
17 | * Iterate to the end
18 | * If the three active vertices do not form a clock wise triangle (the cross product of two triangle sides is >= 0)
19 | * Push the oldest vertex on a stack and get a new vertex.
20 | * Else
21 | * Emit the triangle.
22 | * Remove the middle vertex.
23 | * If there is a vertex on the stack, pop it as oldest vertex.
24 | * Otherwise get a new vertex.
25 | * If there is no new vertex, the triangulation is complete.
26 |
27 |
28 |
29 | Example A:
30 | * Start at 7, after initial pull: 7, 6, 3
31 | * (7, 6, 3) is a clock wise triangle, emit and remove 6.
32 | * Take the oldest (7) and newest (3) vertex and pull a new one (2)
33 | * (7, 3, 2) is a clock wise triangle
34 |
35 | Example B:
36 | * Start at 5, after initial pull: 5, 6, 7
37 | * It's no clock wise triangle, so push 5 on the stack and take 8 as newest vertex.
38 | * (6, 7, 8) is a clock wise triangle, emit and remove 7.
39 | * There is (5) on the stack, use it as oldest
40 | * (5, 6, 8) is a clock wise triangle, emit and remove 6.
41 | * Take the oldest (5) and newest (8) vertex and pull a new one (9)
42 | * (5, 8, 9) is a clock wise triangle
43 |
44 |
45 |
46 | A more complex example:
47 | * Start at 1, after initial pull: 1, 3, 4
48 | * (1, 3, 4) is no clock wise triangle, push 1 and consume 5
49 | * (3, 4, 5) is no clock wise triangle, push 3 and consume 6
50 | * (4, 5, 6) is a clock wise triangle. Emit, remove 5 and pop 3 as oldest
51 | * (3, 4, 6) is a clock wise triangle. Emit, remove 4 and pop 1 as oldest
52 | * (1, 3, 6) is no clock wise triangle, push 1 and consume 7
53 | * (3, 6, 7) is no clock wise triangle, push 3 and consume 8
54 | * (6, 7, 8) is a clock wise triangle. Emit, remove 7 and pop 3 as oldest
55 | * (3, 6, 8) is a clock wise triangle. Emit, remove 6 and pop 1 as oldest
56 | * (1, 3, 8) is a clock wise triangle. Emit, remove 3 and consume 9
57 | * (1, 8, 9) is no clock wise triangle, push 1 and consume 13
58 | * (8, 9, 13) is a clock wise triangle. Emit, remove 9 and pop 1 as oldest
59 | * (1, 8, 13) is a clock wise triangle. Emit, remove 8 and consume 16
60 | * (1, 13, 16) is a clock wise triangle. Emit and stop after last vertex.
61 |
62 | ## Optimization for triangles
63 |
64 | A lot of splits produce sub polygons with 3 vertices, aka triangles.
65 | The polygon split operation simply emits those directly instead of creating an additional sub polygon.
66 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | [](https://opensource.org/licenses/MIT)
2 | [](https://github.com/git-ruttmann/PolygonTriangulation/actions?query=workflow%3A%22.NET+Core%22)
3 | [](https://app.codacy.com/manual/git-ruttmann/PolygonTriangulation)
4 | [](https://www.codacy.com/manual/git-ruttmann/PolygonTriangulation?utm_source=github.com&utm_medium=referral&utm_content=git-ruttmann/PolygonTriangulation&utm_campaign=Badge_Coverage)
5 |
6 | # Polygon Triangulation
7 |
8 | A C# library to convert a complex polygon into triangles.
9 | Support includes:
10 | * multiple polygons
11 | * polygon holes
12 | * fusion vertex (same vertex is used by two sub polygons)
13 |
14 | The project was developed as part of a Unity project to visualize the intersection between a 3D mesh and a plane.
15 |
16 | The included Windows Forms project `PolygonDisplay` visualizes the [Polygon](Documentation/Polygon.md), the detected splits and the [Monotones](Documentation/Monotones.md).
17 |
18 | ## Usage
19 |
20 | Create a `PlanePolygonBuilder(plane)` and call repeatedly `AddEdge(start, end)`.
21 | After adding the last edge, call `Build()`.
22 | The result contains a vertex array and a list of Triangles. A Triangle is defined by three consecutive indices in the vertex array.
23 |
24 | The 3D vertices of the edge are converted to 2D by rotating them along the plane and removing the depth component.
25 |
26 | ## Unity
27 |
28 | Unity is using its own types for Vector2, Vector3, Plane and Quaternion, while the common code uses thy types from `System.Numerics`.
29 | The `#if UNITY_EDITOR || UNITY_STANDALONE` directive changes the namespace and the different case for the property names like `Vector2.x` vs `Vector2.X`.
30 |
31 | To use the code, copy all files from PolygonTriangulation to the unity scripts folder.
32 |
33 | ## Testing
34 |
35 | To test the intermediate steps, create a `IEdgesToPolygonBuilder` by calling `PlanePolygonBuilder.CreatePolygonBuilder()`.
36 | Call `AddEdge(start, end)` to add the polygon edges and finally `BuildPolygon()` to receive a `IPlanePolygon` which contains a
37 | [Polygon](Documentation/Polygon.md) and the original 3D vertices.
38 | The polygon contains the 2D vertices in the same order as the 3D vertices of the result.
39 |
40 | Alternatively construct the [Polygon](Documentation/Polygon.md) with `Polygon.Build()`.
41 |
42 | Create a new `PolygonTriangulator(polygon)` and call `BuildTriangles()`.
43 | That uses [Trapezoidation](Documentation/Polygon.md) to split the [Polygon](Documentation/Polygon.md) into [Monotones](Documentation/Monotones.md) and then
44 | triangulates those monotones.
45 |
46 | To test only the edges to polygon conversion, create a `IPolygonLineDetector` by calling `PlanePolygonBuilder.CreatePolygonLineDetector()`.
47 | Call `JoinEdgesToPolygones(pairsOfEdges)` to create polygon lines. It results in lists of `ClosedPolygons` and `UnclosedPolygons`.
48 |
49 | Unclosed polygons can be joined by `TryClusteringUnclosedEnds(vertices)`.
50 | That merges unclosed polygons by considering vertex coordinates with small distances as the same vertex.
51 |
--------------------------------------------------------------------------------
/PolygonDisplay/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
59 |
60 |
61 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/PolygonDisplay/PolygonController.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonDisplay
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | using PolygonTriangulation;
8 |
9 | ///
10 | /// the necessary abstraction for the form
11 | ///
12 | public interface IPolygonForm
13 | {
14 | ///
15 | /// Refresh all state dependent GUI elements.
16 | ///
17 | void RefreshState();
18 | }
19 |
20 | ///
21 | /// A controller for the polygon behaviour
22 | ///
23 | public class PolygonController
24 | {
25 | private readonly IPolygonForm form;
26 |
27 | ///
28 | /// Initializes a new instance of the class.
29 | ///
30 | /// The form.
31 | public PolygonController(IPolygonForm form)
32 | {
33 | this.form = form;
34 | this.ActiveStorageId = 1;
35 |
36 | PolygonSamples.GenerateDataOne();
37 | }
38 |
39 | ///
40 | /// Gets the id of the actived storage.
41 | ///
42 | public int ActiveStorageId { get; private set; }
43 |
44 | ///
45 | /// Gets the polygon to draw.
46 | ///
47 | public Polygon Polygon { get; private set; }
48 |
49 | ///
50 | /// Gets the splits of the polygon.
51 | ///
52 | public IReadOnlyCollection> Splits { get; private set; }
53 |
54 | ///
55 | /// Activates the storage.
56 | ///
57 | /// The identifier.
58 | public void ActivateStorage(int id)
59 | {
60 | this.ActiveStorageId = id;
61 |
62 | //// this.Polygon = PolygonSamples.Constructed(id);
63 | //// this.Polygon = PolygonSamples.UnityErrors(id);
64 | //// this.Polygon = PolygonSamples.InnerFusionSingle(id);
65 | //// this.Polygon = PolygonSamples.MultiTouch(id);
66 | this.Polygon = PolygonSamples.MoreFusionTests(id);
67 |
68 | try
69 | {
70 | this.Splits = new PolygonTriangulator(this.Polygon).GetSplits().ToArray();
71 | }
72 | catch (Exception e)
73 | {
74 | if (!ExceptionHelper.CanSwallow(e))
75 | {
76 | throw;
77 | }
78 |
79 | this.Splits = new Tuple[0];
80 | }
81 | }
82 |
83 | ///
84 | /// Build debug steps for the trapezoidation.
85 | ///
86 | /// the debug text
87 | public string[] BuildTrapezoidationDebug()
88 | {
89 | var lines = new List();
90 | var trapezoidation = new PolygonTriangulator(this.Polygon);
91 | int limit = 0;
92 | foreach (var vertexInfo in this.Polygon.OrderedVertices)
93 | {
94 | try
95 | {
96 | lines.Add($"{vertexInfo.PrevVertexId}>{vertexInfo.Id}>{vertexInfo.NextVertexId}");
97 | lines.AddRange(trapezoidation.GetEdgesAfterPartialTrapezoidation(++limit));
98 | lines.Add(string.Empty);
99 | }
100 | catch (Exception e)
101 | {
102 | if (!ExceptionHelper.CanSwallow(e))
103 | {
104 | throw;
105 | }
106 |
107 | lines.Add(string.Empty);
108 | lines.Add(e.ToString());
109 | break;
110 | }
111 | }
112 |
113 | return lines.ToArray();
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/PolygonTriangulation/Polygon.NextChainEnumerable.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 |
7 | ///
8 | /// subclass container for polygon
9 | ///
10 | public partial class Polygon
11 | {
12 | ///
13 | /// An enumerable that creates a
14 | ///
15 | private class NextChainEnumerable : IEnumerable
16 | {
17 | private readonly int start;
18 | private readonly IReadOnlyList chain;
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | /// The start.
24 | /// The chain.
25 | public NextChainEnumerable(int start, IReadOnlyList chain)
26 | {
27 | this.start = start;
28 | this.chain = chain;
29 | }
30 |
31 | ///
32 | public IEnumerator GetEnumerator() => new NextChainEnumerator(this.start, this.chain);
33 |
34 | ///
35 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
36 |
37 | ///
38 | /// Internal enumerator
39 | ///
40 | private sealed class NextChainEnumerator : IEnumerator
41 | {
42 | private readonly int start;
43 | private readonly IReadOnlyList chain;
44 | private bool reset;
45 | #if DEBUG
46 | private int maxIteratorCount;
47 | #endif
48 |
49 | ///
50 | /// Initializes a new instance of the class.
51 | ///
52 | /// The start.
53 | /// The chain.
54 | public NextChainEnumerator(int start, IReadOnlyList chain)
55 | {
56 | this.start = start;
57 | this.chain = chain;
58 | this.reset = true;
59 | #if DEBUG
60 | this.maxIteratorCount = chain.Count;
61 | #endif
62 | }
63 |
64 | ///
65 | public int Current { get; private set; }
66 |
67 | ///
68 | object IEnumerator.Current => this.Current;
69 |
70 | ///
71 | public void Dispose()
72 | {
73 | this.Current = -1;
74 | }
75 |
76 | ///
77 | public bool MoveNext()
78 | {
79 | if (this.reset)
80 | {
81 | this.reset = false;
82 | this.Current = this.start;
83 | }
84 | else
85 | {
86 | this.Current = this.chain[this.Current].Next;
87 | if (this.Current == this.start)
88 | {
89 | return false;
90 | }
91 | #if DEBUG
92 | if (--this.maxIteratorCount < 0)
93 | {
94 | throw new InvalidOperationException("Chain is damaged");
95 | }
96 | #endif
97 | }
98 |
99 | return true;
100 | }
101 |
102 | ///
103 | public void Reset()
104 | {
105 | this.reset = true;
106 | }
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/PolygonTriangulation/Polygon.VertexInfo.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System.Diagnostics;
4 |
5 | ///
6 | /// The action necessary for the vertex transition.
7 | /// Ordering is important, because for the same vertex id, we need to process closing before transition before opening
8 | ///
9 | public enum VertexAction
10 | {
11 | ///
12 | /// Prev and next are left of the vertex. => This is a closing cusp.
13 | ///
14 | ClosingCusp,
15 |
16 | ///
17 | /// Transition from one vertex to the net. No cusp.
18 | ///
19 | Transition,
20 |
21 | ///
22 | /// Prev and next are right of the vertex. => This is an opening cusp.
23 | ///
24 | OpeningCusp,
25 | }
26 |
27 | ///
28 | /// Information about an element in the vertex chain of a polygon.
29 | ///
30 | public interface IPolygonVertexInfo
31 | {
32 | ///
33 | /// Gets the action necessary to process the triple
34 | ///
35 | VertexAction Action { get; }
36 |
37 | ///
38 | /// Gets the id of the current vertex
39 | ///
40 | int Id { get; }
41 |
42 | ///
43 | /// Gets the id of the next vertex
44 | ///
45 | int NextVertexId { get; }
46 |
47 | ///
48 | /// Gets the id of the previous vertex
49 | ///
50 | int PrevVertexId { get; }
51 |
52 | ///
53 | /// Gets a unique identifier for overlaying vertexes
54 | ///
55 | int Unique { get; }
56 |
57 | ///
58 | /// Gets the for the next vertex
59 | ///
60 | int NextUnique { get; }
61 |
62 | ///
63 | /// Gets the for the prev vertex
64 | ///
65 | int PrevUnique { get; }
66 | }
67 |
68 | ///
69 | /// subclass container for polygon
70 | ///
71 | public partial class Polygon
72 | {
73 | ///
74 | /// Information about an element in the vertex chain.
75 | ///
76 | [DebuggerDisplay("{Prev}>{Id}>{Next}")]
77 | private class VertexInfo : IPolygonVertexInfo
78 | {
79 | private readonly int element;
80 | private readonly VertexChain[] chain;
81 |
82 | public VertexInfo(int element, VertexChain[] chain)
83 | {
84 | this.element = element;
85 | this.chain = chain;
86 |
87 | var id = this.Id;
88 | var prev = this.PrevVertexId;
89 | var next = this.NextVertexId;
90 | if (prev < id && next < id)
91 | {
92 | this.Action = VertexAction.ClosingCusp;
93 | }
94 | else if (prev > id && next > id)
95 | {
96 | this.Action = VertexAction.OpeningCusp;
97 | }
98 | else
99 | {
100 | this.Action = VertexAction.Transition;
101 | }
102 | }
103 |
104 | ///
105 | public VertexAction Action { get; }
106 |
107 | ///
108 | public int Id => this.chain[this.element].VertexId;
109 |
110 | ///
111 | public int NextVertexId => this.chain[this.chain[this.element].Next].VertexId;
112 |
113 | ///
114 | public int PrevVertexId => this.chain[this.chain[this.element].Prev].VertexId;
115 |
116 | ///
117 | public int Unique => this.element;
118 |
119 | ///
120 | public int NextUnique => this.chain[this.element].Next;
121 |
122 | ///
123 | public int PrevUnique => this.chain[this.element].Prev;
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PolygonTriangulator.ScanSplitByTrapezoidation.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | ///
8 | /// subclass container for triangulator
9 | ///
10 | public partial class PolygonTriangulator
11 | {
12 | ///
13 | /// Traverse the polygon, build trapezoids and collect possible splits
14 | ///
15 | private class ScanSplitByTrapezoidation : IPolygonSplitSink
16 | {
17 | private readonly Trapezoidation activeEdges;
18 | private readonly List> splits;
19 | private readonly Polygon polygon;
20 |
21 | private ScanSplitByTrapezoidation(Polygon polygon)
22 | {
23 | this.splits = new List>();
24 | this.polygon = polygon;
25 |
26 | this.activeEdges = new Trapezoidation(this.polygon.Vertices, this);
27 | }
28 |
29 | ///
30 | /// Build the splits for the polygon
31 | ///
32 | /// the polygon
33 | /// the splits
34 | public static IEnumerable> BuildSplits(Polygon polygon)
35 | {
36 | var splitter = new ScanSplitByTrapezoidation(polygon);
37 | splitter.BuildSplits(-1);
38 | return splitter.splits;
39 | }
40 |
41 | ///
42 | /// Traverse the polygon and build all splits
43 | ///
44 | /// number of steps during debugging. Use -1 for all
45 | public void BuildSplits(int stepCount)
46 | {
47 | foreach (var group in this.polygon.OrderedVertices.GroupBy(x => x.Id))
48 | {
49 | var actions = group.ToArray();
50 | if (actions.Length > 1)
51 | {
52 | actions = actions.OrderBy(x => x.Action).ToArray();
53 | }
54 |
55 | foreach (var info in actions)
56 | {
57 | if (stepCount >= 0)
58 | {
59 | stepCount -= 1;
60 | if (stepCount < 0)
61 | {
62 | return;
63 | }
64 | }
65 |
66 | switch (info.Action)
67 | {
68 | case VertexAction.ClosingCusp:
69 | this.activeEdges.HandleClosingCusp(info);
70 | break;
71 | case VertexAction.Transition:
72 | this.activeEdges.HandleTransition(info);
73 | break;
74 | case VertexAction.OpeningCusp:
75 | this.activeEdges.HandleOpeningCusp(info);
76 | break;
77 | default:
78 | throw new InvalidOperationException($"Unkown action {info.Action}");
79 | }
80 | }
81 | }
82 | }
83 |
84 | ///
85 | void IPolygonSplitSink.SplitPolygon(int leftVertex, int rightVertex)
86 | {
87 | this.splits.Add(Tuple.Create(leftVertex, rightVertex));
88 | }
89 |
90 | ///
91 | /// Run n steps and return the edges after that step
92 | ///
93 | /// the polygon
94 | /// the number of steps to run
95 | /// The edges sorted from High to Low
96 | internal static IEnumerable GetEdgesAfterPartialTrapezoidation(Polygon polygon, int depth)
97 | {
98 | var splitter = new ScanSplitByTrapezoidation(polygon);
99 | splitter.BuildSplits(depth);
100 | return splitter.activeEdges.Edges.Reverse().Select(x => x.ToString());
101 | }
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/PolygonTriangulation/PlanePolygonBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 |
5 | #if UNITY_EDITOR || UNITY_STANDALONE
6 | using Plane = UnityEngine.Plane;
7 | using Quaternion = UnityEngine.Quaternion;
8 | using Vector3 = UnityEngine.Vector3;
9 | #else
10 | using Plane = System.Numerics.Plane;
11 | using Quaternion = System.Numerics.Quaternion;
12 | using Vector3 = System.Numerics.Vector3;
13 | #endif
14 |
15 | ///
16 | /// Collect the edges for a plane polygon
17 | ///
18 | public interface IPlanePolygonEdgeCollector
19 | {
20 | ///
21 | /// Add an edge
22 | ///
23 | /// start point
24 | /// end point
25 | void AddEdge(Vector3 p0, Vector3 p1);
26 |
27 | ///
28 | /// Dump the collected edges
29 | ///
30 | /// dump the collected edges for debug
31 | string Dump();
32 | }
33 |
34 | ///
35 | /// Build a list of triangles from polygon edges
36 | ///
37 | public partial class PlanePolygonBuilder : IPlanePolygonEdgeCollector
38 | {
39 | private const float Epsilon = 1.1E-5f;
40 |
41 | private readonly EdgesToPolygonBuilder edgesToPolygon;
42 |
43 | ///
44 | /// Initializes a new instance of the class.
45 | ///
46 | /// The plane to rotate the 3D point into 2D.
47 | public PlanePolygonBuilder(Plane plane)
48 | {
49 | #if UNITY_EDITOR || UNITY_STANDALONE
50 | var rotation = Quaternion.FromToRotation(plane.normal, new Vector3(0, 0, -1));
51 | #else
52 | var rotation = IdendityQuaternion;
53 | if (plane.Normal != Vector3.UnitZ)
54 | {
55 | throw new NotImplementedException("rotation setup is not implemented");
56 | }
57 | #endif
58 | this.edgesToPolygon = new EdgesToPolygonBuilder(rotation);
59 | }
60 |
61 | ///
62 | /// Gets the 3D to 2D rotation
63 | ///
64 | public Quaternion Rotation => this.edgesToPolygon.Rotation;
65 |
66 | #if UNITY_EDITOR || UNITY_STANDALONE
67 | private static Quaternion IdendityQuaternion => Quaternion.identity;
68 | #else
69 | private static Quaternion IdendityQuaternion => Quaternion.Identity;
70 | #endif
71 |
72 | ///
73 | public void AddEdge(Vector3 p0, Vector3 p1)
74 | {
75 | this.edgesToPolygon.AddEdge(p0, p1);
76 | }
77 |
78 | ///
79 | public string Dump()
80 | {
81 | return this.edgesToPolygon.Dump();
82 | }
83 |
84 | ///
85 | /// Build the plane triangles
86 | ///
87 | /// The triangles, the 2D vertices and the 3D vertices
88 | public ITriangulatedPlanePolygon Build()
89 | {
90 | IPlanePolygon polygonResult = null;
91 | try
92 | {
93 | polygonResult = this.edgesToPolygon.BuildPolygon();
94 | var triangulator = new PolygonTriangulator(polygonResult.Polygon);
95 | var triangles = triangulator.BuildTriangles();
96 | return new TriangulatedPlanePolygon(polygonResult.Vertices, polygonResult.Polygon.Vertices, triangles);
97 | }
98 | catch (Exception e)
99 | {
100 | throw new TriangulationException(polygonResult?.Polygon, this.edgesToPolygon.Dump(), e);
101 | }
102 | }
103 |
104 | ///
105 | /// Create a edges to polygon builder for unit testing
106 | ///
107 | /// the polygon builder
108 | internal static IEdgesToPolygonBuilder CreatePolygonBuilder() => new EdgesToPolygonBuilder(IdendityQuaternion);
109 |
110 | ///
111 | /// Create a polygon from edges detector. The edge is defined by the vertex ids
112 | ///
113 | /// The fusion vertices.
114 | /// the detector
115 | internal static IPolygonLineDetector CreatePolygonLineDetector(params int[] fusionVertices) => new PolygonLineDetector(fusionVertices);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/PolygonTriangulation/RedBlackTree.DumpEnumerator.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | ///
8 | /// subclass container for redblacktree
9 | ///
10 | public sealed partial class RedBlackTree
11 | {
12 | ///
13 | /// Dump enumeration support. Prints one line per tree level with proper spacing.
14 | ///
15 | private class DumpEnumerator : IEnumerable
16 | {
17 | private readonly RedBlackTree tree;
18 | private readonly int configuredDept;
19 |
20 | public DumpEnumerator(RedBlackTree tree, int maxDepth)
21 | {
22 | this.tree = tree;
23 | this.configuredDept = maxDepth;
24 | }
25 |
26 | ///
27 | public IEnumerator GetEnumerator()
28 | {
29 | foreach (var line in this.Dump())
30 | {
31 | yield return line;
32 | }
33 | }
34 |
35 | ///
36 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
37 |
38 | ///
39 | /// Enumerate values, their red/black state and their depth. Used only to dump the tree.
40 | ///
41 | /// the current node
42 | /// the level of node
43 | /// the maximum allowed level
44 | /// tuple of value, red/black state, node depth
45 | private static IEnumerable<(T data, string color, int level)> EnumerateLevels(Node node, int level, int maxDepth)
46 | {
47 | Node left;
48 | Node right;
49 | if (node == null)
50 | {
51 | if (maxDepth < 0 || level > maxDepth)
52 | {
53 | yield break;
54 | }
55 |
56 | left = null;
57 | right = null;
58 | yield return (default(T), "_", level);
59 | }
60 | else
61 | {
62 | left = node.Left;
63 | right = node.Right;
64 | yield return (node.Data, node.ColorText, level);
65 | }
66 |
67 | foreach (var item in EnumerateLevels(left, level + 1, maxDepth))
68 | {
69 | yield return item;
70 | }
71 |
72 | foreach (var item in EnumerateLevels(right, level + 1, maxDepth))
73 | {
74 | yield return item;
75 | }
76 | }
77 |
78 | ///
79 | /// Create one line for each level and space the items suitable for 2 digit numbers
80 | ///
81 | /// array of strings, one line per level
82 | private IEnumerable Dump()
83 | {
84 | var maxDepth = this.configuredDept;
85 | if (maxDepth < 0)
86 | {
87 | if (((ICollection)this.tree).Count == 0)
88 | {
89 | return Enumerable.Empty();
90 | }
91 |
92 | maxDepth = EnumerateLevels(this.tree.root, 0, -1).Select(x => x.level).Max();
93 | }
94 |
95 | var groups = EnumerateLevels(this.tree.root, 0, maxDepth)
96 | .GroupBy(x => x.level)
97 | .OrderBy(x => x.Key)
98 | .ToArray();
99 |
100 | var height = groups.Length;
101 | var total = 1 << height;
102 | var itemLenght = 4 + 1;
103 |
104 | return groups.Select(g =>
105 | {
106 | var spacingFactor = total / (1 << g.Key) / 2;
107 | var spacing = new string(' ', ((itemLenght + 1) * (spacingFactor - 1)) + 1);
108 | var left = new string(' ', (spacing.Length - 1) / 2);
109 | var rest = string.Join(spacing, g.Select(x => ((Equals(x.data, default(T)) && !char.IsLower(x.color[0])) ? "- " : $"{x.data}{x.color}").PadLeft(itemLenght)));
110 | return left + rest;
111 | });
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/PolygonTriangulation/Polygon.Builder.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | #if UNITY_EDITOR || UNITY_STANDALONE
7 | using Vertex = UnityEngine.Vector2;
8 | #else
9 | using Vertex = System.Numerics.Vector2;
10 | #endif
11 |
12 | ///
13 | /// Build a polygon
14 | ///
15 | public interface IPolygonBuilder
16 | {
17 | ///
18 | /// Add a single vertex id
19 | ///
20 | /// the index in the vertices array of the builder
21 | /// the same builder instance for call chains
22 | IPolygonBuilder Add(int vertexId);
23 |
24 | ///
25 | /// Add multiple vertex ids
26 | ///
27 | /// the indices in the vertices array of the builder
28 | /// the same builder instance for call chains
29 | IPolygonBuilder AddVertices(IEnumerable vertices);
30 |
31 | ///
32 | /// Close the current polygon. Next vertices are considered a new polygon line i.e a hole or a non-intersecting polygon
33 | ///
34 | /// the same builder instance for call chains
35 | IPolygonBuilder ClosePartialPolygon();
36 |
37 | ///
38 | /// Close the polygon. Do not use the builder after closing it.
39 | ///
40 | /// vertices that are used in more than one subpolygon
41 | /// a polygon
42 | Polygon Close(params int[] fusionVertices);
43 |
44 | ///
45 | /// Create one polygon that includes all vertices in the builder.
46 | ///
47 | /// a polygon
48 | Polygon Auto();
49 | }
50 |
51 | ///
52 | /// subclass container for polygon
53 | ///
54 | public partial class Polygon
55 | {
56 | ///
57 | /// Build a new polygon
58 | ///
59 | private class PolygonBuilder : IPolygonBuilder
60 | {
61 | private readonly Vertex[] vertices;
62 | private readonly List vertexIds;
63 | private readonly List nextIndices;
64 | private readonly List polygonIds;
65 | private int first;
66 | private int polygonId;
67 |
68 | public PolygonBuilder(Vertex[] vertices)
69 | {
70 | this.first = 0;
71 | this.vertices = vertices;
72 | this.vertexIds = new List();
73 | this.nextIndices = new List();
74 | this.polygonIds = new List();
75 | this.polygonId = 0;
76 | }
77 |
78 | public Polygon Auto()
79 | {
80 | return Polygon.FromVertexList(
81 | this.vertices,
82 | Enumerable.Range(0, this.vertices.Length),
83 | Enumerable.Range(1, this.vertices.Length - 1).Concat(Enumerable.Range(0, 1)),
84 | Enumerable.Repeat(0, this.vertices.Length),
85 | null);
86 | }
87 |
88 | public IPolygonBuilder Add(int vertexId)
89 | {
90 | this.nextIndices.Add(this.nextIndices.Count + 1);
91 | this.vertexIds.Add(vertexId);
92 | this.polygonIds.Add(this.polygonId);
93 | return this;
94 | }
95 |
96 | public IPolygonBuilder AddVertices(IEnumerable vertices)
97 | {
98 | foreach (var vertex in vertices)
99 | {
100 | this.nextIndices.Add(this.nextIndices.Count + 1);
101 | this.vertexIds.Add(vertex);
102 | this.polygonIds.Add(this.polygonId);
103 | }
104 |
105 | return this;
106 | }
107 |
108 | public IPolygonBuilder ClosePartialPolygon()
109 | {
110 | if (this.vertexIds.Count > this.first)
111 | {
112 | this.nextIndices[this.nextIndices.Count - 1] = this.first;
113 | this.polygonId++;
114 | this.first = this.vertexIds.Count;
115 | }
116 |
117 | return this;
118 | }
119 |
120 | public Polygon Close(params int[] fusionVertices)
121 | {
122 | this.ClosePartialPolygon();
123 | return Polygon.FromVertexList(this.vertices, this.vertexIds, this.nextIndices, this.polygonIds, fusionVertices);
124 | }
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/PolygonTriangulation/TriangulationException.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Linq;
5 | using System.Runtime.Serialization;
6 |
7 | ///
8 | /// polygon data for exceptions during polygon triangulation
9 | ///
10 | [Serializable]
11 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1032:Standardausnahmekonstruktoren implementieren", Justification = "Exception must have a polygon")]
12 | public class TriangulationException : InvalidOperationException
13 | {
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The polygon.
18 | /// The edge create code.
19 | /// The inner exception.
20 | public TriangulationException(Polygon polygon, string edgeCreateCode, Exception innerException)
21 | : this(polygon, edgeCreateCode, innerException.Message, innerException)
22 | {
23 | }
24 |
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | /// The polygon.
29 | /// The edge create code.
30 | /// The message.
31 | /// The inner exception.
32 | public TriangulationException(Polygon polygon, string edgeCreateCode, string message, Exception innerException)
33 | : base(message, innerException)
34 | {
35 | this.PolygonCreateCode = BuildPolygonCode(polygon);
36 | this.EdgeCreateCode = edgeCreateCode;
37 | }
38 |
39 | ///
40 | /// Initializes a new instance of the class. Used during deserialization.
41 | ///
42 | /// The object that holds the serialized object data.
43 | /// The contextual information about the source or destination.
44 | protected TriangulationException(SerializationInfo info, StreamingContext context)
45 | : base(info, context)
46 | {
47 | this.EdgeCreateCode = info.GetString(nameof(this.EdgeCreateCode));
48 | this.PolygonCreateCode = info.GetString(nameof(this.PolygonCreateCode));
49 | }
50 |
51 | ///
52 | /// Gets the code to feed the edges to a polygon builder.
53 | ///
54 | public string EdgeCreateCode { get; }
55 |
56 | ///
57 | /// Gets the code to create the polygon in a unittest
58 | ///
59 | public string PolygonCreateCode { get; }
60 |
61 | ///
62 | public override void GetObjectData(SerializationInfo info, StreamingContext context)
63 | {
64 | base.GetObjectData(info, context);
65 | info.AddValue(nameof(this.EdgeCreateCode), this.EdgeCreateCode);
66 | info.AddValue(nameof(this.PolygonCreateCode), this.PolygonCreateCode);
67 | }
68 |
69 | ///
70 | /// Create a polygon string that can be used to create the polygon
71 | ///
72 | /// The polygon to convert to code.
73 | /// polygon as code
74 | internal static string BuildPolygonCode(Polygon polygon)
75 | {
76 | if (polygon == null)
77 | {
78 | return string.Empty;
79 | }
80 |
81 | var sb = new System.Text.StringBuilder();
82 | var culture = System.Globalization.CultureInfo.InvariantCulture;
83 |
84 | sb.AppendLine("var vertices = new[]");
85 | sb.AppendLine("{");
86 | #if UNITY_EDITOR || UNITY_STANDALONE
87 | var vertexStrings = polygon.Vertices.Select(
88 | x => string.Format(culture, " new Vertex({0:0.0000000}f, {1:0.0000000}f),", x.x, x.y));
89 | #else
90 | var vertexStrings = polygon.Vertices.Select(
91 | x => string.Format(culture, " new Vertex({0:0.0000000}f, {1:0.0000000}f),", x.X, x.Y));
92 | #endif
93 | sb.AppendLine(string.Join(Environment.NewLine, vertexStrings));
94 | sb.AppendLine("};");
95 | sb.AppendLine(string.Empty);
96 |
97 | sb.AppendLine("var polygon = Polygon.Build(vertices)");
98 | foreach (var subPolygonId in polygon.SubPolygonIds)
99 | {
100 | sb.AppendLine($" .AddVertices({string.Join(", ", polygon.SubPolygonVertices(subPolygonId))})");
101 | sb.AppendLine($" .ClosePartialPolygon()");
102 | }
103 |
104 | var fusionVerticex = polygon.SubPolygonIds
105 | .SelectMany(x => polygon.SubPolygonVertices(x))
106 | .GroupBy(x => x)
107 | .Where(x => x.Count() > 1)
108 | .Select(x => x.Key);
109 | sb.AppendLine($" .Close({string.Join(", ", fusionVerticex)});");
110 |
111 | return sb.ToString();
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/PolygonDisplay/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/PolygonDisplay/Form1.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/PolygonDisplay/PolygonDrawControl.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/PolygonTriangulation/PlanePolygonBuilder.EdgesToPolygonBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | #if UNITY_EDITOR || UNITY_STANDALONE
9 | using Quaternion = UnityEngine.Quaternion;
10 | using Vector3 = UnityEngine.Vector3;
11 | using Vertex = UnityEngine.Vector2;
12 | #else
13 | using Quaternion = System.Numerics.Quaternion;
14 | using Vector3 = System.Numerics.Vector3;
15 | using Vertex = System.Numerics.Vector2;
16 | #endif
17 |
18 | ///
19 | /// Test interface for the polygon builder
20 | ///
21 | internal interface IEdgesToPolygonBuilder
22 | {
23 | ///
24 | /// Add an edge between the two points
25 | ///
26 | /// start point
27 | /// end point
28 | void AddEdge(Vector3 p0, Vector3 p1);
29 |
30 | ///
31 | /// build the resulting polygon
32 | ///
33 | /// A polygon and the 3D vertices
34 | IPlanePolygon BuildPolygon();
35 | }
36 |
37 | ///
38 | /// subclass container
39 | ///
40 | public partial class PlanePolygonBuilder
41 | {
42 | ///
43 | /// Build a polygon from 3D edges, lying on a common plane.
44 | ///
45 | private class EdgesToPolygonBuilder : IEdgesToPolygonBuilder
46 | {
47 | ///
48 | /// An empty hash set
49 | ///
50 | private static readonly ICollection EmptyHashSet = new HashSet();
51 |
52 | ///
53 | /// current edges in pairs
54 | ///
55 | private readonly List edges;
56 |
57 | ///
58 | /// original vertices
59 | ///
60 | private readonly List vertices3D;
61 |
62 | ///
63 | /// rotated vertices
64 | ///
65 | private readonly List vertices2D;
66 |
67 | ///
68 | /// Initializes a new instance of the class.
69 | ///
70 | /// the rotation to map a vertex to a 2D plane
71 | public EdgesToPolygonBuilder(Quaternion rotation)
72 | {
73 | this.edges = new List();
74 | this.vertices3D = new List();
75 | this.vertices2D = new List();
76 |
77 | this.Rotation = rotation;
78 | }
79 |
80 | ///
81 | /// Gets the 3D to 2D rotation
82 | ///
83 | public Quaternion Rotation { get; }
84 |
85 | ///
86 | /// Dump the edges during debugging
87 | ///
88 | public string Dump()
89 | {
90 | #if DEBUG
91 | var sb = new StringBuilder();
92 |
93 | sb.AppendLine("var builder = PlanePolygonBuilder.CreatePolygonBuilder();");
94 | for (int i = 0; i < this.vertices2D.Count - 1; i += 2)
95 | {
96 | #if UNITY_EDITOR || UNITY_STANDALONE
97 | sb.AppendLine($"builder.AddEdge(new Vector3({this.vertices2D[i].x:0.00000000}f, {this.vertices2D[i].y:0.00000000}f, 0), new Vector3({this.vertices2D[i + 1].x:0.00000000}f, {this.vertices2D[i + 1].y:0.00000000}f, 0));");
98 | #else
99 | sb.AppendLine($"builder.AddEdge(new Vector3({this.vertices2D[i].X:0.00000000}f, {this.vertices2D[i].Y:0.00000000}f, 0), new Vector3({this.vertices2D[i + 1].X:0.00000000}f, {this.vertices2D[i + 1].Y:0.00000000}f, 0));");
100 | #endif
101 | }
102 |
103 | sb.AppendLine("builder.BuildPolygon();");
104 |
105 | return sb.ToString();
106 | #else
107 | return this.ToString();
108 | #endif
109 | }
110 |
111 | ///
112 | /// Add an edge
113 | ///
114 | /// start point
115 | /// end point
116 | public void AddEdge(Vector3 p0, Vector3 p1)
117 | {
118 | var planeTriangleOffset = this.vertices2D.Count;
119 | this.vertices3D.Add(p0);
120 | this.vertices3D.Add(p1);
121 | #if UNITY_EDITOR || UNITY_STANDALONE
122 | var p0Rotated = this.Rotation * p0;
123 | this.vertices2D.Add(new Vertex(p0Rotated.x, p0Rotated.y));
124 |
125 | var p1Rotated = this.Rotation * p1;
126 | this.vertices2D.Add(new Vertex(p1Rotated.x, p1Rotated.y));
127 | #else
128 | var p0Rotated = Vector3.Transform(p0, this.Rotation);
129 | this.vertices2D.Add(new Vertex(p0Rotated.X, p0Rotated.Y));
130 |
131 | var p1Rotated = Vector3.Transform(p1, this.Rotation);
132 | this.vertices2D.Add(new Vertex(p1Rotated.X, p1Rotated.Y));
133 | #endif
134 |
135 | this.edges.Add(planeTriangleOffset + 0);
136 | this.edges.Add(planeTriangleOffset + 1);
137 | }
138 |
139 | ///
140 | /// Compress the vertices and connect all edges to polygons
141 | ///
142 | /// the polygon and the 3D vertices
143 | public IPlanePolygon BuildPolygon()
144 | {
145 | var sorted2D = this.vertices2D.ToArray();
146 | var sortedIndizes = Enumerable.Range(0, sorted2D.Length).ToArray();
147 | var comparer = new ClusterVertexComparer();
148 | ICollection fusionedVertices = null;
149 | Array.Sort(sorted2D, sortedIndizes, comparer);
150 |
151 | var translation = new int[sorted2D.Length];
152 | var writeIndex = 0;
153 | var sameVertexCount = 0;
154 | for (int i = 1; i < sorted2D.Length; i++)
155 | {
156 | if (comparer.Compare(sorted2D[writeIndex], sorted2D[i]) != 0)
157 | {
158 | sorted2D[++writeIndex] = sorted2D[i];
159 | sameVertexCount = 0;
160 | }
161 | else
162 | {
163 | if (++sameVertexCount == 2)
164 | {
165 | fusionedVertices = fusionedVertices ?? new HashSet();
166 | fusionedVertices.Add(writeIndex);
167 | }
168 | }
169 |
170 | translation[sortedIndizes[i]] = writeIndex;
171 | }
172 |
173 | var count = writeIndex + 1;
174 | Array.Resize(ref sorted2D, count);
175 |
176 | var compressed3D = new Vector3[sorted2D.Length];
177 | for (int i = 0; i < translation.Length; i++)
178 | {
179 | compressed3D[translation[i]] = this.vertices3D[i];
180 | }
181 |
182 | var lineDetector = new PolygonLineDetector(fusionedVertices ?? EmptyHashSet);
183 | lineDetector.JoinEdgesToPolygones(this.edges.Select(x => translation[x]));
184 |
185 | if (lineDetector.UnclosedPolygons.Any())
186 | {
187 | lineDetector.TryClusteringUnclosedEnds(sorted2D, Epsilon * 100);
188 | }
189 |
190 | var polygon = Polygon.FromPolygonLines(sorted2D, lineDetector.Lines.Select(x => x.ToIndexes()).ToArray(), fusionedVertices);
191 | return new PlanePolygonData(compressed3D, polygon);
192 | }
193 | }
194 | }
195 | }
--------------------------------------------------------------------------------
/PolygonTriangulation/PolygonTriangulator.MonotonePolygonTriangulator.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonTriangulation
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | #if UNITY_EDITOR || UNITY_STANDALONE
7 | using Vertex = UnityEngine.Vector2;
8 | #else
9 | using Vertex = System.Numerics.Vector2;
10 | #endif
11 |
12 | ///
13 | /// subclass container for triangulator
14 | ///
15 | public partial class PolygonTriangulator
16 | {
17 | ///
18 | /// Class to triangluate a monotone polygon
19 | ///
20 | private class MonotonePolygonTriangulator
21 | {
22 | private readonly Polygon polygon;
23 | private readonly int subPolygonId;
24 | private readonly IReadOnlyList vertices;
25 | private readonly Stack vertexStack;
26 | private int third;
27 | private int second;
28 | private int current;
29 | private IEnumerator iterator;
30 |
31 | public MonotonePolygonTriangulator(Polygon polygon, int subPolygonId)
32 | {
33 | this.polygon = polygon;
34 | this.subPolygonId = subPolygonId;
35 | this.vertices = polygon.Vertices;
36 | this.vertexStack = new Stack();
37 | }
38 |
39 | ///
40 | /// traverse the polygon and add triangles to the collector
41 | ///
42 | /// collector for resulting triangles
43 | public void Build(ITriangleCollector collector)
44 | {
45 | var start = this.FindStartOfMonotonePolygon();
46 | if (start >= 0)
47 | {
48 | this.TriangulateMonotonePolygon(start, collector);
49 | }
50 | else
51 | {
52 | var triangleVertices = this.polygon.SubPolygonVertices(this.subPolygonId).ToArray();
53 | collector.AddTriangle(triangleVertices[0], triangleVertices[1], triangleVertices[2]);
54 | }
55 | }
56 |
57 | ///
58 | /// Create triangles for a monotone polygon
59 | ///
60 | /// the first point (clockwise) of the long edge.
61 | /// the collector for resulting triangles
62 | private void TriangulateMonotonePolygon(int startPoint, ITriangleCollector result)
63 | {
64 | this.PullFirstTriangle(startPoint);
65 | while (true)
66 | {
67 | if (this.IsConvexCorner())
68 | {
69 | result.AddTriangle(this.current, this.third, this.second);
70 | if (!this.PopOrPullNextVertex())
71 | {
72 | return;
73 | }
74 | }
75 | else
76 | {
77 | this.PushAndPullNextVertex();
78 | }
79 | }
80 | }
81 |
82 | ///
83 | /// Gets the first three points from the triangle
84 | ///
85 | /// the start vertex, aka the target vertex of the long edge
86 | private void PullFirstTriangle(int startPoint)
87 | {
88 | this.iterator = this.polygon.IndicesStartingAt(startPoint, this.subPolygonId).GetEnumerator();
89 | this.iterator.MoveNext();
90 | this.third = this.iterator.Current;
91 | this.iterator.MoveNext();
92 | this.second = this.iterator.Current;
93 | this.iterator.MoveNext();
94 | this.current = this.iterator.Current;
95 | }
96 |
97 | ///
98 | /// Current triangle is not valid, push the third point, shift down and pull the next vertex from the polygon
99 | ///
100 | private void PushAndPullNextVertex()
101 | {
102 | this.vertexStack.Push(this.third);
103 | this.third = this.second;
104 | this.second = this.current;
105 | if (!this.iterator.MoveNext())
106 | {
107 | throw new InvalidOperationException("Triangle is incomplete");
108 | }
109 |
110 | this.current = this.iterator.Current;
111 | }
112 |
113 | ///
114 | /// either pop the last vertex from the stack or get the next vertex from the polygon
115 | ///
116 | /// true if there is one more vertex
117 | private bool PopOrPullNextVertex()
118 | {
119 | if (this.vertexStack.Count > 0)
120 | {
121 | this.second = this.third;
122 | this.third = this.vertexStack.Pop();
123 | }
124 | else if (!this.iterator.MoveNext())
125 | {
126 | return false;
127 | }
128 | else
129 | {
130 | this.second = this.current;
131 | this.current = this.iterator.Current;
132 | }
133 |
134 | return true;
135 | }
136 |
137 | ///
138 | /// Test if the current three vertices form a clockwise triangle.
139 | ///
140 | /// true if the triangle is valid
141 | private bool IsConvexCorner()
142 | {
143 | var v0 = this.vertices[this.current];
144 | var v1 = this.vertices[this.second];
145 | var v2 = this.vertices[this.third];
146 | #if UNITY_EDITOR || UNITY_STANDALONE
147 | var cross = ((v2.x - v0.x) * (v1.y - v0.y)) - ((v2.y - v0.y) * (v1.x - v0.x));
148 | #else
149 | var cross = ((v2.X - v0.X) * (v1.Y - v0.Y)) - ((v2.Y - v0.Y) * (v1.X - v0.X));
150 | #endif
151 | return cross < 0;
152 | }
153 |
154 | ///
155 | /// Find the point in the polygon that starts at the monotone side
156 | ///
157 | /// highest/lowest point in the polygon, depending if itss left hand or right hand. -1 if its a triangle.
158 | private int FindStartOfMonotonePolygon()
159 | {
160 | var startLookupIterator = this.polygon.SubPolygonVertices(this.subPolygonId).GetEnumerator();
161 | startLookupIterator.MoveNext();
162 | var first = startLookupIterator.Current;
163 | var posmax = first;
164 | var posmin = first;
165 |
166 | var movedNext = startLookupIterator.MoveNext();
167 | var posmaxNext = startLookupIterator.Current;
168 | var count = 1;
169 |
170 | while (movedNext)
171 | {
172 | var index = startLookupIterator.Current;
173 | movedNext = startLookupIterator.MoveNext();
174 |
175 | if (index > posmax)
176 | {
177 | posmax = index;
178 | posmaxNext = movedNext ? startLookupIterator.Current : first;
179 | }
180 |
181 | if (index < posmin)
182 | {
183 | posmin = index;
184 | }
185 |
186 | count++;
187 | }
188 |
189 | if (count == 3)
190 | {
191 | return -1;
192 | }
193 |
194 | if (posmin == posmaxNext)
195 | {
196 | // LHS is a single segment and it's next in the chain
197 | return posmaxNext;
198 | }
199 | else
200 | {
201 | return posmax;
202 | }
203 | }
204 | }
205 | }
206 | }
--------------------------------------------------------------------------------
/Documentation/Polygon.md:
--------------------------------------------------------------------------------
1 | # Polygon
2 |
3 | A polygon holds a number of vertices, connnected by edges that enclose the polygon area.
4 | During the [Trapezoidation](Trapezoidation.md), the polygon is split into multiple [monotone polygons](Monotones.md).
5 |
6 | A polygon can contain multiple polygon lines, called __sub polygons__.
7 | The enclosed areas can be side by side or they define a hole inside another polygon area.
8 | The edges of the polygon must not intersect.
9 |
10 | Each edge is directed, the polygon area is always to the right of the edge.
11 | Hence the vertices are connected in clock wise order and holes in counter clock wise order.
12 |
13 |
14 |
15 | ## Generating a polygon from edges
16 |
17 | The `PlanePolygonBuilder` creates a polygon from edges on a 3D plane.
18 | The vertices are first rotated to the plane normal and treated as 2D coordinates afterwards.
19 |
20 | Very close vertices are considered the same to compensate rounding issues. That happens during the sort of the vertices.
21 |
22 | Edges, that are not part of a closed polygon, are listed in `IPolygonLineDetector.UnclosedPolygons`.
23 | The top level call of the library, `PlanePolygonBuilder.Build`, simply ignores unclosed polygons.
24 |
25 | ## Splitting a polygon
26 |
27 | The core operation for the polygon is to be splitted into multiple sub polygons.
28 | The initial polygon contains a single sub polygon: 0 1 2 5 4 3.
29 | After the split between 2 and 3, there are 2 sub polygons: 0 1 2 3 and 2 5 4 3.
30 | The vertices 2 and 3 belong to two sub polygons.
31 |
32 |
33 |
34 | A vertex can be the target of multiple splits, so it will be part of more than 2 _sub polygons_.
35 | There is always exactly one _sub polygon_, which contains both vertices of a split.
36 | `FindCommonChain` selects the correct instance for both vertices.
37 |
38 | ### Handling of holes
39 |
40 | A hole is a special case of a split. It's easily detected because the two vertices belong to different sub polygons.
41 | The `JoinHoleIntoPolygon` creates two edges, the first connects from 1 to 2 and the second connects from 2' to 1'.
42 | The resulting single sub polygon is defined by 0 1 2 3 5 4 2' 1' 7 6.
43 |
44 | The second split 5-6 splits that polygon again into two sub polygons: 0 1 2 3 5 6 and 1' 7 6 5 4 2'.
45 |
46 | All splits, that join a hole into another sub polygon are processed first.
47 |
48 |
49 |
50 | ### Combination of splits and holes at a single vertex
51 |
52 | For the next polygon, the [Trapezoidation](Trapezoidation.md) finds the splits 1-2, 2-4, 4-5 and 6-7.
53 | The first split, 1-2, joins the hole into the polygon.
54 |
55 | The next split 2-4 can start either at __2__ or __2'__.
56 | Splitting along 2'-4 creates the correct sub polygons 1' 4 2' and 0 1 2 3 6 5 2' 4 8 7.
57 | Splitting along 2-4 creates the wrong sub polygons 1' 4 2 3 6 5 2' and 0 1 2' 4 8 7.
58 | These sub polygons overlap each other, causing later processing errors.
59 |
60 | `ChooseInstanceForSplit` chooses the correct vertex __2__ between _5 __2'__ 1'_ and _1 __2__ 3_.
61 | The new edge must be between the incoming edge and the outgoing edge,
62 | e.g. 2'-4 is between 2'-5 and 2'-1' but 2-4 is not between 2-1 and 2-3.
63 |
64 |
65 |
66 | ## Internal structure of a polygon
67 |
68 | The 2D vertices are stored in the array `Polygon.Vertices`. The array is never modified.
69 | The vertices must be sorted from left to right and then from bottom to top.
70 |
71 | The polygon outline of a _sub polygon_ is represented by a chain of _vertex instances_.
72 | The `chain` is an array of type `Polygon.VertexChain` and can contain multiple instances of the same Vertex:
73 | * The `VertexId`, defined as array index in `Polygon.Vertices`.
74 | * The `Prev` and `Next` chain element in the current _sub polygon_, indices in `chain`.
75 | * The `SubPolygonId` of the _vertex instance_.
76 | * The `SameVertexChain` points to the next _vertex instance_ with the same `VertexId` but a different `SubPolygonId`.
77 |
78 | The `vertexToChain` maps the _vertex id_ to the index in the vertex `chain`.
79 |
80 | The _sub polygons_ store the index of one chain element (aka _vertex instance_) in `polygonStartIndices`.
81 | The index in that array is the _sub polygon id_, which is used by `Polygon.SubPolygonIds` and `VertexChain.SubPolygonId`.
82 | The vertex list of a _sub polygon_ is available by `Polygon.SubPolygonVertices(int subPolygonId)`.
83 |
84 | ## Crafting a polygon for unittests
85 |
86 | Polygons can be created by `Polygon.Build`.
87 | The vertices must be sorted in X and then in Y direction and they're added in clockwise order.
88 | The vertex id is the index in the `sortedVertices` array:
89 | ``` csharp
90 | var sortedVertices = new[]
91 | {
92 | new Vertex(0, 0),
93 | new Vertex(0, 1),
94 | new Vertex(1, 0),
95 | new Vertex(1, 1),
96 | };
97 |
98 | var polygon = Polygon.Build(sortedVertices)
99 | .AddVertices(0, 1, 3, 2)
100 | .Close();
101 | ```
102 |
103 | A polygon can contains multiple side by side polygons or even holes. The vertices of a hole must be connected in counter-clockwise order:
104 | ``` csharp
105 | var polygon = Polygon.Build(sortedVertices)
106 | .AddVertices(1, 0, 2, 5, 8, 6)
107 | .ClosePartialPolygon()
108 | .AddVertices(3, 4, 7)
109 | .Close();
110 | ```
111 |
112 | A fusion vertex can join two or more polygons in a common vertex. Those must be mentioned during `Close()`:
113 |
114 | ``` csharp
115 | var polygon = Polygon.Build(sortedVertices)
116 | .AddVertices(0, 1, 2)
117 | .ClosePartialPolygon()
118 | .AddVertices(2, 3, 4)
119 | .ClosePartialPolygon()
120 | .AddVertices(4, 5, 6)
121 | .Close(2, 4);
122 | ```
123 |
124 | ## Fusion vertices
125 |
126 | Sometimes the very same vertex is used in two different sub polygons. The two polygons __fusion__ in that point.
127 | That can be two separate polygons or a hole in the polygon. The same two polygons can even touch multiple times.
128 |
129 |
130 |
131 |
132 | ### Fusion vertices during polygon building
133 | The polygon outline must never cross itself.
134 | During polygon construction, `FusionVerticesIntoChain` sorts the edges around each fusion point in clockwise order.
135 | A valid polygon order is 0 2 __5__ 1 3 __5__ 4 6 __5__ 7 8 __5__ 9 10.
136 |
137 |
138 |
139 | But it would be invalid to use 0 2 __5__ 4 6 __5__ 1 3 __5__ 7 8 __5__ 9 10, because 2 5 4 crosses 3 5 7.
140 |
141 |
142 |
143 | ### Fusion vertices during Trapezoidation
144 | The [Trapezoidation](Trapezoidation.md) will process a fusioned vertex multiple times.
145 | The processing order is "Joining cusps", "Transitions", "Opening Cusps", which resembles the left to right processing of the vertices.
146 |
147 |
148 |
149 | A bad example: consider processing the opening cusp 6-4-7 _before_ the transition 2-4-5:
150 | * Order of the active edges before 6-4-7 from high to low: 2-4 0-8.
151 | * 4-6 is inserted and considered to be below 2-4 and above 0-8.
152 | * Active edges after inserting the cusp: 2-4 4-7 4-6 0-8.
153 | * The transition from 2-4 to 4-5 just replaces 2-4 by 4-5. (A transition expects to stay on the same level.)
154 | * Active edges after transition: 4-5 4-7 4-6 0-8. => Corrupted.
155 |
156 | > The problem effectively arises because the vertex 4 (left vertex of the inserted edge) is compared to edge 2-4.
157 | > But it's neither above nor below.
158 | >
159 | > Processing all closing cusps and transitions before the insert operation ensures, that there is no active edge having the fusion vertex on the right.
160 |
161 | Processing the transition before the opening cusp, edge 4-6 is compared to 4-5.
162 | The comparer detects that both edges have the same left vertex and chooses the right vertex 6 for comparison.
163 |
--------------------------------------------------------------------------------
/PolygonDisplay/Form1.cs:
--------------------------------------------------------------------------------
1 | namespace PolygonDisplay
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Drawing;
6 | using System.Linq;
7 | using System.Text.RegularExpressions;
8 | using System.Windows.Forms;
9 |
10 | using PolygonTriangulation;
11 |
12 | ///
13 | /// A simple form to select polygons and host a polygon draw control
14 | ///
15 | public partial class Form1 : Form, IPolygonForm
16 | {
17 | private readonly PolygonController controller;
18 |
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | public Form1()
23 | {
24 | this.InitializeComponent();
25 | this.controller = new PolygonController(this);
26 | this.ActivateStorage(1);
27 | }
28 |
29 | ///
30 | /// Refreshes the state.
31 | ///
32 | public void RefreshState()
33 | {
34 | this.UpdateSelectedBufferState();
35 | this.polygonOrderLabel.Text = string.Join(" ", this.controller.Polygon.Debug);
36 | this.vertexIdSelector.Maximum = this.controller.Polygon.Vertices.Count() - 1;
37 | this.vertexIdSelector.Value = Math.Max(0, Math.Min(this.vertexIdSelector.Maximum, this.polygonPanel.HighlightIndex));
38 | }
39 |
40 | ///
41 | /// Gets the id of the storage button by it's name
42 | ///
43 | /// The sender.
44 | /// the id of the selected storage
45 | private static int IdOfStorageButton(object sender)
46 | {
47 | return int.Parse((sender as Control).Name.Substring(1));
48 | }
49 |
50 | ///
51 | /// Handles the Click event of the storage control.
52 | ///
53 | /// The source of the event.
54 | /// The instance containing the event data.
55 | private void storage_Click(object sender, EventArgs e)
56 | {
57 | var id = IdOfStorageButton(sender);
58 | this.ActivateStorage(id);
59 | }
60 |
61 | ///
62 | /// Activates the storage.
63 | ///
64 | /// The identifier.
65 | private void ActivateStorage(int id)
66 | {
67 | this.controller.ActivateStorage(id);
68 | this.polygonPanel.Polygon = this.controller.Polygon;
69 | this.polygonPanel.Splits = this.controller.Splits;
70 | this.vertexText.Text = string.Join(
71 | Environment.NewLine,
72 | this.controller.Polygon.OrderedVertices
73 | .Select(x => $"{x.PrevVertexId}>{x.Id}>{x.NextVertexId}")
74 | .ToArray());
75 |
76 | if ((Control.ModifierKeys & Keys.Shift) == 0)
77 | {
78 | this.polygonPanel.AutoScale();
79 | }
80 |
81 | this.RefreshState();
82 | }
83 |
84 | ///
85 | /// Handles the Click event of the button1 control.
86 | ///
87 | /// The source of the event.
88 | /// The instance containing the event data.
89 | private void button1_Click(object sender, EventArgs e)
90 | {
91 | this.vertexText.Lines = this.controller.BuildTrapezoidationDebug();
92 | }
93 |
94 | ///
95 | /// Updates the selected state for the selected buffer.
96 | ///
97 | private void UpdateSelectedBufferState()
98 | {
99 | var buttons = this.storagePanel.Controls
100 | .OfType