├── example ├── tools │ ├── css │ │ └── index.css │ ├── polygon.html │ └── js │ │ ├── common.js │ │ ├── polygons.js │ │ └── data.js ├── shape.data ├── CMakeLists.txt └── src │ └── demo.cc ├── UNLICENSE ├── README.md └── include └── polytri ├── polygon_triangulation.inl ├── monotone_partitioning.inl ├── polytri.hpp └── trapezoidal_decomposition.inl /example/tools/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | @import url(https://fonts.googleapis.com/css?family=Quicksand); 3 | 4 | body { 5 | color: #757575; 6 | font-family: sans-serif; 7 | 8 | background-color: #252525; 9 | } 10 | 11 | canvas { 12 | background-color: #959592; 13 | } 14 | 15 | .about { 16 | padding: 32px; 17 | } 18 | 19 | .font-qs { 20 | font-family: 'Quicksand'; 21 | font-size: 24px; 22 | } -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /example/shape.data: -------------------------------------------------------------------------------- 1 | 1 2 | 3 | 108 4 | 372 278 5 | 334 302 6 | 318 323 7 | 324 363 8 | 359 383 9 | 383 365 10 | 355 347 11 | 354 322 12 | 399 303 13 | 415 336 14 | 422 372 15 | 380 405 16 | 395 435 17 | 449 448 18 | 481 427 19 | 478 381 20 | 461 340 21 | 512 338 22 | 517 376 23 | 513 424 24 | 540 432 25 | 566 424 26 | 577 394 27 | 548 349 28 | 530 305 29 | 558 261 30 | 563 228 31 | 470 241 32 | 492 270 33 | 481 298 34 | 437 309 35 | 431 267 36 | 431 224 37 | 524 177 38 | 572 170 39 | 579 117 40 | 546 56 41 | 440 38 42 | 397 38 43 | 392 49 44 | 491 67 45 | 517 86 46 | 530 118 47 | 490 131 48 | 466 120 49 | 444 104 50 | 415 154 51 | 398 199 52 | 398 249 53 | 381 269 54 | 365 248 55 | 339 194 56 | 366 135 57 | 347 98 58 | 290 69 59 | 260 47 60 | 228 40 61 | 212 51 62 | 231 69 63 | 274 78 64 | 317 113 65 | 322 137 66 | 311 166 67 | 277 190 68 | 246 163 69 | 212 124 70 | 164 111 71 | 122 107 72 | 119 117 73 | 94 138 74 | 94 178 75 | 92 214 76 | 90 246 77 | 112 269 78 | 132 248 79 | 125 211 80 | 152 182 81 | 189 178 82 | 231 196 83 | 283 226 84 | 324 251 85 | 300 263 86 | 258 285 87 | 202 303 88 | 169 311 89 | 130 314 90 | 95 332 91 | 88 377 92 | 88 401 93 | 119 423 94 | 177 436 95 | 188 448 96 | 194 445 97 | 165 398 98 | 130 392 99 | 134 355 100 | 190 336 101 | 227 337 102 | 266 345 103 | 277 402 104 | 293 422 105 | 314 396 106 | 282 349 107 | 287 336 108 | 290 310 109 | 313 292 110 | 334 273 111 | 351 261 -------------------------------------------------------------------------------- /example/tools/polygon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | Polygons 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 |
24 |
25 |

Create / Display polygons

26 |

Create polygons by clicking in clockwise-order on the canvas with 'contour' enabled.
27 | Click Export to display the point list in the console.

28 |

Display polygons by reloading the page after running PolyTri on an example.

29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12.0) 2 | 3 | project(polytri VERSION 0.1 LANGUAGES CXX) 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | # ----------------------------------------------------------------------------- 7 | 8 | set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../include) 9 | set(SOURCE_DIR ${CMAKE_SOURCE_DIR}/src) 10 | 11 | # ----------------------------------------------------------------------------- 12 | 13 | if( (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 14 | OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) 15 | list(APPEND CXX_FLAGS 16 | -Wall -Wextra -Wshadow -Wpedantic 17 | -Wnon-virtual-dtor -Woverloaded-virtual 18 | -Wuseless-cast -Wcast-align 19 | #-Wold-style-cast 20 | -Wlogical-op 21 | -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers 22 | -fvisibility=hidden -fno-strict-aliasing -fno-builtin-memcmp 23 | ) 24 | list(APPEND CXX_FLAGS_RELEASE -O2) 25 | list(APPEND CXX_FLAGS_DEBUG -g -O0 -Wconversion) 26 | else() 27 | message(WARNING "Compiler not tested") 28 | endif() 29 | 30 | # ----------------------------------------------------------------------------- 31 | 32 | # add_library( 33 | # ${PROJECT_NAME} 34 | # STATIC 35 | # ${SOURCE_DIR}/demo.cc 36 | # ) 37 | 38 | # target_compile_options(${PROJECT_NAME} PRIVATE 39 | # "${CXX_FLAGS}" 40 | # "$<$:${CXX_FLAGS_DEBUG}>" 41 | # "$<$:${CXX_FLAGS_RELEASE}>" 42 | # ) 43 | # target_include_directories(${PROJECT_NAME} PRIVATE ${SOURCE_DIR}) 44 | 45 | # ----------------------------------------------------------------------------- 46 | 47 | set(TargetName polytri-demo) 48 | 49 | add_executable(${TargetName} ${SOURCE_DIR}/demo.cc) 50 | 51 | target_include_directories(${TargetName} PRIVATE ${INCLUDE_DIR}) 52 | # target_link_libraries(${TargetName} ${PROJECT_NAME}) 53 | 54 | target_compile_definitions(${TargetName} 55 | PRIVATE 56 | -DAPP_DIRECTORY="${CMAKE_CURRENT_LIST_DIR}/" 57 | ) 58 | 59 | # ----------------------------------------------------------------------------- 60 | -------------------------------------------------------------------------------- /example/tools/js/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function TPoint(x, y) { 4 | this.x = x; 5 | this.y = y; 6 | 7 | this.equals = function(o) { 8 | return (o instanceof TPoint) 9 | && (this.x == o.x) 10 | && (this.y == o.y); 11 | } 12 | 13 | // return the dot product of instance with parameter 14 | this.dot = function(p) { 15 | return this.x * p.x + this.y * p.y; 16 | } 17 | 18 | this.add = function(p) { 19 | this.x += p.x; 20 | this.y += p.y; 21 | return this; 22 | } 23 | 24 | this.mult = function(s) { 25 | this.x *= s; 26 | this.y *= s; 27 | return this; 28 | } 29 | 30 | // return the point relative to another (its vector) 31 | this.relativeTo = function(o) { 32 | return new TPoint(this.x - o.x, this.y - o.y); 33 | } 34 | 35 | this.asArray = function() { 36 | return [this.x, this.y]; 37 | } 38 | 39 | // return the squared distance to a point 40 | this.squaredDistanceTo = function(p) { 41 | var v = this.relativeTo(p); 42 | return v.dot(v); 43 | } 44 | 45 | this.getNormal = function() { 46 | var n = new TPoint(-this.y, this.x); 47 | var dp = n.dot(n); 48 | if (dp > 0.0) { 49 | var invLen = Math.sqrt(1.0/dp); 50 | n.x *= invLen; 51 | n.y *= invLen; 52 | } 53 | return n; 54 | } 55 | } 56 | 57 | /* -------------------------------------------------------------------------- */ 58 | 59 | var CanvasView = { 60 | init: function(pointRadius) { 61 | this.canvas = document.getElementById('canvas-app'); 62 | this.ctx = this.canvas.getContext('2d'); 63 | 64 | this.lineColor = '#457590'; 65 | this.pointColor = '#953030'; 66 | this.pointRadius = (pointRadius == undefined) ? 3 : pointRadius; 67 | 68 | this.ctx.fillStyle = this.pointColor; 69 | this.ctx.strokeStyle = this.lineColor; 70 | this.ctx.lineWidth = 1; 71 | }, 72 | 73 | clear: function() { 74 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 75 | }, 76 | 77 | drawLine: function(a, b) { 78 | this.ctx.beginPath(); 79 | this.ctx.moveTo(a.x, a.y); 80 | this.ctx.lineTo(b.x, b.y); 81 | this.ctx.stroke(); 82 | }, 83 | 84 | drawPoint: function(p) { 85 | this.ctx.beginPath(); 86 | this.ctx.arc(p.x, p.y, this.pointRadius, 0.0, 2.0*Math.PI); 87 | this.ctx.fill(); 88 | }, 89 | 90 | drawPoints: function(points) { 91 | points.map(this.drawPoint, this); 92 | }, 93 | 94 | setPointColor: function(color) { 95 | this.ctx.fillStyle = color; 96 | }, 97 | 98 | setLineWidth: function(width) { 99 | this.ctx.lineWidth = width; 100 | }, 101 | }; 102 | -------------------------------------------------------------------------------- /example/tools/js/polygons.js: -------------------------------------------------------------------------------- 1 | 2 | var show_contour = true; 3 | 4 | /* -------------------------------------------------------------------------- */ 5 | 6 | var Data = { 7 | init: function() { 8 | this.points = vertices.map(pt => new TPoint(pt.x, CanvasView.canvas.height - pt.y)); 9 | }, 10 | 11 | // clear the point dataset 12 | clearPoints: function() { 13 | this.points = []; 14 | } 15 | }; 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | 19 | var App = { 20 | // Initialize the applicaton. 21 | init: function() { 22 | CanvasView.init(); 23 | CanvasView.canvas.addEventListener('mousedown', function(event) { 24 | var x = event.pageX - CanvasView.canvas.offsetLeft, 25 | y = event.pageY - CanvasView.canvas.offsetTop; 26 | 27 | Data.points.push(new TPoint(x, y)); 28 | }, false); 29 | 30 | CanvasView.setPointColor(CanvasView.pointColor); 31 | 32 | Data.init(); 33 | 34 | App.update(); 35 | }, 36 | 37 | // Reset data pointset and rendering. 38 | reset: function() { 39 | // Generate new datapoints. 40 | Data.clearPoints(); 41 | TRI = []; 42 | }, 43 | 44 | render: function() { 45 | CanvasView.clear(); 46 | 47 | if (Data.points.length <= 0) { 48 | return; 49 | } 50 | 51 | // render triangles 52 | TRI.forEach((e) => { 53 | var A = Data.points[e[0]]; 54 | var B = Data.points[e[1]]; 55 | var C = Data.points[e[2]]; 56 | CanvasView.drawLine(A, B); 57 | CanvasView.drawLine(B, C); 58 | CanvasView.drawLine(C, A); 59 | }); 60 | 61 | if (show_contour) { 62 | // draw lines 63 | var vertices = [Data.points[Data.points.length-1]] 64 | vertices.push.apply(vertices, Data.points); 65 | 66 | vertices.reduce((a, b) => { 67 | var c = CanvasView.lineColor; 68 | CanvasView.ctx.strokeStyle = '#C33'; 69 | CanvasView.drawLine(a, b); 70 | CanvasView.ctx.strokeStyle = c; 71 | return b; 72 | }); 73 | } 74 | 75 | CanvasView.setPointColor('#00ff00'); 76 | CanvasView.drawPoints(Data.points); 77 | }, 78 | 79 | export: function() { 80 | var s = ""; 81 | s += Data.points.length + "\n"; 82 | for (var i in Data.points) { 83 | var x = Data.points[i].x;// / CanvasView.canvas.width; 84 | var y = CanvasView.canvas.height - Data.points[i].y;// / CanvasView.canvas.height; 85 | s += x + " " + y + "\n"; 86 | } 87 | 88 | console.log(s); 89 | }, 90 | 91 | contour: function() { 92 | show_contour = !show_contour; 93 | }, 94 | 95 | update: function() { 96 | App.render(); 97 | window.requestAnimationFrame(App.update); 98 | } 99 | }; 100 | 101 | /* -------------------------------------------------------------------------- */ 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![unlicense](https://img.shields.io/badge/Unlicense-%23373737)](https://unlicense.org/) 2 | ![language: c++17](https://img.shields.io/badge/c++-17-blue.svg) 3 | ![Stage: alpha](https://img.shields.io/badge/-alpha-red) 4 | 5 | # polytri 6 | 7 | _**polytri**_ is a polygon triangulator for [simple polygons](https://en.wikipedia.org/wiki/Simple_polygon) _without holes_ based on Raimund Seidel's algorithm [1]. 8 | 9 | 10 | [![comparison.webp](https://i.postimg.cc/QCds9qkX/comparison.webp)](https://postimg.cc/cg2PPwbj) 11 | 12 | 27 | 28 | #### Usage 29 | 30 | ```cpp 31 | /* Define this in a single compilation unit. */ 32 | #define POLYTRI_IMPLEMENTATION 33 | #include "polytri/polytri.hpp" 34 | 35 | /* Any type goes if they have public x / y attributes. */ 36 | template 37 | struct Vertex_t { 38 | T x{}; 39 | T y{}; 40 | }; 41 | 42 | /* The vertices list is expect to be a Container-type. */ 43 | template 44 | using Contour_t = std::vector>; 45 | 46 | int main(int argc, char *argv[]) 47 | { 48 | /* Contour must be in counter clockwise order. */ 49 | std::vector> polygons = { 50 | { {-10.0, -9.0}, {11.0, -12.0}, {0.0, 8.0}, {-5.0, 11.0} } 51 | }; 52 | 53 | auto indices = PolyTri::Triangulate( polygons ); 54 | 55 | for (auto const& i : indices) { 56 | std::cerr << i << " "; 57 | } 58 | std::cerr << "\n"; 59 | 60 | return 0; 61 | } 62 | 63 | ``` 64 | 65 | #### Limitations 66 | 67 | Triangulating polygons with _holes_ is technically possible but not currently supported. You can check those libraries for alternatives : 68 | 69 | - [Triangle](http://www.cs.cmu.edu/~quake/triangle.html), A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator. 70 | - [mapbox/earcut.hpp](https://github.com/mapbox/earcut.hpp), A C++ port of earcut.js, a fast, header-only polygon triangulation library. 71 | 72 | --- 73 | 74 | ### References 75 | 76 | 1. R. Seidel. [*A simple and fast incremental randomized algorithm for computing trapezoidal decompositions and for triangulating polygons*](https://www.sciencedirect.com/science/article/pii/0925772191900124?via%3Dihub). Comput. Geom. Theory Appl., 1:51–64, 1991. 77 | 2. A. Narkhede and D. Manocha, [*Fast Polygon Triangulation Based on Seidel's Algorithm*](https://www.cs.unc.edu/~dm/CODE/GEM/chapter.html), Department of Computer Science, UNC Chapel Hill. 78 | 3. Fournier, Alain & Montuno, Delfin. (1984). [*Triangulating Simple Polygons and Equivalent Problems*](https://dl.acm.org/doi/10.1145/357337.357341). ACM Trans. Graph.. 3. 153-174. 10.1145/357337.357341. 79 | 4. M. de Berg, O. Cheong, M. van Kreveld, M. Overmars. [*Computational Geometry, Algorithms and Applications, Third Edition*](https://link.springer.com/book/10.1007/978-3-540-77974-2), Chap. 3 Polygon Triangulation, Springer, 2008. 80 | 81 | -------------------------------------------------------------------------------- /example/src/demo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define POLYTRI_IMPLEMENTATION 6 | #include "polytri/polytri.hpp" 7 | 8 | /* -------------------------------------------------------------------------- */ 9 | 10 | namespace { 11 | 12 | /** Read a data file to retrieve contour vertices */ 13 | int LoadSegments( 14 | char *const filename, 15 | std::vector &contour_lengths, 16 | std::vector &vertices 17 | ) { 18 | std::ifstream fd(filename); 19 | 20 | if (fd.fail()) { 21 | std::cerr << "Error : can't read the file." << std::endl; 22 | return EXIT_FAILURE; 23 | } 24 | 25 | unsigned int ncontours; 26 | fd >> ncontours; 27 | 28 | if (ncontours == 0u) { 29 | std::cerr << "Invalid contour count : " << ncontours << std::endl; 30 | return EXIT_FAILURE; 31 | } 32 | 33 | for (auto cid = 0u; cid < ncontours; ++cid) { 34 | unsigned int npoints; 35 | fd >> npoints; 36 | contour_lengths.push_back(npoints); 37 | 38 | for (auto i = 0u; i < npoints; ++i) { 39 | PolyTri::vertex_t v; 40 | fd >> v.x >> v.y; 41 | vertices.push_back(v); 42 | } 43 | } 44 | 45 | return EXIT_SUCCESS; 46 | } 47 | 48 | void ExportData( 49 | PolyTri::TriangleBuffer_t &triangles, 50 | std::vector &vertices 51 | ) { 52 | const char* filename = APP_DIRECTORY "tools/js/data.js"; 53 | 54 | std::ofstream fd(filename); 55 | if (fd.fail()) { 56 | std::cerr << "Error : cannot locate the file \"" << filename << "\"" << std::endl; 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | fd << "var TRI = [" << std::endl; 61 | 62 | for (auto &t : triangles) { 63 | fd << " [ " 64 | << t.v0 << ", " 65 | << t.v1 << ", " 66 | << t.v2 << "]," << std::endl; 67 | } 68 | fd << "];" << std::endl; 69 | fd << std::endl; 70 | 71 | fd << "var vertices = [" << std::endl; 72 | for (auto i = 0u; i < vertices.size(); ++i) { 73 | auto &v = vertices[i]; 74 | fd << " new TPoint( " << v.x << ", " << v.y << ")," << std::endl; 75 | } 76 | fd << "];" << std::endl; 77 | } 78 | 79 | } 80 | 81 | /* -------------------------------------------------------------------------- */ 82 | 83 | int main(int argc, char *argv[]) 84 | { 85 | #if 0 86 | if (argc < 2) { 87 | std::cerr << "usage : " << argv[0] << " filename." << std::endl; 88 | return EXIT_FAILURE; 89 | } 90 | 91 | std::vector contour_lengths; 92 | std::vector vertices; 93 | if (LoadSegments(argv[1u], contour_lengths, vertices)) { 94 | std::cerr << "error while reading the data file." << std::endl; 95 | return EXIT_FAILURE; 96 | } 97 | 98 | PolyTri::TriangleBuffer_t triangles; 99 | PolyTri::Triangulate( 100 | contour_lengths.size(), 101 | contour_lengths.data(), 102 | vertices.data(), 103 | triangles 104 | ); 105 | 106 | ExportData(triangles, vertices); 107 | #else 108 | // Polygons vertices must be in counter clockwise order. 109 | std::vector> vertices = { 110 | { 111 | {-10.0, -9.0}, {11.0, -12.0}, {0.0, 8.0}, {-5.0, 11.0} 112 | } 113 | }; 114 | 115 | auto indices = PolyTri::Triangulate( vertices ); 116 | 117 | fprintf(stderr, "Results (indices count %u) : ", (uint32_t)indices.size()); 118 | for (auto const& i : indices) { 119 | fprintf(stderr, "%u ", i); 120 | } 121 | fprintf(stderr, "\n"); 122 | #endif 123 | 124 | return EXIT_SUCCESS; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /example/tools/js/data.js: -------------------------------------------------------------------------------- 1 | var TRI = [ 2 | [ 4, 5, 3], 3 | [ 6, 3, 5], 4 | [ 3, 6, 2], 5 | [ 7, 2, 6], 6 | [ 2, 7, 8], 7 | [ 2, 8, 1], 8 | [ 31, 49, 0], 9 | [ 31, 0, 1], 10 | [ 31, 1, 8], 11 | [ 31, 8, 30], 12 | [ 0, 49, 107], 13 | [ 50, 80, 107], 14 | [ 50, 107, 49], 15 | [ 81, 107, 80], 16 | [ 107, 81, 106], 17 | [ 82, 106, 81], 18 | [ 106, 82, 105], 19 | [ 83, 105, 82], 20 | [ 105, 83, 104], 21 | [ 84, 104, 83], 22 | [ 104, 84, 85], 23 | [ 104, 85, 86], 24 | [ 104, 86, 96], 25 | [ 104, 96, 103], 26 | [ 95, 96, 86], 27 | [ 87, 95, 86], 28 | [ 95, 87, 94], 29 | [ 88, 93, 94], 30 | [ 88, 94, 87], 31 | [ 93, 88, 89], 32 | [ 93, 89, 90], 33 | [ 93, 90, 92], 34 | [ 91, 92, 90], 35 | [ 97, 103, 96], 36 | [ 103, 97, 98], 37 | [ 103, 98, 102], 38 | [ 99, 101, 102], 39 | [ 99, 102, 98], 40 | [ 101, 99, 100], 41 | [ 80, 50, 79], 42 | [ 51, 78, 79], 43 | [ 51, 79, 50], 44 | [ 78, 51, 63], 45 | [ 78, 63, 77], 46 | [ 52, 61, 62], 47 | [ 52, 62, 63], 48 | [ 52, 63, 51], 49 | [ 61, 52, 60], 50 | [ 53, 60, 52], 51 | [ 60, 53, 59], 52 | [ 54, 59, 53], 53 | [ 59, 54, 58], 54 | [ 55, 57, 58], 55 | [ 55, 58, 54], 56 | [ 57, 55, 56], 57 | [ 64, 70, 77], 58 | [ 64, 77, 63], 59 | [ 71, 75, 76], 60 | [ 76, 77, 70], 61 | [ 71, 76, 70], 62 | [ 75, 71, 72], 63 | [ 75, 72, 74], 64 | [ 73, 74, 72], 65 | [ 70, 64, 69], 66 | [ 65, 69, 64], 67 | [ 69, 65, 68], 68 | [ 66, 68, 65], 69 | [ 68, 66, 67], 70 | [ 49, 31, 48], 71 | [ 32, 48, 31], 72 | [ 48, 32, 47], 73 | [ 33, 47, 32], 74 | [ 33, 34, 46], 75 | [ 47, 33, 46], 76 | [ 35, 42, 43], 77 | [ 35, 43, 46], 78 | [ 35, 46, 34], 79 | [ 46, 43, 44], 80 | [ 46, 44, 45], 81 | [ 42, 35, 41], 82 | [ 36, 40, 41], 83 | [ 36, 41, 35], 84 | [ 40, 36, 39], 85 | [ 37, 39, 36], 86 | [ 39, 37, 38], 87 | [ 9, 30, 8], 88 | [ 24, 30, 9], 89 | [ 24, 9, 17], 90 | [ 24, 17, 23], 91 | [ 30, 24, 29], 92 | [ 25, 28, 29], 93 | [ 25, 29, 24], 94 | [ 28, 25, 27], 95 | [ 26, 27, 25], 96 | [ 16, 17, 9], 97 | [ 10, 16, 9], 98 | [ 16, 10, 15], 99 | [ 11, 15, 10], 100 | [ 15, 11, 14], 101 | [ 12, 14, 11], 102 | [ 14, 12, 13], 103 | [ 18, 23, 17], 104 | [ 23, 18, 22], 105 | [ 19, 22, 18], 106 | [ 22, 19, 21], 107 | [ 20, 21, 19], 108 | ]; 109 | 110 | var vertices = [ 111 | new TPoint( 372, 278), 112 | new TPoint( 334, 302), 113 | new TPoint( 318, 323), 114 | new TPoint( 324, 363), 115 | new TPoint( 359, 383), 116 | new TPoint( 383, 365), 117 | new TPoint( 355, 347), 118 | new TPoint( 354, 322), 119 | new TPoint( 399, 303), 120 | new TPoint( 415, 336), 121 | new TPoint( 422, 372), 122 | new TPoint( 380, 405), 123 | new TPoint( 395, 435), 124 | new TPoint( 449, 448), 125 | new TPoint( 481, 427), 126 | new TPoint( 478, 381), 127 | new TPoint( 461, 340), 128 | new TPoint( 512, 338), 129 | new TPoint( 517, 376), 130 | new TPoint( 513, 424), 131 | new TPoint( 540, 432), 132 | new TPoint( 566, 424), 133 | new TPoint( 577, 394), 134 | new TPoint( 548, 349), 135 | new TPoint( 530, 305), 136 | new TPoint( 558, 261), 137 | new TPoint( 563, 228), 138 | new TPoint( 470, 241), 139 | new TPoint( 492, 270), 140 | new TPoint( 481, 298), 141 | new TPoint( 437, 309), 142 | new TPoint( 431, 267), 143 | new TPoint( 431, 224), 144 | new TPoint( 524, 177), 145 | new TPoint( 572, 170), 146 | new TPoint( 579, 117), 147 | new TPoint( 546, 56), 148 | new TPoint( 440, 38), 149 | new TPoint( 397, 38), 150 | new TPoint( 392, 49), 151 | new TPoint( 491, 67), 152 | new TPoint( 517, 86), 153 | new TPoint( 530, 118), 154 | new TPoint( 490, 131), 155 | new TPoint( 466, 120), 156 | new TPoint( 444, 104), 157 | new TPoint( 415, 154), 158 | new TPoint( 398, 199), 159 | new TPoint( 398, 249), 160 | new TPoint( 381, 269), 161 | new TPoint( 365, 248), 162 | new TPoint( 339, 194), 163 | new TPoint( 366, 135), 164 | new TPoint( 347, 98), 165 | new TPoint( 290, 69), 166 | new TPoint( 260, 47), 167 | new TPoint( 228, 40), 168 | new TPoint( 212, 51), 169 | new TPoint( 231, 69), 170 | new TPoint( 274, 78), 171 | new TPoint( 317, 113), 172 | new TPoint( 322, 137), 173 | new TPoint( 311, 166), 174 | new TPoint( 277, 190), 175 | new TPoint( 246, 163), 176 | new TPoint( 212, 124), 177 | new TPoint( 164, 111), 178 | new TPoint( 122, 107), 179 | new TPoint( 119, 117), 180 | new TPoint( 94, 138), 181 | new TPoint( 94, 178), 182 | new TPoint( 92, 214), 183 | new TPoint( 90, 246), 184 | new TPoint( 112, 269), 185 | new TPoint( 132, 248), 186 | new TPoint( 125, 211), 187 | new TPoint( 152, 182), 188 | new TPoint( 189, 178), 189 | new TPoint( 231, 196), 190 | new TPoint( 283, 226), 191 | new TPoint( 324, 251), 192 | new TPoint( 300, 263), 193 | new TPoint( 258, 285), 194 | new TPoint( 202, 303), 195 | new TPoint( 169, 311), 196 | new TPoint( 130, 314), 197 | new TPoint( 95, 332), 198 | new TPoint( 88, 377), 199 | new TPoint( 88, 401), 200 | new TPoint( 119, 423), 201 | new TPoint( 177, 436), 202 | new TPoint( 188, 448), 203 | new TPoint( 194, 445), 204 | new TPoint( 165, 398), 205 | new TPoint( 130, 392), 206 | new TPoint( 134, 355), 207 | new TPoint( 190, 336), 208 | new TPoint( 227, 337), 209 | new TPoint( 266, 345), 210 | new TPoint( 277, 402), 211 | new TPoint( 293, 422), 212 | new TPoint( 314, 396), 213 | new TPoint( 282, 349), 214 | new TPoint( 287, 336), 215 | new TPoint( 290, 310), 216 | new TPoint( 313, 292), 217 | new TPoint( 334, 273), 218 | new TPoint( 351, 261), 219 | ]; 220 | -------------------------------------------------------------------------------- /include/polytri/polygon_triangulation.inl: -------------------------------------------------------------------------------- 1 | 2 | /* -------------------------------------------------------------------------- */ 3 | 4 | void PolyTri::Triangulate( 5 | const size_t num_contours, 6 | const uint32_t nvertices_per_contour[], 7 | const vertex_t vertices[], 8 | TriangleBuffer_t &triangles 9 | ) { 10 | assert(0u != num_contours); 11 | assert(nullptr != nvertices_per_contour); 12 | assert(nullptr != vertices); 13 | 14 | PolyTri tri(num_contours, nvertices_per_contour, vertices); 15 | tri.trapezoidal_decomposition(); 16 | tri.monotone_partitioning(); 17 | tri.triangulate_monotone_polygons(triangles); 18 | } 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | 22 | PolyTri::PolyTri( 23 | const size_t num_contours, 24 | const uint32_t nvertices_per_contour[], 25 | const vertex_t *vertices 26 | ) : vertices_(vertices) 27 | { 28 | // Retrieve the total number of segments. 29 | num_segments_ = 0u; 30 | for (auto i = 0u; i < num_contours; ++i) { 31 | num_segments_ += nvertices_per_contour[i]; 32 | } 33 | 34 | // Initialize the list of segments. 35 | segments_.resize(num_segments_); 36 | 37 | uint32_t index = 0u; 38 | for (auto cid = 0u; cid < num_contours; ++cid) { 39 | const auto nverts = nvertices_per_contour[cid]; 40 | 41 | // special case for first segment : save first id. 42 | uint32_t const first_index = index; 43 | 44 | segments_[first_index] = { 45 | .v0 = first_index, 46 | .v1 = first_index + 1u, 47 | }; 48 | ++index; 49 | 50 | for (auto i = 1u; i < nverts; ++i) { 51 | segments_[index] = { 52 | .v0 = segments_[index - 1u].v1, 53 | .v1 = index + 1u 54 | }; 55 | ++index; 56 | } 57 | 58 | // special case for last segment : update last vertex id. 59 | segments_[index - 1u].v1 = first_index; 60 | } 61 | } 62 | 63 | /* -------------------------------------------------------------------------- */ 64 | 65 | void PolyTri::trapezoidal_decomposition() 66 | { 67 | /// Note : 68 | /// The original paper is a bit different than that, 69 | /// it use two loops to construct the trapezoidation in log*(n) with some tricks. 70 | 71 | init_permutation_table(); 72 | init_query_structure(); 73 | 74 | while (!permutation_.empty()) { 75 | add_segment_to_query_structure(new_random_segment_index()); 76 | } 77 | } 78 | 79 | /* -------------------------------------------------------------------------- */ 80 | 81 | void PolyTri::monotone_partitioning() 82 | { 83 | POLYTRI_LOG("\n%s\n", __FUNCTION__); 84 | 85 | // keep track of visited trapezoids. 86 | visited_trapezoids_.resize(trapezoids_.size(), false); 87 | 88 | // we start partitioning on a top triangle. 89 | const auto start_tr = find_top_inside_trapezoid_index(); 90 | POLYTRI_LOG("start_trap_index : %u\n", start_tr); 91 | assert(kInvalidIndex != start_tr); 92 | 93 | visited_trapezoids_[start_tr] = true; 94 | 95 | // recursively build monotone chain. 96 | const auto &trapezoid = trapezoids_[start_tr]; 97 | const auto tr_min = trapezoid.min_y; 98 | const auto tr_max = trapezoid.max_y; 99 | 100 | if (kInvalidIndex != trapezoid.below2) { 101 | auto *monochain = create_monochain(tr_max, tr_min, InsertRight); 102 | build_monotone_chains(monochain, trapezoid.below1, start_tr, true); 103 | 104 | monochain = create_monochain(tr_min, tr_max, InsertLeft); 105 | build_monotone_chains(monochain, trapezoid.below2, start_tr, true); 106 | } else { 107 | const auto& right_segment = segments_[trapezoid.right_segment]; 108 | uint32_t max_y, min_y; 109 | get_max_min_y_indices(right_segment, max_y, min_y); 110 | 111 | const auto right = (tr_min == min_y) ? tr_min : tr_max; 112 | const auto left = (tr_min == min_y) ? tr_max : tr_min; 113 | const auto side = (tr_min == min_y) ? InsertRight : InsertLeft; 114 | auto *monochain = create_monochain(left, right, side); 115 | build_monotone_chains(monochain, trapezoid.below1, start_tr, true); 116 | } 117 | } 118 | 119 | /* -------------------------------------------------------------------------- */ 120 | 121 | void PolyTri::triangulate_monotone_polygons(TriangleBuffer_t &triangles) 122 | { 123 | POLYTRI_LOG("%s\n", __FUNCTION__); 124 | 125 | // Comparator struct ot find top and bottom most vertices in the list. 126 | struct MinMaxComparator { 127 | MinMaxComparator(const vertex_t vertices[]) : 128 | data(vertices) 129 | {} 130 | bool operator() (const uint32_t &i0, const uint32_t &i1) const noexcept { 131 | return data[i0].y < data[i1].y 132 | || ((fabs(data[i0].y - data[i1].y) < DBL_EPSILON) && (data[i0].x < data[i1].x)) 133 | ; 134 | } 135 | vertex_t const* data{}; 136 | } cmp(vertices_); 137 | 138 | // Triangulate each monochains. 139 | for (auto& m : monochains_) { 140 | // find the topmost vertex of the monochain. 141 | const auto min_max = std::minmax_element(m.list.begin(), m.list.end(), cmp); 142 | 143 | // first vertex to use depends on the main edge side (opposite to insertion side). 144 | // Bottommost if insertion is on the left, topmost otherwise. 145 | ChainIterator_t start_it{ 146 | (m.insertion_side == InsertLeft) ? min_max.first : min_max.second 147 | }; 148 | triangulate_monochain(m, start_it, triangles); 149 | } 150 | POLYTRI_LOG("finished trimonotpol\n"); 151 | } 152 | 153 | /* -------------------------------------------------------------------------- */ 154 | 155 | bool PolyTri::is_angle_convex(uint32_t v0, uint32_t v1, uint32_t v2) const 156 | { 157 | const auto& A = vertices_[v0]; 158 | const auto& B = vertices_[v1]; 159 | const auto& C = vertices_[v2]; 160 | 161 | const auto det = (B.x - A.x) * (C.y - B.y) - (B.y - A.y) * (C.x - B.x); 162 | return 0.0 < det; 163 | } 164 | 165 | /* -------------------------------------------------------------------------- */ 166 | 167 | namespace { 168 | 169 | // Return the next or previous iterator of a double linked list 170 | // without its end marker. 171 | template 172 | T next(const T &it, const T &end) 173 | { 174 | const auto &n = std::next(it); 175 | return (n == end) ? std::next(n) : n; 176 | } 177 | 178 | template 179 | T prev(const T &it, const T &end) 180 | { 181 | const auto &n = std::prev(it); 182 | return (n == end) ? std::prev(n) : n; 183 | } 184 | 185 | } 186 | 187 | /* -------------------------------------------------------------------------- */ 188 | 189 | void PolyTri::triangulate_monochain( 190 | Monochain_t &monochain, 191 | const ChainIterator_t &first, 192 | TriangleBuffer_t &triangles 193 | ) { 194 | const auto &end = monochain.list.end(); 195 | 196 | size_t prevsize = monochain.list.size(); 197 | 198 | for (auto current = next(first, end); prevsize >= 3u; ) { 199 | const auto v0 = *prev(current, end); 200 | const auto v1 = *current; 201 | const auto v2 = *next(current, end); 202 | 203 | const auto tri = triangle_t(v2, v1, v0); // 204 | 205 | const bool is_convex = is_angle_convex(tri.v0, tri.v1, tri.v2); 206 | 207 | if (is_convex) { 208 | triangles.push_back(tri); 209 | 210 | // remove current vertex from the chain and update position. 211 | const auto &save = prev(current, end); 212 | monochain.list.erase(current); 213 | current = (first == save) ? next(first, end) : save; 214 | } else { 215 | current = next(current, end); 216 | } 217 | 218 | // ---------------- 219 | // [debug way to break out of an infinite loop] 220 | // if (prevsize == monochain.list.size()) { 221 | // POLYTRI_LOG(">> break to avoid an infinite loop <<\n"); 222 | // break; 223 | // } 224 | prevsize = monochain.list.size(); 225 | // ---------------- 226 | } 227 | } 228 | 229 | /* -------------------------------------------------------------------------- */ 230 | -------------------------------------------------------------------------------- /include/polytri/monotone_partitioning.inl: -------------------------------------------------------------------------------- 1 | 2 | /* -------------------------------------------------------------------------- */ 3 | 4 | PolyTri::InsertionSide_t PolyTri::GetIntersectionSide( 5 | const bool min_is_right, 6 | const bool go_down 7 | ) { 8 | return (min_is_right == go_down) ? PolyTri::InsertRight 9 | : PolyTri::InsertLeft 10 | ; 11 | } 12 | 13 | /* -------------------------------------------------------------------------- */ 14 | 15 | uint32_t PolyTri::find_top_inside_trapezoid_index() const { 16 | POLYTRI_LOG("%s\n", __FUNCTION__); 17 | 18 | /// @bug : currently outside top triangle can be returned. 19 | for (uint32_t i = 0u; i < trapezoids_.size(); ++i) { 20 | if (is_top_inside_triangle(trapezoids_[i])) { 21 | return i; 22 | } 23 | } 24 | return kInvalidIndex; 25 | } 26 | 27 | /* -------------------------------------------------------------------------- */ 28 | 29 | void PolyTri::add_vertex_to_monochain( 30 | const Trapezoid_t &trapezoid, 31 | const bool go_down, 32 | Monochain_t *monochain 33 | ) { 34 | // POLYTRI_LOG("%s %d %p\n", __FUNCTION__, go_down, (void*)monochain); 35 | 36 | /// * Insertion order depends on the side : 37 | /// When is left : up pushed back, down pushed front 38 | /// When is right : down pushed back, up pushed front 39 | 40 | const auto vertex_index = (go_down) ? trapezoid.min_y 41 | : trapezoid.max_y 42 | ; 43 | 44 | /// To insert vertices in direct order we look for the direction. 45 | if ((InsertRight == monochain->insertion_side) == go_down) { 46 | monochain->list.push_back(vertex_index); 47 | } else { 48 | monochain->list.push_front(vertex_index); 49 | } 50 | } 51 | 52 | /* -------------------------------------------------------------------------- */ 53 | 54 | PolyTri::Monochain_t* PolyTri::create_monochain( 55 | const uint32_t first_index, 56 | const uint32_t second_index, 57 | const InsertionSide_t side 58 | ) { 59 | // POLYTRI_LOG("%s %d %d %d\n", __FUNCTION__, first_index, second_index, side); 60 | 61 | /// * For a monochain, vertices are always added to the same side, left or right. 62 | /// This is due to the nature of monochain and the vertical trapezoidation. 63 | /// 64 | /// * We need to know the left or right position only when pushing the first 65 | /// two vertices, For this we check if vertices belong to left / right segment, 66 | /// if they both does not belong to it (2 middles vertices), we took the min 67 | /// and max values. 68 | /// 69 | /// * When entering a new monochain we know the side of insertion by looking 70 | /// at the entering side edge value and comparing it to the trapezoid values 71 | /// (ie. by entering on the right, the right edges must include both the max and 72 | /// min trapezoid y index to be the inserting side). 73 | /// If both diagonal extremities are middle vertices, the main edge is the 74 | /// opposite one (of previous SIDE), otherwise it MUST contains one of the 75 | /// trapezoid vertex (min / max) 76 | /// 77 | /// * [ the real special case is diagonals with middle vertices, all other cases 78 | /// follow the sames rules ] 79 | /// 80 | 81 | monochains_.emplace_back(); 82 | auto *monochain = &monochains_.back(); 83 | monochain->insertion_side = side; 84 | monochain->list.push_back(first_index); 85 | monochain->list.push_back(second_index); 86 | return monochain; 87 | } 88 | 89 | /* -------------------------------------------------------------------------- */ 90 | 91 | void PolyTri::select_monotone_path( 92 | const uint32_t trapezoid_index, 93 | const bool go_down, 94 | const bool come_from_left 95 | ) { 96 | // POLYTRI_LOG("%s(%d, %d, %d)\n", __FUNCTION__, trapezoid_index, go_down, come_from_left); 97 | 98 | const auto &trapezoid = trapezoids_[trapezoid_index]; 99 | POLYTRI_LOG( 100 | " > left_segment = %u || right_segment = %u\n", 101 | trapezoid.left_segment, trapezoid.right_segment 102 | ); 103 | 104 | assert(kInvalidIndex != trapezoid.left_segment); 105 | assert(kInvalidIndex != trapezoid.right_segment); 106 | 107 | // Check if a break occured. 108 | 109 | const auto tr_max = trapezoid.max_y; 110 | const auto tr_min = trapezoid.min_y; 111 | 112 | bool top_left{}, btm_left{}; 113 | if (kInvalidIndex != trapezoid.left_segment) { 114 | uint32_t left_max_y{}, left_min_y{}; 115 | const auto &left_segment = segments_[trapezoid.left_segment]; 116 | get_max_min_y_indices(left_segment, left_max_y, left_min_y); 117 | top_left = (tr_max == left_max_y); 118 | btm_left = (tr_min == left_min_y); 119 | } 120 | 121 | bool top_right{}, btm_right{}; 122 | if (kInvalidIndex != trapezoid.right_segment) { 123 | uint32_t right_max_y{}, right_min_y{}; 124 | const auto &right_segment = segments_[trapezoid.right_segment]; 125 | get_max_min_y_indices(right_segment, right_max_y, right_min_y); 126 | top_right = (tr_max == right_max_y); 127 | btm_right = (tr_min == right_min_y); 128 | } 129 | 130 | const bool top_middle = (!top_left && !top_right); 131 | const bool btm_middle = (!btm_left && !btm_right); 132 | const bool top_triangle = (top_left && top_right); 133 | const bool btm_triangle = (btm_left && btm_right); 134 | 135 | if (!((top_left && btm_left) || (top_right && btm_right))) { 136 | if (top_triangle || btm_triangle) { 137 | // 138 | } else if (top_middle && btm_middle) { 139 | if (come_from_left) { 140 | auto *new_monochain = create_monochain(tr_min, tr_max, InsertLeft); 141 | build_monotone_chains(new_monochain, trapezoid.above2, trapezoid_index, false); 142 | build_monotone_chains(new_monochain, trapezoid.below2, trapezoid_index, true); 143 | } else { 144 | auto *new_monochain = create_monochain(tr_max, tr_min, InsertRight); 145 | build_monotone_chains(new_monochain, trapezoid.below1, trapezoid_index, true); 146 | build_monotone_chains(new_monochain, trapezoid.above1, trapezoid_index, false); 147 | } 148 | } else { 149 | const auto left = (top_left || (top_middle && btm_right)) ? tr_max : tr_min; 150 | const auto right = (left == tr_min) ? tr_max : tr_min; 151 | const bool min_is_right = (right == tr_min); 152 | 153 | if (!top_middle && !btm_middle) { 154 | if (go_down) { 155 | auto *new_monochain = create_monochain(left, right, GetIntersectionSide(min_is_right, true)); 156 | build_monotone_chains(new_monochain, trapezoid.below1, trapezoid_index, true); 157 | } else { 158 | auto *new_monochain = create_monochain(right, left, GetIntersectionSide(min_is_right, false)); 159 | build_monotone_chains(new_monochain, trapezoid.above1, trapezoid_index, false); 160 | } 161 | } else if (btm_middle) { 162 | if (top_left) { 163 | if (!go_down && come_from_left) { 164 | auto *new_monochain = create_monochain(right, left, InsertLeft); 165 | build_monotone_chains(new_monochain, trapezoid.below2, trapezoid_index, true); 166 | build_monotone_chains(new_monochain, trapezoid.above1, trapezoid_index, false); 167 | } else { 168 | auto *new_monochain = create_monochain(left, right, GetIntersectionSide(min_is_right, true)); 169 | build_monotone_chains(new_monochain, trapezoid.below1, trapezoid_index, true); 170 | } 171 | } else if (top_right) { 172 | if (!go_down && !come_from_left) { 173 | auto *new_monochain = create_monochain(right, left, InsertRight); 174 | build_monotone_chains(new_monochain, trapezoid.below1, trapezoid_index, true); 175 | build_monotone_chains(new_monochain, trapezoid.above1, trapezoid_index, false); 176 | } else { 177 | auto *new_monochain = create_monochain(left, right, GetIntersectionSide(min_is_right, true)); 178 | build_monotone_chains(new_monochain, trapezoid.below2, trapezoid_index, true); 179 | } 180 | } 181 | } else if (top_middle) { 182 | if (btm_left) { 183 | if (go_down && come_from_left) { 184 | auto *new_monochain = create_monochain(left, right, InsertLeft); 185 | build_monotone_chains(new_monochain, trapezoid.below1, trapezoid_index, true); 186 | build_monotone_chains(new_monochain, trapezoid.above2, trapezoid_index, false); 187 | } else { 188 | auto *new_monochain = create_monochain(right, left, GetIntersectionSide(min_is_right, false)); 189 | build_monotone_chains(new_monochain, trapezoid.above1, trapezoid_index, false); 190 | } 191 | } else if (btm_right) { 192 | if (go_down && !come_from_left) { 193 | auto *new_monochain = create_monochain(left, right, InsertRight); 194 | build_monotone_chains(new_monochain, trapezoid.below1, trapezoid_index, true); 195 | build_monotone_chains(new_monochain, trapezoid.above1, trapezoid_index, false); 196 | } else { 197 | auto *new_monochain = create_monochain(right, left, GetIntersectionSide(min_is_right, false)); 198 | build_monotone_chains(new_monochain, trapezoid.above2, trapezoid_index, false); 199 | } 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | /* -------------------------------------------------------------------------- */ 207 | 208 | void PolyTri::build_monotone_chains( 209 | Monochain_t *monochain, 210 | const uint32_t trapezoid_index, 211 | const uint32_t from_index, 212 | const bool go_down 213 | ) { 214 | if ((kInvalidIndex == trapezoid_index) 215 | || (visited_trapezoids_[trapezoid_index])) { 216 | return; 217 | } 218 | visited_trapezoids_[trapezoid_index] = true; 219 | // POLYTRI_LOG("%s %d %d %d\n", __FUNCTION__, trapezoid_index, from_index, go_down); 220 | 221 | const auto &trapezoid = trapezoids_[trapezoid_index]; 222 | 223 | // Determine if the current search come from the first children of the trapezoids. 224 | const bool come_from_left = (from_index == trapezoid.above1) 225 | || (from_index == trapezoid.below1) 226 | ; 227 | 228 | add_vertex_to_monochain(trapezoid, go_down, monochain); 229 | select_monotone_path(trapezoid_index, go_down, come_from_left); 230 | 231 | // Continue in the same direction. 232 | if (go_down) { 233 | build_monotone_chains(monochain, trapezoid.below1, trapezoid_index, true); 234 | build_monotone_chains(monochain, trapezoid.below2, trapezoid_index, true); 235 | } else { 236 | build_monotone_chains(monochain, trapezoid.above1, trapezoid_index, false); 237 | build_monotone_chains(monochain, trapezoid.above2, trapezoid_index, false); 238 | } 239 | } 240 | 241 | /* -------------------------------------------------------------------------- */ 242 | -------------------------------------------------------------------------------- /include/polytri/polytri.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // ---------------------------------------------------------------------------- 19 | 20 | #ifndef POLYTRI_ENABLE_PERMUTATION 21 | #define POLYTRI_ENABLE_PERMUTATION 0 22 | #endif 23 | 24 | #define POLYTRI_DEBUG_INFO 0 25 | 26 | #if POLYTRI_DEBUG_INFO 27 | #define POLYTRI_LOG(...) fprintf(stderr, __VA_ARGS__) 28 | #else 29 | #define POLYTRI_LOG(...) 30 | #endif 31 | 32 | // ---------------------------------------------------------------------------- 33 | 34 | class PolyTri { 35 | public: 36 | struct vertex_t { 37 | vertex_t() = default; 38 | vertex_t(double _x, double _y) : x(_x), y(_y) {} 39 | double x{}; 40 | double y{}; 41 | }; 42 | 43 | struct segment_t { 44 | uint32_t v0{}; 45 | uint32_t v1{}; 46 | }; 47 | 48 | struct triangle_t { 49 | triangle_t() = default; 50 | triangle_t(uint32_t _v0, uint32_t _v1, uint32_t _v2) 51 | : v0(_v0), v1(_v1), v2(_v2) 52 | {} 53 | uint32_t v0{}; 54 | uint32_t v1{}; 55 | uint32_t v2{}; 56 | }; 57 | 58 | public: 59 | using TriangleBuffer_t = std::vector; 60 | 61 | /** 62 | * num_contours : number of contours and size of the nvertices_per_contour array. 63 | * nvertices_per_contour : array of num_contour, each cell contain the number of 64 | * vertices in its contour. 65 | * vertices : a set of sequentially ordered 2d floating point coordinates to triangulate. 66 | * triangles : a buffer of indices where the triangles faces will be output. 67 | */ 68 | static void Triangulate( 69 | const size_t num_contours, 70 | const uint32_t nvertices_per_contour[], 71 | const vertex_t *vertices, 72 | TriangleBuffer_t &triangles 73 | ); 74 | 75 | /* mapbox/earcut type interface */ 76 | template 77 | static std::vector Triangulate(P const& polys) { 78 | size_t const NPOLY = polys.size(); 79 | 80 | std::vector contour_lengths; 81 | uint32_t totalLength = 0; 82 | for (size_t j = 0; j < NPOLY; ++j) { 83 | auto const& p = polys[j]; 84 | auto const clen = static_cast(p.size()); 85 | totalLength += clen; 86 | contour_lengths.push_back(clen); 87 | } 88 | 89 | std::vector vertices{}; 90 | vertices.reserve(totalLength); 91 | 92 | for (size_t j = 0; j < NPOLY; ++j) { 93 | auto const& p = polys[j]; 94 | 95 | for (size_t i = 0; i < p.size(); ++i) { 96 | auto const& v = p[i]; 97 | vertices.emplace_back(v.x, v.y); 98 | } 99 | 100 | auto const&a = p[0]; 101 | auto const&b = p[p.size()-1]; 102 | if ((fabs(a.x-b.x) < DBL_EPSILON) && (fabs(a.y-b.y) < DBL_EPSILON)) { 103 | POLYTRI_LOG("Issue : start and end meet. (%.2f %.2f)\n", a.x, a.y); 104 | vertices.resize(vertices.size()-1); 105 | contour_lengths[j] -= 1; 106 | } 107 | } 108 | 109 | TriangleBuffer_t triangles{}; 110 | Triangulate( 111 | contour_lengths.size(), 112 | contour_lengths.data(), 113 | vertices.data(), 114 | triangles 115 | ); 116 | 117 | std::vector indices{}; 118 | indices.reserve(3 * triangles.size()); 119 | for (auto const& t : triangles) { 120 | indices.insert(indices.end(), {t.v0, t.v1, t.v2}); // 121 | } 122 | return indices; 123 | } 124 | 125 | private: 126 | static const uint32_t kInvalidIndex = UINT32_MAX; 127 | 128 | enum QNodeType_t { 129 | X_NODE, 130 | Y_NODE, 131 | SINK 132 | }; 133 | 134 | enum MergeSide_t { 135 | MergeLeft, 136 | MergeRight, 137 | MergeEnd 138 | }; 139 | 140 | struct QNode_t { 141 | QNodeType_t type = SINK; 142 | uint32_t key_index = kInvalidIndex; 143 | QNode_t *left = nullptr; 144 | QNode_t *right = nullptr; 145 | QNode_t *parent = nullptr; 146 | }; 147 | 148 | struct Trapezoid_t { 149 | uint32_t max_y = kInvalidIndex; 150 | uint32_t min_y = kInvalidIndex; 151 | uint32_t left_segment = kInvalidIndex; 152 | uint32_t right_segment = kInvalidIndex; 153 | uint32_t above1 = kInvalidIndex; 154 | uint32_t above2 = kInvalidIndex; 155 | uint32_t below1 = kInvalidIndex; 156 | uint32_t below2 = kInvalidIndex; 157 | QNode_t *sink = nullptr; 158 | }; 159 | 160 | using Chain_t = std::list; 161 | using ChainIterator_t = Chain_t::iterator; 162 | 163 | enum InsertionSide_t { 164 | InsertLeft, 165 | InsertRight 166 | }; 167 | 168 | struct Monochain_t { 169 | InsertionSide_t insertion_side; 170 | Chain_t list; 171 | }; 172 | 173 | static 174 | InsertionSide_t GetIntersectionSide( 175 | const bool min_is_right, 176 | const bool go_down 177 | ); 178 | 179 | private: 180 | PolyTri( 181 | const size_t num_contours, 182 | const uint32_t nvertices_per_contour[], 183 | const vertex_t *vertices 184 | ); 185 | 186 | void trapezoidal_decomposition(); 187 | 188 | void monotone_partitioning(); 189 | 190 | void triangulate_monotone_polygons(TriangleBuffer_t &triangles); 191 | 192 | // ------------------------- 193 | // Trapezoidal Decomposition 194 | // ------------------------- 195 | 196 | // Return true if trapezoid is a top inside triangle. 197 | [[nodiscard]] 198 | bool is_top_inside_triangle(const Trapezoid_t &trapezoid) const; 199 | 200 | // Return true if trapezoid is a top triangle. 201 | [[nodiscard]] 202 | bool is_top_triangle(const Trapezoid_t &trapezoid) const; 203 | 204 | // Return true if trapezoid is a bottom triangle. 205 | [[nodiscard]] 206 | bool is_bottom_triangle(const Trapezoid_t &trapezoid) const; 207 | 208 | // Return a random segment index 209 | [[nodiscard]] 210 | uint32_t new_random_segment_index(); 211 | 212 | // Compute the signed distance of point to a segment, to know its side. 213 | [[nodiscard]] 214 | double distance_from_segment(const vertex_t &v, const segment_t &segment); 215 | 216 | // Return the trapezoid index containing the vertex. 217 | [[nodiscard]] 218 | uint32_t search_trapezoid_index(const vertex_t &v, const QNode_t *node); 219 | 220 | [[nodiscard]] 221 | inline uint32_t search_trapezoid_index(const vertex_t &v) { 222 | return search_trapezoid_index(v, root_); 223 | } 224 | 225 | // Return the index of the next available trapezoid to be initialized. 226 | [[nodiscard]] 227 | uint32_t get_new_trapezoid_index(); 228 | 229 | // Return a pointer to the next available node to be used, initialized with default values. 230 | QNode_t* create_node(QNodeType_t type, QNode_t *n, uint32_t key_index); 231 | 232 | // Link a sink node to its trapezoid and vice versa. 233 | void link_sink_node_and_trapezoid(QNode_t *node, uint32_t trapezoid_index); 234 | 235 | // Determine the max and min vertex indices of segment s. 236 | void get_max_min_y_indices(const segment_t& s, 237 | uint32_t &max_y_index, 238 | uint32_t &min_y_index) const; 239 | 240 | // Update trapezoid neighbors info after a y-split. 241 | void update_ysplit_trapezoid_neighbors(const uint32_t trapezoid_index); 242 | 243 | // Add an endpoint to the query structure, creating a y-node and a new trapezoid. 244 | void add_endpoint_to_query_structure(const uint32_t vertex_index); 245 | 246 | // Add a segment to the query structure. 247 | void add_segment_to_query_structure(const uint32_t segment_index); 248 | 249 | // Return the top_sink fusionned with btm_sink. 250 | [[nodiscard]] 251 | QNode_t* fusion_sinks(QNode_t *top_sink, QNode_t *btm_sink); 252 | 253 | // Update below's above neighbors. 254 | void update_trapezoid_aboves( 255 | const uint32_t trapezoid_index, 256 | Trapezoid_t &below 257 | ); 258 | 259 | // Update a trapezoid neighbors during a X-split. 260 | void update_xsplit_trapezoid_neighbors(const MergeSide_t side, 261 | const uint32_t left_trap_index, 262 | const uint32_t right_trap_index); 263 | 264 | // Update a node, its parent and its previous parent. 265 | void update_node_parent(QNode_t *new_parent, QNode_t *node); 266 | 267 | // Recursively split / merge trapezoids until the last endpoint. 268 | void split_merge_trapezoids(const uint32_t segment_index, 269 | const uint32_t end_y_index, 270 | const uint32_t trapezoid_index, 271 | QNode_t *left_fusion_node, 272 | QNode_t *right_fusion_node); 273 | 274 | // Compute a slighty offset vertex to find the first trapezoid of the segment. 275 | void compute_offset_vertex(const uint32_t max_y_index, 276 | const uint32_t min_y_index, 277 | vertex_t &offset) const; 278 | 279 | // Connect two endpoints by split / merging intermittent trapezoids. 280 | void thread_endpoints(const uint32_t segment_index, 281 | const uint32_t top_index, 282 | const uint32_t bottom_index); 283 | 284 | // Create a randomized table of numsegment indices. 285 | void init_permutation_table(); 286 | 287 | // Initialize the query structure by adding a first random segment. 288 | void init_query_structure(); 289 | 290 | 291 | // ------------------------- 292 | // Monotone Partitioning 293 | // ------------------------- 294 | 295 | [[nodiscard]] 296 | uint32_t find_top_inside_trapezoid_index() const; 297 | 298 | void add_vertex_to_monochain( 299 | const Trapezoid_t &trapezoid, 300 | const bool go_down, 301 | Monochain_t *monochain 302 | ); 303 | 304 | [[nodiscard]] 305 | Monochain_t* create_monochain( 306 | const uint32_t first_index, 307 | const uint32_t second_index, 308 | const InsertionSide_t side 309 | ); 310 | 311 | void select_monotone_path( 312 | const uint32_t trapezoid_index, 313 | const bool go_down, 314 | const bool come_from_left 315 | ); 316 | 317 | void build_monotone_chains( 318 | Monochain_t *monochain, 319 | const uint32_t trapezoid_index, 320 | const uint32_t from_index, 321 | const bool go_down 322 | ); 323 | 324 | // ------------------------- 325 | // Monochain Triangulation 326 | // ------------------------- 327 | 328 | // Triangulate a direct order monochain. 329 | void triangulate_monochain( 330 | Monochain_t &monochain, 331 | const ChainIterator_t &first, 332 | TriangleBuffer_t &triangles 333 | ); 334 | 335 | // Return true if an angle is convex. 336 | [[nodiscard]] 337 | bool is_angle_convex(uint32_t v0, uint32_t v1, uint32_t v2) const; 338 | 339 | // ------------------------- 340 | // Attributes 341 | // ------------------------- 342 | 343 | uint32_t num_segments_{}; 344 | const vertex_t *vertices_{}; 345 | 346 | std::vector segments_{}; 347 | 348 | // Randomized segments indices 349 | std::vector permutation_{}; 350 | 351 | // Query structure for trapezoidation 352 | std::vector trapezoids_{}; 353 | std::vector query_points_{}; 354 | QNode_t *root_{}; 355 | std::vector vertex_ynodes_{}; 356 | uint32_t used_trapezoid_count_{}; 357 | uint32_t used_node_count_{}; 358 | 359 | // Data for monotonization 360 | std::vector visited_trapezoids_{}; 361 | std::list monochains_{}; 362 | 363 | // Output triangles 364 | //std::vector triangles_{}; 365 | }; 366 | 367 | // ---------------------------------------------------------------------------- 368 | 369 | #ifdef POLYTRI_IMPLEMENTATION 370 | 371 | #include "polytri/polygon_triangulation.inl" 372 | #include "polytri/monotone_partitioning.inl" 373 | #include "polytri/trapezoidal_decomposition.inl" 374 | 375 | #endif // POLYTRI_IMPLEMENTATION 376 | 377 | // ---------------------------------------------------------------------------- 378 | -------------------------------------------------------------------------------- /include/polytri/trapezoidal_decomposition.inl: -------------------------------------------------------------------------------- 1 | 2 | /* -------------------------------------------------------------------------- */ 3 | 4 | namespace { 5 | 6 | bool is_vertex_lower( 7 | const PolyTri::vertex_t &a, 8 | const PolyTri::vertex_t &b 9 | ) { 10 | #if POLYTRI_DEBUG_INFO 11 | // fprintf(stderr, "a %.3f %.3f / b %.3f %.3f\n", a.x, a.y, b.x, b.y); 12 | #endif 13 | 14 | // assert((fabs(a.x-b.x) > DBL_EPSILON) || (fabs(a.y-b.y) > DBL_EPSILON)); 15 | if ((fabs(a.x-b.x) <= DBL_EPSILON) && (fabs(a.y-b.y) <= DBL_EPSILON)) { 16 | fprintf(stderr, "a %.3f %.3f / b %.3f %.3f\n", a.x, a.y, b.x, b.y); 17 | assert(0 && "Two vertices are the same"); 18 | } 19 | 20 | return ((a.y < b.y) || ((fabs(a.y-b.y) <= DBL_EPSILON) && (a.x < b.x))); 21 | } 22 | 23 | } 24 | 25 | /* -------------------------------------------------------------------------- */ 26 | 27 | bool PolyTri::is_top_inside_triangle(const Trapezoid_t &trapezoid) const 28 | { 29 | POLYTRI_LOG("(%s)\n", __FUNCTION__); 30 | 31 | // Check if border segments are in direct order. 32 | if (is_top_triangle(trapezoid)) { 33 | auto const side0 = (trapezoid.left_segment + num_segments_ - 1u) % num_segments_; 34 | auto const side1 = (trapezoid.right_segment + 1u) % num_segments_; 35 | 36 | POLYTRI_LOG("> is top, side0 = %d, side1 = %d (L = %u, R = %u)\n", 37 | side0, side1, trapezoid.left_segment, trapezoid.right_segment 38 | ); 39 | 40 | return (side0 == trapezoid.right_segment) 41 | && (side1 == trapezoid.left_segment) 42 | ; 43 | } 44 | return false; 45 | } 46 | 47 | /* -------------------------------------------------------------------------- */ 48 | 49 | bool PolyTri::is_top_triangle(const Trapezoid_t &trapezoid) const 50 | { 51 | if ((kInvalidIndex == trapezoid.left_segment) 52 | || (kInvalidIndex == trapezoid.right_segment)) { 53 | // POLYTRI_LOG("%s left or right segment of the trap non existent\n", __FUNCTION__); 54 | return false; 55 | } 56 | 57 | const auto& left = segments_[trapezoid.left_segment]; 58 | const auto& right = segments_[trapezoid.right_segment]; 59 | 60 | uint32_t left_maxy{}, _{}; 61 | get_max_min_y_indices(left, left_maxy, _); 62 | 63 | if ((left_maxy == right.v0) 64 | || (left_maxy == right.v1)) { 65 | POLYTRI_LOG("%s: right segment has left max y\n", __FUNCTION__); 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /* -------------------------------------------------------------------------- */ 73 | 74 | bool PolyTri::is_bottom_triangle(const Trapezoid_t &trapezoid) const 75 | { 76 | if ((kInvalidIndex == trapezoid.left_segment) 77 | || (kInvalidIndex == trapezoid.right_segment)) { 78 | // POLYTRI_LOG("%s left or right segment of the trap non existent\n", __FUNCTION__); 79 | return false; 80 | } 81 | 82 | const auto& left = segments_[trapezoid.left_segment]; 83 | const auto& right = segments_[trapezoid.right_segment]; 84 | 85 | uint32_t _{}, left_miny{}; 86 | get_max_min_y_indices(left, _, left_miny); 87 | 88 | if ((left_miny == right.v0) 89 | || (left_miny == right.v1)) { 90 | POLYTRI_LOG("%s right segment has left min y\n", __FUNCTION__); 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | /* -------------------------------------------------------------------------- */ 98 | 99 | uint32_t PolyTri::new_random_segment_index() 100 | { 101 | const uint32_t index = permutation_.back(); 102 | permutation_.pop_back(); 103 | return index; 104 | } 105 | 106 | /* -------------------------------------------------------------------------- */ 107 | 108 | double PolyTri::distance_from_segment(const vertex_t &v, const segment_t &segment) 109 | { 110 | uint32_t max_y_index{}, min_y_index{}; 111 | get_max_min_y_indices(segment, max_y_index, min_y_index); 112 | 113 | const auto &A = vertices_[max_y_index]; 114 | const auto &B = vertices_[min_y_index]; 115 | 116 | vertex_t AB; 117 | AB.x = B.x - A.x; 118 | AB.y = B.y - A.y; 119 | 120 | double d = -(AB.y * v.x - AB.x * v.y + B.x*A.y - B.y*A.x); 121 | 122 | // POLYTRI_LOG("%f\n", d); 123 | 124 | return d; 125 | } 126 | 127 | /* -------------------------------------------------------------------------- */ 128 | 129 | uint32_t PolyTri::search_trapezoid_index(const vertex_t &v, const QNode_t *node) 130 | { 131 | assert(nullptr != node); 132 | 133 | POLYTRI_LOG("[node %d](key id %d) v(%3f, %.3f)\n", node->type, node->key_index, v.x, v.y); 134 | 135 | switch (node->type) { 136 | case X_NODE: { 137 | auto const d = distance_from_segment(v, segments_[node->key_index]); 138 | return (d <= DBL_EPSILON) ? 139 | search_trapezoid_index(v, node->left) : 140 | search_trapezoid_index(v, node->right) ; 141 | } 142 | 143 | case Y_NODE: { 144 | return is_vertex_lower(v, vertices_[node->key_index]) ? 145 | search_trapezoid_index(v, node->left) : 146 | search_trapezoid_index(v, node->right) ; 147 | } 148 | 149 | case SINK: { 150 | return node->key_index; 151 | } 152 | } 153 | 154 | return kInvalidIndex; 155 | } 156 | 157 | /* -------------------------------------------------------------------------- */ 158 | 159 | uint32_t PolyTri::get_new_trapezoid_index() { 160 | return used_trapezoid_count_++; 161 | } 162 | 163 | /* -------------------------------------------------------------------------- */ 164 | 165 | PolyTri::QNode_t* PolyTri::create_node( 166 | QNodeType_t type, 167 | QNode_t *parent, 168 | uint32_t key_index 169 | ) { 170 | const uint32_t node_index = used_node_count_++; 171 | 172 | QNode_t *node = &query_points_[node_index]; 173 | 174 | node->type = type; 175 | node->parent = parent; 176 | node->key_index = key_index; 177 | 178 | if (SINK == type) { 179 | link_sink_node_and_trapezoid(node, key_index); 180 | } 181 | 182 | if (nullptr == parent) { 183 | root_ = node; 184 | } 185 | 186 | return node; 187 | } 188 | 189 | /* -------------------------------------------------------------------------- */ 190 | 191 | void PolyTri::link_sink_node_and_trapezoid(QNode_t *node, uint32_t trapezoid_index) 192 | { 193 | // POLYTRI_LOG("%s %p %d\n", __FUNCTION__, (void*)node, trapezoid_index); 194 | 195 | assert(node); 196 | assert(SINK == node->type); 197 | 198 | node->key_index = trapezoid_index; 199 | trapezoids_[trapezoid_index].sink = node; 200 | } 201 | 202 | /* -------------------------------------------------------------------------- */ 203 | 204 | void PolyTri::update_ysplit_trapezoid_neighbors(const uint32_t trapezoid_index) 205 | { 206 | // POLYTRI_LOG("%s %d\n", __FUNCTION__, trapezoid_index); 207 | 208 | const auto &trapezoid = trapezoids_[trapezoid_index]; 209 | 210 | if (kInvalidIndex != trapezoid.below1) { 211 | auto &below_trap = trapezoids_[trapezoid.below1]; 212 | below_trap.above1 = (trapezoid.above1 == below_trap.above1) ? trapezoid_index 213 | : below_trap.above1; 214 | if (kInvalidIndex != trapezoid.above1) 215 | below_trap.above2 = (trapezoid.above1 == below_trap.above2) ? trapezoid_index 216 | : below_trap.above2; 217 | } 218 | if (kInvalidIndex != trapezoid.below2) { 219 | auto &below_trap = trapezoids_[trapezoid.below2]; 220 | below_trap.above1 = (trapezoid.above1 == below_trap.above1) ? trapezoid_index 221 | : below_trap.above1; 222 | if (kInvalidIndex != trapezoid.above1) 223 | below_trap.above2 = (trapezoid.above1 == below_trap.above2) ? trapezoid_index 224 | : below_trap.above2; 225 | } 226 | if (kInvalidIndex != trapezoid.above1) { 227 | auto &above_trap = trapezoids_[trapezoid.above1]; 228 | above_trap.below1 = (trapezoid.below1 == above_trap.below1) ? trapezoid_index 229 | : above_trap.below1; 230 | if (kInvalidIndex != trapezoid.below1) 231 | above_trap.below2 = (trapezoid.below1 == above_trap.below2) ? trapezoid_index 232 | : above_trap.below2; 233 | } 234 | if (kInvalidIndex != trapezoid.above2) { 235 | auto &above_trap = trapezoids_[trapezoid.above2]; 236 | above_trap.below1 = (trapezoid.below1 == above_trap.below1) ? trapezoid_index 237 | : above_trap.below1; 238 | if (kInvalidIndex != trapezoid.below1) 239 | above_trap.below2 = (trapezoid.below1 == above_trap.below2) ? trapezoid_index 240 | : above_trap.below2; 241 | } 242 | } 243 | 244 | /* -------------------------------------------------------------------------- */ 245 | 246 | void PolyTri::add_endpoint_to_query_structure(const uint32_t vertex_index) 247 | { 248 | // POLYTRI_LOG("%s %d\n", __FUNCTION__, vertex_index); 249 | 250 | if (nullptr != vertex_ynodes_[vertex_index]) { 251 | return; 252 | } 253 | 254 | const auto top_trap_index = search_trapezoid_index(vertices_[vertex_index]); 255 | const auto btm_trap_index = get_new_trapezoid_index(); 256 | 257 | auto &top_trapezoid = trapezoids_[top_trap_index]; 258 | auto &btm_trapezoid = trapezoids_[btm_trap_index]; 259 | btm_trapezoid = top_trapezoid; 260 | 261 | QNode_t *sink = top_trapezoid.sink; 262 | QNode_t *y_node = create_node(Y_NODE, sink->parent, vertex_index); 263 | y_node->left = create_node(SINK, y_node, btm_trap_index); 264 | y_node->right = sink; 265 | 266 | if (sink->parent) { 267 | if (sink->parent->left == sink) { 268 | sink->parent->left = y_node; 269 | } else if (sink->parent->right == sink) { 270 | sink->parent->right = y_node; 271 | } 272 | } 273 | 274 | sink->parent = y_node; 275 | 276 | // Quick access to the new y-node. 277 | vertex_ynodes_[vertex_index] = y_node; // 278 | 279 | // Update trapezoids. 280 | top_trapezoid.min_y = vertex_index; 281 | top_trapezoid.below1 = btm_trap_index; 282 | top_trapezoid.below2 = kInvalidIndex; 283 | 284 | btm_trapezoid.max_y = vertex_index; 285 | btm_trapezoid.above1 = top_trap_index; 286 | btm_trapezoid.above2 = kInvalidIndex; 287 | 288 | // The bottom trapezoid is newly created, so we must updated its neighbors as well. 289 | update_ysplit_trapezoid_neighbors(btm_trap_index); 290 | 291 | POLYTRI_LOG("split Y : top %d / bottom %d (at v[%d].y = %.3f).\n", 292 | top_trap_index, btm_trap_index, 293 | vertex_index, vertices_[vertex_index].y); 294 | } 295 | 296 | /* -------------------------------------------------------------------------- */ 297 | 298 | void PolyTri::get_max_min_y_indices( 299 | const segment_t& s, 300 | uint32_t &max_y_index, 301 | uint32_t &min_y_index 302 | ) const { 303 | if (is_vertex_lower(vertices_[s.v0], vertices_[s.v1])) { 304 | max_y_index = s.v1; 305 | min_y_index = s.v0; 306 | } else { 307 | max_y_index = s.v0; 308 | min_y_index = s.v1; 309 | } 310 | } 311 | 312 | /* -------------------------------------------------------------------------- */ 313 | 314 | PolyTri::QNode_t* PolyTri::fusion_sinks( 315 | QNode_t *top_sink, 316 | QNode_t *btm_sink 317 | ) { 318 | // POLYTRI_LOG("%s %p %p\n", __FUNCTION__, (void*)top_sink, (void*)btm_sink); 319 | 320 | auto &top_trap = trapezoids_[top_sink->key_index]; 321 | const auto &btm_trap = trapezoids_[btm_sink->key_index]; 322 | 323 | top_trap.min_y = btm_trap.min_y; 324 | 325 | // used with the second version of update_xsplit 326 | top_trap.below1 = btm_trap.below1; 327 | top_trap.below2 = btm_trap.below2; 328 | 329 | // top_trap.above1 = (kInvalidIndex != top_trap.above2) ? top_trap.above2 : top_trap.above1; 330 | // top_trap.above2 = kInvalidIndex; // 331 | 332 | return top_sink; 333 | } 334 | 335 | /* -------------------------------------------------------------------------- */ 336 | 337 | void PolyTri::update_trapezoid_aboves( 338 | const uint32_t trapezoid_index, 339 | Trapezoid_t &below 340 | ) { 341 | // POLYTRI_LOG("%s %d\n", __FUNCTION__, trapezoid_index); 342 | 343 | if (below.above1 == trapezoid_index) { 344 | below.above1 = kInvalidIndex; 345 | } 346 | 347 | if (below.above2 == trapezoid_index) { 348 | below.above2 = kInvalidIndex; 349 | } 350 | 351 | #if 0 352 | if (kInvalidIndex == below.above1) { 353 | below.above1 = below.above2; 354 | below.above2 = kInvalidIndex; 355 | } 356 | #endif 357 | } 358 | 359 | /* -------------------------------------------------------------------------- */ 360 | 361 | void PolyTri::update_xsplit_trapezoid_neighbors( 362 | const MergeSide_t side, 363 | const uint32_t left_trap_index, 364 | const uint32_t right_trap_index 365 | ) { 366 | POLYTRI_LOG("%s %d %d %d\n", __FUNCTION__, side, left_trap_index, right_trap_index); 367 | 368 | auto &left_trap = trapezoids_[left_trap_index]; 369 | auto &right_trap = trapezoids_[right_trap_index]; 370 | 371 | // We need 'belows' to compute trapezoids and for monotonization, 372 | // we need 'aboves' only for monotonization. 373 | 374 | if (side == MergeLeft) { 375 | update_trapezoid_aboves(left_trap_index, trapezoids_[left_trap.below1]); 376 | 377 | if (kInvalidIndex != left_trap.below2) { 378 | update_trapezoid_aboves(left_trap_index, trapezoids_[left_trap.below2]); 379 | } 380 | 381 | // --------- 382 | 383 | right_trap.below1 = left_trap.below1; 384 | right_trap.below2 = left_trap.below2; 385 | left_trap.below2 = kInvalidIndex; 386 | 387 | auto &below1 = trapezoids_[right_trap.below1]; 388 | below1.above1 = right_trap_index; 389 | 390 | if (kInvalidIndex != right_trap.below2) { 391 | trapezoids_[right_trap.below2].above1 = right_trap_index; 392 | } 393 | } else if (side == MergeRight) { 394 | 395 | update_trapezoid_aboves(right_trap_index, trapezoids_[right_trap.below1]); 396 | if (kInvalidIndex != right_trap.below2) { 397 | update_trapezoid_aboves(right_trap_index, trapezoids_[right_trap.below2]); 398 | } 399 | 400 | // --------- 401 | 402 | right_trap.below1 = (kInvalidIndex != left_trap.below2) ? left_trap.below2 : left_trap.below1; 403 | right_trap.below2 = kInvalidIndex; 404 | 405 | auto &below1 = trapezoids_[left_trap.below1]; 406 | 407 | if (below1.above1 == kInvalidIndex) { 408 | below1.above1 = (left_trap_index != below1.above2) ? left_trap_index : below1.above1; // 409 | } else { 410 | below1.above2 = left_trap_index; 411 | } 412 | 413 | if (kInvalidIndex != left_trap.below2) { 414 | trapezoids_[left_trap.below2].above1 = left_trap_index; 415 | } 416 | } else { 417 | // END TRAPEZOID 418 | 419 | right_trap.below1 = (kInvalidIndex != right_trap.below2) ? right_trap.below2 420 | : right_trap.below1 421 | ; 422 | right_trap.below2 = kInvalidIndex; 423 | left_trap.below2 = kInvalidIndex; 424 | 425 | // When we close the last trapezoid the below trapezoid has 2 aboves, 426 | // unless the vertex is already segmented. 427 | if (left_trap.below1 == right_trap.below1) { 428 | // 1) The below trapezoid is unsegmented. 429 | 430 | // close the trapezoid 431 | auto &below = trapezoids_[left_trap.below1]; 432 | if (is_bottom_triangle(left_trap)) { 433 | POLYTRI_LOG("> left trap is bottom triangle\n"); 434 | left_trap.below1 = kInvalidIndex; 435 | left_trap.below2 = kInvalidIndex; 436 | below.above2 = right_trap_index; 437 | } else if (is_bottom_triangle(right_trap)) { 438 | POLYTRI_LOG("> right trap is bottom triangle\n"); 439 | right_trap.below1 = kInvalidIndex; 440 | right_trap.below2 = kInvalidIndex; 441 | below.above1 = left_trap_index; 442 | below.above2 = (right_trap_index != below.above2) ? below.above2 443 | : kInvalidIndex 444 | ; 445 | } else { 446 | POLYTRI_LOG("> neither traps are bottom triangle\n"); 447 | below.above1 = left_trap_index; 448 | below.above2 = right_trap_index; 449 | } 450 | } else { 451 | // 2) The below trapezoid is segmented. 452 | POLYTRI_LOG("> below trap is segmented\n"); 453 | 454 | auto &left_below = trapezoids_[left_trap.below1]; 455 | left_below.above1 = left_trap_index; 456 | left_below.above2 = kInvalidIndex; 457 | 458 | auto &right_below = trapezoids_[right_trap.below1]; 459 | right_below.above1 = right_trap_index; 460 | right_below.above2 = kInvalidIndex; 461 | } 462 | } 463 | } 464 | 465 | /* -------------------------------------------------------------------------- */ 466 | 467 | void PolyTri::update_node_parent(QNode_t *new_parent, QNode_t *node) { 468 | if (node->parent->left == node) { 469 | node->parent->left = new_parent; 470 | } else if (node->parent->right == node) { 471 | node->parent->right = new_parent; 472 | } 473 | node->parent = new_parent; 474 | } 475 | 476 | /* -------------------------------------------------------------------------- */ 477 | 478 | void PolyTri::split_merge_trapezoids( 479 | const uint32_t segment_index, 480 | const uint32_t end_y_index, 481 | const uint32_t trapezoid_index, 482 | QNode_t *left_fusion_node, 483 | QNode_t *right_fusion_node 484 | ) { 485 | // POLYTRI_LOG( 486 | // "%s %d %d %d %p %p\n", __FUNCTION__, 487 | // segment_index, end_y_index, trapezoid_index, 488 | // (void*)left_fusion_node, (void*)right_fusion_node 489 | // ); 490 | 491 | auto &trapezoid = trapezoids_[trapezoid_index]; 492 | assert(kInvalidIndex != trapezoid_index); 493 | assert(end_y_index != trapezoid.max_y); 494 | 495 | // Create a new X-node. 496 | auto *sink = trapezoid.sink; 497 | auto *parent = sink->parent; 498 | auto *x_node = create_node(X_NODE, parent, segment_index); 499 | 500 | // Set left / right sink, potentially with fusion. 501 | x_node->left = ( left_fusion_node) ? fusion_sinks( left_fusion_node, sink) 502 | : sink; 503 | x_node->right = (right_fusion_node) ? fusion_sinks(right_fusion_node, sink) 504 | : sink; 505 | update_node_parent(x_node, x_node->left); 506 | update_node_parent(x_node, x_node->right); 507 | 508 | //----------------------------------------- 509 | 510 | // Update x-node sub trapezoids segments. 511 | const auto left_trap_index = x_node->left->key_index; 512 | const auto right_trap_index = x_node->right->key_index; 513 | auto &left_trap = trapezoids_[left_trap_index]; 514 | auto &right_trap = trapezoids_[right_trap_index]; 515 | 516 | left_trap.right_segment = segment_index; 517 | right_trap.left_segment = segment_index; 518 | 519 | assert(kInvalidIndex != trapezoid.min_y); 520 | 521 | // Determine the next fusion side. 522 | const auto &vertex = vertices_[trapezoid.min_y]; 523 | const auto &segment = segments_[segment_index]; 524 | 525 | const auto vertex_distance = distance_from_segment(vertex, segment); 526 | 527 | const auto side = (vertex_distance > +DBL_EPSILON) ? MergeLeft 528 | : (vertex_distance < -DBL_EPSILON) ? MergeRight 529 | : MergeEnd 530 | ; 531 | 532 | POLYTRI_LOG("sub-split X (trap %d) : left-right trap %d / %d || side %d\n", 533 | trapezoid_index, left_trap_index, right_trap_index, side 534 | ); 535 | 536 | // Update neighborhood depending on the next fusion side. 537 | update_xsplit_trapezoid_neighbors(side, left_trap_index, right_trap_index); 538 | 539 | // Recursively split merge successive trapezoids. 540 | // The recursion ends on the final vertex (y_side == 0.0). 541 | if (side == MergeLeft) { 542 | split_merge_trapezoids( 543 | segment_index, end_y_index, left_trap.below1, x_node->left, nullptr 544 | ); 545 | } else if (side == MergeRight) { 546 | split_merge_trapezoids( 547 | segment_index, end_y_index, right_trap.below1, nullptr, x_node->right 548 | ); 549 | } 550 | } 551 | 552 | /* -------------------------------------------------------------------------- */ 553 | 554 | void PolyTri::compute_offset_vertex( 555 | const uint32_t max_y_index, 556 | const uint32_t min_y_index, 557 | vertex_t &offset 558 | ) const { 559 | const auto eps = 1.0e-3; 560 | 561 | const auto &A = vertices_[max_y_index]; 562 | const auto &B = vertices_[min_y_index]; 563 | offset.x = B.x - A.x; 564 | offset.y = B.y - A.y; 565 | 566 | const auto invlen = 1.0 / sqrt(offset.x*offset.x + offset.y*offset.y); 567 | offset.x *= invlen * eps; 568 | offset.y *= invlen * eps; 569 | 570 | offset.x = A.x + offset.x; 571 | offset.y = A.y + offset.y; 572 | } 573 | 574 | /* -------------------------------------------------------------------------- */ 575 | 576 | void PolyTri::thread_endpoints( 577 | const uint32_t segment_index, 578 | const uint32_t max_y_index, 579 | const uint32_t min_y_index 580 | ) { 581 | // Recursively split and merge trapezoids intersecting the segment. 582 | 583 | // To find the top trapezoid we need to offset the first vertex in the direction 584 | // of the segment, to distinct between left/right trapezoids neighbors. 585 | vertex_t v{}; 586 | compute_offset_vertex(max_y_index, min_y_index, v); 587 | 588 | /// @note could be constant instead, by storing two below sinks and a segment for each Y. 589 | /// furthermore this will prevent a bug when trapzeoid collapse (on same Ys). 590 | const auto top_trapezoid_index = search_trapezoid_index(v, vertex_ynodes_[max_y_index]); // 591 | 592 | const auto new_trapezoid_index = get_new_trapezoid_index(); 593 | 594 | auto &top_trapezoid = trapezoids_[top_trapezoid_index]; 595 | auto &new_trapezoid = trapezoids_[new_trapezoid_index]; 596 | 597 | // Copy top trapezoid attributes to the new one. 598 | new_trapezoid = top_trapezoid; 599 | 600 | // Link first splitted trapezoid to the one above it. 601 | if (kInvalidIndex != top_trapezoid.above2) { 602 | trapezoids_[top_trapezoid.above2].below1 = new_trapezoid_index; 603 | new_trapezoid.above1 = top_trapezoid.above2; 604 | } else { 605 | auto &above_trapezoid = trapezoids_[top_trapezoid.above1]; 606 | 607 | if (above_trapezoid.below2 == kInvalidIndex) { 608 | // case 1 : new empty trapezoid 609 | above_trapezoid.below2 = new_trapezoid_index; 610 | } else if ( (top_trapezoid_index == above_trapezoid.below2) 611 | && (kInvalidIndex != top_trapezoid.left_segment)) { 612 | // case 2 : old right trap will become a triangle 613 | above_trapezoid.below2 = new_trapezoid_index; 614 | } 615 | } 616 | top_trapezoid.above2 = kInvalidIndex; 617 | new_trapezoid.above2 = kInvalidIndex; 618 | 619 | // Every threading operation create one new sink, used as the right fusion sink 620 | // by default. 621 | auto *new_sink = create_node(SINK, root_, new_trapezoid_index); 622 | 623 | POLYTRI_LOG("> split X, max-min y index : %d %d\n", max_y_index, min_y_index); 624 | 625 | split_merge_trapezoids( 626 | segment_index, min_y_index, top_trapezoid_index, nullptr, new_sink 627 | ); 628 | 629 | // Close connections with above trapezoid when one of the start trapezoids 630 | // (left or right) is a triangle. 631 | if (is_top_triangle(top_trapezoid)) { 632 | POLYTRI_LOG("> top trap is top\n"); 633 | top_trapezoid.above1 = kInvalidIndex; 634 | } else if (is_top_triangle(new_trapezoid)) { 635 | POLYTRI_LOG("> new trap is top\n"); 636 | new_trapezoid.above1 = kInvalidIndex; 637 | } 638 | } 639 | 640 | /* -------------------------------------------------------------------------- */ 641 | 642 | void PolyTri::add_segment_to_query_structure(const uint32_t segment_index) 643 | { 644 | const auto &segment = segments_[segment_index]; 645 | 646 | uint32_t min_y_index{}, max_y_index{}; 647 | get_max_min_y_indices(segment, max_y_index, min_y_index); 648 | 649 | add_endpoint_to_query_structure(max_y_index); 650 | add_endpoint_to_query_structure(min_y_index); 651 | thread_endpoints(segment_index, max_y_index, min_y_index); 652 | } 653 | 654 | /* -------------------------------------------------------------------------- */ 655 | 656 | void PolyTri::init_permutation_table() 657 | { 658 | // assert(num_segments_ == segments_.size()); 659 | POLYTRI_LOG("%s %d %lu\n", __FUNCTION__, num_segments_, segments_.size()); 660 | 661 | permutation_.resize(num_segments_); 662 | for (uint32_t i = 0u; i < num_segments_; ++i) { 663 | permutation_[i] = (num_segments_ - 1) - i; 664 | } 665 | 666 | #if POLYTRI_ENABLE_PERMUTATION 667 | // 1761133065998441995 668 | const auto seed = std::chrono::system_clock::now().time_since_epoch().count(); 669 | POLYTRI_LOG("seed used : %lu\n", seed); 670 | std::shuffle( 671 | permutation_.begin(), permutation_.end(), std::default_random_engine(seed) 672 | ); 673 | #endif 674 | } 675 | 676 | /* -------------------------------------------------------------------------- */ 677 | 678 | void PolyTri::init_query_structure() 679 | { 680 | vertex_ynodes_.resize(num_segments_ + 1u, nullptr); 681 | query_points_.resize(8u * num_segments_); 682 | trapezoids_.resize(4u * num_segments_); 683 | 684 | // Default empty trapezoid. 685 | create_node(SINK, nullptr, get_new_trapezoid_index()); 686 | } 687 | 688 | /* -------------------------------------------------------------------------- */ 689 | --------------------------------------------------------------------------------