DayNight.SunShadow returns a cell offset opposite the sun's east-west position, longest near sunrise/sunset (low sun) and zero at noon/night, tilted slightly south so shadows fall in front of occluders. LightmapBuilder gains a directional shadow pass (MaxShadowCells length cap, SunShadowStrength darkening); LightmapSystem feeds it the current sun shadow each rebuild. Tests cover SunShadow's day-arc behaviour and the builder's directional darkening.
87 lines
3.5 KiB
C#
87 lines
3.5 KiB
C#
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>
|
||
/// 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; <paramref name="maxLength"/> caps
|
||
/// the dawn/dusk shadow length. Tilted slightly "south" (down) so shadows fall in front of objects.
|
||
/// </summary>
|
||
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));
|
||
}
|
||
|
||
/// <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);
|
||
}
|