Grid chunks now use the GridChunkPartition method for generating a set of collision rectangles and their bounds.

Grid collision rectangles are now drawn as a blue overlay in the CollisionOverlay debug view.
GridChunkPartition now also returns the bounding rectangle, which is a union of all the collision rectangles.
Each IPhysShape now handles it's own debug drawing with a new method, DebugDraw.
This commit is contained in:
Acruid
2020-01-13 13:40:09 -08:00
parent 6817969c8b
commit 32db0f82f1
10 changed files with 114 additions and 133 deletions

View File

@@ -95,6 +95,7 @@ namespace Robust.Client.Debugging
protected override void Draw(DrawingHandleBase handle)
{
var worldHandle = (DrawingHandleWorld) handle;
var drawing = new PhysDrawingAdapter(worldHandle);
var viewport = _eyeManager.GetWorldViewport();
foreach (var boundingBox in _componentManager.GetAllComponents<ICollidableComponent>())
@@ -107,7 +108,6 @@ namespace Robust.Client.Debugging
continue;
var worldBox = boundingBox.WorldAABB;
var colorFill = Color.Green.WithAlpha(0.25f);
var colorEdge = Color.Red.WithAlpha(0.33f);
// if not on screen, or too small, continue
@@ -116,24 +116,36 @@ namespace Robust.Client.Debugging
foreach (var shape in boundingBox.PhysicsShapes)
{
// TODO: Add a debug drawing function to IPhysShape
if (shape is PhysShapeAabb aabb)
{
var shapeWorldBox = aabb.LocalBounds.Translated(transform.WorldPosition);
worldHandle.DrawRect(shapeWorldBox, colorFill);
}
else if (shape is PhysShapeRect rect)
{
worldHandle.SetTransform(transform.WorldMatrix);
worldHandle.DrawRect(rect.Rectangle, colorFill);
worldHandle.SetTransform(Matrix3.Identity);
}
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport);
}
// draw AABB
worldHandle.DrawRect(worldBox, colorEdge, false);
}
}
private class PhysDrawingAdapter : DebugDrawingHandle
{
private readonly DrawingHandleWorld _handle;
public PhysDrawingAdapter(DrawingHandleWorld worldHandle)
{
_handle = worldHandle;
}
public override Color GridFillColor => Color.Blue.WithAlpha(0.05f);
public override Color RectFillColor => Color.Green.WithAlpha(0.25f);
public override void DrawRect(in Box2 box, in Color color)
{
_handle.DrawRect(box, color);
}
public override void SetTransform(in Matrix3 transform)
{
_handle.SetTransform(transform);
}
}
}
private sealed class EntityPositionOverlay : Overlay

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Robust.Shared.Maths;
namespace Robust.Shared.Map
@@ -8,8 +9,10 @@ namespace Robust.Shared.Map
/// </summary>
internal static class GridChunkPartition
{
public static void PartitionChunk(IMapChunk chunk, ref IList<Box2> rectangles)
public static void PartitionChunk(IMapChunk chunk, ref IList<Box2> rectangles, out Box2i bounds)
{
rectangles.Clear();
var size = chunk.ChunkSize;
// copy 2d img
@@ -21,6 +24,8 @@ namespace Robust.Shared.Map
Partition(size, size, image, out var blocks, out var blockCount);
bounds = new Box2i();
// convert blocks to rectangles array.
for(int i=0;i< blockCount; i++)
{
@@ -33,9 +38,12 @@ namespace Robust.Shared.Map
var bottom = block.x1;
var top = block.x2 + 1;
var box = new Box2(left, bottom, right, top);
rectangles.Add(new Box2(left, bottom, right, top));
rectangles.Add(box);
if(bounds.Size.Equals(Vector2i.Zero))
bounds = new Box2i(left, bottom, right, top);
else
bounds = bounds.Union(new Box2i(left, bottom, right, top));
}
}

View File

@@ -1,4 +1,6 @@
using Robust.Shared.Timing;
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
@@ -6,8 +8,13 @@ namespace Robust.Shared.Map
internal interface IMapChunkInternal : IMapChunk
{
/// <summary>
/// The last game simulation tick that this chunk was modified.
/// The last game simulation tick that this chunk was modified.
/// </summary>
GameTick LastModifiedTick { get; }
/// <summary>
/// The physical collision boxes of this chunk.
/// </summary>
IEnumerable<Box2> CollisionBoxes { get; }
}
}

View File

@@ -17,10 +17,13 @@ namespace Robust.Shared.Map
private readonly SnapGridCell[,] _snapGrid;
private Box2i _cachedBounds;
private IList<Box2> _colBoxes;
/// <inheritdoc />
public GameTick LastModifiedTick { get; private set; }
public IEnumerable<Box2> CollisionBoxes => _colBoxes;
/// <summary>
/// Constructs an instance of a MapGrid chunk.
/// </summary>
@@ -37,6 +40,7 @@ namespace Robust.Shared.Map
_tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
_colBoxes = new List<Box2>(0);
}
/// <inheritdoc />
@@ -121,7 +125,7 @@ namespace Robust.Shared.Map
LastModifiedTick = _grid.CurTick;
_tiles[xIndex, yIndex] = tile;
CheckBounds(new Vector2i(xIndex, yIndex), tile.IsEmpty);
RegenerateCollision();
_grid.NotifyTileChanged(newTileRef, oldTile);
}
@@ -233,117 +237,10 @@ namespace Robust.Shared.Map
}
}
private void CheckBounds(in Vector2i indices, bool empty)
private void RegenerateCollision()
{
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;
// generate collision rects
GridChunkPartition.PartitionChunk(this, ref _colBoxes, out _cachedBounds);
}
/// <inheritdoc />

View File

@@ -48,6 +48,22 @@ namespace Robust.Shared.Map
_mapGrid = (IMapGridInternal)mapMan.GetGrid(_gridId);
}
public void DebugDraw(DebugDrawingHandle handle, in Matrix3 modelMatrix, in Box2 worldViewport)
{
handle.SetTransform(modelMatrix);
foreach (var chunk in _mapGrid.GetMapChunks().Values)
{
foreach (var box in chunk.CollisionBoxes)
{
var localChunkPos = new Vector2(chunk.Indices.X, chunk.Indices.Y) * _mapGrid.ChunkSize;
var localBox = box.Translated(localChunkPos);
handle.DrawRect(localBox, handle.GridFillColor);
}
}
handle.SetTransform(Matrix3.Identity);
}
/// <summary>
/// Constructs a new instance of <see cref="PhysShapeGrid"/>.
/// </summary>

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using Robust.Shared.Maths;
namespace Robust.Shared.Physics
{
public abstract class DebugDrawingHandle
{
public abstract Color GridFillColor { get; }
public abstract Color RectFillColor { get; }
public abstract void DrawRect(in Box2 box, in Color color);
public abstract void SetTransform(in Matrix3 transform);
}
}

View File

@@ -26,5 +26,7 @@ namespace Robust.Shared.Physics
int CollisionMask { get; set; }
void ApplyState();
void DebugDraw(DebugDrawingHandle handle, in Matrix3 modelMatrix, in Box2 worldViewport);
}
}

View File

@@ -46,6 +46,17 @@ namespace Robust.Shared.Physics
/// <inheritdoc />
public void ApplyState() { }
public void DebugDraw(DebugDrawingHandle handle, in Matrix3 modelMatrix, in Box2 worldViewport)
{
var m = Matrix3.Identity;
m.R0C2 = modelMatrix.R0C2;
m.R1C2 = modelMatrix.R1C2;
handle.SetTransform(m);
handle.DrawRect(LocalBounds, handle.RectFillColor);
handle.SetTransform(Matrix3.Identity);
}
/// <inheritdoc />
public Box2 CalculateLocalBounds(Angle rotation)
{

View File

@@ -37,6 +37,13 @@ namespace Robust.Shared.Physics
/// <inheritdoc />
public void ApplyState() { }
public void DebugDraw(DebugDrawingHandle handle, in Matrix3 modelMatrix, in Box2 worldViewport)
{
handle.SetTransform(modelMatrix);
handle.DrawRect(Rectangle, handle.RectFillColor);
handle.SetTransform(Matrix3.Identity);
}
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _collisionLayer, "layer", 0);

View File

@@ -25,7 +25,7 @@ namespace Robust.UnitTesting.Shared.Map
IList<Box2> rects = new List<Box2>(8);
// Act
GridChunkPartition.PartitionChunk(chunk, ref rects);
GridChunkPartition.PartitionChunk(chunk, ref rects, out var bounds);
// Assert
Assert.That(rects.Count, Is.EqualTo(5));
@@ -43,6 +43,8 @@ namespace Robust.UnitTesting.Shared.Map
Assert.That(rects[2], Is.EqualTo(new Box2(1, 0, 2, 4)));
Assert.That(rects[3], Is.EqualTo(new Box2(2, 1, 3, 3)));
Assert.That(rects[4], Is.EqualTo(new Box2(3, 2, 4, 3)));
Assert.That(bounds, Is.EqualTo(new Box2i(0,0,4,4)));
}
// origin is top left
@@ -61,13 +63,15 @@ namespace Robust.UnitTesting.Shared.Map
IList<Box2> rects = new List<Box2>(16);
// Act
GridChunkPartition.PartitionChunk(chunk, ref rects);
GridChunkPartition.PartitionChunk(chunk, ref rects, out var bounds);
// Assert
Assert.That(rects.Count, Is.EqualTo(1));
// box origin is bottom left
Assert.That(rects[0], Is.EqualTo(new Box2(1,0,3,3)));
Assert.That(bounds, Is.EqualTo(new Box2i(1, 0, 3, 3)));
}
private static IMapChunkInternal ChunkFactory(ushort size, int[] tiles)