SPE-2596 - Camera rotation around screen center, keeping the rotation pivot on the plane z = 0

This commit is contained in:
enricoturri1966 2024-12-02 08:24:37 +01:00 committed by Lukas Matena
parent 63ce55166d
commit 015b9f5536
4 changed files with 54 additions and 151 deletions

View File

@ -24,8 +24,7 @@ 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;
double Camera::SceneBoxScaleFactor = 2.5;
std::string Camera::get_type_as_string() const
{
@ -67,10 +66,20 @@ void Camera::set_target(const Vec3d& target)
Transform3d inv_view_matrix = m_view_matrix.inverse();
inv_view_matrix.translation() = m_target - m_distance * get_dir_forward();
m_view_matrix = inv_view_matrix.inverse();
m_rotation_pivot = m_target;
}
}
BoundingBoxf3 Camera::get_target_validation_box() const
{
const Vec3d center = m_scene_box.center();
Vec3d size = m_scene_box.size();
size.x() *= m_scene_box_scale_factor;
size.y() *= m_scene_box_scale_factor;
size.z() *= 1.5;
const Vec3d half_size = 0.5 * size;
return BoundingBoxf3(center - half_size, center + half_size);
}
void Camera::set_zoom(double zoom)
{
// Don't allow to zoom too far outside the scene.
@ -267,6 +276,8 @@ void Camera::apply_projection(double left, double right, double bottom, double t
void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor)
{
m_distance = DefaultDistance;
// Calculate the zoom factor needed to adjust the view around the given box.
const double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor);
if (zoom > 0.0) {
@ -278,6 +289,8 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor)
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor)
{
m_distance = DefaultDistance;
Vec3d center;
const double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor);
if (zoom > 0.0) {
@ -301,7 +314,6 @@ 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>();
@ -319,7 +331,6 @@ 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);
@ -356,31 +367,11 @@ void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, b
}
}
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 auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ());
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);
}
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
}
// Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians.
@ -402,8 +393,6 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
std::pair<double, double> ret;
auto& [near_z, far_z] = ret;
set_distance(DefaultDistance);
// box in eye space
const BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
near_z = -eye_box.max.z();
@ -591,21 +580,6 @@ 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;
@ -620,15 +594,10 @@ void Camera::set_default_orientation()
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
test_box.scale(m_scene_box_scale_factor);
test_box.translate(m_scene_box.center());
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())};
const BoundingBoxf3 test_box = get_target_validation_box();
return { std::clamp(target.x(), test_box.min.x(), test_box.max.x()),
std::clamp(target.y(), test_box.min.y(), test_box.max.y()),
std::clamp(target.z(), test_box.min.z(), test_box.max.z())};
}
void Camera::update_zenit()

View File

@ -17,8 +17,7 @@ struct Camera
static const double DefaultDistance;
static const double DefaultZoomToBoxMarginFactor;
static const double DefaultZoomToVolumesMarginFactor;
static double SingleBedSceneBoxScaleFactor;
static double MultipleBedSceneBoxScaleFactor;
static double SceneBoxScaleFactor;
static double FrustrumMinZRange;
static double FrustrumMinNearZ;
static double FrustrumZMargin;
@ -38,7 +37,6 @@ private:
EType m_type{ EType::Perspective };
bool m_update_config_on_type_change_enabled{ false };
Vec3d m_target{ Vec3d::Zero() };
Vec3d m_rotation_pivot{ Vec3d::Zero() };
float m_zenit{ 45.0f };
double m_zoom{ 1.0 };
// Distance between camera position and camera target measured along the camera Z axis
@ -51,7 +49,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 };
double m_scene_box_scale_factor{ SceneBoxScaleFactor };
BoundingBoxf3 m_scene_box;
@ -70,11 +68,7 @@ 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; }
BoundingBoxf3 get_target_validation_box() const;
double get_distance() const { return (get_position() - m_target).norm(); }
double get_gui_scale() const { return m_gui_scale; }
@ -151,7 +145,7 @@ 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;
void set_distance(double distance);
private:
// returns tight values for nearZ and farZ plane around the given bounding box
@ -159,7 +153,6 @@ private:
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box);
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor) const;
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
void set_distance(double distance);
void set_default_orientation();
Vec3d validate_target(const Vec3d& target) const;

View File

@ -1869,8 +1869,6 @@ 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();
@ -1886,7 +1884,6 @@ void GLCanvas3D::render()
if (m_last_active_bed_id != curr_active_bed_id) {
const Vec3d bed_offset = s_multiple_beds.get_bed_translation(s_multiple_beds.get_active_bed());
const Vec2d bed_center = m_bed.build_volume().bed_center() + Vec2d(bed_offset.x(), bed_offset.y());
camera.set_rotation_pivot({ bed_center.x(), bed_center.y(), 0.0f });
m_last_active_bed_id = curr_active_bed_id;
}
@ -1961,7 +1958,6 @@ void GLCanvas3D::render()
#if ENABLE_SHOW_CAMERA_TARGET
_render_camera_target();
_render_camera_pivot();
_render_camera_target_validation_box();
#endif // ENABLE_SHOW_CAMERA_TARGET
@ -3858,8 +3854,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_mouse.is_start_position_2D_defined()) {
// get point in model space at Z = 0
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);
const Vec3d cur_pos = _mouse_to_3d(pos, &z, true);
const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z, true);
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(),
@ -3881,6 +3877,25 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (evt.LeftUp() && m_sequential_print_clearance.is_dragging())
m_sequential_print_clearance.stop_dragging();
if (evt.RightUp() && m_mouse.is_start_position_2D_defined()) {
// forces camera target to be on the plane z = 0
Camera& camera = wxGetApp().plater()->get_camera();
if (std::abs(camera.get_dir_forward().dot(Vec3d::UnitZ())) > EPSILON) {
const Vec3d old_pos = camera.get_position();
const double old_distance = camera.get_distance();
const Vec3d old_target = camera.get_target();
const Linef3 ray(old_pos, old_target);
const Vec3d new_target = ray.intersect_plane(0.0);
const BoundingBoxf3 validation_box = camera.get_target_validation_box();
if (validation_box.contains(new_target)) {
const double new_distance = (new_target - old_pos).norm();
camera.set_target(new_target);
camera.set_distance(new_distance);
if (camera.get_type() == Camera::EType::Perspective)
camera.set_zoom(camera.get_zoom() * old_distance / new_distance);
}
}
}
if (m_layers_editing.state != LayersEditing::Unknown) {
m_layers_editing.state = LayersEditing::Unknown;
@ -6627,84 +6642,10 @@ void GLCanvas3D::_render_camera_target()
}
}
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);
const BoundingBoxf3 camera_box = wxGetApp().plater()->get_camera().get_target_validation_box();
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();
@ -7043,7 +6984,7 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
_start_timer();
}
Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, const float* z)
Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, const float* z, bool use_ortho)
{
if (m_canvas == nullptr)
return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
@ -7055,11 +6996,13 @@ Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, const float* z)
else {
Camera& camera = wxGetApp().plater()->get_camera();
const Camera::EType type = camera.get_type();
camera.set_type(Camera::EType::Ortho);
if (use_ortho)
camera.set_type(Camera::EType::Ortho);
const Vec4i viewport(camera.get_viewport().data());
Vec3d out;
igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), *z), camera.get_view_matrix().matrix(), camera.get_projection_matrix().matrix(), viewport, out);
camera.set_type(type);
if (use_ortho)
camera.set_type(type);
return out;
}
}

View File

@ -692,7 +692,6 @@ private:
};
CameraTarget m_camera_target;
CameraTarget m_camera_pivot;
GLModel m_target_validation_box;
#endif // ENABLE_SHOW_CAMERA_TARGET
GLModel m_background;
@ -1085,7 +1084,6 @@ 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();
@ -1106,7 +1104,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, const float* z = nullptr);
Vec3d _mouse_to_3d(const Point& mouse_pos, const float* z = nullptr, bool use_ortho = false);
// Convert the screen space coordinate to world coordinate on the bed.
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);