Files
mrgameeng/src/MrGameEng.Graphics/Lighting/DayNight.cs
T
Leonid Pershin 96e19c7c61 Lighting: directional sun shadows from the day/night sun angle
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.
2026-06-14 07:04:11 +03:00

87 lines
3.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 eastwest 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);
}