Merge pull request #10461 from RoosterDragon/partition-bounds
Calculate better upper bounds in SpatiallyPartitioned.
This commit is contained in:
@@ -327,6 +327,15 @@ namespace OpenRA
|
||||
return root;
|
||||
}
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
int remainder;
|
||||
var quotient = Math.DivRem(dividend, divisor, out remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
}
|
||||
|
||||
public static string JoinWith<T>(this IEnumerable<T> ts, string j)
|
||||
{
|
||||
return string.Join(j, ts);
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace OpenRA.Primitives
|
||||
public SpatiallyPartitioned(int width, int height, int binSize)
|
||||
{
|
||||
this.binSize = binSize;
|
||||
rows = height / binSize + 1;
|
||||
cols = width / binSize + 1;
|
||||
rows = Exts.IntegerDivisionRoundingAwayFromZero(height, binSize);
|
||||
cols = Exts.IntegerDivisionRoundingAwayFromZero(width, binSize);
|
||||
itemBoundsBins = Exts.MakeArray(rows * cols, _ => new Dictionary<T, Rectangle>());
|
||||
}
|
||||
|
||||
@@ -66,15 +66,21 @@ namespace OpenRA.Primitives
|
||||
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)
|
||||
{
|
||||
var top = Math.Max(0, bounds.Top / binSize);
|
||||
var left = Math.Max(0, bounds.Left / binSize);
|
||||
var bottom = Math.Min(rows - 1, bounds.Bottom / binSize);
|
||||
var right = Math.Min(cols - 1, bounds.Right / binSize);
|
||||
int minRow, maxRow, minCol, maxCol;
|
||||
BoundsToBinRowsAndCols(bounds, out minRow, out maxRow, out minCol, out maxCol);
|
||||
|
||||
for (var row = top; row <= bottom; row++)
|
||||
for (var col = left; col <= right; col++)
|
||||
for (var row = minRow; row < maxRow; row++)
|
||||
for (var col = minCol; col < maxCol; col++)
|
||||
action(BinAt(row, col), actor, bounds);
|
||||
}
|
||||
|
||||
@@ -89,18 +95,16 @@ namespace OpenRA.Primitives
|
||||
|
||||
public IEnumerable<T> InBox(Rectangle box)
|
||||
{
|
||||
var left = (box.Left / binSize).Clamp(0, cols - 1);
|
||||
var right = (box.Right / binSize).Clamp(0, cols - 1);
|
||||
var top = (box.Top / binSize).Clamp(0, rows - 1);
|
||||
var bottom = (box.Bottom / binSize).Clamp(0, rows - 1);
|
||||
int minRow, maxRow, minCol, maxCol;
|
||||
BoundsToBinRowsAndCols(box, out minRow, out maxRow, out minCol, out maxCol);
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
var items = top == bottom && left == right ? null : new HashSet<T>();
|
||||
for (var row = top; row <= bottom; row++)
|
||||
for (var col = left; col <= right; col++)
|
||||
var items = minRow >= maxRow || minCol >= maxCol ? null : new HashSet<T>();
|
||||
for (var row = minRow; row < maxRow; row++)
|
||||
for (var col = minCol; col < maxCol; col++)
|
||||
{
|
||||
var binBounds = BinBounds(row, col);
|
||||
foreach (var kvp in BinAt(row, col))
|
||||
|
||||
92
OpenRA.Test/OpenRA.Game/SpatiallyPartitionedTest.cs
Normal file
92
OpenRA.Test/OpenRA.Game/SpatiallyPartitionedTest.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using NUnit.Framework;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Test.OpenRA.Game
|
||||
{
|
||||
[TestFixture]
|
||||
class SpatiallyPartitionedTest
|
||||
{
|
||||
[TestCase(TestName = "SpatiallyPartitioned.At works")]
|
||||
public void SpatiallyPartitionedAtTest()
|
||||
{
|
||||
var partition = new SpatiallyPartitioned<object>(5, 5, 2);
|
||||
|
||||
var a = new object();
|
||||
partition.Add(a, new Rectangle(0, 0, 1, 1));
|
||||
CollectionAssert.Contains(partition.At(new int2(0, 0)), a, "a is not present after add");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(0, 1)), a, "a is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(1, 0)), a, "a is present in the wrong location");
|
||||
|
||||
var b = new object();
|
||||
partition.Add(b, new Rectangle(1, 1, 2, 2));
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(0, 1)), b, "b is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(1, 0)), b, "b is present in the wrong location");
|
||||
CollectionAssert.Contains(partition.At(new int2(1, 1)), b, "b is not present after add");
|
||||
CollectionAssert.Contains(partition.At(new int2(2, 2)), b, "b is not present after add");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(2, 3)), b, "b is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(3, 2)), b, "b is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(3, 3)), b, "b is present in the wrong location");
|
||||
|
||||
partition.Update(b, new Rectangle(4, 4, 1, 1));
|
||||
CollectionAssert.Contains(partition.At(new int2(0, 0)), a, "a wrongly changed location when b was updated");
|
||||
CollectionAssert.Contains(partition.At(new int2(4, 4)), b, "b is not present at the new location in the extreme corner of the partition");
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(1, 1)), b, "b is still present at the old location after update");
|
||||
|
||||
partition.Remove(a);
|
||||
CollectionAssert.DoesNotContain(partition.At(new int2(0, 0)), a, "a is still present after removal");
|
||||
CollectionAssert.Contains(partition.At(new int2(4, 4)), b, "b wrongly changed location when a was removed");
|
||||
}
|
||||
|
||||
[TestCase(TestName = "SpatiallyPartitioned.InBox works")]
|
||||
public void SpatiallyPartitionedInBoxTest()
|
||||
{
|
||||
var partition = new SpatiallyPartitioned<object>(5, 5, 2);
|
||||
|
||||
var a = new object();
|
||||
partition.Add(a, new Rectangle(0, 0, 1, 1));
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(0, 0, 0, 0)), a, "Searching an empty area should not return a");
|
||||
CollectionAssert.Contains(partition.InBox(new Rectangle(0, 0, 1, 1)), a, "a is not present after add");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(0, 1, 1, 1)), a, "a is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(1, 0, 1, 1)), a, "a is present in the wrong location");
|
||||
|
||||
var b = new object();
|
||||
partition.Add(b, new Rectangle(1, 1, 2, 2));
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(0, 1, 1, 1)), b, "b is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(1, 0, 1, 1)), b, "b is present in the wrong location");
|
||||
CollectionAssert.Contains(partition.InBox(new Rectangle(1, 1, 1, 1)), b, "b is not present after add");
|
||||
CollectionAssert.Contains(partition.InBox(new Rectangle(2, 2, 1, 1)), b, "b is not present after add");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(2, 3, 1, 1)), b, "b is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(3, 2, 1, 1)), b, "b is present in the wrong location");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(3, 3, 1, 1)), b, "b is present in the wrong location");
|
||||
|
||||
CollectionAssert.AreEquivalent(new[] { b }, partition.InBox(new Rectangle(1, 1, 1, 1)),
|
||||
"Searching within a single partition bin did not return the correct result");
|
||||
CollectionAssert.AllItemsAreUnique(partition.InBox(new Rectangle(0, 0, 5, 5)),
|
||||
"Searching the whole partition returned duplicates of some items");
|
||||
CollectionAssert.AreEquivalent(new[] { a, b }, partition.InBox(new Rectangle(0, 0, 5, 5)),
|
||||
"Searching the whole partition did not return all items");
|
||||
CollectionAssert.AreEquivalent(new[] { a, b }, partition.InBox(new Rectangle(-10, -10, 25, 25)),
|
||||
"Searching an area larger than the partition did not return all items");
|
||||
|
||||
partition.Update(b, new Rectangle(4, 4, 1, 1));
|
||||
CollectionAssert.Contains(partition.InBox(new Rectangle(0, 0, 1, 1)), a, "a wrongly changed location when b was updated");
|
||||
CollectionAssert.Contains(partition.InBox(new Rectangle(4, 4, 1, 1)), b, "b is not present at the new location in the extreme corner of the partition");
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(1, 1, 1, 1)), b, "b is still present at the old location after update");
|
||||
|
||||
partition.Remove(a);
|
||||
CollectionAssert.DoesNotContain(partition.InBox(new Rectangle(0, 0, 1, 1)), a, "a is still present after removal");
|
||||
CollectionAssert.Contains(partition.InBox(new Rectangle(4, 4, 1, 1)), b, "b wrongly changed location when a was removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,14 @@
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="OpenRA.Game\ActionQueueTest.cs" />
|
||||
<Compile Include="OpenRA.Game\MiniYamlTest.cs" />
|
||||
<Compile Include="OpenRA.Game\ActorInfoTest.cs" />
|
||||
<Compile Include="OpenRA.Game\CoordinateTest.cs" />
|
||||
<Compile Include="OpenRA.Game\SpatiallyPartitionedTest.cs" />
|
||||
<Compile Include="OpenRA.Mods.Common\ShapeTest.cs" />
|
||||
<Compile Include="OpenRA.Game\OrderTest.cs" />
|
||||
<Compile Include="OpenRA.Game\PlatformTest.cs" />
|
||||
|
||||
Reference in New Issue
Block a user