From ec1e07c832e5b1e431c07cadf7f21d840292e77d Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Wed, 23 Feb 2022 09:23:39 -0700 Subject: [PATCH] Map renderer decal painter (#6754) * Map renderer decal painter * not yet * no conk dick --- Content.MapRenderer/Painters/DecalData.cs | 18 ++++ Content.MapRenderer/Painters/DecalPainter.cs | 93 +++++++++++++++++++ Content.MapRenderer/Painters/EntityPainter.cs | 2 +- Content.MapRenderer/Painters/GridPainter.cs | 80 +++++++++++++--- 4 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 Content.MapRenderer/Painters/DecalData.cs create mode 100644 Content.MapRenderer/Painters/DecalPainter.cs diff --git a/Content.MapRenderer/Painters/DecalData.cs b/Content.MapRenderer/Painters/DecalData.cs new file mode 100644 index 0000000000..e0fbf6aed7 --- /dev/null +++ b/Content.MapRenderer/Painters/DecalData.cs @@ -0,0 +1,18 @@ +using Content.Shared.Decals; + +namespace Content.MapRenderer.Painters; + +public sealed class DecalData +{ + public DecalData(Decal decal, float x, float y) + { + Decal = decal; + X = x; + Y = y; + } + + public Decal Decal; + + public float X; + public float Y; +} diff --git a/Content.MapRenderer/Painters/DecalPainter.cs b/Content.MapRenderer/Painters/DecalPainter.cs new file mode 100644 index 0000000000..be501c1df9 --- /dev/null +++ b/Content.MapRenderer/Painters/DecalPainter.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Content.Shared.Decals; +using Content.Shared.SubFloor; +using Robust.Client.ResourceManagement; +using Robust.Client.Utility; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using static Robust.Client.Graphics.RSI.State; +using static Robust.UnitTesting.RobustIntegrationTest; + +namespace Content.MapRenderer.Painters; + +public sealed class DecalPainter +{ + private readonly IResourceCache _cResourceCache; + + private readonly IPrototypeManager _sPrototypeManager; + + private readonly Dictionary _decalTextures = new(); + + public DecalPainter(ClientIntegrationInstance client, ServerIntegrationInstance server) + { + _cResourceCache = client.ResolveDependency(); + _sPrototypeManager = server.ResolveDependency(); + } + + public void Run(Image canvas, List decals) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + decals.Sort(Comparer.Create((x, y) => x.Decal.ZIndex.CompareTo(y.Decal.ZIndex))); + + foreach (var proto in _sPrototypeManager.EnumeratePrototypes()) + { + _decalTextures.Add(proto.ID, proto.Sprite); + } + + foreach (var decal in decals) + { + Run(canvas, decal); + } + + Console.WriteLine($"{nameof(DecalPainter)} painted {decals.Count} decals in {(int) stopwatch.Elapsed.TotalMilliseconds} ms"); + } + + private void Run(Image canvas, DecalData data) + { + var decal = data.Decal; + if (!_decalTextures.TryGetValue(decal.Id, out var sprite)) + { + Console.WriteLine($"Decal {decal.Id} did not have an associated prototype."); + return; + } + + Stream stream; + if (sprite is SpriteSpecifier.Texture texture) + { + stream = _cResourceCache.ContentFileRead(texture.TexturePath); + } + else if (sprite is SpriteSpecifier.Rsi rsi) + { + stream = _cResourceCache.ContentFileRead($"/Textures/{rsi.RsiPath}/{rsi.RsiState}.png"); + } + else + { + // Don't support + return; + } + + var image = Image.Load(stream); + + image.Mutate(o => o.Rotate((float) -decal.Angle.Degrees)); + var coloredImage = new Image(image.Width, image.Height); + Color color = decal.Color?.ConvertImgSharp() ?? Color.White; + coloredImage.Mutate(o => o.BackgroundColor(color)); + + image.Mutate(o => o + .DrawImage(coloredImage, PixelColorBlendingMode.Multiply, PixelAlphaCompositionMode.SrcAtop, 1.0f) + .Flip(FlipMode.Vertical)); + + // Very unsure why the - 1 is needed in the first place but all decals are off by exactly one pixel otherwise + // Woohoo! + canvas.Mutate(o => o.DrawImage(image, new Point((int) data.X, (int) data.Y - 1), 1.0f)); + } +} diff --git a/Content.MapRenderer/Painters/EntityPainter.cs b/Content.MapRenderer/Painters/EntityPainter.cs index fd2a3e6088..ba99dfeb2e 100644 --- a/Content.MapRenderer/Painters/EntityPainter.cs +++ b/Content.MapRenderer/Painters/EntityPainter.cs @@ -44,7 +44,7 @@ public sealed class EntityPainter Run(canvas, entity); } - Console.WriteLine($"{nameof(GridPainter)} painted {entities.Count} entities in {(int) stopwatch.Elapsed.TotalMilliseconds} ms"); + Console.WriteLine($"{nameof(EntityPainter)} painted {entities.Count} entities in {(int) stopwatch.Elapsed.TotalMilliseconds} ms"); } public void Run(Image canvas, EntityData entity) diff --git a/Content.MapRenderer/Painters/GridPainter.cs b/Content.MapRenderer/Painters/GridPainter.cs index d08c91f3b1..2d37eb7ff8 100644 --- a/Content.MapRenderer/Painters/GridPainter.cs +++ b/Content.MapRenderer/Painters/GridPainter.cs @@ -2,10 +2,14 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using Content.Server.Decals; +using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Timing; +using Robust.Shared.Utility; using SixLabors.ImageSharp; using static Robust.UnitTesting.RobustIntegrationTest; @@ -14,24 +18,30 @@ namespace Content.MapRenderer.Painters public sealed class GridPainter { private readonly EntityPainter _entityPainter; + private readonly DecalPainter _decalPainter; private readonly IEntityManager _cEntityManager; private readonly IMapManager _cMapManager; private readonly IEntityManager _sEntityManager; + private readonly IMapManager _sMapManager; private readonly ConcurrentDictionary> _entities; + private readonly Dictionary> _decals; public GridPainter(ClientIntegrationInstance client, ServerIntegrationInstance server) { _entityPainter = new EntityPainter(client, server); + _decalPainter = new DecalPainter(client, server); _cEntityManager = client.ResolveDependency(); _cMapManager = client.ResolveDependency(); _sEntityManager = server.ResolveDependency(); + _sMapManager = server.ResolveDependency(); _entities = GetEntities(); + _decals = GetDecals(); } public void Run(Image gridCanvas, IMapGrid grid) @@ -45,6 +55,14 @@ namespace Content.MapRenderer.Painters return; } + if (!_decals.TryGetValue(grid.Index, out var decals)) + { + Console.WriteLine($"No decals found on grid {grid.Index}"); + return; + } + + // Decals are always painted before entities. + _decalPainter.Run(gridCanvas, decals); _entityPainter.Run(gridCanvas, entities); Console.WriteLine($"{nameof(GridPainter)} painted grid {grid.Index} in {(int) stopwatch.Elapsed.TotalMilliseconds} ms"); } @@ -75,29 +93,63 @@ namespace Content.MapRenderer.Painters $"No sprite component found on an entity for which a server sprite component exists. Prototype id: {prototype.ID}"); } - var xOffset = 0; - var yOffset = 0; - var tileSize = 1; - var transform = _sEntityManager.GetComponent(entity); if (_cMapManager.TryGetGrid(transform.GridID, out var grid)) { - xOffset = (int) Math.Abs(grid.LocalBounds.Left); - yOffset = (int) Math.Abs(grid.LocalBounds.Bottom); - tileSize = grid.TileSize; + var position = transform.LocalPosition; + + var (x, y) = TransformLocalPosition(position, grid); + var data = new EntityData(sprite, x, y); + + components.GetOrAdd(transform.GridID, _ => new List()).Add(data); } - - var position = transform.LocalPosition; - var x = ((float) Math.Floor(position.X) + xOffset) * tileSize * TilePainter.TileImageSize; - var y = ((float) Math.Floor(position.Y) + yOffset) * tileSize * TilePainter.TileImageSize; - var data = new EntityData(sprite, x, y); - - components.GetOrAdd(transform.GridID, _ => new List()).Add(data); } Console.WriteLine($"Found {components.Values.Sum(l => l.Count)} entities on {components.Count} grids in {(int) stopwatch.Elapsed.TotalMilliseconds} ms"); return components; } + + private Dictionary> GetDecals() + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var decals = new Dictionary>(); + + foreach (var grid in _sMapManager.GetAllGrids()) + { + // TODO this needs to use the client entity manager because the client + // actually has the correct z-indices for decals for some reason when the server doesn't, + // BUT can't do that yet because the client hasn't actually received everything yet + // for some reason decal moment i guess. + if (_sEntityManager.TryGetComponent(grid.GridEntityId, out var comp)) + { + foreach (var (_, list) in comp.ChunkCollection.ChunkCollection) + { + foreach (var (_, decal) in list) + { + var (x, y) = TransformLocalPosition(decal.Coordinates, grid); + decals.GetOrNew(grid.Index).Add(new DecalData(decal, x, y)); + } + } + } + } + + Console.WriteLine($"Found {decals.Values.Sum(l => l.Count)} decals on {decals.Count} grids in {(int) stopwatch.Elapsed.TotalMilliseconds} ms"); + return decals; + } + + private (float x, float y) TransformLocalPosition(Vector2 position, IMapGrid grid) + { + var xOffset = (int) Math.Abs(grid.LocalBounds.Left); + var yOffset = (int) Math.Abs(grid.LocalBounds.Bottom); + var tileSize = grid.TileSize; + + var x = ((float) Math.Floor(position.X) + xOffset) * tileSize * TilePainter.TileImageSize; + var y = ((float) Math.Floor(position.Y) + yOffset) * tileSize * TilePainter.TileImageSize; + + return (x, y); + } } }