Add Unity-style scene rotate gizmo

This commit is contained in:
2026-03-31 23:45:08 +08:00
parent 7ff5cd4cf2
commit ac2b7c1fa2
7 changed files with 1119 additions and 37 deletions

View File

@@ -37,6 +37,28 @@ SceneViewportMoveGizmoContext BuildMoveGizmoContext(
return gizmoContext;
}
SceneViewportRotateGizmoContext BuildRotateGizmoContext(
IEditorContext& context,
const SceneViewportOverlayData& overlay,
const ViewportPanelContentResult& content,
const ImVec2& mousePosition) {
SceneViewportRotateGizmoContext gizmoContext = {};
gizmoContext.overlay = overlay;
gizmoContext.viewportSize = Math::Vector2(content.availableSize.x, content.availableSize.y);
gizmoContext.mousePosition = Math::Vector2(
mousePosition.x - content.itemMin.x,
mousePosition.y - content.itemMin.y);
if (context.GetSelectionManager().GetSelectionCount() == 1) {
const uint64_t selectedEntity = context.GetSelectionManager().GetSelectedEntity();
if (selectedEntity != 0) {
gizmoContext.selectedObject = context.GetSceneManager().GetEntity(selectedEntity);
}
}
return gizmoContext;
}
} // namespace
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
@@ -53,36 +75,78 @@ void SceneViewPanel::Render() {
if (IViewportHostService* viewportHostService = m_context->GetViewportHostService()) {
const ImGuiIO& io = ImGui::GetIO();
const bool hasInteractiveViewport = content.hasViewportArea && content.frame.hasTexture;
if (content.focused && !io.WantTextInput && !m_lookDragging && !m_panDragging) {
if (ImGui::IsKeyPressed(ImGuiKey_W, false)) {
if (m_rotateGizmo.IsActive()) {
m_rotateGizmo.CancelDrag(&m_context->GetUndoManager());
}
m_transformTool = SceneViewportTransformTool::Move;
} else if (ImGui::IsKeyPressed(ImGuiKey_E, false)) {
if (m_moveGizmo.IsActive()) {
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
}
m_transformTool = SceneViewportTransformTool::Rotate;
}
}
const bool usingMoveGizmo = m_transformTool == SceneViewportTransformTool::Move;
SceneViewportOverlayData overlay = {};
SceneViewportMoveGizmoContext moveGizmoContext = {};
SceneViewportRotateGizmoContext rotateGizmoContext = {};
if (hasInteractiveViewport) {
overlay = viewportHostService->GetSceneViewOverlayData();
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
if (m_moveGizmo.IsActive() &&
(moveGizmoContext.selectedObject == nullptr ||
m_context->GetSelectionManager().GetSelectedEntity() != m_moveGizmo.GetActiveEntityId())) {
if (usingMoveGizmo) {
if (m_rotateGizmo.IsActive()) {
m_rotateGizmo.CancelDrag(&m_context->GetUndoManager());
}
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
if (m_moveGizmo.IsActive() &&
(moveGizmoContext.selectedObject == nullptr ||
m_context->GetSelectionManager().GetSelectedEntity() != m_moveGizmo.GetActiveEntityId())) {
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
}
m_moveGizmo.Update(moveGizmoContext);
} else {
if (m_moveGizmo.IsActive()) {
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
}
rotateGizmoContext = BuildRotateGizmoContext(*m_context, overlay, content, io.MousePos);
if (m_rotateGizmo.IsActive() &&
(rotateGizmoContext.selectedObject == nullptr ||
m_context->GetSelectionManager().GetSelectedEntity() != m_rotateGizmo.GetActiveEntityId())) {
m_rotateGizmo.CancelDrag(&m_context->GetUndoManager());
}
m_rotateGizmo.Update(rotateGizmoContext);
}
} else {
if (m_moveGizmo.IsActive()) {
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
}
m_moveGizmo.Update(moveGizmoContext);
} else if (m_moveGizmo.IsActive()) {
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
if (m_rotateGizmo.IsActive()) {
m_rotateGizmo.CancelDrag(&m_context->GetUndoManager());
}
}
const bool beginMoveGizmo =
const bool gizmoHovering = usingMoveGizmo ? m_moveGizmo.IsHoveringHandle() : m_rotateGizmo.IsHoveringHandle();
const bool gizmoActive = usingMoveGizmo ? m_moveGizmo.IsActive() : m_rotateGizmo.IsActive();
const bool beginTransformGizmo =
hasInteractiveViewport &&
content.hovered &&
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
content.clickedLeft &&
!m_lookDragging &&
!m_panDragging &&
m_moveGizmo.IsHoveringHandle();
gizmoHovering;
const SceneViewportOrientationAxis orientationAxisHit =
hasInteractiveViewport &&
content.hovered &&
!m_lookDragging &&
!m_panDragging &&
!m_moveGizmo.IsHoveringHandle() &&
!m_moveGizmo.IsActive()
!gizmoHovering &&
!gizmoActive
? HitTestSceneViewportOrientationGizmo(
overlay,
content.itemMin,
@@ -91,45 +155,52 @@ void SceneViewPanel::Render() {
: SceneViewportOrientationAxis::None;
const bool orientationGizmoClick =
hasInteractiveViewport &&
content.hovered &&
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
content.clickedLeft &&
orientationAxisHit != SceneViewportOrientationAxis::None;
const bool selectClick =
hasInteractiveViewport &&
content.hovered &&
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
content.clickedLeft &&
!m_lookDragging &&
!m_panDragging &&
!orientationGizmoClick &&
!m_moveGizmo.IsHoveringHandle() &&
!m_moveGizmo.IsActive();
!gizmoHovering &&
!gizmoActive;
const bool beginLookDrag =
hasInteractiveViewport &&
content.hovered &&
!m_lookDragging &&
!m_moveGizmo.IsActive() &&
!gizmoActive &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right);
const bool beginPanDrag =
hasInteractiveViewport &&
content.hovered &&
!m_panDragging &&
!m_moveGizmo.IsActive() &&
!gizmoActive &&
!m_lookDragging &&
ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
if (beginMoveGizmo || orientationGizmoClick || selectClick || beginLookDrag || beginPanDrag) {
if (beginTransformGizmo || orientationGizmoClick || selectClick || beginLookDrag || beginPanDrag) {
ImGui::SetWindowFocus();
}
if (beginMoveGizmo) {
m_moveGizmo.TryBeginDrag(moveGizmoContext, m_context->GetUndoManager());
if (beginTransformGizmo) {
if (usingMoveGizmo) {
m_moveGizmo.TryBeginDrag(moveGizmoContext, m_context->GetUndoManager());
} else {
m_rotateGizmo.TryBeginDrag(rotateGizmoContext, m_context->GetUndoManager());
}
}
if (orientationGizmoClick) {
viewportHostService->AlignSceneViewToOrientationAxis(orientationAxisHit);
overlay = viewportHostService->GetSceneViewOverlayData();
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
m_moveGizmo.Update(moveGizmoContext);
if (usingMoveGizmo) {
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
m_moveGizmo.Update(moveGizmoContext);
} else {
rotateGizmoContext = BuildRotateGizmoContext(*m_context, overlay, content, io.MousePos);
m_rotateGizmo.Update(rotateGizmoContext);
}
}
if (selectClick) {
@@ -147,11 +218,19 @@ void SceneViewPanel::Render() {
}
}
if (m_moveGizmo.IsActive()) {
if (gizmoActive) {
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
m_moveGizmo.UpdateDrag(moveGizmoContext);
if (usingMoveGizmo) {
m_moveGizmo.UpdateDrag(moveGizmoContext);
} else {
m_rotateGizmo.UpdateDrag(rotateGizmoContext);
}
} else {
m_moveGizmo.EndDrag(m_context->GetUndoManager());
if (usingMoveGizmo) {
m_moveGizmo.EndDrag(m_context->GetUndoManager());
} else {
m_rotateGizmo.EndDrag(m_context->GetUndoManager());
}
}
}
@@ -177,7 +256,7 @@ void SceneViewPanel::Render() {
m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
}
if (m_lookDragging || m_panDragging || m_moveGizmo.IsActive()) {
if (m_lookDragging || m_panDragging || m_moveGizmo.IsActive() || m_rotateGizmo.IsActive()) {
ImGui::SetNextFrameWantCaptureMouse(true);
}
if (m_lookDragging) {
@@ -222,8 +301,16 @@ void SceneViewPanel::Render() {
if (m_panDragging) {
const ImVec2 panDragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Middle, 0.0f);
input.mouseDelta.x += panDragDelta.x - m_lastPanDragDelta.x;
input.mouseDelta.y += panDragDelta.y - m_lastPanDragDelta.y;
ImVec2 framePanDelta(
panDragDelta.x - m_lastPanDragDelta.x,
panDragDelta.y - m_lastPanDragDelta.y);
// Some middle-button drags report a zero drag delta on the interaction surface.
if ((framePanDelta.x == 0.0f && framePanDelta.y == 0.0f) &&
(io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) {
framePanDelta = io.MouseDelta;
}
input.mouseDelta.x += framePanDelta.x;
input.mouseDelta.y += framePanDelta.y;
m_lastPanDragDelta = panDragDelta;
} else {
m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
@@ -234,15 +321,22 @@ void SceneViewPanel::Render() {
if (content.hasViewportArea && content.frame.hasTexture) {
overlay = viewportHostService->GetSceneViewOverlayData();
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
m_moveGizmo.Update(moveGizmoContext);
if (usingMoveGizmo) {
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
m_moveGizmo.Update(moveGizmoContext);
} else {
rotateGizmoContext = BuildRotateGizmoContext(*m_context, overlay, content, io.MousePos);
m_rotateGizmo.Update(rotateGizmoContext);
}
DrawSceneViewportOverlay(
ImGui::GetWindowDrawList(),
overlay,
content.itemMin,
content.itemMax,
content.availableSize,
&m_moveGizmo.GetDrawData());
usingMoveGizmo ? &m_moveGizmo.GetDrawData() : nullptr,
usingMoveGizmo ? nullptr : &m_rotateGizmo.GetDrawData());
}
}