Files
XCEngine/new_editor/app/Scene/SceneViewportCameraController.h

309 lines
10 KiB
C++

#pragma once
#include <XCEngine/Components/TransformComponent.h>
#include <XCEngine/Core/Math/Math.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <algorithm>
#include <cmath>
namespace XCEngine::UI::Editor::App {
struct SceneViewportCameraInputState {
float lookDeltaX = 0.0f;
float lookDeltaY = 0.0f;
float orbitDeltaX = 0.0f;
float orbitDeltaY = 0.0f;
float panDeltaX = 0.0f;
float panDeltaY = 0.0f;
float zoomDelta = 0.0f;
float flySpeedDelta = 0.0f;
float deltaTime = 0.0f;
float moveForward = 0.0f;
float moveRight = 0.0f;
float moveUp = 0.0f;
float viewportHeight = 0.0f;
bool fastMove = false;
};
class SceneViewportCameraController {
public:
void Reset() {
m_focalPoint = Math::Vector3::Zero();
m_distance = 6.0f;
m_flySpeed = 5.0f;
m_yawDegrees = -35.0f;
m_pitchDegrees = -20.0f;
m_snapAnimating = false;
UpdatePositionFromFocalPoint();
}
const Math::Vector3& GetFocalPoint() const {
return m_focalPoint;
}
float GetDistance() const {
return m_distance;
}
float GetFlySpeed() const {
return m_flySpeed;
}
float GetYawDegrees() const {
return m_yawDegrees;
}
float GetPitchDegrees() const {
return m_pitchDegrees;
}
Math::Vector3 GetForward() const {
const float yawRadians = m_yawDegrees * Math::DEG_TO_RAD;
const float pitchRadians = m_pitchDegrees * Math::DEG_TO_RAD;
return Math::Vector3::Normalize(Math::Vector3(
std::cos(pitchRadians) * std::sin(yawRadians),
std::sin(pitchRadians),
std::cos(pitchRadians) * std::cos(yawRadians)));
}
Math::Vector3 GetPosition() const {
return m_position;
}
void Focus(const Math::Vector3& point) {
m_focalPoint = point;
UpdatePositionFromFocalPoint();
}
void SnapToForward(const Math::Vector3& forward) {
if (forward.SqrMagnitude() <= Math::EPSILON) {
return;
}
const OrientationAngles target = ComputeOrientationAngles(forward);
m_yawDegrees = target.yawDegrees;
m_pitchDegrees = target.pitchDegrees;
m_snapAnimating = false;
UpdatePositionFromFocalPoint();
}
void AnimateToForward(const Math::Vector3& forward) {
if (forward.SqrMagnitude() <= Math::EPSILON) {
return;
}
const OrientationAngles target = ComputeOrientationAngles(forward);
m_snapStartYawDegrees = m_yawDegrees;
m_snapStartPitchDegrees = m_pitchDegrees;
m_snapTargetYawDegrees = target.yawDegrees;
m_snapTargetPitchDegrees = target.pitchDegrees;
m_snapElapsed = 0.0f;
m_snapAnimating = true;
}
void ApplyInput(const SceneViewportCameraInputState& input) {
if (input.viewportHeight <= 0.0f) {
return;
}
const bool hasManualInput =
std::abs(input.lookDeltaX) > Math::EPSILON ||
std::abs(input.lookDeltaY) > Math::EPSILON ||
std::abs(input.orbitDeltaX) > Math::EPSILON ||
std::abs(input.orbitDeltaY) > Math::EPSILON ||
std::abs(input.panDeltaX) > Math::EPSILON ||
std::abs(input.panDeltaY) > Math::EPSILON ||
std::abs(input.zoomDelta) > Math::EPSILON ||
std::abs(input.flySpeedDelta) > Math::EPSILON ||
std::abs(input.moveForward) > Math::EPSILON ||
std::abs(input.moveRight) > Math::EPSILON ||
std::abs(input.moveUp) > Math::EPSILON;
if (hasManualInput) {
m_snapAnimating = false;
}
if (std::abs(input.lookDeltaX) > Math::EPSILON ||
std::abs(input.lookDeltaY) > Math::EPSILON) {
ApplyRotationDelta(
input.lookDeltaX,
input.lookDeltaY,
kLookYawSensitivity,
kLookPitchSensitivity);
UpdateFocalPointFromPosition();
}
if (std::abs(input.orbitDeltaX) > Math::EPSILON ||
std::abs(input.orbitDeltaY) > Math::EPSILON) {
ApplyRotationDelta(
input.orbitDeltaX,
input.orbitDeltaY,
kOrbitYawSensitivity,
kOrbitPitchSensitivity);
UpdatePositionFromFocalPoint();
}
if (std::abs(input.panDeltaX) > Math::EPSILON ||
std::abs(input.panDeltaY) > Math::EPSILON) {
const Math::Vector3 right = GetRight();
const Math::Vector3 up = GetUp();
const float worldUnitsPerPixel = ComputeWorldUnitsPerPixel(input.viewportHeight);
const Math::Vector3 delta =
((right * -input.panDeltaX) + (up * input.panDeltaY)) * worldUnitsPerPixel;
m_focalPoint += delta;
m_position += delta;
}
if (std::abs(input.flySpeedDelta) > Math::EPSILON) {
const float speedFactor = std::pow(1.20f, input.flySpeedDelta);
m_flySpeed = std::clamp(m_flySpeed * speedFactor, 0.1f, 500.0f);
}
if (input.deltaTime > 0.0f &&
(std::abs(input.moveForward) > Math::EPSILON ||
std::abs(input.moveRight) > Math::EPSILON ||
std::abs(input.moveUp) > Math::EPSILON)) {
const Math::Vector3 movement =
GetForward() * input.moveForward +
GetRight() * input.moveRight +
Math::Vector3::Up() * input.moveUp;
if (movement.SqrMagnitude() > Math::EPSILON) {
const float speedMultiplier = input.fastMove ? 4.0f : 1.0f;
const float flySpeed = m_flySpeed * speedMultiplier;
const Math::Vector3 delta =
Math::Vector3::Normalize(movement) * flySpeed * input.deltaTime;
m_position += delta;
m_focalPoint += delta;
}
}
if (std::abs(input.zoomDelta) > Math::EPSILON) {
const float zoomFactor = std::pow(0.85f, input.zoomDelta);
m_distance = std::clamp(m_distance * zoomFactor, 0.5f, 500.0f);
UpdatePositionFromFocalPoint();
}
if (m_snapAnimating && input.deltaTime > 0.0f) {
m_snapElapsed += input.deltaTime;
const float t = std::clamp(m_snapElapsed / kSnapDurationSeconds, 0.0f, 1.0f);
const float easedT = 1.0f - std::pow(1.0f - t, 3.0f);
m_yawDegrees = LerpAngleDegrees(m_snapStartYawDegrees, m_snapTargetYawDegrees, easedT);
m_pitchDegrees =
m_snapStartPitchDegrees +
(m_snapTargetPitchDegrees - m_snapStartPitchDegrees) * easedT;
UpdatePositionFromFocalPoint();
if (t >= 1.0f) {
m_snapAnimating = false;
}
}
}
void ApplyTo(Components::TransformComponent& transform) const {
transform.SetPosition(GetPosition());
transform.SetRotation(Math::Quaternion::FromEulerAngles(
-m_pitchDegrees * Math::DEG_TO_RAD,
m_yawDegrees * Math::DEG_TO_RAD,
0.0f));
}
private:
static constexpr float kSnapDurationSeconds = 0.22f;
static constexpr float kLookYawSensitivity = 0.18f;
static constexpr float kLookPitchSensitivity = 0.12f;
static constexpr float kOrbitYawSensitivity = 0.30f;
static constexpr float kOrbitPitchSensitivity = 0.20f;
struct OrientationAngles {
float yawDegrees = 0.0f;
float pitchDegrees = 0.0f;
};
static float NormalizeDegrees(float value) {
while (value > 180.0f) {
value -= 360.0f;
}
while (value < -180.0f) {
value += 360.0f;
}
return value;
}
static float LerpAngleDegrees(float fromDegrees, float toDegrees, float t) {
const float delta = NormalizeDegrees(toDegrees - fromDegrees);
return NormalizeDegrees(fromDegrees + delta * t);
}
static OrientationAngles ComputeOrientationAngles(const Math::Vector3& forward) {
OrientationAngles result = {};
const Math::Vector3 normalizedForward = forward.Normalized();
const float horizontalLengthSq =
normalizedForward.x * normalizedForward.x +
normalizedForward.z * normalizedForward.z;
if (horizontalLengthSq <= Math::EPSILON) {
result.yawDegrees = 0.0f;
} else {
result.yawDegrees =
std::atan2(normalizedForward.x, normalizedForward.z) * Math::RAD_TO_DEG;
}
result.pitchDegrees = std::clamp(
std::asin(std::clamp(normalizedForward.y, -1.0f, 1.0f)) * Math::RAD_TO_DEG,
-89.0f,
89.0f);
return result;
}
void ApplyRotationDelta(
float deltaX,
float deltaY,
float yawSensitivity,
float pitchSensitivity) {
m_yawDegrees += deltaX * yawSensitivity;
m_pitchDegrees =
std::clamp(m_pitchDegrees - deltaY * pitchSensitivity, -89.0f, 89.0f);
}
Math::Vector3 GetRight() const {
return Math::Vector3::Cross(Math::Vector3::Up(), GetForward()).Normalized();
}
Math::Vector3 GetUp() const {
return Math::Vector3::Cross(GetForward(), GetRight()).Normalized();
}
float ComputeWorldUnitsPerPixel(float viewportHeight) const {
if (viewportHeight <= Math::EPSILON) {
return 0.0f;
}
const float verticalFovRadians = 60.0f * Math::DEG_TO_RAD;
const float viewportWorldHeight =
2.0f * std::tan(verticalFovRadians * 0.5f) * m_distance;
return viewportWorldHeight / viewportHeight;
}
void UpdatePositionFromFocalPoint() {
m_position = m_focalPoint - GetForward() * m_distance;
}
void UpdateFocalPointFromPosition() {
m_focalPoint = m_position + GetForward() * m_distance;
}
Math::Vector3 m_focalPoint = Math::Vector3::Zero();
Math::Vector3 m_position = Math::Vector3(0.0f, 0.0f, -6.0f);
float m_distance = 6.0f;
float m_flySpeed = 5.0f;
float m_yawDegrees = -35.0f;
float m_pitchDegrees = -20.0f;
float m_snapStartYawDegrees = 0.0f;
float m_snapStartPitchDegrees = 0.0f;
float m_snapTargetYawDegrees = 0.0f;
float m_snapTargetPitchDegrees = 0.0f;
float m_snapElapsed = 0.0f;
bool m_snapAnimating = false;
};
} // namespace XCEngine::UI::Editor::App