309 lines
10 KiB
C++
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
|