2026-04-27 19:16:08 +08:00
|
|
|
#include "Scene/SceneViewportSceneOverlay.h"
|
2026-04-21 00:57:14 +08:00
|
|
|
|
2026-04-27 19:16:08 +08:00
|
|
|
#include "Scene/SceneViewportTransformGizmoSupport.h"
|
2026-04-28 02:57:49 +08:00
|
|
|
#include "Assets/EditorIconService.h"
|
2026-04-28 03:22:36 +08:00
|
|
|
#include "Scene/EditorSceneRuntime.h"
|
2026-04-21 00:57:14 +08:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
using ::XCEngine::Math::Vector2;
|
|
|
|
|
using ::XCEngine::UI::UIPoint;
|
|
|
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
|
namespace SceneViewportGizmoSupport = ::XCEngine::UI::Editor::App::SceneViewportGizmoSupport;
|
|
|
|
|
|
|
|
|
|
constexpr Vector2 kCameraIconSize(90.0f, 90.0f);
|
|
|
|
|
constexpr Vector2 kLightIconSize(100.0f, 100.0f);
|
|
|
|
|
|
|
|
|
|
bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
|
|
|
|
|
return point.x >= rect.x &&
|
|
|
|
|
point.x <= rect.x + rect.width &&
|
|
|
|
|
point.y >= rect.y &&
|
|
|
|
|
point.y <= rect.y + rect.height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SceneViewportGizmoSupport::SceneViewportOverlayData BuildOverlayData(
|
|
|
|
|
const EditorSceneRuntime& sceneRuntime) {
|
|
|
|
|
SceneViewportGizmoSupport::SceneViewportOverlayData overlay = {};
|
2026-04-29 01:24:21 +08:00
|
|
|
const EditorSceneCameraSnapshot snapshot =
|
|
|
|
|
sceneRuntime.BuildSceneViewCameraSnapshot();
|
|
|
|
|
if (!snapshot.valid) {
|
2026-04-21 00:57:14 +08:00
|
|
|
return overlay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
overlay.valid = true;
|
2026-04-29 01:24:21 +08:00
|
|
|
overlay.cameraPosition = snapshot.position;
|
|
|
|
|
overlay.cameraForward = snapshot.forward;
|
|
|
|
|
overlay.cameraRight = snapshot.right;
|
|
|
|
|
overlay.cameraUp = snapshot.up;
|
|
|
|
|
overlay.verticalFovDegrees = snapshot.verticalFovDegrees;
|
|
|
|
|
overlay.nearClipPlane = snapshot.nearClipPlane;
|
|
|
|
|
overlay.farClipPlane = snapshot.farClipPlane;
|
|
|
|
|
overlay.orbitDistance = snapshot.orbitDistance;
|
2026-04-21 00:57:14 +08:00
|
|
|
return overlay;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 01:24:21 +08:00
|
|
|
::XCEngine::UI::UITextureHandle ResolveViewportIcon(
|
2026-04-28 02:57:49 +08:00
|
|
|
const EditorIconService* icons,
|
2026-04-29 01:24:21 +08:00
|
|
|
EditorSceneViewportIconKind kind) {
|
2026-04-21 00:57:14 +08:00
|
|
|
if (icons == nullptr) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 01:24:21 +08:00
|
|
|
switch (kind) {
|
|
|
|
|
case EditorSceneViewportIconKind::Camera:
|
|
|
|
|
return icons->Resolve(BuiltInIconKind::CameraGizmo);
|
|
|
|
|
case EditorSceneViewportIconKind::DirectionalLight:
|
2026-04-21 00:57:14 +08:00
|
|
|
return icons->Resolve(BuiltInIconKind::DirectionalLightGizmo);
|
2026-04-29 01:24:21 +08:00
|
|
|
case EditorSceneViewportIconKind::PointLight:
|
2026-04-21 00:57:14 +08:00
|
|
|
return icons->Resolve(BuiltInIconKind::PointLightGizmo);
|
2026-04-29 01:24:21 +08:00
|
|
|
case EditorSceneViewportIconKind::SpotLight:
|
2026-04-21 00:57:14 +08:00
|
|
|
return icons->Resolve(BuiltInIconKind::SpotLightGizmo);
|
|
|
|
|
default:
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 01:24:21 +08:00
|
|
|
Vector2 ResolveViewportIconSize(EditorSceneViewportIconKind kind) {
|
|
|
|
|
return kind == EditorSceneViewportIconKind::Camera
|
|
|
|
|
? kCameraIconSize
|
|
|
|
|
: kLightIconSize;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 00:57:14 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
2026-04-28 02:57:49 +08:00
|
|
|
void SceneViewportSceneOverlay::SetIconService(const EditorIconService* icons) {
|
2026-04-21 00:57:14 +08:00
|
|
|
m_icons = icons;
|
|
|
|
|
if (m_icons == nullptr) {
|
|
|
|
|
ResetFrame();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SceneViewportSceneOverlay::ResetFrame() {
|
|
|
|
|
m_frame = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SceneViewportSceneOverlay::Refresh(
|
|
|
|
|
EditorSceneRuntime& sceneRuntime,
|
|
|
|
|
const UIRect& viewportRect) {
|
|
|
|
|
m_frame = {};
|
|
|
|
|
m_frame.clipRect = viewportRect;
|
|
|
|
|
|
|
|
|
|
if (m_icons == nullptr ||
|
|
|
|
|
viewportRect.width <= 1.0f ||
|
|
|
|
|
viewportRect.height <= 1.0f) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SceneViewportGizmoSupport::SceneViewportOverlayData overlay =
|
|
|
|
|
BuildOverlayData(sceneRuntime);
|
|
|
|
|
if (!overlay.valid) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<IconFrame> icons = {};
|
|
|
|
|
const auto tryAppendIcon =
|
2026-04-29 01:24:21 +08:00
|
|
|
[&](const EditorSceneViewportIconSnapshot& snapshot) {
|
|
|
|
|
const ::XCEngine::UI::UITextureHandle texture =
|
|
|
|
|
ResolveViewportIcon(m_icons, snapshot.kind);
|
2026-04-21 00:57:14 +08:00
|
|
|
if (!texture.IsValid()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 01:24:21 +08:00
|
|
|
const Vector2 iconSize = ResolveViewportIconSize(snapshot.kind);
|
2026-04-21 00:57:14 +08:00
|
|
|
const SceneViewportGizmoSupport::SceneViewportProjectedPoint projectedPoint =
|
|
|
|
|
SceneViewportGizmoSupport::ProjectSceneViewportWorldPoint(
|
|
|
|
|
overlay,
|
|
|
|
|
viewportRect.width,
|
|
|
|
|
viewportRect.height,
|
2026-04-29 01:24:21 +08:00
|
|
|
snapshot.worldPosition);
|
2026-04-21 00:57:14 +08:00
|
|
|
if (!projectedPoint.visible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IconFrame icon = {};
|
2026-04-29 01:24:21 +08:00
|
|
|
icon.entityId = snapshot.entityId;
|
2026-04-21 00:57:14 +08:00
|
|
|
icon.rect = UIRect(
|
|
|
|
|
viewportRect.x + projectedPoint.screenPosition.x - iconSize.x * 0.5f,
|
|
|
|
|
viewportRect.y + projectedPoint.screenPosition.y - iconSize.y * 0.5f,
|
|
|
|
|
iconSize.x,
|
|
|
|
|
iconSize.y);
|
|
|
|
|
icon.texture = texture;
|
|
|
|
|
icon.sortDepth = projectedPoint.ndcDepth;
|
|
|
|
|
icons.push_back(std::move(icon));
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-29 01:24:21 +08:00
|
|
|
for (const EditorSceneViewportIconSnapshot& snapshot :
|
|
|
|
|
sceneRuntime.BuildSceneViewportIconSnapshots()) {
|
|
|
|
|
if (!snapshot.IsValid()) {
|
2026-04-21 00:57:14 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 01:24:21 +08:00
|
|
|
tryAppendIcon(snapshot);
|
2026-04-21 00:57:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (icons.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::sort(
|
|
|
|
|
icons.begin(),
|
|
|
|
|
icons.end(),
|
|
|
|
|
[](const IconFrame& lhs, const IconFrame& rhs) {
|
|
|
|
|
return lhs.sortDepth > rhs.sortDepth;
|
|
|
|
|
});
|
|
|
|
|
m_frame.visible = true;
|
|
|
|
|
m_frame.icons = std::move(icons);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SceneViewportSceneOverlay::HitResult SceneViewportSceneOverlay::HitTest(
|
|
|
|
|
const UIPoint& point) const {
|
|
|
|
|
HitResult result = {};
|
|
|
|
|
if (!m_frame.visible) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr float kMetricEpsilon = 0.001f;
|
|
|
|
|
for (const IconFrame& icon : m_frame.icons) {
|
|
|
|
|
if (icon.entityId == 0 ||
|
|
|
|
|
!icon.texture.IsValid() ||
|
|
|
|
|
!ContainsPoint(icon.rect, point)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!result.HasHit() ||
|
|
|
|
|
icon.sortDepth + kMetricEpsilon < result.sortDepth) {
|
|
|
|
|
result.entityId = icon.entityId;
|
|
|
|
|
result.sortDepth = icon.sortDepth;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SceneViewportSceneOverlay::Append(::XCEngine::UI::UIDrawList& drawList) const {
|
|
|
|
|
if (!m_frame.visible || m_frame.icons.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drawList.PushClipRect(m_frame.clipRect);
|
|
|
|
|
for (const IconFrame& icon : m_frame.icons) {
|
|
|
|
|
drawList.AddImage(icon.rect, icon.texture);
|
|
|
|
|
}
|
|
|
|
|
drawList.PopClipRect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|