Merge branch 'lh_mm_gizmo'

This commit is contained in:
Lukas Matena 2024-11-18 23:33:52 +01:00
commit 7cc94a31e5
10 changed files with 705 additions and 189 deletions

View File

@ -7945,7 +7945,7 @@ mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file
while (p_nf + 4 < e) {
mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8);
if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') {
mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1);
mz_uint length = MZ_MIN((mz_uint)len - 5, extra_buf_size - 1);
memcpy(buffer, p_nf + 9, length);
return length;
}

View File

@ -241,27 +241,21 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
{
assert(facet_start < m_orig_size_indices);
assert(this->is_original_triangle(facet_start));
// Save current cursor center, squared radius and camera direction, so we don't
// have to pass it around.
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;
}
// In case user changed cursor parameters size since last time, update triangle edge limit.
set_edge_limit(m_cursor->get_edge_limit());
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
// Now start with the facet the pointer points to and check all adjacent facets.
std::vector<int> facets_to_check;
std::vector<int> facets_to_check = m_cursor->get_facets_to_select(facet_start, m_vertices, m_triangles, m_orig_size_vertices, m_orig_size_indices);
facets_to_check.reserve(16);
facets_to_check.emplace_back(facet_start);
// Keep track of facets of the original mesh we already processed.
std::vector<bool> visited(m_orig_size_indices, false);
// Breadth-first search around the hit point. facets_to_check may grow significantly large.
@ -292,14 +286,28 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp)
return false;
}
bool TriangleSelector::is_any_neighbor_selected_by_seed_fill(const Triangle &triangle) {
size_t triangle_idx = &triangle - m_triangles.data();
assert(triangle_idx < m_triangles.size());
for (int neighbor_idx: m_neighbors[triangle_idx]) {
assert(neighbor_idx >= -1);
if (neighbor_idx >= 0 && m_triangles[neighbor_idx].is_selected_by_seed_fill())
return true;
}
return false;
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate,
const ClippingPlane &clp, float seed_fill_angle, float highlight_by_angle_deg,
bool force_reselection)
const ClippingPlane &clp, float seed_fill_angle, float seed_fill_gap_area,
float highlight_by_angle_deg, ForceReselection force_reselection)
{
assert(facet_start < m_orig_size_indices);
assert(this->is_original_triangle(facet_start));
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active.
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active())
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active())
return;
this->seed_fill_unselect_all_triangles();
@ -308,10 +316,13 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
std::queue<int> facet_queue;
facet_queue.push(facet_start);
const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
const float facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast<float>();
// Facets that need to be checked for gap filling.
std::vector<int> gap_fill_candidate_facets;
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
while (!facet_queue.empty()) {
int current_facet = facet_queue.front();
@ -323,14 +334,15 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) {
assert(split_triangle_idx < int(m_triangles[current_facet].children.size()));
assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.size()));
if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child])
if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) {
// Child triangle shares normal with its parent. Select it.
facet_queue.push(child);
}
}
} else
m_triangles[current_facet].select_by_seed_fill();
if (current_facet < m_orig_size_indices)
if (this->is_original_triangle(current_facet)) {
// Propagate over the original triangles.
for (int neighbor_idx : m_neighbors[current_facet]) {
assert(neighbor_idx >= -1);
@ -338,13 +350,102 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
const Vec3f &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle];
const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle];
if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit)
if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) {
facet_queue.push(neighbor_idx);
} else if (seed_fill_gap_area > 0. && get_triangle_area(m_triangles[neighbor_idx]) <= seed_fill_gap_area) {
gap_fill_candidate_facets.emplace_back(neighbor_idx);
}
}
}
}
}
visited[current_facet] = true;
}
seed_fill_fill_gaps(gap_fill_candidate_facets, seed_fill_gap_area);
}
void TriangleSelector::seed_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, const float seed_fill_gap_area) {
std::vector<bool> visited(m_triangles.size(), false);
for (const int starting_facet_idx: gap_fill_candidate_facets) {
const Triangle &starting_facet = m_triangles[starting_facet_idx];
// If starting_facet_idx was visited from any facet, then we can skip it.
if (visited[starting_facet_idx])
continue;
// In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met.
// But both of those conditions are here to allow more general usage of this method.
if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) {
// Already selected by seed fill or split facet, so no additional actions are required.
visited[starting_facet_idx] = true;
continue;
} else if (!is_any_neighbor_selected_by_seed_fill(starting_facet)) {
// No neighbor triangles are selected by seed fill, so we will skip them for now.
continue;
}
// Now we have a triangle that has at least one neighbor selected by seed fill.
// So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check
// if the total area of unselected triangles by seed fill meets the threshold.
double total_gap_area = 0.;
std::queue<int> facet_queue;
std::vector<int> gap_facets;
facet_queue.push(starting_facet_idx);
while (!facet_queue.empty()) {
const int current_facet_idx = facet_queue.front();
const Triangle &current_facet = m_triangles[current_facet_idx];
facet_queue.pop();
if (visited[current_facet_idx])
continue;
if (this->is_original_triangle(current_facet_idx))
total_gap_area += get_triangle_area(current_facet);
// We exceed maximum gap area.
if (total_gap_area > seed_fill_gap_area) {
// It is necessary to set every facet inside gap_facets unvisited.
// Otherwise, we incorrectly select facets that are in a gap that is bigger
// than seed_fill_gap_area.
for (const int gap_facet_idx : gap_facets)
visited[gap_facet_idx] = false;
gap_facets.clear();
break;
}
if (current_facet.is_split()) {
for (int split_triangle_idx = 0; split_triangle_idx <= current_facet.number_of_split_sides(); ++split_triangle_idx) {
assert(split_triangle_idx < int(current_facet.children.size()));
assert(current_facet.children[split_triangle_idx] < int(m_triangles.size()));
if (int child = current_facet.children[split_triangle_idx]; !visited[child])
facet_queue.push(child);
}
} else if (total_gap_area < seed_fill_gap_area) {
gap_facets.emplace_back(current_facet_idx);
}
if (this->is_original_triangle(current_facet_idx)) {
// Propagate over the original triangles.
for (int neighbor_idx: m_neighbors[current_facet_idx]) {
assert(neighbor_idx >= -1);
if (neighbor_idx >= 0 && !visited[neighbor_idx] && !m_triangles[neighbor_idx].is_selected_by_seed_fill())
facet_queue.push(neighbor_idx);
}
}
visited[current_facet_idx] = true;
}
for (int to_seed_idx : gap_facets)
m_triangles[to_seed_idx].select_by_seed_fill();
gap_facets.clear();
}
}
void TriangleSelector::precompute_all_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_propagated_out) const
@ -451,43 +552,59 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver
process_subtriangle(touching.second, Partition::Second);
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, bool propagate, bool force_reselection)
{
// Returns all triangles that are touching the given facet.
std::vector<int> TriangleSelector::get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const {
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
const Vec3i vertices = { m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2] };
std::vector<int> touching_triangles;
append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles);
append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles);
append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles);
for (int neighbor_idx: neighbors_propagated) {
if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split())
touching_triangles.emplace_back(neighbor_idx);
}
return touching_triangles;
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_start, const ClippingPlane &clp,
float bucket_fill_angle, float bucket_fill_gap_area,
BucketFillPropagate propagate, ForceReselection force_reselection) {
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
assert(start_facet_idx != -1);
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active.
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active()))
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active()))
return;
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) {
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();
}
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector<int> {
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
std::vector<int> touching_triangles;
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles);
append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles);
append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles);
for (int neighbor_idx : neighbors_propagated)
if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split())
touching_triangles.emplace_back(neighbor_idx);
return touching_triangles;
};
const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON;
auto [neighbors, neighbors_propagated] = this->precompute_all_neighbors();
std::vector<bool> visited(m_triangles.size(), false);
std::queue<int> facet_queue;
// Facets that need to be checked for gap filling.
std::vector<int> gap_fill_candidate_facets;
facet_queue.push(start_facet_idx);
while (!facet_queue.empty()) {
int current_facet = facet_queue.front();
@ -497,18 +614,98 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
if (!visited[current_facet]) {
m_triangles[current_facet].select_by_seed_fill();
std::vector<int> touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
for(const int tr_idx : touching_triangles) {
std::vector<int> touching_triangles = this->get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
for (const int tr_idx: touching_triangles) {
if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || is_facet_clipped(tr_idx, clp))
continue;
assert(!m_triangles[tr_idx].is_split());
facet_queue.push(tr_idx);
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
const Vec3f &n1 = m_face_normals[m_triangles[tr_idx].source_triangle];
const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle];
if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) {
assert(!m_triangles[tr_idx].is_split());
facet_queue.push(tr_idx);
} else if (bucket_fill_gap_area > 0. && get_triangle_area(m_triangles[tr_idx]) <= bucket_fill_gap_area) {
gap_fill_candidate_facets.emplace_back(tr_idx);
}
}
}
visited[current_facet] = true;
}
bucket_fill_fill_gaps(gap_fill_candidate_facets, bucket_fill_gap_area, start_facet_state, neighbors, neighbors_propagated);
}
void TriangleSelector::bucket_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, const float bucket_fill_gap_area,
const TriangleStateType start_facet_state, const std::vector<Vec3i> &neighbors,
const std::vector<Vec3i> &neighbors_propagated) {
std::vector<bool> visited(m_triangles.size(), false);
for (const int starting_facet_idx: gap_fill_candidate_facets) {
const Triangle &starting_facet = m_triangles[starting_facet_idx];
// If starting_facet_idx was visited from any facet, then we can skip it.
if (visited[starting_facet_idx])
continue;
// In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met.
// But both of those conditions are here to allow more general usage of this method.
if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) {
// Already selected by seed fill or split facet, so no additional actions are required.
visited[starting_facet_idx] = true;
continue;
}
// In the way how bucket_fill_select_triangles() is implemented, all gap_fill_candidate_facets
// have at least one neighbor selected by seed fill.
// So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check
// if the total area of unselected triangles by seed fill meets the threshold.
double total_gap_area = 0.;
std::queue<int> facet_queue;
std::vector<int> gap_facets;
facet_queue.push(starting_facet_idx);
while (!facet_queue.empty()) {
const int current_facet_idx = facet_queue.front();
const Triangle &current_facet = m_triangles[current_facet_idx];
facet_queue.pop();
if (visited[current_facet_idx])
continue;
total_gap_area += get_triangle_area(current_facet);
// We exceed maximum gap area.
if (total_gap_area > bucket_fill_gap_area) {
// It is necessary to set every facet inside gap_facets unvisited.
// Otherwise, we incorrectly select facets that are in a gap that is bigger
// than bucket_fill_gap_area.
for (const int gap_facet_idx : gap_facets)
visited[gap_facet_idx] = false;
gap_facets.clear();
break;
}
gap_facets.emplace_back(current_facet_idx);
std::vector<int> touching_triangles = this->get_all_touching_triangles(current_facet_idx, neighbors[current_facet_idx], neighbors_propagated[current_facet_idx]);
for (const int tr_idx: touching_triangles) {
if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || m_triangles[tr_idx].is_selected_by_seed_fill())
continue;
facet_queue.push(tr_idx);
}
visited[current_facet_idx] = true;
}
for (int to_seed_idx : gap_facets)
m_triangles[to_seed_idx].select_by_seed_fill();
gap_facets.clear();
}
}
// Selects either the whole triangle (discarding any children it had), or divides
@ -878,7 +1075,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
if (num_of_inside_vertices == 0
&& ! m_cursor->is_pointer_in_triangle(*tr, m_vertices)
&& ! m_cursor->is_edge_inside_cursor(*tr, m_vertices))
&& ! m_cursor->is_any_edge_inside_cursor(*tr, m_vertices))
return false;
if (num_of_inside_vertices == 3) {
@ -918,7 +1115,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
}
void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) {
assert(facet_idx < m_orig_size_indices);
assert(this->is_original_triangle(facet_idx));
undivide_triangle(facet_idx);
assert(! m_triangles[facet_idx].is_split());
m_triangles[facet_idx].set_state(state);
@ -949,8 +1146,8 @@ 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) {
for (size_t i=0; i<pts.size(); ++i) {
if (!m_cursor->uniform_scaling) {
for (size_t i = 0; i < pts.size(); ++i) {
pts_transformed[i] = m_cursor->trafo * (*pts[i]);
pts[i] = &pts_transformed[i];
}
@ -1011,16 +1208,21 @@ int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vec
return inside;
}
// Is any edge inside Sphere cursor?
bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
{
inline std::array<Vec3f, 3> TriangleSelector::Cursor::transform_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const {
std::array<Vec3f, 3> pts;
for (int i = 0; i < 3; ++i) {
for (size_t i = 0; i < 3; ++i) {
pts[i] = vertices[tr.verts_idxs[i]].v;
if (!this->uniform_scaling)
pts[i] = this->trafo * pts[i];
}
return pts;
}
// Is any edge inside Sphere cursor?
bool TriangleSelector::Sphere::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
{
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
for (int side = 0; side < 3; ++side) {
const Vec3f &edge_a = pts[side];
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
@ -1031,16 +1233,10 @@ bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const s
}
// Is edge inside cursor?
bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
bool TriangleSelector::Circle::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
{
std::array<Vec3f, 3> pts;
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 = this->center;
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
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];
@ -1096,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()
@ -1216,7 +1413,7 @@ void TriangleSelector::reset()
void TriangleSelector::set_edge_limit(float edge_limit)
{
m_edge_limit_sqr = std::pow(edge_limit, 2.f);
m_edge_limit_sqr = Slic3r::sqr(edge_limit);
}
int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) {
@ -1860,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);
@ -1872,13 +2084,29 @@ 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;
const stl_vertex &v2 = m_vertices[triangle.verts_idxs[2]].v;
return (v1 - v0).cross(v2 - v0).norm() / 2.;
}
TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
: source{source_}, trafo{trafo_.cast<float>()}, 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 = float(radius_world / sf(0));
radius_sqr = float(Slic3r::sqr(radius_world / sf(0)));
if (is_approx(sf.x(), sf.y()) && is_approx(sf.y(), sf.z())) {
radius = float(radius_world / sf.x());
radius_sqr = float(Slic3r::sqr(radius_world / sf.x()));
uniform_scaling = true;
} else {
// In case that the transformation is non-uniform, all checks whether
@ -1890,6 +2118,8 @@ TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const
radius_sqr = Slic3r::sqr(radius_world);
trafo_normal = trafo.linear().inverse().transpose();
}
m_edge_limit = std::sqrt(radius_sqr) / 5.f;
}
TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
@ -2048,15 +2278,9 @@ bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec
return false;
}
bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
bool TriangleSelector::Capsule3D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
{
std::array<Vec3f, 3> pts;
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 std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
for (int side = 0; side < 3; ++side) {
const Vec3f &edge_a = pts[side];
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
@ -2068,15 +2292,8 @@ bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, cons
}
// Is edge inside cursor?
bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
{
std::array<Vec3f, 3> pts;
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 centers_diff = this->second_center - this->first_center;
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
@ -2095,6 +2312,7 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons
return false;
};
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
for (int side = 0; side < 3; ++side) {
const Vec3f &edge_a = pts[side];
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
@ -2122,4 +2340,54 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons
return false;
}
TriangleSelector::HeightRange::HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane)
: Cursor(Vec3f::Zero(), 0.f, trafo, clipping_plane) {
m_z_range_top = std::max(mesh_hit.z() + z_range / 2.f, float(mesh_bbox.min.z()));
m_z_range_bottom = std::min(mesh_hit.z() - z_range / 2.f, float(mesh_bbox.max.z()));
m_edge_limit = 0.1f;
}
bool TriangleSelector::HeightRange::is_mesh_point_inside(const Vec3f &point) const {
const float transformed_point_z = (this->uniform_scaling ? point : Vec3f(this->trafo * point)).z();
return Slic3r::is_in_range<float>(transformed_point_z, m_z_range_bottom, m_z_range_top);
}
bool TriangleSelector::HeightRange::is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const {
const std::array<Vec3f, 3> pts = this->transform_triangle(tr, vertices);
// If all vertices are below m_z_range_bottom or all vertices are above m_z_range_top, then it means that no edge
// is inside the height range. Otherwise, there is at least one edge inside the height range.
return !((pts[0].z() < m_z_range_bottom && pts[1].z() < m_z_range_bottom && pts[2].z() < m_z_range_bottom) ||
(pts[0].z() > m_z_range_top && pts[1].z() > m_z_range_top && pts[2].z() > m_z_range_top));
}
std::vector<int> TriangleSelector::HeightRange::get_facets_to_select(const int facet_idx, const std::vector<Vertex> &vertices, const std::vector<Triangle> &triangles, const int orig_size_vertices, const int orig_size_indices) const {
std::vector<int> facets_to_check;
// Assigns each vertex a value of -1, 1, or 0. The value -1 indicates a vertex is below m_z_range_bottom,
// while 1 indicates a vertex is above m_z_range_top. The value of 0 indicates that the vertex between
// m_z_range_bottom and m_z_range_top.
std::vector<int8_t> vertex_side(orig_size_vertices, 0);
if (trafo.matrix() == Transform3f::Identity().matrix()) {
for (int i = 0; i < orig_size_vertices; ++i) {
const float z = vertices[i].v.z();
vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0);
}
} else {
for (int i = 0; i < orig_size_vertices; ++i) {
const float z = (this->uniform_scaling ? vertices[i].v : Vec3f(this->trafo * vertices[i].v)).z();
vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0);
}
}
// Determine if each triangle crosses m_z_range_bottom or m_z_range_top.
for (int i = 0; i < orig_size_indices; ++i) {
const std::array<int, 3> &face = triangles[i].verts_idxs;
const std::array<int8_t, 3> sides = { vertex_side[face[0]], vertex_side[face[1]], vertex_side[face[2]] };
if ((sides[0] * sides[1] <= 0) || (sides[1] * sides[2] <= 0) || (sides[0] * sides[2] <= 0))
facets_to_check.emplace_back(i);
}
return facets_to_check;
}
} // namespace Slic3r

View File

@ -67,10 +67,21 @@ protected:
struct Vertex;
public:
enum CursorType {
enum class CursorType {
CIRCLE,
SPHERE,
POINTER
POINTER,
HEIGHT_RANGE
};
enum class ForceReselection {
NO,
YES
};
enum class BucketFillPropagate {
NO,
YES
};
struct ClippingPlane
@ -91,14 +102,21 @@ public:
Cursor() = delete;
virtual ~Cursor() = default;
float get_edge_limit() { return m_edge_limit; };
bool is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const;
std::array<Vec3f, 3> transform_triangle(const Triangle &tr, const std::vector<Vertex> &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<Vertex> &vertices) const;
virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
virtual bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
virtual bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const = 0;
virtual std::vector<int> get_facets_to_select(int facet_idx, const std::vector<Vertex> &vertices, const std::vector<Triangle> &triangles, int orig_size_vertices, int orig_size_indices) const {
return { facet_idx };
};
static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals);
protected:
@ -115,6 +133,8 @@ public:
ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only
float m_edge_limit;
friend TriangleSelector;
};
@ -174,7 +194,7 @@ public:
~Sphere() override = default;
bool is_mesh_point_inside(const Vec3f &point) const override;
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
};
@ -187,9 +207,8 @@ public:
~Circle() override = default;
bool is_mesh_point_inside(const Vec3f &point) const override;
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
{
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override {
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
}
};
@ -204,7 +223,7 @@ public:
~Capsule3D() override = default;
bool is_mesh_point_inside(const Vec3f &point) const override;
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
};
@ -218,13 +237,32 @@ public:
~Capsule2D() override = default;
bool is_mesh_point_inside(const Vec3f &point) const override;
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
{
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override {
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
}
};
class HeightRange : public Cursor
{
public:
HeightRange() = delete;
explicit HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane);
~HeightRange() override = default;
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override { return false; }
bool is_mesh_point_inside(const Vec3f &point) const override;
bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
std::vector<int> get_facets_to_select(int facet_idx, const std::vector<Vertex> &vertices, const std::vector<Triangle> &triangles, int orig_size_vertices, int orig_size_indices) const override;
private:
float m_z_range_top;
float m_z_range_bottom;
};
struct TriangleBitStreamMapping
{
// Index of the triangle to which we assign the bitstream containing splitting information.
@ -298,19 +336,22 @@ public:
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
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
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.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
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
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
float seed_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
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.
ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_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
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_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
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
float bucket_fill_angle, // the maximal angle between two facets to be painted by the same color
float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
BucketFillPropagate propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
bool has_facets(TriangleStateType state) const;
static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state);
@ -360,10 +401,20 @@ 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;
protected:
// Triangle and info about how it's split.
class Triangle {
@ -450,8 +501,9 @@ protected:
int m_orig_size_indices = 0;
std::unique_ptr<Cursor> m_cursor;
// Zero indicates an uninitialized state.
float m_old_cursor_radius_sqr = 0;
// 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:
@ -459,7 +511,7 @@ private:
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);
@ -483,6 +535,12 @@ private:
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const;
// Returns all triangles that are touching the given facet.
std::vector<int> get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const;
// Check if the triangle index is the original triangle from mesh, or it was additionally created by splitting.
bool is_original_triangle(int triangle_idx) const { return triangle_idx < m_orig_size_indices; }
#ifndef NDEBUG
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
bool verify_triangle_midpoints(const Triangle& tr) const;
@ -501,6 +559,17 @@ private:
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
bool is_any_neighbor_selected_by_seed_fill(const Triangle &triangle);
void seed_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, // Facet of the original mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected).
float seed_fill_gap_area); // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
void bucket_fill_fill_gaps(const std::vector<int> &gap_fill_candidate_facets, // Facet of the mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected).
float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected.
TriangleStateType start_facet_state, // The state of the starting facet that determines which neighbors to consider.
const std::vector<Vec3i> &neighbors,
const std::vector<Vec3i> &neighbors_propagate);
int m_free_triangles_head { -1 };
int m_free_vertices_head { -1 };
};

View File

@ -487,6 +487,11 @@ Fn for_each_in_tuple(Fn fn, Tup &&tup)
return fn;
}
template<typename T>
inline bool is_in_range(const T &value, const T &low, const T &high) {
return low <= value && value <= high;
}
} // namespace Slic3r
#endif // _libslic3r_h_

View File

@ -103,7 +103,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (! m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(25.f);
const float approx_height = m_imgui->scaled(26.3f);
y = std::min(y, bottom_limit - approx_height);
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
@ -115,7 +115,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
const float autoset_slider_label_max_width = m_imgui->scaled(7.5f);
const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), false, autoset_slider_label_max_width).x + m_imgui->scaled(1.f);
const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
@ -301,11 +301,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
for (auto &triangle_selector : m_triangle_selectors) {
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) {
for (auto &triangle_selector: m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
}
ImGui::Separator();
@ -459,7 +460,7 @@ void GLGizmoFdmSupports::update_model_object() const
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get());
updated |= mv->supported_facets.set(*m_triangle_selectors[idx]);
}
if (updated) {

View File

@ -110,6 +110,12 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["second_color"] = _u8L("Second color");
m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _u8L("Remove painted color");
m_desc["alt_caption"] = _u8L("Alt + Mouse wheel") + ": ";
m_desc["alt_brush"] = _u8L("Change brush size");
m_desc["alt_fill"] = _u8L("Change angle");
m_desc["alt_height_range"] = _u8L("Change height range");
m_desc["remove_all"] = _u8L("Clear all");
m_desc["circle"] = _u8L("Circle");
m_desc["sphere"] = _u8L("Sphere");
@ -119,10 +125,15 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["tool_brush"] = _u8L("Brush");
m_desc["tool_smart_fill"] = _u8L("Smart fill");
m_desc["tool_bucket_fill"] = _u8L("Bucket fill");
m_desc["tool_height_range"] = _u8L("Height range");
m_desc["smart_fill_angle"] = _u8L("Smart fill angle");
m_desc["bucket_fill_angle"] = _u8L("Bucket fill angle");
m_desc["split_triangles"] = _u8L("Split triangles");
m_desc["height_range_z_range"] = _u8L("Height range");
init_extruders_data();
return true;
@ -263,17 +274,19 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (!m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(22.0f);
const float approx_height = m_imgui->scaled(25.35f);
y = std::min(y, bottom_limit - approx_height);
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x,
ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x,
ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
const float bucket_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("bucket_fill_angle")).x + m_imgui->scaled(1.f);
const float height_range_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("height_range_z_range")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
@ -286,30 +299,39 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float combo_label_width = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("first_color")).x,
ImGuiPureWrap::calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_height_range = ImGuiPureWrap::calc_text_size(m_desc["tool_height_range"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_first_line = tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill;
const float tool_type_radio_second_line = tool_type_radio_height_range;
const float tool_type_radio_max_width = std::max(tool_type_radio_first_line, tool_type_radio_second_line);
const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f;
float caption_max = 0.f;
for (const std::string t : {"first_color", "second_color", "remove", "alt"}) {
caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x);
}
float total_text_max = 0.f;
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x);
for (const std::string t : {"first_color", "second_color", "remove", "alt_brush", "alt_fill", "alt_height_range"}) {
total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
const float sliders_left_width = std::max({smart_fill_slider_left, bucket_fill_slider_left, cursor_slider_left, clipping_slider_left, height_range_slider_left});
const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_max_width);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string& text) {
ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_ORANGE_LIGHT, caption);
@ -317,8 +339,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGuiPureWrap::text(text);
};
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"})
for (const std::string t : {"first_color", "second_color", "remove"}) {
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
}
std::string alt_hint_text = (m_tool_type == ToolType::BRUSH) ? "alt_brush" :
(m_tool_type == ToolType::HEIGHT_RANGE) ? "alt_height_range"
: "alt_fill";
draw_text_with_caption(m_desc.at("alt_caption"), m_desc.at(alt_hint_text));
ImGui::Separator();
@ -360,8 +388,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGuiPureWrap::text(m_desc.at("tool_type"));
ImGui::NewLine();
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f;
ImGui::SameLine(tool_type_offset);
const float tool_type_first_line_offset = (window_width - tool_type_radio_first_line + m_imgui->scaled(1.5f)) / 2.f;
ImGui::SameLine(tool_type_first_line_offset);
ImGui::PushItemWidth(tool_type_radio_brush);
if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) {
m_tool_type = ToolType::BRUSH;
@ -374,7 +402,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width);
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush);
ImGui::PushItemWidth(tool_type_radio_smart_fill);
if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) {
m_tool_type = ToolType::SMART_FILL;
@ -387,7 +415,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill);
ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush + tool_type_radio_smart_fill);
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
if (ImGuiPureWrap::radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) {
m_tool_type = ToolType::BUCKET_FILL;
@ -400,9 +428,25 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets that have the same color."), max_tooltip_width);
ImGui::NewLine();
const float tool_type_second_line_offset = (window_width - tool_type_radio_second_line + m_imgui->scaled(1.5f)) / 2.f;
ImGui::SameLine(tool_type_second_line_offset);
ImGui::PushItemWidth(tool_type_radio_height_range);
if (ImGuiPureWrap::radio_button(m_desc["tool_height_range"], m_tool_type == ToolType::HEIGHT_RANGE)) {
m_tool_type = ToolType::HEIGHT_RANGE;
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints facets within the chosen height range."), max_tooltip_width);
ImGui::Separator();
if(m_tool_type == ToolType::BRUSH) {
if (m_tool_type == ToolType::BRUSH) {
ImGuiPureWrap::text(m_desc.at("cursor_type"));
ImGui::NewLine();
@ -449,18 +493,35 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_imgui->disabled_end();
ImGui::Separator();
} else if(m_tool_type == ToolType::SMART_FILL) {
} else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) {
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":");
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
"placed after the number with no whitespace in between.");
ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_angle"] : m_desc["bucket_fill_angle"]) + ":");
std::string format_str_angle = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
for (auto &triangle_selector : m_triangle_selectors) {
float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle;
if (m_imgui->slider_float("##fill_angle", &fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str_angle.data(), 1.0f, true, _L("Alt + Mouse wheel"))) {
for (auto &triangle_selector: m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
ImGui::Separator();
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc["height_range_z_range"] + ":");
std::string format_str_angle = std::string("%.2f ") + I18N::translate_utf8("mm", "Millimeter sign to use in the respective slider in multi-material painting gizmo,"
"placed after the number with space in between.");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##height_range_z_range", &m_height_range_z_range, HeightRangeZRangeMin, HeightRangeZRangeMax, format_str_angle.data(), 1.0f, true, _L("Alt + Mouse wheel"))) {
for (auto &triangle_selector: m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
ImGui::Separator();
}
@ -509,7 +570,7 @@ void GLGizmoMmuSegmentation::update_model_object() const
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get());
updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx]);
}
if (updated) {
@ -699,7 +760,7 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
const GLint position_id = shader->get_attrib_location("v_position");
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)nullptr));
glsafe(::glEnableVertexAttribArray(position_id));
}

View File

@ -104,7 +104,7 @@ public:
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
static const constexpr size_t EXTRUDERS_LIMIT = 16;
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
float get_cursor_radius_min() const override { return CursorRadiusMin; }
protected:
ColorRGBA get_cursor_sphere_left_button_color() const override;

View File

@ -18,7 +18,9 @@
#include "libslic3r/TriangleMesh.hpp"
#include <memory>
#include <numeric>
#include <optional>
#include <libslic3r/TriangleMeshSlicer.hpp>
namespace Slic3r::GUI {
@ -139,10 +141,12 @@ void GLGizmoPainterBase::render_cursor()
return;
if (m_tool_type == ToolType::BRUSH) {
if (m_cursor_type == TriangleSelector::SPHERE)
if (m_cursor_type == TriangleSelector::CursorType::SPHERE)
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
else if (m_cursor_type == TriangleSelector::CIRCLE)
else if (m_cursor_type == TriangleSelector::CursorType::CIRCLE)
render_cursor_circle();
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
render_cursor_height_range(trafo_matrices[m_rr.mesh_id]);
}
}
@ -270,7 +274,6 @@ void GLGizmoPainterBase::render_cursor_circle()
glsafe(::glEnable(GL_DEPTH_TEST));
}
void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
{
if (s_sphere == nullptr) {
@ -314,6 +317,74 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
shader->stop_using();
}
void GLGizmoPainterBase::render_cursor_height_range(const Transform3d &trafo) const {
const ModelObject &model_object = *m_c->selection_info()->model_object();
const BoundingBoxf3 mesh_bbox = model_object.volumes[m_rr.mesh_id]->mesh().bounding_box();
const std::array<float, 2> z_range = {
std::min(m_rr.hit.z() - m_height_range_z_range / 2.f, float(mesh_bbox.max.z())),
std::max(m_rr.hit.z() + m_height_range_z_range / 2.f, float(mesh_bbox.min.z()))
};
std::vector<Polygons> slice_polygons_per_z;
for (const float z: z_range)
slice_polygons_per_z.emplace_back(slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams()));
const size_t max_vertices_cnt = std::accumulate(slice_polygons_per_z.begin(), slice_polygons_per_z.end(), 0,
[](const size_t sum, const Polygons &polygons) {
return sum + count_points(polygons);
});
GLModel::Geometry z_range_geometry;
z_range_geometry.format = {GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3};
z_range_geometry.reserve_vertices(max_vertices_cnt);
z_range_geometry.reserve_indices(max_vertices_cnt);
z_range_geometry.color = ColorRGBA::WHITE();
size_t vertices_cnt = 0;
for (const float z: z_range) {
const Polygons slice_polygons = slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams());
for (const Polygon &polygon: slice_polygons) {
for (const Point &pt: polygon.points)
z_range_geometry.add_vertex(Vec3f(unscaled<float>(pt.x()), unscaled<float>(pt.y()), z));
for (size_t pt_idx = 1; pt_idx < polygon.points.size(); ++pt_idx)
z_range_geometry.add_line(vertices_cnt + pt_idx - 1, vertices_cnt + pt_idx);
z_range_geometry.add_line(vertices_cnt + polygon.points.size() - 1, vertices_cnt);
vertices_cnt += polygon.points.size();
}
}
GLModel z_range_model;
if (!z_range_geometry.is_empty())
z_range_model.init_from(std::move(z_range_geometry));
const Camera &camera = wxGetApp().plater()->get_camera();
const Transform3d view_model_matrix = camera.get_view_matrix() * trafo;
GLShaderProgram *shader = wxGetApp().get_shader("mm_contour");
if (shader == nullptr)
return;
shader->start_using();
shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
shader->set_uniform("view_model_matrix", view_model_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const bool is_left_handed = Geometry::Transformation(view_model_matrix).is_left_handed();
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
z_range_model.render();
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
shader->stop_using();
}
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const
{
@ -469,22 +540,38 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
: std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
m_parent.set_as_dirty();
return true;
} else if (m_tool_type == ToolType::SMART_FILL) {
m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin)
: std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax);
} else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) {
float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle;
fill_angle = (action == SLAGizmoEventType::MouseWheelDown) ? std::max(fill_angle - SmartFillAngleStep, SmartFillAngleMin)
: std::min(fill_angle + SmartFillAngleStep, SmartFillAngleMax);
m_parent.set_as_dirty();
if (m_rr.mesh_id != -1) {
const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset();
const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
const Selection &selection = m_parent.get_selection();
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
const TriangleSelector::ClippingPlane &clipping_plane = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
if (m_tool_type == ToolType::SMART_FILL) {
const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset();
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clipping_plane, m_smart_fill_angle, SmartFillGapArea,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES);
} else {
assert(m_tool_type == ToolType::BUCKET_FILL);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clipping_plane, m_bucket_fill_angle, BucketFillGapArea,
TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES);
}
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
}
return true;
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
m_height_range_z_range = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_height_range_z_range - HeightRangeZRangeStep, HeightRangeZRangeMin)
: std::min(m_height_range_z_range + HeightRangeZRangeStep, HeightRangeZRangeMax);
m_parent.set_as_dirty();
return true;
}
return false;
@ -556,18 +643,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
assert(mesh_idx < int(m_triangle_selectors.size()));
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
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) {
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_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
else if (m_tool_type == ToolType::BUCKET_FILL)
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
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, SmartFillGapArea,
(m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES);
} else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) {
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES);
} else if (m_tool_type == ToolType::BUCKET_FILL) {
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES);
}
m_seed_fill_last_mesh_id = -1;
}
@ -588,6 +678,16 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), 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);
}
}
} else if (m_tool_type == ToolType::HEIGHT_RANGE) {
for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) {
const Vec3f &mesh_hit = projected_mouse_position.mesh_hit;
const int facet_idx = int(projected_mouse_position.facet_idx);
const BoundingBoxf3 mesh_bbox = mo->volumes[projected_mouse_position.mesh_idx]->mesh().bounding_box();
std::unique_ptr<TriangleSelector::Cursor> cursor = std::make_unique<TriangleSelector::HeightRange>(mesh_hit, mesh_bbox, m_height_range_z_range, trafo_matrix, clp);
m_triangle_selectors[mesh_idx]->select_patch(facet_idx, 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[mesh_idx]->request_update_render_data();
@ -646,12 +746,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
if (m_tool_type == ToolType::SMART_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO);
else if (m_tool_type == ToolType::BUCKET_FILL)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
return true;

View File

@ -97,9 +97,9 @@ public:
// after all volumes (including transparent ones) are rendered.
virtual void render_painter_gizmo() = 0;
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
virtual float get_cursor_radius_min() const { return CursorRadiusMin; }
virtual float get_cursor_radius_max() const { return CursorRadiusMax; }
virtual float get_cursor_radius_step() const { return CursorRadiusStep; }
/// <summary>
/// Implement when want to process mouse events in gizmo
@ -112,9 +112,12 @@ public:
protected:
virtual void render_triangles(const Selection& selection) const;
void render_cursor();
void render_cursor_circle();
void render_cursor_sphere(const Transform3d& trafo) const;
void render_cursor_sphere(const Transform3d &trafo) const;
void render_cursor_height_range(const Transform3d &trafo) const;
virtual void update_model_object() const = 0;
virtual void update_from_model_object() = 0;
@ -132,12 +135,13 @@ protected:
// For each model-part volume, store status and division of the triangles.
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
TriangleSelector::CursorType m_cursor_type = TriangleSelector::CursorType::SPHERE;
enum class ToolType {
BRUSH,
BUCKET_FILL,
SMART_FILL
SMART_FILL,
HEIGHT_RANGE
};
struct ProjectedMousePosition
@ -150,6 +154,8 @@ protected:
bool m_triangle_splitting_enabled = true;
ToolType m_tool_type = ToolType::BRUSH;
float m_smart_fill_angle = 30.f;
float m_bucket_fill_angle = 90.f;
float m_height_range_z_range = 1.00f;
bool m_paint_on_overhangs_only = false;
float m_highlight_by_angle_threshold_deg = 0.f;
@ -162,6 +168,13 @@ protected:
static constexpr float SmartFillAngleMax = 90.f;
static constexpr float SmartFillAngleStep = 1.f;
static constexpr float HeightRangeZRangeMin = 0.1f;
static constexpr float HeightRangeZRangeMax = 10.f;
static constexpr float HeightRangeZRangeStep = 0.1f;
static constexpr float SmartFillGapArea = 0.02f;
static constexpr float BucketFillGapArea = 0.02f;
// It stores the value of the previous mesh_id to which the seed fill was applied.
// It is used to detect when the mouse has moved from one volume to another one.
int m_seed_fill_last_mesh_id = -1;
@ -192,7 +205,6 @@ private:
static std::shared_ptr<GLModel> s_sphere;
bool m_internal_stack_active = false;
bool m_schedule_update = false;
Vec2d m_last_mouse_click = Vec2d::Zero();

View File

@ -79,7 +79,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
if (! m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(12.5f);
const float approx_height = m_imgui->scaled(13.45f);
y = std::min(y, bottom_limit - approx_height);
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
@ -201,7 +201,7 @@ void GLGizmoSeam::update_model_object() const
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get());
updated |= mv->seam_facets.set(*m_triangle_selectors[idx]);
}
if (updated) {