diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj
index 053f561dca..f3bfb5ba67 100644
--- a/OpenRA.Game/OpenRA.Game.csproj
+++ b/OpenRA.Game/OpenRA.Game.csproj
@@ -143,6 +143,7 @@
+
diff --git a/OpenRA.Game/Primitives/SpatiallyPartitioned.cs b/OpenRA.Game/Primitives/SpatiallyPartitioned.cs
new file mode 100644
index 0000000000..728c711ec4
--- /dev/null
+++ b/OpenRA.Game/Primitives/SpatiallyPartitioned.cs
@@ -0,0 +1,108 @@
+#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;
+using System.Collections.Generic;
+using System.Drawing;
+
+namespace OpenRA.Primitives
+{
+ public sealed class SpatiallyPartitioned
+ {
+ readonly int rows, cols, binSize;
+ readonly Dictionary[] itemBoundsBins;
+ readonly Dictionary itemBounds = new Dictionary();
+ readonly Action, T, Rectangle> addItem = (bin, actor, bounds) => bin.Add(actor, bounds);
+ readonly Action, T, Rectangle> removeItem = (bin, actor, bounds) => bin.Remove(actor);
+
+ public SpatiallyPartitioned(int width, int height, int binSize)
+ {
+ this.binSize = binSize;
+ rows = height / binSize + 1;
+ cols = width / binSize + 1;
+ itemBoundsBins = Exts.MakeArray(rows * cols, _ => new Dictionary());
+ }
+
+ public void Add(T item, Rectangle bounds)
+ {
+ itemBounds.Add(item, bounds);
+ MutateBins(item, bounds, addItem);
+ }
+
+ public void Update(T item, Rectangle bounds)
+ {
+ MutateBins(item, itemBounds[item], removeItem);
+ MutateBins(item, itemBounds[item] = bounds, addItem);
+ }
+
+ public void Remove(T item)
+ {
+ MutateBins(item, itemBounds[item], removeItem);
+ itemBounds.Remove(item);
+ }
+
+ Dictionary BinAt(int row, int col)
+ {
+ return itemBoundsBins[row * cols + col];
+ }
+
+ Rectangle BinBounds(int row, int col)
+ {
+ return new Rectangle(col * binSize, row * binSize, binSize, binSize);
+ }
+
+ void MutateBins(T actor, Rectangle bounds, Action, 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);
+
+ for (var row = top; row <= bottom; row++)
+ for (var col = left; col <= right; col++)
+ action(BinAt(row, col), actor, bounds);
+ }
+
+ public IEnumerable At(int2 location)
+ {
+ var col = (location.X / binSize).Clamp(0, cols - 1);
+ var row = (location.Y / binSize).Clamp(0, rows - 1);
+ foreach (var kvp in BinAt(row, col))
+ if (kvp.Value.Contains(location))
+ yield return kvp.Key;
+ }
+
+ public IEnumerable 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);
+
+ var items = new HashSet();
+ for (var row = top; row <= bottom; row++)
+ for (var col = left; col <= right; col++)
+ {
+ var binBounds = BinBounds(row, col);
+ foreach (var kvp in BinAt(row, col))
+ {
+ var item = kvp.Key;
+ var bounds = kvp.Value;
+
+ // Return items that intersect the box. We also want to avoid returning the same item many times.
+ // If the item is contained wholly within this bin, we're good as we know it won't show up in any others.
+ // Otherwise it may appear in another bin. We use a set of seen items to avoid yielding it again.
+ if (bounds.IntersectsWith(box) && (binBounds.Contains(bounds) || items.Add(item)))
+ yield return item;
+ }
+ }
+ }
+ }
+}
diff --git a/OpenRA.Game/Traits/World/ScreenMap.cs b/OpenRA.Game/Traits/World/ScreenMap.cs
index 0a1cf9af7c..a4415e723e 100644
--- a/OpenRA.Game/Traits/World/ScreenMap.cs
+++ b/OpenRA.Game/Traits/World/ScreenMap.cs
@@ -27,99 +27,71 @@ namespace OpenRA.Traits
public class ScreenMap : IWorldLoaded
{
- ScreenMapInfo info;
+ static readonly IEnumerable NoFrozenActors = new FrozenActor[0];
+ readonly Func frozenActorIsValid = fa => fa.IsValid;
+ readonly Func actorIsInWorld = a => a.IsInWorld;
+ readonly Cache> partitionedFrozenActors;
+ readonly SpatiallyPartitioned partitionedActors;
WorldRenderer worldRenderer;
- Cache[]> frozen;
- Dictionary[] actors;
- int rows, cols;
public ScreenMap(World world, ScreenMapInfo info)
{
- this.info = info;
var ts = Game.ModData.Manifest.TileSize;
- cols = world.Map.MapSize.X * ts.Width / info.BinSize + 1;
- rows = world.Map.MapSize.Y * ts.Height / info.BinSize + 1;
-
- frozen = new Cache[]>(InitializeFrozenActors);
- actors = new Dictionary[rows * cols];
- for (var j = 0; j < rows; j++)
- for (var i = 0; i < cols; i++)
- actors[j * cols + i] = new Dictionary();
+ var width = world.Map.MapSize.X * ts.Width;
+ var height = world.Map.MapSize.Y * ts.Height;
+ partitionedFrozenActors = new Cache>(
+ _ => new SpatiallyPartitioned(width, height, info.BinSize));
+ partitionedActors = new SpatiallyPartitioned(width, height, info.BinSize);
}
public void WorldLoaded(World w, WorldRenderer wr) { worldRenderer = wr; }
- Dictionary[] InitializeFrozenActors(Player p)
- {
- var f = new Dictionary[rows * cols];
- for (var j = 0; j < rows; j++)
- for (var i = 0; i < cols; i++)
- f[j * cols + i] = new Dictionary();
-
- return f;
- }
-
- public void Add(Player viewer, FrozenActor fa)
+ Rectangle FrozenActorBounds(FrozenActor fa)
{
var pos = worldRenderer.ScreenPxPosition(fa.CenterPosition);
var bounds = fa.Bounds;
bounds.Offset(pos.X, pos.Y);
-
- var top = Math.Max(0, bounds.Top / info.BinSize);
- var left = Math.Max(0, bounds.Left / info.BinSize);
- var bottom = Math.Min(rows - 1, bounds.Bottom / info.BinSize);
- var right = Math.Min(cols - 1, bounds.Right / info.BinSize);
-
- for (var j = top; j <= bottom; j++)
- for (var i = left; i <= right; i++)
- frozen[viewer][j * cols + i].Add(fa, bounds);
+ return bounds;
}
- public void Remove(Player viewer, FrozenActor fa)
- {
- foreach (var bin in frozen[viewer])
- bin.Remove(fa);
- }
-
- public void Add(Actor a)
+ Rectangle ActorBounds(Actor a)
{
var pos = worldRenderer.ScreenPxPosition(a.CenterPosition);
var bounds = a.Bounds;
bounds.Offset(pos.X, pos.Y);
-
- var top = Math.Max(0, bounds.Top / info.BinSize);
- var left = Math.Max(0, bounds.Left / info.BinSize);
- var bottom = Math.Min(rows - 1, bounds.Bottom / info.BinSize);
- var right = Math.Min(cols - 1, bounds.Right / info.BinSize);
-
- for (var j = top; j <= bottom; j++)
- for (var i = left; i <= right; i++)
- actors[j * cols + i].Add(a, bounds);
+ return bounds;
}
- public void Remove(Actor a)
+ public void Add(Player viewer, FrozenActor fa)
{
- foreach (var bin in actors)
- bin.Remove(a);
+ partitionedFrozenActors[viewer].Add(fa, FrozenActorBounds(fa));
+ }
+
+ public void Remove(Player viewer, FrozenActor fa)
+ {
+ partitionedFrozenActors[viewer].Remove(fa);
+ }
+
+ public void Add(Actor a)
+ {
+ partitionedActors.Add(a, ActorBounds(a));
}
public void Update(Actor a)
{
- Remove(a);
- Add(a);
+ partitionedActors.Update(a, ActorBounds(a));
+ }
+
+ public void Remove(Actor a)
+ {
+ partitionedActors.Remove(a);
}
- public static readonly IEnumerable NoFrozenActors = new FrozenActor[0].AsEnumerable();
public IEnumerable FrozenActorsAt(Player viewer, int2 worldPx)
{
if (viewer == null)
return NoFrozenActors;
-
- var i = (worldPx.X / info.BinSize).Clamp(0, cols - 1);
- var j = (worldPx.Y / info.BinSize).Clamp(0, rows - 1);
- return frozen[viewer][j * cols + i]
- .Where(kv => kv.Key.IsValid && kv.Value.Contains(worldPx))
- .Select(kv => kv.Key);
+ return partitionedFrozenActors[viewer].At(worldPx).Where(frozenActorIsValid);
}
public IEnumerable FrozenActorsAt(Player viewer, MouseInput mi)
@@ -129,11 +101,7 @@ namespace OpenRA.Traits
public IEnumerable ActorsAt(int2 worldPx)
{
- var i = (worldPx.X / info.BinSize).Clamp(0, cols - 1);
- var j = (worldPx.Y / info.BinSize).Clamp(0, rows - 1);
- return actors[j * cols + i]
- .Where(kv => kv.Key.IsInWorld && kv.Value.Contains(worldPx))
- .Select(kv => kv.Key);
+ return partitionedActors.At(worldPx).Where(actorIsInWorld);
}
public IEnumerable ActorsAt(MouseInput mi)
@@ -141,51 +109,31 @@ namespace OpenRA.Traits
return ActorsAt(worldRenderer.Viewport.ViewToWorldPx(mi.Location));
}
+ static Rectangle RectWithCorners(int2 a, int2 b)
+ {
+ return Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y));
+ }
+
public IEnumerable ActorsInBox(int2 a, int2 b)
{
- return ActorsInBox(Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)));
+ return ActorsInBox(RectWithCorners(a, b));
}
public IEnumerable ActorsInBox(Rectangle r)
{
- var left = (r.Left / info.BinSize).Clamp(0, cols - 1);
- var right = (r.Right / info.BinSize).Clamp(0, cols - 1);
- var top = (r.Top / info.BinSize).Clamp(0, rows - 1);
- var bottom = (r.Bottom / info.BinSize).Clamp(0, rows - 1);
-
- var actorsChecked = new HashSet();
- for (var j = top; j <= bottom; j++)
- for (var i = left; i <= right; i++)
- foreach (var kvp in actors[j * cols + i])
- {
- var actor = kvp.Key;
- if (actor.IsInWorld && kvp.Value.IntersectsWith(r) && actorsChecked.Add(actor))
- yield return actor;
- }
+ return partitionedActors.InBox(r).Where(actorIsInWorld);
}
public IEnumerable FrozenActorsInBox(Player p, int2 a, int2 b)
{
- return FrozenActorsInBox(p, Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)));
+ return FrozenActorsInBox(p, RectWithCorners(a, b));
}
public IEnumerable FrozenActorsInBox(Player p, Rectangle r)
{
- var left = (r.Left / info.BinSize).Clamp(0, cols - 1);
- var right = (r.Right / info.BinSize).Clamp(0, cols - 1);
- var top = (r.Top / info.BinSize).Clamp(0, rows - 1);
- var bottom = (r.Bottom / info.BinSize).Clamp(0, rows - 1);
- var bins = frozen[p];
-
- var actorsChecked = new HashSet();
- for (var j = top; j <= bottom; j++)
- for (var i = left; i <= right; i++)
- foreach (var kvp in bins[j * cols + i])
- {
- var actor = kvp.Key;
- if (actor.IsValid && kvp.Value.IntersectsWith(r) && actorsChecked.Add(actor))
- yield return actor;
- }
+ if (p == null)
+ return NoFrozenActors;
+ return partitionedFrozenActors[p].InBox(r).Where(frozenActorIsValid);
}
}
}