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); }