Formalize scene viewport interaction resolver
This commit is contained in:
@@ -1,5 +1,22 @@
|
|||||||
# SceneViewport Overlay/Gizmo Rework Checkpoint
|
# SceneViewport Overlay/Gizmo Rework Checkpoint
|
||||||
|
|
||||||
|
## Update 2026-04-03 Phase 5A
|
||||||
|
|
||||||
|
### Interaction Resolver Completed
|
||||||
|
|
||||||
|
- Added `SceneViewportInteractionResolver.{h,cpp}` as the formal viewport-side interaction arbitration module.
|
||||||
|
- `SceneViewPanel` no longer owns overlay handle priority rules or HUD/world interaction winner selection.
|
||||||
|
- Overlay handle hit testing and HUD hit testing are now composed behind one resolver entry point.
|
||||||
|
- The panel now consumes a resolved interaction result instead of stitching together multiple hit systems inline.
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false`
|
||||||
|
- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportInteractionResolverTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*`
|
||||||
|
- `cmake --build build --config Debug --target XCEditor`
|
||||||
|
|
||||||
|
All commands completed successfully in `Debug`.
|
||||||
|
|
||||||
## Update 2026-04-03
|
## Update 2026-04-03
|
||||||
|
|
||||||
### Phase 4 Completed
|
### Phase 4 Completed
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ add_executable(${PROJECT_NAME} WIN32
|
|||||||
src/Viewport/SceneViewportRotateGizmo.cpp
|
src/Viewport/SceneViewportRotateGizmo.cpp
|
||||||
src/Viewport/SceneViewportScaleGizmo.cpp
|
src/Viewport/SceneViewportScaleGizmo.cpp
|
||||||
src/Viewport/SceneViewportHudOverlay.cpp
|
src/Viewport/SceneViewportHudOverlay.cpp
|
||||||
|
src/Viewport/SceneViewportInteractionResolver.cpp
|
||||||
src/Viewport/SceneViewportOrientationGizmo.cpp
|
src/Viewport/SceneViewportOrientationGizmo.cpp
|
||||||
src/Viewport/SceneViewportOverlayBuilder.cpp
|
src/Viewport/SceneViewportOverlayBuilder.cpp
|
||||||
src/Viewport/SceneViewportOverlayProviders.cpp
|
src/Viewport/SceneViewportOverlayProviders.cpp
|
||||||
|
|||||||
167
editor/src/Viewport/SceneViewportInteractionResolver.cpp
Normal file
167
editor/src/Viewport/SceneViewportInteractionResolver.cpp
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#include "SceneViewportInteractionResolver.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct SceneViewportInteractionCandidate {
|
||||||
|
SceneViewportInteractionResult interaction = {};
|
||||||
|
int priority = 0;
|
||||||
|
int secondaryPriority = 0;
|
||||||
|
float distanceSq = Math::FLOAT_MAX;
|
||||||
|
float depth = Math::FLOAT_MAX;
|
||||||
|
|
||||||
|
bool HasHit() const {
|
||||||
|
return interaction.HasHit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsBetterSceneViewportInteractionCandidate(
|
||||||
|
const SceneViewportInteractionCandidate& candidate,
|
||||||
|
const SceneViewportInteractionCandidate& current) {
|
||||||
|
constexpr float kMetricEpsilon = 0.001f;
|
||||||
|
|
||||||
|
if (!candidate.HasHit()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current.HasHit()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.priority != current.priority) {
|
||||||
|
return candidate.priority > current.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.distanceSq + kMetricEpsilon < current.distanceSq) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (current.distanceSq + kMetricEpsilon < candidate.distanceSq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.depth + kMetricEpsilon < current.depth) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (current.depth + kMetricEpsilon < candidate.depth) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate.secondaryPriority > current.secondaryPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccumulateSceneViewportInteractionCandidate(
|
||||||
|
const SceneViewportInteractionCandidate& candidate,
|
||||||
|
SceneViewportInteractionCandidate& bestCandidate) {
|
||||||
|
if (IsBetterSceneViewportInteractionCandidate(candidate, bestCandidate)) {
|
||||||
|
bestCandidate = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportInteractionCandidate BuildHudOverlayInteractionCandidate(
|
||||||
|
const SceneViewportHudOverlayHitResult& hitResult) {
|
||||||
|
SceneViewportInteractionCandidate candidate = {};
|
||||||
|
switch (hitResult.kind) {
|
||||||
|
case SceneViewportHudOverlayHitKind::OrientationAxis:
|
||||||
|
if (hitResult.orientationAxis == SceneViewportOrientationAxis::None) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
candidate.interaction.kind = SceneViewportInteractionKind::OrientationGizmo;
|
||||||
|
candidate.interaction.orientationAxis = hitResult.orientationAxis;
|
||||||
|
candidate.priority = 200;
|
||||||
|
candidate.distanceSq = 0.0f;
|
||||||
|
candidate.depth = 0.0f;
|
||||||
|
return candidate;
|
||||||
|
case SceneViewportHudOverlayHitKind::None:
|
||||||
|
default:
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportInteractionCandidate BuildOverlayHandleInteractionCandidate(
|
||||||
|
const SceneViewportOverlayHandleHitResult& hitResult) {
|
||||||
|
SceneViewportInteractionCandidate candidate = {};
|
||||||
|
if (!hitResult.HasHit()) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate.priority = hitResult.priority;
|
||||||
|
candidate.distanceSq = hitResult.distanceSq;
|
||||||
|
candidate.depth = hitResult.depth;
|
||||||
|
candidate.interaction.entityId = hitResult.entityId;
|
||||||
|
switch (hitResult.kind) {
|
||||||
|
case SceneViewportOverlayHandleKind::SceneIcon:
|
||||||
|
candidate.interaction.kind = SceneViewportInteractionKind::SceneIcon;
|
||||||
|
return candidate;
|
||||||
|
case SceneViewportOverlayHandleKind::MoveAxis:
|
||||||
|
candidate.interaction.kind = SceneViewportInteractionKind::MoveGizmo;
|
||||||
|
candidate.interaction.moveAxis = static_cast<SceneViewportGizmoAxis>(hitResult.handleId);
|
||||||
|
return candidate;
|
||||||
|
case SceneViewportOverlayHandleKind::MovePlane:
|
||||||
|
candidate.interaction.kind = SceneViewportInteractionKind::MoveGizmo;
|
||||||
|
candidate.interaction.movePlane = static_cast<SceneViewportGizmoPlane>(hitResult.handleId);
|
||||||
|
return candidate;
|
||||||
|
case SceneViewportOverlayHandleKind::RotateAxis:
|
||||||
|
candidate.interaction.kind = SceneViewportInteractionKind::RotateGizmo;
|
||||||
|
candidate.interaction.rotateAxis = static_cast<SceneViewportRotateGizmoAxis>(hitResult.handleId);
|
||||||
|
return candidate;
|
||||||
|
case SceneViewportOverlayHandleKind::ScaleAxis:
|
||||||
|
case SceneViewportOverlayHandleKind::ScaleUniform:
|
||||||
|
candidate.interaction.kind = SceneViewportInteractionKind::ScaleGizmo;
|
||||||
|
candidate.interaction.scaleHandle = static_cast<SceneViewportScaleGizmoHandle>(hitResult.handleId);
|
||||||
|
return candidate;
|
||||||
|
case SceneViewportOverlayHandleKind::None:
|
||||||
|
default:
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportOverlayHandleHitResult HitTestSceneViewportOverlayInteractionHandles(
|
||||||
|
const SceneViewportInteractionResolveRequest& request) {
|
||||||
|
if (request.overlayFrameData == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return HitTestSceneViewportOverlayHandles(
|
||||||
|
*request.overlayFrameData,
|
||||||
|
request.viewportSize,
|
||||||
|
request.localMousePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportHudOverlayHitResult HitTestSceneViewportHudInteraction(
|
||||||
|
const SceneViewportInteractionResolveRequest& request) {
|
||||||
|
if (request.hudOverlay == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return HitTestSceneViewportHudOverlay(
|
||||||
|
*request.hudOverlay,
|
||||||
|
request.viewportMin,
|
||||||
|
request.viewportMax,
|
||||||
|
request.absoluteMousePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SceneViewportInteractionResult ResolveSceneViewportInteraction(
|
||||||
|
const SceneViewportOverlayHandleHitResult& overlayHandleHit,
|
||||||
|
const SceneViewportHudOverlayHitResult& hudOverlayHit) {
|
||||||
|
SceneViewportInteractionCandidate bestCandidate = {};
|
||||||
|
AccumulateSceneViewportInteractionCandidate(
|
||||||
|
BuildOverlayHandleInteractionCandidate(overlayHandleHit),
|
||||||
|
bestCandidate);
|
||||||
|
AccumulateSceneViewportInteractionCandidate(
|
||||||
|
BuildHudOverlayInteractionCandidate(hudOverlayHit),
|
||||||
|
bestCandidate);
|
||||||
|
return bestCandidate.interaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportInteractionResult ResolveSceneViewportInteraction(
|
||||||
|
const SceneViewportInteractionResolveRequest& request) {
|
||||||
|
return ResolveSceneViewportInteraction(
|
||||||
|
HitTestSceneViewportOverlayInteractionHandles(request),
|
||||||
|
HitTestSceneViewportHudInteraction(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
57
editor/src/Viewport/SceneViewportInteractionResolver.h
Normal file
57
editor/src/Viewport/SceneViewportInteractionResolver.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SceneViewportHudOverlay.h"
|
||||||
|
#include "SceneViewportMoveGizmo.h"
|
||||||
|
#include "SceneViewportOverlayHitTester.h"
|
||||||
|
#include "SceneViewportRotateGizmo.h"
|
||||||
|
#include "SceneViewportScaleGizmo.h"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
enum class SceneViewportInteractionKind : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
MoveGizmo,
|
||||||
|
RotateGizmo,
|
||||||
|
ScaleGizmo,
|
||||||
|
OrientationGizmo,
|
||||||
|
SceneIcon
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SceneViewportInteractionResult {
|
||||||
|
SceneViewportInteractionKind kind = SceneViewportInteractionKind::None;
|
||||||
|
uint64_t entityId = 0;
|
||||||
|
SceneViewportGizmoAxis moveAxis = SceneViewportGizmoAxis::None;
|
||||||
|
SceneViewportGizmoPlane movePlane = SceneViewportGizmoPlane::None;
|
||||||
|
SceneViewportRotateGizmoAxis rotateAxis = SceneViewportRotateGizmoAxis::None;
|
||||||
|
SceneViewportScaleGizmoHandle scaleHandle = SceneViewportScaleGizmoHandle::None;
|
||||||
|
SceneViewportOrientationAxis orientationAxis = SceneViewportOrientationAxis::None;
|
||||||
|
|
||||||
|
bool HasHit() const {
|
||||||
|
return kind != SceneViewportInteractionKind::None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SceneViewportInteractionResolveRequest {
|
||||||
|
const SceneViewportOverlayFrameData* overlayFrameData = nullptr;
|
||||||
|
Math::Vector2 viewportSize = Math::Vector2::Zero();
|
||||||
|
Math::Vector2 localMousePosition = Math::Vector2::Zero();
|
||||||
|
const SceneViewportHudOverlayData* hudOverlay = nullptr;
|
||||||
|
ImVec2 viewportMin = ImVec2(0.0f, 0.0f);
|
||||||
|
ImVec2 viewportMax = ImVec2(0.0f, 0.0f);
|
||||||
|
ImVec2 absoluteMousePosition = ImVec2(0.0f, 0.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
SceneViewportInteractionResult ResolveSceneViewportInteraction(
|
||||||
|
const SceneViewportOverlayHandleHitResult& overlayHandleHit,
|
||||||
|
const SceneViewportHudOverlayHitResult& hudOverlayHit);
|
||||||
|
|
||||||
|
SceneViewportInteractionResult ResolveSceneViewportInteraction(
|
||||||
|
const SceneViewportInteractionResolveRequest& request);
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
#include "SceneViewPanel.h"
|
#include "SceneViewPanel.h"
|
||||||
#include "Viewport/SceneViewportEditorOverlayData.h"
|
#include "Viewport/SceneViewportEditorOverlayData.h"
|
||||||
#include "Viewport/SceneViewportHudOverlay.h"
|
#include "Viewport/SceneViewportHudOverlay.h"
|
||||||
|
#include "Viewport/SceneViewportInteractionResolver.h"
|
||||||
#include "Viewport/SceneViewportOverlayHandleBuilder.h"
|
#include "Viewport/SceneViewportOverlayHandleBuilder.h"
|
||||||
#include "Viewport/SceneViewportOverlayHitTester.h"
|
|
||||||
#include "Viewport/SceneViewportMath.h"
|
#include "Viewport/SceneViewportMath.h"
|
||||||
#include "Viewport/SceneViewportTransformGizmoFrameBuilder.h"
|
#include "Viewport/SceneViewportTransformGizmoFrameBuilder.h"
|
||||||
#include "ViewportPanelContent.h"
|
#include "ViewportPanelContent.h"
|
||||||
@@ -30,33 +30,6 @@ struct SceneViewportToolOverlayResult {
|
|||||||
SceneViewportToolMode clickedTool = SceneViewportToolMode::Move;
|
SceneViewportToolMode clickedTool = SceneViewportToolMode::Move;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SceneViewportInteractionKind : uint8_t {
|
|
||||||
None = 0,
|
|
||||||
MoveGizmo,
|
|
||||||
RotateGizmo,
|
|
||||||
ScaleGizmo,
|
|
||||||
OrientationGizmo,
|
|
||||||
SceneIcon
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SceneViewportInteractionCandidate {
|
|
||||||
SceneViewportInteractionKind kind = SceneViewportInteractionKind::None;
|
|
||||||
int priority = 0;
|
|
||||||
int secondaryPriority = 0;
|
|
||||||
float distanceSq = Math::FLOAT_MAX;
|
|
||||||
float depth = Math::FLOAT_MAX;
|
|
||||||
uint64_t entityId = 0;
|
|
||||||
SceneViewportGizmoAxis moveAxis = SceneViewportGizmoAxis::None;
|
|
||||||
SceneViewportGizmoPlane movePlane = SceneViewportGizmoPlane::None;
|
|
||||||
SceneViewportRotateGizmoAxis rotateAxis = SceneViewportRotateGizmoAxis::None;
|
|
||||||
SceneViewportScaleGizmoHandle scaleHandle = SceneViewportScaleGizmoHandle::None;
|
|
||||||
SceneViewportOrientationAxis orientationAxis = SceneViewportOrientationAxis::None;
|
|
||||||
|
|
||||||
bool HasHit() const {
|
|
||||||
return kind != SceneViewportInteractionKind::None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* GetSceneViewportPivotModeLabel(SceneViewportPivotMode mode) {
|
const char* GetSceneViewportPivotModeLabel(SceneViewportPivotMode mode) {
|
||||||
return mode == SceneViewportPivotMode::Pivot ? "Pivot" : "Center";
|
return mode == SceneViewportPivotMode::Pivot ? "Pivot" : "Center";
|
||||||
}
|
}
|
||||||
@@ -81,114 +54,6 @@ SceneViewportActiveGizmoKind ToActiveGizmoKind(SceneViewportInteractionKind kind
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsBetterSceneViewportInteractionCandidate(
|
|
||||||
const SceneViewportInteractionCandidate& candidate,
|
|
||||||
const SceneViewportInteractionCandidate& current) {
|
|
||||||
constexpr float kMetricEpsilon = 0.001f;
|
|
||||||
|
|
||||||
if (!candidate.HasHit()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!current.HasHit()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidate.priority != current.priority) {
|
|
||||||
return candidate.priority > current.priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidate.distanceSq + kMetricEpsilon < current.distanceSq) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (current.distanceSq + kMetricEpsilon < candidate.distanceSq) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidate.depth + kMetricEpsilon < current.depth) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (current.depth + kMetricEpsilon < candidate.depth) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidate.secondaryPriority > current.secondaryPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccumulateSceneViewportInteractionCandidate(
|
|
||||||
const SceneViewportInteractionCandidate& candidate,
|
|
||||||
SceneViewportInteractionCandidate& bestCandidate) {
|
|
||||||
if (IsBetterSceneViewportInteractionCandidate(candidate, bestCandidate)) {
|
|
||||||
bestCandidate = candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportInteractionCandidate BuildOrientationGizmoInteractionCandidate(
|
|
||||||
SceneViewportOrientationAxis axis) {
|
|
||||||
SceneViewportInteractionCandidate candidate = {};
|
|
||||||
if (axis == SceneViewportOrientationAxis::None) {
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate.kind = SceneViewportInteractionKind::OrientationGizmo;
|
|
||||||
candidate.priority = 200;
|
|
||||||
candidate.distanceSq = 0.0f;
|
|
||||||
candidate.depth = 0.0f;
|
|
||||||
candidate.orientationAxis = axis;
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportInteractionCandidate BuildHudOverlayInteractionCandidate(
|
|
||||||
const SceneViewportHudOverlayHitResult& hitResult) {
|
|
||||||
switch (hitResult.kind) {
|
|
||||||
case SceneViewportHudOverlayHitKind::OrientationAxis:
|
|
||||||
return BuildOrientationGizmoInteractionCandidate(hitResult.orientationAxis);
|
|
||||||
case SceneViewportHudOverlayHitKind::None:
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportInteractionCandidate BuildOverlayHandleInteractionCandidate(
|
|
||||||
const SceneViewportOverlayHandleHitResult& hitResult) {
|
|
||||||
SceneViewportInteractionCandidate candidate = {};
|
|
||||||
if (!hitResult.HasHit()) {
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate.priority = hitResult.priority;
|
|
||||||
candidate.distanceSq = hitResult.distanceSq;
|
|
||||||
candidate.depth = hitResult.depth;
|
|
||||||
candidate.entityId = hitResult.entityId;
|
|
||||||
switch (hitResult.kind) {
|
|
||||||
case SceneViewportOverlayHandleKind::SceneIcon:
|
|
||||||
candidate.kind = SceneViewportInteractionKind::SceneIcon;
|
|
||||||
return candidate;
|
|
||||||
case SceneViewportOverlayHandleKind::MoveAxis:
|
|
||||||
candidate.kind = SceneViewportInteractionKind::MoveGizmo;
|
|
||||||
candidate.moveAxis = static_cast<SceneViewportGizmoAxis>(hitResult.handleId);
|
|
||||||
return candidate;
|
|
||||||
case SceneViewportOverlayHandleKind::MovePlane:
|
|
||||||
candidate.kind = SceneViewportInteractionKind::MoveGizmo;
|
|
||||||
candidate.movePlane = static_cast<SceneViewportGizmoPlane>(hitResult.handleId);
|
|
||||||
return candidate;
|
|
||||||
case SceneViewportOverlayHandleKind::RotateAxis:
|
|
||||||
candidate.kind = SceneViewportInteractionKind::RotateGizmo;
|
|
||||||
candidate.rotateAxis = static_cast<SceneViewportRotateGizmoAxis>(hitResult.handleId);
|
|
||||||
return candidate;
|
|
||||||
case SceneViewportOverlayHandleKind::ScaleAxis:
|
|
||||||
case SceneViewportOverlayHandleKind::ScaleUniform:
|
|
||||||
candidate.kind = SceneViewportInteractionKind::ScaleGizmo;
|
|
||||||
candidate.scaleHandle = static_cast<SceneViewportScaleGizmoHandle>(hitResult.handleId);
|
|
||||||
return candidate;
|
|
||||||
case SceneViewportOverlayHandleKind::None:
|
|
||||||
default:
|
|
||||||
return SceneViewportInteractionCandidate{};
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
float GetSceneToolbarToggleWidth(const char* firstLabel, const char* secondLabel) {
|
float GetSceneToolbarToggleWidth(const char* firstLabel, const char* secondLabel) {
|
||||||
constexpr float kHorizontalPadding = 10.0f;
|
constexpr float kHorizontalPadding = 10.0f;
|
||||||
constexpr float kMinWidth = 68.0f;
|
constexpr float kMinWidth = 68.0f;
|
||||||
@@ -539,13 +404,6 @@ void SceneViewPanel::Render() {
|
|||||||
hasInteractiveViewport
|
hasInteractiveViewport
|
||||||
? viewportHostService->GetSceneViewEditorOverlayFrameData(*m_context)
|
? viewportHostService->GetSceneViewEditorOverlayFrameData(*m_context)
|
||||||
: emptySceneOverlayFrameData;
|
: emptySceneOverlayFrameData;
|
||||||
const SceneViewportOverlayHandleHitResult overlayHandleHit =
|
|
||||||
hasInteractiveViewport
|
|
||||||
? HitTestSceneViewportOverlayHandles(
|
|
||||||
interactionOverlayFrameData,
|
|
||||||
Math::Vector2(content.availableSize.x, content.availableSize.y),
|
|
||||||
localMousePosition)
|
|
||||||
: SceneViewportOverlayHandleHitResult{};
|
|
||||||
const bool moveGizmoActive = showingMoveGizmo && m_moveGizmo.IsActive();
|
const bool moveGizmoActive = showingMoveGizmo && m_moveGizmo.IsActive();
|
||||||
const bool rotateGizmoActive = showingRotateGizmo && m_rotateGizmo.IsActive();
|
const bool rotateGizmoActive = showingRotateGizmo && m_rotateGizmo.IsActive();
|
||||||
const bool scaleGizmoActive = showingScaleGizmo && m_scaleGizmo.IsActive();
|
const bool scaleGizmoActive = showingScaleGizmo && m_scaleGizmo.IsActive();
|
||||||
@@ -561,7 +419,7 @@ void SceneViewPanel::Render() {
|
|||||||
const bool gizmoActive = activeGizmoKind != SceneViewportActiveGizmoKind::None;
|
const bool gizmoActive = activeGizmoKind != SceneViewportActiveGizmoKind::None;
|
||||||
const SceneViewportHudOverlayData interactionHudOverlay =
|
const SceneViewportHudOverlayData interactionHudOverlay =
|
||||||
BuildSceneViewportHudOverlayData(overlay);
|
BuildSceneViewportHudOverlayData(overlay);
|
||||||
SceneViewportInteractionCandidate hoveredInteraction = {};
|
SceneViewportInteractionResult hoveredInteraction = {};
|
||||||
const bool canResolveViewportInteraction =
|
const bool canResolveViewportInteraction =
|
||||||
hasInteractiveViewport &&
|
hasInteractiveViewport &&
|
||||||
viewportContentHovered &&
|
viewportContentHovered &&
|
||||||
@@ -571,18 +429,15 @@ void SceneViewPanel::Render() {
|
|||||||
!toolOverlay.hovered &&
|
!toolOverlay.hovered &&
|
||||||
!gizmoActive;
|
!gizmoActive;
|
||||||
if (canResolveViewportInteraction) {
|
if (canResolveViewportInteraction) {
|
||||||
AccumulateSceneViewportInteractionCandidate(
|
SceneViewportInteractionResolveRequest interactionRequest = {};
|
||||||
BuildOverlayHandleInteractionCandidate(overlayHandleHit),
|
interactionRequest.overlayFrameData = &interactionOverlayFrameData;
|
||||||
hoveredInteraction);
|
interactionRequest.viewportSize = viewportSize;
|
||||||
|
interactionRequest.localMousePosition = localMousePosition;
|
||||||
AccumulateSceneViewportInteractionCandidate(
|
interactionRequest.hudOverlay = &interactionHudOverlay;
|
||||||
BuildHudOverlayInteractionCandidate(
|
interactionRequest.viewportMin = content.itemMin;
|
||||||
HitTestSceneViewportHudOverlay(
|
interactionRequest.viewportMax = content.itemMax;
|
||||||
interactionHudOverlay,
|
interactionRequest.absoluteMousePosition = io.MousePos;
|
||||||
content.itemMin,
|
hoveredInteraction = ResolveSceneViewportInteraction(interactionRequest);
|
||||||
content.itemMax,
|
|
||||||
io.MousePos)),
|
|
||||||
hoveredInteraction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gizmoActive) {
|
if (!gizmoActive) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ set(EDITOR_TEST_SOURCES
|
|||||||
test_scene_viewport_rotate_gizmo.cpp
|
test_scene_viewport_rotate_gizmo.cpp
|
||||||
test_scene_viewport_scale_gizmo.cpp
|
test_scene_viewport_scale_gizmo.cpp
|
||||||
test_scene_viewport_picker.cpp
|
test_scene_viewport_picker.cpp
|
||||||
|
test_scene_viewport_interaction_resolver.cpp
|
||||||
test_scene_viewport_shader_paths.cpp
|
test_scene_viewport_shader_paths.cpp
|
||||||
test_scene_viewport_overlay_renderer.cpp
|
test_scene_viewport_overlay_renderer.cpp
|
||||||
test_scene_viewport_overlay_providers.cpp
|
test_scene_viewport_overlay_providers.cpp
|
||||||
@@ -38,6 +39,7 @@ set(EDITOR_TEST_SOURCES
|
|||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportRotateGizmo.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportRotateGizmo.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportScaleGizmo.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportScaleGizmo.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportHudOverlay.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportHudOverlay.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportInteractionResolver.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOrientationGizmo.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOrientationGizmo.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayBuilder.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayBuilder.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayProviders.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayProviders.cpp
|
||||||
|
|||||||
90
tests/editor/test_scene_viewport_interaction_resolver.cpp
Normal file
90
tests/editor/test_scene_viewport_interaction_resolver.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "Viewport/SceneViewportInteractionResolver.h"
|
||||||
|
|
||||||
|
using XCEngine::Editor::ResolveSceneViewportInteraction;
|
||||||
|
using XCEngine::Editor::SceneViewportGizmoAxis;
|
||||||
|
using XCEngine::Editor::SceneViewportHudOverlayHitKind;
|
||||||
|
using XCEngine::Editor::SceneViewportHudOverlayHitResult;
|
||||||
|
using XCEngine::Editor::SceneViewportInteractionKind;
|
||||||
|
using XCEngine::Editor::SceneViewportInteractionResolveRequest;
|
||||||
|
using XCEngine::Editor::SceneViewportOrientationAxis;
|
||||||
|
using XCEngine::Editor::SceneViewportOverlayFrameData;
|
||||||
|
using XCEngine::Editor::SceneViewportOverlayHandleHitResult;
|
||||||
|
using XCEngine::Editor::SceneViewportOverlayHandleKind;
|
||||||
|
using XCEngine::Editor::SceneViewportOverlayHandleRecord;
|
||||||
|
using XCEngine::Editor::SceneViewportOverlayHandleShape;
|
||||||
|
using XCEngine::Math::Vector2;
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionResolverTest, ReturnsNoHitWhenBothSourcesAreEmpty) {
|
||||||
|
const auto interaction = ResolveSceneViewportInteraction(
|
||||||
|
SceneViewportOverlayHandleHitResult{},
|
||||||
|
SceneViewportHudOverlayHitResult{});
|
||||||
|
|
||||||
|
EXPECT_FALSE(interaction.HasHit());
|
||||||
|
EXPECT_EQ(interaction.kind, SceneViewportInteractionKind::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionResolverTest, PrefersTransformHandleOverHudOrientationByPriority) {
|
||||||
|
SceneViewportOverlayHandleHitResult overlayHandleHit = {};
|
||||||
|
overlayHandleHit.kind = SceneViewportOverlayHandleKind::MoveAxis;
|
||||||
|
overlayHandleHit.handleId = static_cast<uint64_t>(SceneViewportGizmoAxis::X);
|
||||||
|
overlayHandleHit.priority = 322;
|
||||||
|
overlayHandleHit.distanceSq = 4.0f;
|
||||||
|
overlayHandleHit.depth = 3.0f;
|
||||||
|
|
||||||
|
SceneViewportHudOverlayHitResult hudHit = {};
|
||||||
|
hudHit.kind = SceneViewportHudOverlayHitKind::OrientationAxis;
|
||||||
|
hudHit.orientationAxis = SceneViewportOrientationAxis::PositiveX;
|
||||||
|
|
||||||
|
const auto interaction = ResolveSceneViewportInteraction(overlayHandleHit, hudHit);
|
||||||
|
|
||||||
|
ASSERT_TRUE(interaction.HasHit());
|
||||||
|
EXPECT_EQ(interaction.kind, SceneViewportInteractionKind::MoveGizmo);
|
||||||
|
EXPECT_EQ(interaction.moveAxis, SceneViewportGizmoAxis::X);
|
||||||
|
EXPECT_EQ(interaction.orientationAxis, SceneViewportOrientationAxis::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionResolverTest, PrefersHudOrientationOverSceneIconByPriority) {
|
||||||
|
SceneViewportOverlayHandleHitResult overlayHandleHit = {};
|
||||||
|
overlayHandleHit.kind = SceneViewportOverlayHandleKind::SceneIcon;
|
||||||
|
overlayHandleHit.entityId = 99;
|
||||||
|
overlayHandleHit.priority = 100;
|
||||||
|
overlayHandleHit.distanceSq = 0.0f;
|
||||||
|
overlayHandleHit.depth = 0.0f;
|
||||||
|
|
||||||
|
SceneViewportHudOverlayHitResult hudHit = {};
|
||||||
|
hudHit.kind = SceneViewportHudOverlayHitKind::OrientationAxis;
|
||||||
|
hudHit.orientationAxis = SceneViewportOrientationAxis::PositiveY;
|
||||||
|
|
||||||
|
const auto interaction = ResolveSceneViewportInteraction(overlayHandleHit, hudHit);
|
||||||
|
|
||||||
|
ASSERT_TRUE(interaction.HasHit());
|
||||||
|
EXPECT_EQ(interaction.kind, SceneViewportInteractionKind::OrientationGizmo);
|
||||||
|
EXPECT_EQ(interaction.orientationAxis, SceneViewportOrientationAxis::PositiveY);
|
||||||
|
EXPECT_EQ(interaction.entityId, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionResolverTest, RequestPathDelegatesToOverlayHandleHitTesting) {
|
||||||
|
SceneViewportOverlayFrameData frameData = {};
|
||||||
|
frameData.overlay.valid = true;
|
||||||
|
|
||||||
|
SceneViewportOverlayHandleRecord& record = frameData.handleRecords.emplace_back();
|
||||||
|
record.kind = SceneViewportOverlayHandleKind::SceneIcon;
|
||||||
|
record.entityId = 77;
|
||||||
|
record.shape = SceneViewportOverlayHandleShape::ScreenRect;
|
||||||
|
record.priority = 100;
|
||||||
|
record.screenCenter = Vector2(80.0f, 60.0f);
|
||||||
|
record.screenHalfSize = Vector2(12.0f, 10.0f);
|
||||||
|
|
||||||
|
SceneViewportInteractionResolveRequest request = {};
|
||||||
|
request.overlayFrameData = &frameData;
|
||||||
|
request.viewportSize = Vector2(200.0f, 150.0f);
|
||||||
|
request.localMousePosition = Vector2(80.0f, 60.0f);
|
||||||
|
|
||||||
|
const auto interaction = ResolveSceneViewportInteraction(request);
|
||||||
|
|
||||||
|
ASSERT_TRUE(interaction.HasHit());
|
||||||
|
EXPECT_EQ(interaction.kind, SceneViewportInteractionKind::SceneIcon);
|
||||||
|
EXPECT_EQ(interaction.entityId, 77u);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user