Cache raycast results so they don't have to be repeated on the same mouse pos

Fixed incorrect handling of clipping plane with multiple volumes - only the first volume was correctly clipped by the painter.
This commit is contained in:
Lukas Matena 2020-10-01 00:03:20 +02:00
parent fac7e735ac
commit 3ec5d9e2cf
2 changed files with 76 additions and 67 deletions

View File

@ -182,33 +182,41 @@ void GLGizmoPainterBase::render_cursor_circle() const
void GLGizmoPainterBase::render_cursor_sphere() const void GLGizmoPainterBase::render_cursor_sphere() const
{ {
int mesh_id = m_last_mesh_idx_and_hit.first; Vec2d mouse_position(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
if (mesh_id == -1)
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; return;
const Vec3f hit_pos = m_last_mesh_idx_and_hit.second; const Transform3d& complete_matrix = trafo_matrices[m_rr.mesh_id];
const Selection& selection = m_parent.get_selection(); const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(complete_matrix).get_matrix(true, true, false, true).inverse();
const ModelObject* mo = m_c->selection_info()->model_object(); const bool is_left_handed = Geometry::Transformation(complete_matrix).is_left_handed();
const ModelVolume* mv = mo->volumes[mesh_id];
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Transform3d instance_matrix = mi->get_transformation().get_matrix() * mv->get_matrix();
const Transform3d instance_scaling_matrix_inverse = Geometry::Transformation(instance_matrix).get_matrix(true, true, false, true).inverse();
const bool is_left_handed = Geometry::Transformation(instance_matrix).is_left_handed();
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(instance_matrix.data())); glsafe(::glMultMatrixd(complete_matrix.data()));
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
glsafe(::glTranslatef(hit_pos(0), hit_pos(1), hit_pos(2))); glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)));
glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius));
if (is_left_handed) if (is_left_handed)
glFrontFace(GL_CW); glFrontFace(GL_CW);
float render_color[4] = { 0.f, 0.f, 0.f, 0.15f }; float render_color[4] = { 0.f, 0.f, 0.f, 0.25f };
if (m_button_down == Button::Left) if (m_button_down == Button::Left)
render_color[2] = 1.f; render_color[2] = 1.f;
else // right else if (m_button_down == Button::Right)
render_color[0] = 1.f; render_color[0] = 1.f;
glsafe(::glColor4fv(render_color)); glsafe(::glColor4fv(render_color));
@ -221,16 +229,12 @@ void GLGizmoPainterBase::render_cursor_sphere() const
} }
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const 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);
@ -299,20 +303,20 @@ 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. // Precalculate transformations of individual meshes.
std::vector<Transform3d> trafo_matrices; std::vector<Transform3d> trafo_matrices;
@ -323,34 +327,28 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
// 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) {
update_raycast_cache(mp, camera, trafo_matrices);
bool clipped_mesh_was_hit = false;
Vec3f hit = Vec3f::Zero();
size_t facet = 0;
int mesh_id = -1;
get_mesh_hit(mp, camera, trafo_matrices, mesh_id, hit, facet, clipped_mesh_was_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 (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 (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;
} }
const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; const Transform3d& trafo_matrix = trafo_matrices[m_rr.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.
@ -360,13 +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 = (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(hit, facet, camera_pos, m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, m_rr.facet, camera_pos,
dir, limit, m_cursor_type, new_state); dir, limit, m_cursor_type, new_state);
m_last_mouse_position = mouse_position; m_last_mouse_click = mouse_position;
m_last_mesh_idx_and_hit = {mesh_id, hit};
} }
return true; return true;
@ -402,8 +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();
m_last_mesh_idx_and_hit = {-1, Vec3f::Zero()};
return true; return true;
} }
@ -411,21 +407,27 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
} }
void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position,
void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
const Camera& camera, const Camera& camera,
const std::vector<Transform3d>& trafo_matrices, const std::vector<Transform3d>& trafo_matrices) const
int& mesh_id, Vec3f& hit, size_t& facet,
bool& clipped_mesh_was_hit) 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 normal = Vec3f::Zero();
size_t current_facet = 0; Vec3f hit = Vec3f::Zero();
size_t facet = 0;
Vec3f closest_hit = Vec3f::Zero(); Vec3f closest_hit = Vec3f::Zero();
double closest_hit_squared_distance = std::numeric_limits<double>::max(); double closest_hit_squared_distance = std::numeric_limits<double>::max();
size_t closest_facet = 0; size_t closest_facet = 0;
int closest_hit_mesh_id = -1; int closest_hit_mesh_id = -1;
// Cast a ray on all meshes, pick the closest hit and save it for the respective mesh // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
for (mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) {
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
mouse_position, mouse_position,
@ -434,10 +436,10 @@ void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position,
hit, hit,
normal, normal,
m_clipping_plane.get(), m_clipping_plane.get(),
&current_facet)) &facet))
{ {
// In case this hit is clipped, skip it. // In case this hit is clipped, skip it.
if (is_mesh_point_clipped(hit.cast<double>())) { if (is_mesh_point_clipped(hit.cast<double>(), trafo_matrices[mesh_id])) {
clipped_mesh_was_hit = true; clipped_mesh_was_hit = true;
continue; continue;
} }
@ -446,16 +448,14 @@ void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position,
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm(); double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
if (hit_squared_distance < closest_hit_squared_distance) { if (hit_squared_distance < closest_hit_squared_distance) {
closest_hit_squared_distance = hit_squared_distance; closest_hit_squared_distance = hit_squared_distance;
closest_facet = current_facet; closest_facet = facet;
closest_hit_mesh_id = mesh_id; closest_hit_mesh_id = mesh_id;
closest_hit = hit; closest_hit = hit;
} }
} }
} }
mesh_id = closest_hit_mesh_id; m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet, clipped_mesh_was_hit};
facet = closest_facet;
hit = closest_hit;
} }
bool GLGizmoPainterBase::on_is_activable() const bool GLGizmoPainterBase::on_is_activable() const
@ -564,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();
} }

View File

@ -88,12 +88,10 @@ protected:
private: private:
bool is_mesh_point_clipped(const Vec3d& point) const; bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
void get_mesh_hit(const Vec2d& mouse_position, void update_raycast_cache(const Vec2d& mouse_position,
const Camera& camera, const Camera& camera,
const std::vector<Transform3d>& trafo_matrices, const std::vector<Transform3d>& trafo_matrices) const;
int& mesh_id, Vec3f& hit, size_t& facet,
bool& clipped_mesh_was_hit) 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;
@ -101,8 +99,7 @@ private:
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();
std::pair<int, Vec3f> m_last_mesh_idx_and_hit = {-1, Vec3f::Zero()};
enum class Button { enum class Button {
None, None,
@ -113,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 {}