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("\"", "\\\"")}\""; } } }