using System;
using System.Numerics;
using System.Runtime.InteropServices;
using AcDream.Core.World;
namespace AcDream.Core.Lighting;
///
/// GPU-facing scene-lighting UBO layout. Matches the GLSL block in
/// mesh.frag / mesh_instanced.vert / terrain.vert
/// bound at binding=1. std140-compliant — each vec4 member
/// lives on a 16-byte boundary, arrays of vec4 pack contiguously,
/// and no pad elements are required because the struct's fields are
/// already 16-byte-aligned.
///
///
/// Layout (r13 §12.3 extended with R12 §13.2 sun+fog):
///
/// struct Light {
/// vec4 posAndKind; // xyz = world pos, w = kind (0=dir, 1=point, 2=spot)
/// vec4 dirAndRange; // xyz = forward, w = range (metres, hard cutoff)
/// vec4 colorAndIntensity; // xyz = RGB linear, w = intensity scalar
/// vec4 coneAngleEtc; // x = cone (rad), y=unused, z=unused, w=unused
/// };
///
/// layout(std140, binding = 1) uniform SceneLighting {
/// Light uLights[8]; // 8 * 64 bytes = 512 bytes
/// vec4 uCellAmbient; // xyz = ambient RGB, w = active light count
/// vec4 uFogParams; // x = fogStart, y = fogEnd, z = lightningFlash, w = fogMode
/// vec4 uFogColor; // xyz = fog RGB, w = unused
/// vec4 uCameraAndTime; // xyz = camera world pos, w = day fraction (debug / sky shader)
/// };
///
///
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UboLight
{
public Vector4 PosAndKind;
public Vector4 DirAndRange;
public Vector4 ColorAndIntensity;
public Vector4 ConeAngleEtc;
/// Pack a into UBO-ready bytes.
public static UboLight FromSource(LightSource ls)
{
return new UboLight
{
PosAndKind = new Vector4(ls.WorldPosition, (float)(int)ls.Kind),
DirAndRange = new Vector4(ls.WorldForward, ls.Range),
ColorAndIntensity = new Vector4(ls.ColorLinear, ls.Intensity),
ConeAngleEtc = new Vector4(ls.ConeAngle, 0f, 0f, 0f),
};
}
/// Packed "zero" light — stored in unused UBO slots so shaders
/// don't read garbage. dirAndRange.w = 0 disables the light
/// even if the active-count sentinel is wrong.
public static UboLight Empty => new()
{
PosAndKind = Vector4.Zero,
DirAndRange = Vector4.Zero,
ColorAndIntensity = Vector4.Zero,
ConeAngleEtc = Vector4.Zero,
};
}
///
/// Full CPU-side scene-lighting UBO buffer. One per frame; lives on the
/// render thread. The GL-side wrapper (SceneLightingUboBinding
/// in AcDream.App) uploads this to binding=1 once per frame.
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SceneLightingUbo
{
// 8 lights × 64 bytes = 512 bytes
public UboLight Light0;
public UboLight Light1;
public UboLight Light2;
public UboLight Light3;
public UboLight Light4;
public UboLight Light5;
public UboLight Light6;
public UboLight Light7;
public Vector4 CellAmbient; // xyz = ambient RGB, w = active count
public Vector4 FogParams; // x = fogStart, y = fogEnd, z = flash, w = fogMode
public Vector4 FogColor; // xyz = color, w = reserved
public Vector4 CameraAndTime; // xyz = camera pos, w = day fraction
public const int SizeInBytes = 8 * 64 + 4 * 16; // 576
public const int BindingPoint = 1;
///
/// Build the full per-frame UBO payload from:
///
/// - An already-ticked .
/// - The current (sky + weather).
/// - The current camera world position (sky shader needs it, fog shader needs it).
/// - The current day fraction (sky shader needs it for scrolling clouds).
///
///
public static SceneLightingUbo Build(
LightManager lights,
in AtmosphereSnapshot atmo,
Vector3 cameraWorldPos,
float dayFraction)
{
ArgumentNullException.ThrowIfNull(lights);
var ubo = new SceneLightingUbo();
// Pack up to 8 lights. Empty slots stay zero.
var active = lights.Active;
int count = active.Length;
if (count > 8) count = 8;
for (int i = 0; i < 8; i++)
{
var packed = (i < count && active[i] is not null)
? UboLight.FromSource(active[i]!)
: UboLight.Empty;
SetLightAt(ref ubo, i, packed);
}
ubo.CellAmbient = new Vector4(lights.CurrentAmbient.AmbientColor, count);
ubo.FogParams = new Vector4(
atmo.FogStart,
atmo.FogEnd,
atmo.LightningFlash,
(float)(int)atmo.FogMode);
ubo.FogColor = new Vector4(atmo.FogColor, 0f);
ubo.CameraAndTime = new Vector4(cameraWorldPos, dayFraction);
return ubo;
}
private static void SetLightAt(ref SceneLightingUbo ubo, int i, in UboLight v)
{
switch (i)
{
case 0: ubo.Light0 = v; break;
case 1: ubo.Light1 = v; break;
case 2: ubo.Light2 = v; break;
case 3: ubo.Light3 = v; break;
case 4: ubo.Light4 = v; break;
case 5: ubo.Light5 = v; break;
case 6: ubo.Light6 = v; break;
case 7: ubo.Light7 = v; break;
}
}
}