feat(lighting): Phase G.2 LightSource + LightManager (data + selection)
Retail-faithful 8-light cap selection (r13 §12) — the fixed-function D3D pipeline's "hardware lights" constraint carried over to modern GL via UBO-per-draw. Core layer (AcDream.Core/Lighting): - LightSource: Kind (Directional/Point/Spot), WorldPosition, WorldForward, ColorLinear, Intensity, Range (hard cutoff), ConeAngle (spot), OwnerId (entity attachment), IsLit latch. - CellAmbientState: (AmbientColor, SunColor, SunDirection) sourced from R12 sky state for outdoor cells or EnvCell dat for indoor cells. - LightManager: Register/Unregister/UnregisterByOwner/Clear + Tick per frame. Selection matches r13 §12.2 exactly: 1) Skip unlit + directional. 2) Compute DistSq for every registered point/spot. 3) Drop lights outside Range² * 1.1 (10% slack prevents pop). 4) Sort by DistSq ascending; take up to 7 (slot 0 reserved for Sun). 5) Slot 0 = Sun (Directional); slots 1..7 = nearest in-range. Tests (9 new): - Register/Unregister/Idempotent register. - Tick picks top 8 by distance when 12 registered. - Range filter drops far lights (5.0 range, 20m away). - Range slack includes lights at exactly the boundary. - Sun reserved at slot 0 across ticks. - Unlit lights excluded; toggling IsLit brings them back. - UnregisterByOwner removes all owner's lights. - DistSq updated each tick for viewer movement. Build green, 596 tests pass (up from 587). Next: wire LightManager into the shader UBO pass (G.2 second commit) and feed Sun from WorldTimeService.CurrentSunDirection per frame. Ref: r13 §10.2 (D3D attenuation = none inside Range + hard cutoff), §12 (full port plan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6850d716a2
commit
a28a69af71
3 changed files with 340 additions and 0 deletions
63
src/AcDream.Core/Lighting/LightSource.cs
Normal file
63
src/AcDream.Core/Lighting/LightSource.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Lighting;
|
||||
|
||||
/// <summary>
|
||||
/// Retail light-source kinds (r13 §12.1).
|
||||
/// </summary>
|
||||
public enum LightKind
|
||||
{
|
||||
Directional = 0, // sun, moon — no position, infinite range
|
||||
Point = 1, // torch, fireplace, spell aura
|
||||
Spot = 2, // cone-shaped (rare in AC, used for a few specific lamps)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame light record. Used by <see cref="LightManager"/> and fed to
|
||||
/// the shader UBO on every draw call.
|
||||
///
|
||||
/// <para>
|
||||
/// Retail semantics (r13 §10.2):
|
||||
/// <list type="bullet">
|
||||
/// <item><description>
|
||||
/// Hard cutoff at <see cref="Range"/> — no smoothstep, no distance
|
||||
/// attenuation inside the range. "Crisp bubble of illumination."
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// Max 8 active lights (<see cref="LightManager.MaxActiveLights"/>),
|
||||
/// ranked by distance-to-viewer. Slot 0 is reserved for the sun.
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// <see cref="IsLit"/> latches the <c>SetLightHook</c> value so
|
||||
/// animations can toggle a light on/off (torch being lit, light
|
||||
/// crystal activating).
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class LightSource
|
||||
{
|
||||
public LightKind Kind;
|
||||
public Vector3 WorldPosition;
|
||||
public Vector3 WorldForward; // for Spot/Directional
|
||||
public Vector3 ColorLinear = Vector3.One; // R,G,B in [0,1], pre-brightness
|
||||
public float Intensity = 1f;
|
||||
public float Range = 10f; // metres, hard cutoff
|
||||
public float ConeAngle = 0f; // radians, Spot only
|
||||
public uint OwnerId; // attached entity id; 0 = world-global
|
||||
public bool IsLit = true; // SetLightHook latch
|
||||
|
||||
// Cached each frame by LightManager.
|
||||
public float DistSq;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-cell ambient + sun state. For outdoor cells this comes from the
|
||||
/// R12 sky state; for indoor cells the EnvCell dat carries a per-cell
|
||||
/// ambient override (r13 §3).
|
||||
/// </summary>
|
||||
public readonly record struct CellAmbientState(
|
||||
Vector3 AmbientColor,
|
||||
Vector3 SunColor,
|
||||
Vector3 SunDirection);
|
||||
Loading…
Add table
Add a link
Reference in a new issue