#region Copyright & License Information /* * Copyright 2007-2014 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; using System.Linq; using OpenRA.Graphics; using OpenRA.Primitives; namespace OpenRA.Traits { public class ScreenMapInfo : ITraitInfo { [Desc("Size of partition bins (world pixels)")] public readonly int BinSize = 250; public object Create(ActorInitializer init) { return new ScreenMap(init.world, this); } } public class ScreenMap : IWorldLoaded { ScreenMapInfo info; 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(); } 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) { 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); } public void Remove(Player viewer, FrozenActor fa) { foreach (var bin in frozen[viewer]) bin.Remove(fa); } public void Add(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); } public void Remove(Actor a) { foreach (var bin in actors) bin.Remove(a); } public void Update(Actor a) { Remove(a); Add(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); } public IEnumerable FrozenActorsAt(Player viewer, MouseInput mi) { return FrozenActorsAt(viewer, worldRenderer.Viewport.ViewToWorldPx(mi.Location)); } 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); } public IEnumerable ActorsAt(MouseInput mi) { return ActorsAt(worldRenderer.Viewport.ViewToWorldPx(mi.Location)); } 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))); } 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; } } 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))); } 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; } } } }