diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index ccc0caf98..632917266 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -29,11 +29,21 @@ inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } struct Interior { TriangleMesh mesh; openvdb::FloatGrid::Ptr gridptr; + mutable std::optional accessor; + double closing_distance = 0.; double thickness = 0.; double voxel_scale = 1.; - double nb_in = 3.; - double nb_out = 3.; + double nb_in = 3.; // narrow band width inwards + double nb_out = 3.; // narrow band width outwards + // Full narrow band is the sum of the two above values. + + void reset_accessor() const // This resets the accessor and its cache + // Not a thread safe call! + { + if (gridptr) + accessor = gridptr->getConstAccessor(); + } }; void InteriorDeleter::operator()(Interior *p) @@ -313,16 +323,243 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) { if (mesh.empty() || interior.mesh.empty()) return; -// if (flags & hfRemoveInsideTriangles && interior.gridptr) -// erase_inside_triangles_2(mesh, interior); + if (flags & hfRemoveInsideTriangles && interior.gridptr) + remove_inside_triangles(mesh, interior); mesh.merge(interior.mesh); mesh.require_shared_vertices(); } +// Get the distance of p to the interior's zero iso_surface. Interior should +// have its zero isosurface positioned at offset + closing_distance inwards form +// the model surface. +static double get_distance_raw(const Vec3f &p, const Interior &interior) +{ + assert(interior.gridptr); + + if (!interior.accessor) interior.reset_accessor(); + + auto v = (p * interior.voxel_scale).cast(); + auto grididx = interior.gridptr->transform().worldToIndexCellCentered( + {v.x(), v.y(), v.z()}); + + return interior.accessor->getValue(grididx) ; +} + +struct TriangleBubble { Vec3f center; double R; }; + +// Return the distance of bubble center to the interior boundary or NaN if the +// triangle is too big to be measured. +static double get_distance(const TriangleBubble &b, const Interior &interior) +{ + double R = b.R * interior.voxel_scale; + double D = get_distance_raw(b.center, interior); + + return (D > 0. && R >= interior.nb_out) || + (D < 0. && R >= interior.nb_in) || + ((D - R) < 0. && 2 * R > interior.thickness) ? + std::nan("") : + // FIXME: Adding interior.voxel_scale is a compromise supposed + // to prevent the deletion of the triangles forming the interior + // itself. This has a side effect that a small portion of the + // bad triangles will still be visible. + D - interior.closing_distance /*+ 2 * interior.voxel_scale*/; +} + +double get_distance(const Vec3f &p, const Interior &interior) +{ + double d = get_distance_raw(p, interior) - interior.closing_distance; + return d / interior.voxel_scale; +} + +// A face that can be divided. Stores the indices into the original mesh if its +// part of that mesh and the vertices it consists of. +enum { NEW_FACE = -1}; +struct DivFace { + Vec3i indx; + std::array verts; + long faceid = NEW_FACE; + long parent = NEW_FACE; +}; + +// Divide a face recursively and call visitor on all the sub-faces. +template +void divide_triangle(const DivFace &face, Fn &&visitor) +{ + std::array edges = {(face.verts[0] - face.verts[1]), + (face.verts[1] - face.verts[2]), + (face.verts[2] - face.verts[0])}; + + std::array edgeidx = {0, 1, 2}; + + std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) { + return edges[e1].squaredNorm() > edges[e2].squaredNorm(); + }); + + DivFace child1, child2; + + child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid; + child1.indx(0) = -1; + child1.indx(1) = face.indx(edgeidx[1]); + child1.indx(2) = face.indx((edgeidx[1] + 1) % 3); + child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.; + child1.verts[1] = face.verts[edgeidx[1]]; + child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3]; + + if (visitor(child1)) + divide_triangle(child1, std::forward(visitor)); + + child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid; + child2.indx(0) = -1; + child2.indx(1) = face.indx(edgeidx[2]); + child2.indx(2) = face.indx((edgeidx[2] + 1) % 3); + child2.verts[0] = child1.verts[0]; + child2.verts[1] = face.verts[edgeidx[2]]; + child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3]; + + if (visitor(child2)) + divide_triangle(child2, std::forward(visitor)); +} + void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) { + enum TrPos { posInside, posTouch, posOutside }; + auto &faces = mesh.its.indices; + auto &vertices = mesh.its.vertices; + auto bb = mesh.bounding_box(); + + // TODO: Parallel mode not working yet + using exec_policy = ccr_seq; + + // Info about the needed modifications on the input mesh. + struct MeshMods { + + // Just a thread safe wrapper for a vector of triangles. + struct { + std::vector> data; + exec_policy::SpinningMutex mutex; + + void emplace_back(const std::array &pts) + { + std::lock_guard lk{mutex}; + data.emplace_back(pts); + } + + size_t size() const { return data.size(); } + const std::array& operator[](size_t idx) const + { + return data[idx]; + } + + } new_triangles; + + // A vector of bool for all faces signaling if it needs to be removed + // or not. + std::vector to_remove; + + MeshMods(const TriangleMesh &mesh): + to_remove(mesh.its.indices.size(), false) {} + + // Number of triangles that need to be removed. + size_t to_remove_cnt() const + { + return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0)); + } + + } mesh_mods{mesh}; + + // Must return true if further division of the face is needed. + auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) { + BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() }; + + // Face is certainly outside the cavity + if (! facebb.intersects(bb) && f.faceid != NEW_FACE) { + return false; + } + + TriangleBubble bubble{facebb.center().cast(), facebb.radius()}; + + double D = get_distance(bubble, interior); + double R = bubble.R * interior.voxel_scale; + + if (std::isnan(D)) // The distance cannot be measured, triangle too big + return true; + + // Distance of the bubble wall to the interior wall. Negative if the + // bubble is overlapping with the interior + double bubble_distance = D - R; + + // The face is crossing the interior or inside, it must be removed and + // parts of it re-added, that are outside the interior + if (bubble_distance < 0.) { + if (f.faceid != NEW_FACE) + mesh_mods.to_remove[f.faceid] = true; + + if (f.parent != NEW_FACE) // Top parent needs to be removed as well + mesh_mods.to_remove[f.parent] = true; + + // If the outside part is between the interior end the exterior + // (inside the wall being invisible), no further division is needed. + if ((R + D) < interior.thickness) + return false; + + return true; + } else if (f.faceid == NEW_FACE) { + // New face completely outside needs to be re-added. + mesh_mods.new_triangles.emplace_back(f.verts); + } + + return false; + }; + + interior.reset_accessor(); + + exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { + const Vec3i &face = faces[face_idx]; + + std::array pts = + { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; + + BoundingBoxf3 facebb { pts.begin(), pts.end() }; + + // Face is certainly outside the cavity + if (! facebb.intersects(bb)) return; + + DivFace df{face, pts, long(face_idx)}; + + if (divfn(df)) + divide_triangle(df, divfn); + + }, exec_policy::max_concurreny()); + + auto new_faces = reserve_vector(faces.size() + + mesh_mods.new_triangles.size()); + + for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) { + if (!mesh_mods.to_remove[face_idx]) + new_faces.emplace_back(faces[face_idx]); + } + + for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) { + size_t o = vertices.size(); + vertices.emplace_back(mesh_mods.new_triangles[i][0]); + vertices.emplace_back(mesh_mods.new_triangles[i][1]); + vertices.emplace_back(mesh_mods.new_triangles[i][2]); + new_faces.emplace_back(int(o), int(o + 1), int(o + 2)); + } + + BOOST_LOG_TRIVIAL(info) + << "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed"; + BOOST_LOG_TRIVIAL(info) + << "Trimming: " << mesh_mods.new_triangles.size() << " triangles added"; + + faces.swap(new_faces); + new_faces = {}; + + mesh = TriangleMesh{mesh.its}; + mesh.repaired = true; + mesh.require_shared_vertices(); } }} // namespace Slic3r::sla