Files
sbox-public/game/addons/tools/Code/Scene/ObjectTool/RotationEditorTool.cs
DrakeFruit 7d6aab5313 Gizmo.Control.Rotate outputs Rotation, no longer explicitly delta, add trackball and view based rotation gizmos
This commit adds a blender style trackball and view space rotation to all rotate gizmos. This is a breaking change to anything that currently uses the rotate method as you now must use the output directly rather than multiply it. Also added a method to get distance vector, not sure why it's only a float.

---

Co-authored-by: William Lemon <William.Lemon2@gmail.com>
2026-02-23 23:58:05 +00:00

115 lines
3.2 KiB
C#

namespace Editor;
/// <summary>
/// Rotate selected GameObjects.<br/> <br/>
/// <b>Ctrl</b> - toggle snap to grid
/// </summary>
[Title( "Rotate" )]
[Icon( "360" )]
[Alias( "tools.rotate-tool" )]
[Group( "1" )]
[Order( 1 )]
public class RotationEditorTool : EditorTool
{
Dictionary<GameObject, Transform> startPoints = new Dictionary<GameObject, Transform>();
Rotation moveDelta;
Vector3 handlePosition;
Rotation handleRotation;
IDisposable undoScope;
public override void OnUpdate()
{
var nonSceneGos = Selection.OfType<GameObject>().Where( go => go.GetType() != typeof( Sandbox.Scene ) );
if ( nonSceneGos.Count() == 0 ) return;
var bbox = BBox.FromPoints( nonSceneGos.Select( x => x.WorldPosition ) );
if ( !Gizmo.Pressed.Any && Gizmo.HasMouseFocus )
{
startPoints.Clear();
moveDelta = Rotation.Identity;
handleRotation = nonSceneGos.FirstOrDefault()?.WorldRotation ?? Rotation.Identity;
handlePosition = bbox.Center;
undoScope?.Dispose();
undoScope = null;
}
var basis = Gizmo.Settings.GlobalSpace ? Rotation.Identity : handleRotation;
// Stop updating the handle position if we're dragging
var origin = Gizmo.Pressed.Any ? handlePosition : bbox.Center;
using ( Gizmo.Scope( "Tool", new Transform( origin, basis ) ) )
{
Gizmo.Hitbox.DepthBias = 0.01f;
if ( Gizmo.Control.Rotate( "rotation", Rotation.Identity, out Rotation rotationDelta ) )
{
StartDrag( nonSceneGos );
moveDelta = Gizmo.Snap( rotationDelta );
foreach ( var entry in startPoints )
{
var rot = basis * moveDelta * basis.Inverse;
var position = entry.Value.Position - handlePosition;
position *= rot;
position += handlePosition;
rot *= entry.Value.Rotation;
var scale = entry.Value.Scale;
var oldPosition = entry.Key.LocalPosition;
entry.Key.BreakProceduralBone();
entry.Key.WorldTransform = new Transform( position, rot, scale );
var newPosition = entry.Key.LocalPosition;
// Only dispatch position event if it actually changed
if ( !newPosition.AlmostEqual( oldPosition ) )
{
entry.Key.DispatchEdited( nameof( GameObject.LocalPosition ) );
}
entry.Key.DispatchEdited( nameof( GameObject.LocalRotation ) );
}
}
}
}
private void StartDrag( IEnumerable<GameObject> selectedGos )
{
if ( startPoints.Any() ) return;
if ( Gizmo.IsShiftPressed )
{
undoScope ??= SceneEditorSession.Active.UndoScope( "Duplicate Object(s)" ).WithGameObjectCreations().Push();
DuplicateSelection();
}
else
{
undoScope ??= SceneEditorSession.Active.UndoScope( "Transform Object(s)" ).WithGameObjectChanges( selectedGos, GameObjectUndoFlags.Properties ).Push();
selectedGos.DispatchPreEdited( nameof( GameObject.LocalPosition ) );
selectedGos.DispatchPreEdited( nameof( GameObject.LocalRotation ) );
}
foreach ( var entry in selectedGos )
{
startPoints[entry] = entry.WorldTransform;
}
}
[Shortcut( "tools.rotate-tool", "e", typeof( SceneViewWidget ) )]
public static void ActivateSubTool()
{
if ( !(EditorToolManager.CurrentModeName == nameof( ObjectEditorTool ) || EditorToolManager.CurrentModeName == "object") ) return;
EditorToolManager.SetSubTool( nameof( RotationEditorTool ) );
}
}