From f8a5796ca5f676c8fa32629fdad6cb0a4559b4d4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Jan 2020 10:57:51 +0100 Subject: [PATCH] add mesh simplification. SPE-1072 Working but flipped normals with the interior. Testing on treefrog passed Oversampling for hollowed mesh should not be less than 3x Flip back normals after simplify and remove redundant test code. --- src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/OpenVDBUtils.cpp | 12 +- src/libslic3r/SLA/Hollowing.cpp | 69 ++- src/libslic3r/SimplifyMesh.cpp | 66 +++ src/libslic3r/SimplifyMesh.hpp | 25 + src/libslic3r/SimplifyMeshImpl.hpp | 699 ++++++++++++++++++++++++++ src/libslic3r/TriangleMesh.cpp | 28 ++ src/libslic3r/TriangleMesh.hpp | 1 + tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_hollowing.cpp | 4 + tests/libslic3r/test_meshsimplify.cpp | 11 + 11 files changed, 875 insertions(+), 44 deletions(-) create mode 100644 src/libslic3r/SimplifyMesh.cpp create mode 100644 src/libslic3r/SimplifyMesh.hpp create mode 100644 src/libslic3r/SimplifyMeshImpl.hpp create mode 100644 tests/libslic3r/test_meshsimplify.cpp 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"); +//} +