├── LICENSE ├── bowyer_watson.h ├── README.md ├── main.cpp └── bowyer_watson.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Joshua Brinsfield 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 | -------------------------------------------------------------------------------- /bowyer_watson.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BOWYER_WATSON_H_ 3 | #define BOWYER_WATSON_H_ 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | //BowyerWatson control class WARNING: make all methods static, or perhaps replace with a namespace? No real point to having a class here except for convenience. 11 | struct BowyerWatson 12 | { 13 | class Triangle; 14 | struct PointComparator; 15 | struct Mesh; 16 | 17 | static const unsigned int cache_line_size = 64; 18 | static const unsigned int point_size = sizeof(std::pair); 19 | 20 | //generate how_many points on a sphere, placing them into empty_vector (the unsigned ints are for storing the triangle to which they belong later) 21 | void generate_points(unsigned int how_many, std::vector > &empty_vector) const; 22 | 23 | //create an initial triangulation (8 triangles comprising two square pyramids sharing a base, using points from the pointset as vertices) 24 | //returns a pair consisting of the first and last triangle in a linked list of 8 triangles; the points passed are sorted and contained within them 25 | std::pair create_initial_triangles(std::vector > &points) const; 26 | std::pair create_initial_triangles(unsigned int how_many) const; //calls generate_points internally 27 | 28 | //create an initial triangulation (8 triangles comprising two square pyramids sharing a base, using the intersections of the x,y,z axes with the unit sphere as the vertices) 29 | //returns a pair consisting of the first and last triangle in a linked list of 8 triangles; the points passed are sorted and contained within them 30 | std::pair create_cardinal_triangles(std::vector > &points) const; 31 | std::pair create_cardinal_triangles(unsigned int how_many_minus_six) const; //calls generate_points internally 32 | 33 | //perform the Bowyer-Watson algorithm, returning a pointer to the first triangle in the resulting null-terminated linked list of triangles 34 | Triangle *perform(Triangle *start_triangle, Triangle *last_triangle); 35 | Triangle *perform(std::pair pair) { return(perform(pair.first, pair.second)); } 36 | 37 | //get a Mesh from the triangles, optionally deleting them afterwards WARNING: total_points is currently unused, but can be used for optimization 38 | Mesh get_mesh(Triangle *first, bool destructive = false, unsigned int total_points = 0); 39 | 40 | //takes the output of perform() and constructs a new pointset by replacing each point with the weighted average of the centroids of all triangles of which it is a vertex 41 | void relax_points(Triangle *start_triangle, std::vector > &empty_vector, bool kill_triangles = true) const; 42 | }; 43 | 44 | 45 | 46 | //class representing a spherical triangle on the unit sphere 47 | class BowyerWatson::Triangle 48 | { 49 | std::pair calculate_circumsphere() const; 50 | public: 51 | Eigen::Vector3f vertices[3]; //vertices in counterclockwise order 52 | Triangle *neighbors[3]; //neighbors[n] is along the edge given by vertices[(n+1)%3] and vertices[(n+2)%3] 53 | 54 | std::pair circumsphere; //(circumcenter, radius^2 of circumsphere) 55 | 56 | unsigned int pointcount; //the number of unprocessed points inside this triangle 57 | std::pair *points; //an array of unprocessed points inside this triangle; the unsigned int will store the index of the new triangle for the point to be moved into as the algorithm progresses 58 | Triangle *prev; //the previous triangle (all triangles are kept track of in a linked list) 59 | Triangle *next; //the next triangle (all triangles are kept track of in a linked list) 60 | 61 | bool tagged; //whether we have been tagged as processed during a neighbor check 62 | bool is_bad; //whether we are a bad triangle or not in the current neighbor check (bad triangles will be removed after sorting their points to new ones) 63 | 64 | //arguments: vertices 0, 1, and 2 in counter-clockwise order (viewed from above, outside the unit sphere); n0 is the neighbor triangle along edge (v1,v2), which is convenient within the algorithm to initialize in the constructor: can be initialized to nullptr otherwise 65 | Triangle(const Eigen::Vector3f &v0, const Eigen::Vector3f &v1, const Eigen::Vector3f &v2, Triangle *n0); 66 | ~Triangle(); //frees points array but does not screw with prev and next or do anything else 67 | 68 | //begins a neighbor_check, calling neighbor_check on neighboring triangles recursively; a neighbor-check finds all triangles whose circumspheres include origin; 69 | // total_points should start at 0, and will be incremented to count the points; 70 | // bads should begin empty and will be filled with triangles whose circumspheres include origin (these will be destroyed); 71 | // polygon should begin empty and will be filled with (v, (t,i)) pairs representing the non-bad triangles bordering the star-shaped hole resulting when the bad triangles are removed: 72 | // t is a pointer to the non-bad triangle 73 | // i is the index of the bad neighbor (vertices (i+1)%3 and (i+2)%3 define the edge shared with the bad neighbor) 74 | // v is the point on the edge shared with the bad neighbor which is immediately adjacent in the counter-clockwise direction to the other point on that edge, from the perspective of the hole; thus it is immediately adjacent in the clockwise direction from the perspective of t, i.e. it is vertex (i+2)%3 75 | void start_neighbor_check(Eigen::Vector3f &origin, unsigned int &total_points, std::vector &bads, std::map, PointComparator> &polygon); 76 | 77 | //recursively called by itself and start_neighbor_check 78 | void neighbor_check(Eigen::Vector3f &point, unsigned int &total_points, std::vector &bads, std::map, PointComparator> &polygon); 79 | 80 | //returns true if and only if point is contained by this triangle; used by create_initial_triangles() and useful as a general utility, but not used by the BW algorithm itself 81 | bool contains(const Eigen::Vector3f &point) const; 82 | }; 83 | 84 | 85 | //a comparison class to allow Eigen::Vector3f objects to be used in STL containers 86 | struct BowyerWatson::PointComparator 87 | { 88 | bool operator()(const Eigen::Vector3f &a, const Eigen::Vector3f &b) 89 | { 90 | if(a[0] != b[0]) 91 | return(a[0] < b[0]); 92 | if(a[1] != b[1]) 93 | return(a[1] < b[1]); 94 | return(a[2] < b[2]); 95 | } 96 | }; 97 | 98 | //a triangular mesh 99 | struct BowyerWatson::Mesh 100 | { 101 | std::vector vertices; //(vertices[3n], vertices[3n+1], vertices[3n+2]) = the nth point's x,y,z coordinates 102 | std::vector triangles; //( triangles[3n]th point, triangles[3n+1]th point, triangles[3n+2]th point ) = the nth triangle's vertices 103 | }; 104 | 105 | 106 | 107 | 108 | #endif /* BOWYER_WATSON_H_ */ 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BowyerWatson 2 | 3 | Implementation of the Bowyer-Watson algorithm (with modified Lloyd relaxation) for finding Delaunay triangulations of pointsets on the unit sphere. 4 | 5 | ## Execution 6 | 7 | The existing code generates `$pointcount` random points on the unit sphere (seeding srand with the seed $seed) and finds a triangulation using the Bowyer-Watson algorithm; this is performed `($relaxations + 1)` times with an application of modified Lloyd relaxation in between executions. The output is a `python2` file which utilizes `mpl_toolkits.mplot3d` and `matplotlib.pyplot` to present an interactive 3D rendering of the resulting mesh. Direct compilation of the code therefore results in an executable designed for testing the code's efficacy and viewing the results; (simple and straightforward) modifications are required to output the results in a different format, or to take the initial pointset from external input rather than randomly generating it--see the section on MODIFICATION below, as well as main.cpp and the comments in `bowyer_watson.h` for usage. 8 | 9 | The help menu can be accessed by passing `--help` to the executable as its only parameter. 10 | 11 | Otherwise, the expected parameters, all optional, and their defaults are as follows: 12 | 13 | 1) `$pointcount = 1000` : A positive integer specifying the number of points to generate. 14 | 2) `$relaxations = 5`: A nonnegative integer specifying the number of times to perform modified Lloyd relaxation. 15 | 3) `$filename = "bwoutput.py"`: A string designating the desired filename for the python2 script output. ".py" will be appended if absent. 16 | 4) `$seed = 666`: An integer to seed the random number generator (srand). 17 | 18 | ## Compilation 19 | 20 | You must compile with support for C++11. External requirements are limited to the Eigen header-only library and Intel Threading Building Blocks; the Eigen folder must be within an include directory, and the TBB library must be linked against. Suggested compilation parameters: 21 | 22 | g++ -ltbb -I"/wherever/eigen/is/located" main.cpp bowyer_watson.cpp 23 | 24 | ## Modification 25 | 26 | See `main.cpp` and the comments in `bowyer_watson.h` for usage. In short: 27 | 28 | (1) Initialize an `std::vector >` to contain the points to be used in the triangulation. These can be generated by `BowyerWatson::generate_points(int how_many_points_to_generate, std::vector<...> &vector_to_them_in)`. One will observe that the vector contains not mere points, but points paired with unsigned integers: the integer associated with a point should be between 0 and 7 inclusive, and can be arbitrary--but for maximal efficiency, the integer chosen should designate the quadrant of Euclidean 3-space containing the point according to the following legend (where `u/d` mean up/down, `n/s` mean north/south, and `e/w` mean east/west, and where the positive `z` axis is up, the positive `y` axis is north, and the positive `x` axis is east): `{ 0: uen, 1: unw, 2: use, 3: uws, 4: dne, 5: dwn, 6: des, 7: dsw }`. 29 | 30 | (2) Pass the point vector to `BowyerWatson::perform(BowyerWatson::create_initial_triangles(your_point_vector))`, obtaining a pointer to a `BowyerWatson::Triangle` as its return value. This triangle is the head of a linked list of all the triangles produced by the triangulation. Alternatively to create_initial_triangles, you may use `BowyerWatson::create_cardinal_triangles`; this will add 6 points to your pointset, one at each intersection of the unit sphere with each of the `x`, `y`, and `z` axes. 31 | 32 | (3) Call `BowyerWatson::relax_points(Triangle *triangle_pointer, vector<...> &point_vector)`, passing the triangle and a reference to your point vector (or to a new one). The point vector will be cleared and filled with the new points resulting from the relaxation procedure. Then call `BowyerWatson::perform(BowyerWatson::create_initial_triangles(point_vector))` on the point vector again to obtain a new triangulation. Do not use `create_cardinal_triangles` here, or you will end up inserting points at the intersections of the unit sphere with the coordinate axes repeatedly. Repeat this as often as you wish, depending on how much relaxation you wish to apply to the pointset. 33 | 34 | (4) Call `bw.get_mesh(triangle_pointer, true_to_call_delete_on_all_the_triangles_false_otherwise)` to obtain a `BowyerWatson::Mesh` object representing the triangle mesh you have created in a more useful form. The mesh consists of an `std::vector` storing the vertices (the Nth point's `x`,`y`,`z` coordinates are `vertices[3n]`, `vertices[3N+1]`, and `vertices[3N+2]`, respectively) and an `std::vector` storing the indices of the vertices belonging to particular triangles (the Mth triangles vertices are the `triangles[3M]`th, `triangles[3M+1]`th, and `triangles[3M+2]`th points, respectively, in counterclockwise order). 35 | 36 | NOTE: If you are seeking tilings of the sphere by Voronoi polygons, they can readily be constructed from the Delaunay triangulations produced by this program, being dual to them. 37 | 38 | ## Potential issues 39 | 40 | (1) There may be issues with pointsets containing multiple copies of the same point; at the very least, this will result in a mesh containing degenerate triangles. 41 | 42 | (2) create_initial_triangles (as opposed to create_cardinal_triangles) potentially has issues if your pointset is limited to a small area of the sphere or a very small number of points, because it constructs the initial triangulation using the 6 points which lie farthest up, down, north, south, east, and west, and does not currently verify that the resulting polyhedron is a valid and non-degenerate triangulation. You will certainly have issues if your pointset consists of fewer than 6 points. 43 | 44 | (3) Modified Lloyd relaxation may not produce nice results if your triangulation includes a triangle so large that it wraps around a significant portion of the sphere, because area and centroids are calculated in Euclidean 3-space; the assumption is that the algorithm will be used on sufficiently large and well-distributed pointsets that this does not become an issue. 45 | 46 | ## Notes on design 47 | 48 | (1) By "modified Lloyd relaxation" is meant the following: the area and centroid of each triangle is calculated in 3D Euclidean space, and the centroid is projected onto the surface of the unit sphere (hence the "modified": properly, this should be done in a spherical geometry, but provided the mesh is fairly dense this method is more efficient and suffices to obtain nearly the same results). Each point is then replaced by the centroid of the triangles which possess it as a vertex--that is, the average of the centroids of these triangles weighted by their respective areas, projected onto the unit sphere. 49 | 50 | (2) For purposes of efficiency in development, the algorithm does not maintain any structure of triangles for use in determining which triangle contains a point when that point is inserted. Instead, all points are generated at the outset, and the algorithm maintains within each triangle an array of all those points which are contained within it; when triangles are destroyed and replaced with others in the course of the algorithm, points contained within the destroyed triangles are copied into the appropriate new triangle. This benefits greatly from parallelization, though an efficient data structure for determining containment might be superior. 51 | 52 | (3) Intel's Threading Building Blocks library is utilized in two places: first, it is utilized in sorting points contained within triangles being removed into the appropriate newly created triangles, and second, it is utilized in actually copying the points into the new triangles. Special care is taken to minimize cache invalidations during these processes; the cache size is not queried from the system but is assumed to be 64, though this can be changed within the BowyerWatson class. The question of when datasets are sufficiently large to benefit from using Intel's parallel_for rather than processing them serially has been addressed by tweaking several parameters, which are marked by comments in the code, according to what values empirically gave the best results on my own machine. 53 | 54 | (4) Multithreading could also be used in point generation and mesh construction, as well as modified Lloyd relaxation, but at present it is not (in fact, the mesh generation algorithm could be greatly improved). Within the Bowyer-Watson algorithm, the process of discovering which triangles should be removed could be parallelized, and if multiple non-overlapping sets of removable triangles could be discovered simultaneously they could be processed in parallel; this, however, would require substantial modifications to the code and would likely require checks too complex to justify the benefit, and so it has not been done. 55 | 56 | ## License and attribution 57 | 58 | Created by Joshua Brinsfield (sapphous at gmail.com) in 2017. 59 | Released under the MIT license (see license file). 60 | I was unable to find a simple implementation of Delaunay triangulation on the sphere, so I made this. Have a blast, if this is useful to you. 61 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | using namespace std; 4 | 5 | #include "bowyer_watson.h" 6 | 7 | #include 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | //function to provide a first-order test of whether BowyerWatson::Triangle::contains() works correctly 13 | void test() 14 | { 15 | typedef BowyerWatson::Triangle Triangle; 16 | //create initial polyhedron 17 | Eigen::Vector3f u(0,0,1); 18 | Eigen::Vector3f e(1,0,0); 19 | Eigen::Vector3f n(0,1,0); 20 | Eigen::Vector3f w(-1,0,0); 21 | Eigen::Vector3f s(0,-1,0); 22 | Eigen::Vector3f d(0,0,-1); 23 | Triangle *uen = new Triangle(u, e, n, 0); 24 | Triangle *unw = new Triangle(u, n, w, 0); 25 | Triangle *uws = new Triangle(u, w, s, 0); 26 | Triangle *use = new Triangle(u, s, e, 0); 27 | Triangle *des = new Triangle(d, e, s, 0); 28 | Triangle *dsw = new Triangle(d, s, w, 0); 29 | Triangle *dwn = new Triangle(d, w, n, 0); 30 | Triangle *dne = new Triangle(d, n, e, 0); 31 | uen->neighbors[0] = dne; uen->neighbors[1] = unw; uen->neighbors[2] = use; 32 | unw->neighbors[0] = dwn; unw->neighbors[1] = uws; unw->neighbors[2] = uen; 33 | uws->neighbors[0] = dsw; uws->neighbors[1] = use; uws->neighbors[2] = unw; 34 | use->neighbors[0] = des; use->neighbors[1] = uen; use->neighbors[2] = uws; 35 | des->neighbors[0] = use; des->neighbors[1] = dsw; des->neighbors[2] = dne; 36 | dsw->neighbors[0] = uws; dsw->neighbors[1] = dwn; dsw->neighbors[2] = des; 37 | dwn->neighbors[0] = unw; dwn->neighbors[1] = dne; dwn->neighbors[2] = dsw; 38 | dne->neighbors[0] = uen; dne->neighbors[1] = des; dne->neighbors[2] = dwn; 39 | Triangle *tris[8] = { uen, unw, use, uws, dne, dwn, des, dsw }; 40 | //test point containment 41 | cout << "Should be ones:" << endl; 42 | for(unsigned int i = 0; i < 8; ++i) 43 | { 44 | Eigen::Vector3f to_test( 45 | 1.0f * (tris[i]->vertices[0][0] + tris[i]->vertices[1][0] + tris[i]->vertices[2][0]) / 3.0f, 46 | 1.0f * (tris[i]->vertices[0][1] + tris[i]->vertices[1][1] + tris[i]->vertices[2][1]) / 3.0f, 47 | 1.0f * (tris[i]->vertices[0][2] + tris[i]->vertices[1][2] + tris[i]->vertices[2][2]) / 3.0f 48 | ); 49 | cout << (tris[i]->contains(to_test) ? '1' : '0'); 50 | } 51 | cout << "\nShould be zeroes:" << endl; 52 | for(unsigned int i = 0; i < 8; ++i) 53 | { 54 | Eigen::Vector3f to_test( 55 | -1.0f * (tris[i]->vertices[0][0] + tris[i]->vertices[1][0] + tris[i]->vertices[2][0]) / 3.0f, 56 | -1.0f * (tris[i]->vertices[0][1] + tris[i]->vertices[1][1] + tris[i]->vertices[2][1]) / 3.0f, 57 | -1.0f * (tris[i]->vertices[0][2] + tris[i]->vertices[1][2] + tris[i]->vertices[2][2]) / 3.0f 58 | ); 59 | cout << (tris[i]->contains(to_test) ? '1' : '0'); 60 | } 61 | for(unsigned int j = 1; j < 8; ++j) 62 | for(unsigned int i = 0; i < 8; ++i) 63 | { 64 | Eigen::Vector3f to_test( 65 | 1.0f * (tris[i]->vertices[0][0] + tris[i]->vertices[1][0] + tris[i]->vertices[2][0]) / 3.0f, 66 | 1.0f * (tris[i]->vertices[0][1] + tris[i]->vertices[1][1] + tris[i]->vertices[2][1]) / 3.0f, 67 | 1.0f * (tris[i]->vertices[0][2] + tris[i]->vertices[1][2] + tris[i]->vertices[2][2]) / 3.0f 68 | ); 69 | cout << (tris[(i+j)%8]->contains(to_test) ? '1' : '0'); 70 | } 71 | cout << endl; 72 | } 73 | 74 | 75 | 76 | //arguments: (pointcount = 1000, relaxations = 5, filename = bwoutput.py, seed = 666) 77 | int main(int argc, char *argv[]) 78 | { 79 | unsigned int pointcount = 1000; 80 | unsigned int relaxations = 5; 81 | std::string output_filename("bwoutput.py"); 82 | unsigned int seed = 666; 83 | 84 | istringstream ss; 85 | bool input_erroneous(false); 86 | bool display_help(false); 87 | switch(argc) 88 | { 89 | case(5): 90 | ss.clear(); 91 | ss.str(argv[4]); 92 | if(!(ss >> seed)) 93 | { 94 | cout << "Received invalid input \"" << argv[4] << "\" as argument 4; this should be an integer specifying the seed to use for the random number generator. Use argument \"--help\" to see help menu." << endl; 95 | input_erroneous = true; 96 | } 97 | case(4): 98 | output_filename = argv[3]; 99 | if(output_filename.substr(output_filename.length() - 3) != ".py") 100 | output_filename += ".py"; 101 | case(3): 102 | ss.clear(); 103 | ss.str(argv[2]); 104 | if(!(ss >> relaxations)) 105 | { 106 | cout << "Received invalid input \"" << argv[2] << "\" as argument 2; this should be a nonnegative integer specifying the number of times to perform modified Lloyd relaxation. Use argument \"--help\" to see help menu." << endl; 107 | input_erroneous = true; 108 | } 109 | case(2): 110 | if(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-help") == 0 || strcmp(argv[1], "help") == 0 || strcmp(argv[1], "\"--help\"") == 0) 111 | { 112 | display_help = true; 113 | input_erroneous = true; 114 | break; 115 | } 116 | ss.clear(); 117 | ss.str(argv[1]); 118 | if(!(ss >> pointcount) || pointcount == 0) 119 | { 120 | cout << "Received invalid input \"" << argv[1] << "\" as argument 1; this should be a positive integer specifying the number of points to generate. Use argument \"--help\" to see help menu." << endl; 121 | input_erroneous = true; 122 | } 123 | case(1): 124 | break; 125 | default: 126 | display_help = true; //the OS provides argv[0], so this line should never be reached 127 | input_erroneous = true; 128 | } 129 | if(display_help) 130 | { 131 | cout << "\tBowyer-Watson Algorithm: Generates random points on the unit sphere, constructs a triangulation thereof, and creates a python file for viewing the resuling mesh.\n"; 132 | cout << "\tTo view this menu, pass \"--help\" as the first argument.\n"; 133 | cout << "\tArguments expected, in order, and their defaults:\n"; 134 | cout << "\t\tpointcount = 1000: a positive integer specifying the number of points to generate.\n"; 135 | cout << "\t\trelaxations = 5: a nonnegative integer specifying the number of times to relax the points (modified Lloyd relaxation).\n"; 136 | cout << "\t\tfilename = bwoutput.py: the filename for the output python file. If it does not end in \".py\", that extension will be appended to it.\n"; 137 | cout << "\t\tseed = 666: a nonnegative integer specifying the seed to be used for the random number generator used in generating points."; 138 | cout << endl; 139 | } 140 | if(input_erroneous) 141 | return(0); 142 | 143 | srand(seed); 144 | chrono::high_resolution_clock::time_point starttime; 145 | chrono::high_resolution_clock::time_point endtime; 146 | chrono::high_resolution_clock::time_point meshendtime; 147 | 148 | BowyerWatson bw; 149 | cout << "\tBeginning spherical Bowyer-Watson test with " << pointcount << " points & " << relaxations << " iterations of the relaxation algorithm..." << endl; 150 | starttime = chrono::high_resolution_clock::now(); 151 | 152 | std::vector > point_vector; 153 | bw.generate_points(pointcount, point_vector); 154 | BowyerWatson::Triangle *results = bw.perform(bw.create_initial_triangles(point_vector)); 155 | for(unsigned int relaxations_performed = 0; relaxations_performed < relaxations; ++relaxations_performed) 156 | { 157 | bw.relax_points(results, point_vector); 158 | results = bw.perform(bw.create_initial_triangles(point_vector)); 159 | } 160 | endtime = chrono::high_resolution_clock::now(); 161 | 162 | cout << "\tRunning time: " << chrono::duration_cast(endtime - starttime).count() << " milliseconds." << endl; 163 | cout << "\tCompleted calculations. Transforming to mesh..." << endl; 164 | BowyerWatson::Mesh mesh = bw.get_mesh(results, true); 165 | meshendtime = chrono::high_resolution_clock::now(); 166 | cout << "\tMesh running time: " << chrono::duration_cast(meshendtime - endtime).count() << " milliseconds." << endl; 167 | cout << "\tw00t, you got a mesh! Writing to file..." << endl; 168 | 169 | /////////////// 170 | ofstream file; 171 | file.open(output_filename); 172 | file << "from mpl_toolkits.mplot3d import Axes3D\n"; 173 | file << "from mpl_toolkits.mplot3d.art3d import Poly3DCollection\n"; 174 | file << "import matplotlib.pyplot as plt\n"; 175 | file << "fig = plt.figure()\n"; 176 | file << "ax = Axes3D(fig)\n"; 177 | for(unsigned int i = 0; i < mesh.triangles.size(); i += 3) 178 | { 179 | file << "ax.add_collection3d(Poly3DCollection([zip([" << 180 | mesh.vertices[mesh.triangles[i]] << "," << mesh.vertices[mesh.triangles[i+1]] << "," << mesh.vertices[mesh.triangles[i+2]] 181 | << "],[" << 182 | mesh.vertices[mesh.triangles[i]+1] << "," << mesh.vertices[mesh.triangles[i+1]+1] << "," << mesh.vertices[mesh.triangles[i+2]+1] 183 | << "],[" << 184 | mesh.vertices[mesh.triangles[i]+2] << "," << mesh.vertices[mesh.triangles[i+1]+2] << "," << mesh.vertices[mesh.triangles[i+2]+2] 185 | << "])], facecolors='w', edgecolors='b'))\n"; 186 | } 187 | file << "ax.set_xlim(-1.4, 1.4)\n"; 188 | file << "ax.set_ylim(-1.4, 1.4)\n"; 189 | file << "ax.set_zlim(-1.4, 1.4)\n"; 190 | file << "plt.show()\n"; 191 | file.close(); 192 | /////////////// 193 | 194 | cout << "\tBowyer-Watson test complete! Wrote to: " << output_filename << endl; 195 | return(0); 196 | } 197 | 198 | -------------------------------------------------------------------------------- /bowyer_watson.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "bowyer_watson.h" 4 | #include "tbb/parallel_for.h" 5 | #include "tbb/task_scheduler_init.h" 6 | 7 | //struct for Intel TBB to iterate over points in bad triangles (i.e. triangles to be removed from the triangulation) 8 | struct PointInterval 9 | { 10 | //MOOSE WARNING: this 7 is a result of some empirical tests & is tweakable. also points_per_run*sizeof(...) needs to divide cache_line_size to avoid cache line conflicts 11 | static const unsigned int points_per_run = 7 * BowyerWatson::cache_line_size / sizeof(std::pair); 12 | static bool worth_parallelizing(unsigned int total_points); 13 | 14 | struct CountNode //binary tree of unsigned-int-bearing nodes constructed by intel TBB before thead fork; each thread can count points in its own variable, and after they're done the main thread will sum them up and delete the structure 15 | { 16 | unsigned int count; 17 | CountNode *children[2]; 18 | CountNode() : count(0), children{nullptr, nullptr} {} 19 | ~CountNode() { if(children[0] != nullptr) delete children[0]; if(children[1] != nullptr) delete children[1]; } 20 | void sum_into(unsigned int &v) { v += count; if(children[0] != nullptr) children[0]->sum_into(v); if(children[1] != nullptr) children[1]->sum_into(v); } 21 | }; 22 | 23 | CountNode *countnode; 24 | std::vector &sources; //vector of triangles from which to take points 25 | unsigned int total_points; //how many points are in this interval? 26 | unsigned int triangle_start; //at what triangle in the sources vector does this interval start? 27 | unsigned int triangle_end; //at what triangle in the sources vector does this interval end? (inclusive) 28 | unsigned int point_start; //at what index in the point array within triangle_start does the interval start? 29 | unsigned int point_end; //at what index in the point array within triangle_end does the interval end? (exclusive) 30 | 31 | 32 | 33 | bool empty() const { return(total_points == 0); } 34 | bool is_divisible() const { return(total_points > 2 * points_per_run); } 35 | 36 | PointInterval(PointInterval& r, tbb::split) : 37 | countnode(new CountNode), 38 | sources(r.sources), total_points(r.total_points), 39 | triangle_start(r.triangle_start), triangle_end(r.triangle_end), 40 | point_start(r.point_start), point_end(r.point_end) 41 | { 42 | r.countnode->children[1] = countnode; 43 | r.countnode = (r.countnode->children[0] = new CountNode); 44 | while(total_points > r.total_points / 2) 45 | { 46 | if(sources[triangle_start]->pointcount - point_start <= points_per_run) 47 | { 48 | total_points -= sources[triangle_start]->pointcount - point_start; 49 | ++triangle_start; //we never need to worry about this taking us past sources.back(), since we're only going halfway through total_points 50 | point_start = 0; 51 | } 52 | else 53 | { 54 | total_points -= points_per_run; 55 | point_start += points_per_run; 56 | } 57 | } 58 | r.total_points -= total_points; 59 | r.triangle_end = triangle_start; 60 | r.point_end = point_start; 61 | } 62 | PointInterval(std::vector &sources_, CountNode *countnode_) : //sources_ is (bad) triangles to copy points from, countnode_ is Countnode to count points in 63 | countnode(countnode_), 64 | sources(sources_), total_points(0), 65 | triangle_start(0), triangle_end(sources.size() - 1), 66 | point_start(0), point_end(sources.back()->pointcount) 67 | { 68 | for(unsigned int i = 0; i < sources.size(); ++i) 69 | total_points += sources[i]->pointcount; 70 | } 71 | }; 72 | 73 | bool PointInterval::worth_parallelizing(unsigned int total_points) 74 | { 75 | return(total_points > 4 * points_per_run); //MOOSE WARNING: this is just a rough empirically good choice 76 | } 77 | 78 | 79 | //struct for finding those points within a PointInterval contained within a given triangle 80 | //WARNING: only to be used within BW algorithm, i.e. in perform(); makes the assumption that we are dealing with a triangular slice of a star-shaped region in determining containment 81 | struct PointSorter 82 | { 83 | unsigned int tag; //the tag to put in the points array to mark a point as belonging to our triangle 84 | Eigen::Vector3f n[2]; //the normal vectors to the appropriate edges of our triangle (we know we are in a star-shaped region, so the edge opposite to our center need not be checked) 85 | 86 | PointSorter(unsigned int tag_, BowyerWatson::Triangle *container) : 87 | tag(tag_), n{ 88 | container->vertices[0].cross(container->vertices[1]), 89 | container->vertices[2].cross(container->vertices[0]) 90 | } 91 | {} 92 | //takes the points in a PointInteval and checks whether they are within the triangle passed to the constructor 93 | void operator()(const PointInterval &interval) const 94 | { 95 | unsigned int count(0); 96 | std::pair *arr; 97 | unsigned int pindex = interval.point_start; 98 | unsigned int stoppoint; 99 | for(unsigned int trindex = interval.triangle_start; trindex <= interval.triangle_end; ++trindex) 100 | { 101 | //determine how many points to iterate 102 | if(trindex == interval.triangle_end) 103 | stoppoint = interval.point_end; 104 | else 105 | stoppoint = interval.sources[trindex]->pointcount; 106 | //create a pointer to the next point to read, and begin reading 107 | arr = interval.sources[trindex]->points; 108 | for(; pindex < stoppoint; ++pindex) 109 | { 110 | if(arr[pindex].second == (unsigned int)(-1) && arr[pindex].first.dot(n[0]) > 0 && arr[pindex].first.dot(n[1]) > 0) 111 | { 112 | arr[pindex].second = tag; 113 | ++count; 114 | } 115 | } 116 | //reset our point index to 0 117 | pindex = 0; 118 | } 119 | interval.countnode->count = count; 120 | } 121 | }; 122 | 123 | //struct for moving points from a set of to-be-destroyed triangles into the appropriate new triangles 124 | struct PointMover 125 | { 126 | std::vector &sources; 127 | std::vector &destinations; 128 | 129 | PointMover(std::vector &sources_, std::vector &destinations_) : 130 | sources(sources_), destinations(destinations_) 131 | {} 132 | 133 | void operator()(const tbb::blocked_range &interval) const 134 | { 135 | //create fun local variables for possible efficiency 136 | unsigned int begin = interval.begin(); 137 | unsigned int end = interval.end(); 138 | //allocate destination arrays and create a vector of pointers to their first elements (these will be incremented as we insert) 139 | std::vector*> dest_ptr; //dest_ptr[x-begin] is the address where the next point inside destinations[x] should be copied 140 | for(unsigned int k = begin; k < end; ++k) 141 | { 142 | //align array to cache boundary; the 2nd argument is size, & to ensure it is divisible by alignment (for some godawful reason the standard requires this), 143 | //we perform Ceiling(needed bytes / cache line size) * (cache line size) 144 | dest_ptr.push_back(destinations[k]->points = (std::pair*) aligned_alloc( 145 | BowyerWatson::cache_line_size, 146 | ((destinations[k]->pointcount * BowyerWatson::point_size + BowyerWatson::cache_line_size - 1) / BowyerWatson::cache_line_size) * BowyerWatson::cache_line_size 147 | )); 148 | } 149 | //copy points in sources[n] for all n whenever the point belongs in one of our destinations (those between destinations[begin] and destinations[end]) 150 | std::pair *to_check; 151 | for(unsigned int i = 0; i < sources.size(); ++i) 152 | { 153 | to_check = sources[i]->points; 154 | for(unsigned int j = 0; j < sources[i]->pointcount; ++to_check, ++j) 155 | if(to_check->second >= begin && to_check->second < end) 156 | *(dest_ptr[to_check->second - begin]++) = std::make_pair(to_check->first, (unsigned int)(-1)); 157 | } 158 | } 159 | }; 160 | 161 | 162 | std::pair BowyerWatson::Triangle::calculate_circumsphere() const 163 | { 164 | Eigen::Vector3f ac = vertices[2] - vertices[0]; 165 | Eigen::Vector3f ab = vertices[1] - vertices[0]; 166 | Eigen::Vector3f abXac = ab.cross(ac); 167 | Eigen::Vector3f aToCenter = (abXac.cross(ab) * ac.dot(ac) + ac.cross(abXac) * ab.dot(ab)) / (2.0f * abXac.dot(abXac)); 168 | return(std::make_pair( 169 | vertices[0] + aToCenter, 170 | aToCenter.dot(aToCenter) 171 | )); 172 | } 173 | 174 | 175 | BowyerWatson::Triangle::Triangle(const Eigen::Vector3f &v0, const Eigen::Vector3f &v1, const Eigen::Vector3f &v2, Triangle *n0) : 176 | vertices{v0, v1, v2}, neighbors{n0, nullptr, nullptr}, 177 | circumsphere(calculate_circumsphere()), 178 | pointcount(0), points(nullptr), prev(nullptr), next(nullptr), 179 | tagged(false), is_bad(false) 180 | { 181 | } 182 | 183 | BowyerWatson::Triangle::~Triangle() 184 | { 185 | if(points != nullptr) 186 | free(points); 187 | } 188 | 189 | 190 | void BowyerWatson::Triangle::start_neighbor_check(Eigen::Vector3f &origin, unsigned int &total_points, std::vector &bads, std::map, PointComparator> &polygon) 191 | { 192 | //we're bad 193 | tagged = true; 194 | is_bad = true; 195 | bads.push_back(this); 196 | total_points += pointcount; 197 | 198 | neighbors[0]->neighbor_check(origin, total_points, bads, polygon); 199 | if(!neighbors[1]->tagged) 200 | neighbors[1]->neighbor_check(origin, total_points, bads, polygon); 201 | if(!neighbors[2]->tagged) 202 | neighbors[2]->neighbor_check(origin, total_points, bads, polygon); 203 | } 204 | 205 | void BowyerWatson::Triangle::neighbor_check(Eigen::Vector3f &origin, unsigned int &total_points, std::vector &bads, std::map, PointComparator> &polygon) 206 | { 207 | //check whether we're bad 208 | Eigen::Vector3f diff = origin - circumsphere.first; 209 | if(diff.dot(diff) >= circumsphere.second) 210 | { 211 | //we're not bad 212 | tagged = true; 213 | is_bad = false; 214 | //if a neighbor is already known to be bad, it is our responsibility to put our border with that neighbor into polygon 215 | if(neighbors[0]->tagged && neighbors[0]->is_bad) polygon.insert(std::make_pair(vertices[2], std::make_pair(this, 0))); 216 | if(neighbors[1]->tagged && neighbors[1]->is_bad) polygon.insert(std::make_pair(vertices[0], std::make_pair(this, 1))); 217 | if(neighbors[2]->tagged && neighbors[2]->is_bad) polygon.insert(std::make_pair(vertices[1], std::make_pair(this, 2))); 218 | //no recursive calls to neighbor_check; all bad triangles have a bad neighbor, so there is no need to recurse from a non-bad triangle 219 | } 220 | else 221 | { 222 | //we're bad 223 | tagged = true; 224 | is_bad = true; 225 | bads.push_back(this); 226 | total_points += pointcount; 227 | //if a neighbor is already known to be non-bad, it is our responsibility to put our border with that neighbor into polygon 228 | if(neighbors[0]->tagged && !neighbors[0]->is_bad) polygon.insert(std::make_pair(vertices[1], std::make_pair(neighbors[0], 229 | neighbors[0]->vertices[0] == vertices[1] ? 1 : 230 | neighbors[0]->vertices[1] == vertices[1] ? 2 : 0 231 | ))); 232 | if(neighbors[1]->tagged && !neighbors[1]->is_bad) polygon.insert(std::make_pair(vertices[2], std::make_pair(neighbors[1], 233 | neighbors[1]->vertices[0] == vertices[2] ? 1 : 234 | neighbors[1]->vertices[1] == vertices[2] ? 2 : 0 235 | ))); 236 | if(neighbors[2]->tagged && !neighbors[2]->is_bad) polygon.insert(std::make_pair(vertices[0], std::make_pair(neighbors[2], 237 | neighbors[2]->vertices[0] == vertices[0] ? 1 : 238 | neighbors[2]->vertices[1] == vertices[0] ? 2 : 0 239 | ))); 240 | //recursively call neighbor_check on neighbors for whom it hasn't yet been called 241 | //WARNING: must check tagged before each of these calls, because the call to neighbors[0]->neighbor_check might tag neighbors[1], etc. 242 | if(!neighbors[0]->tagged) neighbors[0]->neighbor_check(origin, total_points, bads, polygon); 243 | if(!neighbors[1]->tagged) neighbors[1]->neighbor_check(origin, total_points, bads, polygon); 244 | if(!neighbors[2]->tagged) neighbors[2]->neighbor_check(origin, total_points, bads, polygon); 245 | } 246 | } 247 | 248 | bool BowyerWatson::Triangle::contains(const Eigen::Vector3f &point) const 249 | { 250 | Eigen::Matrix3f m; 251 | //check plane including vertices 0 and 1 252 | m.row(0) = vertices[0]; 253 | m.row(1) = vertices[1]; 254 | m.row(2) = point; 255 | if(m.determinant() < 0) 256 | return(false); 257 | //check plane including vertices 1 and 2 258 | m.row(0) = vertices[1]; 259 | m.row(1) = vertices[2]; 260 | m.row(2) = point; 261 | if(m.determinant() < 0) 262 | return(false); 263 | //check plane including vertices 2 and 0 264 | m.row(0) = vertices[2]; 265 | m.row(1) = vertices[0]; 266 | m.row(2) = point; 267 | return(m.determinant() >= 0); 268 | } 269 | 270 | 271 | BowyerWatson::Triangle *BowyerWatson::perform(Triangle *start_triangle, Triangle *last_triangle) 272 | { 273 | //initial setup 274 | Triangle *triangle = start_triangle; 275 | Eigen::Vector3f point; 276 | std::vector bads; 277 | std::map, PointComparator> polygon; //star-shaped border segments parameterized by clockwise-most vertex of each 278 | std::vector created; 279 | unsigned int total_points; 280 | //iterate through all triangles in the linked list 281 | while(triangle != 0) 282 | { 283 | //skip triangle if it contains no points 284 | if(triangle->pointcount == 0) 285 | { 286 | triangle = triangle->next; 287 | continue; 288 | } 289 | point = triangle->points[triangle->pointcount - 1].first; //get the last point in the array to be our new vertex 290 | --triangle->pointcount; //array is deleted using free, so this variable can lie; make it lie to hide the point we're using as a vertex so that it won't be copied into newly created triangles 291 | //find bad triangles and bounding polygon 292 | total_points = 0; 293 | bads.clear(); 294 | polygon.clear(); 295 | triangle->start_neighbor_check(point, total_points, bads, polygon); 296 | //create new triangles 297 | created.clear(); 298 | std::map, PointComparator>::iterator cur_edge = polygon.begin(); 299 | do 300 | { 301 | //untag the triangle that won't get deleted 302 | cur_edge->second.first->tagged = false; 303 | //create the new triangle 304 | created.push_back(cur_edge->second.first->neighbors[cur_edge->second.second] = new Triangle( 305 | point, 306 | cur_edge->second.first->vertices[cur_edge->second.second > 0 ? cur_edge->second.second - 1 : cur_edge->second.second + 2], 307 | cur_edge->second.first->vertices[cur_edge->second.second > 1 ? cur_edge->second.second - 2 : cur_edge->second.second + 1], 308 | cur_edge->second.first 309 | )); 310 | //move counterclockwise around the perimeter of our polygon 311 | cur_edge = polygon.find(cur_edge->second.first->vertices[cur_edge->second.second > 1 ? cur_edge->second.second - 2 : cur_edge->second.second + 1]); 312 | } while(cur_edge != polygon.begin()); //once we've travelled all the way around the polygon back to our starting point, we stop 313 | //connect new triangles to one another 314 | created[0]->neighbors[1] = created[1]; 315 | created[0]->neighbors[2] = created.back(); 316 | created.back()->neighbors[1] = created[0]; 317 | created.back()->neighbors[2] = created[created.size()-2]; //there are always at least 3 triangles created, since the point lies in *triangle 318 | for(unsigned int i = 1; i < created.size() - 1; ++i) 319 | { 320 | created[i]->neighbors[1] = created[i+1]; 321 | created[i]->neighbors[2] = created[i-1]; 322 | } 323 | //determine which new triangles to put points into 324 | if(PointInterval::worth_parallelizing(total_points)) 325 | { 326 | for(unsigned int i = 0; i < created.size(); ++i) 327 | { 328 | //go through all points in the bad triangles to find those that should be in created[i] 329 | PointInterval::CountNode *countnode = new PointInterval::CountNode(); 330 | tbb::parallel_for(PointInterval(bads, countnode), PointSorter(i, created[i])); 331 | //record the count of those points 332 | countnode->sum_into(created[i]->pointcount); 333 | delete countnode; 334 | } 335 | } 336 | else 337 | { 338 | for(unsigned int i = 0; i < created.size(); ++i) 339 | { 340 | //go through all points in the bad triangles to find those that should be in created[i] 341 | PointInterval::CountNode *countnode = new PointInterval::CountNode(); 342 | PointSorter(i, created[i])(PointInterval(bads, countnode)); 343 | //record the count of those points 344 | countnode->sum_into(created[i]->pointcount); 345 | delete countnode; 346 | } 347 | } 348 | //copy points into new triangles MOOSE WARNING: call a function to check whether parallelization is worth it, rather than doing it inline (450 is empirically tested as a pretty good cutoff; may not be optimal) 349 | if(total_points > 450) 350 | tbb::parallel_for(tbb::blocked_range(0, created.size()), PointMover(bads, created)); 351 | else 352 | PointMover(bads, created)(tbb::blocked_range(0, created.size())); 353 | //add new triangles to the linked list (empty ones to the start, others to the end) 354 | for(unsigned int i = 0; i < created.size(); ++i) 355 | { 356 | if(created[i]->pointcount == 0) 357 | { 358 | start_triangle->prev = created[i]; 359 | created[i]->next = start_triangle; 360 | start_triangle = created[i]; 361 | } 362 | else 363 | { 364 | last_triangle->next = created[i]; 365 | created[i]->prev = last_triangle; 366 | last_triangle = created[i]; 367 | } 368 | } 369 | //destroy old bad triangles 370 | for(unsigned int i = 0; i < bads.size(); ++i) 371 | { 372 | if(bads[i] == start_triangle) start_triangle = bads[i]->next; 373 | else bads[i]->prev->next = bads[i]->next; //prev != nullptr since we are not *start_triangle 374 | if(bads[i] == last_triangle) last_triangle = bads[i]->prev; 375 | else bads[i]->next->prev = bads[i]->prev; //next != nullptr since we are not *last_triangle 376 | if(bads[i] == triangle) 377 | triangle = triangle->next; 378 | delete bads[i]; 379 | } 380 | } 381 | return(start_triangle); 382 | } 383 | 384 | 385 | void BowyerWatson::generate_points(unsigned int how_many, std::vector > &created) const 386 | { 387 | created.clear(); 388 | created.reserve(how_many); 389 | float rand1; 390 | float rand2; 391 | for(unsigned int i = 0; i < how_many; ++i) 392 | { 393 | //create the new point 394 | rand1 = ((float)rand())/((float)RAND_MAX) * 2.0f - 1.0f; 395 | rand2 = ((float)rand())/((float)RAND_MAX) * 3.14159265358979323846f * 2.0f; 396 | created.push_back(std::make_pair(Eigen::Vector3f( 397 | sqrtf(1.0f - rand1*rand1) * cos(rand2), 398 | sqrtf(1.0f - rand1*rand1) * sin(rand2), 399 | rand1 400 | ), 0)); 401 | //guess which triangle contains the new point under the assumption that the critical points end up exactly at (1,0,0), (-1,0,0), (0,1,0), etc. 402 | //WARNING: the indices here refer to the by_number array of triangles, created later 403 | if(created.back().first[0] < 0) created.back().second |= 1; 404 | if(created.back().first[1] < 0) created.back().second |= 2; 405 | if(created.back().first[2] < 0) created.back().second |= 4; 406 | } 407 | } 408 | 409 | std::pair BowyerWatson::create_initial_triangles(unsigned int how_many) const 410 | { 411 | std::vector > created; 412 | generate_points(how_many, created); 413 | return(create_initial_triangles(created)); 414 | } 415 | 416 | std::pair BowyerWatson::create_initial_triangles(std::vector > &created) const 417 | { 418 | unsigned int how_many = created.size(); 419 | //find the critical points closest to the intersections of the x,y,z axes with the unit sphere 420 | float max[3] = { -2.0, -2.0, -2.0 }; 421 | float min[3] = { 2.0, 2.0, 2.0 }; 422 | unsigned int critical_index[6] = { 0, 0, 0, 0, 0, 0 }; 423 | unsigned int *max_index = &critical_index[0]; 424 | unsigned int *min_index = &critical_index[3]; 425 | for(unsigned int i = 0; i < how_many; ++i) 426 | { 427 | //update the critical points 428 | for(unsigned int d = 0; d < 3; ++d) 429 | { 430 | if(created[i].first[d] > max[d]) 431 | { 432 | max[d] = created[i].first[d]; 433 | max_index[d] = i; 434 | } 435 | if(created[i].first[d] < min[d]) 436 | { 437 | min[d] = created[i].first[d]; 438 | min_index[d] = i; 439 | } 440 | } 441 | } 442 | //create initial polyhedron triangles from critical points MOOSE WARNING: verify that this is a valid convex hull--should always work for well-distributed pointsets, but need to prevent errors on weird input data 443 | Eigen::Vector3f &u = created[max_index[2]].first; 444 | Eigen::Vector3f &e = created[max_index[0]].first; 445 | Eigen::Vector3f &n = created[max_index[1]].first; 446 | Eigen::Vector3f &w = created[min_index[0]].first; 447 | Eigen::Vector3f &s = created[min_index[1]].first; 448 | Eigen::Vector3f &d = created[min_index[2]].first; 449 | Triangle *uen = new Triangle(u, e, n, 0); 450 | Triangle *unw = new Triangle(u, n, w, 0); 451 | Triangle *uws = new Triangle(u, w, s, 0); 452 | Triangle *use = new Triangle(u, s, e, 0); 453 | Triangle *des = new Triangle(d, e, s, 0); 454 | Triangle *dsw = new Triangle(d, s, w, 0); 455 | Triangle *dwn = new Triangle(d, w, n, 0); 456 | Triangle *dne = new Triangle(d, n, e, 0); 457 | uen->neighbors[0] = dne; uen->neighbors[1] = unw; uen->neighbors[2] = use; 458 | unw->neighbors[0] = dwn; unw->neighbors[1] = uws; unw->neighbors[2] = uen; 459 | uws->neighbors[0] = dsw; uws->neighbors[1] = use; uws->neighbors[2] = unw; 460 | use->neighbors[0] = des; use->neighbors[1] = uen; use->neighbors[2] = uws; 461 | des->neighbors[0] = use; des->neighbors[1] = dsw; des->neighbors[2] = dne; 462 | dsw->neighbors[0] = uws; dsw->neighbors[1] = dwn; dsw->neighbors[2] = des; 463 | dwn->neighbors[0] = unw; dwn->neighbors[1] = dne; dwn->neighbors[2] = dsw; 464 | dne->neighbors[0] = uen; dne->neighbors[1] = des; dne->neighbors[2] = dwn; 465 | //put our triangles into an array and connect them into a linked list 466 | Triangle *by_number[8] = { uen, unw, use, uws, dne, dwn, des, dsw }; 467 | for(unsigned int i = 0; i < 7; ++i) 468 | { 469 | by_number[i]->next = by_number[i+1]; 470 | by_number[i+1]->prev = by_number[i]; 471 | } 472 | //sort critical point indices WARNING: max_index and min_index are invalidated by the sorting, but they aren't used again 473 | for(unsigned int n = 0; n < 5; ++n) 474 | { 475 | unsigned int least(n); 476 | for(unsigned int i = n + 1; i < 6; ++i) 477 | if(critical_index[i] < critical_index[least]) 478 | least = i; 479 | if(least != n) 480 | { 481 | //swap critical_index[least] and critical_index[n] 482 | critical_index[least] ^= critical_index[n]; 483 | critical_index[n] ^= critical_index[least]; 484 | critical_index[least] ^= critical_index[n]; 485 | } 486 | } 487 | //determine which triangle contains each point 488 | { // scope increase to prevent i from polluting enclosing scope 489 | unsigned int i = 0; 490 | for(unsigned int skip_index = 0; skip_index < 7; ++skip_index) 491 | { 492 | unsigned int last = (skip_index == 6 ? how_many : critical_index[skip_index]); 493 | for(; i < last; ++i) 494 | { 495 | //check the expected triangle 496 | if( by_number[created[i].second]->contains(created[i].first) ) 497 | ; //do nothing 498 | //check triangles sharing an edge with the expected triangle 499 | else if( by_number[(created[i].second ^ 1) & 7]->contains(created[i].first) ) 500 | created[i].second = (created[i].second ^ 1) & 7; 501 | else if( by_number[(created[i].second ^ 2) & 7]->contains(created[i].first) ) 502 | created[i].second = (created[i].second ^ 2) & 7; 503 | else if( by_number[(created[i].second ^ 4) & 7]->contains(created[i].first) ) 504 | created[i].second = (created[i].second ^ 4) & 7; 505 | //check triangles sharing a vertex with the expected triangle 506 | else if( by_number[(created[i].second ^ 6) & 7]->contains(created[i].first) ) 507 | created[i].second = (created[i].second ^ 6) & 7; 508 | else if( by_number[(created[i].second ^ 5) & 7]->contains(created[i].first) ) 509 | created[i].second = (created[i].second ^ 5) & 7; 510 | else if( by_number[(created[i].second ^ 3) & 7]->contains(created[i].first) ) 511 | created[i].second = (created[i].second ^ 3) & 7; 512 | //our point is contained in the triangle opposite to the expected triangle 513 | else 514 | created[i].second = (~created[i].second) & 7; 515 | //increment the appropriate triangle's pointcount 516 | ++by_number[created[i].second]->pointcount; 517 | } 518 | ++i; 519 | } 520 | } 521 | //allocate space for points within triangles 522 | std::vector*> dest_ptr; //dest_ptr[x] is the address where the next point inside destinations[x] should be copied 523 | for(unsigned int i = 0; i < 8; ++i) 524 | { 525 | dest_ptr.push_back(by_number[i]->points = (std::pair*) aligned_alloc( //align to cache so perform()'s multi-threaded RW operations can be optimized 526 | BowyerWatson::cache_line_size, 527 | ((by_number[i]->pointcount * BowyerWatson::point_size + BowyerWatson::cache_line_size - 1) / BowyerWatson::cache_line_size) * BowyerWatson::cache_line_size 528 | )); 529 | } 530 | //move points into triangles 531 | { // scope increase to prevent i from polluting enclosing scope 532 | unsigned int i = 0; 533 | for(unsigned int skip_index = 0; skip_index < 7; ++skip_index) 534 | { 535 | unsigned int last = (skip_index == 6 ? how_many : critical_index[skip_index]); 536 | for(; i < last; ++i) 537 | *(dest_ptr[created[i].second]++) = std::make_pair(created[i].first, (unsigned int)(-1)); 538 | ++i; 539 | } 540 | } 541 | //return triangles 542 | return(std::make_pair(by_number[0], by_number[7])); 543 | } 544 | 545 | std::pair BowyerWatson::create_cardinal_triangles(unsigned int how_many_minus_six) const 546 | { 547 | std::vector > created; 548 | generate_points(how_many_minus_six, created); 549 | return(create_cardinal_triangles(created)); 550 | } 551 | 552 | std::pair BowyerWatson::create_cardinal_triangles(std::vector > &created) const 553 | { 554 | unsigned int how_many = created.size(); 555 | //create initial polyhedron 556 | Eigen::Vector3f u(0,0,1); 557 | Eigen::Vector3f e(1,0,0); 558 | Eigen::Vector3f n(0,1,0); 559 | Eigen::Vector3f w(-1,0,0); 560 | Eigen::Vector3f s(0,-1,0); 561 | Eigen::Vector3f d(0,0,-1); 562 | Triangle *uen = new Triangle(u, e, n, 0); 563 | Triangle *unw = new Triangle(u, n, w, 0); 564 | Triangle *uws = new Triangle(u, w, s, 0); 565 | Triangle *use = new Triangle(u, s, e, 0); 566 | Triangle *des = new Triangle(d, e, s, 0); 567 | Triangle *dsw = new Triangle(d, s, w, 0); 568 | Triangle *dwn = new Triangle(d, w, n, 0); 569 | Triangle *dne = new Triangle(d, n, e, 0); 570 | uen->neighbors[0] = dne; uen->neighbors[1] = unw; uen->neighbors[2] = use; 571 | unw->neighbors[0] = dwn; unw->neighbors[1] = uws; unw->neighbors[2] = uen; 572 | uws->neighbors[0] = dsw; uws->neighbors[1] = use; uws->neighbors[2] = unw; 573 | use->neighbors[0] = des; use->neighbors[1] = uen; use->neighbors[2] = uws; 574 | des->neighbors[0] = use; des->neighbors[1] = dsw; des->neighbors[2] = dne; 575 | dsw->neighbors[0] = uws; dsw->neighbors[1] = dwn; dsw->neighbors[2] = des; 576 | dwn->neighbors[0] = unw; dwn->neighbors[1] = dne; dwn->neighbors[2] = dsw; 577 | dne->neighbors[0] = uen; dne->neighbors[1] = des; dne->neighbors[2] = dwn; 578 | //create an array of our triangles and connect them into a linked list 579 | Triangle *by_number[8] = { uen, unw, use, uws, dne, dwn, des, dsw }; 580 | for(unsigned int i = 0; i < 7; ++i) 581 | { 582 | by_number[i]->next = by_number[i+1]; 583 | by_number[i+1]->prev = by_number[i]; 584 | } 585 | //count the number of points per triangle 586 | for(unsigned int i = 0; i < how_many; ++i) 587 | ++by_number[created[i].second]->pointcount; 588 | //allocate space for points within triangles 589 | std::vector*> dest_ptr; //dest_ptr[x] is the address where the next point inside destinations[x] should be copied 590 | for(unsigned int i = 0; i < 8; ++i) 591 | { 592 | dest_ptr.push_back(by_number[i]->points = (std::pair*) aligned_alloc( 593 | BowyerWatson::cache_line_size, 594 | ((by_number[i]->pointcount * BowyerWatson::point_size + BowyerWatson::cache_line_size - 1) / BowyerWatson::cache_line_size) * BowyerWatson::cache_line_size 595 | )); 596 | } 597 | //move points into triangles 598 | for(unsigned int i = 0; i < created.size(); ++i) 599 | *(dest_ptr[created[i].second]++) = std::make_pair(created[i].first, (unsigned int)(-1)); 600 | //return triangles 601 | return(std::make_pair(by_number[0], by_number[7])); 602 | } 603 | 604 | 605 | BowyerWatson::Mesh BowyerWatson::get_mesh(Triangle *first, bool destructive, unsigned int total_points) 606 | { 607 | //MOOSE WARNING: we don't use total_points, because this is an inefficient & crap algorithm that can't make use of it; 608 | //this function is to be rewritten later 609 | Mesh mesh; 610 | std::map>, PointComparator> points_to_triangles; 611 | unsigned int triangle_index = 0; 612 | for(Triangle *triangle = first; triangle != nullptr; triangle = triangle->next) 613 | { 614 | points_to_triangles[triangle->vertices[0]].push_back(std::make_pair(triangle_index, 0)); 615 | points_to_triangles[triangle->vertices[1]].push_back(std::make_pair(triangle_index, 1)); 616 | points_to_triangles[triangle->vertices[2]].push_back(std::make_pair(triangle_index, 2)); 617 | ++triangle_index; 618 | } 619 | mesh.triangles.resize(triangle_index * 3); 620 | mesh.vertices.reserve(points_to_triangles.size() * 3); 621 | for(std::map >, PointComparator>::iterator iter = points_to_triangles.begin(); iter != points_to_triangles.end(); ++iter) 622 | { 623 | for(unsigned int i = 0; i < iter->second.size(); ++i) 624 | mesh.triangles[iter->second[i].first * 3 + iter->second[i].second] = mesh.vertices.size(); 625 | mesh.vertices.push_back(iter->first[0]); 626 | mesh.vertices.push_back(iter->first[1]); 627 | mesh.vertices.push_back(iter->first[2]); 628 | } 629 | if(destructive) 630 | { 631 | Triangle *temp; 632 | while(first != 0) 633 | { 634 | temp = first->next; 635 | delete first; 636 | first = temp; 637 | } 638 | } 639 | return(mesh); 640 | } 641 | 642 | 643 | 644 | 645 | void BowyerWatson::relax_points(Triangle *start_triangle, std::vector > &empty_vector, bool kill_triangles) const 646 | { 647 | //calculate pre-centroids 648 | std::map, PointComparator> pre_centroids; //(point affected, (area-weighted sum of centroids, total area)) 649 | for(Triangle *tri = start_triangle; tri != nullptr; tri = tri->next) 650 | { 651 | float area = (tri->vertices[1] - tri->vertices[0]).cross(tri->vertices[2] - tri->vertices[0]).norm() / 2.0f; 652 | Eigen::Vector3f centroid_times_area = area * (tri->vertices[0] + tri->vertices[1] + tri->vertices[2]) / 3.0f; 653 | for(unsigned int i = 0; i < 3; ++i) 654 | { 655 | std::pair, PointComparator>::iterator, bool> insert_data = pre_centroids.insert( 656 | std::make_pair(tri->vertices[i], std::make_pair(centroid_times_area, area)) 657 | ); 658 | if(!insert_data.second) 659 | { 660 | insert_data.first->second.first += centroid_times_area; 661 | insert_data.first->second.second += area; 662 | } 663 | } 664 | } 665 | //calculate centroids & place in empty_vector 666 | std::vector > &created = empty_vector; 667 | created.clear(); 668 | created.reserve(pre_centroids.size()); 669 | for(std::map, PointComparator>::iterator cur_point = pre_centroids.begin(); cur_point != pre_centroids.end(); ++cur_point) 670 | { 671 | created.push_back(std::make_pair(cur_point->second.first / cur_point->second.second, 0)); 672 | created.back().first /= created.back().first.norm(); 673 | //guess which triangle contains the new point under the assumption that the critical points end up exactly at (1,0,0), (-1,0,0), (0,1,0), etc. 674 | //WARNING: the indices here refer to the by_number array of triangles, created later 675 | if(created.back().first[0] < 0) created.back().second |= 1; 676 | if(created.back().first[1] < 0) created.back().second |= 2; 677 | if(created.back().first[2] < 0) created.back().second |= 4; 678 | } 679 | //kill the triangles if requested 680 | if(kill_triangles) 681 | for(Triangle *tri = start_triangle; tri != nullptr; tri = tri->next) 682 | delete tri; 683 | } 684 | 685 | 686 | 687 | 688 | 689 | --------------------------------------------------------------------------------