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 943b4320..96b0d84b 100644 --- a/CentrED/Data/Languages/Czech.txt +++ b/CentrED/Data/Languages/Czech.txt @@ -261,6 +261,15 @@ 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) RADIUS=Poloměr BRUSH_RADIUS=Poloměr štětce SAMPLE_RADIUS=Poloměr vzorkování diff --git a/CentrED/Data/Languages/English.txt b/CentrED/Data/Languages/English.txt index 85f95c89..0f42a267 100644 --- a/CentrED/Data/Languages/English.txt +++ b/CentrED/Data/Languages/English.txt @@ -261,6 +261,15 @@ SMOOTH_EDGE_TOOLTIP=Edges of set width will be smoothened according to surroundi BLEND_FACTOR=Blend factor BLEND_FACTOR_TOOLTIP=How much should we blend with current terrain altitude 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) RADIUS=Radius BRUSH_RADIUS=Brush radius SAMPLE_RADIUS=Sample radius @@ -268,4 +277,4 @@ SAMPLE_RADIUS_TOOLTIP=How far to look for neighboring tiles when calculating ave STRENGTH=Strength EDGE_FALLOFF=Edge falloff FALLOFF_START=Falloff start -FALLOFF_START_TOOLTIP=Percentage of radius where falloff begins \ No newline at end of file +FALLOFF_START_TOOLTIP=Percentage of radius where falloff begins diff --git a/CentrED/Data/Languages/Polski.txt b/CentrED/Data/Languages/Polski.txt index 47b391b4..4d9b9331 100644 --- a/CentrED/Data/Languages/Polski.txt +++ b/CentrED/Data/Languages/Polski.txt @@ -261,6 +261,15 @@ SMOOTH_EDGE_TOOLTIP=Krawędzie na wyznaczonej szerokości będą wygładzone do 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 +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) RADIUS=Promień BRUSH_RADIUS=Promień pędzla SAMPLE_RADIUS=Promień próbkowania @@ -268,4 +277,4 @@ SAMPLE_RADIUS_TOOLTIP=Jak daleko szukać sąsiednich kafelków przy obliczaniu STRENGTH=Siła EDGE_FALLOFF=Zanikanie krawędzi FALLOFF_START=Początek zanikania -FALLOFF_START_TOOLTIP=Procent promienia, od którego zaczyna się zanikanie \ No newline at end of file +FALLOFF_START_TOOLTIP=Procent promienia, od którego zaczyna się zanikanie diff --git "a/CentrED/Data/Languages/Portugu\303\252s.txt" "b/CentrED/Data/Languages/Portugu\303\252s.txt" index e9ea411a..4ebda013 100644 --- "a/CentrED/Data/Languages/Portugu\303\252s.txt" +++ "b/CentrED/Data/Languages/Portugu\303\252s.txt" @@ -261,6 +261,15 @@ SMOOTH_EDGE_TOOLTIP=As bordas de largura definida serão suavizadas de acordo co BLEND_FACTOR=Fator de Mistura BLEND_FACTOR_TOOLTIP=Quanto devemos misturar com a altitude atual do terreno 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) RADIUS=Raio BRUSH_RADIUS=Raio do pincel SAMPLE_RADIUS=Raio de amostragem @@ -268,4 +277,4 @@ SAMPLE_RADIUS_TOOLTIP=Até onde procurar blocos vizinhos ao calcular a média STRENGTH=Intensidade EDGE_FALLOFF=Atenuação de borda FALLOFF_START=Início da atenuação -FALLOFF_START_TOOLTIP=Porcentagem do raio onde a atenuação começa \ No newline at end of file +FALLOFF_START_TOOLTIP=Porcentagem do raio onde a atenuação começa diff --git a/CentrED/Languages/LangEntry.cs b/CentrED/Languages/LangEntry.cs index ac0f4652..1efef427 100644 --- a/CentrED/Languages/LangEntry.cs +++ b/CentrED/Languages/LangEntry.cs @@ -265,6 +265,15 @@ 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, RADIUS, BRUSH_RADIUS, SAMPLE_RADIUS, 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 8efe70fa..ef00c26c 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() { @@ -1023,6 +1024,7 @@ public void Draw() return; _mapRenderer.SetRenderTarget(null); + Metrics.Measure("DrawImageOverlayBelow", () => DrawImageOverlay(false)); Metrics.Measure("DrawLand", () => DrawLand(Camera, ViewRange)); Metrics.Start("DrawLandGrid"); if (ShowGrid) @@ -1032,6 +1034,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"); @@ -1282,6 +1285,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 6ec16ff0..2daea589 100644 Binary files a/CentrED/Renderer/Shaders/MapEffect.fxc and b/CentrED/Renderer/Shaders/MapEffect.fxc differ 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(); + } + } +}