关键节点
This commit is contained in:
308
editor/app/Scene/SceneViewportCameraController.h
Normal file
308
editor/app/Scene/SceneViewportCameraController.h
Normal file
@@ -0,0 +1,308 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user