[Feature] Implement Blender-style global 3D viewport shortcuts (G/R/S)#313
Open
Szy-Cathay wants to merge 2 commits intodonkeyProgramming:masterfrom
Open
[Feature] Implement Blender-style global 3D viewport shortcuts (G/R/S)#313Szy-Cathay wants to merge 2 commits intodonkeyProgramming:masterfrom
Szy-Cathay wants to merge 2 commits intodonkeyProgramming:masterfrom
Conversation
## 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.**
Contributor
Author
|
Hi Ole, If this PR looks good and you're ready to merge it, could you please:
When reviewing, please focus your checks entirely on the second commit (the Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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), orS(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 supportsX/Y/Zaxis locking,Shiftdamping for fine‑tuning, left‑click to commit, right‑click/Escto cancel, and full integration with the command stack forCtrl+Zundo.Core Changes
New Features
SetCursorPosition.X/Y/Zkeys lock the transformation to the corresponding axis (local/world coordinates).Shiftkey enables damping mode, greatly reducing sensitivity for high‑precision adjustment.Esccancel: Immediately cancel the operation and perfectly restore the model's original state.Ctrl+Z.Modified Files
GameWorld/View3D/Components/Gizmo/GizmoComponent.csGameWorld/View3D/Components/Gizmo/TransformGizmoWrapper.csGameWorld/View3D/Components/Input/MouseComponent.csGameWorld/View3D/WpfWindow/Input/WpfMouse.csShared/SharedCore/Services/ActiveWindowProvider.csShared/SharedCore/Services/IActiveWindowProvider.csShared/SharedCore/DependencyInjectionContainer.csIActiveWindowProviderserviceShared/SharedUI/Common/MenuSystem/ActionHotkeyHandler.csTesting/GameWorld.Core.Test/Components/Gizmo/GizmoComponentTests.csTesting/GameWorld.Core.Test/Components/Gizmo/TransformGizmoWrapperTests.csTesting/GameWorld.Core.Test/Input/MouseComponentTests.csDesign Decisions
GizmoComponent,MouseComponent, etc.IActiveWindowProviderservice to isolate hotkeys in multi‑window environments, avoiding complex global hooks or reflection.Issues & Fixes
_rightButtonWasPressedflag; right‑click detection now immediately cancels_skipNextDeltaflag to eliminate cumulative error caused by MouseState frame‑delay_skipNextDeltaimplementationIActiveWindowProviderservice; hotkeys now respond only for the currently active windowTest Coverage
Notes
Test Plan
Esccancel behavior is correctCtrl+Zundo works correctlyReady for submission – copy and paste the title and description above.