using Microsoft.Xna.Framework;
using MrGameEng.Core;
namespace MrGameEng.Lighting;
/// Colors for the day/night ambient: the tint at deep night and at midday.
public readonly record struct DayNightSettings
{
/// Ambient tint at midnight (a dim, cool night).
public Color NightColor { get; init; }
/// Ambient tint at noon (white = no tint).
public Color DayColor { get; init; }
/// Sensible defaults: a dim blue night, untinted day.
public static DayNightSettings Default =>
new() { NightColor = new Color(45, 55, 95), DayColor = Color.White };
}
///
/// Day/night ambient driven by : a daylight factor (0 at midnight,
/// 1 at noon) and an ambient color lerped from night to day. is the sampleable
/// light interface read by both the renderer (scene darkening) and the simulation (plant light);
/// it returns the global daylight today and will return locally-shadowed light once point lights and
/// occlusion land. Pure read-side, GPU-free and deterministic.
///
public sealed class DayNight
{
private readonly Calendar _calendar;
private readonly DayNightSettings _settings;
/// Creates the cycle reading with the given .
public DayNight(Calendar calendar, DayNightSettings settings)
{
_calendar = calendar ?? throw new ArgumentNullException(nameof(calendar));
_settings = settings;
}
/// Daylight factor in [0, 1] — 0 at midnight, 1 at noon, 0 again at midnight.
public float Daylight
{
get
{
var value = -MathF.Cos(MathF.Tau * _calendar.DayProgress);
return value > 0f ? value : 0f;
}
}
/// Global light intensity in [0, 1]; same as today.
public float Intensity => Daylight;
/// Light intensity at a world point in [0, 1]. Global today; local (with shadows) later.
public float SampleAt(Vector2 world) => Daylight;
///
/// Offset, in grid cells, of the shadow an occluder casts under the current sun: opposite the
/// sun's east–west position and longest near sunrise/sunset (low sun), shrinking to zero at noon
/// and at night. Feeds the lightmap's directional shadow pass; caps
/// the dawn/dusk shadow length. Tilted slightly "south" (down) so shadows fall in front of objects.
///
public Vector2 SunShadow(float maxLength)
{
var day = (_calendar.DayProgress - 0.25f) / 0.5f; // daytime fraction over [06:00, 18:00]
if (day <= 0f || day >= 1f)
{
return Vector2.Zero; // night — no sun; the ambient floor handles darkness
}
var altitude = MathF.Sin(day * MathF.PI); // 0 at dawn/dusk, 1 at noon
var direction = new Vector2(2f * day - 1f, 0.4f); // sun east→west ⇒ shadow west→east, tilted south
direction.Normalize();
return direction * (maxLength * (1f - altitude));
}
/// Ambient tint for the scene: night color at night, day color at noon, eased between.
public Color Ambient
{
get
{
var t = Smoothstep(Daylight);
return Color.Lerp(_settings.NightColor, _settings.DayColor, t);
}
}
private static float Smoothstep(float x) => x * x * (3f - 2f * x);
}