#include "editor_camera.hpp" #include #include 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}); }