From 063cad1afe72b5bd806aaad061fd4b8ceef18435 Mon Sep 17 00:00:00 2001 From: "Senzaiken\\Uri" Date: Mon, 15 Dec 2025 17:08:47 -0500 Subject: [PATCH] Image overlay tool to add reference images onto the map --- CentrED/Config.cs | 13 ++ CentrED/Data/Languages/Czech.txt | 9 + CentrED/Data/Languages/English.txt | 11 +- CentrED/Data/Languages/Polski.txt | 11 +- "CentrED/Data/Languages/Portugu\303\252s.txt" | 11 +- CentrED/Languages/LangEntry.cs | 9 + CentrED/Map/ImageOverlay.cs | 143 +++++++++++++ CentrED/Map/MapManager.cs | 27 +++ CentrED/Renderer/Shaders/MapEffect.fx | 34 +++- CentrED/Renderer/Shaders/MapEffect.fxc | Bin 6692 -> 7588 bytes CentrED/UI/UIManager.cs | 3 +- CentrED/UI/Windows/ImageOverlayWindow.cs | 191 ++++++++++++++++++ 12 files changed, 455 insertions(+), 7 deletions(-) create mode 100644 CentrED/Map/ImageOverlay.cs create mode 100644 CentrED/UI/Windows/ImageOverlayWindow.cs diff --git a/CentrED/Config.cs b/CentrED/Config.cs index b54083d0..015befe8 100644 --- a/CentrED/Config.cs +++ b/CentrED/Config.cs @@ -4,6 +4,18 @@ namespace CentrED; +public class ImageOverlaySettings +{ + public string ImagePath = ""; + public bool Enabled; + public bool DrawAboveTerrain; + public int WorldX; + public int WorldY; + public float Scale = 1.0f; + public float Opacity = 1.0f; + public float Screen = 0.0f; +} + public class ConfigRoot { public string ActiveProfile = ""; @@ -19,6 +31,7 @@ public class ConfigRoot public string FontName = "ProggyClean.ttf"; public string Language = "English"; public UI.NumberDisplayFormat NumberFormat = UI.NumberDisplayFormat.HEX; + public ImageOverlaySettings ImageOverlay = new(); } public static class Config diff --git a/CentrED/Data/Languages/Czech.txt b/CentrED/Data/Languages/Czech.txt index 582d93a0..ce63de2e 100644 --- a/CentrED/Data/Languages/Czech.txt +++ b/CentrED/Data/Languages/Czech.txt @@ -261,3 +261,12 @@ SMOOTH_EDGE_TOOLTIP=Okraje nastavené šířky budou vyhlazeny podle okolního t BLEND_FACTOR=Faktor míchání BLEND_FACTOR_TOOLTIP=Jak moc bychom měli míchat s aktuální nadmořskou výškou terénu? SNAP_TO_TERRAIN=Přichytit k terénu +IMAGE_OVERLAY_WINDOW=Překrytí obrázkem +IMAGE_OVERLAY_LOAD=Načíst obrázek +IMAGE_OVERLAY_UNLOAD=Uvolnit +IMAGE_OVERLAY_DRAW_ABOVE=Kreslit nad terénem +IMAGE_OVERLAY_POSITION=Pozice (X, Y) +IMAGE_OVERLAY_SCALE=Měřítko +IMAGE_OVERLAY_OPACITY=Průhlednost +IMAGE_OVERLAY_SCREEN=Screen +IMAGE_OVERLAY_SIZE=Velikost (dlaždice) diff --git a/CentrED/Data/Languages/English.txt b/CentrED/Data/Languages/English.txt index ab17b4d8..e3f4d1f9 100644 --- a/CentrED/Data/Languages/English.txt +++ b/CentrED/Data/Languages/English.txt @@ -260,4 +260,13 @@ SMOOTH_EDGE=Smooth edge SMOOTH_EDGE_TOOLTIP=Edges of set width will be smoothened according to surrounding terrain BLEND_FACTOR=Blend factor BLEND_FACTOR_TOOLTIP=How much should we blend with current terrain altitude -SNAP_TO_TERRAIN=Snap to terrain \ No newline at end of file +SNAP_TO_TERRAIN=Snap to terrain +IMAGE_OVERLAY_WINDOW=Image Overlay +IMAGE_OVERLAY_LOAD=Load Image +IMAGE_OVERLAY_UNLOAD=Unload +IMAGE_OVERLAY_DRAW_ABOVE=Draw Above Terrain +IMAGE_OVERLAY_POSITION=Position (X, Y) +IMAGE_OVERLAY_SCALE=Scale +IMAGE_OVERLAY_OPACITY=Opacity +IMAGE_OVERLAY_SCREEN=Screen +IMAGE_OVERLAY_SIZE=Size (tiles) \ No newline at end of file diff --git a/CentrED/Data/Languages/Polski.txt b/CentrED/Data/Languages/Polski.txt index 16727662..f02f9742 100644 --- a/CentrED/Data/Languages/Polski.txt +++ b/CentrED/Data/Languages/Polski.txt @@ -260,4 +260,13 @@ SMOOTH_EDGE=Wygładź krawędzie SMOOTH_EDGE_TOOLTIP=Krawędzie na wyznaczonej szerokości będą wygładzone do obecnego terenu BLEND_FACTOR=Współczynnik blendu wysokościa BLEND_FACTOR_TOOLTIP=Jak duży wpływ obecna wysokość terenu ma mieć na wynik operacji -SNAP_TO_TERRAIN=Przyciągnij do terenu \ No newline at end of file +SNAP_TO_TERRAIN=Przyciągnij do terenu +IMAGE_OVERLAY_WINDOW=Nakładka obrazu +IMAGE_OVERLAY_LOAD=Wczytaj obraz +IMAGE_OVERLAY_UNLOAD=Usuń +IMAGE_OVERLAY_DRAW_ABOVE=Rysuj nad terenem +IMAGE_OVERLAY_POSITION=Pozycja (X, Y) +IMAGE_OVERLAY_SCALE=Skala +IMAGE_OVERLAY_OPACITY=Przezroczystość +IMAGE_OVERLAY_SCREEN=Screen +IMAGE_OVERLAY_SIZE=Rozmiar (kafelki) \ No newline at end of file diff --git "a/CentrED/Data/Languages/Portugu\303\252s.txt" "b/CentrED/Data/Languages/Portugu\303\252s.txt" index 3fc60d0c..ae20955f 100644 --- "a/CentrED/Data/Languages/Portugu\303\252s.txt" +++ "b/CentrED/Data/Languages/Portugu\303\252s.txt" @@ -260,4 +260,13 @@ SMOOTH_EDGE=Bordas lisas SMOOTH_EDGE_TOOLTIP=As bordas de largura definida serão suavizadas de acordo com o terreno ao redor BLEND_FACTOR=Fator de Mistura BLEND_FACTOR_TOOLTIP=Quanto devemos misturar com a altitude atual do terreno -SNAP_TO_TERRAIN=Encaixe-o no terreno \ No newline at end of file +SNAP_TO_TERRAIN=Encaixe-o no terreno +IMAGE_OVERLAY_WINDOW=Sobreposição de Imagem +IMAGE_OVERLAY_LOAD=Carregar Imagem +IMAGE_OVERLAY_UNLOAD=Descarregar +IMAGE_OVERLAY_DRAW_ABOVE=Desenhar Acima do Terreno +IMAGE_OVERLAY_POSITION=Posição (X, Y) +IMAGE_OVERLAY_SCALE=Escala +IMAGE_OVERLAY_OPACITY=Opacidade +IMAGE_OVERLAY_SCREEN=Screen +IMAGE_OVERLAY_SIZE=Tamanho (tiles) \ No newline at end of file diff --git a/CentrED/Languages/LangEntry.cs b/CentrED/Languages/LangEntry.cs index 4972f746..31f874c1 100644 --- a/CentrED/Languages/LangEntry.cs +++ b/CentrED/Languages/LangEntry.cs @@ -265,4 +265,13 @@ public enum LangEntry BLEND_FACTOR, BLEND_FACTOR_TOOLTIP, SNAP_TO_TERRAIN, + IMAGE_OVERLAY_WINDOW, + IMAGE_OVERLAY_LOAD, + IMAGE_OVERLAY_UNLOAD, + IMAGE_OVERLAY_DRAW_ABOVE, + IMAGE_OVERLAY_POSITION, + IMAGE_OVERLAY_SCALE, + IMAGE_OVERLAY_OPACITY, + IMAGE_OVERLAY_SCREEN, + IMAGE_OVERLAY_SIZE, } \ No newline at end of file diff --git a/CentrED/Map/ImageOverlay.cs b/CentrED/Map/ImageOverlay.cs new file mode 100644 index 00000000..5147d7a2 --- /dev/null +++ b/CentrED/Map/ImageOverlay.cs @@ -0,0 +1,143 @@ +using System.Numerics; +using CentrED.Renderer; +using Microsoft.Xna.Framework.Graphics; +using static CentrED.Constants; + +namespace CentrED.Map; + +public class ImageOverlay : MapObject +{ + public ImageOverlay() + { + for (int i = 0; i < 4; i++) + { + Vertices[i] = new MapVertex(Vector3.Zero, Vector3.Zero, Vector4.Zero, Vector3.Zero); + } + } + + private bool _enabled; + private bool _drawAboveTerrain; + private int _worldX; + private int _worldY; + private float _scale = 1.0f; + private float _opacity = 1.0f; + private float _screen = 0.0f; + + public bool Enabled + { + get => _enabled; + set => _enabled = value; + } + + public bool DrawAboveTerrain + { + get => _drawAboveTerrain; + set => _drawAboveTerrain = value; + } + + public int WorldX + { + get => _worldX; + set + { + _worldX = value; + UpdateVertices(); + } + } + + public int WorldY + { + get => _worldY; + set + { + _worldY = value; + UpdateVertices(); + } + } + + public float Scale + { + get => _scale; + set + { + _scale = Math.Max(0.1f, Math.Min(10.0f, value)); + UpdateVertices(); + } + } + + public float Opacity + { + get => _opacity; + set + { + _opacity = Math.Max(0.0f, Math.Min(1.0f, value)); + for (int i = 0; i < 4; i++) + { + Vertices[i].Hue.Z = _opacity; + } + } + } + + public float Screen + { + get => _screen; + set + { + _screen = Math.Max(0.0f, Math.Min(1.0f, value)); + for (int i = 0; i < 4; i++) + { + Vertices[i].Hue.Y = _screen; + } + } + } + + public int ImageWidth => Texture?.Width ?? 0; + public int ImageHeight => Texture?.Height ?? 0; + public float WidthInTiles => ImageWidth * _scale; + public float HeightInTiles => ImageHeight * _scale; + + public void LoadImage(GraphicsDevice gd, string path) + { + UnloadImage(); + + using var fileStream = File.OpenRead(path); + Texture = Texture2D.FromStream(gd, fileStream); + TextureBounds = new System.Drawing.Rectangle(0, 0, Texture.Width, Texture.Height); + UpdateVertices(); + } + + public void UnloadImage() + { + if (Texture != null) + { + Texture.Dispose(); + Texture = null!; + } + } + + private void UpdateVertices() + { + if (Texture == null) + return; + + float width = WidthInTiles * TILE_SIZE; + float height = HeightInTiles * TILE_SIZE; + float x = _worldX * TILE_SIZE; + float y = _worldY * TILE_SIZE; + + Vertices[0].Position = new Vector3(x, y, 0); + Vertices[0].Texture = new Vector3(0, 0, 0); + Vertices[1].Position = new Vector3(x + width, y, 0); + Vertices[1].Texture = new Vector3(1, 0, 0); + Vertices[2].Position = new Vector3(x, y + height, 0); + Vertices[2].Texture = new Vector3(0, 1, 0); + Vertices[3].Position = new Vector3(x + width, y + height, 0); + Vertices[3].Texture = new Vector3(1, 1, 0); + + for (int i = 0; i < 4; i++) + { + Vertices[i].Hue = new Vector4(0, _screen, _opacity, 0); + Vertices[i].Normal = Vector3.Zero; + } + } +} diff --git a/CentrED/Map/MapManager.cs b/CentrED/Map/MapManager.cs index 40b28807..a55b29ed 100644 --- a/CentrED/Map/MapManager.cs +++ b/CentrED/Map/MapManager.cs @@ -393,6 +393,7 @@ public static Vector2 ScreenToMapCoordinates(float x, float y) public StaticsManager StaticsManager = new(); public VirtualLayerObject VirtualLayer = VirtualLayerObject.Instance; //Used for drawing + public ImageOverlay ImageOverlay = new(); //Used for image overlay feature public void UpdateAllTiles() { @@ -1022,6 +1023,7 @@ public void Draw() return; _mapRenderer.SetRenderTarget(null); + Metrics.Measure("DrawImageOverlayBelow", () => DrawImageOverlay(false)); Metrics.Measure("DrawLand", () => DrawLand(Camera, ViewRange)); Metrics.Start("DrawLandGrid"); if (ShowGrid) @@ -1031,6 +1033,7 @@ public void Draw() Metrics.Stop("DrawLandGrid"); Metrics.Measure("DrawLandHeight", DrawLandHeight); Metrics.Measure("DrawStatics", () => DrawStatics(Camera, ViewRange)); + Metrics.Measure("DrawImageOverlayAbove", () => DrawImageOverlay(true)); Metrics.Measure("ApplyLights", ApplyLights); Metrics.Measure("DrawVirtualLayer", DrawVirtualLayer); Metrics.Stop("DrawMap"); @@ -1281,6 +1284,30 @@ public void DrawVirtualLayer() _mapRenderer.End(); } + public void DrawImageOverlay(bool aboveTerrain) + { + if (!ImageOverlay.Enabled || ImageOverlay.Texture == null) + { + return; + } + if (ImageOverlay.DrawAboveTerrain != aboveTerrain) + { + return; + } + MapEffect.WorldViewProj = Camera.FnaWorldViewProj; + MapEffect.CurrentTechnique = MapEffect.Techniques["ImageOverlay"]; + _mapRenderer.Begin + ( + MapEffect, + RasterizerState.CullNone, + SamplerState.LinearClamp, + DepthStencilState.None, + BlendState.AlphaBlend + ); + _mapRenderer.DrawMapObject(ImageOverlay, Vector4.Zero); + _mapRenderer.End(); + } + public bool Export = false; public string ExportPath = "render.png"; public int ExportWidth = 1920; diff --git a/CentrED/Renderer/Shaders/MapEffect.fx b/CentrED/Renderer/Shaders/MapEffect.fx index 6972cc70..b3e4587a 100644 --- a/CentrED/Renderer/Shaders/MapEffect.fx +++ b/CentrED/Renderer/Shaders/MapEffect.fx @@ -173,16 +173,36 @@ PSInput VirtualLayerVSMain(VSInput vin) { float4 VirtualLayerPSMain(PSInput pin) : SV_Target0 { //0.7 worked for me as it's not glitching when moving camera - if (abs(fmod(pin.Texture.x, TileSize)) < 0.7 || abs(fmod(pin.Texture.y, TileSize)) < 0.7) + if (abs(fmod(pin.Texture.x, TileSize)) < 0.7 || abs(fmod(pin.Texture.y, TileSize)) < 0.7) { return VirtualLayerBorderColor; - } - else + } + else { return VirtualLayerFillColor; } } +float4 ImageOverlayPSMain(PSInput pin) : SV_Target0 +{ + float4 color = tex2D(TextureSampler, pin.Texture.xy); + + // Hue.y = Screen (makes dark colors more transparent based on luminance) + // Hue.z = Opacity (true uniform transparency for all pixels) + float screen = pin.Hue.y; + float opacity = pin.Hue.z; + + // Apply uniform opacity to ALL channels (RGBA) - this fades the entire image + color *= opacity; + + // Then apply screen effect - makes dark pixels more transparent based on brightness + float luminance = dot(color.rgb, float3(0.299, 0.587, 0.114)); + float screenEffect = lerp(1.0, luminance, screen); + color.a *= screenEffect; + + return color; +} + Technique Terrain { Pass @@ -224,4 +244,12 @@ Technique VirtualLayer { VertexShader = compile vs_2_0 VirtualLayerVSMain(); PixelShader = compile ps_2_0 VirtualLayerPSMain(); } +} + +Technique ImageOverlay { + Pass + { + VertexShader = compile vs_2_0 TileVSMain(); + PixelShader = compile ps_2_0 ImageOverlayPSMain(); + } } \ No newline at end of file diff --git a/CentrED/Renderer/Shaders/MapEffect.fxc b/CentrED/Renderer/Shaders/MapEffect.fxc index 6ec16ff029eda72146337148fb9fc02fa1a767d7..2daea589d95f1769f033ff6e3eb216bd9f9dadc3 100644 GIT binary patch delta 257 zcmZ2tvc#H|k@Nq*CmUI3Gc$5eZe$W?Dx$q+8X79e{AR2>gcjCFD% zt1zR^z=GxKsVFfgzJu@De*Z~n==nvo62V&G!fe2?Rp2mlUW3dH~b diff --git a/CentrED/UI/UIManager.cs b/CentrED/UI/UIManager.cs index 7a79f34b..7e221033 100644 --- a/CentrED/UI/UIManager.cs +++ b/CentrED/UI/UIManager.cs @@ -134,7 +134,8 @@ public unsafe UIManager(GraphicsDevice gd, GameWindow window, Keymap keymap) AddWindow(Category.Tools, new ChatWindow()); AddWindow(Category.Tools, new HistoryWindow()); AddWindow(Category.Tools, new ServerAdminWindow()); - + AddWindow(Category.Tools, new ImageOverlayWindow()); + AddWindow(Category.Menu, new MinimapWindow()); DebugWindow = new DebugWindow(); diff --git a/CentrED/UI/Windows/ImageOverlayWindow.cs b/CentrED/UI/Windows/ImageOverlayWindow.cs new file mode 100644 index 00000000..ce911676 --- /dev/null +++ b/CentrED/UI/Windows/ImageOverlayWindow.cs @@ -0,0 +1,191 @@ +using CentrED.IO.Models; +using CentrED.Map; +using Hexa.NET.ImGui; +using static CentrED.Application; +using static CentrED.LangEntry; + +namespace CentrED.UI.Windows; + +public class ImageOverlayWindow : Window +{ + public override string Name => LangManager.Get(IMAGE_OVERLAY_WINDOW) + "###ImageOverlay"; + public override ImGuiWindowFlags WindowFlags => ImGuiWindowFlags.AlwaysAutoResize; + public override WindowState DefaultState => new() + { + IsOpen = false + }; + + private string _imagePath = ""; + private int[] _position = new int[2]; + private float _scale = 1.0f; + private float _opacity = 1.0f; + private float _screen = 0.0f; + private bool _settingsLoaded = false; + + private ImageOverlaySettings Settings => Config.Instance.ImageOverlay; + + private void LoadSettings() + { + if (_settingsLoaded) + return; + + _settingsLoaded = true; + var overlay = CEDGame.MapManager.ImageOverlay; + + _imagePath = Settings.ImagePath; + overlay.Enabled = Settings.Enabled; + overlay.DrawAboveTerrain = Settings.DrawAboveTerrain; + overlay.WorldX = Settings.WorldX; + overlay.WorldY = Settings.WorldY; + overlay.Scale = Settings.Scale; + overlay.Opacity = Settings.Opacity; + overlay.Screen = Settings.Screen; + + if (!string.IsNullOrEmpty(_imagePath) && File.Exists(_imagePath)) + { + try + { + overlay.LoadImage(CEDGame.GraphicsDevice, _imagePath); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to auto-load overlay image: {ex.Message}"); + } + } + } + + private void SaveSettings() + { + var overlay = CEDGame.MapManager.ImageOverlay; + + Settings.ImagePath = _imagePath; + Settings.Enabled = overlay.Enabled; + Settings.DrawAboveTerrain = overlay.DrawAboveTerrain; + Settings.WorldX = overlay.WorldX; + Settings.WorldY = overlay.WorldY; + Settings.Scale = overlay.Scale; + Settings.Opacity = overlay.Opacity; + Settings.Screen = overlay.Screen; + } + + protected override void InternalDraw() + { + var mapManager = CEDGame.MapManager; + var overlay = mapManager.ImageOverlay; + + LoadSettings(); + + if (ImGui.InputText(LangManager.Get(FILE_PATH), ref _imagePath, 512)) + { + SaveSettings(); + } + ImGui.SameLine(); + if (ImGui.Button("...")) + { + if (TinyFileDialogs.TryOpenFile( + LangManager.Get(SELECT_FILE), + Environment.CurrentDirectory, + ["*.png", "*.jpg", "*.jpeg", "*.bmp"], + "Image files", + false, + out var newPath)) + { + _imagePath = newPath; + SaveSettings(); + } + } + + var hasTexture = overlay.Texture != null; + ImGui.BeginDisabled(string.IsNullOrEmpty(_imagePath)); + if (ImGui.Button(LangManager.Get(IMAGE_OVERLAY_LOAD))) + { + try + { + overlay.LoadImage(CEDGame.GraphicsDevice, _imagePath); + _position[0] = overlay.WorldX; + _position[1] = overlay.WorldY; + SaveSettings(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to load image: {ex.Message}"); + } + } + ImGui.EndDisabled(); + + ImGui.SameLine(); + ImGui.BeginDisabled(!hasTexture); + if (ImGui.Button(LangManager.Get(IMAGE_OVERLAY_UNLOAD))) + { + overlay.UnloadImage(); + SaveSettings(); + } + ImGui.EndDisabled(); + + ImGui.Separator(); + + if (hasTexture) + { + ImGui.Text($"{LangManager.Get(IMAGE_OVERLAY_SIZE)}: {overlay.WidthInTiles:F1} x {overlay.HeightInTiles:F1}"); + ImGui.Text($"Image: {overlay.ImageWidth} x {overlay.ImageHeight} px"); + } + else + { + ImGui.TextDisabled("No image loaded"); + } + + ImGui.Separator(); + + var enabled = overlay.Enabled; + if (ImGui.Checkbox(LangManager.Get(ENABLED), ref enabled)) + { + overlay.Enabled = enabled; + SaveSettings(); + } + + var drawAbove = overlay.DrawAboveTerrain; + if (ImGui.Checkbox(LangManager.Get(IMAGE_OVERLAY_DRAW_ABOVE), ref drawAbove)) + { + overlay.DrawAboveTerrain = drawAbove; + SaveSettings(); + } + + _position[0] = overlay.WorldX; + _position[1] = overlay.WorldY; + if (ImGui.InputInt2(LangManager.Get(IMAGE_OVERLAY_POSITION), ref _position[0])) + { + overlay.WorldX = _position[0]; + overlay.WorldY = _position[1]; + SaveSettings(); + } + + _scale = overlay.Scale; + if (ImGui.SliderFloat(LangManager.Get(IMAGE_OVERLAY_SCALE), ref _scale, 0.1f, 10.0f, "%.2f")) + { + overlay.Scale = _scale; + SaveSettings(); + } + + _opacity = overlay.Opacity; + if (ImGui.SliderFloat(LangManager.Get(IMAGE_OVERLAY_OPACITY), ref _opacity, 0.0f, 1.0f, "%.2f")) + { + overlay.Opacity = _opacity; + SaveSettings(); + } + + _screen = overlay.Screen; + if (ImGui.SliderFloat(LangManager.Get(IMAGE_OVERLAY_SCREEN), ref _screen, 0.0f, 1.0f, "%.2f")) + { + overlay.Screen = _screen; + SaveSettings(); + } + + if (ImGui.Button("Set to View Center")) + { + var tilePos = mapManager.TilePosition; + overlay.WorldX = tilePos.X; + overlay.WorldY = tilePos.Y; + SaveSettings(); + } + } +}