Grid Bounds (#800)

* Added centered unit box static field to Box2.

* MapGrid is more testable.

* Added some unit tests for MapGrid.
Fixed bug in MapChunk.GridTileToLocal().
MapGrid.UpdateAABB() actually expands properly now.

* Moved IMapChunk to Robust.Shared.Map.
Moved Chunk class out of MapManager class.

* Added unit tests for MapChunk.

* Bounds reduce by 1, so almost working.

* Now bound shrinking works :D

* Moved MapGrid out of MapManager.
Moved IMapGrid into the Shared/Map folder.
Replaced all calls to IMapGrid.ParentMap.Index with IMapGrid.ParentMapId.

* Added more MapGrid unit tests.
Fixed a bug in TryGetTileRef.
This commit is contained in:
Acruid
2019-04-29 12:43:20 +02:00
committed by Pieter-Jan Briers
parent d74151d242
commit 1b3cc8aba6
44 changed files with 1685 additions and 817 deletions
@@ -23,7 +23,7 @@ namespace Robust.Client.GameObjects
public IEnumerable<IEntity> GetEntitiesInRange(GridCoordinates position, float Range)
{
var AABB = new Box2(position.Position - new Vector2(Range / 2, Range / 2), position.Position + new Vector2(Range / 2, Range / 2));
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMap.Index, AABB);
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMapId, AABB);
}
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 position)
@@ -307,7 +307,7 @@ namespace Robust.Client.GameObjects
foreach (var effect in _owner._Effects)
{
if (_mapManager.GetGrid(effect.Coordinates.GridID).ParentMap.Index != map)
if (_mapManager.GetGrid(effect.Coordinates.GridID).ParentMapId != map)
{
continue;
}
@@ -46,8 +46,10 @@ namespace Robust.Client.Graphics.Clyde
gridProgram.SetUniform(UniModUV, new Vector4(0, 0, 1, 1));
gridProgram.SetUniform(UniModulate, Color.White);
foreach (var grid in _mapManager.GetMap(map).GetAllGrids())
foreach (var mapGrid in _mapManager.GetMap(map).GetAllGrids())
{
var grid = (IMapGridInternal) mapGrid;
if (!_mapChunkData.ContainsKey(grid.Index))
{
continue;
@@ -57,14 +59,14 @@ namespace Robust.Client.Graphics.Clyde
model.R1C2 = grid.WorldPosition.Y;
gridProgram.SetUniform(UniModelMatrix, model);
foreach (var chunk in grid.GetMapChunks())
foreach (var (index, chunk) in grid.GetMapChunks())
{
if (_isChunkDirty(grid, chunk))
{
_updateChunkMesh(grid, chunk);
}
var datum = _mapChunkData[grid.Index][chunk.Index];
var datum = _mapChunkData[grid.Index][chunk.Indices];
if (datum.TileCount == 0)
{
@@ -86,7 +88,7 @@ namespace Robust.Client.Graphics.Clyde
{
var data = _mapChunkData[grid.Index];
if (!data.TryGetValue(chunk.Index, out var datum))
if (!data.TryGetValue(chunk.Indices, out var datum))
{
datum = _initChunkBuffers(grid, chunk);
}
@@ -149,11 +151,11 @@ namespace Robust.Client.Graphics.Clyde
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
var vbo = new Buffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
vboSize, $"Grid {grid.Index} chunk {chunk.Index} VBO");
vboSize, $"Grid {grid.Index} chunk {chunk.Indices} VBO");
var ebo = new Buffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
eboSize, $"Grid {grid.Index} chunk {chunk.Index} EBO");
eboSize, $"Grid {grid.Index} chunk {chunk.Indices} EBO");
_objectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Index} chunk {chunk.Index} VAO");
_objectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Index} chunk {chunk.Indices} VAO");
// Vertex Coords
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
GL.EnableVertexAttribArray(0);
@@ -174,14 +176,14 @@ namespace Robust.Client.Graphics.Clyde
VBO = vbo
};
_mapChunkData[grid.Index].Add(chunk.Index, datum);
_mapChunkData[grid.Index].Add(chunk.Indices, datum);
return datum;
}
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
{
var data = _mapChunkData[grid.Index];
return !data.TryGetValue(chunk.Index, out var datum) || datum.Dirty;
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
}
public void _setChunkDirty(IMapGrid grid, MapIndices chunk)
@@ -199,7 +201,7 @@ namespace Robust.Client.Graphics.Clyde
foreach (var (pos, _) in args.Modified)
{
var grid = args.Grid;
var chunk = grid.GridTileToGridChunk(pos);
var chunk = grid.GridTileToChunkIndices(pos);
_setChunkDirty(grid, chunk);
}
}
@@ -207,7 +209,7 @@ namespace Robust.Client.Graphics.Clyde
private void _updateTileMapOnUpdate(object sender, TileChangedEventArgs args)
{
var grid = _mapManager.GetGrid(args.NewTile.GridIndex);
var chunk = grid.GridTileToGridChunk(new MapIndices(args.NewTile.X, args.NewTile.Y));
var chunk = grid.GridTileToChunkIndices(new MapIndices(args.NewTile.X, args.NewTile.Y));
_setChunkDirty(grid, chunk);
}
@@ -17,7 +17,7 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToPlayerGrid(mouseScreen);
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
CurrentTile = mapGrid.GetTile(MouseCoords);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
if (pManager.CurrentPermission.IsTile)
{
@@ -32,7 +32,7 @@ namespace Robust.Client.Placement.Modes
var manager = IoCManager.Resolve<IClientEntityManager>();
var snapToEntities = manager.GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => entity.Prototype == pManager.CurrentPrototype && entity.Transform.MapID == mapGrid.ParentMap.Index)
.Where(entity => entity.Prototype == pManager.CurrentPrototype && entity.Transform.MapID == mapGrid.ParentMapId)
.OrderBy(entity => (entity.Transform.WorldPosition - MouseCoords.ToWorld(pManager.MapManager).Position).LengthSquared)
.ToList();
@@ -14,7 +14,7 @@ namespace Robust.Client.Placement.Modes
MouseCoords = ScreenToPlayerGrid(mouseScreen);
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
CurrentTile = mapGrid.GetTile(MouseCoords);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
float tileSize = mapGrid.TileSize; //convert from ushort to float
GridDistancing = tileSize;
@@ -14,7 +14,7 @@ namespace Robust.Client.Placement.Modes
MouseCoords = ScreenToPlayerGrid(mouseScreen);
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
CurrentTile = mapGrid.GetTile(MouseCoords);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
float tileSize = mapGrid.TileSize; //convert from ushort to float
GridDistancing = tileSize;
@@ -17,7 +17,7 @@ namespace Robust.Client.Placement.Modes
MouseCoords = ScreenToPlayerGrid(mouseScreen);
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
CurrentTile = mapGrid.GetTile(MouseCoords);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
float tileSize = mapGrid.TileSize; //convert from ushort to float
GridDistancing = tileSize;
@@ -43,7 +43,7 @@ namespace Robust.Client.Placement.Modes
}
var entitymanager = IoCManager.Resolve<IClientEntityManager>();
return !(entitymanager.AnyEntitiesIntersecting(pManager.MapManager.GetGrid(MouseCoords.GridID).ParentMap.Index,
return !(entitymanager.AnyEntitiesIntersecting(pManager.MapManager.GetGrid(MouseCoords.GridID).ParentMapId,
new Box2(new Vector2(CurrentTile.X, CurrentTile.Y), new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f))));
}
}
@@ -14,7 +14,7 @@ namespace Robust.Client.Placement.Modes
MouseCoords = ScreenToPlayerGrid(mouseScreen);
var mapGrid = pManager.MapManager.GetGrid(MouseCoords.GridID);
CurrentTile = mapGrid.GetTile(MouseCoords);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
float tileSize = mapGrid.TileSize; //convert from ushort to float
GridDistancing = tileSize;
+1 -1
View File
@@ -12,7 +12,7 @@ namespace Robust.Client.Placement.Modes
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
MouseCoords = ScreenToPlayerGrid(mouseScreen);
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTile(MouseCoords);
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTileRef(MouseCoords);
if (pManager.CurrentPermission.IsTile)
{
+1 -1
View File
@@ -9,7 +9,7 @@ namespace Robust.Client.Placement.Modes
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
MouseCoords = ScreenToPlayerGrid(mouseScreen);
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTile(MouseCoords);
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTileRef(MouseCoords);
}
public override bool IsValidPosition(GridCoordinates position)
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Robust.Client.Placement.Modes
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
MouseCoords = ScreenToPlayerGrid(mouseScreen);
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTile(MouseCoords);
CurrentTile = pManager.MapManager.GetGrid(MouseCoords.GridID).GetTileRef(MouseCoords);
}
public override bool IsValidPosition(GridCoordinates position)
+2 -2
View File
@@ -573,7 +573,7 @@ namespace Robust.Client.Placement
private void RequestPlacement(GridCoordinates coordinates)
{
if (MapManager.GetGrid(coordinates.GridID).ParentMap.Index == MapId.Nullspace) return;
if (MapManager.GetGrid(coordinates.GridID).ParentMapId == MapId.Nullspace) return;
if (CurrentPermission == null) return;
if (!CurrentMode.IsValidPosition(coordinates)) return;
if (Hijack != null && Hijack.HijackPlacementRequest(coordinates)) return;
@@ -583,7 +583,7 @@ namespace Robust.Client.Placement
var grid = MapManager.GetGrid(coordinates.GridID);
// no point changing the tile to the same thing.
if (grid.GetTile(coordinates).Tile.TypeId == CurrentPermission.TileType)
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)
return;
foreach (var tileChange in _pendingTileChanges)
+1 -1
View File
@@ -198,7 +198,7 @@ namespace Robust.Client.Placement
bounds.Width,
bounds.Height);
if (pManager.PhysicsManager.IsColliding(collisionbox, pManager.MapManager.GetGrid(coordinates.GridID).ParentMap.Index))
if (pManager.PhysicsManager.IsColliding(collisionbox, pManager.MapManager.GetGrid(coordinates.GridID).ParentMapId))
return true;
return false;
+1 -1
View File
@@ -162,7 +162,7 @@ namespace Robust.Client.State.States
public IList<IEntity> GetEntitiesUnderPosition(GridCoordinates coordinates)
{
// Find all the entities intersecting our click
var entities = _entityManager.GetEntitiesIntersecting(_mapManager.GetGrid(coordinates.GridID).ParentMap.Index, coordinates.Position);
var entities = _entityManager.GetEntitiesIntersecting(_mapManager.GetGrid(coordinates.GridID).ParentMapId, coordinates.Position);
// Check the entities against whether or not we can click them
var foundEntities = new List<(IEntity clicked, int drawDepth)>();
@@ -100,7 +100,7 @@ namespace Robust.Client.UserInterface.CustomControls
try
{
var coords = eyeManager.ScreenToWorld(new ScreenCoordinates(mouseScreenPos));
mouseWorldMap = (int) _mapManager.GetGrid(coords.GridID).ParentMap.Index;
mouseWorldMap = (int) _mapManager.GetGrid(coords.GridID).ParentMapId;
mouseWorldGrid = (int) coords.GridID;
mouseWorldPos = coords;
worldToScreen = eyeManager.WorldToScreen(coords);
@@ -109,7 +109,7 @@ namespace Robust.Client.UserInterface.CustomControls
mouseEntity = gameScreen.GetEntityUnderPosition(coords);
}
tile = _mapManager.GetGrid(coords.GridID).GetTile(coords);
tile = _mapManager.GetGrid(coords.GridID).GetTileRef(coords);
}
catch
{
@@ -161,7 +161,7 @@ namespace Robust.Server.Console.Commands
var pos = player.AttachedEntity.Transform.GridPosition;
shell.SendText(player, $"MapID:{IoCManager.Resolve<IMapManager>().GetGrid(pos.GridID).ParentMap.Index} GridID:{pos.GridID} X:{pos.X:N2} Y:{pos.Y:N2}");
shell.SendText(player, $"MapID:{IoCManager.Resolve<IMapManager>().GetGrid(pos.GridID).ParentMapId} GridID:{pos.GridID} X:{pos.X:N2} Y:{pos.Y:N2}");
}
}
@@ -145,7 +145,7 @@ namespace Robust.Server.GameObjects.EntitySystems
{
var location = entity.Transform;
var grid = _mapManager.GetGrid(location.GridPosition.GridID);
var tile = grid.GetTile(location.GridPosition);
var tile = grid.GetTileRef(location.GridPosition);
var tileDef = _tileDefinitionManager[tile.Tile.TypeId];
if (tileDef.Friction != 0)
{
@@ -173,7 +173,7 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public IEnumerable<IEntity> GetEntitiesIntersecting(GridCoordinates position)
{
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMap.Index, position.ToWorld(_mapManager).Position);
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMapId, position.ToWorld(_mapManager).Position);
}
/// <inheritdoc />
@@ -191,7 +191,7 @@ namespace Robust.Server.GameObjects
public IEnumerable<IEntity> GetEntitiesInRange(GridCoordinates position, float range)
{
var aabb = new Box2(position.Position - new Vector2(range / 2, range / 2), position.Position + new Vector2(range / 2, range / 2));
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMap.Index, aabb);
return GetEntitiesIntersecting(_mapManager.GetGrid(position.GridID).ParentMapId, aabb);
}
/// <inheritdoc />
+4 -2
View File
@@ -15,8 +15,10 @@ namespace Robust.Server.Maps
{
public static class YamlGridSerializer
{
public static YamlMappingNode SerializeGrid(IMapGrid grid)
public static YamlMappingNode SerializeGrid(IMapGrid mapGrid)
{
var grid = (IMapGridInternal) mapGrid;
var gridn = new YamlMappingNode();
var info = new YamlMappingNode();
var chunkSeq = new YamlSequenceNode();
@@ -32,7 +34,7 @@ namespace Robust.Server.Maps
var chunks = grid.GetMapChunks();
foreach (var chunk in chunks)
{
var chunkNode = SerializeChunk(chunk);
var chunkNode = SerializeChunk(chunk.Value);
chunkSeq.Add(chunkNode);
}
+1 -1
View File
@@ -130,7 +130,7 @@ namespace Robust.Server.Placement
foreach (var grid in gridsInArea)
{
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(grid.AABBWorld);
var gridIntersect = gridSearchBox.Intersect(grid.WorldBounds);
var gridDist = (gridIntersect.Center - position).LengthSquared;
if (gridDist >= distance)
+5
View File
@@ -38,6 +38,11 @@ namespace Robust.Shared.Maths
public Vector2 Size => new Vector2(Width, Height);
public Vector2 Center => BottomLeft + Size / 2;
/// <summary>
/// A 1x1 unit box with the origin centered.
/// </summary>
public static readonly Box2 UnitCentered = new Box2(-0.5f, -0.5f, 0.5f, 0.5f);
public Box2(Vector2 bottomLeft, Vector2 topRight) : this(bottomLeft.X, bottomLeft.Y, topRight.X, topRight.Y)
{
}
+17 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
namespace Robust.Shared.Maths
{
@@ -62,6 +62,22 @@ namespace Robust.Shared.Maths
return new Box2i(Left + point.X, Bottom + point.Y, Right + point.X, Top + point.Y);
}
/// <summary>
/// Returns the smallest rectangle that contains both of the rectangles.
/// </summary>
public Box2i Union(in Box2i other)
{
var left = Math.Min(Left, other.Left);
var right = Math.Max(Right, other.Right);
var bottom = Math.Min(Bottom, other.Bottom);
var top = Math.Max(Top, other.Top);
if (left <= right && bottom <= top)
return new Box2i(left, bottom, right, top);
return new Box2i();
}
// override object.Equals
public override bool Equals(object obj)
{
-65
View File
@@ -1,65 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Map;
namespace Robust.Shared.Interfaces.Map
{
/// <summary>
/// This is a square 'chunk' of a MapGrid.
/// </summary>
public interface IMapChunk : IEnumerable<TileRef>
{
/// <summary>
/// The number of tiles per side of the square chunk.
/// </summary>
ushort ChunkSize { get; }
/// <summary>
/// The X index of this chunk.
/// </summary>
int X { get; }
/// <summary>
/// The Y index of this chunk.
/// </summary>
int Y { get; }
MapIndices Index { get; }
/// <summary>
/// Returns the tile at the given indices. The tile indices are relative locations to the
/// chunk origin, NOT the grid origin.
/// </summary>
/// <param name="xTile">The X tile index relative to the chunk.</param>
/// <param name="yTile">The Y tile index relative to the chunk.</param>
/// <returns>A reference to a tile.</returns>
TileRef GetTile(ushort xTile, ushort yTile);
/// <summary>
/// Returns all of the tiles in the chunk.
/// </summary>
/// <param name="ignoreEmpty">Will empty (space) tiles be added to the collection?</param>
/// <returns></returns>
IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true);
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xChunkTile">The X tile index relative to the chunk.</param>
/// <param name="yChunkTile">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
void SetTile(ushort xChunkTile, ushort yChunkTile, Tile tile);
/// <summary>
/// Transforms Tile indices relative to the grid into tile indices relative to this chunk.
/// </summary>
/// <param name="gridTile">Tile indices relative to the grid.</param>
/// <returns>Tile indices relative to this chunk.</returns>
MapIndices GridTileToChunkTile(MapIndices gridTile);
IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset);
void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
}
}
+87
View File
@@ -0,0 +1,87 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Maths;
namespace Robust.Shared.Map
{
/// <summary>
/// A square section of a <see cref="IMapGrid"/>.
/// </summary>
public interface IMapChunk : IEnumerable<TileRef>
{
/// <summary>
/// The number of tiles per side of the square chunk.
/// </summary>
ushort ChunkSize { get; }
/// <summary>
/// The X index of this chunk inside the <see cref="IMapGrid"/>.
/// </summary>
int X { get; }
/// <summary>
/// The Y index of this chunk inside the <see cref="IMapGrid"/>.
/// </summary>
int Y { get; }
/// <summary>
/// The positional indices of this chunk in the <see cref="IMapGrid"/>.
/// </summary>
MapIndices Indices { get; }
/// <summary>
/// Returns the tile at the given indices.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
TileRef GetTileRef(ushort xIndex, ushort yIndex);
/// <summary>
/// Returns the tile reference at the given indices.
/// </summary>
/// <param name="indices">The tile indices relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
TileRef GetTileRef(MapIndices indices);
Tile GetTile(ushort xIndex, ushort yIndex);
/// <summary>
/// Returns all of the tiles in the chunk, while optionally filtering empty files.
/// Returned order is guaranteed to be row-major.
/// </summary>
/// <param name="ignoreEmpty">Will empty (space) tiles be added to the collection?</param>
/// <returns></returns>
IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true);
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
void SetTile(ushort xIndex, ushort yIndex, Tile tile);
/// <summary>
/// Transforms Tile indices relative to the grid into tile indices relative to this chunk.
/// </summary>
/// <param name="gridTile">Tile indices relative to the grid.</param>
/// <returns>Tile indices relative to this chunk.</returns>
MapIndices GridTileToChunkTile(MapIndices gridTile);
/// <summary>
/// Translates chunk tile indices to grid tile indices.
/// </summary>
/// <param name="chunkTile">The indices relative to the chunk origin.</param>
/// <returns>The indices relative to the grid origin.</returns>
MapIndices ChunkTileToGridTile(MapIndices chunkTile);
IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset);
void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap);
Box2i CalcLocalBounds();
}
}
+13
View File
@@ -0,0 +1,13 @@
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
/// <inheritdoc />
internal interface IMapChunkInternal : IMapChunk
{
/// <summary>
/// The last game simulation tick that this chunk was modified.
/// </summary>
GameTick LastModifiedTick { get; }
}
}
@@ -1,20 +1,26 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Map;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Maths;
namespace Robust.Shared.Interfaces.Map
namespace Robust.Shared.Map
{
/// <summary>
/// This is a collection of tiles in a grid format.
/// </summary>
[PublicAPI]
public interface IMapGrid : IDisposable
{
/// <summary>
/// True if we are the default grid of our map.
/// </summary>
bool IsDefaultGrid { get; }
/// <summary>
/// The map that this grid exists inside of.
/// </summary>
IMap ParentMap { get; }
/// <summary>
@@ -22,6 +28,9 @@ namespace Robust.Shared.Interfaces.Map
/// </summary>
MapId ParentMapId { get; set; }
/// <summary>
/// The identifier of this grid.
/// </summary>
GridId Index { get; }
/// <summary>
@@ -32,7 +41,7 @@ namespace Robust.Shared.Interfaces.Map
/// <summary>
/// The bounding box of the grid in world coordinates.
/// </summary>
Box2 AABBWorld { get; }
Box2 WorldBounds { get; }
/// <summary>
/// The length of a side of the square chunk in number of tiles.
@@ -66,14 +75,14 @@ namespace Robust.Shared.Interfaces.Map
/// </summary>
/// <param name="worldPos">The location of the tile in coordinates.</param>
/// <returns>The tile at the world coordinates.</returns>
TileRef GetTile(GridCoordinates worldPos);
TileRef GetTileRef(GridCoordinates worldPos);
/// <summary>
/// Gets a tile a the given tile coordinates. This will not create a new chunk.
/// Gets a tile a the given grid indices. This will not create a new chunk.
/// </summary>
/// <param name="tileCoordinates">The location of the tile in coordinates.</param>
/// <returns>The tile at the tile coordinates.</returns>
TileRef GetTile(MapIndices tileCoordinates);
TileRef GetTileRef(MapIndices tileCoordinates);
/// <summary>
/// Returns all tiles in the grid, in row-major order [xTileIndex, yTileIndex].
@@ -91,11 +100,8 @@ namespace Robust.Shared.Interfaces.Map
/// <summary>
/// Modifies a single tile inside of the chunk.
/// </summary>
/// <param name="worldPos"></param>
/// <param name="tileId">The new internal ID of the tile.</param>
/// <param name="tileData">The new data of the tile.</param>
void SetTile(GridCoordinates worldPos, ushort tileId, ushort tileData = 0);
/// <param name="gridIndices"></param>
/// <param name="tile">The tile to insert at the coordinates.</param>
void SetTile(MapIndices gridIndices, Tile tile);
/// <summary>
@@ -122,39 +128,7 @@ namespace Robust.Shared.Interfaces.Map
void RemoveFromSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap);
#endregion SnapGridAccess
#region ChunkAccess
/// <summary>
/// The total number of chunks contained on this grid.
/// </summary>
int ChunkCount { get; }
/// <summary>
/// Returns the chunk at the given indices. If the chunk does not exist,
/// then a new one is generated that is filled with empty space.
/// </summary>
/// <param name="xIndex">The X index of the chunk in this grid.</param>
/// <param name="yIndex">The Y index of the chunk in this grid.</param>
/// <returns>The existing or new chunk.</returns>
IMapChunk GetChunk(int xIndex, int yIndex);
/// <summary>
/// Returns the chunk at the given indices. If the chunk does not exist,
/// then a new one is generated that is filled with empty space.
/// </summary>
/// <param name="chunkIndices">The indices of the chunk in this grid.</param>
/// <returns>The existing or new chunk.</returns>
IMapChunk GetChunk(MapIndices chunkIndices);
/// <summary>
/// Returns all chunks in this grid. This will not generate new chunks.
/// </summary>
/// <returns>All chunks in the grid.</returns>
IEnumerable<IMapChunk> GetMapChunks();
#endregion ChunkAccess
#region Transforms
/// <summary>
@@ -187,22 +161,29 @@ namespace Robust.Shared.Interfaces.Map
/// <summary>
/// Transforms grid-space tile indices to local coordinates.
/// The resulting coordinates are centered on the tile.
/// </summary>
/// <param name="gridTile"></param>
/// <returns></returns>
GridCoordinates GridTileToLocal(MapIndices gridTile);
/// <summary>
/// Transforms grid indices into an outvar tile, returns false if no tile is found
/// Transforms grid indices into a tile reference, returns false if no tile is found.
/// </summary>
/// <param name="gridTile">The Grid Tile indices.</param>
/// <param name="indices">The Grid Tile indices.</param>
/// <param name="tile"></param>
/// <returns></returns>
bool IndicesToTile(MapIndices indices, out TileRef tile);
bool TryGetTileRef(MapIndices indices, out TileRef tile);
/// <summary>
/// Transforms grid tile indices to grid chunk indices.
/// Transforms grid tile indices to chunk indices.
/// </summary>
MapIndices GridTileToGridChunk(MapIndices gridTile);
MapIndices GridTileToChunkIndices(MapIndices gridTile);
/// <summary>
/// Transforms local grid coordinates to chunk indices.
/// </summary>
MapIndices LocalToChunkIndices(GridCoordinates posWorld);
#endregion Transforms
}
+42
View File
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
internal interface IMapGridInternal : IMapGrid
{
GameTick LastModifiedTick { get; }
GameTick CurTick { get; }
/// <summary>
/// The total number of chunks contained on this grid.
/// </summary>
int ChunkCount { get; }
void NotifyTileChanged(in TileRef tileRef, in Tile oldTile);
/// <summary>
/// Returns the chunk at the given indices. If the chunk does not exist,
/// then a new one is generated that is filled with empty space.
/// </summary>
/// <param name="xIndex">The X index of the chunk in this grid.</param>
/// <param name="yIndex">The Y index of the chunk in this grid.</param>
/// <returns>The existing or new chunk.</returns>
IMapChunkInternal GetChunk(int xIndex, int yIndex);
/// <summary>
/// Returns the chunk at the given indices. If the chunk does not exist,
/// then a new one is generated that is filled with empty space.
/// </summary>
/// <param name="chunkIndices">The indices of the chunk in this grid.</param>
/// <returns>The existing or new chunk.</returns>
IMapChunkInternal GetChunk(MapIndices chunkIndices);
/// <summary>
/// Returns all chunks in this grid. This will not generate new chunks.
/// </summary>
/// <returns>All chunks in the grid.</returns>
IReadOnlyDictionary<MapIndices, IMapChunkInternal> GetMapChunks();
}
}
+18
View File
@@ -0,0 +1,18 @@
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Timing;
namespace Robust.Shared.Map
{
/// <inheritdoc />
internal interface IMapManagerInternal : IMapManager
{
IGameTiming GameTiming { get; }
/// <summary>
/// Raises the OnTileChanged event.
/// </summary>
/// <param name="tileRef">A reference to the new tile.</param>
/// <param name="oldTile">The old tile that got replaced.</param>
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile);
}
}
+365
View File
@@ -0,0 +1,365 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
/// <inheritdoc />
internal class MapChunk : IMapChunkInternal
{
private readonly IMapGridInternal _grid;
private readonly MapIndices _gridIndices;
private readonly Tile[,] _tiles;
private readonly SnapGridCell[,] _snapGrid;
private Box2i _cachedBounds;
/// <inheritdoc />
public GameTick LastModifiedTick { get; private set; }
/// <summary>
/// Constructs an instance of a MapGrid chunk.
/// </summary>
/// <param name="grid"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="chunkSize"></param>
public MapChunk(IMapGridInternal grid, int x, int y, ushort chunkSize)
{
_grid = grid;
LastModifiedTick = grid.CurTick;
_gridIndices = new MapIndices(x, y);
ChunkSize = chunkSize;
_tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
}
/// <inheritdoc />
public ushort ChunkSize { get; }
/// <inheritdoc />
public int X => _gridIndices.X;
/// <inheritdoc />
public int Y => _gridIndices.Y;
/// <inheritdoc />
public MapIndices Indices => _gridIndices;
/// <inheritdoc />
public TileRef GetTileRef(ushort xIndex, ushort yIndex)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = ChunkTileToGridTile(new MapIndices(xIndex, yIndex));
return new TileRef(_grid.ParentMapId, _grid.Index, indices, _tiles[xIndex, yIndex]);
}
/// <inheritdoc />
public TileRef GetTileRef(MapIndices indices)
{
if (indices.X >= ChunkSize || indices.X < 0 || indices.Y >= ChunkSize || indices.Y < 0)
throw new ArgumentOutOfRangeException(nameof(indices), "Tile indices out of bounds.");
var chunkIndices = ChunkTileToGridTile(indices);
return new TileRef(_grid.ParentMapId, _grid.Index, chunkIndices, _tiles[indices.X, indices.Y]);
}
/// <inheritdoc />
public Tile GetTile(ushort xIndex, ushort yIndex)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
return _tiles[xIndex, yIndex];
}
/// <inheritdoc />
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
{
for (var x = 0; x < ChunkSize; x++)
for (var y = 0; y < ChunkSize; y++)
{
if (ignoreEmpty && _tiles[x, y].IsEmpty)
continue;
var indices = ChunkTileToGridTile(new MapIndices(x, y));
yield return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[x, y]);
}
}
/// <inheritdoc />
public void SetTile(ushort xIndex, ushort yIndex, Tile tile)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (_tiles[xIndex, yIndex].TypeId == tile.TypeId)
return;
var gridTile = ChunkTileToGridTile(new MapIndices(xIndex, yIndex));
var newTileRef = new TileRef(_grid.ParentMapId, _grid.Index, gridTile, tile);
var oldTile = _tiles[xIndex, yIndex];
LastModifiedTick = _grid.CurTick;
_tiles[xIndex, yIndex] = tile;
CheckBounds(new Vector2i(xIndex, yIndex), tile.IsEmpty);
_grid.NotifyTileChanged(newTileRef, oldTile);
}
/// <summary>
/// Returns an enumerator that iterates through all grid tiles.
/// </summary>
/// <returns></returns>
public IEnumerator<TileRef> GetEnumerator()
{
for (var x = 0; x < ChunkSize; x++)
for (var y = 0; y < ChunkSize; y++)
{
if (_tiles[x, y].IsEmpty)
continue;
var gridTile = ChunkTileToGridTile(new MapIndices(x, y));
yield return new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, _tiles[x, y]);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
public MapIndices GridTileToChunkTile(MapIndices gridTile)
{
var size = ChunkSize;
var x = MathHelper.Mod(gridTile.X, size);
var y = MathHelper.Mod(gridTile.Y, size);
return new MapIndices(x, y);
}
/// <inheritdoc />
public MapIndices ChunkTileToGridTile(MapIndices chunkTile)
{
return chunkTile + _gridIndices * ChunkSize;
}
/// <inheritdoc />
public IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset)
{
if (xCell >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
if (yCell >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
var cell = _snapGrid[xCell, yCell];
var list = offset == SnapGridOffset.Center ? cell.Center : cell.Edge;
if (list == null)
yield break;
foreach (var element in list)
{
yield return element;
}
}
/// <inheritdoc />
public void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
{
if (xCell >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
if (yCell >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
ref var cell = ref _snapGrid[xCell, yCell];
if (offset == SnapGridOffset.Center)
{
if (cell.Center == null)
{
cell.Center = new List<SnapGridComponent>(1);
}
cell.Center.Add(snap);
}
else
{
if (cell.Edge == null)
{
cell.Edge = new List<SnapGridComponent>(1);
}
cell.Edge.Add(snap);
}
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
{
if (xCell >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xCell), "Tile indices out of bounds.");
if (yCell >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yCell), "Tile indices out of bounds.");
ref var cell = ref _snapGrid[xCell, yCell];
if (offset == SnapGridOffset.Center)
{
cell.Center?.Remove(snap);
}
else
{
cell.Edge?.Remove(snap);
}
}
private void CheckBounds(in Vector2i indices, bool empty)
{
var tileBounds = new Box2i(0,0,1, 1).Translated(indices);
if (!empty)
{
// placing tiles can only expand the bounds, it is easier to just blind union
_cachedBounds = _cachedBounds.Size.Equals(Vector2i.Zero) ? tileBounds : _cachedBounds.Union(tileBounds);
}
else
{
// removing tiles can only shrink the bounds, this is not an easy thing to detect
ReduceBoundsX(_tiles, tileBounds, ref _cachedBounds);
ReduceBoundsY(_tiles, tileBounds, ref _cachedBounds);
}
}
private static void ReduceBoundsX(in Tile[,] tiles, Box2i tBounds, ref Box2i cBounds)
{
if (tBounds.Left != cBounds.Left && tBounds.Right != cBounds.Right)
return; // nothing to do, we are not on an edge
var left = tBounds.Left == cBounds.Left;
// removing a tile can shrink the side more than one tile
while (cBounds.Width > 0)
{
// check if we are the only tile holding the side out
if(!AnyTileOnY(tiles, new Vector2i(cBounds.Bottom, cBounds.Top), tBounds.BottomLeft))
return; // our removal does not modify the edge, we are done here
// shrink the chunk bounds
int newLeft;
int newRight;
if (left)
{
newLeft = cBounds.Left + 1;
newRight = cBounds.Right;
tBounds = tBounds.Translated(new Vector2i(1, 0));
}
else
{
newLeft = cBounds.Left;
newRight = cBounds.Right - 1;
tBounds = tBounds.Translated(new Vector2i(-1, 0));
}
cBounds = new Box2i(newLeft, cBounds.Bottom, newRight, cBounds.Top);
}
}
private static void ReduceBoundsY(in Tile[,] tiles, Box2i tBounds, ref Box2i cBounds)
{
if (tBounds.Bottom != cBounds.Bottom && tBounds.Top != cBounds.Top)
return; // nothing to do, we are not on an edge
var bottom = tBounds.Bottom == cBounds.Bottom; // which side we are moving
// removing a tile can shrink the side more than one tile
while (cBounds.Height > 0)
{
// check if we are the only tile holding the side out
if (!AnyTileOnX(tiles, new Vector2i(cBounds.Left, cBounds.Right), tBounds.BottomLeft))
return; // our removal does not modify the edge
// shrink the chunk bounds
int newBottom;
int newTop;
if (bottom)
{
newBottom = cBounds.Bottom + 1;
newTop = cBounds.Top;
tBounds = tBounds.Translated(new Vector2i(0, 1));
}
else
{
newBottom = cBounds.Bottom;
newTop = cBounds.Top - 1;
tBounds = tBounds.Translated(new Vector2i(0, -1));
}
cBounds = new Box2i(cBounds.Left, newBottom, cBounds.Right, newTop);
}
}
private static bool AnyTileOnX(in Tile[,] tiles, Vector2i extents, in Vector2i indices)
{
var y = indices.Y;
for (var x = extents.X; x < extents.Y; x++)
{
if(tiles[x, y].IsEmpty)
continue;
return false;
}
return true;
}
private static bool AnyTileOnY(in Tile[,] tiles, Vector2i extents, in Vector2i indices)
{
var x = indices.X;
for (var y = extents.X; y < extents.Y; y++)
{
if(tiles[x, y].IsEmpty)
continue;
return false;
}
return true;
}
/// <inheritdoc />
public Box2i CalcLocalBounds()
{
return _cachedBounds;
}
/// <inheritdoc />
public override string ToString()
{
return $"Chunk {_gridIndices}";
}
private struct SnapGridCell
{
public List<SnapGridComponent> Center;
public List<SnapGridComponent> Edge;
}
}
}
+419
View File
@@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
/// <inheritdoc />
internal class MapGrid : IMapGridInternal
{
/// <summary>
/// Game tick that the map was created.
/// </summary>
public GameTick CreatedTick { get; }
/// <summary>
/// Last game tick that the map was modified.
/// </summary>
public GameTick LastModifiedTick { get; private set; }
/// <inheritdoc />
public GameTick CurTick => _mapManager.GameTiming.CurTick;
/// <inheritdoc />
public bool IsDefaultGrid => ParentMap.DefaultGrid == this;
/// <inheritdoc />
public IMap ParentMap => _mapManager.GetMap(ParentMapId);
/// <inheritdoc />
public MapId ParentMapId { get; set; }
/// <summary>
/// Grid chunks than make up this grid.
/// </summary>
private readonly Dictionary<MapIndices, IMapChunkInternal> _chunks = new Dictionary<MapIndices, IMapChunkInternal>();
private readonly IMapManagerInternal _mapManager;
private Vector2 _worldPosition;
/// <summary>
/// Initializes a new instance of the <see cref="MapGrid"/> class.
/// </summary>
/// <param name="mapManager">Reference to the <see cref="MapManager"/> that will manage this grid.</param>
/// <param name="gridIndex">Index identifier of this grid.</param>
/// <param name="chunkSize">The dimension of this square chunk.</param>
/// <param name="snapSize">Distance in world units between the lines on the conceptual snap grid.</param>
/// <param name="parentMapId">Parent map identifier.</param>
internal MapGrid(IMapManagerInternal mapManager, GridId gridIndex, ushort chunkSize, float snapSize,
MapId parentMapId)
{
_mapManager = mapManager;
Index = gridIndex;
ChunkSize = chunkSize;
SnapSize = snapSize;
ParentMapId = parentMapId;
LastModifiedTick = CreatedTick = _mapManager.GameTiming.CurTick;
}
/// <summary>
/// Disposes the grid.
/// </summary>
public void Dispose()
{
// Nothing for now.
}
/// <inheritdoc />
public Box2 WorldBounds => LocalBounds.Translated(WorldPosition);
public Box2 LocalBounds { get; private set; }
/// <inheritdoc />
public ushort ChunkSize { get; }
/// <inheritdoc />
public float SnapSize { get; }
/// <inheritdoc />
public GridId Index { get; }
/// <summary>
/// The length of the side of a square tile in world units.
/// </summary>
public ushort TileSize { get; } = 1;
/// <inheritdoc />
public Vector2 WorldPosition
{
get => _worldPosition;
set
{
_worldPosition = value;
LastModifiedTick = _mapManager.GameTiming.CurTick;
}
}
/// <summary>
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
/// </summary>
/// <param name="gridTile">The new tile to check.</param>
/// <param name="empty"></param>
private void UpdateAABB(MapIndices gridTile, bool empty)
{
LocalBounds = new Box2();
foreach (var chunk in _chunks.Values)
{
var chunkBounds = chunk.CalcLocalBounds();
if(chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (LocalBounds.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalBounds = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalBounds = LocalBounds.Union(gridBounds);
}
}
}
/// <inheritdoc />
public void NotifyTileChanged(in TileRef tileRef, in Tile oldTile)
{
LastModifiedTick = _mapManager.GameTiming.CurTick;
UpdateAABB(tileRef.GridIndices, tileRef.Tile.IsEmpty);
_mapManager.RaiseOnTileChanged(tileRef, oldTile);
}
/// <inheritdoc />
public bool OnSnapCenter(Vector2 position)
{
return (FloatMath.CloseTo(position.X % SnapSize, 0) && FloatMath.CloseTo(position.Y % SnapSize, 0));
}
/// <inheritdoc />
public bool OnSnapBorder(Vector2 position)
{
return (FloatMath.CloseTo(position.X % SnapSize, SnapSize / 2) && FloatMath.CloseTo(position.Y % SnapSize, SnapSize / 2));
}
#region TileAccess
/// <inheritdoc />
public TileRef GetTileRef(GridCoordinates worldPos)
{
return GetTileRef(WorldToTile(worldPos));
}
/// <inheritdoc />
public TileRef GetTileRef(MapIndices tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(tileCoordinates);
if (!_chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(ParentMapId, Index, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return output.GetTileRef((ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetAllTiles(bool ignoreSpace = true)
{
foreach (var kvChunk in _chunks)
{
foreach (var tileRef in kvChunk.Value)
{
if (!tileRef.Tile.IsEmpty)
yield return tileRef;
}
}
}
/// <inheritdoc />
public void SetTile(GridCoordinates worldPos, Tile tile)
{
var localTile = WorldToTile(worldPos);
SetTile(new MapIndices(localTile.X, localTile.Y), tile);
}
/// <inheritdoc />
public void SetTile(MapIndices gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true, Predicate<TileRef> predicate = null)
{
//TODO: needs world -> local -> tile translations.
var gridTileLb = new MapIndices((int)Math.Floor(worldArea.Left), (int)Math.Floor(worldArea.Bottom));
var gridTileRt = new MapIndices((int)Math.Floor(worldArea.Right), (int)Math.Floor(worldArea.Top));
var tiles = new List<TileRef>();
for (var x = gridTileLb.X; x <= gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y <= gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(new MapIndices(x, y));
if (_chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new MapIndices(x, y));
var tile = chunk.GetTileRef((ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
{
tiles.Add(tile);
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(ParentMapId, Index, x, y, new Tile());
if (predicate == null || predicate(tile))
{
tiles.Add(tile);
}
}
}
}
return tiles;
}
#endregion TileAccess
#region ChunkAccess
/// <summary>
/// The total number of allocated chunks in the grid.
/// </summary>
public int ChunkCount => _chunks.Count;
/// <inheritdoc />
public IMapChunkInternal GetChunk(int xIndex, int yIndex)
{
return GetChunk(new MapIndices(xIndex, yIndex));
}
/// <inheritdoc />
public IMapChunkInternal GetChunk(MapIndices chunkIndices)
{
if (_chunks.TryGetValue(chunkIndices, out var output))
return output;
return _chunks[chunkIndices] = new MapChunk(this, chunkIndices.X, chunkIndices.Y, ChunkSize);
}
/// <inheritdoc />
public IReadOnlyDictionary<MapIndices, IMapChunkInternal> GetMapChunks()
{
return _chunks;
}
#endregion ChunkAccess
#region SnapGridAccess
/// <inheritdoc />
public IEnumerable<SnapGridComponent> GetSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset)
{
return GetSnapGridCell(SnapGridCellFor(worldPos, offset), offset);
}
/// <inheritdoc />
public IEnumerable<SnapGridComponent> GetSnapGridCell(MapIndices pos, SnapGridOffset offset)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset);
}
/// <inheritdoc />
public MapIndices SnapGridCellFor(GridCoordinates worldPos, SnapGridOffset offset)
{
var local = worldPos.ConvertToGrid(_mapManager, this);
if (offset == SnapGridOffset.Edge)
{
local = local.Offset(new Vector2(TileSize / 2f, TileSize / 2f));
}
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new MapIndices(x, y);
}
/// <inheritdoc />
public void AddToSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
}
/// <inheritdoc />
public void AddToSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
{
AddToSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
{
RemoveFromSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
}
private (IMapChunk, MapIndices) ChunkAndOffsetForTile(MapIndices pos)
{
var gridChunkIndices = GridTileToChunkIndices(pos);
var chunk = GetChunk(gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
#endregion
#region Transforms
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
return posWorld - WorldPosition;
}
/// <inheritdoc />
public GridCoordinates LocalToWorld(GridCoordinates posLocal)
{
return new GridCoordinates(posLocal.Position + WorldPosition, _mapManager.GetGrid(posLocal.GridID).ParentMap);
}
/// <inheritdoc />
public Vector2 ConvertToWorld(Vector2 posLocal)
{
return posLocal + WorldPosition;
}
public MapIndices WorldToTile(Vector2 posWorld)
{
var local = WorldToLocal(posWorld);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new MapIndices(x, y);
}
/// <summary>
/// Transforms global world coordinates to tile indices relative to grid origin.
/// </summary>
public MapIndices WorldToTile(GridCoordinates posWorld)
{
var local = posWorld.ConvertToGrid(_mapManager, this);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new MapIndices(x, y);
}
/// <summary>
/// Transforms global world coordinates to chunk indices relative to grid origin.
/// </summary>
public MapIndices LocalToChunkIndices(GridCoordinates posWorld)
{
var local = posWorld.ConvertToGrid(_mapManager, this);
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
return new MapIndices(x, y);
}
/// <inheritdoc />
public MapIndices GridTileToChunkIndices(MapIndices gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
return new MapIndices(x, y);
}
/// <inheritdoc />
public GridCoordinates GridTileToLocal(MapIndices gridTile)
{
return new GridCoordinates(gridTile.X * TileSize + (TileSize / 2f), gridTile.Y * TileSize + (TileSize / 2f), this);
}
/// <inheritdoc />
public bool TryGetTileRef(MapIndices indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!_chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = chunk.GetTileRef(cTileIndices);
return true;
}
#endregion Transforms
}
}
+12 -1
View File
@@ -1,5 +1,6 @@
using System;
using System;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
namespace Robust.Shared.Map
@@ -87,5 +88,15 @@ namespace Robust.Shared.Map
{
return X ^ (Y * 23011);
}
public static implicit operator Vector2i(in MapIndices indices)
{
return new Vector2i(indices.X, indices.Y);
}
public static implicit operator MapIndices(in Vector2i indices)
{
return new MapIndices(indices.X, indices.Y);
}
}
}
-231
View File
@@ -1,231 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
public partial class MapManager
{
/// <summary>
/// A square section of the map.
/// </summary>
internal class Chunk : IMapChunk
{
internal GameTick LastModifiedTick { get; private set; }
private readonly MapGrid _grid;
private readonly MapIndices _gridIndices;
private readonly MapManager _mapManager;
internal readonly Tile[,] _tiles;
private readonly SnapGridCell[,] _snapGrid;
/// <summary>
/// Constructs an instance of a MapGrid chunk.
/// </summary>
/// <param name="manager"></param>
/// <param name="grid"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="chunkSize"></param>
public Chunk(MapManager manager, MapGrid grid, int x, int y, ushort chunkSize)
{
_mapManager = manager;
LastModifiedTick = _mapManager._gameTiming.CurTick;
ChunkSize = chunkSize;
_grid = grid;
_gridIndices = new MapIndices(x, y);
_tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
}
/// <inheritdoc />
public ushort ChunkSize { get; }
/// <inheritdoc />
public int X => _gridIndices.X;
/// <inheritdoc />
public int Y => _gridIndices.Y;
public MapIndices Index => new MapIndices(X, Y);
/// <summary>
/// Returns the tile at the given indices. The tile indices are relative locations to the chunk origin,
/// NOT local to the grid.
/// </summary>
/// <param name="xTile">The X tile index relative to the chunk origin.</param>
/// <param name="yTile">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
public TileRef GetTile(ushort xTile, ushort yTile)
{
// array out of bounds
if (xTile >= ChunkSize || yTile >= ChunkSize)
throw new ArgumentOutOfRangeException("Tile indices out of bounds.");
var indices = ChunkTileToGridTile(new MapIndices(xTile, yTile));
return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[xTile, yTile]);
}
public TileRef GetTile(MapIndices indices)
{
// array out of bounds
if (indices.X >= ChunkSize || indices.X < 0 || indices.Y >= ChunkSize || indices.Y < 0)
throw new ArgumentOutOfRangeException("Tile indices out of bounds.");
return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[indices.X, indices.Y]);
}
/// <inheritdoc />
[Obsolete("Enumerate over the chunk instead.")]
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
{
for (var x = 0; x < ChunkSize; x++)
for (var y = 0; y < ChunkSize; y++)
{
if (_tiles[x, y].IsEmpty)
continue;
var indices = ChunkTileToGridTile(new MapIndices(x, y));
yield return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[x, y]);
}
}
/// <inheritdoc />
public void SetTile(ushort xChunkTile, ushort yChunkTile, Tile tile)
{
if (xChunkTile >= ChunkSize || yChunkTile >= ChunkSize)
throw new ArgumentException("Tile indices out of bounds.");
// same tile, no point to continue
if (_tiles[xChunkTile, yChunkTile].TypeId == tile.TypeId)
return;
var gridTile = ChunkTileToGridTile(new MapIndices(xChunkTile, yChunkTile));
var newTileRef = new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, tile);
var oldTile = _tiles[xChunkTile, yChunkTile];
_grid.LastModifiedTick = LastModifiedTick = _mapManager._gameTiming.CurTick;
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
_grid.UpdateAABB(gridTile);
_tiles[xChunkTile, yChunkTile] = tile;
}
/// <summary>
/// Returns an enumerator that iterates through all grid tiles.
/// </summary>
/// <returns></returns>
public IEnumerator<TileRef> GetEnumerator()
{
for (var x = 0; x < ChunkSize; x++)
for (var y = 0; y < ChunkSize; y++)
{
var gridTile = ChunkTileToGridTile(new MapIndices(x, y));
yield return new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, _tiles[x, y]);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
public MapIndices GridTileToChunkTile(MapIndices gridTile)
{
var size = ChunkSize;
var x = MathHelper.Mod(gridTile.X, size);
var y = MathHelper.Mod(gridTile.Y, size);
return new MapIndices(x, y);
}
/// <inheritdoc />
public void SetTile(ushort xChunkTile, ushort yChunkTile, ushort tileId, ushort tileData = 0)
{
SetTile(xChunkTile, yChunkTile, new Tile(tileId, tileData));
}
public IEnumerable<SnapGridComponent> GetSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset)
{
var cell = _snapGrid[xCell, yCell];
List<SnapGridComponent> list;
if (offset == SnapGridOffset.Center)
{
list = cell.Center;
}
else
{
list = cell.Edge;
}
if (list != null)
{
foreach (var element in list)
{
yield return element;
}
}
}
public void AddToSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
{
ref var cell = ref _snapGrid[xCell, yCell];
if (offset == SnapGridOffset.Center)
{
if (cell.Center == null)
{
cell.Center = new List<SnapGridComponent>(1);
}
cell.Center.Add(snap);
}
else
{
if (cell.Edge == null)
{
cell.Edge = new List<SnapGridComponent>(1);
}
cell.Edge.Add(snap);
}
}
public void RemoveFromSnapGridCell(ushort xCell, ushort yCell, SnapGridOffset offset, SnapGridComponent snap)
{
ref var cell = ref _snapGrid[xCell, yCell];
if (offset == SnapGridOffset.Center)
{
cell.Center?.Remove(snap);
}
else
{
cell.Edge?.Remove(snap);
}
}
/// <summary>
/// Translates chunk tile indices to grid tile indices.
/// </summary>
/// <param name="chunkTile">The indices relative to the chunk origin.</param>
/// <returns>The indices relative to the grid origin.</returns>
private MapIndices ChunkTileToGridTile(MapIndices chunkTile)
{
return chunkTile + _gridIndices * ChunkSize;
}
/// <inheritdoc />
public override string ToString()
{
return $"Chunk {_gridIndices}, {ChunkSize}";
}
private struct SnapGridCell
{
public List<SnapGridComponent> Center;
public List<SnapGridComponent> Edge;
}
}
}
}
+2 -2
View File
@@ -75,7 +75,7 @@ namespace Robust.Shared.Map
public IMapGrid FindGridAt(Vector2 worldPos)
{
foreach (var kvGrid in _grids)
if (kvGrid.Value.AABBWorld.Contains(worldPos) && kvGrid.Value != DefaultGrid)
if (kvGrid.Value.WorldBounds.Contains(worldPos) && kvGrid.Value != DefaultGrid)
return kvGrid.Value;
return DefaultGrid;
}
@@ -89,7 +89,7 @@ namespace Robust.Shared.Map
{
var gridList = new List<MapGrid>();
foreach (var kvGrid in _grids)
if (kvGrid.Value.AABBWorld.Intersects(worldArea))
if (kvGrid.Value.WorldBounds.Intersects(worldArea))
gridList.Add(kvGrid.Value);
return gridList;
}
-418
View File
@@ -1,418 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
/// <inheritdoc cref="IMapManager"/>
public partial class MapManager
{
/// <inheritdoc />
internal class MapGrid : IMapGrid
{
/// <summary>
/// Game tick that the map was created.
/// </summary>
public GameTick CreatedTick { get; }
/// <summary>
/// Last game tick that the map was modified.
/// </summary>
public GameTick LastModifiedTick { get; internal set; }
/// <inheritdoc />
public bool IsDefaultGrid => ParentMap.DefaultGrid == this;
/// <inheritdoc />
public IMap ParentMap => _mapManager.GetMap(ParentMapId);
/// <inheritdoc />
public MapId ParentMapId { get; set; }
/// <summary>
/// Grid chunks than make up this grid.
/// </summary>
internal readonly Dictionary<MapIndices, Chunk> _chunks = new Dictionary<MapIndices, Chunk>();
private readonly MapManager _mapManager;
private Vector2 _worldPosition;
/// <summary>
/// Initializes a new instance of the <see cref="MapGrid"/> class.
/// </summary>
/// <param name="mapManager">Reference to the <see cref="MapManager"/> that will manage this grid.</param>
/// <param name="gridIndex">Index identifier of this grid.</param>
/// <param name="chunkSize">The dimension of this square chunk.</param>
/// <param name="snapSize">Distance in world units between the lines on the conceptual snap grid.</param>
/// <param name="parentMapId">Parent map identifier.</param>
internal MapGrid(MapManager mapManager, GridId gridIndex, ushort chunkSize, float snapSize, MapId parentMapId)
{
_mapManager = mapManager;
Index = gridIndex;
ChunkSize = chunkSize;
SnapSize = snapSize;
ParentMapId = parentMapId;
LastModifiedTick = CreatedTick = _mapManager._gameTiming.CurTick;
}
/// <summary>
/// Disposes the grid.
/// </summary>
public void Dispose()
{
// Nothing for now.
}
/// <inheritdoc />
public Box2 AABBWorld { get; private set; }
/// <inheritdoc />
public ushort ChunkSize { get; }
/// <inheritdoc />
public float SnapSize { get; }
/// <inheritdoc />
public GridId Index { get; }
/// <summary>
/// The length of the side of a square tile in world units.
/// </summary>
public ushort TileSize { get; } = 1;
/// <inheritdoc />
public Vector2 WorldPosition
{
get => _worldPosition;
set
{
_worldPosition = value;
LastModifiedTick = _mapManager._gameTiming.CurTick;
}
}
/// <summary>
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
/// </summary>
/// <param name="gridTile">The new tile to check.</param>
public void UpdateAABB(MapIndices gridTile)
{
var worldPos = GridTileToLocal(gridTile).ToWorld(_mapManager);
if (AABBWorld.Contains(worldPos.Position))
return;
// rect union
var a = AABBWorld;
var b = worldPos;
var minX = Math.Min(a.Left, b.X);
var maxX = Math.Max(a.Right, b.X);
var minY = Math.Min(a.Bottom, b.Y);
var maxY = Math.Max(a.Top, b.Y);
AABBWorld = Box2.FromDimensions(minX, minY, maxX - minX, maxY - minY);
}
/// <inheritdoc />
public bool OnSnapCenter(Vector2 position)
{
return (FloatMath.CloseTo(position.X % SnapSize, 0) && FloatMath.CloseTo(position.Y % SnapSize, 0));
}
/// <inheritdoc />
public bool OnSnapBorder(Vector2 position)
{
return (FloatMath.CloseTo(position.X % SnapSize, SnapSize / 2) && FloatMath.CloseTo(position.Y % SnapSize, SnapSize / 2));
}
#region TileAccess
/// <inheritdoc />
public TileRef GetTile(GridCoordinates worldPos)
{
return GetTile(WorldToTile(worldPos));
}
/// <inheritdoc />
public TileRef GetTile(MapIndices tileCoordinates)
{
var chunkIndices = GridTileToGridChunk(tileCoordinates);
if (!_chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(ParentMapId, Index, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return output.GetTile((ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetAllTiles(bool ignoreSpace = true)
{
foreach (var kvChunk in _chunks)
{
foreach (var tileRef in kvChunk.Value)
{
if (!tileRef.Tile.IsEmpty)
yield return tileRef;
}
}
}
/// <inheritdoc />
public void SetTile(GridCoordinates worldPos, Tile tile)
{
var localTile = WorldToTile(worldPos);
SetTile(localTile.X, localTile.Y, tile);
}
private void SetTile(int xIndex, int yIndex, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(new MapIndices(xIndex, yIndex));
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
/// <inheritdoc />
public void SetTile(GridCoordinates worldPos, ushort tileId, ushort tileData = 0)
{
SetTile(worldPos, new Tile(tileId, tileData));
}
public void SetTile(MapIndices gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true, Predicate<TileRef> predicate = null)
{
//TODO: needs world -> local -> tile translations.
var gridTileLb = new MapIndices((int)Math.Floor(worldArea.Left), (int)Math.Floor(worldArea.Bottom));
var gridTileRt = new MapIndices((int)Math.Floor(worldArea.Right), (int)Math.Floor(worldArea.Top));
var tiles = new List<TileRef>();
for (var x = gridTileLb.X; x <= gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y <= gridTileRt.Y; y++)
{
var gridChunk = GridTileToGridChunk(new MapIndices(x, y));
if (_chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new MapIndices(x, y));
var tile = chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
{
tiles.Add(tile);
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(ParentMapId, Index, x, y, new Tile());
if (predicate == null || predicate(tile))
{
tiles.Add(tile);
}
}
}
}
return tiles;
}
#endregion TileAccess
#region ChunkAccess
/// <summary>
/// The total number of allocated chunks in the grid.
/// </summary>
public int ChunkCount => _chunks.Count;
/// <inheritdoc />
public IMapChunk GetChunk(int xIndex, int yIndex)
{
return GetChunk(new MapIndices(xIndex, yIndex));
}
/// <inheritdoc />
public IMapChunk GetChunk(MapIndices chunkIndices)
{
if (_chunks.TryGetValue(chunkIndices, out var output))
return output;
return _chunks[chunkIndices] = new Chunk(_mapManager, this, chunkIndices.X, chunkIndices.Y, ChunkSize);
}
/// <inheritdoc />
public IEnumerable<IMapChunk> GetMapChunks()
{
foreach (var kvChunk in _chunks)
{
yield return kvChunk.Value;
}
}
#endregion ChunkAccess
#region SnapGridAccess
/// <inheritdoc />
public IEnumerable<SnapGridComponent> GetSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset)
{
return GetSnapGridCell(SnapGridCellFor(worldPos, offset), offset);
}
/// <inheritdoc />
public IEnumerable<SnapGridComponent> GetSnapGridCell(MapIndices pos, SnapGridOffset offset)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset);
}
/// <inheritdoc />
public MapIndices SnapGridCellFor(GridCoordinates worldPos, SnapGridOffset offset)
{
var local = worldPos.ConvertToGrid(_mapManager, this);
if (offset == SnapGridOffset.Edge)
{
local = local.Offset(new Vector2(TileSize / 2f, TileSize / 2f));
}
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new MapIndices(x, y);
}
/// <inheritdoc />
public void AddToSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
}
/// <inheritdoc />
public void AddToSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
{
AddToSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(MapIndices pos, SnapGridOffset offset, SnapGridComponent snap)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, offset, snap);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(GridCoordinates worldPos, SnapGridOffset offset, SnapGridComponent snap)
{
RemoveFromSnapGridCell(SnapGridCellFor(worldPos, offset), offset, snap);
}
private (IMapChunk, MapIndices) ChunkAndOffsetForTile(MapIndices pos)
{
var gridChunkIndices = GridTileToGridChunk(pos);
var chunk = GetChunk(gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
#endregion
#region Transforms
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
return posWorld - WorldPosition;
}
/// <inheritdoc />
public GridCoordinates LocalToWorld(GridCoordinates posLocal)
{
return new GridCoordinates(posLocal.Position + WorldPosition, _mapManager.GetGrid(posLocal.GridID).ParentMap);
}
/// <inheritdoc />
public Vector2 ConvertToWorld(Vector2 posLocal)
{
return posLocal + WorldPosition;
}
public MapIndices WorldToTile(Vector2 posWorld)
{
var local = WorldToLocal(posWorld);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new MapIndices(x, y);
}
/// <summary>
/// Transforms global world coordinates to tile indices relative to grid origin.
/// </summary>
public MapIndices WorldToTile(GridCoordinates posWorld)
{
var local = posWorld.ConvertToGrid(_mapManager, this);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new MapIndices(x, y);
}
/// <summary>
/// Transforms global world coordinates to chunk indices relative to grid origin.
/// </summary>
public MapIndices WorldToChunk(GridCoordinates posWorld)
{
var local = posWorld.ConvertToGrid(_mapManager, this);
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
return new MapIndices(x, y);
}
/// <inheritdoc />
public MapIndices GridTileToGridChunk(MapIndices gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
return new MapIndices(x, y);
}
/// <inheritdoc />
public GridCoordinates GridTileToLocal(MapIndices gridTile)
{
return new GridCoordinates(gridTile.X * TileSize + (TileSize / 2), gridTile.Y * TileSize + (TileSize / 2), this);
}
/// <inheritdoc />
public bool IndicesToTile(MapIndices indices, out TileRef tile)
{
var chunkIndices = new MapIndices(indices.X / ChunkSize, indices.Y / ChunkSize);
if (!_chunks.ContainsKey(chunkIndices))
{
tile = new TileRef(); //Nothing should ever use or access this, bool check should occur first
return false;
}
var chunk = _chunks[chunkIndices];
tile = chunk.GetTile(new MapIndices(indices.X % ChunkSize, indices.Y % ChunkSize));
return true;
}
#endregion Transforms
}
}
}
+3 -3
View File
@@ -30,7 +30,7 @@ namespace Robust.Shared.Map
}
var chunkData = new List<GameStateMapData.ChunkDatum>();
foreach (var (index, chunk) in grid._chunks)
foreach (var (index, chunk) in grid.GetMapChunks())
{
if (chunk.LastModifiedTick < fromTick)
{
@@ -45,7 +45,7 @@ namespace Robust.Shared.Map
for (var x = 0; x < grid.ChunkSize; x++)
for (var y = 0; y < grid.ChunkSize; y++)
{
tileBuffer[x * grid.ChunkSize + y] = chunk._tiles[x, y];
tileBuffer[x * grid.ChunkSize + y] = chunk.GetTile((ushort) x, (ushort) y);
}
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
@@ -153,7 +153,7 @@ namespace Robust.Shared.Map
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunkData.TileData[counter++];
if (chunk.GetTile(x, y).Tile != tile)
if (chunk.GetTileRef(x, y).Tile != tile)
{
chunk.SetTile(x, y, tile);
modified.Add((new MapIndices(chunk.X * grid.ChunkSize + x, chunk.Y * grid.ChunkSize + y), tile));
+5 -3
View File
@@ -9,10 +9,12 @@ using Robust.Shared.Utility;
namespace Robust.Shared.Map
{
/// <inheritdoc />
public partial class MapManager : IMapManager, IPostInjectInit
/// <inheritdoc cref="IMapManager"/>
public partial class MapManager : IMapManagerInternal, IPostInjectInit
{
[Dependency] private protected readonly IGameTiming _gameTiming;
[Dependency] private readonly IGameTiming _gameTiming;
public IGameTiming GameTiming => _gameTiming;
/// <inheritdoc />
public IMap DefaultMap => GetMap(MapId.Nullspace);
+1 -1
View File
@@ -15,7 +15,7 @@ namespace Robust.Shared.Map
public readonly MapId MapIndex;
/// <summary>
/// Identifier of the <see cref="MapManager.MapGrid"/> this Tile belongs to.
/// Identifier of the <see cref="MapGrid"/> this Tile belongs to.
/// </summary>
public readonly GridId GridIndex;
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Serialization;
+7 -4
View File
@@ -171,8 +171,7 @@
<Compile Include="Interfaces\Resources\IResourceManager.cs" />
<Compile Include="Interfaces\Resources\IWritableDirProvider.cs" />
<Compile Include="Interfaces\Serialization\IRobustSerializer.cs" />
<Compile Include="Interfaces\Map\IMapChunk.cs" />
<Compile Include="Interfaces\Map\IMapGrid.cs" />
<Compile Include="Map\IMapGrid.cs" />
<Compile Include="Interfaces\Map\IMapManager.cs" />
<Compile Include="Interfaces\Map\ITileDefinition.cs" />
<Compile Include="Interfaces\Map\ITileDefinitionManager.cs" />
@@ -194,6 +193,10 @@
<Compile Include="Log\LogManager.cs" />
<Compile Include="Map\GridId.cs" />
<Compile Include="Map\Coordinates.cs" />
<Compile Include="Map\IMapChunk.cs" />
<Compile Include="Map\IMapChunkInternal.cs" />
<Compile Include="Map\IMapGridInternal.cs" />
<Compile Include="Map\IMapManagerInternal.cs" />
<Compile Include="Map\MapId.cs" />
<Compile Include="Map\MapManager.cs" />
<Compile Include="Map\MapManager.Network.cs" />
@@ -304,7 +307,7 @@
<Compile Include="GameObjects\Components\NetIDs.cs" />
<Compile Include="Input\BoundKeyMap.cs" />
<Compile Include="Map\MapManager.Map.cs" />
<Compile Include="Map\MapManager.MapGrid.cs" />
<Compile Include="Map\MapGrid.cs" />
<Compile Include="ViewVariables\ViewVariablesAttribute.cs" />
<Compile Include="ViewVariables\ViewVariablesBlob.cs" />
<Compile Include="ViewVariables\ViewVariablesManagerShared.cs" />
@@ -374,7 +377,7 @@
<ResourceRoot />
<Visible>False</Visible>
</Resources>
<Compile Include="Map\MapManager.Chunk.cs" />
<Compile Include="Map\MapChunk.cs" />
<Compile Include="Map\MapIndices.cs" />
<Compile Include="GameObjects\Components\Renderable\SharedSpriteComponent.cs" />
<Compile Include="Interfaces\Serialization\IExposeData.cs" />
@@ -7,6 +7,7 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
+6 -3
View File
@@ -58,9 +58,9 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.10.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Smocks.ExtensionPackage">
<Version>0.7.5-beta2</Version>
</PackageReference>
@@ -110,7 +110,10 @@
<Compile Include="Shared\GameObjects\ComponentManager_Test.cs" />
<Compile Include="Shared\GameObjects\EntityState_Tests.cs" />
<Compile Include="Shared\IoC\IoCManager_Test.cs" />
<Compile Include="Shared\Map\MapChunk_Tests.cs" />
<Compile Include="Shared\Map\MapGrid_Tests.cs" />
<Compile Include="Shared\Maths\Angle_Test.cs" />
<Compile Include="Shared\Maths\Box2i_Test.cs" />
<Compile Include="Shared\Maths\UIBox2i_Test.cs" />
<Compile Include="Shared\Maths\Box2_Test.cs" />
<Compile Include="Shared\Maths\UIBox2_Test.cs" />
@@ -0,0 +1,455 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Robust.UnitTesting.Shared.Map
{
[TestFixture, Parallelizable, TestOf(typeof(MapChunk))]
class MapChunk_Tests
{
[Test]
public void GetChunkSize()
{
var chunk = MapChunkFactory(7, 9);
Assert.That(chunk.ChunkSize, Is.EqualTo((ushort)8));
}
[Test]
public void GetIndices()
{
var chunk = MapChunkFactory(7, 9);
Assert.That(chunk.X, Is.EqualTo(7));
Assert.That(chunk.Y, Is.EqualTo(9));
Assert.That(chunk.Indices, Is.EqualTo(new MapIndices(7,9)));
}
[Test]
public void ConstructorSetsLastTick()
{
var chunk = MapChunkFactory(7, 9);
Assert.That(chunk.LastModifiedTick, Is.EqualTo(new GameTick(11)));
}
[Test]
public void SetTileThrowsOutOfRange()
{
var chunk = MapChunkFactory(7, 9);
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.SetTile(8,0, new Tile())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.SetTile(0, 8, new Tile())));
}
[Test]
public void SetTileModifiesLastTick()
{
var curTick = new GameTick(11);
var mapGrid = new Mock<IMapGridInternal>();
mapGrid.SetupGet(f => f.CurTick).Returns((() => curTick));
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
curTick = new GameTick(13);
chunk.SetTile(3, 5, new Tile(1, 3));
Assert.That(chunk.LastModifiedTick, Is.EqualTo(new GameTick(13)));
}
[Test]
public void SetTileDuplicateDoesNotModifyLastTick()
{
var curTick = new GameTick(11);
var mapGrid = new Mock<IMapGridInternal>();
mapGrid.SetupGet(f => f.CurTick).Returns((() => curTick));
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
curTick = new GameTick(13);
chunk.SetTile(3, 5, new Tile(1, 3));
curTick = new GameTick(14);
chunk.SetTile(3, 5, new Tile(1, 3));
Assert.That(chunk.LastModifiedTick, Is.EqualTo(new GameTick(13)));
}
[Test]
public void GetTileRefByIndex()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(3, 5, new Tile(1, 3));
var result = chunk.GetTileRef(3, 5);
Assert.That(result.X, Is.EqualTo(8 * 7 + 3));
Assert.That(result.Y, Is.EqualTo(8 * 9 + 5));
Assert.That(result.Tile.TypeId, Is.EqualTo(1));
Assert.That(result.Tile.Data, Is.EqualTo((ushort) 3));
Assert.That(result.GridIndex, Is.EqualTo(new GridId(13)));
Assert.That(result.MapIndex, Is.EqualTo(new MapId(11)));
}
[Test]
public void GetTileRefByIndices()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(3, 5, new Tile(1, 3));
var result = chunk.GetTileRef(new MapIndices(3, 5));
Assert.That(result.X, Is.EqualTo(8 * 7 + 3));
Assert.That(result.Y, Is.EqualTo(8 * 9 + 5));
Assert.That(result.Tile.TypeId, Is.EqualTo(1));
Assert.That(result.Tile.Data, Is.EqualTo((ushort)3));
Assert.That(result.GridIndex, Is.EqualTo(new GridId(13)));
Assert.That(result.MapIndex, Is.EqualTo(new MapId(11)));
}
[Test]
public void GetTileRefThrowsOutOfRange()
{
var chunk = MapChunkFactory(7, 9);
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(8, 0)));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(0, 8)));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(new MapIndices(8,0))));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTileRef(new MapIndices(0, 8))));
}
[Test]
public void GetTileByIndex()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(3, 5, new Tile(1, 3));
var result = chunk.GetTile(3, 5);
Assert.That(result.TypeId, Is.EqualTo(1));
Assert.That(result.Data, Is.EqualTo((ushort)3));
}
[Test]
public void GetTileByIndexThrowsOutOfRange()
{
var chunk = MapChunkFactory(7, 9);
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTile(8, 0)));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetTile(0, 8)));
}
[Test]
public void GetAllTilesNotEmpty()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(5, 4, new Tile(5, 7));
chunk.SetTile(3, 5, new Tile(1, 3));
var tiles = chunk.GetAllTiles().ToList();
Assert.That(tiles.Count, Is.EqualTo(2));
// Order is guaranteed to be row-major (because C# is), the Y value is contiguous in memory
Assert.That(tiles[0],
Is.EqualTo(new TileRef(new MapId(11),
new GridId(13),
new MapIndices(8 * 7 + 3, 8 * 9 + 5),
new Tile(1, 3))));
Assert.That(tiles[1],
Is.EqualTo(new TileRef(new MapId(11),
new GridId(13),
new MapIndices(8 * 7 + 5, 8 * 9 + 4),
new Tile(5, 7))));
}
[Test]
public void GetAllTilesEmpty()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(5, 4, new Tile(5, 7));
chunk.SetTile(3, 5, new Tile(1, 3));
var tiles = chunk.GetAllTiles(false).ToList();
Assert.That(tiles.Count, Is.EqualTo(8*8));
// Order is guaranteed to be row-major (because C# is), the Y value is contiguous in memory
Assert.That(tiles[8*3+5],
Is.EqualTo(new TileRef(new MapId(11),
new GridId(13),
new MapIndices(8 * 7 + 3, 8 * 9 + 5),
new Tile(1, 3))));
Assert.That(tiles[8*5+4],
Is.EqualTo(new TileRef(new MapId(11),
new GridId(13),
new MapIndices(8 * 7 + 5, 8 * 9 + 4),
new Tile(5, 7))));
}
[Test]
public void SetTileNotifiesGrid()
{
var mapGrid = new Mock<IMapGridInternal>();
mapGrid.SetupGet(f => f.CurTick).Returns(new GameTick(11));
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
mapGrid.Setup(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny)).Verifiable();
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
chunk.SetTile(3, 5, new Tile(1, 3));
mapGrid.Verify(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny), Times.Once);
}
[Test]
public void SetTileDuplicateNotifiesOnce()
{
var mapGrid = new Mock<IMapGridInternal>();
mapGrid.SetupGet(f => f.CurTick).Returns(new GameTick(11));
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
mapGrid.Setup(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny)).Verifiable();
var chunk = new MapChunk(mapGrid.Object, 7, 9, 8);
chunk.SetTile(3, 5, new Tile(1, 3));
chunk.SetTile(3, 5, new Tile(1, 3));
mapGrid.Verify(f => f.NotifyTileChanged(It.Ref<TileRef>.IsAny, It.Ref<Tile>.IsAny), Times.Once);
}
[Test]
public void EnumerateTiles()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(5, 4, new Tile(5, 7));
chunk.SetTile(3, 5, new Tile(1, 3));
var tiles = new List<TileRef>();
foreach (var tileRef in ((IEnumerable)chunk))
{
tiles.Add((TileRef)tileRef);
}
Assert.That(tiles.Count, Is.EqualTo(2));
// Order is guaranteed to be row-major (because C# is), the Y value is contiguous in memory
Assert.That(tiles[0],
Is.EqualTo(new TileRef(new MapId(11),
new GridId(13),
new MapIndices(8 * 7 + 3, 8 * 9 + 5),
new Tile(1, 3))));
Assert.That(tiles[1],
Is.EqualTo(new TileRef(new MapId(11),
new GridId(13),
new MapIndices(8 * 7 + 5, 8 * 9 + 4),
new Tile(5, 7))));
}
[Test]
public void GridToChunkIndices()
{
// chunk indices are relative to the lowest coordinates of the chunk
// EX 8x8 chunk (0,0) occupies tiles 0 to 8 on each axis
// 8x8 chunk (-1,-1) occupies tiles -8 to -1 on each axis
var chunk = MapChunkFactory(-1, -1);
var indices = chunk.GridTileToChunkTile(new MapIndices(-3, -5));
// drawing this out helps a ton
// grid tile -1,-1 is chunk tile 7,7
// grid tile -8,-8 is chunk tile 0,0
Assert.That(indices, Is.EqualTo(new MapIndices(5, 3)));
}
[Test]
public void GetToString()
{
var chunk = MapChunkFactory(7, 9);
var result = chunk.ToString();
Assert.That(result, Is.EqualTo("Chunk {7,9}"));
}
[Test]
public void GetEmptySnapGrid()
{
var chunk = MapChunkFactory(7, 9);
Assert.That(chunk.GetSnapGridCell(0,0, SnapGridOffset.Center).ToList().Count, Is.EqualTo(0));
Assert.That(chunk.GetSnapGridCell(0, 0, SnapGridOffset.Edge).ToList().Count, Is.EqualTo(0));
}
[Test]
public void GetSnapGridThrowsOutOfRange()
{
var chunk = MapChunkFactory(7, 9);
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(8,0, SnapGridOffset.Center).ToList()));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(0, 8, SnapGridOffset.Center).ToList()));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(8,0,SnapGridOffset.Edge).ToList()));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.GetSnapGridCell(0, 8, SnapGridOffset.Edge).ToList()));
}
[Test]
public void AddSnapGridCellCenter()
{
var chunk = MapChunkFactory(7, 9);
var snapGridComponent = new SnapGridComponent();
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, snapGridComponent);
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, new SnapGridComponent());
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Center, new SnapGridComponent());
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Center).ToList();
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result[0], Is.EqualTo(snapGridComponent));
}
[Test]
public void AddSnapGridCellEdge()
{
var chunk = MapChunkFactory(7, 9);
var snapGridComponent = new SnapGridComponent();
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, snapGridComponent);
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, new SnapGridComponent());
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Edge, new SnapGridComponent());
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Edge).ToList();
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result[0], Is.EqualTo(snapGridComponent));
}
[Test]
public void AddSnapGridThrowsOutOfRange()
{
var chunk = MapChunkFactory(7, 9);
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(8, 0, SnapGridOffset.Center, new SnapGridComponent())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(0, 8, SnapGridOffset.Center, new SnapGridComponent())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(8, 0, SnapGridOffset.Edge, new SnapGridComponent())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.AddToSnapGridCell(0, 8, SnapGridOffset.Edge, new SnapGridComponent())));
}
[Test]
public void RemoveSnapGridCellCenter()
{
var chunk = MapChunkFactory(7, 9);
var snapGridComponent = new SnapGridComponent();
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, snapGridComponent);
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, new SnapGridComponent());
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Center, new SnapGridComponent());
chunk.RemoveFromSnapGridCell(3, 5, SnapGridOffset.Center, snapGridComponent);
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Center).ToList();
Assert.That(result.Count, Is.EqualTo(0));
}
[Test]
public void RemoveSnapGridCellEdge()
{
var chunk = MapChunkFactory(7, 9);
var snapGridComponent = new SnapGridComponent();
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Edge, snapGridComponent);
chunk.AddToSnapGridCell(3, 5, SnapGridOffset.Center, new SnapGridComponent());
chunk.AddToSnapGridCell(3, 6, SnapGridOffset.Edge, new SnapGridComponent());
chunk.RemoveFromSnapGridCell(3, 5, SnapGridOffset.Edge, snapGridComponent);
var result = chunk.GetSnapGridCell(3, 5, SnapGridOffset.Edge).ToList();
Assert.That(result.Count, Is.EqualTo(0));
}
[Test]
public void RemoveSnapGridThrowsOutOfRange()
{
var chunk = MapChunkFactory(7, 9);
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(8, 0, SnapGridOffset.Center, new SnapGridComponent())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(0, 8, SnapGridOffset.Center, new SnapGridComponent())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(8, 0, SnapGridOffset.Edge, new SnapGridComponent())));
Assert.Throws<ArgumentOutOfRangeException>((() => chunk.RemoveFromSnapGridCell(0, 8, SnapGridOffset.Edge, new SnapGridComponent())));
}
[Test]
public void BoundsExpandWhenTileAdded()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(3, 5, new Tile(1));
chunk.SetTile(5, 7, new Tile(1));
var bounds = chunk.CalcLocalBounds();
Assert.That(bounds.Left, Is.EqualTo(3));
Assert.That(bounds.Bottom, Is.EqualTo(5));
Assert.That(bounds.Right, Is.EqualTo(6));
Assert.That(bounds.Top, Is.EqualTo(8));
}
[Test]
public void BoundsContractWhenTileRemovedBL()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(3, 5, new Tile(1));
chunk.SetTile(5, 7, new Tile(1));
chunk.SetTile(3, 5, Tile.Empty);
var bounds = chunk.CalcLocalBounds();
Assert.That(bounds.Left, Is.EqualTo(5));
Assert.That(bounds.Bottom, Is.EqualTo(7));
Assert.That(bounds.Right, Is.EqualTo(6));
Assert.That(bounds.Top, Is.EqualTo(8));
}
[Test]
public void BoundsContractWhenTileRemovedTR()
{
var chunk = MapChunkFactory(7, 9);
chunk.SetTile(3, 5, new Tile(1));
chunk.SetTile(5, 7, new Tile(1));
chunk.SetTile(5, 7, Tile.Empty);
var bounds = chunk.CalcLocalBounds();
Assert.That(bounds.Left, Is.EqualTo(3));
Assert.That(bounds.Bottom, Is.EqualTo(5));
Assert.That(bounds.Right, Is.EqualTo(4));
Assert.That(bounds.Top, Is.EqualTo(6));
}
public IMapChunkInternal MapChunkFactory(int xChunkIndex, int yChunkIndex)
{
var mapGrid = new Mock<IMapGridInternal>();
mapGrid.SetupGet(f=>f.CurTick).Returns(new GameTick(11));
mapGrid.SetupGet(f => f.ParentMapId).Returns(new MapId(11));
mapGrid.SetupGet(f => f.Index).Returns(new GridId(13));
return new MapChunk(mapGrid.Object, xChunkIndex, yChunkIndex, 8);
}
}
}
@@ -0,0 +1,133 @@
using System.Linq;
using Moq;
using NUnit.Framework;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using MapGrid = Robust.Shared.Map.MapGrid;
namespace Robust.UnitTesting.Shared.Map
{
[TestFixture, Parallelizable, TestOf(typeof(MapGrid))]
class MapGrid_Tests
{
[Test]
public void GetTileRefCoords()
{
var grid = MapGridFactory(new GridId(1));
grid.SetTile(new MapIndices(-9, -1), new Tile(1, 2));
var result = grid.GetTileRef(new MapIndices(-9, -1));
Assert.That(grid.ChunkCount, Is.EqualTo(1));
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new MapIndices(-2, -1)));
Assert.That(result, Is.EqualTo(new TileRef(new MapId(3), new GridId(1), new MapIndices(-9,-1), new Tile(1, 2))));
}
/// <summary>
/// Verifies that the world Bounds of the grid properly expand when tiles are placed.
/// </summary>
[Test]
public void BoundsExpansion()
{
var grid = MapGridFactory(new GridId(1));
grid.SetTile(new MapIndices(-1, -2), new Tile(1));
grid.SetTile(new MapIndices(1, 2), new Tile(1));
var bounds = grid.WorldBounds;
// this is world, so add the grid world pos
Assert.That(bounds.Bottom, Is.EqualTo(-2+5));
Assert.That(bounds.Left, Is.EqualTo(-1+3));
Assert.That(bounds.Top, Is.EqualTo(3+5));
Assert.That(bounds.Right, Is.EqualTo(2+3));
}
/// <summary>
/// Verifies that the world bounds of the grid properly contract when a tile is removed.
/// </summary>
[Test]
public void BoundsContract()
{
var grid = MapGridFactory(new GridId(1));
grid.SetTile(new MapIndices(-1, -2), new Tile(1));
grid.SetTile(new MapIndices(1, 2), new Tile(1));
grid.SetTile(new MapIndices(1, 2), Tile.Empty);
var bounds = grid.WorldBounds;
// this is world, so add the grid world pos
Assert.That(bounds.Bottom, Is.EqualTo(-2+5));
Assert.That(bounds.Left, Is.EqualTo(-1+3));
Assert.That(bounds.Top, Is.EqualTo(-1+5));
Assert.That(bounds.Right, Is.EqualTo(0+3));
}
[Test]
public void GridTileToChunkIndices()
{
var grid = MapGridFactory(new GridId(1));
var result = grid.GridTileToChunkIndices(new MapIndices(-9, -1));
Assert.That(result, Is.EqualTo(new MapIndices(-2, -1)));
}
/// <summary>
/// Verifies that the local position is centered on the tile, instead of bottom left.
/// </summary>
[Test]
public void ToLocalCentered()
{
var grid = MapGridFactory(new GridId(1));
var result = grid.GridTileToLocal(new MapIndices(0, 0)).Position;
Assert.That(result.X, Is.EqualTo(0.5f));
Assert.That(result.Y, Is.EqualTo(0.5f));
}
[Test]
public void TryGetTileRefNoTile()
{
var grid = MapGridFactory(new GridId(1));
var foundTile = grid.TryGetTileRef(new MapIndices(-9, -1), out var tileRef);
Assert.That(foundTile, Is.False);
Assert.That(tileRef, Is.EqualTo(new TileRef()));
Assert.That(grid.ChunkCount, Is.EqualTo(0));
}
[Test]
public void TryGetTileRefTileExists()
{
var grid = MapGridFactory(new GridId(1));
grid.SetTile(new MapIndices(-9, -1), new Tile(1, 2));
var foundTile = grid.TryGetTileRef(new MapIndices(-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 MapIndices(-2, -1)));
Assert.That(tileRef, Is.EqualTo(new TileRef(new MapId(3), new GridId(1), new MapIndices(-9, -1), new Tile(1, 2))));
}
private static IMapGridInternal MapGridFactory(GridId id)
{
var timing = new Mock<IGameTiming>();
var mapMan = new Mock<IMapManagerInternal>();
mapMan.SetupGet(p => p.GameTiming).Returns(timing.Object);
var newGrid = new MapGrid(mapMan.Object, id, 8, 1, new MapId(3))
{
WorldPosition = new Vector2(3, 5)
};
return newGrid;
}
}
}
@@ -0,0 +1,23 @@
using NUnit.Framework;
using Robust.Shared.Maths;
namespace Robust.UnitTesting.Shared.Maths
{
[TestFixture, Parallelizable, TestOf(typeof(Box2i))]
class Box2i_Test
{
[Test]
public void Box2iUnion()
{
var boxOne = new Box2i(-1, -1, 1, 1);
var boxTwo = new Box2i(0, 0, 2, 2);
var result = boxOne.Union(boxTwo);
Assert.That(result.Left, Is.EqualTo(-1));
Assert.That(result.Bottom, Is.EqualTo(-1));
Assert.That(result.Right, Is.EqualTo(2));
Assert.That(result.Top, Is.EqualTo(2));
}
}
}