using System.Collections.Generic;
using System.Text.Json;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.UI.Lights;
using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Core.UI.Tool.LightEditor
{
///
/// Data controller for the light editor tool. Maintains a list of
/// objects and feeds up to 10 lights plus one
/// sentinel entry into the "timed_sprite" shader material for
/// real-time preview. Exports lights as JSON ()
/// or as a scenario DSL snippet ().
/// Uses a dirty-flag pattern so shader uniform updates only occur when
/// the light list changes.
///
public class LightEditorController
{
/// The editable list of light definitions. Capped at 10 for shader preview.
public List Lights { get; } = new();
private readonly Vector2[] previewPositions = new Vector2[11];
private readonly Color[] previewColors = new Color[11];
private readonly float[] previewRadii = new float[11];
private readonly float[] previewInfluence = new float[11];
private bool dirty = true;
/// Initialises preview arrays with out-of-bounds sentinel values.
public LightEditorController()
{
for (int i = 0; i < 11; i++)
{
previewPositions[i] = new Vector2(-1000f, -1000f);
previewRadii[i] = 0f;
previewInfluence[i] = 0f;
previewColors[i] = UiTokens.White;
}
}
///
/// Creates a using the "timed_sprite" shader
/// with night-time weights applied. Returns null if the shader is not found.
///
public ShaderMaterial CreatePreviewMaterial()
{
var shader = Shaders.Resolve("timed_sprite");
if (shader == null)
return null;
var mat = new ShaderMaterial { Shader = shader };
var night = new Vector4(0, 0, 0, 1);
mat.SetShaderParameter("time_weights", night);
mat.SetShaderParameter("use_time", true);
return mat;
}
/// Sets the background texture uniform on the preview shader material.
public void SetBackgroundForPreview(Texture2D texture, ShaderMaterial material)
{
if (material != null)
material.SetShaderParameter("background_map", texture);
}
/// Sets the depth mask texture uniform on the preview shader material.
public void SetDepthForPreview(Texture2D texture, ShaderMaterial material)
{
if (material != null)
material.SetShaderParameter("depth_mask", texture);
}
/// Sets the albedo texture uniform on the preview shader material.
public void SetAlbedoForPreview(Texture2D texture, ShaderMaterial material)
{
if (material != null)
material.SetShaderParameter("albedo_map", texture);
}
/// Appends a to the list and marks the preview dirty.
public void AddLight(LightDef light)
{
Lights.Add(light);
dirty = true;
}
/// Removes the light at . No-op if out of range.
public void RemoveLight(int index)
{
if (index < 0 || index >= Lights.Count)
return;
Lights.RemoveAt(index);
dirty = true;
}
/// Removes all lights from the list and marks dirty.
public void Clear()
{
Lights.Clear();
dirty = true;
}
/// Forces the preview to be recalculated on the next call.
public void MarkDirty()
{
dirty = true;
}
///
/// Packs up to 10 lights plus a sentinel into shader uniform arrays.
/// Only executes if dirty is true. Converts each light's
/// normalised position to pixel coordinates relative to the preview area.
///
public void UpdatePreview(ShaderMaterial material, Vector2 previewGlobalPos, Vector2 previewSize)
{
if (material == null || !dirty)
return;
for (int i = 0; i < 11; i++)
{
previewPositions[i] = new Vector2(-1000f, -1000f);
previewRadii[i] = 0f;
previewInfluence[i] = 0f;
previewColors[i] = UiTokens.White;
}
int count = Mathf.Min(Lights.Count, 10);
for (int i = 0; i < count; i++)
{
var pixelPos = Lights[i].ToPixelPos(previewSize);
previewPositions[i] = new Vector2(
pixelPos.X + previewGlobalPos.X,
pixelPos.Y + previewGlobalPos.Y);
previewRadii[i] = Lights[i].Radius;
previewInfluence[i] = Lights[i].Influence;
previewColors[i] = Lights[i].ToColor();
}
var posArray = new Godot.Collections.Array();
var colArray = new Godot.Collections.Array();
var radArray = new Godot.Collections.Array();
var infArray = new Godot.Collections.Array();
for (int i = 0; i < 11; i++)
{
posArray.Add(previewPositions[i]);
colArray.Add(previewColors[i]);
radArray.Add(previewRadii[i]);
infArray.Add(previewInfluence[i]);
}
material.SetShaderParameter("light_count", count);
material.SetShaderParameter("light_positions", posArray);
material.SetShaderParameter("light_colors", colArray);
material.SetShaderParameter("light_radii", radArray);
material.SetShaderParameter("light_influence", infArray);
material.SetShaderParameter("light_radius_scale", 1.0f);
dirty = false;
}
/// Serialises the light list as a compact JSON string.
public string ExportLightsJson()
{
return JsonSerializer.Serialize(Lights, Cthangover.Core.Utils.JsonOptionsProvider.WriteCompactOptions);
}
/// Produces a light_set scenario DSL command with the lights JSON as its argument.
public string ExportScenarioSnippet()
{
var json = ExportLightsJson();
return $"light_set \"{json.Replace("\"", "\\\"")}\"";
}
}
}