/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include #include #include #include // Boost pool: Don't use mutexes to synchronize memory allocation. #define BOOST_POOL_NO_MT #include #include "stl.h" struct HashEdge { // Key of a hash edge: sorted vertices of the edge. uint32_t key[6]; // Compare two keys. bool operator==(const HashEdge &rhs) const { return memcmp(key, rhs.key, sizeof(key)) == 0; } bool operator!=(const HashEdge &rhs) const { return ! (*this == rhs); } int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } // Index of a facet owning this edge. int facet_number; // Index of this edge inside the facet with an index of facet_number. // If this edge is stored backwards, which_edge is increased by 3. int which_edge; HashEdge *next; void load_exact(stl_file *stl, const stl_vertex *a, const stl_vertex *b) { { stl_vertex diff = (*a - *b).cwiseAbs(); float max_diff = std::max(diff(0), std::max(diff(1), diff(2))); stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge); } // Ensure identical vertex ordering of equal edges. // This method is numerically robust. if (vertex_lower(*a, *b)) { } else { // This edge is loaded backwards. std::swap(a, b); this->which_edge += 3; } memcpy(&this->key[0], a->data(), sizeof(stl_vertex)); memcpy(&this->key[3], b->data(), sizeof(stl_vertex)); // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. for (size_t i = 0; i < 6; ++ i) { unsigned char *p = (unsigned char*)(this->key + i); #if BOOST_ENDIAN_LITTLE_BYTE if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) // Negative zero, switch to positive zero. p[3] = 0; #else /* BOOST_ENDIAN_LITTLE_BYTE */ if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) // Negative zero, switch to positive zero. p[0] = 0; #endif /* BOOST_ENDIAN_LITTLE_BYTE */ } } bool load_nearby(const stl_file *stl, const stl_vertex &a, const stl_vertex &b, float tolerance) { // Index of a grid cell spaced by tolerance. typedef Eigen::Matrix Vec3i; Vec3i vertex1 = ((a - stl->stats.min) / tolerance).cast(); Vec3i vertex2 = ((b - stl->stats.min) / tolerance).cast(); static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); if (vertex1 == vertex2) // Both vertices hash to the same value return false; // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. // This method is numerically robust. if ((vertex1[0] != vertex2[0]) ? (vertex1[0] < vertex2[0]) : ((vertex1[1] != vertex2[1]) ? (vertex1[1] < vertex2[1]) : (vertex1[2] < vertex2[2]))) { memcpy(&this->key[0], vertex1.data(), sizeof(stl_vertex)); memcpy(&this->key[3], vertex2.data(), sizeof(stl_vertex)); } else { memcpy(&this->key[0], vertex2.data(), sizeof(stl_vertex)); memcpy(&this->key[3], vertex1.data(), sizeof(stl_vertex)); this->which_edge += 3; /* this edge is loaded backwards */ } return true; } private: inline bool vertex_lower(const stl_vertex &a, const stl_vertex &b) { return (a(0) != b(0)) ? (a(0) < b(0)) : ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); } }; struct HashTableEdges { HashTableEdges(size_t number_of_faces) { this->M = (int)hash_size_from_nr_faces(number_of_faces); this->heads.assign(this->M, nullptr); this->tail = pool.construct(); this->tail->next = this->tail; for (int i = 0; i < this->M; ++ i) this->heads[i] = this->tail; } ~HashTableEdges() { #ifndef NDEBUG for (int i = 0; i < this->M; ++ i) for (HashEdge *temp = this->heads[i]; temp != this->tail; temp = temp->next) ++ this->freed; this->tail = nullptr; #endif /* NDEBUG */ } void insert_edge_exact(stl_file *stl, const HashEdge &edge) { this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { record_neighbors(stl, edge1, edge2); }); } void insert_edge_nearby(stl_file *stl, const HashEdge &edge) { this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { match_neighbors_nearby(stl, edge1, edge2); }); } // Hash table on edges std::vector heads; HashEdge* tail; int M; boost::object_pool pool; #ifndef NDEBUG size_t malloced = 0; size_t freed = 0; size_t collisions = 0; #endif /* NDEBUG */ private: static inline size_t hash_size_from_nr_faces(const size_t nr_faces) { // Good primes for addressing a cca. 30 bit space. // https://planetmath.org/goodhashtableprimes static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; // Find a prime number for 50% filling of the shared triangle edges in the mesh. auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); return (it == primes.end()) ? primes.back() : *it; } // MatchNeighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) template void insert_edge(stl_file *stl, const HashEdge &edge, MatchNeighbors match_neighbors) { int chain_number = edge.hash(this->M); HashEdge *link = this->heads[chain_number]; if (link == this->tail) { // This list doesn't have any edges currently in it. Add this one. HashEdge *new_edge = pool.construct(edge); #ifndef NDEBUG ++ this->malloced; #endif /* NDEBUG */ new_edge->next = this->tail; this->heads[chain_number] = new_edge; } else if (edges_equal(edge, *link)) { // This is a match. Record result in neighbors list. match_neighbors(edge, *link); // Delete the matched edge from the list. this->heads[chain_number] = link->next; // pool.destroy(link); #ifndef NDEBUG ++ this->freed; #endif /* NDEBUG */ } else { // Continue through the rest of the list. for (;;) { if (link->next == this->tail) { // This is the last item in the list. Insert a new edge. HashEdge *new_edge = pool.construct(); #ifndef NDEBUG ++ this->malloced; #endif /* NDEBUG */ *new_edge = edge; new_edge->next = this->tail; link->next = new_edge; #ifndef NDEBUG ++ this->collisions; #endif /* NDEBUG */ break; } if (edges_equal(edge, *link->next)) { // This is a match. Record result in neighbors list. match_neighbors(edge, *link->next); // Delete the matched edge from the list. HashEdge *temp = link->next; link->next = link->next->next; // pool.destroy(temp); #ifndef NDEBUG ++ this->freed; #endif /* NDEBUG */ break; } // This is not a match. Go to the next link. link = link->next; #ifndef NDEBUG ++ this->collisions; #endif /* NDEBUG */ } } } // Edges equal for hashing. Edgesof different facet are allowed to be matched. static inline bool edges_equal(const HashEdge &edge_a, const HashEdge &edge_b) { return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b; } // Connect edge_a with edge_b, update edge connection statistics. static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) { // Facet a's neighbor is facet b stl->neighbors_start[edge_a.facet_number].neighbor[edge_a.which_edge % 3] = edge_b.facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] = (edge_b.which_edge + 2) % 3; /* sets the .which_vertex_not part */ // Facet b's neighbor is facet a stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */ if ((edge_a.which_edge < 3 && edge_b.which_edge < 3) || (edge_a.which_edge > 2 && edge_b.which_edge > 2)) { // These facets are oriented in opposite directions, their normals are probably messed up. stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3; stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3; } // Count successful connects: // Total connects: stl->stats.connected_edges += 2; // Count individual connects: switch (stl->neighbors_start[edge_a.facet_number].num_neighbors()) { case 1: ++ stl->stats.connected_facets_1_edge; break; case 2: ++ stl->stats.connected_facets_2_edge; break; case 3: ++ stl->stats.connected_facets_3_edge; break; default: assert(false); } switch (stl->neighbors_start[edge_b.facet_number].num_neighbors()) { case 1: ++ stl->stats.connected_facets_1_edge; break; case 2: ++ stl->stats.connected_facets_2_edge; break; case 3: ++ stl->stats.connected_facets_3_edge; break; default: assert(false); } } static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) { record_neighbors(stl, edge_a, edge_b); // Which vertices to change int facet1 = -1; int facet2 = -1; int vertex1, vertex2; stl_vertex new_vertex1, new_vertex2; { int v1a; // pair 1, facet a int v1b; // pair 1, facet b int v2a; // pair 2, facet a int v2b; // pair 2, facet b // Find first pair. if (edge_a.which_edge < 3) { v1a = edge_a.which_edge; v2a = (edge_a.which_edge + 1) % 3; } else { v2a = edge_a.which_edge % 3; v1a = (edge_a.which_edge + 1) % 3; } if (edge_b.which_edge < 3) { v1b = edge_b.which_edge; v2b = (edge_b.which_edge + 1) % 3; } else { v2b = edge_b.which_edge % 3; v1b = (edge_b.which_edge + 1) % 3; } // Of the first pair, which vertex, if any, should be changed if (stl->facet_start[edge_a.facet_number].vertex[v1a] != stl->facet_start[edge_b.facet_number].vertex[v1b]) { // These facets are different. if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v1a] == -1) && (stl->neighbors_start[edge_a.facet_number].neighbor[(v1a + 2) % 3] == -1)) { // This vertex has no neighbors. This is a good one to change. facet1 = edge_a.facet_number; vertex1 = v1a; new_vertex1 = stl->facet_start[edge_b.facet_number].vertex[v1b]; } else { facet1 = edge_b.facet_number; vertex1 = v1b; new_vertex1 = stl->facet_start[edge_a.facet_number].vertex[v1a]; } } // Of the second pair, which vertex, if any, should be changed. if (stl->facet_start[edge_a.facet_number].vertex[v2a] == stl->facet_start[edge_b.facet_number].vertex[v2b]) { // These facets are different. if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v2a] == -1) && (stl->neighbors_start[edge_a.facet_number].neighbor[(v2a + 2) % 3] == -1)) { // This vertex has no neighbors. This is a good one to change. facet2 = edge_a.facet_number; vertex2 = v2a; new_vertex2 = stl->facet_start[edge_b.facet_number].vertex[v2b]; } else { facet2 = edge_b.facet_number; vertex2 = v2b; new_vertex2 = stl->facet_start[edge_a.facet_number].vertex[v2a]; } } } auto change_vertices = [stl](int facet_num, int vnot, stl_vertex new_vertex) { int first_facet = facet_num; bool direction = false; for (;;) { int pivot_vertex; int next_edge; if (vnot > 2) { if (direction) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } direction = !direction; } else { if (direction) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } } #if 0 if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex(0), new_vertex(1), new_vertex(2)); else { if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", stl->facet_start[facet_num].vertex[pivot_vertex](0), *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), new_vertex(0), *reinterpret_cast(&new_vertex(0))); if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", stl->facet_start[facet_num].vertex[pivot_vertex](1), *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), new_vertex(1), *reinterpret_cast(&new_vertex(1))); if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", stl->facet_start[facet_num].vertex[pivot_vertex](2), *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), new_vertex(2), *reinterpret_cast(&new_vertex(2))); } #endif stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; if (facet_num == -1) break; if (facet_num == first_facet) { // back to the beginning BOOST_LOG_TRIVIAL(info) << "Back to the first facet changing vertices: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; return; } } }; if (facet1 != -1) { int vnot1 = (facet1 == edge_a.facet_number) ? (edge_a.which_edge + 2) % 3 : (edge_b.which_edge + 2) % 3; if (((vnot1 + 2) % 3) == vertex1) vnot1 += 3; change_vertices(facet1, vnot1, new_vertex1); } if (facet2 != -1) { int vnot2 = (facet2 == edge_a.facet_number) ? (edge_a.which_edge + 2) % 3 : (edge_b.which_edge + 2) % 3; if (((vnot2 + 2) % 3) == vertex2) vnot2 += 3; change_vertices(facet2, vnot2, new_vertex2); } stl->stats.edges_fixed += 2; } }; // This function builds the neighbors list. No modifications are made // to any of the facets. The edges are said to match only if all six // floats of the first edge matches all six floats of the second edge. void stl_check_facets_exact(stl_file *stl) { assert(stl->facet_start.size() == stl->neighbors_start.size()); stl->stats.connected_edges = 0; stl->stats.connected_facets_1_edge = 0; stl->stats.connected_facets_2_edge = 0; stl->stats.connected_facets_3_edge = 0; // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet // will break the references. for (uint32_t i = 0; i < stl->stats.number_of_facets;) { stl_facet &facet = stl->facet_start[i]; if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { // Remove the degenerate facet. facet = stl->facet_start[-- stl->stats.number_of_facets]; stl->facet_start.pop_back(); stl->neighbors_start.pop_back(); stl->stats.facets_removed += 1; stl->stats.degenerate_facets += 1; } else ++ i; } // Initialize hash table. HashTableEdges hash_table(stl->stats.number_of_facets); for (auto &neighbor : stl->neighbors_start) neighbor.reset(); // Connect neighbor edges. for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { const stl_facet &facet = stl->facet_start[i]; for (int j = 0; j < 3; ++ j) { HashEdge edge; edge.facet_number = i; edge.which_edge = j; edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); hash_table.insert_edge_exact(stl, edge); } } #if 0 printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", stl->stats.number_of_facets, stl->stats.number_of_facets * 3, stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); #endif } void stl_check_facets_nearby(stl_file *stl, float tolerance) { assert(stl->stats.connected_facets_3_edge <= stl->stats.connected_facets_2_edge); assert(stl->stats.connected_facets_2_edge <= stl->stats.connected_facets_1_edge); assert(stl->stats.connected_facets_1_edge <= stl->stats.number_of_facets); if (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets) // No need to check any further. All facets are connected. return; HashTableEdges hash_table(stl->stats.number_of_facets); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { //FIXME is the copy necessary? stl_facet facet = stl->facet_start[i]; for (int j = 0; j < 3; j++) { if (stl->neighbors_start[i].neighbor[j] == -1) { HashEdge edge; edge.facet_number = i; edge.which_edge = j; if (edge.load_nearby(stl, facet.vertex[j], facet.vertex[(j + 1) % 3], tolerance)) // Only insert edges that have different keys. hash_table.insert_edge_nearby(stl, edge); } } } } void stl_remove_unconnected_facets(stl_file *stl) { // A couple of things need to be done here. One is to remove any completely unconnected facets (0 edges connected) since these are // useless and could be completely wrong. The second thing that needs to be done is to remove any degenerate facets that were created during // stl_check_facets_nearby(). auto remove_facet = [stl](int facet_number) { ++ stl->stats.facets_removed; /* Update list of connected edges */ stl_neighbors &neighbors = stl->neighbors_start[facet_number]; // Update statistics on unconnected triangle edges. switch (neighbors.num_neighbors()) { case 3: -- stl->stats.connected_facets_3_edge; // fall through case 2: -- stl->stats.connected_facets_2_edge; // fall through case 1: -- stl->stats.connected_facets_1_edge; // fall through case 0: break; default: assert(false); } if (facet_number < int(-- stl->stats.number_of_facets)) { // Removing a face, which was not the last one. // Copy the face and neighborship from the last face to facet_number. stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; neighbors = stl->neighbors_start[stl->stats.number_of_facets]; // Update neighborship of faces, which used to point to the last face, now moved to facet_number. for (int i = 0; i < 3; ++ i) if (neighbors.neighbor[i] != -1) { int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; if (other_face_idx != stl->stats.number_of_facets) { BOOST_LOG_TRIVIAL(info) << "in remove_facet: neighbor = " << other_face_idx << " numfacets = " << stl->stats.number_of_facets << " this is wrong"; return; } other_face_idx = facet_number; } } stl->facet_start.pop_back(); stl->neighbors_start.pop_back(); }; auto remove_degenerate = [stl, remove_facet](int facet) { // Update statistics on face connectivity after one edge was disconnected on the facet "facet_num". auto update_connects_remove_1 = [stl](int facet_num) { switch (stl->neighbors_start[facet_num].num_neighbors()) { case 0: assert(false); break; case 1: -- stl->stats.connected_facets_1_edge; break; case 2: -- stl->stats.connected_facets_2_edge; break; case 3: -- stl->stats.connected_facets_3_edge; break; default: assert(false); } }; int edge_to_collapse = 0; if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { // All 3 vertices are equal. Collapse the edge with no neighbor if it exists. const int *nbr = stl->neighbors_start[facet].neighbor; edge_to_collapse = (nbr[0] == -1) ? 0 : (nbr[1] == -1) ? 1 : 2; } else { edge_to_collapse = 0; } } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { edge_to_collapse = 1; } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { edge_to_collapse = 2; } else { // No degenerate. Function shouldn't have been called. return; } int edge[3] = { (edge_to_collapse + 1) % 3, (edge_to_collapse + 2) % 3, edge_to_collapse }; int neighbor[] = { stl->neighbors_start[facet].neighbor[edge[0]], stl->neighbors_start[facet].neighbor[edge[1]], stl->neighbors_start[facet].neighbor[edge[2]] }; int vnot[] = { stl->neighbors_start[facet].which_vertex_not[edge[0]], stl->neighbors_start[facet].which_vertex_not[edge[1]], stl->neighbors_start[facet].which_vertex_not[edge[2]] }; // Update statistics on edge connectivity. if ((neighbor[0] == -1) && (neighbor[1] != -1)) update_connects_remove_1(neighbor[1]); if ((neighbor[1] == -1) && (neighbor[0] != -1)) update_connects_remove_1(neighbor[0]); if (neighbor[0] >= 0) { if (neighbor[1] >= 0) { // Adjust the "flip" flag for the which_vertex_not values. if (vnot[0] > 2) { if (vnot[1] > 2) { // The face to be removed has its normal flipped compared to the left & right neighbors, therefore after removing this face // the two remaining neighbors will be oriented correctly. vnot[0] -= 3; vnot[1] -= 3; } else // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. // After removal, the two neighbors will have their normals flipped. vnot[1] += 3; } else if (vnot[1] > 2) // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. // After removal, the two neighbors will have their normals flipped. vnot[0] += 3; } stl->neighbors_start[neighbor[0]].neighbor[(vnot[0] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[1]; stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = vnot[1]; } if (neighbor[1] >= 0) { stl->neighbors_start[neighbor[1]].neighbor[(vnot[1] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[0]; stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0]; } if (neighbor[2] >= 0) { update_connects_remove_1(neighbor[2]); stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1; } remove_facet(facet); }; // remove degenerate facets for (uint32_t i = 0; i < stl->stats.number_of_facets;) if (stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { remove_degenerate(i); // assert(stl_validate(stl)); } else ++ i; if (stl->stats.connected_facets_1_edge < (int)stl->stats.number_of_facets) { // There are some faces with no connected edge at all. Remove completely unconnected facets. for (uint32_t i = 0; i < stl->stats.number_of_facets;) if (stl->neighbors_start[i].num_neighbors() == 0) { // This facet is completely unconnected. Remove it. remove_facet(i); assert(stl_validate(stl)); } else ++ i; } } void stl_fill_holes(stl_file *stl) { // Insert all unconnected edges into hash list. HashTableEdges hash_table(stl->stats.number_of_facets); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_facet facet = stl->facet_start[i]; for (int j = 0; j < 3; ++ j) { if(stl->neighbors_start[i].neighbor[j] != -1) continue; HashEdge edge; edge.facet_number = i; edge.which_edge = j; edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); hash_table.insert_edge_exact(stl, edge); } } for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_facet facet = stl->facet_start[i]; int neighbors_initial[3] = { stl->neighbors_start[i].neighbor[0], stl->neighbors_start[i].neighbor[1], stl->neighbors_start[i].neighbor[2] }; int first_facet = i; for (int j = 0; j < 3; ++ j) { if (stl->neighbors_start[i].neighbor[j] != -1) continue; stl_facet new_facet; new_facet.vertex[0] = facet.vertex[j]; new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; bool direction = neighbors_initial[(j + 2) % 3] == -1; int facet_num = i; int vnot = (j + 2) % 3; for (;;) { int pivot_vertex = 0; int next_edge = 0; if (vnot > 2) { if (direction) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } direction = ! direction; } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } int next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; if (next_facet == -1) { new_facet.vertex[2] = stl->facet_start[facet_num].vertex[vnot % 3]; stl_add_facet(stl, &new_facet); for (int k = 0; k < 3; ++ k) { HashEdge edge; edge.facet_number = stl->stats.number_of_facets - 1; edge.which_edge = k; edge.load_exact(stl, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); hash_table.insert_edge_exact(stl, edge); } break; } vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; facet_num = next_facet; if (facet_num == first_facet) { // back to the beginning BOOST_LOG_TRIVIAL(info) << "Back to the first facet filling holes: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; return; } } } } } void stl_add_facet(stl_file *stl, const stl_facet *new_facet) { assert(stl->facet_start.size() == stl->stats.number_of_facets); assert(stl->neighbors_start.size() == stl->stats.number_of_facets); stl->facet_start.emplace_back(*new_facet); // note that the normal vector is not set here, just initialized to 0. stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); stl->neighbors_start.emplace_back(); ++ stl->stats.facets_added; ++ stl->stats.number_of_facets; }