Add support for tile variants. (#2577)

This commit is contained in:
Moony
2022-03-09 13:56:58 -06:00
committed by GitHub
parent 4ca3fbe4bf
commit d3eaea5697
10 changed files with 205 additions and 135 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -1,7 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -101,13 +99,18 @@ namespace Robust.Client.Graphics.Clyde
if (tile.IsEmpty)
continue;
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.TypeId);
if (regionMaybe == null)
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
Box2 region;
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
{
continue;
region = _tileDefinitionManager.ErrorTileRegion;
}
else
{
region = regionMaybe[tile.Variant];
}
var region = regionMaybe.Value;
var gx = x + cScaled.X;
var gy = y + cScaled.Y;

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -22,16 +22,18 @@ namespace Robust.Client.Map
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2> _tileRegions = new();
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
public Box2 ErrorTileRegion { get; private set; }
/// <inheritdoc />
public Box2? TileAtlasRegion(Tile tile)
public Box2[]? TileAtlasRegion(Tile tile)
{
return TileAtlasRegion(tile.TypeId);
}
/// <inheritdoc />
public Box2? TileAtlasRegion(ushort tileType)
public Box2[]? TileAtlasRegion(ushort tileType)
{
if (_tileRegions.TryGetValue(tileType, out var region))
{
@@ -58,39 +60,79 @@ namespace Robust.Client.Map
const int tileSize = EyeManager.PixelsPerMeter;
var dimensionX = (int) Math.Ceiling(Math.Sqrt(defList.Count));
var dimensionY = (int) Math.Ceiling((float) defList.Count / dimensionX);
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
var sheet = new Image<Rgba32>(dimensionX * tileSize, dimensionY * tileSize);
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
for (var i = 0; i < defList.Count; i++)
var imgWidth = dimensionX * tileSize;
var imgHeight = dimensionY * tileSize;
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
// Add in the missing tile texture sprite as tile texture 0.
{
var def = defList[i];
var column = i % dimensionX;
var row = i / dimensionX;
var w = (float) sheet.Width;
var h = (float) sheet.Height;
ErrorTileRegion = Box2.FromDimensions(
0, (h - EyeManager.PixelsPerMeter) / h,
tileSize / w, tileSize / h);
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png"))
{
image = Image.Load<Rgba32>(stream);
}
image.Blit(new UIBox2i(0, 0, tileSize, tileSize), sheet, Vector2i.Zero);
}
if (imgWidth >= 2048 || imgHeight >= 2048)
{
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
Logger.WarningS("clyde",
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
}
var column = 1;
var row = 0;
foreach (var def in defList)
{
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
{
image = Image.Load<Rgba32>(stream);
}
if (image.Width != tileSize || image.Height != tileSize)
if (image.Width != (tileSize * def.Variants) || image.Height != tileSize)
{
throw new NotSupportedException("Unable to use tiles with a dimension other than 32x32.");
throw new NotSupportedException(
$"Unable to load {new ResourcePath(def.Path) / $"{def.SpriteName}.png"}, due to being unable to use tile texture with a dimension other than {tileSize}x({tileSize} * Variants).");
}
var point = new Vector2i(column * tileSize, row * tileSize);
var regionList = new Box2[def.Variants];
image.Blit(new UIBox2i(0, 0, image.Width, image.Height), sheet, point);
for (var j = 0; j < def.Variants; j++)
{
var point = new Vector2i(column * tileSize, row * tileSize);
var w = (float) sheet.Width;
var h = (float) sheet.Height;
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
image.Blit(box, sheet, point);
_tileRegions.Add(def.TileId,
Box2.FromDimensions(
var w = (float) sheet.Width;
var h = (float) sheet.Height;
regionList[j] = Box2.FromDimensions(
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
tileSize / w, tileSize / h));
tileSize / w, tileSize / h);
column++;
if (column >= dimensionX)
{
column = 0;
row++;
}
}
_tileRegions.Add(def.TileId, regionList);
}
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -9,6 +10,8 @@ namespace Robust.Client.Map
/// </summary>
internal interface IClydeTileDefinitionManager : ITileDefinitionManager
{
Box2 ErrorTileRegion { get; }
/// <summary>
/// The texture atlas containing all the tiles.
/// </summary>
@@ -18,12 +21,12 @@ namespace Robust.Client.Map
/// Gets the region inside the texture atlas to use to draw a tile.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2? TileAtlasRegion(Tile tile);
Box2[]? TileAtlasRegion(Tile tile);
/// <summary>
/// Gets the region inside the texture atlas to use to draw a tile type.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2? TileAtlasRegion(ushort tileType);
Box2[]? TileAtlasRegion(ushort tileType);
}
}

View File

@@ -68,7 +68,8 @@ namespace Robust.Server.Maps
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
writer.Write(tile.Data);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
}
}
}
@@ -129,12 +130,13 @@ namespace Robust.Server.Maps
for (ushort x = 0; x < grid.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var data = reader.ReadUInt16();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileDefMapping[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, data);
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
}
}

View File

@@ -27,6 +27,8 @@
/// <summary>
/// Path to the folder where the tile sprite is contained.
/// The texture dimensions should be PixelsPerMeter x (PixelsPerMeter * Variants).
/// This is likely 32 x (32 * variants) if you're working on Space Station 14.
/// </summary>
string Path { get; }
@@ -35,6 +37,11 @@
/// </summary>
float Friction { get; }
/// <summary>
/// Number of variants this tile has. ALSO DETERMINES THE EXPECTED INPUT TEXTURE SIZE.
/// </summary>
byte Variants { get; }
/// <summary>
/// Assign a new value to <see cref="TileId"/>, used when registering the tile definition.
/// </summary>

View File

@@ -101,6 +101,7 @@ namespace Robust.Shared.Map
/// <exception cref="ArgumentOutOfRangeException">The index is less than or greater than the size of the chunk.</exception>
public Tile GetTile(ushort xIndex, ushort yIndex)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
@@ -108,7 +109,6 @@ internal sealed class NetworkedMapManager : MapManager, INetworkedMapManager
tileBuffer[x * grid.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
}

View File

@@ -1,114 +1,126 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.Map
namespace Robust.Shared.Map;
/// <summary>
/// This structure contains the data for an individual Tile in a <c>MapGrid</c>.
/// </summary>
[PublicAPI, Serializable]
public readonly struct Tile : IEquatable<Tile>
{
/// <summary>
/// This structure contains the data for an individual Tile in a <c>MapGrid</c>.
/// Internal type ID of this tile.
/// </summary>
[PublicAPI, Serializable]
public readonly struct Tile : IEquatable<Tile>
public readonly ushort TypeId;
/// <summary>
/// Rendering flags.
/// </summary>
public readonly TileRenderFlag Flags;
/// <summary>
/// Variant of this tile to render.
/// </summary>
public readonly byte Variant;
/// <summary>
/// An empty tile that can be compared against.
/// </summary>
public static readonly Tile Empty = new(0);
/// <summary>
/// Is this tile space (empty)?
/// </summary>
public bool IsEmpty => TypeId == 0;
/// <summary>
/// Creates a new instance of a grid tile.
/// </summary>
/// <param name="typeId">Internal type ID.</param>
/// <param name="flags">Flags used by toolbox's rendering.</param>
/// <param name="variant">The visual variant this tile is using.</param>
public Tile(ushort typeId, TileRenderFlag flags = 0, byte variant = 0)
{
/// <summary>
/// Internal type ID of this tile.
/// </summary>
public readonly ushort TypeId;
TypeId = typeId;
Flags = flags;
Variant = variant;
}
/// <summary>
/// Optional per-tile data of this tile.
/// </summary>
public readonly ushort Data;
/// <summary>
/// Explicit conversion of <c>Tile</c> to <c>uint</c> . This should only
/// be used in special cases like serialization. Do NOT use this in
/// content.
/// </summary>
public static explicit operator uint(Tile tile)
{
return ((uint)tile.TypeId << 16) | (uint)tile.Flags << 8 | tile.Variant;
}
/// <summary>
/// An empty tile that can be compared against.
/// </summary>
public static readonly Tile Empty = new(0);
/// <summary>
/// Explicit conversion of <c>uint</c> to <c>Tile</c> . This should only
/// be used in special cases like serialization. Do NOT use this in
/// content.
/// </summary>
public static explicit operator Tile(uint tile)
{
return new(
(ushort)(tile >> 16),
(TileRenderFlag)(tile >> 8),
(byte)tile
);
}
/// <summary>
/// Is this tile space (empty)?
/// </summary>
public bool IsEmpty => TypeId == 0;
/// <summary>
/// Check for equality by value between two objects.
/// </summary>
public static bool operator ==(Tile a, Tile b)
{
return a.Equals(b);
}
/// <summary>
/// Creates a new instance of a grid tile.
/// </summary>
/// <param name="typeId">Internal type ID.</param>
/// <param name="data">Optional per-tile data.</param>
public Tile(ushort typeId, ushort data = 0)
/// <summary>
/// Check for inequality by value between two objects.
/// </summary>
public static bool operator !=(Tile a, Tile b)
{
return !a.Equals(b);
}
/// <summary>
/// Generates String representation of this Tile.
/// </summary>
/// <returns>String representation of this Tile.</returns>
public override string ToString()
{
return $"Tile {TypeId}, {Flags}, {Variant}";
}
/// <inheritdoc />
public bool Equals(Tile other)
{
return TypeId == other.TypeId && Flags == other.Flags && Variant == other.Variant;
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is Tile other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
TypeId = typeId;
Data = data;
}
/// <summary>
/// Explicit conversion of <c>Tile</c> to <c>uint</c> . This should only
/// be used in special cases like serialization. Do NOT use this in
/// content.
/// </summary>
public static explicit operator uint(Tile tile)
{
return ((uint)tile.TypeId << 16) | tile.Data;
}
/// <summary>
/// Explicit conversion of <c>uint</c> to <c>Tile</c> . This should only
/// be used in special cases like serialization. Do NOT use this in
/// content.
/// </summary>
public static explicit operator Tile(uint tile)
{
return new(
(ushort)(tile >> 16),
(ushort)tile
);
}
/// <summary>
/// Check for equality by value between two objects.
/// </summary>
public static bool operator ==(Tile a, Tile b)
{
return a.Equals(b);
}
/// <summary>
/// Check for inequality by value between two objects.
/// </summary>
public static bool operator !=(Tile a, Tile b)
{
return !a.Equals(b);
}
/// <summary>
/// Generates String representation of this Tile.
/// </summary>
/// <returns>String representation of this Tile.</returns>
public override string ToString()
{
return $"Tile {TypeId}, {Data}";
}
/// <inheritdoc />
public bool Equals(Tile other)
{
return TypeId == other.TypeId && Data == other.Data;
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is Tile other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return (TypeId.GetHashCode() * 397) ^ Data.GetHashCode();
}
return (TypeId.GetHashCode() * 397) ^ Flags.GetHashCode() ^ Variant.GetHashCode();
}
}
}
public enum TileRenderFlag : byte
{
}

View File

@@ -32,13 +32,13 @@ namespace Robust.UnitTesting.Shared.Map
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
grid.SetTile(new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1));
var result = grid.GetTileRef(new Vector2i(-9, -1));
Assert.That(grid.ChunkCount, Is.EqualTo(1));
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
Assert.That(result, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9,-1), new Tile(1, 2))));
Assert.That(result, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9,-1), new Tile(1, (TileRenderFlag)1, 1))));
}
/// <summary>
@@ -144,14 +144,14 @@ namespace Robust.UnitTesting.Shared.Map
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
grid.SetTile(new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1));
var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef);
Assert.That(foundTile, Is.True);
Assert.That(grid.ChunkCount, Is.EqualTo(1));
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
Assert.That(tileRef, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9, -1), new Tile(1, 2))));
Assert.That(tileRef, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9, -1), new Tile(1, (TileRenderFlag)1, 1))));
}
[Test]