Formalize scene viewport interaction resolver
This commit is contained in:
@@ -83,6 +83,7 @@ add_executable(${PROJECT_NAME} WIN32
|
||||
src/Viewport/SceneViewportRotateGizmo.cpp
|
||||
src/Viewport/SceneViewportScaleGizmo.cpp
|
||||
src/Viewport/SceneViewportHudOverlay.cpp
|
||||
src/Viewport/SceneViewportInteractionResolver.cpp
|
||||
src/Viewport/SceneViewportOrientationGizmo.cpp
|
||||
src/Viewport/SceneViewportOverlayBuilder.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 "Viewport/SceneViewportEditorOverlayData.h"
|
||||
#include "Viewport/SceneViewportHudOverlay.h"
|
||||
#include "Viewport/SceneViewportInteractionResolver.h"
|
||||
#include "Viewport/SceneViewportOverlayHandleBuilder.h"
|
||||
#include "Viewport/SceneViewportOverlayHitTester.h"
|
||||
#include "Viewport/SceneViewportMath.h"
|
||||
#include "Viewport/SceneViewportTransformGizmoFrameBuilder.h"
|
||||
#include "ViewportPanelContent.h"
|
||||
@@ -30,33 +30,6 @@ struct SceneViewportToolOverlayResult {
|
||||
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) {
|
||||
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) {
|
||||
constexpr float kHorizontalPadding = 10.0f;
|
||||
constexpr float kMinWidth = 68.0f;
|
||||
@@ -539,13 +404,6 @@ void SceneViewPanel::Render() {
|
||||
hasInteractiveViewport
|
||||
? viewportHostService->GetSceneViewEditorOverlayFrameData(*m_context)
|
||||
: 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 rotateGizmoActive = showingRotateGizmo && m_rotateGizmo.IsActive();
|
||||
const bool scaleGizmoActive = showingScaleGizmo && m_scaleGizmo.IsActive();
|
||||
@@ -561,7 +419,7 @@ void SceneViewPanel::Render() {
|
||||
const bool gizmoActive = activeGizmoKind != SceneViewportActiveGizmoKind::None;
|
||||
const SceneViewportHudOverlayData interactionHudOverlay =
|
||||
BuildSceneViewportHudOverlayData(overlay);
|
||||
SceneViewportInteractionCandidate hoveredInteraction = {};
|
||||
SceneViewportInteractionResult hoveredInteraction = {};
|
||||
const bool canResolveViewportInteraction =
|
||||
hasInteractiveViewport &&
|
||||
viewportContentHovered &&
|
||||
@@ -571,18 +429,15 @@ void SceneViewPanel::Render() {
|
||||
!toolOverlay.hovered &&
|
||||
!gizmoActive;
|
||||
if (canResolveViewportInteraction) {
|
||||
AccumulateSceneViewportInteractionCandidate(
|
||||
BuildOverlayHandleInteractionCandidate(overlayHandleHit),
|
||||
hoveredInteraction);
|
||||
|
||||
AccumulateSceneViewportInteractionCandidate(
|
||||
BuildHudOverlayInteractionCandidate(
|
||||
HitTestSceneViewportHudOverlay(
|
||||
interactionHudOverlay,
|
||||
content.itemMin,
|
||||
content.itemMax,
|
||||
io.MousePos)),
|
||||
hoveredInteraction);
|
||||
SceneViewportInteractionResolveRequest interactionRequest = {};
|
||||
interactionRequest.overlayFrameData = &interactionOverlayFrameData;
|
||||
interactionRequest.viewportSize = viewportSize;
|
||||
interactionRequest.localMousePosition = localMousePosition;
|
||||
interactionRequest.hudOverlay = &interactionHudOverlay;
|
||||
interactionRequest.viewportMin = content.itemMin;
|
||||
interactionRequest.viewportMax = content.itemMax;
|
||||
interactionRequest.absoluteMousePosition = io.MousePos;
|
||||
hoveredInteraction = ResolveSceneViewportInteraction(interactionRequest);
|
||||
}
|
||||
|
||||
if (!gizmoActive) {
|
||||
|
||||
Reference in New Issue
Block a user