From e898eda320c9eb4fedeb72d3c42e1afd3a841810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 15 Nov 2021 11:39:47 +0100 Subject: [PATCH] Refactoring of Cursors in TriangleSelector as preparation for upcoming improvements of painting. --- src/libslic3r/TriangleSelector.cpp | 167 +++++++++---------- src/libslic3r/TriangleSelector.hpp | 126 ++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 9 +- 3 files changed, 175 insertions(+), 127 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 169978688..a74bd2179 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -124,24 +124,20 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c return this->select_unsplit_triangle(hit, facet_idx, neighbors); } -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, - const Vec3f& source, float radius, - CursorType cursor_type, EnforcerBlockerType new_state, - const Transform3d& trafo, const Transform3d& trafo_no_translate, - bool triangle_splitting, const ClippingPlane &clp, float highlight_by_angle_deg) +void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) { assert(facet_start < m_orig_size_indices); // Save current cursor center, squared radius and camera direction, so we don't // have to pass it around. - m_cursor = Cursor(hit, source, radius, cursor_type, trafo, clp); + m_cursor = std::move(cursor); // In case user changed cursor size since last time, update triangle edge limit. // It is necessary to compare the internal radius in m_cursor! radius is in // world coords and does not change after scaling. - if (m_old_cursor_radius_sqr != m_cursor.radius_sqr) { - set_edge_limit(std::sqrt(m_cursor.radius_sqr) / 5.f); - m_old_cursor_radius_sqr = m_cursor.radius_sqr; + if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) { + set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f); + m_old_cursor_radius_sqr = m_cursor->radius_sqr; } const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); @@ -163,7 +159,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, if (select_triangle(facet, new_state, triangle_splitting)) { // add neighboring facets to list to be processed later for (int neighbor_idx : m_neighbors[facet]) - if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) + if (neighbor_idx >= 0 && m_cursor->is_facet_visible(neighbor_idx, m_face_normals)) facets_to_check.push_back(neighbor_idx); } } @@ -788,11 +784,11 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei assert(this->verify_triangle_neighbors(*tr, neighbors)); - int num_of_inside_vertices = vertices_inside(facet_idx); + int num_of_inside_vertices = m_cursor->vertices_inside(*tr, m_vertices); if (num_of_inside_vertices == 0 - && ! is_pointer_in_triangle(facet_idx) - && ! is_edge_inside_cursor(facet_idx)) + && ! m_cursor->is_pointer_in_triangle(*tr, m_vertices) + && ! m_cursor->is_edge_inside_cursor(*tr, m_vertices)) return false; if (num_of_inside_vertices == 3) { @@ -840,7 +836,7 @@ void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state) } // called by select_patch()->select_triangle()...select_triangle() -// to decide which sides of the traingle to split and to actually split it calling set_division() and perform_split(). +// to decide which sides of the triangle to split and to actually split it calling set_division() and perform_split(). void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) { if (m_triangles[facet_idx].is_split()) { @@ -864,9 +860,9 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) // In case the object is non-uniformly scaled, transform the // points to world coords. - if (! m_cursor.uniform_scaling) { + if (! m_cursor->uniform_scaling) { for (size_t i=0; itrafo * (*pts[i]); pts[i] = &pts_transformed[i]; } } @@ -897,72 +893,63 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) perform_split(facet_idx, neighbors, old_type); } - - // Is pointer in a triangle? -bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const -{ - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; - return m_cursor.is_pointer_in_triangle(p1, p2, p3); +bool TriangleSelector::Cursor::is_pointer_in_triangle(const Triangle &tr, const std::vector &vertices) const { + const Vec3f& p1 = vertices[tr.verts_idxs[0]].v; + const Vec3f& p2 = vertices[tr.verts_idxs[1]].v; + const Vec3f& p3 = vertices[tr.verts_idxs[2]].v; + return this->is_pointer_in_triangle(p1, p2, p3); } - - // Determine whether this facet is potentially visible (still can be obscured). -bool TriangleSelector::faces_camera(int facet) const +bool TriangleSelector::Circle::is_facet_visible(int facet_idx, const std::vector &face_normals) const { - assert(facet < m_orig_size_indices); - Vec3f n = m_face_normals[facet]; - if (! m_cursor.uniform_scaling) - n = m_cursor.trafo_normal * n; - return n.dot(m_cursor.dir) < 0.; + assert(facet_idx < int(face_normals.size())); + Vec3f n = face_normals[facet_idx]; + if (!this->uniform_scaling) + n = this->trafo_normal * n; + return n.dot(this->dir) < 0.f; } - // How many vertices of a triangle are inside the circle? -int TriangleSelector::vertices_inside(int facet_idx) const +int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vector &vertices) const { int inside = 0; - for (size_t i=0; i<3; ++i) { - if (m_cursor.is_mesh_point_inside(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) + for (size_t i = 0; i < 3; ++i) + if (this->is_mesh_point_inside(vertices[tr.verts_idxs[i]].v)) ++inside; - } + return inside; } - // Is edge inside cursor? -bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const +bool TriangleSelector::SinglePointCursor::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { std::array pts; - for (int i=0; i<3; ++i) { - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; - if (! m_cursor.uniform_scaling) - pts[i] = m_cursor.trafo * pts[i]; + for (int i = 0; i < 3; ++i) { + pts[i] = vertices[tr.verts_idxs[i]].v; + if (!this->uniform_scaling) + pts[i] = this->trafo * pts[i]; } - const Vec3f& p = m_cursor.center; + const Vec3f &p = this->center; for (int side = 0; side < 3; ++side) { - const Vec3f& a = pts[side]; - const Vec3f& b = pts[side<2 ? side+1 : 0]; - Vec3f s = (b-a).normalized(); - float t = (p-a).dot(s); - Vec3f vector = a+t*s - p; + const Vec3f &a = pts[side]; + const Vec3f &b = pts[side < 2 ? side + 1 : 0]; + Vec3f s = (b - a).normalized(); + float t = (p - a).dot(s); + Vec3f vector = a + t * s - p; // vector is 3D vector from center to the intersection. What we want to // measure is length of its projection onto plane perpendicular to dir. - float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); - if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(this->dir), 2.f); + if (dist_sqr < this->radius_sqr && t >= 0.f && t <= (b - a).norm()) return true; } return false; } - - // Recursively remove all subtriangles. void TriangleSelector::undivide_triangle(int facet_idx) { @@ -1002,7 +989,6 @@ void TriangleSelector::undivide_triangle(int facet_idx) } } - void TriangleSelector::remove_useless_children(int facet_idx) { // Check that all children are leafs of the same type. If not, try to @@ -1041,8 +1027,6 @@ void TriangleSelector::remove_useless_children(int facet_idx) tr.set_state(first_child_type); } - - void TriangleSelector::garbage_collect() { // First make a map from old to new triangle indices. @@ -1103,7 +1087,6 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh) reset(); } - void TriangleSelector::reset() { m_vertices.clear(); @@ -1124,17 +1107,11 @@ void TriangleSelector::reset() } - - - - void TriangleSelector::set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } - - int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state) { for (int i : {a, b, c}) { @@ -1693,42 +1670,55 @@ void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_stat } } -TriangleSelector::Cursor::Cursor( - const Vec3f& center_, const Vec3f& source_, float radius_world, - CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_) - : center{center_}, - source{source_}, - type{type_}, - trafo{trafo_.cast()}, - clipping_plane(clipping_plane_) +TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) + : source{source_}, trafo{trafo_.cast()}, clipping_plane{clipping_plane_} { Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor(); if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) { - radius_sqr = float(std::pow(radius_world / sf(0), 2)); + radius_sqr = float(std::pow(radius_world / sf(0), 2)); uniform_scaling = true; - } - else { + } else { // In case that the transformation is non-uniform, all checks whether // something is inside the cursor should be done in world coords. - // First transform center, source and dir in world coords and remember - // that we did this. - center = trafo * center; - source = trafo * source; + // First transform source in world coords and remember that we did this. + source = trafo * source; uniform_scaling = false; - radius_sqr = radius_world * radius_world; - trafo_normal = trafo.linear().inverse().transpose(); + radius_sqr = radius_world * radius_world; + trafo_normal = trafo.linear().inverse().transpose(); } +} + +TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_) + : center{center_}, Cursor(source_, radius_world, trafo_, clipping_plane_) +{ + // In case that the transformation is non-uniform, all checks whether + // something is inside the cursor should be done in world coords. + // Because of the center is transformed. + if (!uniform_scaling) + center = trafo * center; // Calculate dir, in whatever coords is appropriate. dir = (center - source).normalized(); } -// Is a point (in mesh coords) inside a cursor? -bool TriangleSelector::Cursor::is_mesh_point_inside(const Vec3f &point) const +// Is a point (in mesh coords) inside a Sphere cursor? +bool TriangleSelector::Sphere::is_mesh_point_inside(const Vec3f &point) const +{ + const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point); + const bool is_point_inside = (center - point).squaredNorm() < radius_sqr; + + if (is_point_inside && clipping_plane.is_active()) + return !clipping_plane.is_mesh_point_clipped(point); + + return is_point_inside; +} + +// Is a point (in mesh coords) inside a Circle cursor? +bool TriangleSelector::Circle::is_mesh_point_inside(const Vec3f &point) const { const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point); const Vec3f diff = center - transformed_point; - const bool is_point_inside = (type == CIRCLE ? (diff - diff.dot(dir) * dir).squaredNorm() : diff.squaredNorm()) < radius_sqr; + const bool is_point_inside = (diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr; if (is_point_inside && clipping_plane.is_active()) return !clipping_plane.is_mesh_point_clipped(point); @@ -1737,10 +1727,7 @@ bool TriangleSelector::Cursor::is_mesh_point_inside(const Vec3f &point) const } // p1, p2, p3 are in mesh coords! -bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_, - const Vec3f& p2_, - const Vec3f& p3_) const -{ +static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_, const Vec3f ¢er, const Vec3f &dir, const bool uniform_scaling, const Transform3f &trafo) { const Vec3f& q1 = center + dir; const Vec3f& q2 = center - dir; @@ -1761,4 +1748,10 @@ bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_, return signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos; } +// p1, p2, p3 are in mesh coords! +bool TriangleSelector::SinglePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const +{ + return is_circle_pointer_inside_triangle(p1_, p2_, p3_, center, dir, uniform_scaling, trafo); +} + } // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index b9f136c2e..05930731c 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -15,7 +15,12 @@ enum class EnforcerBlockerType : int8_t; // Following class holds information about selected triangles. It also has power // to recursively subdivide the triangles and make the selection finer. -class TriangleSelector { +class TriangleSelector +{ +protected: + class Triangle; + struct Vertex; + public: enum CursorType { CIRCLE, @@ -35,6 +40,83 @@ public: bool is_mesh_point_clipped(const Vec3f &point) const { return normal.dot(point) - offset > 0.f; } }; + class Cursor + { + public: + Cursor() = delete; + virtual ~Cursor() = default; + + bool is_pointer_in_triangle(const Triangle &tr, const std::vector &vertices) const; + + virtual bool is_mesh_point_inside(const Vec3f &point) const = 0; + virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0; + virtual int vertices_inside(const Triangle &tr, const std::vector &vertices) const; + virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; + virtual bool is_facet_visible(int facet_idx, const std::vector &face_normals) const { return true; } + + protected: + explicit Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_); + + Transform3f trafo; + Vec3f source; + + bool uniform_scaling; + Transform3f trafo_normal; + float radius_sqr; + Vec3f dir = Vec3f(0.f, 0.f, 0.f); + + ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only + + friend TriangleSelector; + }; + + class SinglePointCursor : public Cursor + { + public: + SinglePointCursor() = delete; + ~SinglePointCursor() override = default; + + bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override; + + static std::unique_ptr cursor_factory(const Vec3f ¢er, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane) + { + assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE); + if (cursor_type == TriangleSelector::CursorType::SPHERE) + return std::make_unique(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane); + else + return std::make_unique(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane); + } + + protected: + explicit SinglePointCursor(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_); + + Vec3f center; + }; + + class Sphere : public SinglePointCursor + { + public: + Sphere() = delete; + explicit Sphere(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) + : SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){}; + ~Sphere() override = default; + + bool is_mesh_point_inside(const Vec3f &point) const override; + }; + + class Circle : public SinglePointCursor + { + public: + Circle() = delete; + explicit Circle(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) + : SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){}; + ~Circle() override = default; + + bool is_mesh_point_inside(const Vec3f &point) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override; + }; + std::pair, std::vector> precompute_all_neighbors() const; void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_normal_out) const; @@ -51,17 +133,12 @@ public: [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const; // Select all triangles fully inside the circle, subdivide where needed. - void select_patch(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - const Vec3f &source, // camera position (mesh coords) - float radius, // radius of the cursor - CursorType type, // current type of cursor - EnforcerBlockerType new_state, // enforcer or blocker? - const Transform3d &trafo, // matrix to get from mesh to world - const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation - bool triangle_splitting, // If triangles will be split base on the cursor or not - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + void select_patch(int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + std::unique_ptr &&cursor, // Cursor containing information about the point where to start, camera position (mesh coords), matrix to get from mesh to world, and its shape and type. + EnforcerBlockerType new_state, // enforcer or blocker? + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + bool triangle_splitting, // If triangles will be split base on the cursor or not + float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. void seed_fill_select_triangles(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to @@ -195,39 +272,16 @@ protected: int m_orig_size_vertices = 0; int m_orig_size_indices = 0; - // Cache for cursor position, radius and direction. - struct Cursor { - Cursor() = default; - Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world, - CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_); - bool is_mesh_point_inside(const Vec3f &pt) const; - bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const; - - Vec3f center; - Vec3f source; - Vec3f dir; - float radius_sqr; - CursorType type; - Transform3f trafo; - Transform3f trafo_normal; - bool uniform_scaling; - ClippingPlane clipping_plane; - }; - - Cursor m_cursor; + std::unique_ptr m_cursor; float m_old_cursor_radius_sqr; // Private functions: private: bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting); bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting); - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i &neighbors); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. - bool is_pointer_in_triangle(int facet_idx) const; - bool is_edge_inside_cursor(int facet_idx) const; bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const; int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0}); void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 821ce367a..1b9d996e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -365,10 +365,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true, true); m_seed_fill_last_mesh_id = -1; - } else if (m_tool_type == ToolType::BRUSH) - m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type, - new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, clp, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } else if (m_tool_type == ToolType::BRUSH) { + auto cursor = TriangleSelector::SinglePointCursor::cursor_factory(m_rr.hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp); + m_triangle_selectors[m_rr.mesh_id]->select_patch(int(m_rr.facet), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } + m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_last_mouse_click = mouse_position; }