├── Runtime ├── iShape │ ├── Triangulation │ │ ├── Extension.meta │ │ ├── Shape │ │ │ ├── Delaunay │ │ │ │ ├── CentroidNet.cs.meta │ │ │ │ ├── IndexBuffer.cs.meta │ │ │ │ ├── Tessellation.cs.meta │ │ │ │ ├── Side.cs.meta │ │ │ │ ├── Delaunay.cs.meta │ │ │ │ ├── SliceBuffer.cs.meta │ │ │ │ ├── Triangle.cs.meta │ │ │ │ ├── ConvexPolygon.cs.meta │ │ │ │ ├── TriangleStack.cs.meta │ │ │ │ ├── Triangulation.cs.meta │ │ │ │ ├── Side.cs │ │ │ │ ├── IndexBuffer.cs │ │ │ │ ├── TriangleStack.cs │ │ │ │ ├── Triangle.cs │ │ │ │ ├── ConvexPolygon.cs │ │ │ │ ├── SliceBuffer.cs │ │ │ │ ├── Triangulation.cs │ │ │ │ ├── Tessellation.cs │ │ │ │ ├── CentroidNet.cs │ │ │ │ └── Delaunay.cs │ │ │ ├── Delaunay.meta │ │ │ ├── LinkNature.cs │ │ │ ├── Slice.cs │ │ │ ├── Link.cs.meta │ │ │ ├── Slice.cs.meta │ │ │ ├── LayoutExt.cs.meta │ │ │ ├── LinkNature.cs.meta │ │ │ ├── MonotoneLayout.cs.meta │ │ │ ├── NavigatorExt.cs.meta │ │ │ ├── ShapeNavigator.cs.meta │ │ │ ├── TriangulationExt.cs.meta │ │ │ ├── Link.cs │ │ │ ├── MonotoneLayout.cs │ │ │ ├── ShapeNavigator.cs │ │ │ ├── TriangulationExt.cs │ │ │ └── NavigatorExt.cs │ │ ├── Extension │ │ │ ├── ConvexPolygonTriangulationExt.cs.meta │ │ │ └── ConvexPolygonTriangulationExt.cs │ │ ├── Shape.meta │ │ ├── Util.meta │ │ └── Util │ │ │ ├── IntTriangle.cs.meta │ │ │ └── IntTriangle.cs │ └── Triangulation.meta ├── iShape.meta ├── iShape.Triangulation.asmdef.meta └── iShape.Triangulation.asmdef ├── LICENSE.meta ├── CHANGELOG.md.meta ├── README.md.meta ├── package.json.meta ├── Readme ├── eagle_centroid.svg.meta ├── star_polygon.svg.meta ├── star_triangle.svg.meta ├── cheese_example_0.svg.meta ├── cheese_example_1.svg.meta ├── cheese_example_2.svg.meta ├── cheese_example_3.svg.meta ├── eagle_tessellation.svg.meta ├── eagle_triangles_extra_points.svg.meta ├── star_polygon.svg └── star_triangle.svg ├── Readme.meta ├── Runtime.meta ├── Tests.meta ├── Tests ├── Editor.meta └── Editor │ ├── Triangulation.meta │ ├── Triangulation │ ├── Data.meta │ ├── Util.meta │ ├── Util │ │ ├── Triangle.cs.meta │ │ ├── IntArrayExt.cs.meta │ │ ├── Triangle.cs │ │ └── IntArrayExt.cs │ ├── ComplexPlainTests.cs.meta │ ├── Data │ │ ├── ComplexTests.cs.meta │ │ ├── MonotoneTests.cs.meta │ │ └── MonotoneTests.cs │ ├── MonotonePlainTests.cs.meta │ ├── ComplexDelaunayTests.cs.meta │ ├── MonotoneDelaunayTests.cs.meta │ ├── MonotoneDelaunayTests.cs │ ├── MonotonePlainTests.cs │ └── ComplexDelaunayTests.cs │ ├── iShape.Triangulation.Editor.Tests.asmdef.meta │ └── iShape.Triangulation.Editor.Tests.asmdef ├── CHANGELOG.md ├── .gitignore ├── package.json ├── LICENSE └── README.md /Runtime/iShape/Triangulation/Extension.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19cb119c8bcb46d4acb5c360b44d761e 3 | timeCreated: 1608964616 -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/CentroidNet.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 09bbbec3178e4caeaec2c443df6092ff 3 | timeCreated: 1595523342 -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/IndexBuffer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f61dd810689484d9c485bb6b6183901 3 | timeCreated: 1609009188 -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Tessellation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 052a3f26df7b46ceb3e4481e0ada6300 3 | timeCreated: 1595519503 -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Extension/ConvexPolygonTriangulationExt.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34bd7c6244344c47a1eda14fe44991af 3 | timeCreated: 1608964642 -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f57ace8ab0124cf68f06bd4e4a33977 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c09f5fda5429c4250b2d2eb9611bc4be 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 499cee9e7a0bc4b34bf552c884b61c44 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3aea397d873d4152b741097b2380be2 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/eagle_centroid.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0aa94e1542b744f2686b016b9541c1fe 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/star_polygon.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba2b985eb55e34abba1a43e93755b0a1 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/star_triangle.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9f7066f1254ec47fa99c06558fa74bd6 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d337a014a101b478ea9dbb64f17099b8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Readme/cheese_example_0.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1fd1482e5c17849eea9279aac7a8566b 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/cheese_example_1.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b1c72c6e3d70d4635b66079d2d1b07fb 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/cheese_example_2.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7be4b815790814ca5bc0a94c281fd7f9 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/cheese_example_3.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e12ab8b49d8f846f0a704a098a5b13c8 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Readme/eagle_tessellation.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae74e06aaad204d2e935b195efde6877 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b461e94009e74849a48af546f88925e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 64ceb1b747c7e4ea8b37d4028d67b283 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/iShape.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 32a5b52252bfc4bc4820a6702c1ae24a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bbb7859e239394c2187ff4e0a93479f9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Readme/eagle_triangles_extra_points.svg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: db430bb6c03e54e0086c9daa4b0c40ef 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/iShape.Triangulation.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9267cde3c47644be2aa278c43d7895c3 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 612555ed3a4804a39bc21926b779a8f2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d9074840c9344a92b78642bcd2b0ac7 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 715a1252f942944a5ad65d4203519f04 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Util.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d199ce3a16da14250bf9c3594b093440 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Data.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 770555f36f14540fea6626fc655648e5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Util.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 49d35b4453a3c43e1aef77948f878b76 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7a25c8c7491fa48b891d7c44cdd2db24 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/iShape.Triangulation.Editor.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c65af2c98251b4ca790782041db7267d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/LinkNature.cs: -------------------------------------------------------------------------------- 1 | namespace iShape.Triangulation.Shape { 2 | 3 | internal enum LinkNature { 4 | end = 0, 5 | start = 1, 6 | split = 2, 7 | extra = 3, 8 | merge = 4, 9 | simple = 5 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Slice.cs: -------------------------------------------------------------------------------- 1 | namespace iShape.Triangulation.Shape { 2 | 3 | public readonly struct Slice { 4 | public readonly int a; 5 | public readonly int b; 6 | 7 | public Slice(int a, int b) { 8 | this.a = a; 9 | this.b = b; 10 | } 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Link.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7304c39b9e8b4494d8e78a9ab8717206 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Slice.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d88be09995ae457c8e5ad5101088141 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Util/Triangle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d5c73553a704401caf03b282cf2ccbf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/LayoutExt.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1692db967578848a78a9a44f1b310291 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/LinkNature.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ee4fb87dff53844028e2e9ce7df69eb1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Util/IntTriangle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa6634086df7e49618555d917d834b90 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/ComplexPlainTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a6b54215b9ef645aab5ba8f54c02d87a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Data/ComplexTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 269529d6350ee41ddaa764e5c4cdeece 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Data/MonotoneTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc23e12d71ead494294273441d3908d8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/MonotonePlainTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 63eb6efe885a74cd895731ccdead9e9a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Util/IntArrayExt.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cf7084246a35c4634aaaf126e9371de8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.0.8] - 2024-07-05 4 | 5 | ### Fixes 6 | 7 | - Test assembly 8 | 9 | ## [0.0.7] - 2024-03-07 10 | 11 | ### Fixes 12 | 13 | - Memory leak 14 | 15 | ## [0.0.2] - 2023-03-05 16 | 17 | ### Fixes 18 | 19 | - Fix dependencies 20 | 21 | ## [0.0.1] - 2020-09-15 22 | 23 | ### Added 24 | 25 | - First release -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Side.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cbde6754af07a4a0a9fba3c447ed1320 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/MonotoneLayout.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d051050221aa455db982a4c5bb85ab7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/NavigatorExt.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d21668a42eb5c400eac37bb43003662c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/ShapeNavigator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 74fb18fc43c8f429db0617cc717a02ec 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/TriangulationExt.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 993fa31efd59f4a749f8d628364a9b96 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/ComplexDelaunayTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fdeb7aa915ab4cfdbb518d30745f02a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/MonotoneDelaunayTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4af37712590e74d6c900fdbbb815c5f0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Delaunay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4f492862e32124869a56d29d908e1685 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/SliceBuffer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa9584033990c4c0cae97dbff81629cf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Triangle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2eab2015208e040d99bf0551640b5d65 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/ConvexPolygon.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a1664064a5bb345c9810e56c01794a38 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/TriangleStack.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c92cff999dd240c1bcdbdc0f866b101 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Triangulation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 529a109d3fa1a4d4aa0b1c2e37e364c5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Side.cs: -------------------------------------------------------------------------------- 1 | namespace iShape.Triangulation.Shape.Delaunay { 2 | 3 | internal struct Side { 4 | 5 | internal readonly int id; 6 | internal int edge; 7 | internal int triangle; 8 | 9 | internal bool IsEmpty => triangle == -1; 10 | 11 | internal Side(int id, int edge, int triangle) { 12 | this.id = id; 13 | this.edge = edge; 14 | this.triangle = triangle; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Link.cs: -------------------------------------------------------------------------------- 1 | using iShape.Geometry; 2 | 3 | namespace iShape.Triangulation.Shape { 4 | 5 | public struct Link { 6 | public static readonly Link empty = new Link(0, 0, 0, Vertex.empty); 7 | 8 | public int prev; 9 | public readonly int self; 10 | public int next; 11 | 12 | public Vertex vertex; 13 | 14 | public Link(int prev, int self, int next, Vertex vertex) { 15 | this.prev = prev; 16 | this.self = self; 17 | this.next = next; 18 | this.vertex = vertex; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | Assets/AssetStoreTools* 7 | 8 | # Visual Studio cache directory 9 | .vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | *.opendb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | *.unitypackage 37 | -------------------------------------------------------------------------------- /Tests/Editor/iShape.Triangulation.Editor.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iShape.Triangulation.Editor.Tests", 3 | "rootNamespace": "Tests.Triangulation", 4 | "references": [ 5 | "iShape.Geometry", 6 | "iShape.Triangulation" 7 | ], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": true, 13 | "overrideReferences": false, 14 | "precompiledReferences": [ 15 | "nunit.framework.dll" 16 | ], 17 | "autoReferenced": false, 18 | "defineConstraints": [ 19 | "UNITY_INCLUDE_TESTS" 20 | ], 21 | "versionDefines": [], 22 | "noEngineReferences": false 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.ishape.triangulation", 3 | "version": "0.0.8", 4 | "displayName": "iShape.Triangulation", 5 | "description": "Complex polygon triangulation. A fast O(n*log(n)) algorithm based on 'Triangulation of monotone polygons'. The result can be represented as a Delaunay triangulation.", 6 | "unity": "2022.2", 7 | "keywords": [ 8 | "iShape", 9 | "triangulation", 10 | "tesselation", 11 | "centroid" 12 | ], 13 | "license": "MIT", 14 | "author": { 15 | "name": "iShape", 16 | "email": "nailxsharipov@gmail.com" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/iShapeUnity/Triangulation.git" 21 | }, 22 | "dependencies": { 23 | "com.ishape.geometry": "0.0.5", 24 | "com.unity.mathematics": "1.3.1", 25 | "com.unity.collections": "2.2.0" 26 | } 27 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/MonotoneLayout.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | 3 | namespace iShape.Triangulation.Shape { 4 | 5 | public struct MonotoneLayout { 6 | 7 | public readonly int pathCount; 8 | public readonly int extraCount; 9 | public NativeArray links; 10 | public NativeArray slices; 11 | public NativeArray indices; 12 | 13 | public MonotoneLayout(int pathCount, int extraCount, NativeArray links, NativeArray slices, NativeArray indices) { 14 | this.pathCount = pathCount; 15 | this.extraCount = extraCount; 16 | this.links = links; 17 | this.slices = slices; 18 | this.indices = indices; 19 | } 20 | 21 | public void Dispose() { 22 | links.Dispose(); 23 | slices.Dispose(); 24 | indices.Dispose(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Runtime/iShape.Triangulation.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iShape.Triangulation", 3 | "rootNamespace": "", 4 | "references": [ 5 | "iShape.Geometry", 6 | "Unity.Collections", 7 | "Unity.Mathematics" 8 | ], 9 | "includePlatforms": [], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [ 17 | { 18 | "name": "com.ishape.geometry", 19 | "expression": "0.0.3" 20 | }, 21 | { 22 | "name": "com.unity.mathematics", 23 | "expression": "1.2.6" 24 | }, 25 | { 26 | "name": "com.unity.collections", 27 | "expression": "2.1.4" 28 | } 29 | ], 30 | "noEngineReferences": false 31 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/ShapeNavigator.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | 3 | namespace iShape.Triangulation.Shape { 4 | 5 | internal struct ShapeNavigator { 6 | 7 | internal readonly int pathCount; 8 | internal readonly int extraCount; 9 | internal NativeArray links; 10 | internal NativeArray natures; 11 | internal NativeArray indices; 12 | 13 | internal ShapeNavigator(int pathCount, int extraCount, NativeArray links, NativeArray natures, NativeArray indices) { 14 | this.pathCount = pathCount; 15 | this.extraCount = extraCount; 16 | this.links = links; 17 | this.natures = natures; 18 | this.indices = indices; 19 | } 20 | 21 | internal void Dispose() { 22 | this.links.Dispose(); 23 | this.natures.Dispose(); 24 | this.indices.Dispose(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Util/Triangle.cs: -------------------------------------------------------------------------------- 1 | using iShape.Geometry; 2 | using Unity.Collections; 3 | 4 | namespace Tests.Triangulation.Util { 5 | 6 | internal struct Triangle { 7 | 8 | internal static bool IsCCW(NativeArray points, NativeArray triangles) { 9 | int n = triangles.Length; 10 | 11 | var i = 0; 12 | while(i < n) { 13 | int ai = triangles[i]; 14 | int bi = triangles[i + 1]; 15 | int ci = triangles[i + 2]; 16 | 17 | 18 | var a = points[ai]; 19 | 20 | var b = points[bi]; 21 | 22 | var c = points[ci]; 23 | 24 | if(!IsCCW_or_Line(a, b, c)) { 25 | return false; 26 | 27 | } 28 | i += 3; 29 | } 30 | return true; 31 | } 32 | 33 | private static bool IsCCW_or_Line(IntVector a, IntVector b, IntVector c) { 34 | long m0 = (c.y - a.y) * (b.x - a.x); 35 | 36 | long m1 = (b.y - a.y) * (c.x - a.x); 37 | 38 | return m0 <= m1; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 iShape 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 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Extension/ConvexPolygonTriangulationExt.cs: -------------------------------------------------------------------------------- 1 | using iShape.Geometry.Polygon; 2 | using Unity.Collections; 3 | using UnityEngine; 4 | 5 | namespace iShape.Triangulation.Extension { 6 | 7 | public static class ConvexPolygonTriangulationExt { 8 | 9 | public static NativeArray GetTriangles(this Polygon self, Allocator allocator) { 10 | int n = self.points.Length; 11 | var count = 3 * (n - 2); 12 | var triangles = new NativeArray(count, allocator); 13 | for (int i = 2, j = 0; i < n; ++i, j += 3) { 14 | triangles[j] = 0; 15 | triangles[j + 1] = i - 1; 16 | triangles[j + 2] = i; 17 | } 18 | 19 | return triangles; 20 | } 21 | 22 | public static NativeArray GetVertices(this Polygon self, Allocator allocator) { 23 | int n = self.points.Length; 24 | var vertices = new NativeArray(n, allocator); 25 | for (int i = 0; i < n; ++i) { 26 | vertices[i] = self.points[i]; 27 | } 28 | return vertices; 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Util/IntTriangle.cs: -------------------------------------------------------------------------------- 1 | using iShape.Geometry; 2 | 3 | namespace iShape.Triangulation.Util { 4 | 5 | public struct IntTriangle { 6 | 7 | public enum Orientation { 8 | clockWise, counterClockWise, line 9 | } 10 | 11 | public static Orientation GetOrientation(IntVector a, IntVector b, IntVector c) { 12 | long m0 = (c.y - a.y) * (b.x - a.x); 13 | long m1 = (b.y - a.y) * (c.x - a.x); 14 | 15 | if(m0 < m1) { 16 | return Orientation.clockWise; 17 | } else if(m0 > m1) { 18 | return Orientation.counterClockWise; 19 | } else { 20 | return Orientation.line; 21 | } 22 | } 23 | 24 | public static bool IsNotLine(IntVector a, IntVector b, IntVector c) { 25 | long m0 = (c.y - a.y) * (b.x - a.x); 26 | long m1 = (b.y - a.y) * (c.x - a.x); 27 | 28 | return m0 != m1; 29 | } 30 | 31 | public static bool IsCCW_or_Line(IntVector a, IntVector b, IntVector c) { 32 | long m0 = (c.y - a.y) * (b.x - a.x); 33 | long m1 = (b.y - a.y) * (c.x - a.x); 34 | 35 | return m0 <= m1; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Util/IntArrayExt.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Tests.Triangulation.Util { 5 | 6 | public static class IntArrayExt { 7 | 8 | private struct Triangle { 9 | 10 | private readonly int a; 11 | private readonly int b; 12 | private readonly int c; 13 | 14 | internal Triangle(int a, int b, int c) { 15 | this.a = a; 16 | this.b = b; 17 | this.c = c; 18 | } 19 | 20 | public override bool Equals(object obj) { 21 | return (obj is Triangle triangle) && Equals(triangle); 22 | } 23 | 24 | private bool Equals(Triangle other) { 25 | return 26 | this.a == other.a && this.b == other.b && this.c == other.c || 27 | this.a == other.b && this.b == other.c && this.c == other.a || 28 | this.a == other.c && this.b == other.a && this.c == other.b; 29 | } 30 | 31 | public override int GetHashCode() { 32 | if(a > b && a > c) { 33 | return a; 34 | } 35 | 36 | if(b > a && b > c) { 37 | return b; 38 | } 39 | 40 | return c; 41 | } 42 | } 43 | 44 | public static bool CompareTriangles(this NativeArray self, NativeArray array) { 45 | int n = self.Length; 46 | if(n != array.Length || n % 3 != 0) { 47 | return false; 48 | } 49 | 50 | var set = new HashSet(); 51 | for(int i = 0; i < n; i += 3) { 52 | set.Add(new Triangle(self[i], self[i + 1], self[i + 2])); 53 | } 54 | 55 | for(int i = 0; i < n; i += 3) { 56 | var trinagle = new Triangle(array[i], array[i + 1], array[i + 2]); 57 | if (!set.Remove(trinagle)) { 58 | return false; 59 | } 60 | } 61 | 62 | return true; 63 | } 64 | 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/IndexBuffer.cs: -------------------------------------------------------------------------------- 1 | using iShape.Collections; 2 | using Unity.Collections; 3 | 4 | namespace iShape.Triangulation.Shape.Delaunay { 5 | internal struct IndexBuffer { 6 | 7 | private struct Link { 8 | internal static readonly Link empty = new Link(true, -1); 9 | internal readonly bool isFree; 10 | internal int next; 11 | 12 | internal Link(bool isFree, int next) { 13 | this.isFree = isFree; 14 | this.next = next; 15 | } 16 | } 17 | 18 | private DynamicArray array; 19 | private int first; 20 | internal IndexBuffer(int count, Allocator allocator) { 21 | this.array = new DynamicArray(count, count, allocator); 22 | if (count == 0) { 23 | this.first = -1; 24 | } 25 | this.first = 0; 26 | for (int i = 0; i < count - 1; ++i) { 27 | this.array[i] = new Link(false, i + 1); 28 | } 29 | 30 | this.array[count - 1] = new Link(false, -1); 31 | } 32 | 33 | internal bool hasNext => first >= 0; 34 | 35 | internal int Next() { 36 | int index = first; 37 | first = array[index].next; 38 | array[index] = Link.empty; 39 | return index; 40 | } 41 | 42 | internal void Add(int index) { 43 | var isOverflow = index >= array.Count; 44 | if (isOverflow || array[index].isFree) { 45 | if (isOverflow) { 46 | int count = index - array.Count + 1; 47 | array.Add(Link.empty, count); 48 | } 49 | 50 | array[index] = new Link(false, first); 51 | if (first >= 0) { 52 | var link = array[first]; 53 | array[first] = link; 54 | } 55 | 56 | first = index; 57 | } 58 | } 59 | 60 | internal void Dispose() { 61 | this.array.Dispose(); 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Triangulation [Port](https://github.com/iShape-Swift/iShapeTriangulation/) 2 | Complex polygon triangulation, tessellation and split into convex polygons. A fast O(n*log(n)) algorithm based on "Triangulation of monotone polygons". The result can be represented as a Delaunay triangulation. 3 | ## Delaunay triangulation 4 |

5 | 6 |

7 | 8 | ## Triangulation with extra points 9 |

10 | 11 |

12 | 13 | ## Tessellation 14 |

15 | 16 |

17 | 18 | ## Centroid net 19 |

20 | 21 |

22 | 23 | ## Features 24 | 25 | 💡 Fast O(n*log(n)) algorithm based on "Triangulation of monotone polygons" 26 | 27 | 💡 All code is written to suit "Data Oriented Design". No reference type like class, just structs. 28 | 29 | 💡 Supports polygons with holes 30 | 31 | 💡 Supports plain and Delaunay triangulation 32 | 33 | 💡 Supports tesselation 34 | 35 | 💡 Supports building centroid net 36 | 37 | 💡 Same points is not restricted 38 | 39 | 💡 Polygon must not have self intersections 40 | 41 | 💡 Use integer geometry for calculations 42 | 43 | 💡 More then 100 tests 44 | 45 | ## Installation 46 | 47 | To use iShape.Triangulation in your Unity project, follow these steps: 48 | 49 | - Open your Unity project. 50 | - In the top menu, select "Window" > "Package Manager". 51 | - Click on the "+" button in the top-left corner of the Package Manager window. 52 | - Select "Add package from git URL...". 53 | - Enter the following URL: https://github.com/iShapeUnity/Geometry.git 54 | - Click the "Add" button. 55 | - Wait for the package to be imported. 56 | - After repeat all for https://github.com/iShapeUnity/Triangulation.git 57 | 58 | ## Demo 59 | 60 | The [TriangulationDebug](https://github.com/iShapeUnity/TriangulationDebug) project is a samll demo project with some showcases. 61 | 62 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/TriangleStack.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using iShape.Geometry; 3 | using iShape.Collections; 4 | 5 | namespace iShape.Triangulation.Shape.Delaunay { 6 | 7 | internal struct TriangleStack { 8 | 9 | private readonly struct Edge { 10 | internal readonly int a; // vertex index 11 | internal readonly int b; // vertex index 12 | internal readonly int neighbor; // prev triangle index 13 | 14 | internal Edge(int a, int b, int neighbor) { 15 | this.a = a; 16 | this.b = b; 17 | this.neighbor = neighbor; 18 | } 19 | } 20 | 21 | private DynamicArray edges; 22 | private NativeArray triangles; 23 | private readonly Allocator allocator; 24 | private int counter; 25 | 26 | internal TriangleStack(int count, Allocator allocator) { 27 | this.counter = 0; 28 | this.allocator = allocator; 29 | this.edges = new DynamicArray(8, allocator); 30 | this.triangles = new NativeArray(count, allocator); 31 | } 32 | 33 | internal NativeArray Convert() { 34 | edges.Dispose(); 35 | if (this.counter == triangles.Length) { 36 | return triangles; 37 | } else { 38 | var newTriangles = new NativeArray(counter, allocator); 39 | newTriangles.Slice(0, counter).CopyFrom(triangles.Slice(0, counter)); 40 | triangles.Dispose(); 41 | return newTriangles; 42 | } 43 | } 44 | 45 | internal void Reset() { 46 | edges.RemoveAll(); 47 | } 48 | 49 | internal void Add(Vertex a, Vertex b, Vertex c) { 50 | if (a.index == b.index || a.index == c.index|| b.index == c.index) { 51 | // ignore triangle with tween vertices 52 | return; 53 | } 54 | 55 | var triangle = new Triangle(this.counter++, a, b, c); 56 | 57 | var ac = this.Pop(a.index, c.index); 58 | if (ac.a != -1) { 59 | var neighbor = triangles[ac.neighbor]; 60 | 61 | neighbor.nA = triangle.index; 62 | triangle.nB = neighbor.index; 63 | 64 | triangles[neighbor.index] = neighbor; 65 | } 66 | 67 | var ab = this.Pop(a.index, b.index); 68 | if(ab.a != -1) { 69 | var neighbor = triangles[ab.neighbor]; 70 | 71 | neighbor.nA = triangle.index; 72 | triangle.nC = neighbor.index; 73 | 74 | triangles[neighbor.index] = neighbor; 75 | } 76 | 77 | this.edges.Add(new Edge(b.index, c.index, triangle.index)); // bc is always slice 78 | 79 | triangles[triangle.index] = triangle; 80 | } 81 | 82 | private Edge Pop(int a, int b) { 83 | int last = edges.Count - 1; 84 | 85 | var i = 0; 86 | while (i <= last) { 87 | var e = edges[i]; 88 | if ((e.a == a || e.a == b) && (e.b == a || e.b == b)) { 89 | if (i != last) { 90 | edges[i] = edges[last]; 91 | } 92 | edges.RemoveLast(); 93 | 94 | return e; 95 | } 96 | ++i; 97 | } 98 | return new Edge(-1, -1, -1); 99 | } 100 | 101 | 102 | } 103 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Triangle.cs: -------------------------------------------------------------------------------- 1 | using iShape.Geometry; 2 | 3 | namespace iShape.Triangulation.Shape.Delaunay { 4 | 5 | public struct Triangle { 6 | 7 | internal readonly int index; 8 | 9 | // a(0), b(1), c(2) 10 | internal Vertex vA; 11 | internal Vertex vB; 12 | internal Vertex vC; 13 | 14 | // BC - a(0), AC - b(1), AB - c(2) 15 | internal int nA; 16 | internal int nB; 17 | internal int nC; 18 | 19 | internal Triangle(int index, Vertex a, Vertex b, Vertex c) { 20 | this.index = index; 21 | this.vA = a; 22 | this.vB = b; 23 | this.vC = c; 24 | 25 | this.nA = -1; 26 | this.nB = -1; 27 | this.nC = -1; 28 | } 29 | 30 | internal Triangle(int index, Vertex a, Vertex b, Vertex c, int nA, int nB, int nC) { 31 | this.index = index; 32 | this.vA = a; 33 | this.vB = b; 34 | this.vC = c; 35 | 36 | this.nA = nA; 37 | this.nB = nB; 38 | this.nC = nC; 39 | } 40 | 41 | internal Vertex Vertex(int i) { 42 | switch(i) { 43 | case 0: 44 | return vA; 45 | case 1: 46 | return vB; 47 | default: 48 | return vC; 49 | } 50 | } 51 | 52 | internal int FindIndex(int vertexIndex) { 53 | if(vA.index == vertexIndex) { 54 | return 0; 55 | } 56 | 57 | return vB.index == vertexIndex ? 1 : 2; 58 | } 59 | 60 | internal int Opposite(int neighbor) { 61 | if(nA == neighbor) { 62 | return 0; 63 | } 64 | 65 | return nB == neighbor ? 1 : 2; 66 | } 67 | 68 | internal Vertex OppositeVertex(int neighbor) { 69 | if(nA == neighbor) { 70 | return vA; 71 | } 72 | 73 | return nB == neighbor ? vB : vC; 74 | } 75 | 76 | internal int Neighbor(int i) { 77 | switch(i) { 78 | case 0: 79 | return nA; 80 | case 1: 81 | return nB; 82 | default: 83 | return nC; 84 | } 85 | } 86 | 87 | internal int FindNeighbor(int vertexIndex) { 88 | if(vA.index == vertexIndex) { 89 | return nA; 90 | } 91 | 92 | return vB.index == vertexIndex ? nB : nC; 93 | } 94 | 95 | internal void SetNeighbor(int i, int value) { 96 | switch(i) { 97 | case 0: 98 | nA = value; 99 | break; 100 | case 1: 101 | nB = value; 102 | break; 103 | default: 104 | nC = value; 105 | break; 106 | } 107 | } 108 | 109 | internal void SetVertex(int i, Vertex value) { 110 | switch(i) { 111 | case 0: 112 | vA = value; 113 | break; 114 | case 1: 115 | vB = value; 116 | break; 117 | default: 118 | vC = value; 119 | break; 120 | } 121 | } 122 | 123 | internal void Update(Vertex vertex) { 124 | if (vA.index != vertex.index) { 125 | vA = vertex; 126 | } else if (vB.index != vertex.index) { 127 | vB = vertex; 128 | } else if (vC.index != vertex.index) { 129 | vC = vertex; 130 | } 131 | } 132 | 133 | internal void UpdateOpposite(int oldNeighbor, int newNeighbor) { 134 | if(nA == oldNeighbor) { 135 | nA = newNeighbor; 136 | return; 137 | } 138 | 139 | if(nB == oldNeighbor) { 140 | nB = newNeighbor; 141 | return; 142 | } 143 | 144 | if(nC == oldNeighbor) { 145 | nC = newNeighbor; 146 | } 147 | } 148 | 149 | internal int AdjacentNeighbor(int vertex, int neighbor) { 150 | if (vA.index != vertex && nA != neighbor) { 151 | return nA; 152 | } 153 | if (vB.index != vertex && nB != neighbor) { 154 | return nB; 155 | } 156 | return nC; 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/ConvexPolygon.cs: -------------------------------------------------------------------------------- 1 | using iShape.Collections; 2 | using iShape.Geometry; 3 | using iShape.Geometry.Container; 4 | using iShape.Geometry.Polygon; 5 | using Unity.Collections; 6 | 7 | namespace iShape.Triangulation.Shape.Delaunay { 8 | 9 | internal struct ConvexPolygon { 10 | internal readonly struct Edge { 11 | internal readonly int triangleIndex; 12 | internal readonly int neighbor; 13 | internal readonly int a; 14 | internal readonly int b; 15 | 16 | internal Edge(int triangleIndex, int neighbor, int a, int b) { 17 | this.triangleIndex = triangleIndex; 18 | this.neighbor = neighbor; 19 | this.a = a; 20 | this.b = b; 21 | } 22 | } 23 | 24 | internal DynamicArray edges; 25 | private DynamicArray links; 26 | 27 | internal NativeArray Points(Allocator allocator) { 28 | int n = this.links.Count; 29 | var result = new NativeArray(n, allocator); 30 | for (int i = 0; i < n; ++i) { 31 | result[i] = this.links[i].vertex.point; 32 | } 33 | 34 | return result; 35 | } 36 | 37 | internal bool Add(Edge edge, Triangle triangle) { 38 | var v = triangle.OppositeVertex(edge.triangleIndex); 39 | 40 | // a0 -> a1 -> p 41 | var link_a1 = this.links[edge.a]; 42 | var va0 = this.links[link_a1.prev].vertex; 43 | var va1 = link_a1.vertex; 44 | 45 | var aa = va1.point - va0.point; 46 | var ap = v.point - va1.point; 47 | 48 | var apa = aa.CrossProduct(ap); 49 | if (apa > 0) { 50 | return false; 51 | } 52 | 53 | // b0 <- b1 <- p 54 | 55 | var link_b1 = this.links[edge.b]; 56 | var vb0 = this.links[link_b1.next].vertex; 57 | var vb1 = link_b1.vertex; 58 | 59 | var bb = vb0.point - vb1.point; 60 | var bp = vb1.point - v.point; 61 | 62 | var bpb = bp.CrossProduct(bb); 63 | if (bpb > 0) { 64 | return false; 65 | } 66 | 67 | var linkIndex = this.links.Count; 68 | var link_p = new Link(link_a1.self, linkIndex, link_b1.self, v); 69 | 70 | link_a1.next = linkIndex; 71 | link_b1.prev = linkIndex; 72 | 73 | this.links.Add(link_p); 74 | this.links[link_a1.self] = link_a1; 75 | this.links[link_b1.self] = link_b1; 76 | 77 | var n0 = triangle.FindNeighbor(vb1.index); 78 | if (n0 >= 0) { 79 | var edge0 = new Edge(triangle.index, n0, edge.a, linkIndex); 80 | this.edges.Add(edge0); 81 | } 82 | 83 | var n1 = triangle.FindNeighbor(va1.index); 84 | if (n1 >= 0) { 85 | var edge1 = new Edge(triangle.index, n1, linkIndex, edge.b); 86 | this.edges.Add(edge1); 87 | } 88 | 89 | return true; 90 | } 91 | 92 | internal ConvexPolygon(Triangle triangle) { 93 | const int capacity = 16; 94 | 95 | this.links = new DynamicArray(capacity, Allocator.Temp); 96 | this.links.Add(new Link(2, 0, 1, triangle.Vertex(0))); 97 | this.links.Add(new Link(0, 1, 2, triangle.Vertex(1))); 98 | this.links.Add(new Link(1, 2, 0, triangle.Vertex(2))); 99 | 100 | this.edges = new DynamicArray(capacity, Allocator.Temp); 101 | 102 | var ab = triangle.Neighbor(2); 103 | if (ab >= 0) { 104 | this.edges.Add(new Edge(triangle.index, ab, 0, 1)); 105 | } 106 | 107 | var bc = triangle.Neighbor(0); 108 | if (bc >= 0) { 109 | this.edges.Add(new Edge(triangle.index, bc, 1, 2)); 110 | } 111 | 112 | var ca = triangle.Neighbor(1); 113 | if (ca >= 0) { 114 | this.edges.Add(new Edge(triangle.index, ca, 2, 0)); 115 | } 116 | } 117 | 118 | internal void Dispose() { 119 | this.edges.Dispose(); 120 | this.links.Dispose(); 121 | } 122 | } 123 | 124 | public static class Polygons { 125 | 126 | public static List ConvexPolygons(this PlainShape self, Allocator allocator, IntGeom intGeom) { 127 | var delaunay = self.Delaunay(Allocator.Temp); 128 | var list = delaunay.ConvexPolygons(intGeom, allocator); 129 | delaunay.Dispose(); 130 | return list; 131 | } 132 | 133 | public static List ConvexPolygons(this Delaunay self, IntGeom intGeom, Allocator allocator) { 134 | int n = self.triangles.Count; 135 | var dynamicList = new DynamicList(self.points.Count, n >> 1, allocator); 136 | var visited = new NativeArray(n, Allocator.Temp); 137 | 138 | for (int i = 0; i < n; ++i) { 139 | if (visited[i]) { 140 | continue; 141 | } 142 | 143 | var first = self.triangles[i]; 144 | visited[i] = true; 145 | var convexPolygon = new ConvexPolygon(first); 146 | 147 | while (convexPolygon.edges.Count > 0) { 148 | var edge = convexPolygon.edges.Last(); 149 | convexPolygon.edges.RemoveLast(); 150 | if (visited[edge.neighbor]) { 151 | continue; 152 | } 153 | 154 | var next = self.triangles[edge.neighbor]; 155 | if (convexPolygon.Add(edge, next)) { 156 | visited[edge.neighbor] = true; 157 | } 158 | } 159 | 160 | var iPoints = convexPolygon.Points(Allocator.Temp); 161 | var points = intGeom.Float(iPoints, Allocator.Temp); 162 | var polygon = new Polygon(points, allocator); 163 | dynamicList.Add(polygon); 164 | 165 | iPoints.Dispose(); 166 | points.Dispose(); 167 | convexPolygon.Dispose(); 168 | } 169 | 170 | visited.Dispose(); 171 | 172 | return dynamicList.Convert(); 173 | } 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/Data/MonotoneTests.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Tests.Triangulation.Data { 4 | 5 | public struct MonotoneTests { 6 | 7 | public static readonly Vector2[][] data = { 8 | // test 0 9 | new Vector2[] { 10 | new Vector2(-15, -15), 11 | new Vector2(-15, 15), 12 | new Vector2(15, 15), 13 | new Vector2(15, -15) 14 | }, 15 | // test 1 16 | new Vector2[] { 17 | new Vector2(-15, 10), 18 | new Vector2(5, 10), 19 | new Vector2(15, -10), 20 | new Vector2(-5, -10) 21 | }, 22 | // test 2 23 | new Vector2[] { 24 | new Vector2(-15, -15), 25 | new Vector2(-25, 0), 26 | new Vector2(-15, 15), 27 | new Vector2(15, 15), 28 | new Vector2(15, -15) 29 | }, 30 | // test 3 31 | new Vector2[] { 32 | new Vector2(-5, -15), 33 | new Vector2(-10, 0), 34 | new Vector2( 0, 15), 35 | new Vector2(10, 5), 36 | new Vector2( 5, -10) 37 | }, 38 | // test 4 39 | new Vector2[] { 40 | new Vector2(0, -5), 41 | new Vector2(0, 0), 42 | new Vector2(10, -10), 43 | new Vector2(-10, -10) 44 | }, 45 | // test 5 46 | new Vector2[] { 47 | new Vector2(-15, -15), 48 | new Vector2(-15, 0), 49 | new Vector2(0, 0), 50 | new Vector2(0, 15), 51 | new Vector2(15, -15) 52 | }, 53 | // test 6 54 | new Vector2[] { 55 | new Vector2(-15, -15), 56 | new Vector2(-15, 0), 57 | new Vector2(-1, 20), 58 | new Vector2(0, 5), 59 | new Vector2(15, -15) 60 | }, 61 | // test 7 62 | new Vector2[] { 63 | new Vector2(-10, 10), 64 | new Vector2(-5, 5), 65 | new Vector2(10, 20), 66 | new Vector2(20, 20), 67 | new Vector2(25, 20), 68 | new Vector2(25, -5), 69 | new Vector2(10, -5), 70 | new Vector2(10, -10), 71 | new Vector2(-10, -10) 72 | }, 73 | // test 8 74 | new Vector2[] { 75 | new Vector2(-10, 10), 76 | new Vector2(-5, 15), 77 | new Vector2(10, 20), 78 | new Vector2(20, 20), 79 | new Vector2(25, 20), 80 | new Vector2(25, -5), 81 | new Vector2(10, -5), 82 | new Vector2(10, -10), 83 | new Vector2(-10, -10) 84 | }, 85 | // test 9 86 | new Vector2[] { 87 | new Vector2(-10, 10), 88 | new Vector2( -5, 5), 89 | new Vector2( 10, 20), 90 | new Vector2( 15, 10), 91 | new Vector2( 25, 20), 92 | new Vector2( 25, 0), 93 | new Vector2(10, 0), 94 | new Vector2(10, -10), 95 | new Vector2(-10, -10) 96 | }, 97 | // test 10 98 | new Vector2[] { 99 | new Vector2(-10, 10), 100 | new Vector2( -5, -5), 101 | new Vector2( 10, 20), 102 | new Vector2( 15, 10), 103 | new Vector2( 25, 20), 104 | new Vector2(25, 0), 105 | new Vector2(10, 0), 106 | new Vector2(10, -10), 107 | new Vector2(-10, -10) 108 | }, 109 | // test 11 110 | new Vector2[] { 111 | new Vector2(-10, 10), 112 | new Vector2( 10, 10), 113 | new Vector2( 10, 20), 114 | new Vector2( 15, 10), 115 | new Vector2( 25, 20), 116 | new Vector2(25, 0), 117 | new Vector2(10, 0), 118 | new Vector2(10, -10), 119 | new Vector2(-10, -10) 120 | }, 121 | // test 12 122 | new Vector2[] { 123 | new Vector2(-35, 5), 124 | new Vector2(-20, 10), 125 | new Vector2(-18, 20), 126 | new Vector2(0, 20), 127 | new Vector2(5, 10), 128 | new Vector2(15, 5), 129 | new Vector2(20, 10), 130 | new Vector2(35, 0), 131 | new Vector2(25, -10), 132 | new Vector2(10, -4), 133 | new Vector2(-5, -15), 134 | new Vector2(-5, -20), 135 | new Vector2(-15, -25), 136 | new Vector2(-20, -10), 137 | new Vector2(-30, -5) 138 | }, 139 | // test 13 140 | new Vector2[] { 141 | new Vector2(-35, 5), 142 | new Vector2(-20, 10), 143 | new Vector2(-10, 20), 144 | new Vector2(0, 20), 145 | new Vector2(5, 10), 146 | new Vector2(15, 5), 147 | new Vector2(20, 10), 148 | new Vector2(35, 0), 149 | new Vector2(25, -10), 150 | new Vector2(10, -4), 151 | new Vector2(-5, -15), 152 | new Vector2(-5, -20), 153 | new Vector2(-15, -25), 154 | new Vector2(-20, -10), 155 | new Vector2(-30, -5) 156 | }, 157 | // test 14 158 | new Vector2[] { 159 | new Vector2(-10, -10), 160 | new Vector2(-10, -5), 161 | new Vector2(-10, 0), 162 | new Vector2(-10, 5), 163 | new Vector2(-10, 10), 164 | new Vector2(10, 10), 165 | new Vector2(10, 5), 166 | new Vector2(10, 0), 167 | new Vector2(10, -5), 168 | new Vector2(10, -10) 169 | }, 170 | // test 15 171 | new Vector2[] { 172 | new Vector2(-20, 0), 173 | new Vector2(-15, 15), 174 | new Vector2(-10, 20), 175 | new Vector2( -5, 15), 176 | new Vector2( 0, 20), 177 | new Vector2( 5, 15), 178 | new Vector2( 10, 20), 179 | new Vector2( 15, 15), 180 | new Vector2( 25, 0), 181 | new Vector2( 20, -15), 182 | new Vector2( 15, -20), 183 | new Vector2( 10, -15), 184 | new Vector2( 5, -20), 185 | new Vector2( 0, -15), 186 | new Vector2( -5, -20), 187 | new Vector2(-10, -15) 188 | }, 189 | // test 16 190 | new Vector2[] { 191 | new Vector2(-20, 5), 192 | new Vector2(-10, 10), 193 | new Vector2( -5, 20), 194 | new Vector2( 0, 25), 195 | new Vector2( 5, 15), 196 | new Vector2( 10, 0), 197 | new Vector2( 15, 5), 198 | new Vector2( 20, -5), 199 | new Vector2( 15, -15), 200 | new Vector2( 5, -25), 201 | new Vector2( 0, -15), 202 | new Vector2(-10, -10), 203 | new Vector2(-15, -5), 204 | }, 205 | // test 17 206 | new Vector2[] { 207 | new Vector2(-35, 5), 208 | new Vector2(-13.5f, 8), 209 | new Vector2(-9.5f, 20), 210 | new Vector2(3, 20), 211 | new Vector2(8.5f, 11), 212 | new Vector2( 15, 5), 213 | new Vector2( 32, 14.5f), 214 | new Vector2( 35, 0), 215 | new Vector2( 25, -10), 216 | new Vector2( 0, 1.5f), 217 | new Vector2(-0.5f, -12.5f), 218 | new Vector2( -5, -20), 219 | new Vector2(-7.5f, 2.5f), 220 | new Vector2(-31, -4) 221 | }, 222 | // test 18 223 | new Vector2[] { 224 | new Vector2(-10, 5), 225 | new Vector2( -5, 5), 226 | new Vector2( 0, 0), 227 | new Vector2( 5, 5), 228 | new Vector2( 10, 5), 229 | new Vector2( 10, -5), 230 | new Vector2( 5, -5), 231 | new Vector2( 0, 0), 232 | new Vector2( -5, -5), 233 | new Vector2(-10, -5) 234 | } 235 | }; 236 | } 237 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/SliceBuffer.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | 3 | namespace iShape.Triangulation.Shape.Delaunay { 4 | 5 | internal struct SliceBuffer { 6 | 7 | private readonly int vertexCount; 8 | private NativeArray sides; 9 | private NativeArray vertexMark; 10 | 11 | internal SliceBuffer(int vertexCount, NativeArray slices, Allocator allocator) { 12 | this.vertexCount = vertexCount; 13 | this.vertexMark = new NativeArray(vertexCount, allocator); 14 | int n = slices.Length; 15 | this.sides = new NativeArray(n, allocator); 16 | 17 | for(int i = 0; i < n; ++i) { 18 | var slice = slices[i]; 19 | 20 | vertexMark[slice.a] = true; 21 | vertexMark[slice.b] = true; 22 | 23 | int id; 24 | 25 | if(slice.a < slice.b) { 26 | id = slice.a * vertexCount + slice.b; 27 | } else { 28 | id = slice.b * vertexCount + slice.a; 29 | } 30 | sides[i] = new Side(id, -1, -1); 31 | } 32 | 33 | Sort(sides); 34 | } 35 | 36 | public void Dispose() { 37 | this.sides.Dispose(); 38 | this.vertexMark.Dispose(); 39 | } 40 | 41 | public void AddConnections(NativeArray triangles) { 42 | int n = triangles.Length; 43 | 44 | for(int i = 0; i < n; ++i) { 45 | var triangle = triangles[i]; 46 | int a = triangle.vA.index; 47 | int b = triangle.vB.index; 48 | int c = triangle.vC.index; 49 | 50 | int sideIndex = this.Find(a, b); 51 | if(sideIndex >= 0) { 52 | var side = this.sides[sideIndex]; 53 | if(side.IsEmpty) { 54 | side.triangle = i; 55 | side.edge = 2; 56 | this.sides[sideIndex] = side; 57 | } else { 58 | triangle.SetNeighbor(2, side.triangle); 59 | var neighbor = triangles[side.triangle]; 60 | neighbor.SetNeighbor(side.edge, i); 61 | triangles[side.triangle] = neighbor; 62 | triangles[i] = triangle; 63 | } 64 | } 65 | 66 | sideIndex = this.Find(a, c); 67 | if(sideIndex >= 0) { 68 | var side = this.sides[sideIndex]; 69 | if(side.IsEmpty) { 70 | side.triangle = i; 71 | side.edge = 1; 72 | this.sides[sideIndex] = side; 73 | } else { 74 | triangle.SetNeighbor(1, side.triangle); 75 | var neighbor = triangles[side.triangle]; 76 | neighbor.SetNeighbor(side.edge, i); 77 | triangles[side.triangle] = neighbor; 78 | triangles[i] = triangle; 79 | } 80 | } 81 | 82 | sideIndex = this.Find(b, c); 83 | if(sideIndex >= 0) { 84 | var side = this.sides[sideIndex]; 85 | if(side.IsEmpty) { 86 | side.triangle = i; 87 | side.edge = 0; 88 | this.sides[sideIndex] = side; 89 | } else { 90 | triangle.SetNeighbor(0, side.triangle); 91 | var neighbor = triangles[side.triangle]; 92 | neighbor.SetNeighbor(side.edge, i); 93 | triangles[side.triangle] = neighbor; 94 | triangles[i] = triangle; 95 | } 96 | } 97 | } 98 | } 99 | 100 | private int Find(int a, int b) { 101 | if(!vertexMark[a] || !vertexMark[b]) { 102 | return -1; 103 | } 104 | int id; 105 | if(a < b) { 106 | id = a * vertexCount + b; 107 | } else { 108 | id = b * vertexCount + a; 109 | } 110 | 111 | var left = 0; 112 | var right = sides.Length - 1; 113 | 114 | do { 115 | int k; 116 | if(left + 1 < right) { 117 | k = (left + right) >> 1; 118 | } else { 119 | do { 120 | if(sides[left].id == id) { 121 | return left; 122 | } 123 | ++left; 124 | } while(left <= right); 125 | return -1; 126 | } 127 | 128 | int e = sides[k].id; 129 | if(e > id) { 130 | right = k; 131 | } else if(e < id) { 132 | left = k; 133 | } else { 134 | return k; 135 | } 136 | } while(true); 137 | } 138 | 139 | private static void Sort(NativeArray array) { 140 | int n = array.Length; 141 | int r = 2; 142 | int rank = 1; 143 | 144 | while(r <= n) { 145 | rank = r; 146 | r <<= 1; 147 | } 148 | rank -= 1; 149 | 150 | int jEnd = rank; 151 | 152 | int jStart = ((jEnd + 1) >> 1) - 1; 153 | 154 | 155 | while(jStart >= 0) { 156 | int k = jStart; 157 | while(k < jEnd) { 158 | int j = k; 159 | 160 | var a = array[j]; 161 | bool fallDown; 162 | do { 163 | fallDown = false; 164 | 165 | int j0 = (j << 1) + 1; 166 | int j1 = j0 + 1; 167 | 168 | if(j1 < n) { 169 | var a0 = array[j0]; 170 | var a1 = array[j1]; 171 | 172 | if(a.id < a0.id || a.id < a1.id) { 173 | if(a0.id > a1.id) { 174 | array[j0] = a; 175 | array[j] = a0; 176 | j = j0; 177 | } else { 178 | array[j1] = a; 179 | array[j] = a1; 180 | j = j1; 181 | } 182 | fallDown = j < rank; 183 | } 184 | } else if(j0 < n) { 185 | var ax = array[j]; 186 | var a0 = array[j0]; 187 | if(ax.id < a0.id) { 188 | array[j0] = ax; 189 | array[j] = a0; 190 | } 191 | } 192 | 193 | } while(fallDown); 194 | ++k; 195 | } 196 | 197 | jEnd = jStart; 198 | jStart = ((jEnd + 1) >> 1) - 1; 199 | } 200 | 201 | while(n > 0) { 202 | int m = n - 1; 203 | 204 | var a = array[m]; 205 | array[m] = array[0]; 206 | array[0] = a; 207 | 208 | int j = 0; 209 | bool fallDown; 210 | do { 211 | fallDown = false; 212 | 213 | int j0 = (j << 1) + 1; 214 | int j1 = j0 + 1; 215 | 216 | if(j1 < m) { 217 | var a0 = array[j0]; 218 | var a1 = array[j1]; 219 | fallDown = a.id < a0.id || a.id < a1.id; 220 | 221 | if(fallDown) { 222 | if(a0.id > a1.id) { 223 | array[j0] = a; 224 | array[j] = a0; 225 | j = j0; 226 | } else { 227 | array[j1] = a; 228 | array[j] = a1; 229 | j = j1; 230 | } 231 | } 232 | } else if(j0 < m) { 233 | var ax = array[j]; 234 | var a0 = array[j0]; 235 | if(ax.id < a0.id) { 236 | array[j0] = ax; 237 | array[j] = a0; 238 | } 239 | } 240 | 241 | } while(fallDown); 242 | 243 | n = m; 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/TriangulationExt.cs: -------------------------------------------------------------------------------- 1 | using iShape.Triangulation.Util; 2 | using iShape.Geometry; 3 | using iShape.Geometry.Container; 4 | using Unity.Collections; 5 | using UnityEngine; 6 | 7 | namespace iShape.Triangulation.Shape { 8 | 9 | public static class TriangulationExt { 10 | 11 | public static Mesh Triangulate(this PlainShape shape, IntGeom iGeom) { 12 | int n = shape.points.Length; 13 | var vertices = new Vector3[n]; 14 | for (int i = 0; i < n; ++i) { 15 | var v = iGeom.Float(shape.points[i]); 16 | vertices[i] = new Vector3(v.x, v.y, 0); 17 | } 18 | var extraPoints = new NativeArray(0, Allocator.Temp); 19 | var nTriangles = shape.Triangulate(extraPoints, Allocator.Temp); 20 | extraPoints.Dispose(); 21 | 22 | var mesh = new Mesh { 23 | vertices = vertices, 24 | triangles = nTriangles.ToArray() 25 | }; 26 | 27 | nTriangles.Dispose(); 28 | 29 | return mesh; 30 | } 31 | 32 | public static NativeArray Triangulate(this PlainShape shape, Allocator allocator) { 33 | var extraPoints = new NativeArray(0, Allocator.Temp); 34 | var triangles = Triangulate(shape, extraPoints, allocator); 35 | extraPoints.Dispose(); 36 | return triangles; 37 | } 38 | 39 | public static NativeArray Triangulate(this PlainShape shape, NativeArray extraPoints, Allocator allocator) { 40 | var layout = shape.Split(0, extraPoints, Allocator.Temp); 41 | int totalCount = shape.points.Length + ((shape.layouts.Length - 2) << 1); 42 | 43 | int trianglesCount = 3 * totalCount; 44 | 45 | var triangles = new NativeArray(trianglesCount, allocator); 46 | int counter = 0; 47 | for(int i = 0; i < layout.indices.Length; ++i) { 48 | int index = layout.indices[i]; 49 | Triangulate(index, ref counter, layout.links, triangles); 50 | } 51 | 52 | layout.Dispose(); 53 | 54 | if(counter == trianglesCount) { 55 | return triangles; 56 | } else { 57 | var newTriangles = new NativeArray(counter, allocator); 58 | newTriangles.Slice(0, counter).CopyFrom(triangles.Slice(0, counter)); 59 | triangles.Dispose(); 60 | return newTriangles; 61 | } 62 | } 63 | 64 | public static void Triangulate(int index, ref int counter, NativeArray links, NativeArray triangles) { 65 | var c = links[index]; 66 | 67 | var a0 = links[c.next]; 68 | var b0 = links[c.prev]; 69 | 70 | while(a0.self != b0.self) { 71 | var a1 = links[a0.next]; 72 | var b1 = links[b0.prev]; 73 | 74 | 75 | var aBit0 = a0.vertex.point.BitPack; 76 | var aBit1 = a1.vertex.point.BitPack; 77 | if(aBit1 < aBit0) { 78 | aBit1 = aBit0; 79 | } 80 | 81 | var bBit0 = b0.vertex.point.BitPack; 82 | var bBit1 = b1.vertex.point.BitPack; 83 | if(bBit1 < bBit0) { 84 | bBit1 = bBit0; 85 | } 86 | 87 | if(aBit0 <= bBit1 && bBit0 <= aBit1) { 88 | if(IntTriangle.IsNotLine(c.vertex.point, a0.vertex.point, b0.vertex.point)) { 89 | triangles[counter++] = c.vertex.index; 90 | triangles[counter++] = a0.vertex.index; 91 | triangles[counter++] = b0.vertex.index; 92 | } 93 | 94 | a0.prev = b0.self; 95 | b0.next = a0.self; 96 | links[a0.self] = a0; 97 | links[b0.self] = b0; 98 | 99 | 100 | if(bBit0 < aBit0) { 101 | c = b0; 102 | b0 = b1; 103 | } else { 104 | c = a0; 105 | a0 = a1; 106 | } 107 | } else { 108 | if(aBit1 < bBit1) { 109 | var cx = c; 110 | var ax0 = a0; 111 | var ax1 = a1; 112 | long ax1Bit = long.MinValue; 113 | do { 114 | var orientation = IntTriangle.GetOrientation(cx.vertex.point, ax0.vertex.point, ax1.vertex.point); 115 | switch(orientation) { 116 | case IntTriangle.Orientation.clockWise: 117 | triangles[counter++] = cx.vertex.index; 118 | triangles[counter++] = ax0.vertex.index; 119 | triangles[counter++] = ax1.vertex.index; 120 | goto case IntTriangle.Orientation.line; 121 | case IntTriangle.Orientation.line: 122 | ax1.prev = cx.self; 123 | cx.next = ax1.self; 124 | links[cx.self] = cx; 125 | links[ax1.self] = ax1; 126 | 127 | if(cx.self != c.self) { 128 | // move back 129 | ax0 = cx; 130 | cx = links[cx.prev]; 131 | continue; 132 | } else { 133 | // move forward 134 | ax0 = ax1; 135 | ax1 = links[ax1.next]; 136 | break; 137 | } 138 | case IntTriangle.Orientation.counterClockWise: 139 | cx = ax0; 140 | ax0 = ax1; 141 | ax1 = links[ax1.next]; 142 | break; 143 | } 144 | ax1Bit = ax1.vertex.point.BitPack; 145 | } while(ax1Bit < bBit0); 146 | } else { 147 | var cx = c; 148 | var bx0 = b0; 149 | var bx1 = b1; 150 | long bx1Bit = long.MinValue; 151 | do { 152 | var orientation = IntTriangle.GetOrientation(cx.vertex.point, bx1.vertex.point, bx0.vertex.point); 153 | switch(orientation) { 154 | case IntTriangle.Orientation.clockWise: 155 | triangles[counter++] = cx.vertex.index; 156 | triangles[counter++] = bx1.vertex.index; 157 | triangles[counter++] = bx0.vertex.index; 158 | goto case IntTriangle.Orientation.line; 159 | case IntTriangle.Orientation.line: 160 | bx1.next = cx.self; 161 | cx.prev = bx1.self; 162 | links[cx.self] = cx; 163 | links[bx1.self] = bx1; 164 | 165 | if(cx.self != c.self) { 166 | // move back 167 | bx0 = cx; 168 | cx = links[cx.next]; 169 | continue; 170 | } else { 171 | // move forward 172 | bx0 = bx1; 173 | bx1 = links[bx0.prev]; 174 | break; 175 | } 176 | case IntTriangle.Orientation.counterClockWise: 177 | cx = bx0; 178 | bx0 = bx1; 179 | bx1 = links[bx1.prev]; 180 | break; 181 | } 182 | bx1Bit = bx1.vertex.point.BitPack; 183 | } while(bx1Bit < aBit0); 184 | } 185 | 186 | c = links[c.self]; 187 | a0 = links[c.next]; 188 | b0 = links[c.prev]; 189 | 190 | 191 | aBit0 = a0.vertex.point.BitPack; 192 | bBit0 = b0.vertex.point.BitPack; 193 | 194 | if(IntTriangle.IsNotLine(c.vertex.point, a0.vertex.point, b0.vertex.point)) { 195 | triangles[counter++] = c.vertex.index; 196 | triangles[counter++] = a0.vertex.index; 197 | triangles[counter++] = b0.vertex.index; 198 | } 199 | a0.prev = b0.self; 200 | b0.next = a0.self; 201 | links[a0.self] = a0; 202 | links[b0.self] = b0; 203 | 204 | if(bBit0 < aBit0) { 205 | c = b0; 206 | b0 = links[b0.prev]; 207 | } else { 208 | c = a0; 209 | a0 = links[a0.next]; 210 | } 211 | 212 | } //while 213 | } 214 | } 215 | } 216 | 217 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Triangulation.cs: -------------------------------------------------------------------------------- 1 | using iShape.Triangulation.Util; 2 | using iShape.Geometry; 3 | using iShape.Geometry.Container; 4 | using Unity.Collections; 5 | using UnityEngine; 6 | 7 | namespace iShape.Triangulation.Shape.Delaunay { 8 | 9 | public static class Triangulation { 10 | 11 | public static Mesh DelaunayTriangulate(this PlainShape shape, IntGeom iGeom) { 12 | int n = shape.points.Length; 13 | var vertices = new Vector3[n]; 14 | for (int i = 0; i < n; ++i) { 15 | var v = iGeom.Float(shape.points[i]); 16 | vertices[i] = new Vector3(v.x, v.y, 0); 17 | } 18 | var extraPoints = new NativeArray(0, Allocator.Temp); 19 | var delaunay = shape.Delaunay(0, extraPoints, Allocator.Temp); 20 | extraPoints.Dispose(); 21 | 22 | var nTriangles = delaunay.Indices(Allocator.Temp); 23 | delaunay.Dispose(); 24 | 25 | var mesh = new Mesh { 26 | vertices = vertices, 27 | triangles = nTriangles.ToArray() 28 | }; 29 | 30 | nTriangles.Dispose(); 31 | 32 | return mesh; 33 | } 34 | 35 | public static NativeArray DelaunayTriangulate(this PlainShape shape, Allocator allocator) { 36 | var extraPoints = new NativeArray(0, Allocator.Temp); 37 | var delaunay = shape.Delaunay(0, extraPoints, allocator); 38 | extraPoints.Dispose(); 39 | 40 | var triangles = delaunay.Indices(allocator); 41 | delaunay.Dispose(); 42 | 43 | return triangles; 44 | } 45 | 46 | public static NativeArray DelaunayTriangulate(this PlainShape shape, Allocator allocator, NativeArray extraPoints) { 47 | var delaunay = shape.Delaunay(0, extraPoints, allocator); 48 | var triangles = delaunay.Indices(allocator); 49 | delaunay.Dispose(); 50 | 51 | return triangles; 52 | } 53 | 54 | public static Delaunay Delaunay(this PlainShape shape, long maxEdge, NativeArray extraPoints, Allocator allocator) { 55 | var layout = shape.Split(maxEdge, extraPoints, Allocator.Temp); 56 | 57 | int holesCount = shape.layouts.Length; 58 | int totalCount = layout.pathCount + 2 * layout.extraCount + holesCount * 2 - 2; 59 | 60 | var triangleStack = new TriangleStack(totalCount, allocator); 61 | 62 | for(int i = 0; i < layout.indices.Length; ++i) { 63 | int index = layout.indices[i]; 64 | Triangulate(index, layout.links, ref triangleStack); 65 | triangleStack.Reset(); 66 | } 67 | 68 | var triangles = triangleStack.Convert(); 69 | 70 | var sliceBuffer = new SliceBuffer(layout.links.Length, layout.slices, Allocator.Temp); 71 | sliceBuffer.AddConnections(triangles); 72 | 73 | sliceBuffer.Dispose(); 74 | 75 | Delaunay delaunay; 76 | if (extraPoints.Length == 0 && maxEdge == 0) { 77 | delaunay = new Delaunay(shape.points, triangles, allocator); 78 | } else { 79 | var points = new NativeArray(layout.links.Length, Allocator.Temp); 80 | for(int i = 0; i < layout.links.Length; ++i) { 81 | var link = layout.links[i]; 82 | points[link.vertex.index] = link.vertex.point; 83 | } 84 | delaunay = new Delaunay(points, triangles, allocator); 85 | points.Dispose(); 86 | } 87 | 88 | triangles.Dispose(); 89 | 90 | layout.Dispose(); 91 | 92 | delaunay.Build(); 93 | 94 | return delaunay; 95 | } 96 | 97 | public static Delaunay Delaunay(this PlainShape shape, long maxEdge, Allocator allocator) { 98 | var extraPoints = new NativeArray(0, Allocator.Temp); 99 | var delaunay = shape.Delaunay(maxEdge, extraPoints, allocator); 100 | extraPoints.Dispose(); 101 | return delaunay; 102 | } 103 | 104 | public static Delaunay Delaunay(this PlainShape shape, Allocator allocator) { 105 | var extraPoints = new NativeArray(0, Allocator.Temp); 106 | var delaunay = shape.Delaunay(0, extraPoints, allocator); 107 | extraPoints.Dispose(); 108 | return delaunay; 109 | } 110 | 111 | private static void Triangulate(int index, NativeArray links, ref TriangleStack triangleStack) { 112 | 113 | var c = links[index]; 114 | 115 | var a0 = links[c.next]; 116 | var b0 = links[c.prev]; 117 | 118 | while(a0.self != b0.self) { 119 | var a1 = links[a0.next]; 120 | var b1 = links[b0.prev]; 121 | 122 | 123 | var aBit0 = a0.vertex.point.BitPack; 124 | var aBit1 = a1.vertex.point.BitPack; 125 | if(aBit1 < aBit0) { 126 | aBit1 = aBit0; 127 | } 128 | 129 | var bBit0 = b0.vertex.point.BitPack; 130 | var bBit1 = b1.vertex.point.BitPack; 131 | if(bBit1 < bBit0) { 132 | bBit1 = bBit0; 133 | } 134 | 135 | if(aBit0 <= bBit1 && bBit0 <= aBit1) { 136 | triangleStack.Add(c.vertex, a0.vertex, b0.vertex); 137 | 138 | a0.prev = b0.self; 139 | b0.next = a0.self; 140 | links[a0.self] = a0; 141 | links[b0.self] = b0; 142 | 143 | if(bBit0 < aBit0) { 144 | c = b0; 145 | b0 = b1; 146 | } else { 147 | c = a0; 148 | a0 = a1; 149 | } 150 | 151 | } else { 152 | if(aBit1 < bBit1) { 153 | var cx = c; 154 | var ax0 = a0; 155 | var ax1 = a1; 156 | long ax1Bit; 157 | do { 158 | var isCCW_or_Line = IntTriangle.IsCCW_or_Line(cx.vertex.point, ax0.vertex.point, ax1.vertex.point); 159 | 160 | if(isCCW_or_Line) { 161 | triangleStack.Add(ax0.vertex, ax1.vertex, cx.vertex); 162 | 163 | ax1.prev = cx.self; 164 | cx.next = ax1.self; 165 | links[cx.self] = cx; 166 | links[ax1.self] = ax1; 167 | 168 | if(cx.self != c.self) { 169 | // move back 170 | ax0 = cx; 171 | cx = links[cx.prev]; 172 | } else { 173 | // move forward 174 | ax0 = ax1; 175 | ax1 = links[ax1.next]; 176 | } 177 | } else { 178 | cx = ax0; 179 | ax0 = ax1; 180 | ax1 = links[ax1.next]; 181 | } 182 | ax1Bit = ax1.vertex.point.BitPack; 183 | } while(ax1Bit < bBit0); 184 | } else { 185 | var cx = c; 186 | var bx0 = b0; 187 | var bx1 = b1; 188 | long bx1Bit; 189 | do { 190 | bool isCCW_or_Line = IntTriangle.IsCCW_or_Line(cx.vertex.point, bx1.vertex.point, bx0.vertex.point); 191 | if(isCCW_or_Line) { 192 | triangleStack.Add(bx0.vertex, cx.vertex, bx1.vertex); 193 | 194 | bx1.next = cx.self; 195 | cx.prev = bx1.self; 196 | links[cx.self] = cx; 197 | links[bx1.self] = bx1; 198 | 199 | if(cx.self != c.self) { 200 | // move back 201 | bx0 = cx; 202 | cx = links[cx.next]; 203 | } else { 204 | // move forward 205 | bx0 = bx1; 206 | bx1 = links[bx0.prev]; 207 | } 208 | } else { 209 | cx = bx0; 210 | bx0 = bx1; 211 | bx1 = links[bx1.prev]; 212 | } 213 | bx1Bit = bx1.vertex.point.BitPack; 214 | } while(bx1Bit < aBit0); 215 | } 216 | 217 | c = links[c.self]; 218 | a0 = links[c.next]; 219 | b0 = links[c.prev]; 220 | 221 | aBit0 = a0.vertex.point.BitPack; 222 | bBit0 = b0.vertex.point.BitPack; 223 | 224 | triangleStack.Add(c.vertex, a0.vertex, b0.vertex); 225 | 226 | a0.prev = b0.self; 227 | b0.next = a0.self; 228 | links[a0.self] = a0; 229 | links[b0.self] = b0; 230 | 231 | if(bBit0 < aBit0) { 232 | c = b0; 233 | b0 = links[b0.prev]; 234 | } else { 235 | c = a0; 236 | a0 = links[a0.next]; 237 | } 238 | 239 | } 240 | } // while 241 | } 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Tessellation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using iShape.Geometry; 3 | using iShape.Geometry.Container; 4 | using iShape.Geometry.Extension; 5 | using Unity.Collections; 6 | using UnityEngine; 7 | 8 | namespace iShape.Triangulation.Shape.Delaunay { 9 | 10 | internal readonly struct Validator { 11 | private static readonly float mergeCos = Mathf.Cos(0.8f * Mathf.PI); 12 | internal static readonly float sqrMergeCos = mergeCos * mergeCos; 13 | private readonly float maxArea; 14 | private readonly IntGeom intGeom; 15 | 16 | internal Validator(IntGeom intGeom, float maxArea) { 17 | this.intGeom = intGeom; 18 | this.maxArea = 2f * maxArea; 19 | } 20 | 21 | internal int TestRegular(Triangle triangle) { 22 | var a = intGeom.Float(triangle.vA.point); 23 | var b = intGeom.Float(triangle.vB.point); 24 | var c = intGeom.Float(triangle.vC.point); 25 | 26 | var ab = a.SqrDistance(b); 27 | var ca = c.SqrDistance(a); 28 | var bc = b.SqrDistance(c); 29 | 30 | float s0 = a.x * (c.y - b.y) + b.x * (a.y - c.y) + c.x * (b.y - a.y); 31 | float s1; 32 | 33 | int k; 34 | float sCos; 35 | 36 | if (ab >= bc + ca) { 37 | // c, ab 38 | k = 2; 39 | float l = bc + ca - ab; 40 | sCos = l * l / (4 * bc * ca); 41 | s1 = s0 / (1 - sCos); 42 | } else if (bc >= ca + ab) { 43 | // a, bc 44 | k = 0; 45 | float l = ca + ab - bc; 46 | sCos = l * l / (4 * ca * ab); 47 | s1 = s0 / (1 - sCos); 48 | } else if (ca >= bc + ab) { 49 | // b, ca 50 | k = 1; 51 | float l = bc + ab - ca; 52 | sCos = l * l / (4 * bc * ab); 53 | s1 = s0 / (1 - sCos); 54 | } else { 55 | if (ab >= bc && ab >= ca) { 56 | k = 2; 57 | } else if (bc >= ca) { 58 | k = 0; 59 | } else { 60 | k = 1; 61 | } 62 | 63 | s1 = s0; 64 | } 65 | 66 | if (s1 > maxArea) { 67 | return k; 68 | } 69 | 70 | return -1; 71 | } 72 | 73 | internal static float SqrCos(IntVector a, IntVector b, IntVector c) { 74 | long ab = a.SqrDistance(b); 75 | long ca = c.SqrDistance(a); 76 | long bc = b.SqrDistance(c); 77 | 78 | if (ab >= bc + ca) { 79 | float aa = bc; 80 | float bb = ca; 81 | float cc = ab; 82 | 83 | float l = aa + bb - cc; 84 | return l * l / (4 * aa * bb); 85 | } 86 | 87 | return 0f; 88 | } 89 | } 90 | 91 | public static class TessellationExtension { 92 | public static Delaunay Tessellate(ref this PlainShape self, Allocator allocator, IntGeom intGeom, float maxEdge, NativeArray extraPoints, float maxArea = 0) { 93 | long iEdge = intGeom.Int(maxEdge); 94 | var delaunay = self.Delaunay(iEdge, extraPoints, allocator); 95 | 96 | float area; 97 | if (maxArea > 0) { 98 | area = maxArea; 99 | } else { 100 | area = 0.4f * maxEdge * maxEdge; 101 | } 102 | delaunay.Tessellate(intGeom, area); 103 | return delaunay; 104 | } 105 | 106 | public static Delaunay Tessellate(ref this PlainShape self, Allocator allocator, IntGeom intGeom, float maxEdge, float maxArea = 0) { 107 | long iEdge = intGeom.Int(maxEdge); 108 | var extraPoints = new NativeArray(0, Allocator.Temp); 109 | var delaunay = self.Delaunay(iEdge, extraPoints, allocator); 110 | extraPoints.Dispose(); 111 | 112 | float area; 113 | if (maxArea > 0) { 114 | area = maxArea; 115 | } else { 116 | area = 0.4f * maxEdge * maxEdge; 117 | } 118 | delaunay.Tessellate(intGeom, area); 119 | return delaunay; 120 | } 121 | 122 | public static void Tessellate(ref this Delaunay self, IntGeom intGeom, float maxArea) { 123 | var validator = new Validator(intGeom, maxArea); 124 | var unprocessed = new IndexBuffer(self.triangles.Count, Allocator.Temp); 125 | 126 | var fixIndices = new NativeArray(4, Allocator.Temp); 127 | 128 | while (unprocessed.hasNext) { 129 | int i = unprocessed.Next(); 130 | var triangle = self.triangles[i]; 131 | 132 | int k = validator.TestRegular(triangle); 133 | 134 | if (k < 0) { 135 | continue; 136 | } 137 | 138 | int nIx = triangle.Neighbor(k); 139 | 140 | if (nIx < 0) { 141 | continue; 142 | } 143 | 144 | var p = triangle.CircumscribedCenter(); 145 | 146 | var neighbor = self.triangles[nIx]; 147 | 148 | if (!neighbor.IsContain(p)) { 149 | continue; 150 | } 151 | 152 | int j = neighbor.Opposite(triangle.index); 153 | int j_next = (j + 1) % 3; 154 | int j_prev = (j + 2) % 3; 155 | 156 | if (neighbor.Neighbor(j_next) == -1 || neighbor.Neighbor(j_prev) == -1) { 157 | var njp = neighbor.Vertex(j).point; 158 | var nextCos = Validator.SqrCos(neighbor.Vertex(j_prev).point, njp, p); 159 | if (nextCos > Validator.sqrMergeCos) { 160 | continue; 161 | } 162 | 163 | var prevCos = Validator.SqrCos(njp, neighbor.Vertex(j_next).point, p); 164 | if (prevCos > Validator.sqrMergeCos) { 165 | continue; 166 | } 167 | } 168 | 169 | int k_next = (k + 1) % 3; 170 | int k_prev = (k + 2) % 3; 171 | 172 | int l = neighbor.Opposite(i); 173 | 174 | int l_next = (l + 1) % 3; 175 | int l_prev = (l + 2) % 3; 176 | 177 | var vertex = new Vertex(self.points.Count, Vertex.Nature.extraTessellated, p); 178 | self.points.Add(p); 179 | 180 | int n = self.triangles.Count; 181 | 182 | var t0 = triangle; 183 | t0.SetVertex(k_prev, vertex); 184 | t0.SetNeighbor(k_next, n); 185 | self.triangles[i] = t0; 186 | unprocessed.Add(t0.index); 187 | 188 | 189 | var t1 = neighbor; 190 | t1.SetVertex(l_next, vertex); 191 | t1.SetNeighbor(l_prev, n + 1); 192 | self.triangles[nIx] = t1; 193 | unprocessed.Add(t1.index); 194 | 195 | 196 | var t2Neighbor = triangle.Neighbor(k_next); 197 | var t2 = new Triangle( 198 | n, 199 | triangle.Vertex(k), 200 | vertex, 201 | triangle.Vertex(k_prev), 202 | n + 1, 203 | t2Neighbor, 204 | i 205 | ); 206 | 207 | if (t2Neighbor >= 0) { 208 | var t2n = self.triangles[t2Neighbor]; 209 | t2n.UpdateOpposite(i, n); 210 | self.triangles[t2Neighbor] = t2n; 211 | } 212 | 213 | self.triangles.Add(t2); 214 | unprocessed.Add(t2.index); 215 | 216 | var t3Neighbor = neighbor.Neighbor(l_prev); 217 | var t3 = new Triangle( 218 | n + 1, 219 | neighbor.Vertex(l), 220 | neighbor.Vertex(l_next), 221 | vertex, 222 | n, 223 | nIx, 224 | t3Neighbor 225 | ); 226 | 227 | if (t3Neighbor >= 0) { 228 | var t3n = self.triangles[t3Neighbor]; 229 | t3n.UpdateOpposite(nIx, n + 1); 230 | self.triangles[t3Neighbor] = t3n; 231 | } 232 | 233 | self.triangles.Add(t3); 234 | unprocessed.Add(t3.index); 235 | 236 | fixIndices[0] = i; 237 | fixIndices[1] = nIx; 238 | fixIndices[2] = n; 239 | fixIndices[3] = n + 1; 240 | self.Fix(ref unprocessed, fixIndices); 241 | } 242 | 243 | unprocessed.Dispose(); 244 | fixIndices.Dispose(); 245 | } 246 | } 247 | 248 | 249 | internal static class TessellationExt { 250 | internal static IntVector CircumscribedCenter(this Triangle self) { 251 | var a = self.vA.point; 252 | var b = self.vB.point; 253 | var c = self.vC.point; 254 | double ax = a.x; 255 | double ay = a.y; 256 | double bx = b.x; 257 | double by = b.y; 258 | double cx = c.x; 259 | double cy = c.y; 260 | 261 | double d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)); 262 | double aa = ax * ax + ay * ay; 263 | double bb = bx * bx + by * by; 264 | double cc = cx * cx + cy * cy; 265 | double x = (aa * (by - cy) + bb * (cy - ay) + cc * (ay - by)) / d; 266 | double y = (aa * (cx - bx) + bb * (ax - cx) + cc * (bx - ax)) / d; 267 | 268 | return new IntVector((long) Math.Round(x, MidpointRounding.AwayFromZero), (long) Math.Round(y, MidpointRounding.AwayFromZero)); 269 | } 270 | 271 | internal static bool IsContain(this Triangle self, IntVector p) { 272 | var a = self.vA.point; 273 | var b = self.vB.point; 274 | var c = self.vC.point; 275 | 276 | var d1 = Sign(p, a, b); 277 | var d2 = Sign(p, b, c); 278 | var d3 = Sign(p, c, a); 279 | 280 | bool has_neg = d1 < 0 || d2 < 0 || d3 < 0; 281 | bool has_pos = d1 > 0 || d2 > 0 || d3 > 0; 282 | 283 | return !(has_neg && has_pos); 284 | } 285 | 286 | private static long Sign(IntVector a, IntVector b, IntVector c) { 287 | return (a.x - c.x) * (b.y - c.y) - (b.x - c.x) * (a.y - c.y); 288 | } 289 | } 290 | 291 | } -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/NavigatorExt.cs: -------------------------------------------------------------------------------- 1 | using iShape.Collections; 2 | using Unity.Collections; 3 | using iShape.Geometry; 4 | using iShape.Geometry.Container; 5 | using UnityEngine; 6 | 7 | namespace iShape.Triangulation.Shape { 8 | 9 | internal static class PlainShapeNavigatorExt { 10 | private struct Node { 11 | internal readonly int index; 12 | internal IntVector point; 13 | 14 | internal Node(int index, IntVector point) { 15 | this.index = index; 16 | this.point = point; 17 | } 18 | } 19 | 20 | private struct SplitLayout { 21 | internal NativeArray layouts; 22 | internal NativeArray nodes; 23 | 24 | internal SplitLayout(NativeArray layouts, NativeArray nodes) { 25 | this.layouts = layouts; 26 | this.nodes = nodes; 27 | } 28 | 29 | internal void Dispose() { 30 | this.layouts.Dispose(); 31 | this.nodes.Dispose(); 32 | } 33 | } 34 | 35 | private readonly struct SortData { 36 | internal readonly int index; 37 | internal readonly long factor; 38 | private readonly int nature; 39 | 40 | internal SortData(int index, long factor, int nature) { 41 | this.index = index; 42 | this.factor = factor; 43 | this.nature = nature; 44 | } 45 | 46 | public static bool operator <(SortData a, SortData b) { 47 | if(a.factor != b.factor) { 48 | return a.factor < b.factor; 49 | } else if (a.nature != b.nature) { 50 | return a.nature < b.nature; 51 | } else { 52 | return a.index < b.index; 53 | } 54 | } 55 | 56 | public static bool operator >(SortData a, SortData b) { 57 | if(a.factor != b.factor) { 58 | return a.factor > b.factor; 59 | } else if(a.nature != b.nature) { 60 | return a.nature > b.nature; 61 | } else { 62 | return a.index > b.index; 63 | } 64 | } 65 | } 66 | 67 | internal static ShapeNavigator GetNavigator(this PlainShape shape, long maxEdge, NativeArray extraPoints, Allocator allocator) { 68 | SplitLayout splitLayout; 69 | if (maxEdge == 0) { 70 | splitLayout = shape.Plain(Allocator.Temp); 71 | } else { 72 | splitLayout = shape.Split(maxEdge, Allocator.Temp); 73 | } 74 | 75 | int pathCount = splitLayout.nodes.Length; 76 | int extraCount = extraPoints.Length; 77 | 78 | int n; 79 | if (extraCount > 0) { 80 | n = pathCount + extraCount; 81 | } else { 82 | n = pathCount; 83 | } 84 | 85 | var links = new NativeArray(n, allocator); 86 | var natures = new NativeArray(n, allocator); 87 | 88 | int m = splitLayout.layouts.Length; 89 | for(int j = 0; j < m; ++j) { 90 | var layout = splitLayout.layouts[j]; 91 | var prev = layout.end - 1; 92 | 93 | var self = layout.end; 94 | var next = layout.begin; 95 | 96 | var a = splitLayout.nodes[prev]; 97 | var b = splitLayout.nodes[self]; 98 | 99 | var A = a.point.BitPack; 100 | var B = b.point.BitPack; 101 | 102 | while(next <= layout.end) { 103 | var c = splitLayout.nodes[next]; 104 | var C = c.point.BitPack; 105 | 106 | var nature = LinkNature.simple; 107 | bool isCCW = IsCCW(a.point, b.point, c.point); 108 | 109 | if(layout.isClockWise) { 110 | if(A > B && B < C) { 111 | if(isCCW) { 112 | nature = LinkNature.start; 113 | } else { 114 | nature = LinkNature.split; 115 | } 116 | } 117 | 118 | if(A < B && B > C) { 119 | if(isCCW) { 120 | nature = LinkNature.end; 121 | } else { 122 | nature = LinkNature.merge; 123 | 124 | } 125 | } 126 | } else { 127 | if(A > B && B < C) { 128 | if(isCCW) { 129 | nature = LinkNature.start; 130 | } else { 131 | nature = LinkNature.split; 132 | } 133 | } 134 | 135 | if(A < B && B > C) { 136 | if(isCCW) { 137 | nature = LinkNature.end; 138 | } else { 139 | nature = LinkNature.merge; 140 | } 141 | } 142 | } 143 | 144 | var verNature = b.index < shape.points.Length ? Vertex.Nature.origin : Vertex.Nature.extraPath; 145 | 146 | links[self] = new Link(prev, self, next, new Vertex(self, verNature, b.point)); 147 | natures[self] = nature; 148 | 149 | a = b; 150 | b = c; 151 | 152 | A = B; 153 | B = C; 154 | 155 | prev = self; 156 | self = next; 157 | 158 | ++next; 159 | } 160 | } 161 | 162 | splitLayout.Dispose(); 163 | 164 | if (extraCount > 0) { 165 | for(int k = 0; k < extraPoints.Length; ++k) { 166 | var p = extraPoints[k]; 167 | var j = k + pathCount; 168 | links[j] = new Link(j, j, j, new Vertex(j, Vertex.Nature.extraInner, p)); 169 | natures[j] = LinkNature.extra; 170 | } 171 | } 172 | 173 | // sort 174 | 175 | var dataList = new NativeArray(n, Allocator.Temp); 176 | 177 | for(int j = 0; j < n; ++j) { 178 | var p = links[j].vertex.point; 179 | dataList[j] = new SortData(j, p.BitPack, (int)natures[j]); 180 | } 181 | 182 | Sort(dataList); 183 | 184 | var indices = new NativeArray(n, allocator); 185 | 186 | // filter same points 187 | var x1 = new SortData(-1, long.MinValue, int.MinValue); 188 | 189 | int i = 0; 190 | 191 | while(i < n) { 192 | var x0 = dataList[i]; 193 | indices[i] = x0.index; 194 | if(x0.factor == x1.factor) { 195 | var v = links[x1.index].vertex; 196 | 197 | do { 198 | var link = links[x0.index]; 199 | links[x0.index] = new Link(link.prev, link.self, link.next, new Vertex(v.index, v.nature, v.point)); 200 | ++i; 201 | if(i < n) { 202 | x0 = dataList[i]; 203 | indices[i] = x0.index; 204 | } else { 205 | break; 206 | } 207 | } while(x0.factor == x1.factor); 208 | } 209 | x1 = x0; 210 | ++i; 211 | } 212 | 213 | dataList.Dispose(); 214 | 215 | return new ShapeNavigator(pathCount, extraCount, links, natures, indices); 216 | } 217 | 218 | private static void Sort(NativeArray array) { 219 | int n = array.Length; 220 | int r = 2; 221 | int rank = 1; 222 | 223 | while(r <= n) { 224 | rank = r; 225 | r <<= 1; 226 | } 227 | rank -= 1; 228 | 229 | int jEnd = rank; 230 | 231 | int jStart = ((jEnd + 1) >> 1) - 1; 232 | 233 | 234 | while(jStart >= 0) { 235 | int k = jStart; 236 | while(k < jEnd) { 237 | int j = k; 238 | 239 | var a = array[j]; 240 | bool fallDown; 241 | do { 242 | fallDown = false; 243 | 244 | int j0 = (j << 1) + 1; 245 | int j1 = j0 + 1; 246 | 247 | 248 | if(j1 < n) { 249 | var a0 = array[j0]; 250 | var a1 = array[j1]; 251 | 252 | if(a < a0 || a < a1) { 253 | if(a0 > a1) { 254 | array[j0] = a; 255 | array[j] = a0; 256 | j = j0; 257 | } else { 258 | array[j1] = a; 259 | array[j] = a1; 260 | j = j1; 261 | } 262 | fallDown = j < rank; 263 | 264 | } 265 | } else if(j0 < n) { 266 | var ax = array[j]; 267 | var a0 = array[j0]; 268 | if(ax < a0) { 269 | array[j0] = ax; 270 | array[j] = a0; 271 | } 272 | } 273 | 274 | } while(fallDown); 275 | ++k; 276 | } 277 | 278 | jEnd = jStart; 279 | jStart = ((jEnd + 1) >> 1) - 1; 280 | } 281 | 282 | while(n > 0) { 283 | int m = n - 1; 284 | 285 | var a = array[m]; 286 | array[m] = array[0]; 287 | array[0] = a; 288 | 289 | int j = 0; 290 | bool fallDown; 291 | do { 292 | fallDown = false; 293 | 294 | int j0 = (j << 1) + 1; 295 | int j1 = j0 + 1; 296 | 297 | if(j1 < m) { 298 | var a0 = array[j0]; 299 | var a1 = array[j1]; 300 | fallDown = a < a0 || a < a1; 301 | 302 | if(fallDown) { 303 | if(a0 > a1) { 304 | array[j0] = a; 305 | array[j] = a0; 306 | j = j0; 307 | } else { 308 | array[j1] = a; 309 | array[j] = a1; 310 | j = j1; 311 | } 312 | } 313 | } else if(j0 < m) { 314 | var ax = array[j]; 315 | var a0 = array[j0]; 316 | if(ax < a0) { 317 | array[j0] = ax; 318 | array[j] = a0; 319 | } 320 | } 321 | 322 | } while(fallDown); 323 | 324 | n = m; 325 | } 326 | } 327 | 328 | private static bool IsCCW(IntVector a, IntVector b, IntVector c) { 329 | long m0 = (c.y - a.y) * (b.x - a.x); 330 | long m1 = (b.y - a.y) * (c.x - a.x); 331 | 332 | return m0 < m1; 333 | } 334 | 335 | private static SplitLayout Split(this PlainShape self, long maxEgeSize, Allocator allocator) { 336 | var originalCount = self.points.Length; 337 | var nodes = new DynamicArray(originalCount, allocator); 338 | var layouts = new DynamicArray(originalCount, allocator); 339 | var sqrMaxSize = maxEgeSize * maxEgeSize; 340 | 341 | var begin = 0; 342 | var originalIndex = 0; 343 | var extraIndex = originalCount; 344 | 345 | for (int j = 0; j < self.layouts.Length; ++j) { 346 | var layout = self.layouts[j]; 347 | var last = layout.end; 348 | var a = self.points[last]; 349 | var length = 0; 350 | 351 | for (int i = layout.begin; i <= layout.end; ++i) { 352 | var b = self.points[i]; 353 | var dx = b.x - a.x; 354 | var dy = b.y - a.y; 355 | var sqrSize = dx * dx + dy * dy; 356 | if (sqrSize > sqrMaxSize) { 357 | var l = (long) Mathf.Sqrt(sqrSize); 358 | int s = (int) (l / maxEgeSize); 359 | double ds = s; 360 | double sx = dx / ds; 361 | double sy = dy / ds; 362 | double fx = 0; 363 | double fy = 0; 364 | for (int k = 1; k < s; ++k) { 365 | fx += sx; 366 | fy += sy; 367 | 368 | long x = a.x + (long) fx; 369 | long y = a.y + (long) fy; 370 | nodes.Add(new Node(extraIndex, new IntVector(x, y))); 371 | extraIndex += 1; 372 | } 373 | 374 | length += s - 1; 375 | } 376 | 377 | length += 1; 378 | nodes.Add(new Node(originalIndex, b)); 379 | originalIndex += 1; 380 | a = b; 381 | } 382 | 383 | layouts.Add(new PathLayout(begin, length, layout.isClockWise)); 384 | begin += length; 385 | } 386 | 387 | return new SplitLayout(layouts.Convert(), nodes.Convert()); 388 | } 389 | 390 | private static SplitLayout Plain(this PlainShape self, Allocator allocator) { 391 | var nodes = new NativeArray(self.points.Length, allocator); 392 | for (int i = 0; i < self.points.Length; ++i) { 393 | nodes[i] = new Node(i, self.points[i]); 394 | } 395 | 396 | var layouts = new NativeArray(self.layouts, allocator); 397 | 398 | return new SplitLayout(layouts, nodes); 399 | } 400 | } 401 | 402 | } -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/MonotoneDelaunayTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using iShape.Geometry; 3 | using iShape.Geometry.Container; 4 | using iShape.Triangulation.Shape.Delaunay; 5 | using Tests.Triangulation.Util; 6 | using Tests.Triangulation.Data; 7 | using Unity.Collections; 8 | using Triangle = Tests.Triangulation.Util.Triangle; 9 | 10 | 11 | namespace Tests.Triangulation { 12 | 13 | public class MonotoneDelaunayTests { 14 | 15 | private NativeArray Triangulate(int index) { 16 | var iGeom = IntGeom.DefGeom; 17 | 18 | var data = MonotoneTests.data[index]; 19 | var iPoints = iGeom.Int(data); 20 | 21 | var iShape = new IntShape(iPoints, new IntVector[0][]); 22 | var pShape = new PlainShape(iShape, Allocator.Temp); 23 | 24 | var triangles = pShape.DelaunayTriangulate(Allocator.Temp); 25 | 26 | Assert.IsTrue(Triangle.IsCCW(pShape.points, triangles)); 27 | 28 | pShape.Dispose(); 29 | 30 | return triangles; 31 | } 32 | 33 | [Test] 34 | public void TestTriangulate_00() { 35 | var triangles = this.Triangulate(0); 36 | var origin = new NativeArray(new int[] { 37 | 0, 1, 3, 38 | 1, 2, 3 39 | }, Allocator.Temp); 40 | 41 | bool isEqual = triangles.CompareTriangles(origin); 42 | Assert.IsTrue(isEqual); 43 | triangles.Dispose(); 44 | origin.Dispose(); 45 | } 46 | 47 | [Test] 48 | public void TestTriangulate_01() { 49 | var triangles = this.Triangulate(1); 50 | var origin = new NativeArray(new int[] { 51 | 0, 1, 3, 52 | 3, 1, 2 53 | }, Allocator.Temp); 54 | 55 | bool isEqual = triangles.CompareTriangles(origin); 56 | Assert.IsTrue(isEqual); 57 | triangles.Dispose(); 58 | origin.Dispose(); 59 | } 60 | 61 | [Test] 62 | public void TestTriangulate_02() { 63 | var triangles = this.Triangulate(2); 64 | var origin = new NativeArray(new int[] { 65 | 1, 2, 0, 66 | 0, 2, 4, 67 | 2, 3, 4 68 | }, Allocator.Temp); 69 | 70 | bool isEqual = triangles.CompareTriangles(origin); 71 | Assert.IsTrue(isEqual); 72 | triangles.Dispose(); 73 | origin.Dispose(); 74 | } 75 | 76 | [Test] 77 | public void TestTriangulate_03() { 78 | var triangles = this.Triangulate(3); 79 | var origin = new NativeArray(new int[] { 80 | 3, 1, 2, 81 | 1, 4, 0, 82 | 3, 4, 1 83 | }, Allocator.Temp); 84 | 85 | bool isEqual = triangles.CompareTriangles(origin); 86 | Assert.IsTrue(isEqual); 87 | triangles.Dispose(); 88 | origin.Dispose(); 89 | } 90 | 91 | [Test] 92 | public void TestTriangulate_04() { 93 | var triangles = this.Triangulate(4); 94 | var origin = new NativeArray(new int[] { 95 | 3, 0, 2, 96 | 0, 1, 2 97 | }, Allocator.Temp); 98 | 99 | bool isEqual = triangles.CompareTriangles(origin); 100 | Assert.IsTrue(isEqual); 101 | triangles.Dispose(); 102 | origin.Dispose(); 103 | } 104 | 105 | [Test] 106 | public void TestTriangulate_05() { 107 | var triangles = this.Triangulate(5); 108 | var origin = new NativeArray(new int[] { 109 | 0, 1, 2, 110 | 0, 2, 4, 111 | 2, 3, 4 112 | }, Allocator.Temp); 113 | 114 | bool isEqual = triangles.CompareTriangles(origin); 115 | Assert.IsTrue(isEqual); 116 | triangles.Dispose(); 117 | origin.Dispose(); 118 | } 119 | 120 | [Test] 121 | public void TestTriangulate_06() { 122 | var triangles = this.Triangulate(6); 123 | var origin = new NativeArray(new int[] { 124 | 1, 2, 3, 125 | 1, 3, 0, 126 | 4, 0, 3 127 | }, Allocator.Temp); 128 | 129 | bool isEqual = triangles.CompareTriangles(origin); 130 | Assert.IsTrue(isEqual); 131 | triangles.Dispose(); 132 | origin.Dispose(); 133 | } 134 | 135 | [Test] 136 | public void TestTriangulate_07() { 137 | var triangles = this.Triangulate(7); 138 | var origin = new NativeArray(new int[] { 139 | 0, 1, 8, 140 | 8, 1, 6, 141 | 8, 6, 7, 142 | 2, 6, 1, 143 | 3, 6, 2, 144 | 5, 6, 3, 145 | 4, 5, 3 146 | }, Allocator.Temp); 147 | 148 | bool isEqual = triangles.CompareTriangles(origin); 149 | Assert.IsTrue(isEqual); 150 | triangles.Dispose(); 151 | origin.Dispose(); 152 | } 153 | 154 | [Test] 155 | public void TestTriangulate_08() { 156 | var triangles = this.Triangulate(8); 157 | var origin = new NativeArray(new int[] { 158 | 6, 0, 1, 159 | 8, 0, 6, 160 | 8, 6, 7, 161 | 2, 6, 1, 162 | 3, 6, 2, 163 | 5, 6, 3, 164 | 4, 5, 3 165 | }, Allocator.Temp); 166 | 167 | bool isEqual = triangles.CompareTriangles(origin); 168 | Assert.IsTrue(isEqual); 169 | triangles.Dispose(); 170 | origin.Dispose(); 171 | } 172 | 173 | [Test] 174 | public void TestTriangulate_09() { 175 | var triangles = this.Triangulate(9); 176 | var origin = new NativeArray(new int[] { 177 | 0, 1, 8, 178 | 7, 8, 1, 179 | 6, 7, 1, 180 | 1, 2, 3, 181 | 1, 3, 6, 182 | 5, 6, 3, 183 | 4, 5, 3 184 | }, Allocator.Temp); 185 | 186 | bool isEqual = triangles.CompareTriangles(origin); 187 | Assert.IsTrue(isEqual); 188 | triangles.Dispose(); 189 | origin.Dispose(); 190 | } 191 | 192 | [Test] 193 | public void TestTriangulate_10() { 194 | var triangles = this.Triangulate(10); 195 | var origin = new NativeArray(new int[] { 196 | 8, 0, 1, 197 | 8, 1, 7, 198 | 1, 6, 7, 199 | 1, 2, 6, 200 | 6, 2, 3, 201 | 6, 3, 5, 202 | 3, 4, 5 203 | }, Allocator.Temp); 204 | 205 | bool isEqual = triangles.CompareTriangles(origin); 206 | Assert.IsTrue(isEqual); 207 | triangles.Dispose(); 208 | origin.Dispose(); 209 | } 210 | 211 | [Test] 212 | public void TestTriangulate_11() { 213 | var triangles = this.Triangulate(11); 214 | var origin = new NativeArray(new int[] { 215 | 8, 0, 6, 216 | 8, 6, 7, 217 | 1, 6, 0, 218 | 1, 2, 3, 219 | 1, 3, 6, 220 | 5, 6, 3, 221 | 4, 5, 3 222 | }, Allocator.Temp); 223 | 224 | bool isEqual = triangles.CompareTriangles(origin); 225 | Assert.IsTrue(isEqual); 226 | triangles.Dispose(); 227 | origin.Dispose(); 228 | } 229 | 230 | [Test] 231 | public void TestTriangulate_12() { 232 | var triangles = this.Triangulate(12); 233 | var origin = new NativeArray(new int[] { 234 | 14, 0, 1, 235 | 14, 1, 13, 236 | 1, 2, 3, 237 | 13, 1, 10, 238 | 10, 12, 13, 239 | 10, 11, 12, 240 | 1, 3, 4, 241 | 1, 4, 10, 242 | 9, 10, 4, 243 | 5, 9, 4, 244 | 5, 6, 8, 245 | 5, 8, 9, 246 | 7, 8, 6 247 | }, Allocator.Temp); 248 | 249 | bool isEqual = triangles.CompareTriangles(origin); 250 | Assert.IsTrue(isEqual); 251 | triangles.Dispose(); 252 | origin.Dispose(); 253 | } 254 | 255 | [Test] 256 | public void TestTriangulate_13() { 257 | var triangles = this.Triangulate(13); 258 | var origin = new NativeArray(new int[] { 259 | 14, 0, 1, 260 | 14, 1, 13, 261 | 2, 3, 4, 262 | 10, 13, 1, 263 | 12, 13, 10, 264 | 12, 10, 11, 265 | 2, 4, 1, 266 | 1, 4, 10, 267 | 9, 10, 4, 268 | 5, 9, 4, 269 | 5, 6, 8, 270 | 5, 8, 9, 271 | 7, 8, 6 272 | }, Allocator.Temp); 273 | 274 | bool isEqual = triangles.CompareTriangles(origin); 275 | Assert.IsTrue(isEqual); 276 | triangles.Dispose(); 277 | origin.Dispose(); 278 | } 279 | 280 | [Test] 281 | public void TestTriangulate_14() { 282 | var triangles = this.Triangulate(14); 283 | var origin = new NativeArray(new int[] { 284 | 1, 2, 8, 285 | 2, 3, 7, 286 | 6, 3, 4, 287 | 9, 0, 1, 288 | 1, 8, 9, 289 | 2, 7, 8, 290 | 6, 7, 3, 291 | 5, 6, 4 292 | }, Allocator.Temp); 293 | 294 | bool isEqual = triangles.CompareTriangles(origin); 295 | Assert.IsTrue(isEqual); 296 | triangles.Dispose(); 297 | origin.Dispose(); 298 | } 299 | 300 | [Test] 301 | public void TestTriangulate_15() { 302 | var triangles = this.Triangulate(15); 303 | var origin = new NativeArray(new int[] { 304 | 0, 1, 3, 305 | 3, 1, 2, 306 | 13, 0, 3, 307 | 13, 15, 0, 308 | 13, 14, 15, 309 | 5, 3, 4, 310 | 5, 13, 3, 311 | 11, 13, 5, 312 | 11, 12, 13, 313 | 7, 5, 6, 314 | 8, 5, 7, 315 | 8, 11, 5, 316 | 9, 10, 11, 317 | 11, 8, 9 318 | }, Allocator.Temp); 319 | 320 | bool isEqual = triangles.CompareTriangles(origin); 321 | Assert.IsTrue(isEqual); 322 | triangles.Dispose(); 323 | origin.Dispose(); 324 | } 325 | 326 | [Test] 327 | public void TestTriangulate_16() { 328 | var triangles = this.Triangulate(16); 329 | var origin = new NativeArray(new int[] { 330 | 12, 0, 1, 331 | 12, 1, 11, 332 | 1, 2, 4, 333 | 5, 11, 1, 334 | 4, 2, 3, 335 | 5, 1, 4, 336 | 5, 10, 11, 337 | 10, 5, 8, 338 | 10, 8, 9, 339 | 5, 6, 7, 340 | 5, 7, 8 341 | }, Allocator.Temp); 342 | 343 | bool isEqual = triangles.CompareTriangles(origin); 344 | Assert.IsTrue(isEqual); 345 | triangles.Dispose(); 346 | origin.Dispose(); 347 | } 348 | 349 | [Test] 350 | public void TestTriangulate_17() { 351 | var triangles = this.Triangulate(17); 352 | var origin = new NativeArray(new int[] { 353 | 1, 13, 0, 354 | 12, 13, 1, 355 | 2, 12, 1, 356 | 3, 12, 2, 357 | 10, 11, 12, 358 | 9, 10, 12, 359 | 12, 3, 9, 360 | 4, 9, 3, 361 | 5, 9, 4, 362 | 8, 9, 5, 363 | 5, 6, 7, 364 | 5, 7, 8 365 | }, Allocator.Temp); 366 | 367 | bool isEqual = triangles.CompareTriangles(origin); 368 | Assert.IsTrue(isEqual); 369 | triangles.Dispose(); 370 | origin.Dispose(); 371 | } 372 | } 373 | } -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/MonotonePlainTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using iShape.Geometry; 3 | using iShape.Triangulation.Shape; 4 | using Tests.Triangulation.Util; 5 | using Tests.Triangulation.Data; 6 | using Unity.Collections; 7 | using iShape.Geometry.Container; 8 | using Triangle = Tests.Triangulation.Util.Triangle; 9 | 10 | 11 | namespace Tests.Triangulation { 12 | 13 | public class MonotonePlainTests { 14 | 15 | private NativeArray Triangulate(int index) { 16 | var iGeom = IntGeom.DefGeom; 17 | 18 | var data = MonotoneTests.data[index]; 19 | var iPoints = iGeom.Int(data); 20 | 21 | var iShape = new IntShape(iPoints, new IntVector[0][]); 22 | var pShape = new PlainShape(iShape, Allocator.Temp); 23 | 24 | var triangles = pShape.Triangulate(Allocator.Temp); 25 | 26 | Assert.IsTrue(Triangle.IsCCW(pShape.points, triangles)); 27 | 28 | pShape.Dispose(); 29 | 30 | return triangles; 31 | } 32 | 33 | [Test] 34 | public void TestTriangulate_00() { 35 | var triangles = this.Triangulate(0); 36 | var origin = new NativeArray(new int[] { 37 | 0, 1, 3, 38 | 1, 2, 3 39 | }, Allocator.Temp); 40 | 41 | bool isEqual = triangles.CompareTriangles(origin); 42 | Assert.IsTrue(isEqual); 43 | triangles.Dispose(); 44 | origin.Dispose(); 45 | } 46 | 47 | [Test] 48 | public void TestTriangulate_01() { 49 | var triangles = this.Triangulate(1); 50 | var origin = new NativeArray(new int[] { 51 | 0, 1, 3, 52 | 3, 1, 2 53 | }, Allocator.Temp); 54 | 55 | 56 | bool isEqual = triangles.CompareTriangles(origin); 57 | Assert.IsTrue(isEqual); 58 | triangles.Dispose(); 59 | origin.Dispose(); 60 | } 61 | 62 | [Test] 63 | public void TestTriangulate_02() { 64 | var triangles = this.Triangulate(2); 65 | var origin = new NativeArray(new int[] { 66 | 1, 2, 0, 67 | 0, 2, 4, 68 | 2, 3, 4 69 | }, Allocator.Temp); 70 | bool isEqual = triangles.CompareTriangles(origin); 71 | Assert.IsTrue(isEqual); 72 | triangles.Dispose(); 73 | origin.Dispose(); 74 | } 75 | 76 | [Test] 77 | public void TestTriangulate_03() { 78 | var triangles = this.Triangulate(3); 79 | var origin = new NativeArray(new int[] { 80 | 1, 2, 0, 81 | 0, 2, 4, 82 | 2, 3, 4 83 | }, Allocator.Temp); 84 | bool isEqual = triangles.CompareTriangles(origin); 85 | Assert.IsTrue(isEqual); 86 | triangles.Dispose(); 87 | origin.Dispose(); 88 | } 89 | 90 | [Test] 91 | public void TestTriangulate_04() { 92 | var triangles = this.Triangulate(4); 93 | var origin = new NativeArray(new int[] { 94 | 3, 0, 2, 95 | 0, 1, 2 96 | }, Allocator.Temp); 97 | bool isEqual = triangles.CompareTriangles(origin); 98 | Assert.IsTrue(isEqual); 99 | triangles.Dispose(); 100 | origin.Dispose(); 101 | } 102 | 103 | [Test] 104 | public void TestTriangulate_05() { 105 | var triangles = this.Triangulate(5); 106 | var origin = new NativeArray(new int[] { 107 | 0, 1, 2, 108 | 0, 2, 4, 109 | 2, 3, 4 110 | }, Allocator.Temp); 111 | bool isEqual = triangles.CompareTriangles(origin); 112 | Assert.IsTrue(isEqual); 113 | triangles.Dispose(); 114 | origin.Dispose(); 115 | 116 | 117 | } 118 | 119 | [Test] 120 | public void TestTriangulate_06() { 121 | var triangles = this.Triangulate(6); 122 | var origin = new NativeArray(new int[] { 123 | 0, 1, 2, 124 | 0, 2, 3, 125 | 0, 3, 4 126 | }, Allocator.Temp); 127 | bool isEqual = triangles.CompareTriangles(origin); 128 | Assert.IsTrue(isEqual); 129 | triangles.Dispose(); 130 | origin.Dispose(); 131 | } 132 | 133 | [Test] 134 | public void TestTriangulate_07() { 135 | var triangles = this.Triangulate(7); 136 | var origin = new NativeArray(new int[] { 137 | 8, 0, 1, 138 | 8, 1, 7, 139 | 1, 6, 7, 140 | 1, 2, 6, 141 | 6, 2, 3, 142 | 6, 3, 5, 143 | 3, 4, 5 144 | }, Allocator.Temp); 145 | bool isEqual = triangles.CompareTriangles(origin); 146 | Assert.IsTrue(isEqual); 147 | triangles.Dispose(); 148 | origin.Dispose(); 149 | } 150 | 151 | [Test] 152 | public void TestTriangulate_08() { 153 | var triangles = this.Triangulate(8); 154 | var origin = new NativeArray(new int[] { 155 | 8, 0, 1, 156 | 8, 1, 7, 157 | 1, 6, 7, 158 | 1, 2, 6, 159 | 6, 2, 3, 160 | 6, 3, 5, 161 | 3, 4, 5 162 | }, Allocator.Temp); 163 | bool isEqual = triangles.CompareTriangles(origin); 164 | Assert.IsTrue(isEqual); 165 | triangles.Dispose(); 166 | origin.Dispose(); 167 | } 168 | 169 | [Test] 170 | public void TestTriangulate_09() { 171 | var triangles = this.Triangulate(9); 172 | var origin = new NativeArray(new int[] { 173 | 8, 0, 1, 174 | 8, 1, 7, 175 | 1, 6, 7, 176 | 1, 2, 6, 177 | 6, 2, 3, 178 | 6, 3, 5, 179 | 3, 4, 5 180 | }, Allocator.Temp); 181 | bool isEqual = triangles.CompareTriangles(origin); 182 | Assert.IsTrue(isEqual); 183 | triangles.Dispose(); 184 | origin.Dispose(); 185 | } 186 | 187 | [Test] 188 | public void TestTriangulate_10() { 189 | var triangles = this.Triangulate(10); 190 | var origin = new NativeArray(new int[] { 191 | 8, 0, 1, 192 | 8, 1, 7, 193 | 1, 6, 7, 194 | 1, 2, 6, 195 | 6, 2, 3, 196 | 6, 3, 5, 197 | 3, 4, 5 198 | }, Allocator.Temp); 199 | bool isEqual = triangles.CompareTriangles(origin); 200 | Assert.IsTrue(isEqual); 201 | triangles.Dispose(); 202 | origin.Dispose(); 203 | } 204 | 205 | [Test] 206 | public void TestTriangulate_11() { 207 | var triangles = this.Triangulate(11); 208 | var origin = new NativeArray(new int[] { 209 | 8, 0, 7, 210 | 0, 6, 7, 211 | 0, 1, 6, 212 | 6, 2, 3, 213 | 6, 3, 5, 214 | 3, 4, 5 215 | }, Allocator.Temp); 216 | bool isEqual = triangles.CompareTriangles(origin); 217 | Assert.IsTrue(isEqual); 218 | triangles.Dispose(); 219 | origin.Dispose(); 220 | } 221 | 222 | 223 | [Test] 224 | public void TestTriangulate_12() { 225 | var triangles = this.Triangulate(12); 226 | var origin = new NativeArray(new int[] { 227 | 0, 13, 14, 228 | 0, 1, 13, 229 | 13, 1, 2, 230 | 13, 2, 12, 231 | 2, 11, 12, 232 | 2, 10, 11, 233 | 2, 3, 10, 234 | 10, 3, 4, 235 | 10, 4, 9, 236 | 4, 5, 9, 237 | 9, 5, 6, 238 | 9, 6, 8, 239 | 6, 7, 8 240 | }, Allocator.Temp); 241 | bool isEqual = triangles.CompareTriangles(origin); 242 | Assert.IsTrue(isEqual); 243 | triangles.Dispose(); 244 | origin.Dispose(); 245 | } 246 | 247 | 248 | [Test] 249 | public void TestTriangulate_13() { 250 | var triangles = this.Triangulate(13); 251 | var origin = new NativeArray(new int[] { 252 | 0, 13, 14, 253 | 0, 1, 13, 254 | 13, 1, 12, 255 | 1, 2, 12, 256 | 12, 2, 11, 257 | 2, 10, 11, 258 | 2, 3, 10, 259 | 10, 3, 4, 260 | 10, 4, 9, 261 | 4, 5, 9, 262 | 9, 5, 6, 263 | 9, 6, 8, 264 | 6, 7, 8 265 | }, Allocator.Temp); 266 | bool isEqual = triangles.CompareTriangles(origin); 267 | Assert.IsTrue(isEqual); 268 | triangles.Dispose(); 269 | origin.Dispose(); 270 | } 271 | 272 | 273 | [Test] 274 | public void TestTriangulate_14() { 275 | var triangles = this.Triangulate(14); 276 | var origin = new NativeArray(new int[] { 277 | 0, 4, 9, 278 | 4, 8, 9, 279 | 4, 7, 8, 280 | 4, 6, 7, 281 | 4, 5, 6 282 | }, Allocator.Temp); 283 | bool isEqual = triangles.CompareTriangles(origin); 284 | Assert.IsTrue(isEqual); 285 | triangles.Dispose(); 286 | origin.Dispose(); 287 | } 288 | 289 | 290 | [Test] 291 | public void TestTriangulate_15() { 292 | var triangles = this.Triangulate(15); 293 | var origin = new NativeArray(new int[] { 294 | 0, 1, 15, 295 | 1, 2, 15, 296 | 15, 2, 14, 297 | 2, 3, 14, 298 | 14, 3, 13, 299 | 3, 4, 13, 300 | 13, 4, 12, 301 | 4, 5, 12, 302 | 12, 5, 11, 303 | 5, 6, 11, 304 | 11, 6, 10, 305 | 6, 7, 10, 306 | 10, 7, 9, 307 | 7, 8, 9 308 | }, Allocator.Temp); 309 | bool isEqual = triangles.CompareTriangles(origin); 310 | Assert.IsTrue(isEqual); 311 | triangles.Dispose(); 312 | origin.Dispose(); 313 | } 314 | 315 | [Test] 316 | public void TestTriangulate_16() { 317 | var triangles = this.Triangulate(16); 318 | var origin = new NativeArray(new int[] { 319 | 0, 11, 12, 320 | 0, 1, 11, 321 | 11, 1, 2, 322 | 11, 2, 10, 323 | 2, 3, 10, 324 | 10, 3, 9, 325 | 3, 4, 9, 326 | 9, 4, 5, 327 | 9, 5, 8, 328 | 5, 6, 8, 329 | 8, 6, 7 330 | }, Allocator.Temp); 331 | bool isEqual = triangles.CompareTriangles(origin); 332 | Assert.IsTrue(isEqual); 333 | triangles.Dispose(); 334 | origin.Dispose(); 335 | } 336 | 337 | [Test] 338 | public void TestTriangulate_17() { 339 | var triangles = this.Triangulate(17); 340 | var origin = new NativeArray(new int[] { 341 | 0, 1, 13, 342 | 13, 1, 12, 343 | 1, 2, 12, 344 | 12, 10, 11, 345 | 2, 10, 12, 346 | 2, 9, 10, 347 | 2, 3, 9, 348 | 9, 3, 4, 349 | 9, 4, 5, 350 | 9, 5, 8, 351 | 5, 6, 8, 352 | 8, 6, 7 353 | }, Allocator.Temp); 354 | bool isEqual = triangles.CompareTriangles(origin); 355 | Assert.IsTrue(isEqual); 356 | triangles.Dispose(); 357 | origin.Dispose(); 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/CentroidNet.cs: -------------------------------------------------------------------------------- 1 | using iShape.Collections; 2 | using iShape.Geometry; 3 | using iShape.Geometry.Container; 4 | using iShape.Geometry.Extension; 5 | using iShape.Geometry.Polygon; 6 | using Unity.Collections; 7 | using UnityEngine; 8 | 9 | namespace iShape.Triangulation.Shape.Delaunay { 10 | 11 | public static class CentroidNet { 12 | 13 | public static List MakeCentroidNet(this PlainShape self, Allocator allocator, IntGeom intGeom, float maxEdge, float maxArea = 0, float minArea = 0, bool onlyConvex = false) { 14 | long iEdge = intGeom.Int(maxEdge); 15 | var delaunay = self.Delaunay(iEdge, Allocator.Temp); 16 | float aMaxArea; 17 | if (maxArea > 0) { 18 | aMaxArea = maxArea; 19 | } else { 20 | aMaxArea = 0.4f * maxEdge * maxEdge; 21 | } 22 | 23 | delaunay.Tessellate(intGeom, aMaxArea); 24 | 25 | var iMinArea = intGeom.SqrInt(minArea); 26 | var shape = delaunay.MakeCentroidNet(Allocator.Temp, iMinArea, onlyConvex); 27 | delaunay.Dispose(); 28 | 29 | int n = shape.layouts.Length; 30 | var dynamicList = new DynamicList(8 * n, n, allocator); 31 | 32 | for (int i = 0; i < n; ++i) { 33 | var iPath = shape.Get(i); 34 | var path = intGeom.Float(iPath, Allocator.Temp); 35 | var polygon = new Polygon(path, Allocator.Temp); 36 | dynamicList.Add(polygon); 37 | } 38 | 39 | shape.Dispose(); 40 | 41 | return dynamicList.Convert(); 42 | } 43 | 44 | public static PlainShape MakeCentroidNet(this Delaunay self, Allocator allocator, long minArea = 0, bool onlyConvex = false) { 45 | int n = self.triangles.Count; 46 | 47 | var details = new NativeArray(n, Allocator.Temp); 48 | for (int i = 0; i < n; ++i) { 49 | var triangle = self.triangles[i]; 50 | var count = 0; 51 | if (triangle.nA >= 0) { 52 | ++count; 53 | } 54 | 55 | if (triangle.nB >= 0) { 56 | ++count; 57 | } 58 | 59 | if (triangle.nC >= 0) { 60 | ++count; 61 | } 62 | 63 | details[i] = new Detail(triangle.Center(), count); 64 | } 65 | 66 | int capacity = self.points.Count; 67 | 68 | var visitedIndex = new NativeArray(capacity, Allocator.Temp); 69 | var result = new DynamicPlainShape(8 * capacity, capacity, allocator); 70 | var forePoints = new NativeArray(4, Allocator.Temp); 71 | var path = new DynamicArray(8, Allocator.Temp); 72 | var subPath = new DynamicArray(8, Allocator.Temp); 73 | 74 | for (int i = 0; i < n; ++i) { 75 | var triangle = self.triangles[i]; 76 | var detail = details[i]; 77 | 78 | for (int j = 0; j <= 2; ++j) { 79 | 80 | var v = triangle.Vertex(j); 81 | if (visitedIndex[v.index]) { 82 | continue; 83 | } 84 | 85 | visitedIndex[v.index] = true; 86 | 87 | if (v.isPath) { 88 | if (detail.count == 1 && triangle.Neighbor(j) >= 0) { 89 | switch (j) { 90 | case 0: // a 91 | var ab0 = v.point.Center(triangle.vB.point); 92 | var ca0 = v.point.Center(triangle.vC.point); 93 | 94 | forePoints[0] = v.point; 95 | forePoints[1] = ab0; 96 | forePoints[2] = detail.center; 97 | forePoints[3] = ca0; 98 | break; 99 | case 1: // b 100 | var bc1 = v.point.Center(point: triangle.vC.point); 101 | var ab1 = v.point.Center(point: triangle.vA.point); 102 | 103 | forePoints[0] = v.point; 104 | forePoints[1] = bc1; 105 | forePoints[2] = detail.center; 106 | forePoints[3] = ab1; 107 | break; 108 | default: // c 109 | var ca2 = v.point.Center(point: triangle.vA.point); 110 | var bc2 = v.point.Center(point: triangle.vB.point); 111 | 112 | forePoints[0] = v.point; 113 | forePoints[1] = ca2; 114 | forePoints[2] = detail.center; 115 | forePoints[3] = bc2; 116 | break; 117 | } 118 | 119 | if (minArea == 0 || forePoints.Area() > minArea) { 120 | result.Add(forePoints, true); 121 | } 122 | } else { 123 | path.RemoveAll(); 124 | // first going in a counterclockwise direction 125 | var current = triangle; 126 | int k = triangle.FindIndex(v.index); 127 | int right = (k + 2) % 3; 128 | var prev = triangle.Neighbor(right); 129 | while (prev >= 0) { 130 | var prevTriangle = self.triangles[prev]; 131 | k = prevTriangle.FindIndex(v.index); 132 | if (k < 0) { 133 | break; 134 | } 135 | 136 | current = prevTriangle; 137 | path.Add(details[prev].center); 138 | 139 | right = (k + 2) % 3; 140 | prev = current.Neighbor(right); 141 | } 142 | 143 | var left = (k + 1) % 3; 144 | var lastPrevPair = current.Vertex(left).point; 145 | path.Add(lastPrevPair.Center(v.point)); 146 | 147 | path.Reverse(); 148 | 149 | path.Add(details[i].center); 150 | 151 | // now going in a clockwise direction 152 | current = triangle; 153 | k = triangle.FindIndex(v.index); 154 | left = (k + 1) % 3; 155 | var next = triangle.Neighbor(left); 156 | while (next >= 0) { 157 | var nextTriangle = self.triangles[next]; 158 | k = nextTriangle.FindIndex(v.index); 159 | if (k < 0) { 160 | break; 161 | } 162 | 163 | current = nextTriangle; 164 | path.Add(details[next].center); 165 | left = (k + 1) % 3; 166 | next = current.Neighbor(left); 167 | } 168 | 169 | right = (k + 2) % 3; 170 | var lastNextPair = current.Vertex(right).point; 171 | path.Add(lastNextPair.Center(v.point)); 172 | 173 | if (onlyConvex) { 174 | // split path into convex subPath 175 | var c = v.point; 176 | var p0 = path[0]; 177 | var v0 = p0 - c; 178 | var d0 = v0; 179 | 180 | subPath.RemoveAll(); 181 | 182 | subPath.Add(c); 183 | subPath.Add(path[0]); 184 | for (int t = 1; t < path.Count; ++t) { 185 | 186 | var p1 = path[t]; 187 | var d1 = p1 - p0; 188 | var v1 = p1 - c; 189 | if (v0.CrossProduct(v1) <= 0 && d0.CrossProduct(d1) <= 0) { 190 | subPath.Add(p1); 191 | } else { 192 | if (minArea == 0 || subPath.slice.Area() > minArea) { 193 | result.Add(subPath.slice, true); 194 | } 195 | subPath.RemoveAll(); 196 | subPath.Add(c); 197 | subPath.Add(p0); 198 | subPath.Add(p1); 199 | v0 = p0 - c; 200 | } 201 | 202 | p0 = p1; 203 | d0 = d1; 204 | } 205 | 206 | if (minArea == 0 || subPath.slice.Area() > minArea) { 207 | result.Add(subPath.slice, true); 208 | } 209 | } else { 210 | path.Add(v.point); 211 | if (minArea == 0 || path.slice.Area() > minArea) { 212 | result.Add(path.slice, true); 213 | } 214 | } 215 | } 216 | } else { 217 | path.RemoveAll(); 218 | int start = i; 219 | var next = start; 220 | do { 221 | var t = self.triangles[next]; 222 | var center = details[next].center; 223 | path.Add(center); 224 | int index = (t.FindIndex(v.index) + 1) % 3; 225 | next = t.Neighbor(index); 226 | } while (next != start && next >= 0); 227 | if (minArea == 0 || path.slice.Area() > minArea) { 228 | result.Add(path.slice, true); 229 | } 230 | } 231 | } 232 | } 233 | 234 | path.Dispose(); 235 | subPath.Dispose(); 236 | forePoints.Dispose(); 237 | details.Dispose(); 238 | visitedIndex.Dispose(); 239 | 240 | return result.Convert(); 241 | } 242 | private readonly struct Detail { 243 | internal readonly IntVector center; 244 | internal readonly int count; 245 | 246 | internal Detail(IntVector center, int count) { 247 | this.center = center; 248 | this.count = count; 249 | } 250 | } 251 | private static IntVector Center(this Triangle self) { 252 | var a = self.vA.point; 253 | var b = self.vB.point; 254 | var c = self.vC.point; 255 | 256 | return new IntVector((a.x + b.x + c.x) / 3, (a.y + b.y + c.y) / 3); 257 | } 258 | 259 | private static IntVector Center(this IntVector self, IntVector point) { 260 | return new IntVector((self.x + point.x) / 2, (self.y + point.y) / 2); 261 | } 262 | 263 | private static void Reverse(this NativeList self) { 264 | int length = self.Length; 265 | int n = self.Length >> 1; 266 | for (int i = 0, j = length - 1; i < n; ++i, --j) { 267 | var a = self[i]; 268 | var b = self[j]; 269 | self[j] = a; 270 | self[i] = b; 271 | } 272 | } 273 | 274 | } 275 | } -------------------------------------------------------------------------------- /Readme/star_polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 45 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 210 | 215 | 216 | -------------------------------------------------------------------------------- /Runtime/iShape/Triangulation/Shape/Delaunay/Delaunay.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using iShape.Geometry; 3 | using iShape.Collections; 4 | using UnityEngine; 5 | 6 | namespace iShape.Triangulation.Shape.Delaunay { 7 | 8 | public struct Delaunay { 9 | 10 | internal DynamicArray points; 11 | internal DynamicArray triangles; 12 | 13 | public NativeArray Indices(Allocator allocator) { 14 | int n = triangles.Count; 15 | var result = new NativeArray(3 * n, allocator); 16 | int i = 0; 17 | int j = 0; 18 | do { 19 | var triangle = this.triangles[i]; 20 | result[j] = triangle.vA.index; 21 | result[j + 1] = triangle.vB.index; 22 | result[j + 2] = triangle.vC.index; 23 | 24 | j += 3; 25 | i += 1; 26 | } while(i < n); 27 | 28 | return result; 29 | } 30 | 31 | public NativeArray Vertices(Allocator allocator, IntGeom intGeom, float z = 0) { 32 | int n = points.Count; 33 | var result = new NativeArray(n, allocator); 34 | for (int i = 0; i < n; ++i) { 35 | var p = intGeom.Float(points[i]); 36 | result[i] = new Vector3(p.x, p.y, z); 37 | } 38 | 39 | return result; 40 | } 41 | 42 | public Delaunay(NativeArray points, NativeArray triangles, Allocator allocator) { 43 | this.points = new DynamicArray(points, allocator); 44 | this.triangles = new DynamicArray(triangles, allocator); 45 | } 46 | 47 | public void Build() { 48 | int count = triangles.Count; 49 | var visitMarks = new NativeArray(count, Allocator.Temp); 50 | var visitIndex = 0; 51 | 52 | var origin = new DynamicArray(16, Allocator.Temp); 53 | var buffer = new DynamicArray(16, Allocator.Temp); 54 | 55 | origin.Add(0); 56 | 57 | while(origin.Count > 0) { 58 | buffer.RemoveAll(); 59 | for(int l = 0; l < origin.Count; ++l) { 60 | int i = origin[l]; 61 | var triangle = this.triangles[i]; 62 | visitMarks[i] = true; 63 | 64 | for(int k = 0; k < 3; ++k) { 65 | 66 | int neighborIndex = triangle.Neighbor(k); 67 | if(neighborIndex >= 0) { 68 | var neighbor = triangles[neighborIndex]; 69 | if(this.Swap(triangle, neighbor)) { 70 | 71 | triangle = this.triangles[triangle.index]; 72 | neighbor = this.triangles[neighbor.index]; 73 | 74 | for(int j = 0; j < 3; ++j) { 75 | int ni = triangle.Neighbor(j); 76 | if(ni >= 0 && ni != neighbor.index) { 77 | buffer.Add(ni); 78 | } 79 | } 80 | 81 | for(int j = 0; j < 3; ++j) { 82 | int ni = neighbor.Neighbor(j); 83 | if(ni >= 0 && ni != triangle.index) { 84 | buffer.Add(ni); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | origin.RemoveAll(); 92 | 93 | if(buffer.Count == 0 && visitIndex < count) { 94 | ++visitIndex; 95 | while(visitIndex < count) { 96 | if(visitMarks[visitIndex] == false) { 97 | origin.Add(visitIndex); 98 | break; 99 | } 100 | ++visitIndex; 101 | } 102 | } else { 103 | origin.Add(buffer); 104 | } 105 | } 106 | 107 | origin.Dispose(); 108 | buffer.Dispose(); 109 | visitMarks.Dispose(); 110 | } 111 | public void Dispose() { 112 | this.points.Dispose(); 113 | this.triangles.Dispose(); 114 | } 115 | 116 | internal static bool IsDelaunay(IntVector p0, IntVector p1, IntVector p2, IntVector p3) { 117 | long x01 = p0.x - p1.x; 118 | long x03 = p0.x - p3.x; 119 | long x12 = p1.x - p2.x; 120 | long x32 = p3.x - p2.x; 121 | 122 | long y01 = p0.y - p1.y; 123 | long y03 = p0.y - p3.y; 124 | long y12 = p1.y - p2.y; 125 | long y23 = p2.y - p3.y; 126 | 127 | long cosA = x01 * x03 + y01 * y03; 128 | long cosB = x12 * x32 - y12 * y23; 129 | 130 | if (cosA < 0 && cosB < 0) { 131 | return false; 132 | } 133 | 134 | if (cosA >= 0 && cosB >= 0) { 135 | return true; 136 | } 137 | 138 | // we can not just compare 139 | // sinA * cosB + cosA * sinB ? 0 140 | // cause we need weak Delaunay condition 141 | 142 | long sinA = x01 * y03 - x03 * y01; 143 | long sinB = x12 * y23 + x32 * y12; 144 | 145 | long sl01 = x01 * x01 + y01 * y01; 146 | long sl03 = x03 * x03 + y03 * y03; 147 | long sl12 = x12 * x12 + y12 * y12; 148 | long sl23 = x32 * x32 + y23 * y23; 149 | 150 | double max0 = sl01 > sl03 ? sl01 : sl03; 151 | double max1 = sl12 > sl23 ? sl12 : sl23; 152 | 153 | double sinAB = ((double) sinA * (double) cosB + (double) cosA * (double) sinB) / (max0 * max1); 154 | 155 | return sinAB < 0.001; 156 | } 157 | 158 | public static bool IsCCW(IntVector a, IntVector b, IntVector c) { 159 | long m0 = (c.y - a.y) * (b.x - a.x); 160 | long m1 = (b.y - a.y) * (c.x - a.x); 161 | 162 | return m0 < m1; 163 | } 164 | 165 | } 166 | 167 | internal static class DelaunayExt { 168 | internal static bool Swap(this ref Delaunay delaunay, Triangle abc, Triangle pbc) { 169 | int ai = abc.Opposite(pbc.index); // opposite a-p 170 | int bi; // edge bc 171 | int ci; 172 | 173 | Vertex a, b, c; 174 | 175 | int acIndex; 176 | 177 | switch (ai) { 178 | case 0: 179 | bi = 1; 180 | ci = 2; 181 | a = abc.vA; 182 | b = abc.vB; 183 | c = abc.vC; 184 | 185 | acIndex = abc.nB; 186 | break; 187 | case 1: 188 | bi = 2; 189 | ci = 0; 190 | a = abc.vB; 191 | b = abc.vC; 192 | c = abc.vA; 193 | 194 | acIndex = abc.nC; 195 | break; 196 | default: 197 | bi = 0; 198 | ci = 1; 199 | a = abc.vC; 200 | b = abc.vA; 201 | c = abc.vB; 202 | 203 | acIndex = abc.nA; 204 | break; 205 | } 206 | 207 | var p = pbc.OppositeVertex(abc.index); 208 | 209 | bool isPrefect = Delaunay.IsDelaunay(p.point, c.point, a.point, b.point); 210 | 211 | if(isPrefect) { 212 | return false; 213 | } 214 | 215 | bool isABP_CCW = Delaunay.IsCCW(a.point, b.point, p.point); 216 | 217 | int bp = pbc.FindNeighbor(c.index); 218 | int cp = pbc.FindNeighbor(b.index); 219 | int ab = abc.Neighbor(ci); 220 | int ac = abc.Neighbor(bi); 221 | 222 | // abc -> abp 223 | Triangle abp; 224 | 225 | // pbc -> acp 226 | Triangle acp; 227 | 228 | if(isABP_CCW) { 229 | abp = new Triangle(abc.index, a, b, p) { 230 | nA = bp, // a - bp 231 | nB = pbc.index, // b - ap 232 | nC = ab // p - ab 233 | }; 234 | 235 | acp = new Triangle(pbc.index, a, p, c) { 236 | nA = cp, // a - cp 237 | nB = ac, // p - ac 238 | nC = abc.index // c - ap 239 | }; 240 | } else { 241 | abp = new Triangle(abc.index, a, p, b) { 242 | nA = bp, // a - bp 243 | nB = ab, // p - ab 244 | nC = pbc.index // b - ap 245 | }; 246 | 247 | acp = new Triangle(pbc.index, a, c, p) { 248 | nA = cp, // a - cp 249 | nB = abc.index, // c - ap 250 | nC = ac // p - ac 251 | }; 252 | } 253 | 254 | // fix neighbor's link 255 | // ab, cp didn't change neighbor 256 | // bc -> ap, so no changes 257 | 258 | // ac (abc) is now edge of acp 259 | // int acIndex = abc.GetNeighborByIndex(bi); // b - angle 260 | if(acIndex >= 0) { 261 | var neighbor = delaunay.triangles[acIndex]; 262 | neighbor.UpdateOpposite(abc.index, acp.index); 263 | delaunay.triangles[acIndex] = neighbor; 264 | } 265 | 266 | // bp (pbc) is now edge of abp 267 | int bpIndex = pbc.FindNeighbor(c.index); // c - angle 268 | if(bpIndex >= 0) { 269 | var neighbor = delaunay.triangles[bpIndex]; 270 | neighbor.UpdateOpposite(pbc.index, abp.index); 271 | delaunay.triangles[bpIndex] = neighbor; 272 | } 273 | 274 | delaunay.triangles[abc.index] = abp; 275 | delaunay.triangles[pbc.index] = acp; 276 | 277 | return true; 278 | } 279 | 280 | internal static void Fix(this ref Delaunay delaunay, ref IndexBuffer indexBuffer, NativeArray indices) { 281 | var origin = new NativeArray(indices, Allocator.Temp); 282 | var buffer = new DynamicArray(16, Allocator.Temp); 283 | 284 | while (origin.Length > 0) { 285 | buffer.RemoveAll(); 286 | for (int ii = 0; ii < origin.Length; ++ii) { 287 | int i = origin[ii]; 288 | var triangle = delaunay.triangles[i]; 289 | for (int k = 0; k <= 2; ++k) { 290 | int neighborIndex = triangle.Neighbor(k); 291 | if (neighborIndex >= 0) { 292 | var neighbor = delaunay.triangles[neighborIndex]; 293 | 294 | if (delaunay.Swap(triangle, neighbor)) { 295 | 296 | indexBuffer.Add(triangle.index); 297 | indexBuffer.Add(neighbor.index); 298 | 299 | triangle = delaunay.triangles[triangle.index]; 300 | neighbor = delaunay.triangles[neighbor.index]; 301 | 302 | if (triangle.nA >= 0 && triangle.nA != neighbor.index) { 303 | buffer.Add(triangle.nA); 304 | } 305 | if (triangle.nB >= 0 && triangle.nB != neighbor.index) { 306 | buffer.Add(triangle.nB); 307 | } 308 | if (triangle.nC >= 0 && triangle.nC != neighbor.index) { 309 | buffer.Add(triangle.nC); 310 | } 311 | 312 | if (neighbor.nA >= 0 && neighbor.nA != triangle.index) { 313 | buffer.Add(neighbor.nA); 314 | } 315 | if (neighbor.nB >= 0 && neighbor.nB != triangle.index) { 316 | buffer.Add(neighbor.nB); 317 | } 318 | if (neighbor.nC >= 0 && neighbor.nC != triangle.index) { 319 | buffer.Add(neighbor.nC); 320 | } 321 | } 322 | } 323 | } 324 | } 325 | 326 | origin.Dispose(); 327 | origin = buffer.ToArray(Allocator.Temp); 328 | } 329 | } 330 | } 331 | 332 | } 333 | -------------------------------------------------------------------------------- /Tests/Editor/Triangulation/ComplexDelaunayTests.cs: -------------------------------------------------------------------------------- 1 | using iShape.Geometry; 2 | using iShape.Geometry.Container; 3 | using iShape.Triangulation.Shape.Delaunay; 4 | using Tests.Triangulation.Util; 5 | using Tests.Triangulation.Data; 6 | using NUnit.Framework; 7 | using Unity.Collections; 8 | using UnityEngine; 9 | using Triangle = Tests.Triangulation.Util.Triangle; 10 | 11 | namespace Tests.Triangulation { 12 | 13 | public class ComplexDelaunayTests { 14 | 15 | private NativeArray Triangulate(int index) { 16 | var iGeom = IntGeom.DefGeom; 17 | 18 | var data = ComplexTests.data[index]; 19 | 20 | var hull = iGeom.Int(data[0]); 21 | var holes = new IntVector[data.Length - 1][]; 22 | for(int i = 1; i < data.Length; ++i) { 23 | holes[i - 1] = iGeom.Int(data[i]); 24 | } 25 | 26 | var iShape = new IntShape(hull, holes); 27 | var pShape = new PlainShape(iShape, Allocator.Temp); 28 | 29 | var triangles = pShape.DelaunayTriangulate(Allocator.Temp); 30 | 31 | Assert.IsTrue(Triangle.IsCCW(pShape.points, triangles)); 32 | 33 | pShape.Dispose(); 34 | 35 | return triangles; 36 | } 37 | 38 | [Test] 39 | public void TestTriangulate_00() { 40 | var triangles = this.Triangulate(0); 41 | var origin = new NativeArray(new int[] { 42 | 0, 1, 3, 43 | 1, 2, 3 44 | }, Allocator.Temp); 45 | 46 | bool isEqual = triangles.CompareTriangles(origin); 47 | Assert.IsTrue(isEqual); 48 | triangles.Dispose(); 49 | origin.Dispose(); 50 | } 51 | 52 | [Test] 53 | public void TestTriangulate_01() { 54 | var triangles = this.Triangulate(1); 55 | var origin = new NativeArray(new int[] { 56 | 1, 2, 0, 57 | 0, 2, 4, 58 | 2, 3, 4 59 | }, Allocator.Temp); 60 | 61 | bool isEqual = triangles.CompareTriangles(origin); 62 | Assert.IsTrue(isEqual); 63 | triangles.Dispose(); 64 | origin.Dispose(); 65 | } 66 | 67 | [Test] 68 | public void TestTriangulate_02() { 69 | var triangles = this.Triangulate(2); 70 | var origin = new NativeArray(new int[] { 71 | 0, 5, 6, 72 | 0, 6, 3, 73 | 6, 7, 3, 74 | 0, 1, 5, 75 | 1, 4, 5, 76 | 1, 7, 4, 77 | 1, 2, 7, 78 | 7, 2, 3 79 | }, Allocator.Temp); 80 | 81 | bool isEqual = triangles.CompareTriangles(origin); 82 | Assert.IsTrue(isEqual); 83 | triangles.Dispose(); 84 | origin.Dispose(); 85 | } 86 | 87 | [Test] 88 | public void TestTriangulate_03() { 89 | var triangles = this.Triangulate(3); 90 | var origin = new NativeArray(new int[] { 91 | 2, 3, 1, 92 | 0, 1, 3 93 | }, Allocator.Temp); 94 | 95 | bool isEqual = triangles.CompareTriangles(origin); 96 | Assert.IsTrue(isEqual); 97 | triangles.Dispose(); 98 | origin.Dispose(); 99 | } 100 | 101 | [Test] 102 | public void TestTriangulate_04() { 103 | var triangles = this.Triangulate(4); 104 | var origin = new NativeArray(new int[] { 105 | 1, 4, 0, 106 | 1, 2, 3, 107 | 1, 3, 4 108 | }, Allocator.Temp); 109 | 110 | bool isEqual = triangles.CompareTriangles(origin); 111 | Assert.IsTrue(isEqual); 112 | triangles.Dispose(); 113 | origin.Dispose(); 114 | } 115 | 116 | [Test] 117 | public void TestTriangulate_05() { 118 | var triangles = this.Triangulate(5); 119 | var origin = new NativeArray(new int[] { 120 | 1, 4, 0, 121 | 1, 2, 3, 122 | 1, 3, 4 123 | }, Allocator.Temp); 124 | 125 | bool isEqual = triangles.CompareTriangles(origin); 126 | Assert.IsTrue(isEqual); 127 | triangles.Dispose(); 128 | origin.Dispose(); 129 | } 130 | 131 | [Test] 132 | public void TestTriangulate_06() { 133 | var triangles = this.Triangulate(6); 134 | var origin = new NativeArray(new int[] { 135 | 0, 2, 3, 136 | 0, 1, 2 137 | }, Allocator.Temp); 138 | 139 | bool isEqual = triangles.CompareTriangles(origin); 140 | Assert.IsTrue(isEqual); 141 | triangles.Dispose(); 142 | origin.Dispose(); 143 | } 144 | 145 | [Test] 146 | public void TestTriangulate_07() { 147 | var triangles = this.Triangulate(7); 148 | var origin = new NativeArray(new int[] { 149 | 1, 7, 0, 150 | 7, 5, 6, 151 | 7, 1, 4, 152 | 3, 1, 2, 153 | 1, 3, 4, 154 | 7, 4, 5 155 | }, Allocator.Temp); 156 | 157 | bool isEqual = triangles.CompareTriangles(origin); 158 | Assert.IsTrue(isEqual); 159 | triangles.Dispose(); 160 | origin.Dispose(); 161 | } 162 | 163 | [Test] 164 | public void TestTriangulate_08() { 165 | var triangles = this.Triangulate(8); 166 | var origin = new NativeArray(new int[] { 167 | 7, 5, 6, 168 | 1, 2, 0, 169 | 7, 0, 2, 170 | 2, 3, 4, 171 | 2, 4, 7, 172 | 4, 5, 7 173 | }, Allocator.Temp); 174 | 175 | bool isEqual = triangles.CompareTriangles(origin); 176 | Assert.IsTrue(isEqual); 177 | triangles.Dispose(); 178 | origin.Dispose(); 179 | } 180 | 181 | [Test] 182 | public void TestTriangulate_09() { 183 | var triangles = this.Triangulate(9); 184 | var origin = new NativeArray(new int[] { 185 | 2, 0, 1, 186 | 3, 0, 2, 187 | 7, 5, 6, 188 | 0, 5, 7, 189 | 0, 3, 4, 190 | 0, 4, 5 191 | }, Allocator.Temp); 192 | 193 | bool isEqual = triangles.CompareTriangles(origin); 194 | Assert.IsTrue(isEqual); 195 | triangles.Dispose(); 196 | origin.Dispose(); 197 | } 198 | 199 | [Test] 200 | public void TestTriangulate_10() { 201 | var triangles = this.Triangulate(10); 202 | var origin = new NativeArray(new int[] { 203 | 7, 0, 6, 204 | 5, 6, 0, 205 | 4, 5, 0, 206 | 1, 2, 4, 207 | 1, 4, 0, 208 | 3, 4, 2 209 | }, Allocator.Temp); 210 | 211 | bool isEqual = triangles.CompareTriangles(origin); 212 | Assert.IsTrue(isEqual); 213 | triangles.Dispose(); 214 | origin.Dispose(); 215 | } 216 | 217 | [Test] 218 | public void TestTriangulate_11() { 219 | var triangles = this.Triangulate(11); 220 | var origin = new NativeArray(new int[] { 221 | 0, 1, 4, 222 | 0, 4, 7, 223 | 4, 5, 7, 224 | 7, 5, 6, 225 | 4, 1, 3, 226 | 1, 2, 3 227 | }, Allocator.Temp); 228 | 229 | bool isEqual = triangles.CompareTriangles(origin); 230 | Assert.IsTrue(isEqual); 231 | triangles.Dispose(); 232 | origin.Dispose(); 233 | } 234 | 235 | [Test] 236 | public void TestTriangulate_12() { 237 | var triangles = this.Triangulate(12); 238 | var origin = new NativeArray(new int[] { 239 | 0, 4, 7, 240 | 4, 5, 7, 241 | 6, 7, 5, 242 | 2, 3, 1, 243 | 0, 1, 4, 244 | 4, 1, 3 245 | }, Allocator.Temp); 246 | 247 | bool isEqual = triangles.CompareTriangles(origin); 248 | Assert.IsTrue(isEqual); 249 | triangles.Dispose(); 250 | origin.Dispose(); 251 | } 252 | 253 | [Test] 254 | public void TestTriangulate_13() { 255 | var triangles = this.Triangulate(13); 256 | var origin = new NativeArray(new int[] { 257 | 0, 1, 6, 258 | 0, 6, 11, 259 | 11, 9, 10, 260 | 7, 9, 11, 261 | 8, 9, 7, 262 | 7, 11, 6, 263 | 6, 1, 5, 264 | 1, 2, 3, 265 | 1, 3, 5, 266 | 5, 3, 4 267 | }, Allocator.Temp); 268 | 269 | bool isEqual = triangles.CompareTriangles(origin); 270 | Assert.IsTrue(isEqual); 271 | triangles.Dispose(); 272 | origin.Dispose(); 273 | } 274 | 275 | [Test] 276 | public void TestTriangulate_14() { 277 | var triangles = this.Triangulate(14); 278 | var origin = new NativeArray(new int[] { 279 | 9, 0, 1, 280 | 4, 9, 1, 281 | 8, 9, 5, 282 | 6, 7, 5, 283 | 8, 5, 7, 284 | 4, 5, 9, 285 | 1, 2, 3, 286 | 1, 3, 4 287 | }, Allocator.Temp); 288 | 289 | bool isEqual = triangles.CompareTriangles(origin); 290 | Assert.IsTrue(isEqual); 291 | triangles.Dispose(); 292 | origin.Dispose(); 293 | } 294 | 295 | [Test] 296 | public void TestTriangulate_15() { 297 | var triangles = this.Triangulate(15); 298 | var origin = new NativeArray(new int[] { 299 | 4, 2, 3, 300 | 4, 0, 2, 301 | 0, 1, 2 302 | }, Allocator.Temp); 303 | 304 | bool isEqual = triangles.CompareTriangles(origin); 305 | Assert.IsTrue(isEqual); 306 | triangles.Dispose(); 307 | origin.Dispose(); 308 | } 309 | 310 | [Test] 311 | public void TestTriangulate_16() { 312 | var triangles = this.Triangulate(16); 313 | var origin = new NativeArray(new int[] { 314 | 33, 1, 2, 315 | 20, 21, 0, 316 | 0, 35, 20, 317 | 27, 28, 16, 318 | 27, 16, 26, 319 | 29, 16, 28, 320 | 19, 16, 29, 321 | 15, 22, 14, 322 | 13, 14, 22, 323 | 23, 13, 22, 324 | 13, 23, 24, 325 | 25, 13, 24, 326 | 25, 12, 13, 327 | 25, 26, 10, 328 | 11, 12, 25, 329 | 16, 17, 26, 330 | 26, 17, 10, 331 | 25, 10, 11, 332 | 9, 10, 17, 333 | 18, 9, 17, 334 | 8, 9, 18, 335 | 21, 22, 15, 336 | 0, 21, 15, 337 | 1, 34, 0, 338 | 0, 34, 35, 339 | 33, 34, 1, 340 | 33, 2, 3, 341 | 33, 3, 32, 342 | 31, 32, 3, 343 | 30, 31, 3, 344 | 4, 30, 3, 345 | 5, 30, 4, 346 | 29, 30, 5, 347 | 29, 5, 19, 348 | 19, 5, 6, 349 | 19, 6, 18, 350 | 18, 6, 7, 351 | 18, 7, 8 352 | }, Allocator.Temp); 353 | 354 | bool isEqual = triangles.CompareTriangles(origin); 355 | Assert.IsTrue(isEqual); 356 | triangles.Dispose(); 357 | origin.Dispose(); 358 | } 359 | 360 | [Test] 361 | public void TestTriangulate_17() { 362 | var triangles = this.Triangulate(17); 363 | var origin = new NativeArray(new int[] { 364 | 6, 7, 8, 365 | 6, 8, 5, 366 | 0, 8, 7, 367 | 11, 8, 0, 368 | 1, 11, 0, 369 | 1, 10, 11, 370 | 8, 9, 5, 371 | 4, 5, 9, 372 | 10, 4, 9, 373 | 2, 10, 1, 374 | 10, 3, 4, 375 | 2, 3, 10 376 | }, Allocator.Temp); 377 | 378 | bool isEqual = triangles.CompareTriangles(origin); 379 | Assert.IsTrue(isEqual); 380 | triangles.Dispose(); 381 | origin.Dispose(); 382 | } 383 | 384 | 385 | [Test] 386 | public void TestTriangulate_18() 387 | { 388 | var triangles = this.Triangulate(18); 389 | var origin = new NativeArray(new int[] { 390 | 8, 4, 0, 391 | 4, 5, 3, 392 | 6, 3, 5, 393 | 4, 3, 0, 394 | 8, 0, 11, 395 | 0, 1, 11, 396 | 11, 1, 10, 397 | 10, 1, 2, 398 | 3, 6, 2, 399 | 10, 2, 6 400 | }, Allocator.Temp); 401 | 402 | bool isEqual = triangles.CompareTriangles(origin); 403 | Assert.IsTrue(isEqual); 404 | triangles.Dispose(); 405 | origin.Dispose(); 406 | } 407 | 408 | [Test] 409 | public void TestTriangulate_19() 410 | { 411 | var triangles = this.Triangulate(19); 412 | var origin = new NativeArray(new int[] { 413 | 6, 8, 9, 414 | 8, 6, 7, 415 | 6, 9, 5, 416 | 1, 2, 4, 417 | 1, 4, 5, 418 | 4, 2, 3 419 | }, Allocator.Temp); 420 | 421 | bool isEqual = triangles.CompareTriangles(origin); 422 | Assert.IsTrue(isEqual); 423 | triangles.Dispose(); 424 | origin.Dispose(); 425 | } 426 | 427 | [Test] 428 | public void TestTriangulate_20() 429 | { 430 | var triangles = this.Triangulate(20); 431 | var origin = new NativeArray(new int[] { 432 | 1, 3, 4, 433 | 3, 1, 2, 434 | 1, 4, 0, 435 | 6, 7, 9, 436 | 6, 9, 0, 437 | 9, 7, 8 438 | }, Allocator.Temp); 439 | 440 | bool isEqual = triangles.CompareTriangles(origin); 441 | Assert.IsTrue(isEqual); 442 | triangles.Dispose(); 443 | origin.Dispose(); 444 | } 445 | 446 | [Test] 447 | public void TestTriangulate_21() 448 | { 449 | var triangles = this.Triangulate(21); 450 | var origin = new NativeArray(new int[] { 451 | 4, 5, 0, 452 | 8, 9, 5, 453 | 5, 9, 0, 454 | 10, 3, 9, 455 | 9, 3, 0, 456 | 0, 1, 4, 457 | 4, 1, 7, 458 | 8, 7, 11, 459 | 7, 1, 11, 460 | 3, 10, 2, 461 | 10, 11, 2, 462 | 11, 1, 2 463 | }, Allocator.Temp); 464 | 465 | bool isEqual = triangles.CompareTriangles(origin); 466 | Assert.IsTrue(isEqual); 467 | triangles.Dispose(); 468 | origin.Dispose(); 469 | } 470 | 471 | [Test] 472 | public void TestTriangulate_22() 473 | { 474 | var triangles = this.Triangulate(22); 475 | var origin = new NativeArray(new int[] { 476 | 4, 5, 8, 477 | 10, 5, 6, 478 | 8, 9, 3, 479 | 6, 2, 10, 480 | 10, 2, 9, 481 | 9, 2, 3, 482 | 3, 0, 8, 483 | 8, 0, 4, 484 | 4, 0, 7, 485 | 2, 6, 1, 486 | 6, 7, 1, 487 | 7, 0, 1 488 | }, Allocator.Temp); 489 | 490 | bool isEqual = triangles.CompareTriangles(origin); 491 | Assert.IsTrue(isEqual); 492 | triangles.Dispose(); 493 | origin.Dispose(); 494 | } 495 | 496 | [Test] 497 | public void TestTriangulate_23() 498 | { 499 | var triangles = this.Triangulate(23); 500 | var origin = new NativeArray(new int[] { 501 | 8, 7, 4, 502 | 6, 7, 10, 503 | 4, 5, 3, 504 | 10, 2, 6, 505 | 6, 2, 5, 506 | 5, 2, 3, 507 | 3, 0, 4, 508 | 4, 0, 8, 509 | 8, 0, 11, 510 | 2, 10, 1, 511 | 10, 11, 1, 512 | 11, 0, 1 513 | }, Allocator.Temp); 514 | 515 | bool isEqual = triangles.CompareTriangles(origin); 516 | Assert.IsTrue(isEqual); 517 | triangles.Dispose(); 518 | origin.Dispose(); 519 | } 520 | 521 | [Test] 522 | public void TestTriangulate_24() 523 | { 524 | var triangles = this.Triangulate(24); 525 | var origin = new NativeArray(new int[] { 526 | 6, 0, 7, 527 | 0, 1, 7, 528 | 1, 2, 7, 529 | 9, 2, 3, 530 | 7, 8, 6, 531 | 3, 4, 9, 532 | 4, 5, 9, 533 | 9, 5, 8, 534 | 8, 5, 6 535 | }, Allocator.Temp); 536 | 537 | bool isEqual = triangles.CompareTriangles(origin); 538 | Assert.IsTrue(isEqual); 539 | triangles.Dispose(); 540 | origin.Dispose(); 541 | } 542 | 543 | [Test] 544 | public void TestTriangulate_25() 545 | { 546 | var triangles = this.Triangulate(25); 547 | var origin = new NativeArray(new int[] { 548 | 7, 4, 5, 549 | 4, 9, 3, 550 | 2, 3, 9, 551 | 6, 0, 7, 552 | 6, 7, 5, 553 | 7, 0, 10, 554 | 2, 9, 1, 555 | 9, 10, 1, 556 | 10, 0, 1 557 | }, Allocator.Temp); 558 | 559 | bool isEqual = triangles.CompareTriangles(origin); 560 | Assert.IsTrue(isEqual); 561 | triangles.Dispose(); 562 | origin.Dispose(); 563 | } 564 | 565 | [Test] 566 | public void TestTriangulate_26() 567 | { 568 | var triangles = this.Triangulate(26); 569 | var origin = new NativeArray(new int[] { 570 | 9, 0, 10, 571 | 9, 10, 8, 572 | 0, 1, 10, 573 | 1, 2, 10, 574 | 12, 2, 3, 575 | 10, 7, 8, 576 | 7, 12, 6, 577 | 4, 12, 3, 578 | 12, 5, 6, 579 | 4, 5, 12 580 | }, Allocator.Temp); 581 | 582 | bool isEqual = triangles.CompareTriangles(origin); 583 | Assert.IsTrue(isEqual); 584 | triangles.Dispose(); 585 | origin.Dispose(); 586 | } 587 | 588 | [Test] 589 | public void TestTriangulate_27() 590 | { 591 | var triangles = this.Triangulate(27); 592 | var origin = new NativeArray(new int[] { 593 | 6, 0, 2, 594 | 2, 0, 1, 595 | 2, 3, 6, 596 | 6, 3, 5, 597 | 5, 3, 1 598 | }, Allocator.Temp); 599 | 600 | bool isEqual = triangles.CompareTriangles(origin); 601 | Assert.IsTrue(isEqual); 602 | triangles.Dispose(); 603 | origin.Dispose(); 604 | } 605 | 606 | [Test] 607 | public void TestTriangulate_28() 608 | { 609 | var triangles = this.Triangulate(28); 610 | var origin = new NativeArray(new int[] { 611 | 4, 5, 3, 612 | 5, 6, 3, 613 | 3, 6, 2, 614 | 4, 2, 0, 615 | 2, 6, 0 616 | }, Allocator.Temp); 617 | 618 | bool isEqual = triangles.CompareTriangles(origin); 619 | Assert.IsTrue(isEqual); 620 | triangles.Dispose(); 621 | origin.Dispose(); 622 | } 623 | 624 | [Test] 625 | public void TestTriangulate_29() 626 | { 627 | var triangles = this.Triangulate(29); 628 | var origin = new NativeArray(new int[] { 629 | 1, 2, 3, 630 | 2, 6, 4, 631 | 0, 4, 6, 632 | 0, 3, 4, 633 | 3, 0, 1 634 | }, Allocator.Temp); 635 | 636 | bool isEqual = triangles.CompareTriangles(origin); 637 | Assert.IsTrue(isEqual); 638 | triangles.Dispose(); 639 | origin.Dispose(); 640 | } 641 | 642 | [Test] 643 | public void TestTriangulate_30() 644 | { 645 | var triangles = this.Triangulate(30); 646 | var origin = new NativeArray(new int[] { 647 | 3, 1, 6, 648 | 1, 2, 5, 649 | 1, 5, 6, 650 | 5, 2, 4, 651 | 2, 3, 4 652 | }, Allocator.Temp); 653 | 654 | bool isEqual = triangles.CompareTriangles(origin); 655 | Assert.IsTrue(isEqual); 656 | triangles.Dispose(); 657 | origin.Dispose(); 658 | } 659 | 660 | [Test] 661 | public void TestTriangulate_33() 662 | { 663 | var triangles = this.Triangulate(33); 664 | var origin = new NativeArray(new int[] { 665 | 2, 4, 6, 666 | 6, 4, 5, 667 | 2, 3, 4, 668 | 6, 0, 2, 669 | 2, 0, 1 670 | }, Allocator.Temp); 671 | 672 | bool isEqual = triangles.CompareTriangles(origin); 673 | Assert.IsTrue(isEqual); 674 | triangles.Dispose(); 675 | origin.Dispose(); 676 | } 677 | 678 | [Test] 679 | public void TestTriangulate_34() 680 | { 681 | var triangles = this.Triangulate(34); 682 | var origin = new NativeArray(new int[] { 683 | 16, 17, 15, 684 | 21, 22, 20, 685 | 20, 22, 19, 686 | 22, 23, 19, 687 | 23, 24, 19, 688 | 19, 24, 18, 689 | 17, 18, 25, 690 | 24, 25, 18, 691 | 17, 25, 15, 692 | 12, 13, 11, 693 | 6, 8, 10, 694 | 10, 8, 9, 695 | 6, 7, 8, 696 | 26, 0, 1, 697 | 1, 2, 3, 698 | 14, 15, 13, 699 | 10, 15, 25, 700 | 10, 13, 15, 701 | 13, 10, 11, 702 | 10, 25, 6, 703 | 25, 26, 6, 704 | 26, 1, 3, 705 | 26, 3, 6, 706 | 6, 3, 5, 707 | 3, 4, 5 708 | }, Allocator.Temp); 709 | 710 | bool isEqual = triangles.CompareTriangles(origin); 711 | Assert.IsTrue(isEqual); 712 | triangles.Dispose(); 713 | origin.Dispose(); 714 | } 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /Readme/star_triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 43 | 45 | 47 | 48 | 50 | image/svg+xml 51 | 53 | 54 | 55 | 56 | 57 | 60 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 176 | 181 | 186 | 191 | 196 | 201 | 206 | 211 | 216 | 221 | 226 | 231 | 236 | 241 | 246 | 251 | 256 | 261 | 266 | 271 | 276 | 281 | 286 | 291 | 296 | 301 | 306 | 311 | 316 | 321 | 326 | 331 | 336 | 341 | 346 | 351 | 356 | 361 | 366 | 371 | 376 | 381 | 386 | 387 | 388 | --------------------------------------------------------------------------------