Pawn/Camera/SkateCamera.cs
namespace Skateboard;

internal sealed class SkateCamera : Component
{
	[Property] public CameraComponent Camera { get; set; }
	[Property] public Skateboard.Player.SkatePawn Target { get; set; }
	private Skateboard.Player.SkatePawn _cachedTarget;

	private float orbitDistance = 200f;
	private float orbitHeight = 80f;

	private float vertCameraHeight = 400f;
	private float vertCameraTall = 50f;

	private float lookDown;
	private float minimumDistance = 0.2f;

	private float rotationLerpSpeed = 3f;
	private float speedLerpSpeed = 5f;
	private float speedDistanceMultiplier = 0.05f;

	private float currentSpeedDistance;
	private float currentVertLerp;

	private float vertLerpSpeed = 1f;
	private float vertRotationSpeed = 10f;

	private float targetTowardsOriginOffset = 1f;

	protected override void OnUpdate()
	{
		var camera = Camera ?? Components.GetOrCreate<CameraComponent>();
		var pawn = Target ?? _cachedTarget;
		if ( pawn is null || !pawn.IsValid() )
		{
			var localId = Connection.Local.Id;
			pawn = Scene.GetAllComponents<Skateboard.Player.SkatePawn>()
				.FirstOrDefault( x => x.GameObject.Network.IsOwner || x.OwningConnectionId == localId );
			_cachedTarget = pawn;
		}

		if ( pawn is null )
			return;

		camera.FieldOfView = 90f;

		var ragdoll = pawn.Ragdoll;
		if ( pawn.Bailed && ragdoll.IsValid() )
		{
			var bailFocusPosition = ragdoll.WorldPosition;
			var renderer = ragdoll.Components.Get<SkinnedModelRenderer>( FindMode.EverythingInSelfAndDescendants );
			if ( renderer is not null )
			{
				if ( renderer.TryGetBoneTransform( "pelvis", out var pelvis ) ||
					renderer.TryGetBoneTransform( "Pelvis", out pelvis ) ||
					renderer.TryGetBoneTransform( "hips", out pelvis ) ||
					renderer.TryGetBoneTransform( "Hips", out pelvis ) )
				{
					bailFocusPosition = pelvis.Position;
				}
			}

			WorldPosition = bailFocusPosition + Vector3.Up * vertCameraHeight;
			WorldRotation = Rotation.LookAt( Vector3.Down, pawn.WorldRotation.Forward );
			return;
		}

		var controller = pawn.Controller;
		var planarSpeed = pawn.Velocity.WithZ( 0 ).Length;
		var pawnForward = pawn.WorldRotation.Forward;
		var focusPosition = pawn.WorldPosition;
		var focusUp = pawn.WorldRotation.Up;

		if ( planarSpeed > Skateboard.Player.SkateController.StoppedVelocity && (controller?.IsGrounded ?? false) == false )
			pawnForward = pawn.Velocity.WithZ( 0 ).Normal;

		var pawnLook = Rotation.LookAt( pawnForward, Vector3.Up )
			.RotateAroundAxis( Vector3.Left, lookDown );

		var center = focusPosition + focusUp * orbitHeight;
		var targetPos = center + WorldRotation.Backward * orbitDistance;

		var speedOffset = planarSpeed * speedDistanceMultiplier;
		currentSpeedDistance = MathX.Lerp(
			currentSpeedDistance,
			speedOffset,
			speedLerpSpeed * Time.Delta
		);

		targetPos += WorldRotation.Backward * currentSpeedDistance;

		var vertCamPos =
			focusPosition +
			focusUp * vertCameraTall +
			Vector3.Up * vertCameraHeight;

		var vertCenter = focusPosition + Vector3.Up * vertCameraHeight;

		currentVertLerp = MathX.Lerp(
			currentVertLerp,
			pawn.OnVert ? 1f : 0f,
			(pawn.OnVert ? vertLerpSpeed : speedLerpSpeed) * Time.Delta
		);

		targetPos = Vector3.Lerp( targetPos, vertCamPos, currentVertLerp );
		center = Vector3.Lerp( center, vertCenter, currentVertLerp );

		var dir = (targetPos - center).Normal;
		center += dir * targetTowardsOriginOffset;

		var trace = Scene.Trace.Ray( center, targetPos )
			.WithAnyTags( "solid" )
			.IgnoreGameObjectHierarchy( pawn.GameObject )
			.IgnoreGameObjectHierarchy( ragdoll )
			.Radius( 8f )
			.Run();

		WorldPosition =
			trace.Fraction <= minimumDistance
				? Vector3.Lerp( center, targetPos, minimumDistance )
				: trace.EndPosition;

		if ( pawn.OnVert )
		{
			pawnLook = Rotation.LookAt(
				(pawn.WorldPosition - WorldPosition + pawn.WorldRotation.Up * vertCameraTall).Normal,
				pawn.VertNormal
			);

			WorldRotation = Rotation.Slerp(
				WorldRotation,
				pawnLook,
				vertRotationSpeed * Time.Delta
			);
		}
		else
		{
			WorldRotation = Rotation.Slerp(
				WorldRotation,
				pawnLook,
				rotationLerpSpeed * Time.Delta
			);
		}
	}


}