├── 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 | Monotones 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 | Monotone A Monotone B 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 | Monotone Three 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 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 2 | [![.NET Core](https://github.com/git-ruttmann/PolygonTriangulation/workflows/.NET%20Core/badge.svg)](https://github.com/git-ruttmann/PolygonTriangulation/actions?query=workflow%3A%22.NET+Core%22) 3 | [![Codacy Quality](https://api.codacy.com/project/badge/Grade/26eb06d8a0d84eff830589eb4d2a99d5)](https://app.codacy.com/manual/git-ruttmann/PolygonTriangulation) 4 | [![Codacy Coverage](https://app.codacy.com/project/badge/Coverage/26eb06d8a0d84eff830589eb4d2a99d5)](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 | Before single split 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 | Before single split Monotone A 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 | Hole integrated by split Hole after full split 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 | Split after join 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 | Single Fusion Single Fusion 130 | Single Fusion Single Fusion 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 | Good Fusion Order 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 | Bad Fusion Order 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 | Single Fusion 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