using System;
using System.Collections.Generic;
namespace AcDream.Core.Lighting;
///
/// Packs a point-light snapshot into the flat float layout the bindless mesh
/// shader reads at SSBO binding=4 (mesh_modern.vert GlobalLight gLights[]):
/// 16 floats (4 vec4) per light — posAndKind, dirAndRange, colorAndIntensity,
/// coneAngleEtc. Pure (no GL), so both WbDrawDispatcher and
/// EnvCellRenderer share ONE layout and cannot drift.
///
public static class GlobalLightPacker
{
public const int FloatsPerLight = 16;
///
/// Fill (grown + zero-cleared as needed) with the
/// packed snapshot; returns the light count n. The buffer always has at
/// least floats (so a zero-light frame still
/// uploads a non-empty SSBO). Callers upload max(n,1) * FloatsPerLight floats.
///
public static int Pack(IReadOnlyList? snapshot, ref float[] buffer)
{
int n = snapshot?.Count ?? 0;
int floatsNeeded = Math.Max(n, 1) * FloatsPerLight;
if (buffer.Length < floatsNeeded)
buffer = new float[floatsNeeded + FloatsPerLight * 16];
Array.Clear(buffer, 0, floatsNeeded);
for (int i = 0; i < n; i++)
{
var L = snapshot![i];
int o = i * FloatsPerLight;
// posAndKind (xyz world pos, w kind)
buffer[o + 0] = L.WorldPosition.X;
buffer[o + 1] = L.WorldPosition.Y;
buffer[o + 2] = L.WorldPosition.Z;
buffer[o + 3] = (int)L.Kind;
// dirAndRange (xyz forward, w range)
buffer[o + 4] = L.WorldForward.X;
buffer[o + 5] = L.WorldForward.Y;
buffer[o + 6] = L.WorldForward.Z;
buffer[o + 7] = L.Range; // w = Range = Falloff × static_light_factor (1.3), pre-multiplied by LightInfoLoader — NOT the raw dat Falloff
// colorAndIntensity (xyz linear colour, w intensity)
buffer[o + 8] = L.ColorLinear.X;
buffer[o + 9] = L.ColorLinear.Y;
buffer[o + 10] = L.ColorLinear.Z;
buffer[o + 11] = L.Intensity;
// coneAngleEtc (x cone radians; y = #143 dynamic flag; zw reserved)
buffer[o + 12] = L.ConeAngle;
buffer[o + 13] = L.IsDynamic ? 1f : 0f; // shader: 1/d D3D attenuation vs static 1/d³
}
return n;
}
}