Merge branch 'lm_sphere_cursor2'
This commit is contained in:
commit
26fb87242b
6 changed files with 255 additions and 102 deletions
|
@ -35,14 +35,15 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
|
||||||
|
|
||||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||||
const Vec3f& source, const Vec3f& dir,
|
const Vec3f& source, const Vec3f& dir,
|
||||||
float radius, EnforcerBlockerType new_state)
|
float radius, CursorType cursor_type,
|
||||||
|
EnforcerBlockerType new_state)
|
||||||
{
|
{
|
||||||
assert(facet_start < m_orig_size_indices);
|
assert(facet_start < m_orig_size_indices);
|
||||||
assert(is_approx(dir.norm(), 1.f));
|
assert(is_approx(dir.norm(), 1.f));
|
||||||
|
|
||||||
// Save current cursor center, squared radius and camera direction,
|
// Save current cursor center, squared radius and camera direction,
|
||||||
// so we don't have to pass it around.
|
// so we don't have to pass it around.
|
||||||
m_cursor = {hit, source, dir, radius*radius};
|
m_cursor = {hit, source, dir, radius*radius, cursor_type};
|
||||||
|
|
||||||
// In case user changed cursor size since last time, update triangle edge limit.
|
// In case user changed cursor size since last time, update triangle edge limit.
|
||||||
if (m_old_cursor_radius != radius) {
|
if (m_old_cursor_radius != radius) {
|
||||||
|
@ -61,7 +62,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||||
// add neighboring facets to list to be proccessed later
|
// add neighboring facets to list to be proccessed later
|
||||||
for (int n=0; n<3; ++n) {
|
for (int n=0; n<3; ++n) {
|
||||||
int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n];
|
int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n];
|
||||||
if (neighbor_idx >=0 && faces_camera(neighbor_idx))
|
if (neighbor_idx >=0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx)))
|
||||||
facets_to_check.push_back(neighbor_idx);
|
facets_to_check.push_back(neighbor_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,7 +208,11 @@ void TriangleSelector::split_triangle(int facet_idx)
|
||||||
bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const
|
bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const
|
||||||
{
|
{
|
||||||
Vec3f diff = m_cursor.center - point;
|
Vec3f diff = m_cursor.center - point;
|
||||||
|
|
||||||
|
if (m_cursor.type == CIRCLE)
|
||||||
return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr;
|
return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr;
|
||||||
|
else // SPHERE
|
||||||
|
return diff.squaredNorm() < m_cursor.radius_sqr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,11 @@ enum class EnforcerBlockerType : int8_t;
|
||||||
// to recursively subdivide the triangles and make the selection finer.
|
// to recursively subdivide the triangles and make the selection finer.
|
||||||
class TriangleSelector {
|
class TriangleSelector {
|
||||||
public:
|
public:
|
||||||
|
enum CursorType {
|
||||||
|
CIRCLE,
|
||||||
|
SPHERE
|
||||||
|
};
|
||||||
|
|
||||||
void set_edge_limit(float edge_limit);
|
void set_edge_limit(float edge_limit);
|
||||||
|
|
||||||
// Create new object on a TriangleMesh. The referenced mesh must
|
// Create new object on a TriangleMesh. The referenced mesh must
|
||||||
|
@ -29,6 +34,7 @@ public:
|
||||||
const Vec3f& source, // camera position (mesh coords)
|
const Vec3f& source, // camera position (mesh coords)
|
||||||
const Vec3f& dir, // direction of the ray (mesh coords)
|
const Vec3f& dir, // direction of the ray (mesh coords)
|
||||||
float radius, // radius of the cursor
|
float radius, // radius of the cursor
|
||||||
|
CursorType type, // current type of cursor
|
||||||
EnforcerBlockerType new_state); // enforcer or blocker?
|
EnforcerBlockerType new_state); // enforcer or blocker?
|
||||||
|
|
||||||
// Get facets currently in the given state.
|
// Get facets currently in the given state.
|
||||||
|
@ -127,6 +133,7 @@ protected:
|
||||||
Vec3f source;
|
Vec3f source;
|
||||||
Vec3f dir;
|
Vec3f dir;
|
||||||
float radius_sqr;
|
float radius_sqr;
|
||||||
|
CursorType type;
|
||||||
};
|
};
|
||||||
|
|
||||||
Cursor m_cursor;
|
Cursor m_cursor;
|
||||||
|
|
|
@ -42,6 +42,7 @@ bool GLGizmoFdmSupports::on_init()
|
||||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||||
m_desc["reset_direction"] = _L("Reset direction");
|
m_desc["reset_direction"] = _L("Reset direction");
|
||||||
m_desc["cursor_size"] = _L("Cursor size") + ": ";
|
m_desc["cursor_size"] = _L("Cursor size") + ": ";
|
||||||
|
m_desc["cursor_type"] = _L("Cursor type") + ": ";
|
||||||
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
|
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
|
||||||
m_desc["enforce"] = _L("Enforce supports");
|
m_desc["enforce"] = _L("Enforce supports");
|
||||||
m_desc["block_caption"] = _L("Right mouse button") + " ";
|
m_desc["block_caption"] = _L("Right mouse button") + " ";
|
||||||
|
@ -66,7 +67,7 @@ void GLGizmoFdmSupports::on_render() const
|
||||||
render_triangles(selection);
|
render_triangles(selection);
|
||||||
|
|
||||||
m_c->object_clipper()->render_cut();
|
m_c->object_clipper()->render_cut();
|
||||||
render_cursor_circle();
|
render_cursor();
|
||||||
|
|
||||||
glsafe(::glDisable(GL_BLEND));
|
glsafe(::glDisable(GL_BLEND));
|
||||||
}
|
}
|
||||||
|
@ -78,16 +79,26 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||||
if (! m_c->selection_info()->model_object())
|
if (! m_c->selection_info()->model_object())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const float approx_height = m_imgui->scaled(18.0f);
|
const float approx_height = m_imgui->scaled(14.0f);
|
||||||
y = std::min(y, bottom_limit - approx_height);
|
y = std::min(y, bottom_limit - approx_height);
|
||||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||||
|
|
||||||
if (! m_setting_angle) {
|
if (! m_setting_angle) {
|
||||||
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
||||||
|
|
||||||
|
std::vector<std::string> cursor_types;
|
||||||
|
cursor_types.push_back(_L("Circle").ToUTF8().data());
|
||||||
|
cursor_types.push_back(_L("Sphere").ToUTF8().data());
|
||||||
|
|
||||||
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
|
// 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(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
|
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
|
||||||
|
m_imgui->calc_text_size(m_desc.at("reset_direction")).x)
|
||||||
|
+ m_imgui->scaled(1.5f);
|
||||||
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||||
|
const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
|
||||||
|
const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0])).x,
|
||||||
|
m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1])).x)
|
||||||
|
+ m_imgui->scaled(2.5f);
|
||||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||||
|
|
||||||
|
@ -103,6 +114,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||||
float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
|
float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
|
||||||
window_width = std::max(window_width, total_text_max);
|
window_width = std::max(window_width, total_text_max);
|
||||||
window_width = std::max(window_width, button_width);
|
window_width = std::max(window_width, button_width);
|
||||||
|
window_width = std::max(window_width, cursor_type_combo_left + cursor_type_combo_width);
|
||||||
|
|
||||||
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
||||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
|
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
|
||||||
|
@ -139,8 +151,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||||
|
|
||||||
m_imgui->text(m_desc.at("cursor_size"));
|
m_imgui->text(m_desc.at("cursor_size"));
|
||||||
ImGui::SameLine(clipping_slider_left);
|
ImGui::SameLine(cursor_slider_left);
|
||||||
ImGui::PushItemWidth(window_width - clipping_slider_left);
|
ImGui::PushItemWidth(window_width - cursor_slider_left);
|
||||||
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
|
@ -150,6 +162,23 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||||
ImGui::EndTooltip();
|
ImGui::EndTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m_imgui->text(m_desc.at("cursor_type"));
|
||||||
|
ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f));
|
||||||
|
ImGui::PushItemWidth(cursor_type_combo_width);
|
||||||
|
int selection = int(m_cursor_type);
|
||||||
|
m_imgui->combo("", cursor_types, selection);
|
||||||
|
m_cursor_type = TriangleSelector::CursorType(selection);
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||||
|
ImGui::TextUnformatted(_L("Sphere paints all facets inside, regardless of their orientation.\n\n"
|
||||||
|
"Circle ignores facets facing away from the camera.").ToUTF8().data());
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (m_c->object_clipper()->get_position() == 0.f)
|
if (m_c->object_clipper()->get_position() == 0.f)
|
||||||
m_imgui->text(m_desc.at("clipping_of_view"));
|
m_imgui->text(m_desc.at("clipping_of_view"));
|
||||||
|
|
|
@ -21,6 +21,14 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic
|
||||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||||
{
|
{
|
||||||
m_clipping_plane.reset(new ClippingPlane());
|
m_clipping_plane.reset(new ClippingPlane());
|
||||||
|
// Make sphere and save it into a vertex buffer.
|
||||||
|
const TriangleMesh sphere_mesh = make_sphere(1., (2*M_PI)/24.);
|
||||||
|
for (size_t i=0; i<sphere_mesh.its.vertices.size(); ++i)
|
||||||
|
m_vbo_sphere.push_geometry(sphere_mesh.its.vertices[i].cast<double>(),
|
||||||
|
sphere_mesh.stl.facet_start[i].normal.cast<double>());
|
||||||
|
for (const stl_triangle_vertex_indices& indices : sphere_mesh.its.indices)
|
||||||
|
m_vbo_sphere.push_triangle(indices(0), indices(1), indices(2));
|
||||||
|
m_vbo_sphere.finalize_geometry(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,6 +125,16 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GLGizmoPainterBase::render_cursor() const
|
||||||
|
{
|
||||||
|
if (m_cursor_type == TriangleSelector::SPHERE)
|
||||||
|
render_cursor_sphere();
|
||||||
|
else
|
||||||
|
render_cursor_circle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void GLGizmoPainterBase::render_cursor_circle() const
|
void GLGizmoPainterBase::render_cursor_circle() const
|
||||||
{
|
{
|
||||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
@ -162,17 +180,61 @@ void GLGizmoPainterBase::render_cursor_circle() const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GLGizmoPainterBase::render_cursor_sphere() const
|
||||||
|
{
|
||||||
|
Vec2d mouse_position(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
|
||||||
|
|
||||||
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const
|
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||||
|
const Selection& selection = m_parent.get_selection();
|
||||||
|
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
|
||||||
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
|
|
||||||
|
// Precalculate transformations of individual meshes.
|
||||||
|
std::vector<Transform3d> trafo_matrices;
|
||||||
|
for (const ModelVolume* mv : mo->volumes) {
|
||||||
|
if (mv->is_model_part())
|
||||||
|
trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
|
||||||
|
}
|
||||||
|
update_raycast_cache(mouse_position, camera, trafo_matrices);
|
||||||
|
if (m_rr.mesh_id == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Transform3d& complete_matrix = trafo_matrices[m_rr.mesh_id];
|
||||||
|
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(complete_matrix).get_matrix(true, true, false, true).inverse();
|
||||||
|
const bool is_left_handed = Geometry::Transformation(complete_matrix).is_left_handed();
|
||||||
|
|
||||||
|
glsafe(::glPushMatrix());
|
||||||
|
glsafe(::glMultMatrixd(complete_matrix.data()));
|
||||||
|
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
|
||||||
|
glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)));
|
||||||
|
glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
|
||||||
|
glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius));
|
||||||
|
|
||||||
|
if (is_left_handed)
|
||||||
|
glFrontFace(GL_CW);
|
||||||
|
|
||||||
|
float render_color[4] = { 0.f, 0.f, 0.f, 0.25f };
|
||||||
|
if (m_button_down == Button::Left)
|
||||||
|
render_color[2] = 1.f;
|
||||||
|
else if (m_button_down == Button::Right)
|
||||||
|
render_color[0] = 1.f;
|
||||||
|
glsafe(::glColor4fv(render_color));
|
||||||
|
|
||||||
|
m_vbo_sphere.render();
|
||||||
|
|
||||||
|
if (is_left_handed)
|
||||||
|
glFrontFace(GL_CCW);
|
||||||
|
|
||||||
|
glsafe(::glPopMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const
|
||||||
{
|
{
|
||||||
if (m_c->object_clipper()->get_position() == 0.)
|
if (m_c->object_clipper()->get_position() == 0.)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto sel_info = m_c->selection_info();
|
auto sel_info = m_c->selection_info();
|
||||||
int active_inst = m_c->selection_info()->get_active_instance();
|
|
||||||
const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
|
|
||||||
const Transform3d& trafo = mi->get_transformation().get_matrix();
|
|
||||||
|
|
||||||
Vec3d transformed_point = trafo * point;
|
Vec3d transformed_point = trafo * point;
|
||||||
transformed_point(2) += sel_info->get_sla_shift();
|
transformed_point(2) += sel_info->get_sla_shift();
|
||||||
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
|
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
|
||||||
|
@ -241,104 +303,52 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
// add several positions from between into the list, so there
|
// add several positions from between into the list, so there
|
||||||
// are no gaps in the painted region.
|
// are no gaps in the painted region.
|
||||||
{
|
{
|
||||||
if (m_last_mouse_position == Vec2d::Zero())
|
if (m_last_mouse_click == Vec2d::Zero())
|
||||||
m_last_mouse_position = mouse_position;
|
m_last_mouse_click = mouse_position;
|
||||||
// resolution describes minimal distance limit using circle radius
|
// resolution describes minimal distance limit using circle radius
|
||||||
// as a unit (e.g., 2 would mean the patches will be touching).
|
// as a unit (e.g., 2 would mean the patches will be touching).
|
||||||
double resolution = 0.7;
|
double resolution = 0.7;
|
||||||
double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
|
double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
|
||||||
int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px);
|
int patches_in_between = int(((mouse_position - m_last_mouse_click).norm() - diameter_px) / diameter_px);
|
||||||
if (patches_in_between > 0) {
|
if (patches_in_between > 0) {
|
||||||
Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1);
|
Vec2d diff = (mouse_position - m_last_mouse_click)/(patches_in_between+1);
|
||||||
for (int i=1; i<=patches_in_between; ++i)
|
for (int i=1; i<=patches_in_between; ++i)
|
||||||
mouse_positions.emplace_back(m_last_mouse_position + i*diff);
|
mouse_positions.emplace_back(m_last_mouse_click + i*diff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved
|
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||||
|
|
||||||
|
// Precalculate transformations of individual meshes.
|
||||||
|
std::vector<Transform3d> trafo_matrices;
|
||||||
|
for (const ModelVolume* mv : mo->volumes) {
|
||||||
|
if (mv->is_model_part())
|
||||||
|
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
|
||||||
|
}
|
||||||
|
|
||||||
// Now "click" into all the prepared points and spill paint around them.
|
// Now "click" into all the prepared points and spill paint around them.
|
||||||
for (const Vec2d& mp : mouse_positions) {
|
for (const Vec2d& mp : mouse_positions) {
|
||||||
std::vector<std::vector<std::pair<Vec3f, size_t>>> hit_positions_and_facet_ids;
|
update_raycast_cache(mp, camera, trafo_matrices);
|
||||||
bool clipped_mesh_was_hit = false;
|
|
||||||
|
|
||||||
Vec3f normal = Vec3f::Zero();
|
|
||||||
Vec3f hit = Vec3f::Zero();
|
|
||||||
size_t facet = 0;
|
|
||||||
Vec3f closest_hit = Vec3f::Zero();
|
|
||||||
double closest_hit_squared_distance = std::numeric_limits<double>::max();
|
|
||||||
size_t closest_facet = 0;
|
|
||||||
int closest_hit_mesh_id = -1;
|
|
||||||
|
|
||||||
// Transformations of individual meshes
|
|
||||||
std::vector<Transform3d> trafo_matrices;
|
|
||||||
|
|
||||||
int mesh_id = -1;
|
|
||||||
// Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
|
|
||||||
for (const ModelVolume* mv : mo->volumes) {
|
|
||||||
if (! mv->is_model_part())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
++mesh_id;
|
|
||||||
|
|
||||||
trafo_matrices.push_back(instance_trafo * mv->get_matrix());
|
|
||||||
hit_positions_and_facet_ids.push_back(std::vector<std::pair<Vec3f, size_t>>());
|
|
||||||
|
|
||||||
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
|
|
||||||
mp,
|
|
||||||
trafo_matrices[mesh_id],
|
|
||||||
camera,
|
|
||||||
hit,
|
|
||||||
normal,
|
|
||||||
m_clipping_plane.get(),
|
|
||||||
&facet))
|
|
||||||
{
|
|
||||||
// In case this hit is clipped, skip it.
|
|
||||||
if (is_mesh_point_clipped(hit.cast<double>())) {
|
|
||||||
clipped_mesh_was_hit = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this hit the closest to the camera so far?
|
|
||||||
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
|
|
||||||
if (hit_squared_distance < closest_hit_squared_distance) {
|
|
||||||
closest_hit_squared_distance = hit_squared_distance;
|
|
||||||
closest_facet = facet;
|
|
||||||
closest_hit_mesh_id = mesh_id;
|
|
||||||
closest_hit = hit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||||
|
|
||||||
// The mouse button click detection is enabled when there is a valid hit
|
// The mouse button click detection is enabled when there is a valid hit
|
||||||
// or when the user clicks the clipping plane. Missing the object entirely
|
// or when the user clicks the clipping plane. Missing the object entirely
|
||||||
// shall not capture the mouse.
|
// shall not capture the mouse.
|
||||||
if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) {
|
if (m_rr.mesh_id != -1 || m_rr.clipped_mesh_was_hit) {
|
||||||
if (m_button_down == Button::None)
|
if (m_button_down == Button::None)
|
||||||
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
|
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closest_hit_mesh_id == -1) {
|
if (m_rr.mesh_id == -1) {
|
||||||
// In case we have no valid hit, we can return. The event will
|
// In case we have no valid hit, we can return. The event will
|
||||||
// be stopped in following two cases:
|
// be stopped in following two cases:
|
||||||
// 1. clicking the clipping plane
|
// 1. clicking the clipping plane
|
||||||
// 2. dragging while painting (to prevent scene rotations and moving the object)
|
// 2. dragging while painting (to prevent scene rotations and moving the object)
|
||||||
return clipped_mesh_was_hit
|
return m_rr.clipped_mesh_was_hit
|
||||||
|| dragging_while_painting;
|
|| dragging_while_painting;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find respective mesh id.
|
const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id];
|
||||||
mesh_id = -1;
|
|
||||||
for (const ModelVolume* mv : mo->volumes) {
|
|
||||||
if (! mv->is_model_part())
|
|
||||||
continue;
|
|
||||||
++mesh_id;
|
|
||||||
if (mesh_id == closest_hit_mesh_id)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
|
|
||||||
|
|
||||||
// Calculate how far can a point be from the line (in mesh coords).
|
// Calculate how far can a point be from the line (in mesh coords).
|
||||||
// FIXME: The scaling of the mesh can be non-uniform.
|
// FIXME: The scaling of the mesh can be non-uniform.
|
||||||
|
@ -348,12 +358,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
|
|
||||||
// Calculate direction from camera to the hit (in mesh coords):
|
// Calculate direction from camera to the hit (in mesh coords):
|
||||||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||||
Vec3f dir = (closest_hit - camera_pos).normalized();
|
Vec3f dir = (m_rr.hit - camera_pos).normalized();
|
||||||
|
|
||||||
assert(mesh_id < int(m_triangle_selectors.size()));
|
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||||
m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos,
|
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, m_rr.facet, camera_pos,
|
||||||
dir, limit, new_state);
|
dir, limit, m_cursor_type, new_state);
|
||||||
m_last_mouse_position = mouse_position;
|
m_last_mouse_click = mouse_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -389,7 +399,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
update_model_object();
|
update_model_object();
|
||||||
|
|
||||||
m_button_down = Button::None;
|
m_button_down = Button::None;
|
||||||
m_last_mouse_position = Vec2d::Zero();
|
m_last_mouse_click = Vec2d::Zero();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,6 +408,56 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
|
||||||
|
const Camera& camera,
|
||||||
|
const std::vector<Transform3d>& trafo_matrices) const
|
||||||
|
{
|
||||||
|
if (m_rr.mouse_position == mouse_position) {
|
||||||
|
// Same query as last time - the answer is already in the cache.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clipped_mesh_was_hit{false};
|
||||||
|
Vec3f normal = Vec3f::Zero();
|
||||||
|
Vec3f hit = Vec3f::Zero();
|
||||||
|
size_t facet = 0;
|
||||||
|
Vec3f closest_hit = Vec3f::Zero();
|
||||||
|
double closest_hit_squared_distance = std::numeric_limits<double>::max();
|
||||||
|
size_t closest_facet = 0;
|
||||||
|
int closest_hit_mesh_id = -1;
|
||||||
|
|
||||||
|
// Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
|
||||||
|
for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) {
|
||||||
|
|
||||||
|
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
|
||||||
|
mouse_position,
|
||||||
|
trafo_matrices[mesh_id],
|
||||||
|
camera,
|
||||||
|
hit,
|
||||||
|
normal,
|
||||||
|
m_clipping_plane.get(),
|
||||||
|
&facet))
|
||||||
|
{
|
||||||
|
// In case this hit is clipped, skip it.
|
||||||
|
if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id])) {
|
||||||
|
clipped_mesh_was_hit = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this hit the closest to the camera so far?
|
||||||
|
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
|
||||||
|
if (hit_squared_distance < closest_hit_squared_distance) {
|
||||||
|
closest_hit_squared_distance = hit_squared_distance;
|
||||||
|
closest_facet = facet;
|
||||||
|
closest_hit_mesh_id = mesh_id;
|
||||||
|
closest_hit = hit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet, clipped_mesh_was_hit};
|
||||||
|
}
|
||||||
|
|
||||||
bool GLGizmoPainterBase::on_is_activable() const
|
bool GLGizmoPainterBase::on_is_activable() const
|
||||||
{
|
{
|
||||||
const Selection& selection = m_parent.get_selection();
|
const Selection& selection = m_parent.get_selection();
|
||||||
|
@ -504,13 +564,13 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
||||||
m_iva_blockers.finalize_geometry(true);
|
m_iva_blockers.finalize_geometry(true);
|
||||||
|
|
||||||
if (m_iva_enforcers.has_VBOs()) {
|
if (m_iva_enforcers.has_VBOs()) {
|
||||||
::glColor4f(0.f, 0.f, 1.f, 0.3f);
|
::glColor4f(0.f, 0.f, 1.f, 0.4f);
|
||||||
m_iva_enforcers.render();
|
m_iva_enforcers.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (m_iva_blockers.has_VBOs()) {
|
if (m_iva_blockers.has_VBOs()) {
|
||||||
::glColor4f(1.f, 0.f, 0.f, 0.3f);
|
::glColor4f(1.f, 0.f, 0.f, 0.4f);
|
||||||
m_iva_blockers.render();
|
m_iva_blockers.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace GUI {
|
||||||
|
|
||||||
enum class SLAGizmoEventType : unsigned char;
|
enum class SLAGizmoEventType : unsigned char;
|
||||||
class ClippingPlane;
|
class ClippingPlane;
|
||||||
|
class Camera;
|
||||||
|
|
||||||
enum class PainterGizmoType {
|
enum class PainterGizmoType {
|
||||||
FDM_SUPPORTS,
|
FDM_SUPPORTS,
|
||||||
|
@ -67,10 +68,13 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void render_triangles(const Selection& selection) const;
|
void render_triangles(const Selection& selection) const;
|
||||||
|
void render_cursor() const;
|
||||||
void render_cursor_circle() const;
|
void render_cursor_circle() const;
|
||||||
|
void render_cursor_sphere() const;
|
||||||
virtual void update_model_object() const = 0;
|
virtual void update_model_object() const = 0;
|
||||||
virtual void update_from_model_object() = 0;
|
virtual void update_from_model_object() = 0;
|
||||||
void activate_internal_undo_redo_stack(bool activate);
|
void activate_internal_undo_redo_stack(bool activate);
|
||||||
|
void set_cursor_type(TriangleSelector::CursorType);
|
||||||
|
|
||||||
float m_cursor_radius = 2.f;
|
float m_cursor_radius = 2.f;
|
||||||
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
|
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
|
||||||
|
@ -80,16 +84,22 @@ protected:
|
||||||
// For each model-part volume, store status and division of the triangles.
|
// For each model-part volume, store status and division of the triangles.
|
||||||
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
|
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
|
||||||
|
|
||||||
|
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||||
|
void update_raycast_cache(const Vec2d& mouse_position,
|
||||||
|
const Camera& camera,
|
||||||
|
const std::vector<Transform3d>& trafo_matrices) const;
|
||||||
|
|
||||||
float m_clipping_plane_distance = 0.f;
|
float m_clipping_plane_distance = 0.f;
|
||||||
std::unique_ptr<ClippingPlane> m_clipping_plane;
|
std::unique_ptr<ClippingPlane> m_clipping_plane;
|
||||||
|
GLIndexedVertexArray m_vbo_sphere;
|
||||||
|
|
||||||
bool m_internal_stack_active = false;
|
bool m_internal_stack_active = false;
|
||||||
bool m_schedule_update = false;
|
bool m_schedule_update = false;
|
||||||
Vec2d m_last_mouse_position = Vec2d::Zero();
|
Vec2d m_last_mouse_click = Vec2d::Zero();
|
||||||
|
|
||||||
enum class Button {
|
enum class Button {
|
||||||
None,
|
None,
|
||||||
|
@ -100,6 +110,18 @@ private:
|
||||||
Button m_button_down = Button::None;
|
Button m_button_down = Button::None;
|
||||||
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||||
|
|
||||||
|
// Following cache holds result of a raycast query. The queries are asked
|
||||||
|
// during rendering the sphere cursor and painting, this saves repeated
|
||||||
|
// raycasts when the mouse position is the same as before.
|
||||||
|
struct RaycastResult {
|
||||||
|
Vec2d mouse_position;
|
||||||
|
int mesh_id;
|
||||||
|
Vec3f hit;
|
||||||
|
size_t facet;
|
||||||
|
bool clipped_mesh_was_hit;
|
||||||
|
};
|
||||||
|
mutable RaycastResult m_rr;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void on_set_state() override;
|
void on_set_state() override;
|
||||||
void on_start_dragging() override {}
|
void on_start_dragging() override {}
|
||||||
|
|
|
@ -25,6 +25,7 @@ bool GLGizmoSeam::on_init()
|
||||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||||
m_desc["reset_direction"] = _L("Reset direction");
|
m_desc["reset_direction"] = _L("Reset direction");
|
||||||
m_desc["cursor_size"] = _L("Cursor size") + ": ";
|
m_desc["cursor_size"] = _L("Cursor size") + ": ";
|
||||||
|
m_desc["cursor_type"] = _L("Cursor type") + ": ";
|
||||||
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
|
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
|
||||||
m_desc["enforce"] = _L("Enforce seam");
|
m_desc["enforce"] = _L("Enforce seam");
|
||||||
m_desc["block_caption"] = _L("Right mouse button") + " ";
|
m_desc["block_caption"] = _L("Right mouse button") + " ";
|
||||||
|
@ -55,7 +56,7 @@ void GLGizmoSeam::on_render() const
|
||||||
render_triangles(selection);
|
render_triangles(selection);
|
||||||
|
|
||||||
m_c->object_clipper()->render_cut();
|
m_c->object_clipper()->render_cut();
|
||||||
render_cursor_circle();
|
render_cursor();
|
||||||
|
|
||||||
glsafe(::glDisable(GL_BLEND));
|
glsafe(::glDisable(GL_BLEND));
|
||||||
}
|
}
|
||||||
|
@ -67,16 +68,26 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||||
if (! m_c->selection_info()->model_object())
|
if (! m_c->selection_info()->model_object())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const float approx_height = m_imgui->scaled(18.0f);
|
const float approx_height = m_imgui->scaled(14.0f);
|
||||||
y = std::min(y, bottom_limit - approx_height);
|
y = std::min(y, bottom_limit - approx_height);
|
||||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||||
|
|
||||||
|
std::vector<std::string> cursor_types;
|
||||||
|
cursor_types.push_back(_L("Circle").ToUTF8().data());
|
||||||
|
cursor_types.push_back(_L("Sphere").ToUTF8().data());
|
||||||
|
|
||||||
|
|
||||||
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
m_imgui->begin(on_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:
|
// 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(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
|
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
|
||||||
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
m_imgui->calc_text_size(m_desc.at("reset_direction")).x)
|
||||||
|
+ m_imgui->scaled(1.5f);
|
||||||
|
const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||||
|
const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
|
||||||
|
const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0])).x,
|
||||||
|
m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1])).x)
|
||||||
|
+ m_imgui->scaled(2.5f);
|
||||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||||
|
|
||||||
|
@ -89,9 +100,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||||
caption_max += m_imgui->scaled(1.f);
|
caption_max += m_imgui->scaled(1.f);
|
||||||
total_text_max += m_imgui->scaled(1.f);
|
total_text_max += m_imgui->scaled(1.f);
|
||||||
|
|
||||||
float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
|
float window_width = minimal_slider_width + std::max(cursor_size_slider_left, clipping_slider_left);
|
||||||
window_width = std::max(window_width, total_text_max);
|
window_width = std::max(window_width, total_text_max);
|
||||||
window_width = std::max(window_width, button_width);
|
window_width = std::max(window_width, button_width);
|
||||||
|
window_width = std::max(window_width, cursor_type_combo_left + cursor_type_combo_width);
|
||||||
|
|
||||||
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
||||||
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
|
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
|
||||||
|
@ -123,8 +135,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||||
|
|
||||||
m_imgui->text(m_desc.at("cursor_size"));
|
m_imgui->text(m_desc.at("cursor_size"));
|
||||||
ImGui::SameLine(clipping_slider_left);
|
ImGui::SameLine(cursor_size_slider_left);
|
||||||
ImGui::PushItemWidth(window_width - clipping_slider_left);
|
ImGui::PushItemWidth(window_width - cursor_size_slider_left);
|
||||||
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
|
@ -134,6 +146,24 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||||
ImGui::EndTooltip();
|
ImGui::EndTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
m_imgui->text(m_desc.at("cursor_type"));
|
||||||
|
ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f));
|
||||||
|
ImGui::PushItemWidth(cursor_type_combo_width);
|
||||||
|
int selection = int(m_cursor_type);
|
||||||
|
m_imgui->combo("", cursor_types, selection);
|
||||||
|
m_cursor_type = TriangleSelector::CursorType(selection);
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||||
|
ImGui::TextUnformatted(_L("Sphere paints all facets inside, regardless of their orientation.\n\n"
|
||||||
|
"Circle ignores facets facing away from the camera.").ToUTF8().data());
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (m_c->object_clipper()->get_position() == 0.f)
|
if (m_c->object_clipper()->get_position() == 0.f)
|
||||||
m_imgui->text(m_desc.at("clipping_of_view"));
|
m_imgui->text(m_desc.at("clipping_of_view"));
|
||||||
|
|
Loading…
Add table
Reference in a new issue