using System; using System.Numerics; namespace AcDream.App.Rendering; /// /// Third-person chase camera that follows behind and above a player /// character. Implements so it plugs into the /// existing renderer pipeline. /// 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; /// Distance behind the player. Clamped to [, ]. public float Distance { get; set; } = 8f; public const float DistanceMin = 2f; public const float DistanceMax = 40f; /// Camera pitch above horizontal (radians). Positive = look down. public float Pitch { get; set; } = 0.35f; // ~20 degrees /// /// Additional yaw applied on top of the player's heading when positioning /// the camera. Used by the hold-RMB "inspect" mode to orbit around the /// player without rotating the character. Snap to 0 to return the camera /// to directly behind the player. /// public float YawOffset { get; set; } = 0f; /// Vertical offset from the player's feet to the look-at point (eye height). public float EyeHeight { get; set; } = 1.5f; // Pitch range: negative values place the camera below the player's Z // (at distance * sin(Pitch)) so the player can be viewed from a low // angle. Clamped to -0.7 to avoid pushing the camera deep underground; // at -0.7 and Distance=8 the camera is ~5m below player-Z which will // clip terrain on hills but is OK on flat ground. 1.4 ≈ looking // straight down. Wider than the old [0.05, 1.4] so mouse-Y moves the // camera in both directions from the neutral [~20°] default. private const float PitchMin = -0.7f; private const float PitchMax = 1.4f; private float _playerYaw; private Vector3 _lookAt; public Matrix4x4 View => Matrix4x4.CreateLookAt(Position, _lookAt, Vector3.UnitZ); public Matrix4x4 Projection => Matrix4x4.CreatePerspectiveFieldOfView(FovY, Aspect, 1f, 5000f); /// /// Update the camera position to follow the player. /// public void Update(Vector3 playerPosition, float playerYaw) { _playerYaw = playerYaw; _lookAt = playerPosition + new Vector3(0f, 0f, EyeHeight); // Camera offset: behind the player (-forward direction) plus any // YawOffset for the hold-RMB inspect orbit mode. float effectiveYaw = playerYaw + YawOffset; float forwardX = MathF.Cos(effectiveYaw); float forwardY = MathF.Sin(effectiveYaw); 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); } /// /// Adjust pitch by a delta (from mouse Y movement). /// public void AdjustPitch(float delta) { Pitch = Math.Clamp(Pitch + delta, PitchMin, PitchMax); } /// /// Adjust distance (zoom) by a delta, clamped to [DistanceMin, DistanceMax]. /// public void AdjustDistance(float delta) { Distance = Math.Clamp(Distance + delta, DistanceMin, DistanceMax); } }