Abstract camera behavior component used by CameraManager. It defines whether the behavior wants control, priority, blend time, active state, activation hook, evaluation for each frame, and helpers for a top-down pose and getting a racing-line tangent.
using Machines.Race;
namespace Machines.Components;
/// <summary>
/// A single camera mode; <see cref="CameraManager"/> picks the highest-priority active one and blends.
/// </summary>
public abstract class CameraBehaviour : Component
{
/// <summary>
/// Whether this behaviour is applicable right now.
/// </summary>
public abstract bool WantsControl { get; }
/// <summary>
/// Tie-breaker when several behaviours want control. Higher wins.
/// </summary>
public virtual int Priority => 0;
/// <summary>
/// Seconds the director eases into this behaviour. 0 = instant.
/// </summary>
[Property, Group( "Blend" )]
public float BlendInTime { get; set; } = 0.5f;
/// <summary>
/// True while this behaviour is the one driving the camera.
/// </summary>
public bool IsActive { get; private set; }
internal void SetActive( bool active ) => IsActive = active;
/// <summary>
/// Called when this becomes active; seed smoothing from <paramref name="from"/> to avoid popping.
/// </summary>
public virtual void OnActivated( CameraPose from ) { }
/// <summary>
/// Compute this frame's desired pose; <paramref name="current"/> is the live applied pose.
/// </summary>
public abstract CameraPose Evaluate( CameraPose current );
/// <summary>
/// Angled top-down pose behind <paramref name="groundCenter"/> along <paramref name="heading"/>, pitched down to look at it.
/// </summary>
protected static CameraPose TopDown( Vector3 groundCenter, Vector3 heading, float pitch, float height, float fieldOfView )
{
var pitchRad = MathX.DegreeToRadian( pitch );
var horizontalOffset = height / MathF.Tan( pitchRad );
var position = groundCenter - heading * horizontalOffset + Vector3.Up * height;
var rotation = Rotation.LookAt( (groundCenter - position).Normal, Vector3.Up );
return new CameraPose( position, rotation, fieldOfView );
}
/// <summary>
/// Flat tangent of the racing line nearest <paramref name="point"/>, or <paramref name="fallback"/> if none.
/// </summary>
protected static Vector3 RacingLineTangent( Vector3 point, Vector3 fallback )
{
var line = RacingPath.Current?.Optimal;
if ( line is null || !line.IsValid )
return fallback;
var dist = line.GetDistanceAtPosition( point );
var tangent = line.GetTangentAtDistance( dist ).WithZ( 0f );
return tangent.LengthSquared > 0.001f ? tangent.Normal : fallback;
}
}