From 7f3e3d2873a1759a9960170e600f7b80ab10045c Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:33:39 +0300 Subject: [PATCH 1/3] tile space edges rendering --- .../Graphics/Clyde/Clyde.GridRendering.cs | 2 +- .../Map/ClydeTileDefinitionManager.cs | 148 ++++++++++-------- .../Map/IClydeTileDefinitionManager.cs | 4 +- Robust.Shared/Map/ITileDefinition.cs | 6 + 4 files changed, 90 insertions(+), 70 deletions(-) diff --git a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs index 1d649db87d9..806c8ea09c1 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs @@ -324,7 +324,7 @@ private void _updateChunkEdges(Entity grid, MapChunk chunk, Ma continue; var direction = new Vector2i(nx, ny).AsDirection().GetOpposite(); - var regionMaybe = _tileDefinitionManager.TileAtlasRegion(neighborTile.TypeId, direction); + var regionMaybe = _tileDefinitionManager.TileAtlasRegion(neighborTile.TypeId, direction, neighborTile.IsEmpty); if (regionMaybe == null) continue; diff --git a/Robust.Client/Map/ClydeTileDefinitionManager.cs b/Robust.Client/Map/ClydeTileDefinitionManager.cs index 78692560ccf..76094e6fd47 100644 --- a/Robust.Client/Map/ClydeTileDefinitionManager.cs +++ b/Robust.Client/Map/ClydeTileDefinitionManager.cs @@ -32,7 +32,13 @@ internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClyde public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent; - private FrozenDictionary<(int Id, Direction Direction), Box2[]> _tileRegions = FrozenDictionary<(int Id, Direction Direction), Box2[]>.Empty; + /// + /// A dictionary that stores references to the textures of all tiles. + /// Id - tile id + /// Direction - if Invalid, tile texture itself. If not - tile edge sprite + /// Space - used only for tile edges sprite, allow use dirrerent sets of edge sprites, used in contact with empty space + /// + private FrozenDictionary<(int Id, Direction Direction, bool Space), Box2[]> _tileRegions = FrozenDictionary<(int Id, Direction Direction, bool Space), Box2[]>.Empty; public Box2 ErrorTileRegion { get; private set; } @@ -49,10 +55,10 @@ internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClyde } /// - public Box2[]? TileAtlasRegion(int tileType, Direction direction) + public Box2[]? TileAtlasRegion(int tileType, Direction direction, bool space = false) { // ReSharper disable once CanSimplifyDictionaryTryGetValueWithGetValueOrDefault - if (_tileRegions.TryGetValue((tileType, direction), out var region)) + if (_tileRegions.TryGetValue((tileType, direction, space), out var region)) { return region; } @@ -94,7 +100,7 @@ private void OnReload(ResPath obj) internal void _genTextureAtlas() { var sw = RStopwatch.StartNew(); - var tileRegs = new Dictionary<(int Id, Direction Direction), Box2[]>(); + var tileRegs = new Dictionary<(int Id, Direction Direction, bool Space), Box2[]>(); _tileTextureAtlas = null; var defList = TileDefs.Where(t => t.Sprite != null).ToList(); @@ -105,7 +111,7 @@ internal void _genTextureAtlas() const int tileSize = EyeManager.PixelsPerMeter; - var tileCount = defList.Select(x => x.Variants + x.EdgeSprites.Count).Sum() + 1; + var tileCount = defList.Select(x => x.Variants + x.EdgeSprites.Count + x.EdgeSpaceSprites.Count).Sum() + 1; var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount)); var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX); @@ -171,76 +177,84 @@ internal void _genTextureAtlas() BumpColumn(ref row, ref column, dimensionX); } - tileRegs.Add((def.TileId, Direction.Invalid), regionList); + tileRegs.Add((def.TileId, Direction.Invalid, false), regionList); // Edges - if (def.EdgeSprites.Count <= 0) + if (def.EdgeSprites.Count <= 0 && def.EdgeSpaceSprites.Count <= 0) continue; - foreach (var direction in DirectionExtensions.AllDirections) + // Helper method to process edge set + void ProcessEdgeSet(Dictionary edges, bool isSpace) { - if (!def.EdgeSprites.TryGetValue(direction, out var edge)) - continue; - - using (var stream = _manager.ContentFileRead(edge)) - { - image = Image.Load(stream); - } - - if (image.Width != tileSize || image.Height != tileSize) - { - throw new NotSupportedException( - $"Unable to load {path}, due to being unable to use tile textures with a dimension other than {tileSize}x{tileSize}."); - } - - Angle angle = Angle.Zero; - - switch (direction) - { - // Corner sprites - case Direction.SouthEast: - break; - case Direction.NorthEast: - angle = new Angle(MathF.PI / 2f); - break; - case Direction.NorthWest: - angle = new Angle(MathF.PI); - break; - case Direction.SouthWest: - angle = new Angle(MathF.PI * 1.5f); - break; - // Edge sprites - case Direction.South: - break; - case Direction.East: - angle = new Angle(MathF.PI / 2f); - break; - case Direction.North: - angle = new Angle(MathF.PI); - break; - case Direction.West: - angle = new Angle(MathF.PI * 1.5f); - break; - } - - if (angle != Angle.Zero) + foreach (var direction in DirectionExtensions.AllDirections) { - image.Mutate(o => o.Rotate((float)-angle.Degrees)); + if (!edges.TryGetValue(direction, out var edge)) + continue; + + using (var stream = _manager.ContentFileRead(edge)) + { + image = Image.Load(stream); + } + + if (image.Width != tileSize || image.Height != tileSize) + { + throw new NotSupportedException( + $"Unable to load {edge}, due to being unable to use tile textures with a dimension other than {tileSize}x{tileSize}."); + } + + Angle angle = Angle.Zero; + switch (direction) + { + // Corner sprites + case Direction.SouthEast: + break; + case Direction.NorthEast: + angle = new Angle(MathF.PI / 2f); + break; + case Direction.NorthWest: + angle = new Angle(MathF.PI); + break; + case Direction.SouthWest: + angle = new Angle(MathF.PI * 1.5f); + break; + // Edge sprites + case Direction.South: + break; + case Direction.East: + angle = new Angle(MathF.PI / 2f); + break; + case Direction.North: + angle = new Angle(MathF.PI); + break; + case Direction.West: + angle = new Angle(MathF.PI * 1.5f); + break; + } + + if (angle != Angle.Zero) + { + image.Mutate(o => o.Rotate((float)-angle.Degrees)); + } + + var point = new Vector2i(column * tileSize, row * tileSize); + var box = new UIBox2i(0, 0, tileSize, tileSize); + image.Blit(box, sheet, point); + + // If you ever need edge variants then you could just bump this. + var edgeList = new Box2[1]; + edgeList[0] = Box2.FromDimensions( + point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h, + tileSize / w, tileSize / h); + + var key2 = (def.TileId, direction, IsSpace: isSpace); + tileRegs.Add(key2, edgeList); + BumpColumn(ref row, ref column, dimensionX); } - - var point = new Vector2i(column * tileSize, row * tileSize); - var box = new UIBox2i(0, 0, tileSize, tileSize); - image.Blit(box, sheet, point); - - // If you ever need edge variants then you could just bump this. - var edgeList = new Box2[1]; - edgeList[0] = Box2.FromDimensions( - point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h, - tileSize / w, tileSize / h); - - tileRegs.Add((def.TileId, direction), edgeList); - BumpColumn(ref row, ref column, dimensionX); } + + // process both sets + ProcessEdgeSet(def.EdgeSprites, false); + ProcessEdgeSet(def.EdgeSpaceSprites, true); } _tileRegions = tileRegs.ToFrozenDictionary(); diff --git a/Robust.Client/Map/IClydeTileDefinitionManager.cs b/Robust.Client/Map/IClydeTileDefinitionManager.cs index 99f083c11d7..0b23c55d16f 100644 --- a/Robust.Client/Map/IClydeTileDefinitionManager.cs +++ b/Robust.Client/Map/IClydeTileDefinitionManager.cs @@ -32,9 +32,9 @@ internal interface IClydeTileDefinitionManager : ITileDefinitionManager /// /// Gets the region inside the texture atlas to use to draw a tile type. - /// Also handles edge sprites. + /// Also handles edge sprites, both those touching space and those touching other tiles /// /// If null, do not draw the tile at all. - public Box2[]? TileAtlasRegion(int tileType, Direction direction); + public Box2[]? TileAtlasRegion(int tileType, Direction direction, bool space); } } diff --git a/Robust.Shared/Map/ITileDefinition.cs b/Robust.Shared/Map/ITileDefinition.cs index 0c17213badc..9e741420c13 100644 --- a/Robust.Shared/Map/ITileDefinition.cs +++ b/Robust.Shared/Map/ITileDefinition.cs @@ -30,6 +30,12 @@ public interface ITileDefinition : IPrototype /// Dictionary EdgeSprites { get; } + + /// + /// Possible sprites to use if we're neighboring empty tile. + /// + Dictionary EdgeSpaceSprites { get; } + /// /// When drawing adjacent tiles that both specify edge sprites, the one with the higher priority /// is always solely drawn. From db37c4495e80b08519cddd2ef54db650f6bcd83f Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:45:14 +0300 Subject: [PATCH 2/3] Update TestComponents.cs --- Robust.UnitTesting/Shared/EntitySerialization/TestComponents.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Robust.UnitTesting/Shared/EntitySerialization/TestComponents.cs b/Robust.UnitTesting/Shared/EntitySerialization/TestComponents.cs index 685a939d550..00376285ac1 100644 --- a/Robust.UnitTesting/Shared/EntitySerialization/TestComponents.cs +++ b/Robust.UnitTesting/Shared/EntitySerialization/TestComponents.cs @@ -55,6 +55,7 @@ public sealed partial class TileDef : ITileDefinition public string ID { get; private set; } = default!; public ResPath? Sprite => null; public Dictionary EdgeSprites => new(); + public Dictionary EdgeSpaceSprites => new(); public int EdgeSpritePriority => 0; public float Friction => 0; public byte Variants => 0; From 5ea9c722172bffa51af9e8e651e3442c4e2b7a33 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:56:32 +0300 Subject: [PATCH 3/3] Update ITileDefinition.cs --- Robust.Shared/Map/ITileDefinition.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Robust.Shared/Map/ITileDefinition.cs b/Robust.Shared/Map/ITileDefinition.cs index 9e741420c13..2575182933e 100644 --- a/Robust.Shared/Map/ITileDefinition.cs +++ b/Robust.Shared/Map/ITileDefinition.cs @@ -29,8 +29,7 @@ public interface ITileDefinition : IPrototype /// Possible sprites to use if we're neighboring another tile. /// Dictionary EdgeSprites { get; } - - + /// /// Possible sprites to use if we're neighboring empty tile. ///