Editor/WeaponImporter/Tool/UI/Preview/WeaponViewport.Camera.cs
using Editor;
using Sandbox;
namespace WeaponImporter.Tool.UI;
public sealed partial class WeaponViewport
{
private float _yaw = 145f;
private float _pitch = 12f;
private float _distance = 70f;
private Vector3 _target = new( 0, 0, 48 );
private int _frameIn = 6; // frames until the first framing (the hold must have placed the weapon)
private Vector2 _lastMouse;
private Vector2 _pressAt;
private bool _dragged;
private Transform CharacterRoot => _characterObject.IsValid() ? _characterObject.WorldTransform : Transform.Zero;
/// <summary>Orbit around the upper body, from the front right (the weapon side).</summary>
public void FrameCharacter()
{
var root = CharacterRoot;
var chest = root.PointToWorld( new Vector3( 4, 0, 50 ) );
_target = _weaponObject.IsValid() && _c.Session?.Grip is not null ? Vector3.Lerp( chest, WeaponWorld.Position, 0.6f ) : chest;
_yaw = 145f;
_pitch = 10f;
_distance = 84f;
}
/// <summary>Frame the weapon (F).</summary>
public void FrameWeapon()
{
var analysis = _c.Analysis;
if ( analysis is null || !_weaponObject.IsValid() )
{
FrameCharacter();
return;
}
var size = analysis.WeaponBounds.Size;
var center = analysis.WeaponBounds.Center;
var radius = MathF.Max( 6f, new Vector3( size.X, size.Y, size.Z ).Length * 0.5f ) * 1.35f;
if ( CameraMode != ViewportCamera.Orbit )
SetCamera( ViewportCamera.Orbit );
_target = WeaponWorld.PointToWorld( new Vector3( center.X, center.Y, center.Z ) );
_distance = MathX.SphereCameraDistance( radius, Camera.FieldOfView );
}
public event Action CameraModeChanged;
public void SetCamera( ViewportCamera mode )
{
if ( CameraMode == mode )
return;
CameraMode = mode;
CameraModeChanged?.Invoke();
}
private void UpdateCamera()
{
if ( !Camera.IsValid() )
return;
var root = CharacterRoot;
var forward = root.Rotation.Forward;
var right = root.Rotation.Right;
var up = Vector3.Up;
var weapon = _weaponObject.IsValid() ? WeaponWorld.Position : root.PointToWorld( new Vector3( 12, -6, 50 ) );
switch ( CameraMode )
{
case ViewportCamera.FirstPerson:
{
// Through the character's eyes, looking where it aims: its own arms hold the weapon.
var eyes = _body.IsValid() ? _body.GetAttachment( "eyes", true ) : null;
var eye = eyes?.Position ?? root.Position + up * 64f;
// The third-person hold carries the weapon low; look down at it like a viewmodel
// shows it, a little past the weapon so the barrel stays in frame.
var look = weapon + forward * 16f;
Camera.FieldOfView = 70f;
Camera.ZNear = 3f; // the head sits around the camera
Camera.WorldPosition = eye + forward * 1.5f;
Camera.WorldRotation = Rotation.LookAt( (look - Camera.WorldPosition).Normal, Vector3.Up );
return;
}
case ViewportCamera.Gameplay:
{
var eye = root.Position + forward * -46f + right * 18f + up * 68f;
var look = root.Position + forward * 70f + right * 2f + up * 58f;
Camera.FieldOfView = 60f;
Camera.WorldPosition = eye;
Camera.WorldRotation = Rotation.LookAt( (look - eye).Normal, Vector3.Up );
return;
}
case ViewportCamera.Front:
{
var eye = weapon + forward * 62f + up * 4f;
Camera.FieldOfView = 45f;
Camera.WorldPosition = eye;
Camera.WorldRotation = Rotation.LookAt( (weapon - eye).Normal, Vector3.Up );
return;
}
case ViewportCamera.Side:
{
var eye = weapon + right * 58f + up * 3f;
Camera.FieldOfView = 45f;
Camera.WorldPosition = eye;
Camera.WorldRotation = Rotation.LookAt( (weapon - eye).Normal, Vector3.Up );
return;
}
default:
{
var rotation = Rotation.From( _pitch, _yaw, 0 );
Camera.FieldOfView = 45f;
Camera.WorldPosition = _target - rotation.Forward * _distance;
Camera.WorldRotation = rotation;
return;
}
}
}
/// <summary>Leaves a fixed camera for orbit at the same place, so dragging feels continuous.</summary>
private void TakeOverCamera()
{
if ( CameraMode is ViewportCamera.Orbit or ViewportCamera.FirstPerson || !Camera.IsValid() )
return;
var rotation = Camera.WorldRotation;
var angles = rotation.Angles();
_pitch = angles.pitch;
_yaw = angles.yaw;
var weapon = _weaponObject.IsValid() ? WeaponWorld.Position : _target;
_distance = MathF.Max( 10f, (weapon - Camera.WorldPosition).Length );
_target = Camera.WorldPosition + rotation.Forward * _distance;
SetCamera( ViewportCamera.Orbit );
}
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
_lastMouse = e.LocalPosition;
_pressAt = e.LocalPosition;
_dragged = false;
Focus();
if ( e.LeftMouseButton )
BeginGripDrag( e.LocalPosition );
}
protected override void OnMouseMove( MouseEvent e )
{
base.OnMouseMove( e );
var delta = e.LocalPosition - _lastMouse;
_lastMouse = e.LocalPosition;
_hoverPosition = e.LocalPosition;
if ( (e.LocalPosition - _pressAt).Length > 3 )
_dragged = true;
var buttons = e.ButtonState;
if ( _dragGrip is not null )
{
if ( (buttons & MouseButtons.Left) != 0 )
DragGrip( e.LocalPosition );
else
EndGripDrag();
return;
}
// A grip contact under the cursor can be grabbed.
if ( buttons == 0 )
Cursor = HitGripContact( e.LocalPosition ) is not null ? CursorShape.OpenHand : CursorShape.Arrow;
if ( (buttons & MouseButtons.Left) != 0 && _c.Pick == PickTarget.None )
{
TakeOverCamera();
_yaw -= delta.x * 0.4f;
_pitch = Math.Clamp( _pitch + delta.y * 0.3f, -85f, 85f );
}
else if ( (buttons & (MouseButtons.Right | MouseButtons.Middle)) != 0 )
{
TakeOverCamera();
var rotation = Rotation.From( _pitch, _yaw, 0 );
var scale = _distance * 0.0016f;
_target += (rotation.Right * -delta.x + rotation.Up * delta.y) * scale;
}
}
protected override void OnMouseReleased( MouseEvent e )
{
base.OnMouseReleased( e );
if ( _dragGrip is not null )
{
EndGripDrag();
return;
}
if ( e.LeftMouseButton && !_dragged )
Click( e.LocalPosition );
}
protected override void OnMouseWheel( WheelEvent e )
{
TakeOverCamera();
_distance = Math.Clamp( _distance * (e.Delta > 0 ? 0.88f : 1.12f), 4f, 600f );
e.Accept();
}
protected override void OnKeyPress( KeyEvent e )
{
if ( e.Key == KeyCode.F )
{
FrameWeapon();
e.Accepted = true;
return;
}
if ( e.Key == KeyCode.Escape && _c.Pick != PickTarget.None )
{
_c.BeginPick( PickTarget.None );
_c.SetStatus( "Picking cancelled.", Theme.TextLight );
e.Accepted = true;
return;
}
base.OnKeyPress( e );
}
}