/* 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 "stl.h" void stl_verify_neighbors(stl_file *stl) { stl->stats.backwards_edges = 0; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { for (int j = 0; j < 3; ++ j) { struct stl_edge { stl_vertex p1; stl_vertex p2; int facet_number; }; stl_edge edge_a; edge_a.p1 = stl->facet_start[i].vertex[j]; edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; int neighbor = stl->neighbors_start[i].neighbor[j]; if (neighbor == -1) continue; // this edge has no neighbor... Continue. int vnot = stl->neighbors_start[i].which_vertex_not[j]; stl_edge edge_b; if (vnot < 3) { edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; } else { stl->stats.backwards_edges += 1; edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; } if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { // These edges should match but they don't. Print results. BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor; stl_write_facet(stl, (char*)"first facet", i); stl_write_facet(stl, (char*)"second facet", neighbor); } } } } void stl_translate(stl_file *stl, float x, float y, float z) { stl_vertex new_min(x, y, z); stl_vertex shift = new_min - stl->stats.min; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j] += shift; stl->stats.min = new_min; stl->stats.max += shift; } /* Translates the stl by x,y,z, relatively from wherever it is currently */ void stl_translate_relative(stl_file *stl, float x, float y, float z) { stl_vertex shift(x, y, z); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j] += shift; stl->stats.min += shift; stl->stats.max += shift; } void stl_scale_versor(stl_file *stl, const stl_vertex &versor) { // Scale extents. auto s = versor.array(); stl->stats.min.array() *= s; stl->stats.max.array() *= s; // Scale size. stl->stats.size.array() *= s; // Scale volume. if (stl->stats.volume > 0.0) stl->stats.volume *= versor(0) * versor(1) * versor(2); // Scale the mesh. for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j].array() *= s; } static void calculate_normals(stl_file *stl) { stl_normal normal; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); stl->facet_start[i].normal = normal; } } static inline void rotate_point_2d(float &x, float &y, const double c, const double s) { double xold = x; double yold = y; x = float(c * xold - s * yold); y = float(s * xold + c * yold); } void stl_rotate_x(stl_file *stl, float angle) { double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) rotate_point_2d(stl->facet_start[i].vertex[j](1), stl->facet_start[i].vertex[j](2), c, s); stl_get_size(stl); calculate_normals(stl); } void stl_rotate_y(stl_file *stl, float angle) { double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) rotate_point_2d(stl->facet_start[i].vertex[j](2), stl->facet_start[i].vertex[j](0), c, s); stl_get_size(stl); calculate_normals(stl); } void stl_rotate_z(stl_file *stl, float angle) { double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) rotate_point_2d(stl->facet_start[i].vertex[j](0), stl->facet_start[i].vertex[j](1), c, s); stl_get_size(stl); calculate_normals(stl); } void its_rotate_x(indexed_triangle_set &its, float angle) { double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); for (stl_vertex &v : its.vertices) rotate_point_2d(v(1), v(2), c, s); } void its_rotate_y(indexed_triangle_set& its, float angle) { double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); for (stl_vertex& v : its.vertices) rotate_point_2d(v(2), v(0), c, s); } void its_rotate_z(indexed_triangle_set& its, float angle) { double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); for (stl_vertex& v : its.vertices) rotate_point_2d(v(0), v(1), c, s); } void stl_get_size(stl_file *stl) { if (stl->stats.number_of_facets == 0) return; stl->stats.min = stl->facet_start[0].vertex[0]; stl->stats.max = stl->stats.min; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { const stl_facet &face = stl->facet_start[i]; for (int j = 0; j < 3; ++ j) { stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); } } stl->stats.size = stl->stats.max - stl->stats.min; stl->stats.bounding_diameter = stl->stats.size.norm(); } void stl_mirror_xy(stl_file *stl) { for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j](2) *= -1.0; float temp_size = stl->stats.min(2); stl->stats.min(2) = stl->stats.max(2); stl->stats.max(2) = temp_size; stl->stats.min(2) *= -1.0; stl->stats.max(2) *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_yz(stl_file *stl) { for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; j++) stl->facet_start[i].vertex[j](0) *= -1.0; float temp_size = stl->stats.min(0); stl->stats.min(0) = stl->stats.max(0); stl->stats.max(0) = temp_size; stl->stats.min(0) *= -1.0; stl->stats.max(0) *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_xz(stl_file *stl) { for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j](1) *= -1.0; float temp_size = stl->stats.min(1); stl->stats.min(1) = stl->stats.max(1); stl->stats.max(1) = temp_size; stl->stats.min(1) *= -1.0; stl->stats.max(1) *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; // for not altering stats } static float get_area(stl_facet *facet) { /* cast to double before calculating cross product because large coordinates can result in overflowing product (bad area is responsible for bad volume and bad facets reversal) */ double cross[3][3]; for (int i = 0; i < 3; i++) { cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); } stl_normal sum; sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; // This should already be done. But just in case, let's do it again. //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. stl_normal n; stl_calculate_normal(n, facet); stl_normalize_vector(n); return 0.5f * n.dot(sum); } static float get_volume(stl_file *stl) { // Choose a point, any point as the reference. stl_vertex p0 = stl->facet_start[0].vertex[0]; float volume = 0.f; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { // Do dot product to get distance from point to plane. float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); float area = get_area(&stl->facet_start[i]); volume += (area * height) / 3.0f; } return volume; } void stl_calculate_volume(stl_file *stl) { stl->stats.volume = get_volume(stl); if (stl->stats.volume < 0.0) { stl_reverse_all_facets(stl); stl->stats.volume = -stl->stats.volume; } } void stl_repair( stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag) { if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { if (verbose_flag) printf("Checking exact...\n"); exact_flag = true; stl_check_facets_exact(stl); stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); } if (nearby_flag || fixall_flag) { if (! tolerance_flag) tolerance = stl->stats.shortest_edge; if (! increment_flag) increment = stl->stats.bounding_diameter / 10000.0; } if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) { int last_edges_fixed = 0; for (int i = 0; i < iterations; ++ i) { if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) { if (verbose_flag) printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(stl, tolerance); if (verbose_flag) printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed); last_edges_fixed = stl->stats.edges_fixed; tolerance += increment; } else { if (verbose_flag) printf("All facets connected. No further nearby check necessary.\n"); break; } } } else if (verbose_flag) printf("All facets connected. No nearby check necessary.\n"); if (remove_unconnected_flag || fixall_flag || fill_holes_flag) { if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) { if (verbose_flag) printf("Removing unconnected facets...\n"); stl_remove_unconnected_facets(stl); } else if (verbose_flag) printf("No unconnected need to be removed.\n"); } if (fill_holes_flag || fixall_flag) { if (stl->stats.connected_facets_3_edge < int(stl->stats.number_of_facets)) { if (verbose_flag) printf("Filling holes...\n"); stl_fill_holes(stl); } else if (verbose_flag) printf("No holes need to be filled.\n"); } if (reverse_all_flag) { if (verbose_flag) printf("Reversing all facets...\n"); stl_reverse_all_facets(stl); } if (normal_directions_flag || fixall_flag) { if (verbose_flag) printf("Checking normal directions...\n"); stl_fix_normal_directions(stl); } if (normal_values_flag || fixall_flag) { if (verbose_flag) printf("Checking normal values...\n"); stl_fix_normal_values(stl); } // Always calculate the volume. It shouldn't take too long. if (verbose_flag) printf("Calculating volume...\n"); stl_calculate_volume(stl); if (exact_flag) { if (verbose_flag) printf("Verifying neighbors...\n"); stl_verify_neighbors(stl); } }