Optimize painting using single triangle brush.

Especially on the detail models, painting using the single triangle brush is noticeably faster.
This commit is contained in:
Lukáš Hejl 2024-04-02 12:24:22 +02:00 committed by Lukas Matena
parent 8cd2e8264a
commit 72f47f7963
3 changed files with 59 additions and 16 deletions

View File

@ -583,11 +583,17 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_
assert(!m_triangles[start_facet_idx].is_split());
TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state();
this->seed_fill_unselect_all_triangles();
if (propagate == BucketFillPropagate::NO) {
if (m_triangle_selected_by_seed_fill != -1)
this->seed_fill_unselect_triangle(m_triangle_selected_by_seed_fill);
m_triangles[start_facet_idx].select_by_seed_fill();
m_triangle_selected_by_seed_fill = start_facet_idx;
return;
} else {
m_triangle_selected_by_seed_fill = -1;
this->seed_fill_unselect_all_triangles();
}
const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON;
@ -1286,42 +1292,43 @@ void TriangleSelector::undivide_triangle(int facet_idx)
}
}
void TriangleSelector::remove_useless_children(int facet_idx)
{
// Returns true when some triangle during recursive descending was removed (undivided).
bool TriangleSelector::remove_useless_children(int facet_idx) {
// Check that all children are leafs of the same type. If not, try to
// make them (recursive call). Remove them if sucessful.
// make them (recursive call). Remove them if successful.
assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid());
Triangle& tr = m_triangles[facet_idx];
Triangle &tr = m_triangles[facet_idx];
if (! tr.is_split()) {
if (!tr.is_split()) {
// This is a leaf, there nothing to do. This can happen during the
// first (non-recursive call). Shouldn't otherwise.
return;
return false;
}
// Call this for all non-leaf children.
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
bool children_removed = false;
for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) {
assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid());
if (m_triangles[tr.children[child_idx]].is_split())
remove_useless_children(tr.children[child_idx]);
children_removed |= remove_useless_children(tr.children[child_idx]);
}
// Return if a child is not leaf or two children differ in type.
TriangleStateType first_child_type = TriangleStateType::NONE;
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) {
if (m_triangles[tr.children[child_idx]].is_split())
return;
return children_removed;
if (child_idx == 0)
first_child_type = m_triangles[tr.children[0]].get_state();
else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type)
return;
return children_removed;
}
// If we got here, the children can be removed.
undivide_triangle(facet_idx);
tr.set_state(first_child_type);
return true;
}
void TriangleSelector::garbage_collect()
@ -2050,7 +2057,22 @@ void TriangleSelector::seed_fill_unselect_all_triangles()
triangle.unselect_by_seed_fill();
}
void TriangleSelector::seed_fill_unselect_triangle(const int facet_idx) {
assert(facet_idx > 0 && facet_idx < m_triangles.size());
Triangle &triangle = m_triangles[facet_idx];
assert(!triangle.is_split());
if (!triangle.is_split())
triangle.unselect_by_seed_fill();
}
void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) {
if (m_triangle_selected_by_seed_fill != -1) {
this->seed_fill_apply_on_single_triangle(new_state, m_triangle_selected_by_seed_fill);
m_triangle_selected_by_seed_fill = -1;
return;
}
for (Triangle &triangle : m_triangles)
if (!triangle.is_split() && triangle.is_selected_by_seed_fill())
triangle.set_state(new_state);
@ -2062,6 +2084,15 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state)
}
}
void TriangleSelector::seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx) {
assert(facet_idx > 0 && facet_idx < m_triangles.size());
if (Triangle &triangle = m_triangles[facet_idx]; !triangle.is_split() && triangle.is_selected_by_seed_fill()) {
triangle.set_state(new_state);
remove_useless_children(triangle.source_triangle);
}
}
double TriangleSelector::get_triangle_area(const Triangle &triangle) const {
const stl_vertex &v0 = m_vertices[triangle.verts_idxs[0]].v;
const stl_vertex &v1 = m_vertices[triangle.verts_idxs[1]].v;

View File

@ -401,10 +401,17 @@ public:
// For all triangles, remove the flag indicating that the triangle was selected by seed fill.
void seed_fill_unselect_all_triangles();
// Remove the flag indicating that the triangle was selected by seed fill.
void seed_fill_unselect_triangle(int facet_idx);
// For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill.
// The operation may merge split triangles if they are being assigned the same color.
void seed_fill_apply_on_triangles(TriangleStateType new_state);
// For the triangle selected by seed fill, set a new TriangleStateType and remove the flag indicating that the triangle was selected by seed fill.
// The operation may merge a split triangle if it is being assigned the same color.
void seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx);
// Compute total area of the triangle.
double get_triangle_area(const Triangle &triangle) const;
@ -495,13 +502,16 @@ protected:
std::unique_ptr<Cursor> m_cursor;
// Single triangle selected by seed fill. It is used to optimize painting using a single triangle brush.
int m_triangle_selected_by_seed_fill = -1;
// Private functions:
private:
bool select_triangle(int facet_idx, TriangleStateType type, bool triangle_splitting);
bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting);
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 remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE);
void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state);

View File

@ -643,9 +643,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) {
assert(projected_mouse_position.mesh_idx == mesh_idx);
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
const int facet_idx = int(projected_mouse_position.facet_idx);
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
const int facet_idx = int(projected_mouse_position.facet_idx);
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
if (m_tool_type == ToolType::SMART_FILL) {
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area,
(m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES);