feat(app): Phase B.2 — ChaseCamera (third-person follow camera)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 14:26:20 +02:00
parent 631fd3c9bb
commit 84d7d06008
2 changed files with 130 additions and 0 deletions

View file

@ -0,0 +1,66 @@
using System;
using System.Numerics;
namespace AcDream.App.Rendering;
/// <summary>
/// Third-person chase camera that follows behind and above a player
/// character. Implements <see cref="ICamera"/> so it plugs into the
/// existing renderer pipeline.
/// </summary>
public sealed class ChaseCamera : ICamera
{
public Vector3 Position { get; private set; }
public float Aspect { get; set; } = 16f / 9f;
public float FovY { get; set; } = MathF.PI / 3f;
/// <summary>Distance behind the player.</summary>
public float Distance { get; set; } = 8f;
/// <summary>Camera pitch above horizontal (radians). Positive = look down.</summary>
public float Pitch { get; set; } = 0.35f; // ~20 degrees
/// <summary>Vertical offset from the player's feet to the look-at point (eye height).</summary>
public float EyeHeight { get; set; } = 1.5f;
private const float PitchMin = 0.05f;
private const float PitchMax = 1.4f; // ~80 degrees
private float _playerYaw;
private Vector3 _lookAt;
public Matrix4x4 View =>
Matrix4x4.CreateLookAt(Position, _lookAt, Vector3.UnitZ);
public Matrix4x4 Projection =>
Matrix4x4.CreatePerspectiveFieldOfView(FovY, Aspect, 1f, 5000f);
/// <summary>
/// Update the camera position to follow the player.
/// </summary>
public void Update(Vector3 playerPosition, float playerYaw)
{
_playerYaw = playerYaw;
_lookAt = playerPosition + new Vector3(0f, 0f, EyeHeight);
// Camera offset: behind the player (-forward direction) and above.
float forwardX = MathF.Cos(playerYaw);
float forwardY = MathF.Sin(playerYaw);
float horizontalDist = Distance * MathF.Cos(Pitch);
float verticalDist = Distance * MathF.Sin(Pitch);
Position = new Vector3(
playerPosition.X - forwardX * horizontalDist,
playerPosition.Y - forwardY * horizontalDist,
playerPosition.Z + EyeHeight + verticalDist);
}
/// <summary>
/// Adjust pitch by a delta (from mouse Y movement).
/// </summary>
public void AdjustPitch(float delta)
{
Pitch = Math.Clamp(Pitch + delta, PitchMin, PitchMax);
}
}