diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 385d2966a7..841a678ca7 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -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(); Vec3f target = m_target.cast(); + Vec3f pivot = m_rotation_pivot.cast(); float distance = (float)get_distance(); float zenit = (float)m_zenit; Vec3f forward = get_dir_forward().cast(); @@ -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 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() diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 3117c6f3bb..183086fe27 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -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 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 diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1537c84dbe..f8ba951c69 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -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(); - m_camera_target.target = target.cast(); + 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& 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(); + const Vec3f b_max = camera_box.max.cast(); + + 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& 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& 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); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d6bd675173..2f7fc33a31 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -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);