Adaptive perspective camera frustrum
This commit is contained in:
parent
e5be8adadf
commit
8b3d88bc0a
@ -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<float>();
|
||||
Vec3f target = m_target.cast<float>();
|
||||
float distance = (float)get_distance();
|
||||
Vec3f forward = get_dir_forward().cast<float>();
|
||||
Vec3f right = get_dir_right().cast<float>();
|
||||
Vec3f up = get_dir_up().cast<float>();
|
||||
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<char*>(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<double, double> 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<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
|
||||
ret.second = mid_z + half_size;
|
||||
}
|
||||
|
||||
assert(ret.first > 0.0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -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<int, 4> 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;
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user