239 lines
8.3 KiB
C++
239 lines
8.3 KiB
C++
#pragma once
|
|
|
|
#include "IViewportHostService.h"
|
|
|
|
#include <XCEngine/Core/Math/Matrix4.h>
|
|
#include <XCEngine/Core/Math/Plane.h>
|
|
#include <XCEngine/Core/Math/Vector2.h>
|
|
#include <XCEngine/Core/Math/Vector4.h>
|
|
|
|
namespace XCEngine {
|
|
namespace Editor {
|
|
|
|
struct SceneViewportProjectedPoint {
|
|
Math::Vector2 screenPosition = Math::Vector2::Zero();
|
|
float ndcDepth = 0.0f;
|
|
bool visible = false;
|
|
};
|
|
|
|
inline Math::Matrix4x4 BuildSceneViewportViewMatrix(const SceneViewportOverlayData& overlay) {
|
|
const Math::Vector3 right = overlay.cameraRight.Normalized();
|
|
const Math::Vector3 up = overlay.cameraUp.Normalized();
|
|
const Math::Vector3 forward = overlay.cameraForward.Normalized();
|
|
|
|
Math::Matrix4x4 view = Math::Matrix4x4::Identity();
|
|
view.m[0][0] = right.x;
|
|
view.m[0][1] = right.y;
|
|
view.m[0][2] = right.z;
|
|
view.m[0][3] = -Math::Vector3::Dot(right, overlay.cameraPosition);
|
|
|
|
view.m[1][0] = up.x;
|
|
view.m[1][1] = up.y;
|
|
view.m[1][2] = up.z;
|
|
view.m[1][3] = -Math::Vector3::Dot(up, overlay.cameraPosition);
|
|
|
|
view.m[2][0] = forward.x;
|
|
view.m[2][1] = forward.y;
|
|
view.m[2][2] = forward.z;
|
|
view.m[2][3] = -Math::Vector3::Dot(forward, overlay.cameraPosition);
|
|
return view;
|
|
}
|
|
|
|
inline Math::Matrix4x4 BuildSceneViewportProjectionMatrix(
|
|
const SceneViewportOverlayData& overlay,
|
|
float viewportWidth,
|
|
float viewportHeight) {
|
|
const float aspect = viewportHeight > 0.0f
|
|
? viewportWidth / viewportHeight
|
|
: 1.0f;
|
|
return Math::Matrix4x4::Perspective(
|
|
overlay.verticalFovDegrees * Math::DEG_TO_RAD,
|
|
aspect,
|
|
overlay.nearClipPlane,
|
|
overlay.farClipPlane);
|
|
}
|
|
|
|
inline Math::Matrix4x4 BuildSceneViewportViewProjectionMatrix(
|
|
const SceneViewportOverlayData& overlay,
|
|
float viewportWidth,
|
|
float viewportHeight) {
|
|
return BuildSceneViewportProjectionMatrix(overlay, viewportWidth, viewportHeight) *
|
|
BuildSceneViewportViewMatrix(overlay);
|
|
}
|
|
|
|
inline SceneViewportProjectedPoint ProjectSceneViewportWorldPoint(
|
|
const SceneViewportOverlayData& overlay,
|
|
float viewportWidth,
|
|
float viewportHeight,
|
|
const Math::Vector3& worldPoint) {
|
|
SceneViewportProjectedPoint result = {};
|
|
if (!overlay.valid || viewportWidth <= 1.0f || viewportHeight <= 1.0f) {
|
|
return result;
|
|
}
|
|
|
|
const Math::Vector4 clipPoint =
|
|
BuildSceneViewportViewProjectionMatrix(overlay, viewportWidth, viewportHeight) *
|
|
Math::Vector4(worldPoint, 1.0f);
|
|
if (clipPoint.w <= Math::EPSILON) {
|
|
return result;
|
|
}
|
|
|
|
const Math::Vector3 ndcPoint = clipPoint.ToVector3() / clipPoint.w;
|
|
result.screenPosition.x = (ndcPoint.x * 0.5f + 0.5f) * viewportWidth;
|
|
result.screenPosition.y = (1.0f - (ndcPoint.y * 0.5f + 0.5f)) * viewportHeight;
|
|
result.ndcDepth = ndcPoint.z;
|
|
result.visible =
|
|
ndcPoint.x >= -1.0f && ndcPoint.x <= 1.0f &&
|
|
ndcPoint.y >= -1.0f && ndcPoint.y <= 1.0f &&
|
|
ndcPoint.z >= 0.0f && ndcPoint.z <= 1.0f;
|
|
return result;
|
|
}
|
|
|
|
inline bool ProjectSceneViewportAxisDirection(
|
|
const SceneViewportOverlayData& overlay,
|
|
const Math::Vector3& worldAxis,
|
|
Math::Vector2& outScreenDirection) {
|
|
if (!overlay.valid) {
|
|
return false;
|
|
}
|
|
|
|
const Math::Vector3 viewAxis = BuildSceneViewportViewMatrix(overlay).MultiplyVector(worldAxis.Normalized());
|
|
const Math::Vector2 screenDirection(viewAxis.x, -viewAxis.y);
|
|
if (screenDirection.SqrMagnitude() <= Math::EPSILON) {
|
|
return false;
|
|
}
|
|
|
|
outScreenDirection = screenDirection.Normalized();
|
|
return true;
|
|
}
|
|
|
|
inline bool ProjectSceneViewportAxisDirectionAtPoint(
|
|
const SceneViewportOverlayData& overlay,
|
|
float viewportWidth,
|
|
float viewportHeight,
|
|
const Math::Vector3& worldPoint,
|
|
const Math::Vector3& worldAxis,
|
|
Math::Vector2& outScreenDirection,
|
|
float sampleDistance = 1.0f) {
|
|
const Math::Vector3 axis = worldAxis.Normalized();
|
|
if (!overlay.valid ||
|
|
viewportWidth <= 1.0f ||
|
|
viewportHeight <= 1.0f ||
|
|
axis.SqrMagnitude() <= Math::EPSILON ||
|
|
sampleDistance <= Math::EPSILON) {
|
|
return false;
|
|
}
|
|
|
|
const Math::Matrix4x4 viewProjection =
|
|
BuildSceneViewportViewProjectionMatrix(overlay, viewportWidth, viewportHeight);
|
|
const Math::Vector4 startClip = viewProjection * Math::Vector4(worldPoint, 1.0f);
|
|
const Math::Vector4 endClip = viewProjection * Math::Vector4(worldPoint + axis * sampleDistance, 1.0f);
|
|
if (startClip.w <= Math::EPSILON || endClip.w <= Math::EPSILON) {
|
|
return ProjectSceneViewportAxisDirection(overlay, axis, outScreenDirection);
|
|
}
|
|
|
|
const Math::Vector3 startNdc = startClip.ToVector3() / startClip.w;
|
|
const Math::Vector3 endNdc = endClip.ToVector3() / endClip.w;
|
|
const Math::Vector2 startScreen(
|
|
(startNdc.x * 0.5f + 0.5f) * viewportWidth,
|
|
(1.0f - (startNdc.y * 0.5f + 0.5f)) * viewportHeight);
|
|
const Math::Vector2 endScreen(
|
|
(endNdc.x * 0.5f + 0.5f) * viewportWidth,
|
|
(1.0f - (endNdc.y * 0.5f + 0.5f)) * viewportHeight);
|
|
|
|
const Math::Vector2 screenDirection = endScreen - startScreen;
|
|
if (screenDirection.SqrMagnitude() <= Math::EPSILON) {
|
|
return ProjectSceneViewportAxisDirection(overlay, axis, outScreenDirection);
|
|
}
|
|
|
|
outScreenDirection = screenDirection.Normalized();
|
|
return true;
|
|
}
|
|
|
|
inline bool ProjectSceneViewportWorldPointClamped(
|
|
const SceneViewportOverlayData& overlay,
|
|
float viewportWidth,
|
|
float viewportHeight,
|
|
const Math::Vector3& worldPoint,
|
|
float edgePadding,
|
|
SceneViewportProjectedPoint& outProjectedPoint) {
|
|
outProjectedPoint = ProjectSceneViewportWorldPoint(overlay, viewportWidth, viewportHeight, worldPoint);
|
|
if (!overlay.valid || viewportWidth <= 1.0f || viewportHeight <= 1.0f || outProjectedPoint.ndcDepth < 0.0f) {
|
|
return false;
|
|
}
|
|
|
|
const float minX = edgePadding;
|
|
const float minY = edgePadding;
|
|
const float maxX = viewportWidth - edgePadding;
|
|
const float maxY = viewportHeight - edgePadding;
|
|
outProjectedPoint.screenPosition.x = std::clamp(outProjectedPoint.screenPosition.x, minX, maxX);
|
|
outProjectedPoint.screenPosition.y = std::clamp(outProjectedPoint.screenPosition.y, minY, maxY);
|
|
return true;
|
|
}
|
|
|
|
inline float DistanceToSegmentSquared(
|
|
const Math::Vector2& point,
|
|
const Math::Vector2& segmentStart,
|
|
const Math::Vector2& segmentEnd,
|
|
float* outSegmentT = nullptr) {
|
|
const Math::Vector2 segment = segmentEnd - segmentStart;
|
|
const float segmentLengthSq = segment.SqrMagnitude();
|
|
if (segmentLengthSq <= Math::EPSILON) {
|
|
if (outSegmentT != nullptr) {
|
|
*outSegmentT = 0.0f;
|
|
}
|
|
return (point - segmentStart).SqrMagnitude();
|
|
}
|
|
|
|
const float segmentT = std::clamp(
|
|
Math::Vector2::Dot(point - segmentStart, segment) / segmentLengthSq,
|
|
0.0f,
|
|
1.0f);
|
|
if (outSegmentT != nullptr) {
|
|
*outSegmentT = segmentT;
|
|
}
|
|
|
|
const Math::Vector2 closestPoint = segmentStart + segment * segmentT;
|
|
return (point - closestPoint).SqrMagnitude();
|
|
}
|
|
|
|
inline Math::Plane BuildSceneViewportPlaneFromPointNormal(
|
|
const Math::Vector3& point,
|
|
const Math::Vector3& normal) {
|
|
const Math::Vector3 planeNormal = normal.Normalized();
|
|
return Math::Plane(planeNormal, -Math::Vector3::Dot(planeNormal, point));
|
|
}
|
|
|
|
inline bool BuildSceneViewportAxisDragPlaneNormal(
|
|
const SceneViewportOverlayData& overlay,
|
|
const Math::Vector3& worldAxis,
|
|
Math::Vector3& outPlaneNormal) {
|
|
if (!overlay.valid || worldAxis.SqrMagnitude() <= Math::EPSILON) {
|
|
return false;
|
|
}
|
|
|
|
const Math::Vector3 axis = worldAxis.Normalized();
|
|
const Math::Vector3 candidates[] = {
|
|
Math::Vector3::ProjectOnPlane(overlay.cameraForward.Normalized(), axis),
|
|
Math::Vector3::ProjectOnPlane(overlay.cameraUp.Normalized(), axis),
|
|
Math::Vector3::ProjectOnPlane(overlay.cameraRight.Normalized(), axis),
|
|
Math::Vector3::ProjectOnPlane(Math::Vector3::Up(), axis),
|
|
Math::Vector3::ProjectOnPlane(Math::Vector3::Right(), axis),
|
|
Math::Vector3::ProjectOnPlane(Math::Vector3::Forward(), axis)
|
|
};
|
|
|
|
for (const Math::Vector3& candidate : candidates) {
|
|
if (candidate.SqrMagnitude() <= Math::EPSILON) {
|
|
continue;
|
|
}
|
|
|
|
outPlaneNormal = candidate.Normalized();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace Editor
|
|
} // namespace XCEngine
|