Files
sophia/editor/editor_camera.cpp

100 lines
3.0 KiB
C++

#include "editor_camera.hpp"
#include <algorithm>
#include <cmath>
namespace {
constexpr float ORBIT_RADIANS_PER_PIXEL = 0.005f;
const float MAX_PITCH = glm::radians(89.f);
// Each wheel step moves this fraction of the way towards the pivot.
constexpr float ZOOM_STEP = 0.1f;
constexpr float MIN_DISTANCE = 0.05f;
constexpr float MIN_ORTHO_HEIGHT = 0.01f;
// Fly speed per unit of distance from the pivot, so it suits any scale.
constexpr float FLY_SPEED = 1.5f;
} // namespace
void EditorCamera::orbit(glm::vec2 pixels) {
if (mode != Mode::Scene3D) {
return;
}
yaw -= pixels.x * ORBIT_RADIANS_PER_PIXEL;
pitch = std::clamp(pitch - pixels.y * ORBIT_RADIANS_PER_PIXEL, -MAX_PITCH,
MAX_PITCH);
}
void EditorCamera::pan(glm::vec2 pixels, float viewportHeight) {
if (viewportHeight <= 0.f) {
return;
}
// World units one pixel covers at the pivot's depth.
float unitsPerPixel =
mode == Mode::Scene3D
? 2.f * distance * std::tan(fov / 2.f) / viewportHeight
: orthoHeight / viewportHeight;
// Screen y runs down, world y up. Dragging moves the scene with the mouse,
// so the camera moves the other way.
glm::vec3 offset{-pixels.x * unitsPerPixel, pixels.y * unitsPerPixel, 0.f};
pivot += mode == Mode::Scene3D ? getRotation() * offset : offset;
}
void EditorCamera::zoom(float steps) {
float scale = std::pow(1.f - ZOOM_STEP, steps);
if (mode == Mode::Scene3D) {
distance = std::max(distance * scale, MIN_DISTANCE);
} else {
orthoHeight = std::max(orthoHeight * scale, MIN_ORTHO_HEIGHT);
}
}
void EditorCamera::fly(glm::vec3 direction, float deltaTime) {
if (mode != Mode::Scene3D || direction == glm::vec3{0.f}) {
return;
}
float speed = FLY_SPEED * std::max(distance, 1.f);
pivot += getRotation() * glm::normalize(direction) * speed * deltaTime;
}
void EditorCamera::frame(glm::vec3 target, float radius) {
pivot = target;
radius = std::max(radius, 0.1f);
// Far enough that the sphere fits in the vertical field of view.
distance = radius / std::sin(fov / 2.f);
orthoHeight = radius * 2.5f;
}
sophia::TransformComponent EditorCamera::getTransform() const {
sophia::TransformComponent transform;
if (mode == Mode::Scene3D) {
transform.rotation = getRotation();
transform.position =
pivot + transform.rotation * glm::vec3{0.f, 0.f, distance};
} else {
transform.position = pivot;
}
return transform;
}
sophia::CameraComponent EditorCamera::getCamera() const {
sophia::CameraComponent camera;
if (mode == Mode::Scene3D) {
camera.projection = sophia::CameraComponent::Projection::Perspective;
camera.perspective.fov = fov;
// Room to zoom far out without the scene clipping.
camera.perspective.farPlane = 1000.f;
} else {
camera.projection = sophia::CameraComponent::Projection::Orthographic;
camera.ortho.height = orthoHeight;
}
return camera;
}
glm::quat EditorCamera::getRotation() const {
return glm::angleAxis(yaw, glm::vec3{0.f, 1.f, 0.f}) *
glm::angleAxis(pitch, glm::vec3{1.f, 0.f, 0.f});
}