Skip to content

[Feature] Implement Blender-style global 3D viewport shortcuts (G/R/S)#313

Open
Szy-Cathay wants to merge 2 commits intodonkeyProgramming:masterfrom
Szy-Cathay:core/blender-like-operation-farmework
Open

[Feature] Implement Blender-style global 3D viewport shortcuts (G/R/S)#313
Szy-Cathay wants to merge 2 commits intodonkeyProgramming:masterfrom
Szy-Cathay:core/blender-like-operation-farmework

Conversation

@Szy-Cathay
Copy link
Contributor

@Szy-Cathay Szy-Cathay commented Mar 22, 2026

Summary

This PR introduces a true Blender-style Immediate-Action transform system to AssetEditor's 3D viewport. When the user presses G (translate), R (rotate), or S (scale), the object immediately enters transform mode—no mouse button needs to be held. Moving the mouse directly drives the transformation while the cursor is automatically hidden. The system supports X/Y/Z axis locking, Shift damping for fine‑tuning, left‑click to commit, right‑click/Esc to cancel, and full integration with the command stack for Ctrl+Z undo.

Core Changes

New Features

  • Immediate‑Action state machine: Press G/R/S to instantly enter transform state; mouse movement maps directly to screen‑space Delta.
  • Cursor hiding & infinite movement: Cursor is hidden during operation; infinite range is achieved by resetting SetCursorPosition.
  • Axis locking & damping fine‑tuning:
    • X/Y/Z keys lock the transformation to the corresponding axis (local/world coordinates).
    • Shift key enables damping mode, greatly reducing sensitivity for high‑precision adjustment.
  • Operation completion:
    • Left‑click commit: Submit the transformation and record it to the command system.
    • Right‑click/Esc cancel: Immediately cancel the operation and perfectly restore the model's original state.
  • Undo support: Committed transformations can be fully undone with Ctrl+Z.

Modified Files

File Change Type Description
GameWorld/View3D/Components/Gizmo/GizmoComponent.cs Core logic added Implements state machine, Delta calculation, axis locking, damping handling
GameWorld/View3D/Components/Gizmo/TransformGizmoWrapper.cs Enhanced Adds transformation application and cancel‑restore mechanism
GameWorld/View3D/Components/Input/MouseComponent.cs Enhanced Adds cursor hide/show, mouse ownership management
GameWorld/View3D/WpfWindow/Input/WpfMouse.cs Adapted Mouse‑control adaptation for WPF windows
Shared/SharedCore/Services/ActiveWindowProvider.cs New Service that determines the currently active window
Shared/SharedCore/Services/IActiveWindowProvider.cs New Interface for active‑window service
Shared/SharedCore/DependencyInjectionContainer.cs Modified Registers IActiveWindowProvider service
Shared/SharedUI/Common/MenuSystem/ActionHotkeyHandler.cs Modified Adds keyboard isolation to prevent hotkey conflicts across multiple windows
Testing/GameWorld.Core.Test/Components/Gizmo/GizmoComponentTests.cs New Unit tests covering G/R/S triggering, axis locking, cancellation, etc.
Testing/GameWorld.Core.Test/Components/Gizmo/TransformGizmoWrapperTests.cs New Unit tests covering properties, cancel‑restore, etc.
Testing/GameWorld.Core.Test/Input/MouseComponentTests.cs New Unit tests covering mouse‑button state detection

Design Decisions

  1. State machine sunk into underlying components: Strictly follows the "do not pollute ViewModel" architecture principle; all interaction logic is encapsulated in foundational classes like GizmoComponent, MouseComponent, etc.
  2. Immediate‑Action mode: Adopts Blender's "press‑and‑operate" pattern instead of the traditional tool‑switch mode, reducing operational steps and improving fluidity.
  3. Screen‑space Delta mapping: Uses the mouse's on‑screen movement directly, making rotation/translation more intuitive (similar to Blender's Trackball).
  4. Keyboard isolation solution: Uses IActiveWindowProvider service to isolate hotkeys in multi‑window environments, avoiding complex global hooks or reflection.

Issues & Fixes

Issue Fix
Right‑click could not cancel; instead triggered "commit" Removed _rightButtonWasPressed flag; right‑click detection now immediately cancels
Translation "drift" phenomenon Introduced _skipNextDelta flag to eliminate cumulative error caused by MouseState frame‑delay
Incorrect rotation axis (not screen‑space) Changed rotation axis to camera view‑matrix right (X) and up (Y) axes, achieving true screen‑space Trackball rotation
Severe frame‑rate drop Rolled back over‑complex state‑checking logic, simplified _skipNextDelta implementation
Hotkey conflicts across multiple windows Introduced IActiveWindowProvider service; hotkeys now respond only for the currently active window

Test Coverage

  • All unit tests pass: GizmoComponentTests, TransformGizmoWrapperTests, MouseComponentTests all pass (green check).
  • Functional verification: Real‑world testing via the KitbashEditor tool confirms all interactive behaviors match Blender‑style operation specifications.

Notes

  • No business‑layer modifications: This PR is purely an underlying framework upgrade; it does not include any modifications to KitbashEditor business logic (only provides test‑harness integration code).
  • Backward compatibility: The original Gizmo interaction method remains unchanged; the new Immediate‑Action mode is added as an enhancement.

Test Plan

  • All unit tests pass
  • Manual test of G/R/S Immediate‑Action in KitbashEditor
  • Verify axis‑locking and damping fine‑tuning
  • Confirm left‑click commit, right‑click/Esc cancel behavior is correct
  • Verify Ctrl+Z undo works correctly

Ready for submission – copy and paste the title and description above.

## Description

This PR implements Blender-style keyboard shortcuts for the 3D transformation gizmo system, enabling artists to manipulate objects using familiar hotkeys (G/R/S for Grab/Rotate/Scale, X/Y/Z for axis locking, Shift for precision damping, and Esc to cancel operations).

### Features Added:
- **G/R/S Mode Activation:** Press `G` for Grab (Translate), `R` for Rotate, or `S` for Scale to activate the corresponding gizmo mode.
- **X/Y/Z Axis Locking:** After activating a transform mode, press `X`, `Y`, or `Z` to constrain the transformation to that axis.
- **Shift Precision Mode:** Hold `Shift` while dragging to apply a damping factor (0.1x) for fine-tuned adjustments.
- **Esc Cancel:** Press `Escape` to cancel the current transform operation and restore the object to its original state.

---

## Technical Rationale (Why this approach?)

### 1. Adherence to Architectural Guidelines
As requested, the logic has been placed in the shared core classes rather than ViewModel:
- State Machine: Implemented in `GizmoComponent`, which acts as the coordinator for all gizmo-related logic.
- Keyboard Interception: Added to `GizmoComponent.Update()`, leveraging the existing `IKeyboardComponent` interface.
- Damping Factor: Implemented in `Gizmo` class as a mathematical concern, not a UI concern.

### 2. Minimal Invasiveness
The implementation is additive rather than modificative:
- No existing functionality was removed or altered.
- The traditional mouse-click-to-select-axis workflow remains intact.

### 3. State Machine Design
A lightweight state machine tracks keyboard-initiated transform operations:
`Idle → [G/R/S] → ModeActive → [X/Y/Z] → AxisLocked → [Mouse Drag] → Transforming → [Release/Esc] → Idle`
This design ensures clear state transitions, no state pollution, and robust cancellation support (restoring original transforms on Esc).

### 4. Damping Implementation Location
The damping factor is applied at the mathematical calculation layer (`Gizmo.HandleTranslateAndScale` and `Gizmo.HandleRotation`). Applying damping during calculation is more efficient than modifying event payloads and keeps the `TransformationEventArgs` signature unchanged.

### 5. Axis Locking via Existing Mechanisms
Axis locking uses the existing `ActiveAxis` property rather than introducing new filtering logic, ensuring automatic compatibility with Local/World space transformations without code duplication.

### 6. Unit Testing Omission (Hardware / Headless Environment Limitation)
*Please note that automated headless unit tests for the `GizmoComponent` state machine were deliberately omitted.*
While striving to add tests for all new features, `GizmoComponent.Initialize()` inherently creates a `BasicEffect` shader. This performs native assertions requiring a fully initialized, non-null `GraphicsDevice` (GPU context). In a headless NUnit CI pipeline, attempting to mock or bypass this results in underlying native pointer crashes. Thus, this component is fundamentally untestable without heavy graphics abstraction, falling under the "where applicable" exception for new tests.

---

## Files Modified
- `GameWorld.Core.Components.Gizmo/GizmoComponent.cs` - Added keyboard shortcut handling and state machine.
- `GameWorld.Core.Components.Gizmo/Gizmo.cs` - Added damping factor system for precision control.

---

## Manual Testing & Verification Notes
To compensate for the headless testing limitation, the feature has been thoroughly play-tested manually in-editor across:
- Object selection mode (multiple objects)
- Vertex selection mode (mesh editing)
- Bone selection mode (skeleton animation)
- All three transform modes (Translate, Rotate, Scale)
- Both Local and World space transformations
- Combinations of keyboard shortcuts with Shift precision mode
…ate Machine

## Summary
This PR introduces a **true Blender-style Immediate-Action transform system** to AssetEditor's 3D viewport. When the user presses `G` (translate), `R` (rotate), or `S` (scale), the object immediately enters transform mode—no mouse button needs to be held. Moving the mouse directly drives the transformation while the cursor is automatically hidden. The system supports `X`/`Y`/`Z` axis locking, `Shift` damping for fine‑tuning, left‑click to commit, right‑click/`Esc` to cancel, and full integration with the command stack for `Ctrl+Z` undo.

## Core Changes
### New Features
- **Immediate‑Action state machine**: Press G/R/S to instantly enter transform state; mouse movement maps directly to screen‑space Delta.
- **Cursor hiding & infinite movement**: Cursor is hidden during operation; infinite range is achieved by resetting `SetCursorPosition`.
- **Axis locking & damping fine‑tuning**:
  - `X`/`Y`/`Z` keys lock the transformation to the corresponding axis (local/world coordinates).
  - `Shift` key enables damping mode, greatly reducing sensitivity for high‑precision adjustment.
- **Operation completion**:
  - **Left‑click commit**: Submit the transformation and record it to the command system.
  - **Right‑click/`Esc` cancel**: Immediately cancel the operation and perfectly restore the model's original state.
- **Undo support**: Committed transformations can be fully undone with `Ctrl+Z`.

### Modified Files
| File | Change Type | Description |
|------|-------------|-------------|
| `GameWorld/View3D/Components/Gizmo/GizmoComponent.cs` | Core logic added | Implements state machine, Delta calculation, axis locking, damping handling |
| `GameWorld/View3D/Components/Gizmo/TransformGizmoWrapper.cs` | Enhanced | Adds transformation application and cancel‑restore mechanism |
| `GameWorld/View3D/Components/Input/MouseComponent.cs` | Enhanced | Adds cursor hide/show, mouse ownership management |
| `GameWorld/View3D/WpfWindow/Input/WpfMouse.cs` | Adapted | Mouse‑control adaptation for WPF windows |
| `Shared/SharedCore/Services/ActiveWindowProvider.cs` | New | Service that determines the currently active window |
| `Shared/SharedCore/Services/IActiveWindowProvider.cs` | New | Interface for active‑window service |
| `Shared/SharedCore/DependencyInjectionContainer.cs` | Modified | Registers `IActiveWindowProvider` service |
| `Shared/SharedUI/Common/MenuSystem/ActionHotkeyHandler.cs` | Modified | Adds keyboard isolation to prevent hotkey conflicts across multiple windows |
| `Testing/GameWorld.Core.Test/Components/Gizmo/GizmoComponentTests.cs` | New | Unit tests covering G/R/S triggering, axis locking, cancellation, etc. |
| `Testing/GameWorld.Core.Test/Components/Gizmo/TransformGizmoWrapperTests.cs` | New | Unit tests covering properties, cancel‑restore, etc. |
| `Testing/GameWorld.Core.Test/Input/MouseComponentTests.cs` | New | Unit tests covering mouse‑button state detection |

### Design Decisions
1. **State machine sunk into underlying components**: Strictly follows the "do not pollute ViewModel" architecture principle; all interaction logic is encapsulated in foundational classes like `GizmoComponent`, `MouseComponent`, etc.
2. **Immediate‑Action mode**: Adopts Blender's "press‑and‑operate" pattern instead of the traditional tool‑switch mode, reducing operational steps and improving fluidity.
3. **Screen‑space Delta mapping**: Uses the mouse's on‑screen movement directly, making rotation/translation more intuitive (similar to Blender's Trackball).
4. **Keyboard isolation solution**: Uses `IActiveWindowProvider` service to isolate hotkeys in multi‑window environments, avoiding complex global hooks or reflection.

### Issues & Fixes
| Issue | Fix |
|-------|-----|
| Right‑click could not cancel; instead triggered "commit" | Removed `_rightButtonWasPressed` flag; right‑click detection now immediately cancels |
| Translation "drift" phenomenon | Introduced `_skipNextDelta` flag to eliminate cumulative error caused by MouseState frame‑delay |
| Incorrect rotation axis (not screen‑space) | Changed rotation axis to camera view‑matrix right (X) and up (Y) axes, achieving true screen‑space Trackball rotation |
| Severe frame‑rate drop | Rolled back over‑complex state‑checking logic, simplified `_skipNextDelta` implementation |
| Hotkey conflicts across multiple windows | Introduced `IActiveWindowProvider` service; hotkeys now respond only for the currently active window |

### Test Coverage
- **All unit tests pass**: GizmoComponentTests, TransformGizmoWrapperTests, MouseComponentTests all pass (green check).
- **Functional verification**: Real‑world testing via the KitbashEditor tool confirms all interactive behaviors match Blender‑style operation specifications.

### Notes
- **No business‑layer modifications**: This PR is purely an underlying framework upgrade; it does not include any modifications to KitbashEditor business logic (only provides test‑harness integration code).
- **Backward compatibility**: The original Gizmo interaction method remains unchanged; the new Immediate‑Action mode is added as an enhancement.

---

**Test Plan**
- [x] All unit tests pass
- [x] Manual test of G/R/S Immediate‑Action in KitbashEditor
- [x] Verify axis‑locking and damping fine‑tuning
- [x] Confirm left‑click commit, right‑click/`Esc` cancel behavior is correct
- [x] Verify `Ctrl+Z` undo works correctly

---

**Ready for submission – copy and paste the title and description above.**
@Szy-Cathay
Copy link
Contributor Author

Hi Ole,

If this PR looks good and you're ready to merge it, could you please:

  1. Drop the commit titled:
    [Feature] Implement Blender-style global 3D viewport shortcuts (G/R/S)

  2. Keep only the commit titled:
    feat(Gizmo): Implement Blender-style Immediate-Action 3D Transform State Machine

When reviewing, please focus your checks entirely on the second commit (the feat(Gizmo):… one). The first commit can be safely ignored—it was an early partial implementation that's fully superseded by the second.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant