#include "MeshUtils.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "slic3r/GUI/Camera.hpp" #include #include namespace Slic3r { namespace GUI { void MeshClipper::set_plane(const ClippingPlane& plane) { if (m_plane != plane) { m_plane = plane; m_triangles_valid = false; } } void MeshClipper::set_mesh(const TriangleMesh& mesh) { if (m_mesh != &mesh) { m_mesh = &mesh; m_triangles_valid = false; m_triangles2d.resize(0); } } void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) { if (m_negative_mesh != &mesh) { m_negative_mesh = &mesh; m_triangles_valid = false; m_triangles2d.resize(0); } } void MeshClipper::set_transformation(const Geometry::Transformation& trafo) { if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { m_trafo = trafo; m_triangles_valid = false; m_triangles2d.resize(0); } } void MeshClipper::render_cut() { if (! m_triangles_valid) recalculate_triangles(); if (m_vertex_array.has_VBOs()) m_vertex_array.render(); } void MeshClipper::recalculate_triangles() { const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); const Vec3f& scaling = m_trafo.get_scaling_factor().cast(); // Calculate clipping plane normal in mesh coordinates. Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); Vec3d up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); // Now do the cutting MeshSlicingParamsEx slicing_params; slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); assert(m_mesh->has_shared_vertices()); std::vector list_of_expolys = slice_mesh_ex(m_mesh->its, std::vector{height_mesh}, slicing_params); if (m_negative_mesh && !m_negative_mesh->empty()) { assert(m_negative_mesh->has_shared_vertices()); std::vector neg_polys = slice_mesh_ex(m_negative_mesh->its, std::vector{height_mesh}, slicing_params); list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); } m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: Eigen::Quaterniond q; q.setFromTwoVectors(Vec3d::UnitZ(), up); Transform3d tr = Transform3d::Identity(); tr.rotate(q); tr = m_trafo.get_matrix() * tr; // to avoid z-fighting height_mesh += 0.001f; m_vertex_array.release_geometry(); for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); size_t idx = it - m_triangles2d.cbegin(); m_vertex_array.push_triangle(idx, idx+1, idx+2); } m_vertex_array.finalize_geometry(true); m_triangles_valid = true; } Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const { return m_normals[facet_idx]; } void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) const { Matrix4d modelview = camera.get_view_matrix().matrix(); Matrix4d projection= camera.get_projection_matrix().matrix(); Vec4i viewport(camera.get_viewport().data()); Vec3d pt1; Vec3d pt2; igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.), modelview, projection, viewport, pt1); igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), modelview, projection, viewport, pt2); Transform3d inv = trafo.inverse(); pt1 = inv * pt1; pt2 = inv * pt2; point = pt1; direction = pt2-pt1; } bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, size_t* facet_idx) const { Vec3d point; Vec3d direction; line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); std::vector hits = m_emesh.query_ray_hits(point, direction); if (hits.empty()) return false; // no intersection found unsigned i = 0; // Remove points that are obscured or cut by the clipping plane if (clipping_plane) { for (i=0; iis_point_clipped(trafo * hits[i].position())) break; if (i==hits.size() || (hits.size()-i) % 2 != 0) { // All hits are either clipped, or there is an odd number of unclipped // hits - meaning the nearest must be from inside the mesh. return false; } } // Now stuff the points in the provided vector and calculate normals if asked about them: position = hits[i].position().cast(); normal = hits[i].normal().cast(); if (facet_idx) *facet_idx = hits[i].face(); return true; } std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, const ClippingPlane* clipping_plane) const { std::vector out; const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); Vec3f direction_to_camera = -camera.get_dir_forward().cast(); Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); Vec3f scaling = trafo.get_scaling_factor().cast(); direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); const Transform3f inverse_trafo = trafo.get_matrix().inverse().cast(); for (size_t i=0; iis_point_clipped(pt.cast())) continue; bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; // Offset the start of the ray by EPSILON to account for numerical inaccuracies. hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast(), direction_to_camera.cast()); if (! hits.empty()) { // If the closest hit facet normal points in the same direction as the ray, // we are looking through the mesh and should therefore discard the point: if (hits.front().normal().dot(direction_to_camera_mesh.cast()) > 0) is_obscured = true; // Eradicate all hits that the caller wants to ignore for (unsigned j=0; jis_point_clipped(trafo.get_matrix() * hits[j].position())) { hits.erase(hits.begin()+j); --j; } } // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. // Also, the threshold is in mesh coordinates, not in actual dimensions. if (! hits.empty()) is_obscured = true; } if (! is_obscured) out.push_back(i); } return out; } Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const { int idx = 0; Vec3d closest_point; m_emesh.squared_distance(point.cast(), idx, closest_point); if (normal) *normal = m_normals[idx]; return closest_point.cast(); } } // namespace GUI } // namespace Slic3r