Calculate better upper bounds in SpatiallyPartitioned.

SpatiallyPartitioned was calculating larger that necessary bounding regions as this was simple to do and does not impact correctness. By being careful to calculate the upper bounds exactly we can avoid checking more bins than we need to during updates and queries.
This commit is contained in:
RoosterDragon
2016-01-09 15:52:29 +00:00
parent 8ff08d4e19
commit a15d1801e1

View File

@@ -25,8 +25,8 @@ namespace OpenRA.Primitives
public SpatiallyPartitioned(int width, int height, int binSize) public SpatiallyPartitioned(int width, int height, int binSize)
{ {
this.binSize = binSize; this.binSize = binSize;
rows = height / binSize + 1; rows = Exts.IntegerDivisionRoundingAwayFromZero(height, binSize);
cols = width / binSize + 1; cols = Exts.IntegerDivisionRoundingAwayFromZero(width, binSize);
itemBoundsBins = Exts.MakeArray(rows * cols, _ => new Dictionary<T, Rectangle>()); itemBoundsBins = Exts.MakeArray(rows * cols, _ => new Dictionary<T, Rectangle>());
} }
@@ -58,15 +58,21 @@ namespace OpenRA.Primitives
return new Rectangle(col * binSize, row * binSize, binSize, binSize); return new Rectangle(col * binSize, row * binSize, binSize, binSize);
} }
void BoundsToBinRowsAndCols(Rectangle bounds, out int minRow, out int maxRow, out int minCol, out int maxCol)
{
minRow = Math.Max(0, bounds.Top / binSize);
minCol = Math.Max(0, bounds.Left / binSize);
maxRow = Math.Min(rows, Exts.IntegerDivisionRoundingAwayFromZero(bounds.Bottom, binSize));
maxCol = Math.Min(cols, Exts.IntegerDivisionRoundingAwayFromZero(bounds.Right, binSize));
}
void MutateBins(T actor, Rectangle bounds, Action<Dictionary<T, Rectangle>, T, Rectangle> action) void MutateBins(T actor, Rectangle bounds, Action<Dictionary<T, Rectangle>, T, Rectangle> action)
{ {
var top = Math.Max(0, bounds.Top / binSize); int minRow, maxRow, minCol, maxCol;
var left = Math.Max(0, bounds.Left / binSize); BoundsToBinRowsAndCols(bounds, out minRow, out maxRow, out minCol, out maxCol);
var bottom = Math.Min(rows - 1, bounds.Bottom / binSize);
var right = Math.Min(cols - 1, bounds.Right / binSize);
for (var row = top; row <= bottom; row++) for (var row = minRow; row < maxRow; row++)
for (var col = left; col <= right; col++) for (var col = minCol; col < maxCol; col++)
action(BinAt(row, col), actor, bounds); action(BinAt(row, col), actor, bounds);
} }
@@ -81,18 +87,16 @@ namespace OpenRA.Primitives
public IEnumerable<T> InBox(Rectangle box) public IEnumerable<T> InBox(Rectangle box)
{ {
var left = (box.Left / binSize).Clamp(0, cols - 1); int minRow, maxRow, minCol, maxCol;
var right = (box.Right / binSize).Clamp(0, cols - 1); BoundsToBinRowsAndCols(box, out minRow, out maxRow, out minCol, out maxCol);
var top = (box.Top / binSize).Clamp(0, rows - 1);
var bottom = (box.Bottom / binSize).Clamp(0, rows - 1);
// We want to return any items intersecting the box. // We want to return any items intersecting the box.
// If the box covers multiple bins, we must handle items that are contained in multiple bins and avoid // If the box covers multiple bins, we must handle items that are contained in multiple bins and avoid
// returning them more than once. We shall use a set to track these. // returning them more than once. We shall use a set to track these.
// PERF: If we are only looking inside one bin, we can avoid the cost of performing this tracking. // PERF: If we are only looking inside one bin, we can avoid the cost of performing this tracking.
var items = top == bottom && left == right ? null : new HashSet<T>(); var items = minRow >= maxRow || minCol >= maxCol ? null : new HashSet<T>();
for (var row = top; row <= bottom; row++) for (var row = minRow; row < maxRow; row++)
for (var col = left; col <= right; col++) for (var col = minCol; col < maxCol; col++)
{ {
var binBounds = BinBounds(row, col); var binBounds = BinBounds(row, col);
foreach (var kvp in BinAt(row, col)) foreach (var kvp in BinAt(row, col))