SPE-2506 - Fixed camera jumping when panning after rotating scene containing multiple beds

This commit is contained in:
enricoturri1966 2024-11-08 12:29:18 +01:00 committed by Lukas Matena
parent 311434382d
commit bbdd2e9d07
4 changed files with 229 additions and 27 deletions

View File

@ -24,6 +24,8 @@ double Camera::FrustrumMinZRange = 50.0;
double Camera::FrustrumMinNearZ = 100.0;
double Camera::FrustrumZMargin = 10.0;
double Camera::MaxFovDeg = 60.0;
double Camera::SingleBedSceneBoxScaleFactor = 1.5;
double Camera::MultipleBedSceneBoxScaleFactor = 4.0;
std::string Camera::get_type_as_string() const
{
@ -287,7 +289,7 @@ void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor)
void Camera::debug_render() const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
ImGui::Begin(std::string("Camera statistics").c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
std::string type = get_type_as_string();
if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get_bool("use_free_camera")))
@ -297,6 +299,7 @@ void Camera::debug_render() const
Vec3f position = get_position().cast<float>();
Vec3f target = m_target.cast<float>();
Vec3f pivot = m_rotation_pivot.cast<float>();
float distance = (float)get_distance();
float zenit = (float)m_zenit;
Vec3f forward = get_dir_forward().cast<float>();
@ -314,6 +317,7 @@ void Camera::debug_render() const
ImGui::Separator();
ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Pivot", pivot.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Zenit", &zenit, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
@ -332,7 +336,7 @@ void Camera::debug_render() const
ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
imgui.end();
ImGui::End();
}
#endif // ENABLE_CAMERA_STATISTICS
@ -355,6 +359,26 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b
m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_rotation_pivot) + translation, m_view_rotation, Vec3d(1., 1., 1.));
// adjust target from position
const Vec3d pivot = m_rotation_pivot;
if (!m_target.isApprox(pivot)) {
const Vec3d position = get_position();
const Vec3d forward = get_dir_forward();
const Vec3d new_target = position + m_distance * forward;
if (!is_target_valid()) {
const float forward_dot_unitZ = forward.dot(Vec3d::UnitZ());
if (std::abs(forward_dot_unitZ) > EPSILON) {
const float dist_to_xy = -position.dot(Vec3d::UnitZ()) / forward_dot_unitZ;
set_target(position + dist_to_xy * forward);
}
else
set_target(new_target);
}
else
set_target(new_target);
set_rotation_pivot(pivot);
}
}
// Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians.
@ -362,13 +386,13 @@ void Camera::rotate_local_around_target(const Vec3d& rotation_rad)
{
const double angle = rotation_rad.norm();
if (std::abs(angle) > EPSILON) {
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_rotation_pivot;
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target;
const Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized();
m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis));
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_rotation_pivot) + translation, m_view_rotation, Vec3d(1., 1., 1.));
update_zenit();
}
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
update_zenit();
}
}
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box)
@ -565,6 +589,21 @@ void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up
update_zenit();
}
bool Camera::is_target_valid() const
{
if (m_target.isApprox(m_rotation_pivot))
return true;
const Vec3d box_size = m_scene_box.size();
BoundingBoxf3 test_box = m_scene_box;
const Vec3d scene_box_center = m_scene_box.center();
test_box.translate(-scene_box_center);
test_box.scale(m_scene_box_scale_factor);
test_box.translate(scene_box_center);
test_box.offset(EPSILON);
return test_box.contains(get_position() + m_distance * get_dir_forward());
}
void Camera::set_default_orientation()
{
m_zenit = 45.0f;
@ -582,13 +621,12 @@ Vec3d Camera::validate_target(const Vec3d& target) const
BoundingBoxf3 test_box = m_scene_box;
test_box.translate(-m_scene_box.center());
// We may let this factor be customizable
static const double ScaleFactor = 1.5;
test_box.scale(ScaleFactor);
test_box.scale(m_scene_box_scale_factor);
test_box.translate(m_scene_box.center());
return { std::clamp(target(0), test_box.min(0), test_box.max(0)),
std::clamp(target(1), test_box.min(1), test_box.max(1)),
std::clamp(target(2), test_box.min(2), test_box.max(2)) };
return { std::clamp(target(0), test_box.min.x(), test_box.max.x()),
std::clamp(target(1), test_box.min.y(), test_box.max.y()),
std::clamp(target(2), test_box.min.z(), test_box.max.z())};
}
void Camera::update_zenit()

View File

@ -17,6 +17,8 @@ struct Camera
static const double DefaultDistance;
static const double DefaultZoomToBoxMarginFactor;
static const double DefaultZoomToVolumesMarginFactor;
static double SingleBedSceneBoxScaleFactor;
static double MultipleBedSceneBoxScaleFactor;
static double FrustrumMinZRange;
static double FrustrumMinNearZ;
static double FrustrumZMargin;
@ -49,6 +51,7 @@ private:
Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 };
Transform3d m_projection_matrix{ Transform3d::Identity() };
std::pair<double, double> m_frustrum_zs;
double m_scene_box_scale_factor{ SingleBedSceneBoxScaleFactor };
BoundingBoxf3 m_scene_box;
@ -67,6 +70,9 @@ public:
const Vec3d& get_target() const { return m_target; }
void set_target(const Vec3d& target);
void set_scene_box_scale_factor(float factor) { m_scene_box_scale_factor = factor; }
double get_scene_box_scale_factor() const { return m_scene_box_scale_factor; }
const Vec3d& get_rotation_pivot() const { return m_rotation_pivot; }
void set_rotation_pivot(const Vec3d& pivot) { m_rotation_pivot = pivot; }
@ -145,6 +151,8 @@ public:
double max_zoom() const { return 250.0; }
double min_zoom() const { return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); }
bool is_target_valid() const;
private:
// returns tight values for nearZ and farZ plane around the given bounding box
// the camera MUST be outside of the bounding box in eye coordinate of the given box

View File

@ -1845,6 +1845,8 @@ void GLCanvas3D::render()
// and the viewport was set incorrectly, leading to tripping glAsserts further down
// the road (in apply_projection). That's why the minimum size is forced to 10.
Camera& camera = wxGetApp().plater()->get_camera();
camera.set_scene_box_scale_factor((s_multiple_beds.get_number_of_beds() > 1) ?
Camera::MultipleBedSceneBoxScaleFactor : Camera::SingleBedSceneBoxScaleFactor);
camera.set_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
camera.apply_viewport();
@ -1948,6 +1950,8 @@ void GLCanvas3D::render()
#if ENABLE_SHOW_CAMERA_TARGET
_render_camera_target();
_render_camera_pivot();
_render_camera_target_validation_box();
#endif // ENABLE_SHOW_CAMERA_TARGET
if (m_picking_enabled && m_rectangle_selection.is_dragging())
@ -3804,13 +3808,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
}
else if (evt.MiddleIsDown() || evt.RightIsDown()) {
Camera& camera = wxGetApp().plater()->get_camera();
// If dragging over blank area with right/middle button, pan.
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
float z = 0.0f;
const float z = 0.0f;
const Vec3d cur_pos = _mouse_to_3d(pos, &z);
const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
Camera& camera = wxGetApp().plater()->get_camera();
if (!wxGetApp().app_config->get_bool("use_free_camera"))
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
@ -3818,11 +3822,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// See GH issue #3816.
camera.recover_from_free_camera();
camera.set_target(camera.get_target() + orig - cur_pos);
camera.set_target(m_mouse.drag.camera_start_target + orig - cur_pos);
m_dirty = true;
}
m_mouse.drag.start_position_2D = pos;
else {
m_mouse.drag.start_position_2D = pos;
m_mouse.drag.camera_start_target = camera.get_target();
}
}
}
else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) {
@ -5919,6 +5925,7 @@ void GLCanvas3D::_render_background()
// Draws a bottom to top gradient over the complete screen.
glsafe(::glDisable(GL_DEPTH_TEST));
const ColorRGBA top_color = use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR;
const ColorRGBA bottom_color = use_error_color ? ERROR_BG_DARK_COLOR : DEFAULT_BG_DARK_COLOR;
if (!m_background.is_initialized()) {
@ -5945,7 +5952,7 @@ void GLCanvas3D::_render_background()
GLShaderProgram* shader = wxGetApp().get_shader("background");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("top_color", use_error_color ? ERROR_BG_LIGHT_COLOR : DEFAULT_BG_LIGHT_COLOR);
shader->set_uniform("top_color", top_color);
shader->set_uniform("bottom_color", bottom_color);
m_background.render();
shader->stop_using();
@ -6361,7 +6368,7 @@ void GLCanvas3D::_render_view_toolbar() const
#if ENABLE_SHOW_CAMERA_TARGET
void GLCanvas3D::_render_camera_target()
{
static const float half_length = 5.0f;
static const float half_length = 10.0f;
glsafe(::glDisable(GL_DEPTH_TEST));
#if !SLIC3R_OPENGL_ES
@ -6369,8 +6376,7 @@ void GLCanvas3D::_render_camera_target()
glsafe(::glLineWidth(2.0f));
#endif // !SLIC3R_OPENGL_ES
const Vec3f& target = wxGetApp().plater()->get_camera().get_target().cast<float>();
m_camera_target.target = target.cast<double>();
m_camera_target.target = wxGetApp().plater()->get_camera().get_target();
for (int i = 0; i < 3; ++i) {
if (!m_camera_target.axis[i].is_initialized()) {
@ -6378,7 +6384,7 @@ void GLCanvas3D::_render_camera_target()
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
init_data.color = (i == X) ? ColorRGBA::X() : ((i == Y) ? ColorRGBA::Y() : ColorRGBA::Z());
init_data.color = (i == X) ? ColorRGBA::X() : (i == Y) ? ColorRGBA::Y() : ColorRGBA::Z();
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
@ -6429,10 +6435,152 @@ void GLCanvas3D::_render_camera_target()
shader->stop_using();
}
}
void GLCanvas3D::_render_camera_pivot()
{
static const float half_length = 10.0f;
glsafe(::glDisable(GL_DEPTH_TEST));
#if !SLIC3R_OPENGL_ES
if (!OpenGLManager::get_gl_info().is_core_profile())
glsafe(::glLineWidth(2.0f));
#endif // !SLIC3R_OPENGL_ES
m_camera_pivot.target = wxGetApp().plater()->get_camera().get_rotation_pivot();
for (int i = 0; i < 3; ++i) {
if (!m_camera_pivot.axis[i].is_initialized()) {
m_camera_pivot.axis[i].reset();
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
init_data.color = (i == X) ? ColorRGBA::X() : (i == Y) ? ColorRGBA::Y() : ColorRGBA::Z();
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
if (i == X) {
init_data.add_vertex(Vec3f(-half_length, 0.0f, 0.0f));
init_data.add_vertex(Vec3f(+half_length, 0.0f, 0.0f));
}
else if (i == Y) {
init_data.add_vertex(Vec3f(0.0f, -half_length, 0.0f));
init_data.add_vertex(Vec3f(0.0f, +half_length, 0.0f));
}
else {
init_data.add_vertex(Vec3f(0.0f, 0.0f, -half_length));
init_data.add_vertex(Vec3f(0.0f, 0.0f, +half_length));
}
// indices
init_data.add_line(0, 1);
m_camera_pivot.axis[i].init_from(std::move(init_data));
}
}
#if SLIC3R_OPENGL_ES
GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines");
#else
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
#endif // SLIC3R_OPENGL_ES
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(m_camera_pivot.target));
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if !SLIC3R_OPENGL_ES
if (OpenGLManager::get_gl_info().is_core_profile()) {
#endif // !SLIC3R_OPENGL_ES
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 0.5f);
shader->set_uniform("gap_size", 0.0f);
#if !SLIC3R_OPENGL_ES
}
#endif // !SLIC3R_OPENGL_ES
for (int i = 0; i < 3; ++i) {
m_camera_pivot.axis[i].render();
}
shader->stop_using();
}
}
void GLCanvas3D::_render_camera_target_validation_box()
{
const BoundingBoxf3& curr_box = m_target_validation_box.get_bounding_box();
BoundingBoxf3 camera_box = wxGetApp().plater()->get_camera().get_scene_box();
Vec3d camera_box_center = camera_box.center();
camera_box.translate(-camera_box_center);
camera_box.scale(wxGetApp().plater()->get_camera().get_scene_box_scale_factor());
camera_box.translate(camera_box_center);
if (!m_target_validation_box.is_initialized() || !is_approx(camera_box.min, curr_box.min) || !is_approx(camera_box.max, curr_box.max)) {
m_target_validation_box.reset();
const Vec3f b_min = camera_box.min.cast<float>();
const Vec3f b_max = camera_box.max.cast<float>();
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
init_data.reserve_vertices(12);
init_data.reserve_indices(12);
// vertices
init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z()));
init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z()));
init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z()));
init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z()));
init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z()));
init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z()));
init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z()));
init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z()));
init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z()));
init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z()));
init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z()));
init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z()));
// indices
for (unsigned int i = 0; i < 12; ++i) {
init_data.add_index(i);
}
m_target_validation_box.init_from(std::move(init_data));
}
glsafe(::glEnable(GL_DEPTH_TEST));
#if SLIC3R_OPENGL_ES
GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines");
#else
if (!OpenGLManager::get_gl_info().is_core_profile())
glsafe(::glLineWidth(2.0f));
GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
#endif // SLIC3R_OPENGL_ES
if (shader == nullptr)
return;
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
#if !SLIC3R_OPENGL_ES
if (OpenGLManager::get_gl_info().is_core_profile()) {
#endif // !SLIC3R_OPENGL_ES
const std::array<int, 4>& viewport = camera.get_viewport();
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
shader->set_uniform("width", 1.5f);
shader->set_uniform("gap_size", 0.0f);
#if !SLIC3R_OPENGL_ES
}
#endif // !SLIC3R_OPENGL_ES
m_target_validation_box.set_color(to_rgba(ColorRGB::WHITE()));
m_target_validation_box.render();
shader->stop_using();
}
#endif // ENABLE_SHOW_CAMERA_TARGET
static void render_sla_layer_legend(const SLAPrint& print, int layer_idx, int cnv_width)
{
const std::vector<double>& areas = print.print_statistics().layers_areas;
@ -6703,7 +6851,7 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
_start_timer();
}
Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, const float* z)
{
if (m_canvas == nullptr)
return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);

View File

@ -323,6 +323,7 @@ class GLCanvas3D
Point start_position_2D{ Invalid_2D_Point };
Vec3d start_position_3D{ Invalid_3D_Point };
Vec3d camera_start_target{ Invalid_3D_Point };
int move_volume_idx{ -1 };
bool move_requires_threshold{ false };
Point move_start_threshold_position_2D{ Invalid_2D_Point };
@ -336,10 +337,13 @@ class GLCanvas3D
void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; }
void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; }
void set_camera_start_target_as_invalid() { drag.camera_start_target = Drag::Invalid_3D_Point; }
void set_move_start_threshold_position_2D_as_invalid() { drag.move_start_threshold_position_2D = Drag::Invalid_2D_Point; }
bool is_start_position_2D_defined() const { return (drag.start_position_2D != Drag::Invalid_2D_Point); }
bool is_start_position_3D_defined() const { return (drag.start_position_3D != Drag::Invalid_3D_Point); }
bool is_start_position_2D_defined() const { return drag.start_position_2D != Drag::Invalid_2D_Point; }
bool is_start_position_3D_defined() const { return drag.start_position_3D != Drag::Invalid_3D_Point; }
bool is_camera_start_target_defined() { return drag.camera_start_target != Drag::Invalid_3D_Point; }
bool is_move_start_threshold_position_2D_defined() const { return (drag.move_start_threshold_position_2D != Drag::Invalid_2D_Point); }
bool is_move_threshold_met(const Point& mouse_pos) const {
return (std::abs(mouse_pos(0) - drag.move_start_threshold_position_2D(0)) > Drag::MoveThresholdPx)
@ -683,6 +687,8 @@ private:
};
CameraTarget m_camera_target;
CameraTarget m_camera_pivot;
GLModel m_target_validation_box;
#endif // ENABLE_SHOW_CAMERA_TARGET
GLModel m_background;
@ -1073,6 +1079,8 @@ private:
void _render_view_toolbar() const;
#if ENABLE_SHOW_CAMERA_TARGET
void _render_camera_target();
void _render_camera_pivot();
void _render_camera_target_validation_box();
#endif // ENABLE_SHOW_CAMERA_TARGET
void _render_sla_slices();
void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field); }
@ -1092,7 +1100,7 @@ private:
// Convert the screen space coordinate to an object space coordinate.
// If the Z screen space coordinate is not provided, a depth buffer value is substituted.
Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
Vec3d _mouse_to_3d(const Point& mouse_pos, const float* z = nullptr);
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);