namespace Sandbox; public static partial class Gizmo { static GizmoControls _gizmo = new(); /// /// Holds fully realized controls to manipulate some value /// public static GizmoControls Control => _gizmo; /// /// Extendable helper to create common gizmos /// public sealed partial class GizmoControls { internal GizmoControls() { } /// /// A front left up position movement widget. If widget was moved then will return true and out will return the new position. /// public bool Position( string name, Vector3 position, out Vector3 newPos, Rotation? axisRotation = null, float squareSize = 3.0f ) { if ( position.IsNaN ) { newPos = Vector3.Zero; return true; } var localRotation = Transform.Rotation; var localScale = Transform.UniformScale; var axis = axisRotation ?? Rotation.Identity; using var x = Sandbox.Gizmo.Scope( name, Vector3.Zero, axis ); using var scaler = PushFixedScale(); // I don't know if we should even be doing this here or leave it to the implementation if ( Settings.GlobalSpace ) Transform = Transform.WithRotation( Rotation.Identity ); Sandbox.Gizmo.Draw.IgnoreDepth = true; var rot = Transform.Rotation; var forward = rot.Forward; var left = rot.Left; var up = rot.Up; Vector3 movement = Vector3.Zero; Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Colors.Up; if ( Arrow( "up", Vector3.Up, out var xdist ) ) { movement += Vector3.Up * xdist; } Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Colors.Left; if ( Arrow( "left", Vector3.Left, out var ydist ) ) { movement += Vector3.Left * ydist; } Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Colors.Forward; if ( Arrow( "forward", Vector3.Forward, out var zdist ) ) { movement += Vector3.Forward * zdist; } if ( squareSize > 0 ) { float squareOffset = squareSize * 3.0f; using var scope = Sandbox.Gizmo.Scope( "squares" ); Hitbox.DepthBias *= 0.8f; { var camRot = Transform.RotationToLocal( Camera.Rotation ); Sandbox.Gizmo.Draw.Color = Color.White.WithAlpha( 1.1f ); if ( DragSquare( "drag-camera", squareSize, camRot, out var moved, DrawPositionCenter ) ) { // movement is in camera space movement += moved; } } using ( Sandbox.Gizmo.Scope() ) { Sandbox.Gizmo.Transform = Sandbox.Gizmo.Transform.ToWorld( new Transform( Vector3.Up * squareOffset + Vector3.Left * squareOffset ) ); Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Colors.Up; if ( DragSquare( "left-up", squareSize, Rotation.LookAt( Vector3.Backward, Vector3.Up ), out var moved ) ) { movement += moved; } } using ( Sandbox.Gizmo.Scope() ) { Sandbox.Gizmo.Transform = Sandbox.Gizmo.Transform.ToWorld( new Transform( Vector3.Forward * squareOffset + Vector3.Left * squareOffset ) ); Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Colors.Left; if ( DragSquare( "forward-left", squareSize, Rotation.LookAt( Vector3.Up, Vector3.Forward ), out var moved ) ) { movement += moved; } } using ( Sandbox.Gizmo.Scope() ) { Sandbox.Gizmo.Transform = Sandbox.Gizmo.Transform.ToWorld( new Transform( Vector3.Forward * squareOffset + Vector3.Up * squareOffset ) ); Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Colors.Forward; if ( DragSquare( "forward-up", squareSize, Rotation.LookAt( Vector3.Left, Vector3.Down ), out var moved ) ) { movement += moved; } } } if ( movement.IsNearlyZero() ) { newPos = position; return false; } movement = (movement * Transform.Rotation) * localRotation.Inverse; if ( localScale != 0.0f ) { movement *= (1.0f / localScale); } if ( movement.IsNearlyZero() ) { newPos = position; return false; } newPos = position + movement; return true; } static void DrawPositionCenter() { Sandbox.Gizmo.Draw.LineThickness = 2.0f; Sandbox.Gizmo.Draw.LineCircle( 0, Sandbox.Gizmo.IsHovered ? 0.6f : 0.5f, 8 ); } /// /// Draw an arrow - return move delta if interacted with /// public bool Arrow( string name, Vector3 axis, out float distance, float length = 24.0f, float girth = 6.0f, float axisOffset = 2.0f, float cullAngle = 10.0f, float snapSize = 0.0f, string head = "cone" ) { distance = 0; var angle = Vector3.GetAngle( axis, Transform.NormalToLocal( Camera.Rotation.Forward ) ); if ( angle < cullAngle || angle > 180.0f - cullAngle ) return false; var localCam = Transform.RotationToLocal( Camera.Rotation ); // Use the camera to provide a plane that'll work for us var rot = Rotation.LookAt( axis, Vector3.Up ); using var x = Sandbox.Gizmo.Scope( name, axis * axisOffset, rot ); girth *= 0.5f; Sandbox.Gizmo.Hitbox.BBox( new BBox( new Vector3( 0, -girth, -girth ), new Vector3( length, girth, girth ) ) ); if ( !Sandbox.Gizmo.IsHovered ) Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Draw.Color.Darken( 0.33f ); Sandbox.Gizmo.Draw.LineThickness = girth; var lineLength = length; var headLength = 4.0f; if ( snapSize > 0 ) { lineLength = lineLength.SnapToGrid( snapSize ); } // not pressed, no movement Sandbox.Gizmo.Draw.Line( 0, Vector3.Forward * (lineLength - headLength) ); if ( head == "cone" ) { Sandbox.Gizmo.Draw.SolidCone( Vector3.Forward * (lineLength - headLength), Vector3.Forward * headLength, headLength * 0.33f ); } if ( head == "box" ) { Sandbox.Gizmo.Draw.SolidBox( BBox.FromPositionAndSize( Vector3.Forward * (lineLength - headLength), headLength * 0.5f ) ); } if ( !Pressed.This ) return false; // use a plane that follows the axis but that uses the camera's plane Gizmo.Transform = Gizmo.Transform.WithRotation( Rotation.LookAt( Transform.Rotation.Forward, Camera.Rotation.Forward ) ); // // Get the delta between trace hits against a plane // var delta = Sandbox.Gizmo.GetMouseDelta( Vector3.Zero, Vector3.Up ); distance = Vector3.Forward.Dot( delta ); // restrict movement to the axis direction return distance != 0.0f; } public bool DragBox( string name, Vector3 size, Rotation rotation, out Vector3 movement ) { movement = default; var box = new BBox( -size * 0.5f, size * 0.5f ); using var x = Sandbox.Gizmo.Scope( name, new Transform( 0, rotation ) ); // Hitbox Sandbox.Gizmo.Hitbox.BBox( box ); // Draw if ( !Sandbox.Gizmo.IsHovered ) { Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Draw.Color.Darken( 0.33f ); } if ( !Sandbox.Gizmo.IsHovered ) Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Draw.Color.Darken( 0.03f ); Sandbox.Gizmo.Draw.SolidBox( box ); if ( !Pressed.This ) return false; // // action // var localCameraRot = Transform.RotationToLocal( Camera.Rotation ); var delta = Sandbox.Gizmo.GetMouseDelta( 0, Rotation.FromAxis( localCameraRot.Forward, 0 ).Forward ); movement = Vector3.One.Dot( delta ); // Optional: Debug drawing if ( Hitbox.Debug && Sandbox.Gizmo.IsHovered ) { using var scope = Scope(); var os = Transform; Transform = Transform.WithScale( 1.0f ); Sandbox.Gizmo.Draw.LineThickness = 1.0f; Sandbox.Gizmo.Draw.Color = Color.White; Sandbox.Gizmo.Draw.Plane( 0, Vector3.Forward ); Transform = os; } return movement != Vector3.Zero; } /// /// Manipulate a 2d value by moving on 2 axis /// public bool DragSquare( string name, Vector2 size, Rotation rotation, out Vector3 movement, Action drawHandle = null ) { movement = default; var bbox = new BBox( new Vector3( -0.01f, -size.x, -size.y ), new Vector3( 0.01f, size.x, size.y ) ); using var x = Sandbox.Gizmo.Scope( name, new Transform( 0, rotation ) ); // // hitbox // Sandbox.Gizmo.Hitbox.BBox( bbox ); // // drawing // if ( !Sandbox.Gizmo.IsHovered ) Sandbox.Gizmo.Draw.Color = Sandbox.Gizmo.Draw.Color.Darken( 0.33f ); if ( drawHandle == null ) { Sandbox.Gizmo.Draw.LineThickness = 2.0f; Sandbox.Gizmo.Draw.LineBBox( bbox ); //Scene.Draw.LineCircle( 0, size.x ); } drawHandle?.Invoke(); if ( !Pressed.This ) return false; // // action // movement = Sandbox.Gizmo.GetMouseDelta( 0, Vector3.Forward ); if ( Hitbox.Debug && Sandbox.Gizmo.IsHovered ) { using var scope = Scope(); var os = Transform; Transform = Transform.WithScale( 1.0f ); Sandbox.Gizmo.Draw.LineThickness = 1.0f; Sandbox.Gizmo.Draw.Color = Color.White; Sandbox.Gizmo.Draw.Plane( 0, Vector3.Forward ); Transform = os; } movement *= rotation; return movement != 0; } /// /// Scope this before drawing a control to obey Settings.GizmoScale /// public static IDisposable PushFixedScale( float? scale = null ) { if ( !scale.HasValue ) scale = Settings.GizmoScale; if ( Camera.Ortho ) { scale *= Camera.OrthoHeight * 0.006f; } else { var screenSize = 1024.0f / Camera.Size.Length; scale *= screenSize; var dist = Camera.Position.Distance( Transform.Position ); scale *= dist * Camera.FieldOfView.DegreeToRadian() * 0.006f; } var s = Scope( "WorldScale" ); Transform = Transform.WithScale( scale.Value ); return s; } } }