mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Refactor TileEdgeOverlay (#5295)
* Refactor TileEdgeOverlay * weh * Updates * exweh * Stupid weh noises * I am le stupid * Add logging about tile atlas build time Seems fine, but wanted to check. * Don't over-allocate edge tile region array * Add DirectionExtensions.AllDirections * Clean up iteration in ClydeTileDefinitionManager * Don't stackalloc large chunk edge buffers, other code cleanup. * More release notes --------- Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -39,7 +39,7 @@ END TEMPLATE-->
|
||||
|
||||
### New features
|
||||
|
||||
*None yet*
|
||||
* Added `DirectionExtensions.AllDirections`, which contains a list of all `Direction`s for easy enumeration.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -47,7 +47,7 @@ END TEMPLATE-->
|
||||
|
||||
### Other
|
||||
|
||||
*None yet*
|
||||
* Significantly optimized tile edge rendering.
|
||||
|
||||
### Internal
|
||||
|
||||
|
||||
@@ -9,10 +9,6 @@ namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
@@ -23,16 +19,4 @@ public sealed class MapSystem : SharedMapSystem
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
@@ -22,6 +23,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// </summary>
|
||||
private HashSet<Type> _erroredGridOverlays = new();
|
||||
|
||||
private Vertex2D[]? _chunkMeshBuilderVertexBuffer;
|
||||
private ushort[]? _chunkMeshBuilderIndexBuffer;
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
@@ -63,29 +67,78 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(transform));
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(mapGrid));
|
||||
var enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Handle base texture updates.
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
DebugTools.Assert(chunk.FilledTiles > 0);
|
||||
if (!data.TryGetValue(chunk.Indices, out MapChunkData? datum))
|
||||
data[chunk.Indices] = datum = _initChunkBuffers(mapGrid, chunk);
|
||||
var datum = EnsureChunkInitialized(data, chunk, mapGrid);
|
||||
|
||||
if (datum.Dirty)
|
||||
_updateChunkMesh(mapGrid, chunk, datum);
|
||||
|
||||
DebugTools.Assert(datum.TileCount > 0);
|
||||
if (datum.TileCount == 0)
|
||||
if (!datum.Dirty)
|
||||
continue;
|
||||
|
||||
BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
_updateChunkMesh(mapGrid, chunk, datum);
|
||||
|
||||
// Dirty edge tiles for next step.
|
||||
datum.EdgeDirty = true;
|
||||
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
var neighbor = chunk.Indices + new Vector2i(x, y);
|
||||
|
||||
if (!mapGrid.Comp.Chunks.TryGetValue(neighbor, out var neighborChunk))
|
||||
continue;
|
||||
|
||||
var neighborDatum = EnsureChunkInitialized(data, neighborChunk, mapGrid);
|
||||
neighborDatum.EdgeDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Handle edge sprites.
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var datum = data[chunk.Indices];
|
||||
|
||||
if (!datum.EdgeDirty)
|
||||
continue;
|
||||
|
||||
_updateChunkEdges(mapGrid, chunk, datum);
|
||||
}
|
||||
|
||||
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Draw chunks
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var datum = data[chunk.Indices];
|
||||
DebugTools.Assert(datum.TileCount > 0);
|
||||
if (datum.TileCount > 0)
|
||||
{
|
||||
BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (datum.EdgeCount > 0)
|
||||
{
|
||||
BindVertexArray(datum.EdgeVAO);
|
||||
CheckGlError();
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.EdgeCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
requiresFlush = false;
|
||||
@@ -117,6 +170,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CullEmptyChunks();
|
||||
}
|
||||
|
||||
private MapChunkData EnsureChunkInitialized(Dictionary<Vector2i, MapChunkData> data, MapChunk chunk, Entity<MapGridComponent> mapGrid)
|
||||
{
|
||||
if (!data.TryGetValue(chunk.Indices, out var datum))
|
||||
{
|
||||
data[chunk.Indices] = datum = new MapChunkData();
|
||||
_initChunkBuffers(mapGrid, chunk, datum);
|
||||
}
|
||||
|
||||
return datum;
|
||||
}
|
||||
|
||||
private void CullEmptyChunks()
|
||||
{
|
||||
foreach (var (grid, chunks) in _mapChunkData)
|
||||
@@ -138,66 +202,141 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _updateChunkMesh(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
Span<ushort> indexBuffer = stackalloc ushort[_indicesPerChunk(chunk)];
|
||||
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
|
||||
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk));
|
||||
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk));
|
||||
|
||||
var i = 0;
|
||||
var cSz = grid.Comp.ChunkSize;
|
||||
var cScaled = chunk.Indices * cSz;
|
||||
for (ushort x = 0; x < cSz; x++)
|
||||
var chunkSize = grid.Comp.ChunkSize;
|
||||
var chunkOriginScaled = chunk.Indices * chunkSize;
|
||||
|
||||
for (ushort x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < cSz; y++)
|
||||
for (ushort y = 0; y < chunkSize; y++)
|
||||
{
|
||||
var gridX = x + chunkOriginScaled.X;
|
||||
var gridY = y + chunkOriginScaled.Y;
|
||||
var tile = chunk.GetTile(x, y);
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
// Tile render
|
||||
if (x != chunkSize && y != chunkSize)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
// ReSharper disable once IntVariableOverflowInUncheckedContext
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var gx = x + cScaled.X;
|
||||
var gy = y + cScaled.Y;
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top, Color.White);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top, Color.White);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
|
||||
var vertSlice = vertexBuffer[..(i * 4)];
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
datum.EBO.Use();
|
||||
datum.VBO.Use();
|
||||
datum.EBO.Reallocate(indexBuffer[..(i * GetQuadBatchIndexCount())]);
|
||||
datum.VBO.Reallocate(vertexBuffer[..(i * 4)]);
|
||||
datum.Dirty = false;
|
||||
datum.EBO.Reallocate(indexSlice);
|
||||
datum.VBO.Reallocate(vertSlice);
|
||||
|
||||
datum.TileCount = i;
|
||||
datum.Dirty = false;
|
||||
}
|
||||
|
||||
private unsafe MapChunkData _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk)
|
||||
private void _updateChunkEdges(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
// Need a buffer that can potentially store all neighbor tiles
|
||||
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk) * 8);
|
||||
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk) * 8);
|
||||
|
||||
var i = 0;
|
||||
var chunkSize = grid.Comp.ChunkSize;
|
||||
var chunkOriginScaled = chunk.Indices * chunkSize;
|
||||
var maps = _entityManager.System<SharedMapSystem>();
|
||||
|
||||
for (ushort x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < chunkSize; y++)
|
||||
{
|
||||
var gridX = x + chunkOriginScaled.X;
|
||||
var gridY = y + chunkOriginScaled.Y;
|
||||
var tile = chunk.GetTile(x, y);
|
||||
var tileDef = _tileDefinitionManager[tile.TypeId];
|
||||
|
||||
// Edge render
|
||||
for (var nx = -1; nx <= 1; nx++)
|
||||
{
|
||||
for (var ny = -1; ny <= 1; ny++)
|
||||
{
|
||||
if (nx == 0 && ny == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(gridX + nx, gridY + ny);
|
||||
if (!maps.TryGetTile(grid.Comp, neighborIndices, out var neighborTile))
|
||||
continue;
|
||||
|
||||
var neighborDef = _tileDefinitionManager[neighborTile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tile.TypeId == neighborTile.TypeId || neighborDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// If neighbor is a lower priority then us then don't draw on our tile.
|
||||
if (neighborDef.EdgeSpritePriority < tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(nx, ny).AsDirection().GetOpposite();
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(neighborTile.TypeId, direction);
|
||||
|
||||
if (regionMaybe == null)
|
||||
continue;
|
||||
|
||||
var region = regionMaybe[0];
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We don't save the edge buffers back because we might need to re-use it if a neighbor chunk updates.
|
||||
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
|
||||
var vertSlice = vertexBuffer[..(i * 4)];
|
||||
|
||||
GL.BindVertexArray(datum.EdgeVAO);
|
||||
CheckGlError();
|
||||
datum.EdgeEBO.Use();
|
||||
datum.EdgeVBO.Use();
|
||||
datum.EdgeEBO.Reallocate(indexSlice);
|
||||
datum.EdgeVBO.Reallocate(vertSlice);
|
||||
|
||||
datum.EdgeCount = i;
|
||||
datum.EdgeDirty = false;
|
||||
}
|
||||
|
||||
private unsafe void _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
// Base VAO
|
||||
var vao = GenVertexArray();
|
||||
BindVertexArray(vao);
|
||||
CheckGlError();
|
||||
|
||||
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
var vbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize, $"Grid {grid.Owner} chunk {chunk.Indices} VBO");
|
||||
var ebo = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
@@ -212,12 +351,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
vbo.Use();
|
||||
ebo.Use();
|
||||
|
||||
var datum = new MapChunkData(vao, vbo, ebo)
|
||||
{
|
||||
Dirty = true
|
||||
};
|
||||
datum.EBO = ebo;
|
||||
datum.VBO = vbo;
|
||||
datum.VAO = vao;
|
||||
|
||||
return datum;
|
||||
// EdgeVAO
|
||||
var edgeVao = GenVertexArray();
|
||||
BindVertexArray(edgeVao);
|
||||
CheckGlError();
|
||||
|
||||
var edgeVbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize * 8, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeVBO");
|
||||
var edgeEbo = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
eboSize * 8, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeEBO");
|
||||
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeVAO");
|
||||
SetupVAOLayout();
|
||||
CheckGlError();
|
||||
|
||||
edgeVbo.Use();
|
||||
edgeEbo.Use();
|
||||
|
||||
datum.EdgeEBO = edgeEbo;
|
||||
datum.EdgeVBO = edgeVbo;
|
||||
datum.EdgeVAO = edgeVao;
|
||||
}
|
||||
|
||||
private void DeleteChunk(MapChunkData data)
|
||||
@@ -254,19 +411,49 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mapChunkData.Remove(gridId);
|
||||
}
|
||||
|
||||
private static T[] EnsureSize<T>(ref T[]? field, int size)
|
||||
{
|
||||
if (field == null || field.Length < size)
|
||||
field = new T[size];
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
private void WriteTileToBuffers(
|
||||
int i,
|
||||
int gridX,
|
||||
int gridY,
|
||||
Span<Vertex2D> vertexBuffer,
|
||||
Span<ushort> indexBuffer,
|
||||
Box2 region)
|
||||
{
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gridX, gridY, region.Left, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gridX + 1, gridY, region.Right, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gridX + 1, gridY + 1, region.Right, region.Top, Color.White);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gridX, gridY + 1, region.Left, region.Top, Color.White);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
}
|
||||
|
||||
private sealed class MapChunkData
|
||||
{
|
||||
public bool Dirty;
|
||||
public readonly uint VAO;
|
||||
public readonly GLBuffer VBO;
|
||||
public readonly GLBuffer EBO;
|
||||
public bool EdgeDirty = true;
|
||||
public bool Dirty = true;
|
||||
|
||||
public uint VAO;
|
||||
public GLBuffer VBO = default!;
|
||||
public GLBuffer EBO = default!;
|
||||
public int TileCount;
|
||||
|
||||
public MapChunkData(uint vao, GLBuffer vbo, GLBuffer ebo)
|
||||
public uint EdgeVAO;
|
||||
public GLBuffer EdgeVBO = default!;
|
||||
public GLBuffer EdgeEBO = default!;
|
||||
public int EdgeCount;
|
||||
|
||||
public MapChunkData()
|
||||
{
|
||||
VAO = vao;
|
||||
VBO = vbo;
|
||||
EBO = ebo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -10,9 +11,11 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Robust.Client.Map
|
||||
{
|
||||
@@ -29,7 +32,7 @@ namespace Robust.Client.Map
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<int, Box2[]> _tileRegions = new();
|
||||
private FrozenDictionary<(int Id, Direction Direction), Box2[]> _tileRegions = FrozenDictionary<(int Id, Direction Direction), Box2[]>.Empty;
|
||||
|
||||
public Box2 ErrorTileRegion { get; private set; }
|
||||
|
||||
@@ -42,7 +45,14 @@ namespace Robust.Client.Map
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(int tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
return TileAtlasRegion(tileType, Direction.Invalid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(int tileType, Direction direction)
|
||||
{
|
||||
// ReSharper disable once CanSimplifyDictionaryTryGetValueWithGetValueOrDefault
|
||||
if (_tileRegions.TryGetValue((tileType, direction), out var region))
|
||||
{
|
||||
return region;
|
||||
}
|
||||
@@ -83,7 +93,8 @@ namespace Robust.Client.Map
|
||||
|
||||
internal void _genTextureAtlas()
|
||||
{
|
||||
_tileRegions.Clear();
|
||||
var sw = RStopwatch.StartNew();
|
||||
var tileRegs = new Dictionary<(int Id, Direction Direction), Box2[]>();
|
||||
_tileTextureAtlas = null;
|
||||
|
||||
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
|
||||
@@ -94,7 +105,7 @@ namespace Robust.Client.Map
|
||||
|
||||
const int tileSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
|
||||
var tileCount = defList.Select(x => x.Variants + x.EdgeSprites.Count).Sum() + 1;
|
||||
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
|
||||
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
|
||||
@@ -102,11 +113,11 @@ namespace Robust.Client.Map
|
||||
var imgWidth = dimensionX * tileSize;
|
||||
var imgHeight = dimensionY * tileSize;
|
||||
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
// Add in the missing tile texture sprite as tile texture 0.
|
||||
{
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
ErrorTileRegion = Box2.FromDimensions(
|
||||
0, (h - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
@@ -154,25 +165,98 @@ namespace Robust.Client.Map
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
|
||||
image.Blit(box, sheet, point);
|
||||
|
||||
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);
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
BumpColumn(ref row, ref column, dimensionX);
|
||||
}
|
||||
|
||||
_tileRegions.Add(def.TileId, regionList);
|
||||
tileRegs.Add((def.TileId, Direction.Invalid), regionList);
|
||||
|
||||
// Edges
|
||||
if (def.EdgeSprites.Count <= 0)
|
||||
continue;
|
||||
|
||||
foreach (var direction in DirectionExtensions.AllDirections)
|
||||
{
|
||||
if (!def.EdgeSprites.TryGetValue(direction, out var edge))
|
||||
continue;
|
||||
|
||||
using (var stream = _manager.ContentFileRead(edge))
|
||||
{
|
||||
image = Image.Load<Rgba32>(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)
|
||||
{
|
||||
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);
|
||||
|
||||
tileRegs.Add((def.TileId, direction), edgeList);
|
||||
BumpColumn(ref row, ref column, dimensionX);
|
||||
}
|
||||
}
|
||||
|
||||
_tileRegions = tileRegs.ToFrozenDictionary();
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
_sawmill.Debug($"Tile atlas took {sw.Elapsed} to build");
|
||||
}
|
||||
|
||||
private void BumpColumn(ref int row, ref int column, int dimensionX)
|
||||
{
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
|
||||
@@ -29,5 +29,12 @@ namespace Robust.Client.Map
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2[]? TileAtlasRegion(int tileType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// Also handles edge sprites.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
public Box2[]? TileAtlasRegion(int tileType, Direction direction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Map;
|
||||
|
||||
/// <summary>
|
||||
/// Draws border sprites for tiles that support them.
|
||||
/// </summary>
|
||||
public sealed class TileEdgeOverlay : GridOverlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IResourceCache _resource;
|
||||
private readonly ITileDefinitionManager _tileDefManager;
|
||||
|
||||
public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_resource = resource;
|
||||
_tileDefManager = tileDefManager;
|
||||
ZIndex = -1;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
var tileSize = Grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var bounds = args.WorldBounds;
|
||||
bounds = new Box2Rotated(bounds.Box.Enlarged(1), bounds.Rotation, bounds.Origin);
|
||||
var localAABB = invMatrix.TransformBox(bounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
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)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
|
||||
RequiresFlush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -37,6 +38,21 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static class DirectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of all cardinal and diagonal <see cref="Direction"/>s.
|
||||
/// </summary>
|
||||
public static readonly ImmutableArray<Direction> AllDirections =
|
||||
[
|
||||
Direction.South,
|
||||
Direction.SouthEast,
|
||||
Direction.East,
|
||||
Direction.NorthEast,
|
||||
Direction.North,
|
||||
Direction.NorthWest,
|
||||
Direction.West,
|
||||
Direction.SouthWest
|
||||
];
|
||||
|
||||
private const double Segment = 2 * Math.PI / 8.0; // Cut the circle into 8 pieces
|
||||
|
||||
public static Direction AsDir(this DirectionFlag directionFlag)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
|
||||
Reference in New Issue
Block a user