diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9245e5c2a..d01c01ec2 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -183,6 +183,9 @@ add_library(libslic3r STATIC MinAreaBoundingBox.cpp miniz_extension.hpp miniz_extension.cpp + SimplifyMesh.hpp + SimplifyMeshImpl.hpp + SimplifyMesh.cpp ${OpenVDBUtils_SOURCES} SLA/Common.hpp SLA/Common.cpp diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index c76bad96c..c30052036 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -84,9 +84,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, template sla::Contour3D _volumeToMesh(const Grid &grid, - double isovalue, - double adaptivity, - bool relaxDisorientedTriangles) + double isovalue, + double adaptivity, + bool relaxDisorientedTriangles) { openvdb::initialize(); @@ -110,9 +110,9 @@ sla::Contour3D _volumeToMesh(const Grid &grid, } TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid, - double isovalue, - double adaptivity, - bool relaxDisorientedTriangles) + double isovalue, + double adaptivity, + bool relaxDisorientedTriangles) { return to_triangle_mesh( _volumeToMesh(grid, isovalue, adaptivity, relaxDisorientedTriangles)); diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 5ab85c1cc..1ce0c4c67 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -24,41 +25,15 @@ template> inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } template> -inline void _scale(S s, Contour3D &m) -{ - for (auto &p : m.points) p *= s; -} +inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } -template -remove_cvref_t _grid_to_mesh(const openvdb::FloatGrid &grid, - double isosurf, - double adapt); - -template<> -TriangleMesh _grid_to_mesh(const openvdb::FloatGrid &grid, - double isosurf, - double adapt) +static TriangleMesh _generate_interior(const TriangleMesh &mesh, + const JobController &ctl, + double min_thickness, + double voxel_scale, + double closing_dist) { - return grid_to_mesh(grid, isosurf, adapt); -} - -template<> -Contour3D _grid_to_mesh(const openvdb::FloatGrid &grid, - double isosurf, - double adapt) -{ - return grid_to_contour3d(grid, isosurf, adapt); -} - -template -remove_cvref_t _generate_interior(Mesh &&mesh, - const JobController &ctl, - double min_thickness, - double voxel_scale, - double closing_dist) -{ - using MMesh = remove_cvref_t; - MMesh imesh{std::forward(mesh)}; + TriangleMesh imesh{mesh}; _scale(voxel_scale, imesh); @@ -76,7 +51,7 @@ remove_cvref_t _generate_interior(Mesh &&mesh, if (!gridptr) { BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; - return MMesh{}; + return {}; } if (ctl.stopcondition()) return {}; @@ -93,7 +68,7 @@ remove_cvref_t _generate_interior(Mesh &&mesh, double iso_surface = D; double adaptivity = 0.; - auto omesh = _grid_to_mesh(*gridptr, iso_surface, adaptivity); + auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); _scale(1. / voxel_scale, omesh); @@ -107,7 +82,8 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, const HollowingConfig &hc, const JobController & ctl) { - static const double MAX_OVERSAMPL = 7.; + static const double MIN_OVERSAMPL = 3.; + static const double MAX_OVERSAMPL = 8.; // I can't figure out how to increase the grid resolution through openvdb // API so the model will be scaled up before conversion and the result @@ -116,10 +92,27 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, // voxels. // // max 8x upscale, min is native voxel size - auto voxel_scale = (1.0 + MAX_OVERSAMPL * hc.quality); - return std::make_unique( + auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; + auto meshptr = std::make_unique( _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, hc.closing_distance)); + + if (meshptr) { + + // This flips the normals to be outward facing... + meshptr->require_shared_vertices(); + indexed_triangle_set its = std::move(meshptr->its); + + Slic3r::simplify_mesh(its); + + // flip normals back... + for (stl_triangle_vertex_indices &ind : its.indices) + std::swap(ind(0), ind(2)); + + *meshptr = Slic3r::TriangleMesh{its}; + } + + return meshptr; } Contour3D DrainHole::to_mesh() const diff --git a/src/libslic3r/SimplifyMesh.cpp b/src/libslic3r/SimplifyMesh.cpp new file mode 100644 index 000000000..d30ecfec5 --- /dev/null +++ b/src/libslic3r/SimplifyMesh.cpp @@ -0,0 +1,66 @@ +#include "SimplifyMesh.hpp" +#include "SimplifyMeshImpl.hpp" + +namespace SimplifyMesh { + +template<> struct vertex_traits { + using coord_type = float; + using compute_type = double; + + static inline float x(const stl_vertex &v) { return v.x(); } + static inline float& x(stl_vertex &v) { return v.x(); } + + static inline float y(const stl_vertex &v) { return v.y(); } + static inline float& y(stl_vertex &v) { return v.y(); } + + static inline float z(const stl_vertex &v) { return v.z(); } + static inline float& z(stl_vertex &v) { return v.z(); } +}; + +template<> struct mesh_traits { + using vertex_t = stl_vertex; + static size_t face_count(const indexed_triangle_set &m) + { + return m.indices.size(); + } + static size_t vertex_count(const indexed_triangle_set &m) + { + return m.vertices.size(); + } + static vertex_t vertex(const indexed_triangle_set &m, size_t idx) + { + return m.vertices[idx]; + } + static void vertex(indexed_triangle_set &m, size_t idx, const vertex_t &v) + { + m.vertices[idx] = v; + } + static Index3 triangle(const indexed_triangle_set &m, size_t idx) + { + std::array t; + for (size_t i = 0; i < 3; ++i) t[i] = size_t(m.indices[idx](int(i))); + return t; + } + static void triangle(indexed_triangle_set &m, size_t fidx, const Index3 &t) + { + auto &face = m.indices[fidx]; + face(0) = int(t[0]); face(1) = int(t[1]); face(2) = int(t[2]); + } + static void update(indexed_triangle_set &m, size_t vc, size_t fc) + { + m.vertices.resize(vc); + m.indices.resize(fc); + } +}; + +} // namespace SimplifyMesh + +namespace Slic3r { + +void simplify_mesh(indexed_triangle_set &m) +{ + SimplifyMesh::implementation::SimplifiableMesh sm{&m}; + sm.simplify_mesh_lossless(); +} + +} diff --git a/src/libslic3r/SimplifyMesh.hpp b/src/libslic3r/SimplifyMesh.hpp new file mode 100644 index 000000000..fb3e73d04 --- /dev/null +++ b/src/libslic3r/SimplifyMesh.hpp @@ -0,0 +1,25 @@ +#ifndef MESHSIMPLIFY_HPP +#define MESHSIMPLIFY_HPP + +#include + +#include + +namespace Slic3r { + +void simplify_mesh(indexed_triangle_set &); + +// TODO: (but this can be done with IGL as well) +// void simplify_mesh(indexed_triangle_set &, int face_count, float agressiveness = 0.5f); + +template void simplify_mesh(TriangleMesh &m, Args &&...a) +{ + m.require_shared_vertices(); + simplify_mesh(m.its, std::forward(a)...); + m = TriangleMesh{m.its}; + m.require_shared_vertices(); +} + +} // namespace Slic3r + +#endif // MESHSIMPLIFY_H diff --git a/src/libslic3r/SimplifyMeshImpl.hpp b/src/libslic3r/SimplifyMeshImpl.hpp new file mode 100644 index 000000000..6add08930 --- /dev/null +++ b/src/libslic3r/SimplifyMeshImpl.hpp @@ -0,0 +1,699 @@ +// /////////////////////////////////////////// +// +// Mesh Simplification Tutorial +// +// (C) by Sven Forstmann in 2014 +// +// License : MIT +// http://opensource.org/licenses/MIT +// +// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification +// +// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile +// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/ +// +// libslic3r refactor by tamasmeszaros + +#ifndef SIMPLIFYMESHIMPL_HPP +#define SIMPLIFYMESHIMPL_HPP + +#include +#include +#include +#include + +#ifndef NDEBUG +#include +#endif + +namespace SimplifyMesh { + +using Bary = std::array; +using Index3 = std::array; + +template struct vertex_traits { + using coord_type = typename Vertex::coord_type; + using compute_type = coord_type; + + static coord_type x(const Vertex &v); + static coord_type& x(Vertex &v); + + static coord_type y(const Vertex &v); + static coord_type& y(Vertex &v); + + static coord_type z(const Vertex &v); + static coord_type& z(Vertex &v); +}; + +template struct mesh_traits { + using vertex_t = typename Mesh::vertex_t; + + static size_t face_count(const Mesh &m); + static size_t vertex_count(const Mesh &m); + static vertex_t vertex(const Mesh &m, size_t vertex_idx); + static void vertex(Mesh &m, size_t vertex_idx, const vertex_t &v); + static Index3 triangle(const Mesh &m, size_t face_idx); + static void triangle(Mesh &m, size_t face_idx, const Index3 &t); + static void update(Mesh &m, size_t vertex_count, size_t face_count); +}; + +namespace implementation { + +// A shorter C++14 style form of the enable_if metafunction +template +using enable_if_t = typename std::enable_if::type; + +// Meta predicates for floating, 'scaled coord' and generic arithmetic types +template +using FloatingOnly = enable_if_t::value, O>; + +template +using IntegerOnly = enable_if_t::value, O>; + +template +using ArithmeticOnly = enable_if_t::value, O>; + +template< class T > +struct remove_cvref { + using type = typename std::remove_cv< + typename std::remove_reference::type>::type; +}; + +template< class T > +using remove_cvref_t = typename remove_cvref::type; + +struct DOut { +#ifndef NDEBUG + std::ostream& out = std::cout; +#endif +}; + +template +inline DOut&& operator<<( DOut&& out, T&& d) { +#ifndef NDEBUG + out.out << d; +#endif + return std::move(out); +} + +inline DOut dout() { return DOut(); } + +template FloatingOnly is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; } +template IntegerOnly is_approx(T val, T ref) { val == ref; } + +template class SymetricMatrix { +public: + + explicit SymetricMatrix(ArithmeticOnly c = T()) { std::fill(m, m + N, c); } + + SymetricMatrix(T m11, T m12, T m13, T m14, + T m22, T m23, T m24, + T m33, T m34, + T m44) + { + m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14; + m[4] = m22; m[5] = m23; m[6] = m24; + m[7] = m33; m[8] = m34; + m[9] = m44; + } + + // Make plane + SymetricMatrix(T a, T b, T c, T d) + { + m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d; + m[4] = b * b; m[5] = b * c; m[6] = b * d; + m[7] = c * c; m[8] = c * d; + m[9] = d * d; + } + + T operator[](int c) const { return m[c]; } + + // Determinant + T det(int a11, int a12, int a13, + int a21, int a22, int a23, + int a31, int a32, int a33) + { + T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] + + m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] - + m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33]; + + return det; + } + + const SymetricMatrix operator+(const SymetricMatrix& n) const + { + return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3]+n[3], + m[4] + n[4], m[5] + n[5], m[6] + n[6], + m[7] + n[7], m[8] + n[8], + m[9] + n[9]); + } + + SymetricMatrix& operator+=(const SymetricMatrix& n) + { + m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3]; + m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7]; + m[8]+=n[8]; m[9]+=n[9]; + + return *this; + } + + T m[N]; +}; + +template using TCoord = typename vertex_traits>::coord_type; +template using TCompute = typename vertex_traits>::compute_type; +template inline TCoord x(const V &v) { return vertex_traits>::x(v); } +template inline TCoord y(const V &v) { return vertex_traits>::y(v); } +template inline TCoord z(const V &v) { return vertex_traits>::z(v); } +template inline TCoord& x(V &v) { return vertex_traits>::x(v); } +template inline TCoord& y(V &v) { return vertex_traits>::y(v); } +template inline TCoord& z(V &v) { return vertex_traits>::z(v); } +template using TVertex = typename mesh_traits>::vertex_t; +template using TMeshCoord = TCoord>; + +template TCompute dot(const Vertex &v1, const Vertex &v2) +{ + return TCompute(x(v1)) * x(v2) + + TCompute(y(v1)) * y(v2) + + TCompute(z(v1)) * z(v2); +} + +template Vertex cross(const Vertex &a, const Vertex &b) +{ + return Vertex{y(a) * z(b) - z(a) * y(b), + z(a) * x(b) - x(a) * z(b), + x(a) * y(b) - y(a) * x(b)}; +} + +template TCompute lengthsq(const Vertex &v) +{ + return TCompute(x(v)) * x(v) + TCompute(y(v)) * y(v) + + TCompute(z(v)) * z(v); +} + +template void normalize(Vertex &v) +{ + double square = std::sqrt(lengthsq(v)); + x(v) /= square; y(v) /= square; z(v) /= square; +} + +using Bary = std::array; + +template +Bary barycentric(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c) +{ + Vertex v0 = (b - a); + Vertex v1 = (c - a); + Vertex v2 = (p - a); + + double d00 = dot(v0, v0); + double d01 = dot(v0, v1); + double d11 = dot(v1, v1); + double d20 = dot(v2, v0); + double d21 = dot(v2, v1); + double denom = d00 * d11 - d01 * d01; + double v = (d11 * d20 - d01 * d21) / denom; + double w = (d00 * d21 - d01 * d20) / denom; + double u = 1.0 - v - w; + + return {u, v, w}; +} + +template class SimplifiableMesh { + Mesh *m_mesh; + + using Vertex = TVertex; + using Coord = TMeshCoord; + using HiPrecison = TCompute>; + using SymMat = SymetricMatrix; + + struct FaceInfo { + size_t idx; + double err[4] = {0.}; + bool deleted = false, dirty = false; + Vertex n; + explicit FaceInfo(size_t id): idx(id) {} + }; + + struct VertexInfo { + size_t idx; + size_t tstart = 0, tcount = 0; + bool border = false; + SymMat q; + explicit VertexInfo(size_t id): idx(id) {} + }; + + struct Ref { size_t face; size_t vertex; }; + + std::vector m_refs; + std::vector m_faceinfo; + std::vector m_vertexinfo; + + void compact_faces(); + void compact(); + + size_t mesh_vcount() const { return mesh_traits::vertex_count(*m_mesh); } + size_t mesh_facecount() const { return mesh_traits::face_count(*m_mesh); } + + size_t vcount() const { return m_vertexinfo.size(); } + + inline Vertex read_vertex(size_t vi) const + { + return mesh_traits::vertex(*m_mesh, vi); + } + + inline Vertex read_vertex(const VertexInfo &vinf) const + { + return read_vertex(vinf.idx); + } + + inline void write_vertex(size_t idx, const Vertex &v) const + { + mesh_traits::vertex(*m_mesh, idx, v); + } + + inline void write_vertex(const VertexInfo &vinf, const Vertex &v) const + { + write_vertex(vinf.idx, v); + } + + inline Index3 read_triangle(size_t fi) const + { + return mesh_traits::triangle(*m_mesh, fi); + } + + inline Index3 read_triangle(const FaceInfo &finf) const + { + return read_triangle(finf.idx); + } + + inline void write_triangle(size_t idx, const Index3 &t) + { + return mesh_traits::triangle(*m_mesh, idx, t); + } + + inline void write_triangle(const FaceInfo &finf, const Index3 &t) + { + return write_triangle(finf.idx, t); + } + + inline std::array triangle_vertices(const Index3 &f) const + { + std::array p; + for (size_t i = 0; i < 3; ++i) p[i] = read_vertex(f[i]); + return p; + } + + // Error between vertex and Quadric + static double vertex_error(const SymMat &q, const Vertex &v) + { + Coord _x = x(v) , _y = y(v), _z = z(v); + return q[0] * _x * _x + 2 * q[1] * _x * _y + 2 * q[2] * _x * _z + + 2 * q[3] * _x + q[4] * _y * _y + 2 * q[5] * _y * _z + + 2 * q[6] * _y + q[7] * _z * _z + 2 * q[8] * _z + q[9]; + } + + // Error for one edge + double calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result); + + void calculate_error(FaceInfo &fi) + { + Vertex p; + Index3 t = read_triangle(fi); + for (size_t j = 0; j < 3; ++j) + fi.err[j] = calculate_error(t[j], t[(j + 1) % 3], p); + + fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2])); + } + + void update_mesh(int iteration); + + // Update triangle connections and edge error after a edge is collapsed + void update_triangles(size_t i, VertexInfo &vi, std::vector &deleted, int &deleted_triangles); + + // Check if a triangle flips when this edge is removed + bool flipped(const Vertex &p, size_t i0, size_t i1, VertexInfo &v0, VertexInfo &v1, std::vector &deleted); + +public: + + explicit SimplifiableMesh(Mesh *m) : m_mesh{m} + { + static_assert( + std::is_arithmetic::value, + "Coordinate type of mesh has to be an arithmetic type!"); + + m_faceinfo.reserve(mesh_traits::face_count(*m)); + m_vertexinfo.reserve(mesh_traits::vertex_count(*m)); + for (size_t i = 0; i < mesh_facecount(); ++i) m_faceinfo.emplace_back(i); + for (size_t i = 0; i < mesh_vcount(); ++i) m_vertexinfo.emplace_back(i); + + } + + void simplify_mesh_lossless(); +}; + + +template void SimplifiableMesh::compact_faces() +{ + auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(), + [](const FaceInfo &inf) { return inf.deleted; }); + + m_faceinfo.erase(it, m_faceinfo.end()); +} + +template void SimplifiableMesh::compact() +{ + for (auto &vi : m_vertexinfo) vi.tcount = 0; + + compact_faces(); + + for (FaceInfo &fi : m_faceinfo) + for (size_t vidx : read_triangle(fi)) m_vertexinfo[vidx].tcount = 1; + + size_t dst = 0; + for (VertexInfo &vi : m_vertexinfo) { + if (vi.tcount) { + vi.tstart = dst; + write_vertex(dst++, read_vertex(vi)); + } + } + + size_t vertex_count = dst; + + dst = 0; + for (const FaceInfo &fi : m_faceinfo) { + Index3 t = read_triangle(fi); + for (size_t &idx : t) idx = m_vertexinfo[idx].tstart; + write_triangle(dst++, t); + } + + mesh_traits::update(*m_mesh, vertex_count, m_faceinfo.size()); +} + +template +double SimplifiableMesh::calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result) +{ + // compute interpolated vertex + + SymMat q = m_vertexinfo[id_v1].q + m_vertexinfo[id_v2].q; + + bool border = m_vertexinfo[id_v1].border & m_vertexinfo[id_v2].border; + double error = 0; + HiPrecison det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); + + if (!is_approx(det, HiPrecison(0)) && !border) + { + // q_delta is invertible + x(p_result) = Coord(-1) / det * q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta) + y(p_result) = Coord( 1) / det * q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta) + z(p_result) = Coord(-1) / det * q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta) + + error = vertex_error(q, p_result); + } else { + // det = 0 -> try to find best result + Vertex p1 = read_vertex(id_v1); + Vertex p2 = read_vertex(id_v2); + Vertex p3 = (p1 + p2) / 2; + double error1 = vertex_error(q, p1); + double error2 = vertex_error(q, p2); + double error3 = vertex_error(q, p3); + error = std::min(error1, std::min(error2, error3)); + + if (is_approx(error1, error)) p_result = p1; + if (is_approx(error2, error)) p_result = p2; + if (is_approx(error3, error)) p_result = p3; + } + + return error; +} + +template void SimplifiableMesh::update_mesh(int iteration) +{ + if (iteration > 0) compact_faces(); + + assert(mesh_vcount() == m_vertexinfo.size()); + + // + // Init Quadrics by Plane & Edge Errors + // + // required at the beginning ( iteration == 0 ) + // recomputing during the simplification is not required, + // but mostly improves the result for closed meshes + // + if (iteration == 0) { + + for (VertexInfo &vinf : m_vertexinfo) vinf.q = SymMat{}; + for (FaceInfo &finf : m_faceinfo) { + Index3 t = read_triangle(finf); + std::array p = triangle_vertices(t); + Vertex n = cross(Vertex(p[1] - p[0]), Vertex(p[2] - p[0])); + normalize(n); + finf.n = n; + + for (size_t fi : t) + m_vertexinfo[fi].q += SymMat(x(n), y(n), z(n), -dot(n, p[0])); + + calculate_error(finf); + } + } + + // Init Reference ID list + for (VertexInfo &vi : m_vertexinfo) { vi.tstart = 0; vi.tcount = 0; } + + for (FaceInfo &fi : m_faceinfo) + for (size_t vidx : read_triangle(fi)) + m_vertexinfo[vidx].tcount++; + + size_t tstart = 0; + for (VertexInfo &vi : m_vertexinfo) { + vi.tstart = tstart; + tstart += vi.tcount; + vi.tcount = 0; + } + + // Write References + m_refs.resize(m_faceinfo.size() * 3); + for (size_t i = 0; i < m_faceinfo.size(); ++i) { + const FaceInfo &fi = m_faceinfo[i]; + Index3 t = read_triangle(fi); + for (size_t j = 0; j < 3; ++j) { + VertexInfo &vi = m_vertexinfo[t[j]]; + + assert(vi.tstart + vi.tcount < m_refs.size()); + + Ref &ref = m_refs[vi.tstart + vi.tcount]; + ref.face = i; + ref.vertex = j; + vi.tcount++; + } + } + + // Identify boundary : vertices[].border=0,1 + if (iteration == 0) { + for (VertexInfo &vi: m_vertexinfo) vi.border = false; + + std::vector vcount, vids; + + for (VertexInfo &vi: m_vertexinfo) { + vcount.clear(); + vids.clear(); + + for(size_t j = 0; j < vi.tcount; ++j) { + assert(vi.tstart + j < m_refs.size()); + FaceInfo &fi = m_faceinfo[m_refs[vi.tstart + j].face]; + Index3 t = read_triangle(fi); + + for (size_t fid : t) { + size_t ofs=0; + while (ofs < vcount.size()) + { + if (vids[ofs] == fid) break; + ofs++; + } + if (ofs == vcount.size()) + { + vcount.emplace_back(1); + vids.emplace_back(fid); + } + else + vcount[ofs]++; + } + } + + for (size_t j = 0; j < vcount.size(); ++j) + if(vcount[j] == 1) m_vertexinfo[vids[j]].border = true; + } + } +} + +template +void SimplifiableMesh::update_triangles(size_t i0, + VertexInfo & vi, + std::vector &deleted, + int &deleted_triangles) +{ + Vertex p; + for (size_t k = 0; k < vi.tcount; ++k) { + assert(vi.tstart + k < m_refs.size()); + + Ref &r = m_refs[vi.tstart + k]; + FaceInfo &fi = m_faceinfo[r.face]; + + if (fi.deleted) continue; + + if (deleted[k]) { + fi.deleted = true; + deleted_triangles++; + continue; + } + + Index3 t = read_triangle(fi); + t[r.vertex] = i0; + write_triangle(fi, t); + + fi.dirty = true; + fi.err[0] = calculate_error(t[0], t[1], p); + fi.err[1] = calculate_error(t[1], t[2], p); + fi.err[2] = calculate_error(t[2], t[0], p); + fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2])); + m_refs.emplace_back(r); + } +} + +template +bool SimplifiableMesh::flipped(const Vertex & p, + size_t /*i0*/, + size_t i1, + VertexInfo & v0, + VertexInfo & /*v1*/, + std::vector &deleted) +{ + for (size_t k = 0; k < v0.tcount; ++k) { + size_t ridx = v0.tstart + k; + assert(ridx < m_refs.size()); + + FaceInfo &fi = m_faceinfo[m_refs[ridx].face]; + if (fi.deleted) continue; + + Index3 t = read_triangle(fi); + int s = m_refs[ridx].vertex; + size_t id1 = t[(s+1) % 3]; + size_t id2 = t[(s+2) % 3]; + + if(id1 == i1 || id2 == i1) // delete ? + { + deleted[k] = true; + continue; + } + + Vertex d1 = read_vertex(id1) - p; + normalize(d1); + Vertex d2 = read_vertex(id2) - p; + normalize(d2); + + if (std::abs(dot(d1, d2)) > 0.999) return true; + + Vertex n = cross(d1, d2); + normalize(n); + + deleted[k] = false; + if (dot(n, fi.n) < 0.2) return true; + } + + return false; +} + +template +void SimplifiableMesh::simplify_mesh_lossless() +{ + // init + for (FaceInfo &fi : m_faceinfo) fi.deleted = false; + + // main iteration loop + int deleted_triangles=0; + std::vector deleted0, deleted1; + + for (int iteration = 0; iteration < 9999; iteration ++) { + // update mesh constantly + update_mesh(iteration); + + // clear dirty flag + for (FaceInfo &fi : m_faceinfo) fi.dirty = false; + + // + // All triangles with edges below the threshold will be removed + // + // The following numbers works well for most models. + // If it does not, try to adjust the 3 parameters + // + double threshold = std::numeric_limits::epsilon(); //1.0E-3 EPS; // Really? (tm) + + dout() << "lossless iteration " << iteration << "\n"; + + for (FaceInfo &fi : m_faceinfo) { + if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue; + + for (size_t j = 0; j < 3; ++j) { + if (fi.err[j] > threshold) continue; + + Index3 t = read_triangle(fi); + size_t i0 = t[j]; + VertexInfo &v0 = m_vertexinfo[i0]; + + size_t i1 = t[(j + 1) % 3]; + VertexInfo &v1 = m_vertexinfo[i1]; + + // Border check + if(v0.border != v1.border) continue; + + // Compute vertex to collapse to + Vertex p; + calculate_error(i0, i1, p); + + deleted0.resize(v0.tcount); // normals temporarily + deleted1.resize(v1.tcount); // normals temporarily + + // don't remove if flipped + if (flipped(p, i0, i1, v0, v1, deleted0)) continue; + if (flipped(p, i1, i0, v1, v0, deleted1)) continue; + + // not flipped, so remove edge + write_vertex(v0, p); + v0.q = v1.q + v0.q; + size_t tstart = m_refs.size(); + + update_triangles(i0, v0, deleted0, deleted_triangles); + update_triangles(i0, v1, deleted1, deleted_triangles); + + assert(m_refs.size() >= tstart); + + size_t tcount = m_refs.size() - tstart; + + if(tcount <= v0.tcount) + { + // save ram + if (tcount) { + auto from = m_refs.begin() + tstart, to = from + tcount; + std::copy(from, to, m_refs.begin() + v0.tstart); + } + } + else + // append + v0.tstart = tstart; + + v0.tcount = tcount; + break; + } + } + + if (deleted_triangles <= 0) break; + deleted_triangles = 0; + } + + compact(); +} + +} // namespace implementation +} // namespace SimplifyMesh + +#endif // SIMPLIFYMESHIMPL_HPP diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 5cd97522d..4c6cd62cf 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -70,6 +70,34 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f stl_get_size(&stl); } +TriangleMesh::TriangleMesh(const indexed_triangle_set &M) +{ + stl.stats.type = inmemory; + + // count facets and allocate memory + stl.stats.number_of_facets = uint32_t(M.indices.size()); + stl.stats.original_num_facets = int(stl.stats.number_of_facets); + stl_allocate(&stl); + + for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { + stl_facet facet; + facet.vertex[0] = M.vertices[size_t(M.indices[i](0))]; + facet.vertex[1] = M.vertices[size_t(M.indices[i](1))]; + facet.vertex[2] = M.vertices[size_t(M.indices[i](2))]; + facet.extra[0] = 0; + facet.extra[1] = 0; + + stl_normal normal; + stl_calculate_normal(normal, &facet); + stl_normalize_vector(normal); + facet.normal = normal; + + stl.facet_start[i] = facet; + } + + stl_get_size(&stl); +} + // #define SLIC3R_TRACE_REPAIR void TriangleMesh::repair(bool update_shared_vertices) diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index ef935455e..1a22a9343 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -23,6 +23,7 @@ class TriangleMesh public: TriangleMesh() : repaired(false) {} TriangleMesh(const Pointf3s &points, const std::vector &facets); + explicit TriangleMesh(const indexed_triangle_set &M); void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 8ee43acc8..adcb2722d 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(${_TEST_NAME}_tests test_placeholder_parser.cpp test_polygon.cpp test_stl.cpp + test_meshsimplify.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 0cb1ac343..65b87c2a2 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -9,6 +9,8 @@ #include +#include + #if defined(WIN32) || defined(_WIN32) #define PATH_SEPARATOR R"(\)" #else @@ -23,6 +25,7 @@ static Slic3r::TriangleMesh load_model(const std::string &obj_filename) return mesh; } + TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]") { Slic3r::TriangleMesh in_mesh = load_model("20mm_cube.obj"); @@ -40,3 +43,4 @@ TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]") in_mesh.require_shared_vertices(); in_mesh.WriteOBJFile("merged_out.obj"); } + diff --git a/tests/libslic3r/test_meshsimplify.cpp b/tests/libslic3r/test_meshsimplify.cpp new file mode 100644 index 000000000..d21c3a892 --- /dev/null +++ b/tests/libslic3r/test_meshsimplify.cpp @@ -0,0 +1,11 @@ +#include +#include + +//#include + +//TEST_CASE("Mesh simplification", "[mesh_simplify]") { +// Simplify::load_obj(TEST_DATA_DIR PATH_SEPARATOR "zaba.obj"); +// Simplify::simplify_mesh_lossless(); +// Simplify::write_obj("zaba_simplified.obj"); +//} +