diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index b8c182ef4..1f8513ac2 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -24,6 +24,8 @@ namespace GUI { const double Camera::DefaultDistance = 1000.0; double Camera::FrustrumMinZSize = 50.0; double Camera::FrustrumZMargin = 10.0; +double Camera::FovMinDeg = 5.0; +double Camera::FovMaxDeg = 75.0; Camera::Camera() : phi(45.0f) @@ -34,6 +36,7 @@ Camera::Camera() , m_theta(45.0f) , m_zoom(1.0) , m_distance(DefaultDistance) + , m_gui_scale(1.0) , m_view_matrix(Transform3d::Identity()) , m_projection_matrix(Transform3d::Identity()) { @@ -148,6 +151,18 @@ bool Camera::select_view(const std::string& direction) return false; } +double Camera::get_fov() const +{ + switch (m_type) + { + case Perspective: + return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1))); + default: + case Ortho: + return 0.0; + }; +} + void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const { glsafe(::glViewport(0, 0, w, h)); @@ -174,17 +189,67 @@ void Camera::apply_view_matrix() const void Camera::apply_projection(const BoundingBoxf3& box) const { - m_frustrum_zs = calc_tight_frustrum_zs_around(box); + m_distance = DefaultDistance; + double w = 0.0; + double h = 0.0; - double w = (double)m_viewport[2]; - double h = (double)m_viewport[3]; - - double two_zoom = 2.0 * m_zoom; - if (two_zoom != 0.0) + while (true) { - double inv_two_zoom = 1.0 / two_zoom; - w *= inv_two_zoom; - h *= inv_two_zoom; + m_frustrum_zs = calc_tight_frustrum_zs_around(box); + + w = (double)m_viewport[2]; + h = (double)m_viewport[3]; + + double two_zoom = 2.0 * m_zoom; + if (two_zoom != 0.0) + { + double inv_two_zoom = 1.0 / two_zoom; + w *= inv_two_zoom; + h *= inv_two_zoom; + } + + switch (m_type) + { + default: + case Ortho: + { + m_gui_scale = 1.0; + break; + } + case Perspective: + { + // scale near plane to keep w and h constant on the plane at z = m_distance + double scale = m_frustrum_zs.first / m_distance; + w *= scale; + h *= scale; + m_gui_scale = scale; + break; + } + } + + if (m_type == Perspective) + { + double fov_rad = 2.0 * std::atan(h / m_frustrum_zs.first); + double fov_deg = Geometry::rad2deg(fov_rad); + + // adjust camera distance to keep fov in a limited range + if (fov_deg > FovMaxDeg + 0.001) + { + double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMaxDeg)); + m_distance += (new_near_z - m_frustrum_zs.first); + apply_view_matrix(); + } + else if (fov_deg < FovMinDeg - 0.001) + { + double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMinDeg)); + m_distance += (new_near_z - m_frustrum_zs.first); + apply_view_matrix(); + } + else + break; + } + else + break; } glsafe(::glMatrixMode(GL_PROJECTION)); @@ -231,17 +296,22 @@ void Camera::debug_render() const std::string type = get_type_as_string(); Vec3f position = get_position().cast(); Vec3f target = m_target.cast(); + float distance = (float)get_distance(); Vec3f forward = get_dir_forward().cast(); Vec3f right = get_dir_right().cast(); Vec3f up = get_dir_up().cast(); float nearZ = (float)m_frustrum_zs.first; float farZ = (float)m_frustrum_zs.second; float deltaZ = farZ - nearZ; + float zoom = (float)m_zoom; + float fov = (float)get_fov(); + float gui_scale = (float)get_gui_scale(); ImGui::InputText("Type", const_cast(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); @@ -250,6 +320,11 @@ void Camera::debug_render() const ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); imgui.end(); } #endif // ENABLE_CAMERA_STATISTICS @@ -273,11 +348,10 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo vertices.push_back(bb_max); vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); - // set the Z range in eye coordinates (only negative Zs are in front of the camera) + // set the Z range in eye coordinates (negative Zs are in front of the camera) for (const Vec3d& v : vertices) { - // ensure non-negative values - double z = std::max(-(m_view_matrix * v)(2), 0.0); + double z = -(m_view_matrix * v)(2); ret.first = std::min(ret.first, z); ret.second = std::max(ret.second, z); } @@ -295,8 +369,6 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo ret.second = mid_z + half_size; } - assert(ret.first > 0.0); - return ret; } diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 6a2f65a30..bd2541ce2 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -12,6 +12,8 @@ struct Camera static const double DefaultDistance; static double FrustrumMinZSize; static double FrustrumZMargin; + static double FovMinDeg; + static double FovMaxDeg; enum EType : unsigned char { @@ -31,7 +33,8 @@ private: float m_theta; double m_zoom; // Distance between camera position and camera target measured along the camera Z axis - double m_distance; + mutable double m_distance; + mutable double m_gui_scale; mutable std::array m_viewport; mutable Transform3d m_view_matrix; @@ -52,13 +55,14 @@ public: const Vec3d& get_target() const { return m_target; } void set_target(const Vec3d& target); + double get_distance() const { return m_distance; } + double get_gui_scale() const { return m_gui_scale; } + float get_theta() const { return m_theta; } void set_theta(float theta, bool apply_limit); double get_zoom() const { return m_zoom; } void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h); - // this method does not check if the given zoom is valid, use instead the other set_zoom() method - // called only by: void GLCanvas3D::update_ui_from_settings() void set_zoom(double zoom) { m_zoom = zoom; } const BoundingBoxf3& get_scene_box() const { return m_scene_box; } @@ -79,6 +83,8 @@ public: double get_near_z() const { return m_frustrum_zs.first; } double get_far_z() const { return m_frustrum_zs.second; } + double get_fov() const; + void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; void apply_projection(const BoundingBoxf3& box) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d9a8a870c..2bb284fd4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3902,8 +3902,11 @@ void GLCanvas3D::_render_overlays() const glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); - // ensure the textures are renderered inside the frustrum + // ensure that the textures are renderered inside the frustrum glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = m_camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); _render_gizmos_overlay(); _render_warning_texture();