├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Readme.md ├── delaunay.hpp ├── delaunay.png └── demo.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(delaunay-demo) 2 | cmake_minimum_required(VERSION 3.2) 3 | 4 | find_package(OpenGL REQUIRED) 5 | find_package(GLUT REQUIRED) 6 | 7 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} OpenGL::OpenGL) 8 | 9 | add_executable(demo demo.cpp) 10 | target_link_libraries(demo OpenGL::OpenGL OpenGL::GLU GLUT::GLUT) 11 | target_compile_options(demo PRIVATE -std=c++14) 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jean Bégaint 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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # C++11 Delaunay implementation 2 | 3 | Implementation of the Bowyer-Watson algorithm, heavily inspired from: http://paulbourke.net/papers/triangulate. 4 | 5 | ![Delaunay triangulation demo](delaunay.png) 6 | 7 | ## Requirements 8 | 9 | * C++11 compliant compiler 10 | * (OpenGL for the demo) 11 | 12 | ## How to use 13 | 14 | * just include the ```delaunay.hpp``` header file 15 | * do not use in production, more efficient implementation of Delaunay's triangulation can be found elsewhere. 16 | 17 | 18 | ## Example 19 | 20 | * 21 | ``` 22 | ... 23 | #include "delaunay.hpp" 24 | ... 25 | std::vector> points; 26 | /* Initialize the points. */ 27 | ... 28 | /* Triangulate. */ 29 | const auto triangulation = delaunay::triangulate(point); 30 | ... 31 | /* Do domething with the edges or the triangles. */ 32 | for (auto const& e : triangulation.edges) { 33 | ... 34 | } 35 | .... 36 | ``` 37 | 38 | * a working example can be found in the ```demo.cpp``` file, in addition the ```delaunay.hpp```header file should be self-explanatory. 39 | -------------------------------------------------------------------------------- /delaunay.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Bowyer-Watson algorithm 3 | * C++ implementation of http://paulbourke.net/papers/triangulate . 4 | **/ 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace delaunay { 12 | 13 | constexpr double eps = 1e-4; 14 | 15 | template 16 | struct Point { 17 | T x, y; 18 | 19 | Point() : x{0}, y{0} {} 20 | Point(T _x, T _y) : x{_x}, y{_y} {} 21 | 22 | template 23 | Point(U _x, U _y) : x{static_cast(_x)}, y{static_cast(_y)} 24 | { 25 | } 26 | 27 | friend std::ostream& operator<<(std::ostream& os, const Point& p) 28 | { 29 | os << "x=" << p.x << " y=" << p.y; 30 | return os; 31 | } 32 | 33 | bool operator==(const Point& other) const 34 | { 35 | return (other.x == x && other.y == y); 36 | } 37 | 38 | bool operator!=(const Point& other) const { return !operator==(other); } 39 | }; 40 | 41 | template 42 | struct Edge { 43 | using Node = Point; 44 | Node p0, p1; 45 | 46 | Edge(Node const& _p0, Node const& _p1) : p0{_p0}, p1{_p1} {} 47 | 48 | friend std::ostream& operator<<(std::ostream& os, const Edge& e) 49 | { 50 | os << "p0: [" << e.p0 << " ] p1: [" << e.p1 << "]"; 51 | return os; 52 | } 53 | 54 | bool operator==(const Edge& other) const 55 | { 56 | return ((other.p0 == p0 && other.p1 == p1) || 57 | (other.p0 == p1 && other.p1 == p0)); 58 | } 59 | }; 60 | 61 | template 62 | struct Circle { 63 | T x, y, radius; 64 | Circle() = default; 65 | }; 66 | 67 | template 68 | struct Triangle { 69 | using Node = Point; 70 | Node p0, p1, p2; 71 | Edge e0, e1, e2; 72 | Circle circle; 73 | 74 | Triangle(const Node& _p0, const Node& _p1, const Node& _p2) 75 | : p0{_p0}, 76 | p1{_p1}, 77 | p2{_p2}, 78 | e0{_p0, _p1}, 79 | e1{_p1, _p2}, 80 | e2{_p0, _p2}, 81 | circle{} 82 | { 83 | const auto ax = p1.x - p0.x; 84 | const auto ay = p1.y - p0.y; 85 | const auto bx = p2.x - p0.x; 86 | const auto by = p2.y - p0.y; 87 | 88 | const auto m = p1.x * p1.x - p0.x * p0.x + p1.y * p1.y - p0.y * p0.y; 89 | const auto u = p2.x * p2.x - p0.x * p0.x + p2.y * p2.y - p0.y * p0.y; 90 | const auto s = 1. / (2. * (ax * by - ay * bx)); 91 | 92 | circle.x = ((p2.y - p0.y) * m + (p0.y - p1.y) * u) * s; 93 | circle.y = ((p0.x - p2.x) * m + (p1.x - p0.x) * u) * s; 94 | 95 | const auto dx = p0.x - circle.x; 96 | const auto dy = p0.y - circle.y; 97 | circle.radius = dx * dx + dy * dy; 98 | } 99 | }; 100 | 101 | template 102 | struct Delaunay { 103 | std::vector> triangles; 104 | std::vector> edges; 105 | }; 106 | 107 | template < 108 | typename T, 109 | typename = typename std::enable_if::value>::type> 110 | Delaunay triangulate(const std::vector>& points) 111 | { 112 | using Node = Point; 113 | if (points.size() < 3) { 114 | return Delaunay{}; 115 | } 116 | auto xmin = points[0].x; 117 | auto xmax = xmin; 118 | auto ymin = points[0].y; 119 | auto ymax = ymin; 120 | for (auto const& pt : points) { 121 | xmin = std::min(xmin, pt.x); 122 | xmax = std::max(xmax, pt.x); 123 | ymin = std::min(ymin, pt.y); 124 | ymax = std::max(ymax, pt.y); 125 | } 126 | 127 | const auto dx = xmax - xmin; 128 | const auto dy = ymax - ymin; 129 | const auto dmax = std::max(dx, dy); 130 | const auto midx = (xmin + xmax) / static_cast(2.); 131 | const auto midy = (ymin + ymax) / static_cast(2.); 132 | 133 | /* Init Delaunay triangulation. */ 134 | auto d = Delaunay{}; 135 | 136 | const auto p0 = Node{midx - 20 * dmax, midy - dmax}; 137 | const auto p1 = Node{midx, midy + 20 * dmax}; 138 | const auto p2 = Node{midx + 20 * dmax, midy - dmax}; 139 | d.triangles.emplace_back(Triangle{p0, p1, p2}); 140 | 141 | for (auto const& pt : points) { 142 | std::vector> edges; 143 | std::vector> tmps; 144 | for (auto const& tri : d.triangles) { 145 | /* Check if the point is inside the triangle circumcircle. */ 146 | const auto dist = (tri.circle.x - pt.x) * (tri.circle.x - pt.x) + 147 | (tri.circle.y - pt.y) * (tri.circle.y - pt.y); 148 | if ((dist - tri.circle.radius) <= eps) { 149 | edges.push_back(tri.e0); 150 | edges.push_back(tri.e1); 151 | edges.push_back(tri.e2); 152 | } 153 | else { 154 | tmps.push_back(tri); 155 | } 156 | } 157 | 158 | /* Delete duplicate edges. */ 159 | std::vector remove(edges.size(), false); 160 | for (auto it1 = edges.begin(); it1 != edges.end(); ++it1) { 161 | for (auto it2 = edges.begin(); it2 != edges.end(); ++it2) { 162 | if (it1 == it2) { 163 | continue; 164 | } 165 | if (*it1 == *it2) { 166 | remove[std::distance(edges.begin(), it1)] = true; 167 | remove[std::distance(edges.begin(), it2)] = true; 168 | } 169 | } 170 | } 171 | 172 | edges.erase( 173 | std::remove_if(edges.begin(), edges.end(), 174 | [&](auto const& e) { return remove[&e - &edges[0]]; }), 175 | edges.end()); 176 | 177 | /* Update triangulation. */ 178 | for (auto const& e : edges) { 179 | tmps.push_back({e.p0, e.p1, {pt.x, pt.y}}); 180 | } 181 | d.triangles = tmps; 182 | } 183 | 184 | /* Remove original super triangle. */ 185 | d.triangles.erase( 186 | std::remove_if(d.triangles.begin(), d.triangles.end(), 187 | [&](auto const& tri) { 188 | return ((tri.p0 == p0 || tri.p1 == p0 || tri.p2 == p0) || 189 | (tri.p0 == p1 || tri.p1 == p1 || tri.p2 == p1) || 190 | (tri.p0 == p2 || tri.p1 == p2 || tri.p2 == p2)); 191 | }), 192 | d.triangles.end()); 193 | 194 | /* Add edges. */ 195 | for (auto const& tri : d.triangles) { 196 | d.edges.push_back(tri.e0); 197 | d.edges.push_back(tri.e1); 198 | d.edges.push_back(tri.e2); 199 | } 200 | return d; 201 | } 202 | 203 | } /* namespace delaunay */ 204 | -------------------------------------------------------------------------------- /delaunay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbegaint/delaunay-cpp/a5735771621d889bd5b85ffc228ed358e4bf3c9b/delaunay.png -------------------------------------------------------------------------------- /demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "delaunay.hpp" 8 | 9 | using namespace delaunay; 10 | 11 | namespace context { 12 | std::vector> points; 13 | } /* namespace context */ 14 | 15 | void displayMe() 16 | { 17 | /* Draw points. */ 18 | glClear(GL_COLOR_BUFFER_BIT); 19 | glColor3f(1, 1, 1); 20 | glBegin(GL_POINTS); 21 | for (auto const& p : context::points) { 22 | glVertex2i(p.x, p.y); 23 | } 24 | glEnd(); 25 | 26 | const auto triangulation = triangulate(context::points); 27 | 28 | /* Draw lines. */ 29 | glBegin(GL_LINES); 30 | for (auto const& e : triangulation.edges) { 31 | glVertex2i(e.p0.x, e.p0.y); 32 | glVertex2i(e.p1.x, e.p1.y); 33 | } 34 | glEnd(); 35 | 36 | glutSwapBuffers(); 37 | } 38 | 39 | void mouse_callback(int button, int state, int x, int y) 40 | { 41 | y = glutGet(GLUT_WINDOW_HEIGHT) - y; 42 | switch (button) { 43 | case GLUT_LEFT_BUTTON: 44 | if (state == GLUT_UP) { 45 | context::points.push_back({x, y}); 46 | } 47 | break; 48 | case GLUT_MIDDLE_BUTTON: 49 | context::points.clear(); 50 | break; 51 | case GLUT_RIGHT_BUTTON: 52 | /* Find closest point (with threshold). */ 53 | auto it = context::points.begin(); 54 | auto it_best = context::points.end(); 55 | int best_dist = 100; /* min dist */ 56 | for (; it != context::points.end(); ++it) { 57 | const auto dist = (it->x - x) * (it->x - x) + (it->y - y) * (it->y - y); 58 | if (dist < best_dist) { 59 | it_best = it; 60 | best_dist = dist; 61 | } 62 | } 63 | if (it_best != context::points.end()) { 64 | context::points.erase(it_best); 65 | } 66 | break; 67 | } 68 | displayMe(); 69 | } 70 | 71 | int main(int argc, char** argv) 72 | { 73 | glutInit(&argc, argv); 74 | glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); 75 | glutInitWindowSize(600, 600); 76 | glutCreateWindow("Delaunay Triangulation demo"); 77 | glClearColor(0, 0, 0, 0); 78 | glPointSize(5); 79 | 80 | glutMouseFunc(mouse_callback); 81 | 82 | gluOrtho2D(0.0, glutGet(GLUT_WINDOW_WIDTH), 0, glutGet(GLUT_WINDOW_HEIGHT)); 83 | glutDisplayFunc(displayMe); 84 | glutMainLoop(); 85 | 86 | return 0; 87 | } 88 | --------------------------------------------------------------------------------