ModelViewer/CameraController.cs
using Sandbox;
using System;
[Title( "Camera Controller" )]
[Category( "Model Viewer" )]
[Icon( "videocam", "red", "white" )]
[Tint( EditorTint.Yellow )]
public sealed class CameraController : Component
{
public enum CameraMode
{
Orbit,
Maya,
FreeCam
}
//CameraMode
[Property][Title( "Camera Mode" )][Group( "Camera Mode" )] public CameraMode Mode { get; set; } = CameraMode.Orbit;
[Property][Title( "Lock Camera" )][Group( "Camera Mode" )] public bool LockCamera { get; set; } = false;
[Property][Title( "Camera Focus Object" )][Group( "Camera Mode" )] public GameObject FocusObject { get; set; }
[Property]
[Title( "Field of View" )]
[Range( 0, 180 )]
[Group( "Camera" )]
public float Fov { get; set; } = 80.0f;
[Property]
[Title( "Near Clip" )]
[Range( 0, 10 )]
[Group( "Camera" )]
public float Near { get; set; } = 0.1f;
[Property]
[Title( "Far Clip" )]
[Range( 0, 10000 )]
[Group( "Camera" )]
public float Far { get; set; } = 10000.0f;
[Property]
[Title( "Orthographic" )]
[Group( "Camera" )]
public bool Ortho { get; set; } = false;
[Property]
[Title( "Orthographic Size" )]
[Range( 0, 1000 )]
[Group( "Camera" )]
public float OrthoSize { get; set; } = 1000.0f;
[Property]
[Group( "Camera" )]
public bool UseViewModelCamera { get; set; } = false;
//Orbit Controls
[Property][Group( "Orbit Controls" ), ShowIf( "Mode", CameraMode.Orbit )] public float CameraDistance { get; set; } = 200.0f;
//
public Vector3 orbitObject { get; set; }
private float orbitDistance = 200.0f;
//Maya Controls
[Property][Title( "Maya Orbit Speed" )][Range( 0, 1 )][Group( "Maya Controls" ), ShowIf( "Mode", CameraMode.Maya )] private float orbitSpeed { get; set; } = 1.0f;
[Property][Title( "Maya Zoom Speed" )][Range( 0, 1 )][Group( "Maya Controls" ), ShowIf( "Mode", CameraMode.Maya )] private float zoomSpeed { get; set; } = 10.0f;
[Property][Title( "Maya Pan Speed" )][Range( 0, 10 )][Group( "Maya Controls" ), ShowIf( "Mode", CameraMode.Maya )] private float panSpeed { get; set; } = 10.0f;
[Property][Title( "Maya Fly Speed" )][Range( 0, 200 )][Group( "Maya Controls" ), ShowIf( "Mode", CameraMode.Maya )] private float MayaFlySpeed { get; set; } = 100.0f;
//
//Lighting Rig Rotation
[Property, Group( "LightingRigRotation" )][Title( "Rotation Speed" )][Range( 0, 5 )] public float LightingRigRotationSpeed { get; set; } = 1.0f;
[Property, Group( "LightingRigRotation" )] public GameObject LightingRig { get; set; }
//
//References
public GameObject CameraObject { get; set; }
[Property][Group( "References" )] public GameObject ViewModelCameraObject { get; set; }
[Property][Group( "References" )] public GameObject CameraOrbit { get; set; }
//
private float MayaSpeed { get; set; } = 100.0f;
private Vector2 cameraAngles;
Vector3 wishDir = default;
public Angles EyeAngles;
public float ZOffset;
public float FovScale = 50.0f;
public float CharacterRotation = 180.0f;
public float flySpeed = 50.0f;
private float targetCameraDistance;
private float targetZOffset;
private float targetFovScale;
private float targetCharacterRotation;
private float lightRigRotation;
public CameraController()
{
targetCameraDistance = CameraDistance;
targetZOffset = ZOffset;
targetFovScale = FovScale;
targetCharacterRotation = CharacterRotation;
}
protected override void OnStart()
{
base.OnStart();
CameraObject = Scene.Camera.GameObject;
orbitObject = Vector3.Zero;
CameraObject.WorldPosition = Vector3.Zero + CameraObject.WorldRotation.Backward * orbitDistance;
}
protected override void OnUpdate()
{
UpdateCameraSettings();
RotateLightingRig();
// Eye input
EyeAngles.pitch += Input.MouseDelta.y * 0.1f;
EyeAngles.yaw -= Input.MouseDelta.x * 0.1f;
EyeAngles.roll = 0;
if ( Input.Pressed( "slot1" ) )
{
Mode = CameraMode.Orbit;
}
if ( Input.Pressed( "slot2" ) )
{
Mode = CameraMode.Maya;
}
if ( Input.Pressed( "slot3" ) )
{
Mode = CameraMode.FreeCam;
}
if ( Mode == CameraMode.Orbit )
{
OrbitCamera();
return;
}
if ( Mode == CameraMode.Maya )
{
MayaCamera();
return;
}
if ( Mode == CameraMode.FreeCam )
{
HandleFlyCameraMovement();
return;
}
}
private void MayaCamera()
{
if ( !LockCamera )
{
var camera = CameraObject.Components.Get<CameraComponent>( FindMode.EverythingInSelf );
if ( camera is not null )
{
float x = Input.MouseDelta.x;
float y = Input.MouseDelta.y;
if ( Input.Down( "attack1" ) && Input.Down( "walk" ) )
{
cameraAngles += new Vector2( y * orbitSpeed, x * orbitSpeed );
cameraAngles.x = Math.Clamp( cameraAngles.x, -89.9f, 89.9f );
CameraObject.WorldRotation = Rotation.From( cameraAngles.x, -cameraAngles.y, 0 );
var newCameraPosition = orbitObject + CameraObject.WorldRotation.Backward * orbitDistance;
CameraObject.WorldPosition = newCameraPosition;
}
else if ( Input.Down( "attack2" ) && Input.Down( "walk" ) )
{
var currentZoomSpeed = Math.Clamp( zoomSpeed * (orbitDistance / 50), 0.1f, 2.0f );
CameraObject.WorldPosition += CameraObject.WorldRotation.Backward * (y * -1f * currentZoomSpeed);
orbitDistance = CameraObject.WorldPosition.Distance( orbitObject );
}
else if ( Input.Down( "attack3" ) && Input.Down( "walk" ) )
{
var translateX = CameraObject.WorldRotation.Right * (-x * panSpeed * RealTime.Delta);
var translateY = CameraObject.WorldRotation.Up * (y * panSpeed * RealTime.Delta);
CameraObject.WorldPosition += translateX;
CameraObject.WorldPosition += translateY;
orbitObject += translateX;
orbitObject += translateY;
}
else
{
var currentZoomSpeed = Math.Clamp( zoomSpeed * (orbitDistance / 50), 0.1f, 2.0f );
CameraObject.WorldPosition += CameraObject.WorldRotation.Backward * (Input.MouseWheel.y * -1f * currentZoomSpeed);
wishDir = Vector3.Zero;
if ( Input.Down( "Forward" ) ) wishDir.x = 1;
else if ( Input.Down( "Backward" ) ) wishDir.x = -1;
if ( Input.Down( "Left" ) ) wishDir.y = 1;
else if ( Input.Down( "Right" ) ) wishDir.y = -1;
wishDir = wishDir.Normal;
MayaSpeed = Input.Down( "run" ) ? MayaFlySpeed : MayaFlySpeed / 2;
if ( wishDir.Length > 0 )
{
CameraObject.WorldPosition += (CameraObject.WorldRotation * wishDir).Normal * MayaSpeed * Time.Delta;
}
if ( Input.Pressed( "flashlight" ) )
{
if ( FocusObject is not null )
{
CameraObject.WorldPosition = FocusObject.WorldPosition + FocusObject.WorldRotation.Forward * orbitDistance;
CameraObject.WorldRotation = Rotation.LookAt( FocusObject.WorldPosition - CameraObject.WorldPosition );
}
else
{
CameraObject.WorldPosition = Vector3.Zero + CameraObject.WorldRotation.Forward * orbitDistance;
CameraObject.WorldRotation = Rotation.LookAt( Vector3.Zero - CameraObject.WorldPosition );
}
}
orbitObject = CameraObject.WorldPosition + CameraObject.WorldRotation.Forward * orbitDistance;
}
}
}
}
private void OrbitCamera()
{
if ( !LockCamera )
{
if ( !Input.Down( "Walk" ) && !Input.Down( "Run" ) && !Input.Down( "Duck" ) )
{
targetCameraDistance = Math.Clamp( targetCameraDistance + Input.MouseWheel.y * -5, 50, 500 );
CameraDistance = Lerp( CameraDistance, targetCameraDistance, 0.1f );
}
if ( Input.Down( "Walk" ) )
{
targetZOffset = Math.Clamp( targetZOffset + Input.MouseWheel.y * -2, -100, 100 );
ZOffset = Lerp( ZOffset, targetZOffset, 0.1f );
}
if ( Input.Down( "Duck" ) )
{
targetCharacterRotation = targetCharacterRotation + Input.MouseWheel.y * -5;
CharacterRotation = Lerp( CharacterRotation, targetCharacterRotation, 0.1f );
}
var camera = CameraObject.Components.Get<CameraComponent>( FindMode.EverythingInSelf );
if ( camera is not null )
{
var camPos = CameraOrbit.WorldPosition - EyeAngles.ToRotation().Forward * CameraDistance;
camPos.z += ZOffset;
camera.WorldPosition = camPos;
camera.WorldRotation = EyeAngles.ToRotation();
}
}
}
private void HandleFlyCameraMovement()
{
var direction = Vector3.Zero;
if ( Input.Down( "Forward" ) )
direction += CameraObject.WorldRotation.Forward;
if ( Input.Down( "Backward" ) )
direction -= CameraObject.WorldRotation.Forward;
if ( Input.Down( "Left" ) )
direction -= CameraObject.WorldRotation.Right;
if ( Input.Down( "Right" ) )
direction += CameraObject.WorldRotation.Right;
if ( Input.Down( "Jump" ) )
direction += Vector3.Up;
if ( Input.Down( "Duck" ) )
direction -= Vector3.Up;
flySpeed = Input.Down( "Run" ) ? 150.0f : 50.0f;
float magnitude = direction.Length;
if ( magnitude > 0 )
{
direction.x /= magnitude;
direction.y /= magnitude;
direction.z /= magnitude;
}
CameraObject.WorldPosition += direction * flySpeed * Time.Delta;
EyeAngles.pitch += Input.MouseDelta.y * 0.03f;
EyeAngles.yaw -= Input.MouseDelta.x * 0.03f;
EyeAngles.pitch = Math.Clamp( EyeAngles.pitch, -89.9f, 89.9f );
CameraObject.WorldRotation = EyeAngles.ToRotation();
}
public void RotateLightingRig()
{
if ( !LightingRig.IsValid() )
return;
float x = Input.MouseDelta.x;
if ( Input.Down( "run" ) && Input.Down( "attack2" ) )
{
lightRigRotation += x * LightingRigRotationSpeed;
LightingRig.WorldRotation = Rotation.From( 0, lightRigRotation, 0 );
}
}
public void UpdateCameraSettings()
{
if ( !CameraObject.IsValid() )
{
CameraObject = Scene.Camera.GameObject;
}
var cam = CameraObject.GetComponent<CameraComponent>( true );
if ( ViewModelCameraObject.IsValid() )
{
var vm = ViewModelCameraObject.GetComponent<CameraComponent>( true );
if ( Input.Pressed( "voice" ) ) UseViewModelCamera = !UseViewModelCamera;
if ( vm.IsValid() && UseViewModelCamera )
{
vm.Enabled = true;
cam.Enabled = false;
return;
}
else if ( vm.IsValid() && !UseViewModelCamera )
{
vm.Enabled = false;
cam.Enabled = true;
}
}
if ( cam is not null )
{
cam.FieldOfView = Fov;
cam.ZNear = Near;
cam.ZFar = Far;
cam.Orthographic = Ortho;
cam.OrthographicHeight = OrthoSize;
}
}
public static float Lerp( float a, float b, float t )
{
return a + t * (b - a);
}
}