Add Climate and day/night ambient lighting
CI / build-test (push) Successful in 1m11s

Climate (Core) layers a continuous seasonal-plus-daily temperature and the
current season over the Calendar, registered via context.UseClimate. A new
Lighting module (Graphics) adds DayNight: a daylight factor and ambient
color from the calendar's time of day, pushed into the renderer's new
AmbientLight (multiplied into world-space sprites only, so the scene
darkens at night while screen-space overlays stay readable) and exposed as
a sampleable SampleAt for the simulation. Both deterministic and GPU-free,
covered by tests; docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-12 16:45:49 +03:00
co-authored by Claude Opus 4.8
parent f39ee9e787
commit a684c23cd9
8 changed files with 413 additions and 5 deletions
@@ -0,0 +1,66 @@
using Microsoft.Xna.Framework;
using MrGameEng.Core;
namespace MrGameEng.Lighting;
/// <summary>Colors for the day/night ambient: the tint at deep night and at midday.</summary>
public readonly record struct DayNightSettings
{
/// <summary>Ambient tint at midnight (a dim, cool night).</summary>
public Color NightColor { get; init; }
/// <summary>Ambient tint at noon (white = no tint).</summary>
public Color DayColor { get; init; }
/// <summary>Sensible defaults: a dim blue night, untinted day.</summary>
public static DayNightSettings Default =>
new() { NightColor = new Color(45, 55, 95), DayColor = Color.White };
}
/// <summary>
/// Day/night ambient driven by <see cref="Calendar.DayProgress"/>: a daylight factor (0 at midnight,
/// 1 at noon) and an ambient color lerped from night to day. <see cref="SampleAt"/> 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.
/// </summary>
public sealed class DayNight
{
private readonly Calendar _calendar;
private readonly DayNightSettings _settings;
/// <summary>Creates the cycle reading <paramref name="calendar"/> with the given <paramref name="settings"/>.</summary>
public DayNight(Calendar calendar, DayNightSettings settings)
{
_calendar = calendar ?? throw new ArgumentNullException(nameof(calendar));
_settings = settings;
}
/// <summary>Daylight factor in <c>[0, 1]</c> — 0 at midnight, 1 at noon, 0 again at midnight.</summary>
public float Daylight
{
get
{
var value = -MathF.Cos(MathF.Tau * _calendar.DayProgress);
return value > 0f ? value : 0f;
}
}
/// <summary>Global light intensity in <c>[0, 1]</c>; same as <see cref="Daylight"/> today.</summary>
public float Intensity => Daylight;
/// <summary>Light intensity at a world point in <c>[0, 1]</c>. Global today; local (with shadows) later.</summary>
public float SampleAt(Vector2 world) => Daylight;
/// <summary>Ambient tint for the scene: night color at night, day color at noon, eased between.</summary>
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);
}