1 #include "MeshUtils.hpp"
2 
3 #include "libslic3r/Tesselate.hpp"
4 #include "libslic3r/TriangleMesh.hpp"
5 
6 #include "slic3r/GUI/Camera.hpp"
7 
8 #include <GL/glew.h>
9 
10 
11 namespace Slic3r {
12 namespace GUI {
13 
set_plane(const ClippingPlane & plane)14 void MeshClipper::set_plane(const ClippingPlane& plane)
15 {
16     if (m_plane != plane) {
17         m_plane = plane;
18         m_triangles_valid = false;
19     }
20 }
21 
22 
23 
set_mesh(const TriangleMesh & mesh)24 void MeshClipper::set_mesh(const TriangleMesh& mesh)
25 {
26     if (m_mesh != &mesh) {
27         m_mesh = &mesh;
28         m_triangles_valid = false;
29         m_triangles2d.resize(0);
30         m_tms.reset(nullptr);
31     }
32 }
33 
34 
35 
set_transformation(const Geometry::Transformation & trafo)36 void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
37 {
38     if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
39         m_trafo = trafo;
40         m_triangles_valid = false;
41         m_triangles2d.resize(0);
42     }
43 }
44 
45 
46 
render_cut()47 void MeshClipper::render_cut()
48 {
49     if (! m_triangles_valid)
50         recalculate_triangles();
51 
52     if (m_vertex_array.has_VBOs())
53         m_vertex_array.render();
54 }
55 
56 
57 
recalculate_triangles()58 void MeshClipper::recalculate_triangles()
59 {
60     if (! m_tms) {
61         m_tms.reset(new TriangleMeshSlicer);
62         m_tms->init(m_mesh, [](){});
63     }
64 
65     const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
66     const Vec3f& scaling = m_trafo.get_scaling_factor().cast<float>();
67     // Calculate clipping plane normal in mesh coordinates.
68     Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
69     Vec3d up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
70     // Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
71     float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
72 
73     // Now do the cutting
74     std::vector<ExPolygons> list_of_expolys;
75     m_tms->set_up_direction(up.cast<float>());
76     m_tms->slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){});
77     m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.);
78 
79     // Rotate the cut into world coords:
80     Eigen::Quaterniond q;
81     q.setFromTwoVectors(Vec3d::UnitZ(), up);
82     Transform3d tr = Transform3d::Identity();
83     tr.rotate(q);
84     tr = m_trafo.get_matrix() * tr;
85 
86     // to avoid z-fighting
87     height_mesh += 0.001f;
88 
89     m_vertex_array.release_geometry();
90     for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
91         m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
92         m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
93         m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
94         size_t idx = it - m_triangles2d.cbegin();
95         m_vertex_array.push_triangle(idx, idx+1, idx+2);
96     }
97     m_vertex_array.finalize_geometry(true);
98 
99     m_triangles_valid = true;
100 }
101 
102 
get_triangle_normal(size_t facet_idx) const103 Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const
104 {
105     return m_normals[facet_idx];
106 }
107 
line_from_mouse_pos(const Vec2d & mouse_pos,const Transform3d & trafo,const Camera & camera,Vec3d & point,Vec3d & direction) const108 void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
109                                         Vec3d& point, Vec3d& direction) const
110 {
111     const std::array<int, 4>& viewport = camera.get_viewport();
112     const Transform3d& model_mat = camera.get_view_matrix();
113     const Transform3d& proj_mat = camera.get_projection_matrix();
114 
115     Vec3d pt1;
116     Vec3d pt2;
117     ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2));
118     ::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2));
119 
120     Transform3d inv = trafo.inverse();
121     pt1 = inv * pt1;
122     pt2 = inv * pt2;
123 
124     point = pt1;
125     direction = pt2-pt1;
126 }
127 
128 
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) const129 bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
130                                       Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane,
131                                       size_t* facet_idx) const
132 {
133     Vec3d point;
134     Vec3d direction;
135     line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
136 
137     std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
138 
139     if (hits.empty())
140         return false; // no intersection found
141 
142     unsigned i = 0;
143 
144     // Remove points that are obscured or cut by the clipping plane
145     if (clipping_plane) {
146         for (i=0; i<hits.size(); ++i)
147             if (! clipping_plane->is_point_clipped(trafo * hits[i].position()))
148                 break;
149 
150         if (i==hits.size() || (hits.size()-i) % 2 != 0) {
151             // All hits are either clipped, or there is an odd number of unclipped
152             // hits - meaning the nearest must be from inside the mesh.
153             return false;
154         }
155     }
156 
157     // Now stuff the points in the provided vector and calculate normals if asked about them:
158     position = hits[i].position().cast<float>();
159     normal = hits[i].normal().cast<float>();
160 
161     if (facet_idx)
162         *facet_idx = hits[i].face();
163 
164     return true;
165 }
166 
167 
get_unobscured_idxs(const Geometry::Transformation & trafo,const Camera & camera,const std::vector<Vec3f> & points,const ClippingPlane * clipping_plane) const168 std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
169                                                        const ClippingPlane* clipping_plane) const
170 {
171     std::vector<unsigned> out;
172 
173     const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true);
174     Vec3f direction_to_camera = -camera.get_dir_forward().cast<float>();
175     Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
176     Vec3f scaling = trafo.get_scaling_factor().cast<float>();
177     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));
178     const Transform3f inverse_trafo = trafo.get_matrix().inverse().cast<float>();
179 
180     for (size_t i=0; i<points.size(); ++i) {
181         const Vec3f& pt = points[i];
182         if (clipping_plane && clipping_plane->is_point_clipped(pt.cast<double>()))
183             continue;
184 
185         bool is_obscured = false;
186         // Cast a ray in the direction of the camera and look for intersection with the mesh:
187         std::vector<sla::IndexedMesh::hit_result> hits;
188         // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
189         hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
190                                       direction_to_camera.cast<double>());
191 
192 
193         if (! hits.empty()) {
194             // If the closest hit facet normal points in the same direction as the ray,
195             // we are looking through the mesh and should therefore discard the point:
196             if (hits.front().normal().dot(direction_to_camera_mesh.cast<double>()) > 0)
197                 is_obscured = true;
198 
199             // Eradicate all hits that the caller wants to ignore
200             for (unsigned j=0; j<hits.size(); ++j) {
201                 if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * hits[j].position())) {
202                     hits.erase(hits.begin()+j);
203                     --j;
204                 }
205             }
206 
207             // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
208             // Also, the threshold is in mesh coordinates, not in actual dimensions.
209             if (! hits.empty())
210                 is_obscured = true;
211         }
212         if (! is_obscured)
213             out.push_back(i);
214     }
215     return out;
216 }
217 
218 
get_closest_point(const Vec3f & point,Vec3f * normal) const219 Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
220 {
221     int idx = 0;
222     Vec3d closest_point;
223     m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
224     if (normal)
225         *normal = m_normals[idx];
226 
227     return closest_point.cast<float>();
228 }
229 
230 
231 
232 } // namespace GUI
233 } // namespace Slic3r
234