context: #region Copyright & License Information
  /*
   * Copyright 2007-2020 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, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.Effects;
  using OpenRA.Graphics;
  using OpenRA.Primitives;
  
  namespace OpenRA.Traits
  {
  	public struct ActorBoundsPair
  	{
  		public readonly Actor Actor;
  		public readonly Polygon Bounds;
  
  		public ActorBoundsPair(Actor actor, Polygon bounds) { Actor = actor; Bounds = bounds; }
  
  		public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
  
  		public override string ToString() { return "{0}->{1}".F(Actor.Info.Name, Bounds.GetType().Name); }
  	}
  
  	public class ScreenMapInfo : TraitInfo
  	{
  		[Desc("Size of partition bins (world pixels)")]
  		public readonly int BinSize = 250;
  
  		public override object Create(ActorInitializer init) { return new ScreenMap(init.World, this); }
  	}
  
  	public class ScreenMap : IWorldLoaded
  	{
  		static readonly IEnumerable<FrozenActor> NoFrozenActors = new FrozenActor[0];
  		readonly Func<FrozenActor, bool> frozenActorIsValid = fa => fa.IsValid;
  		readonly Func<Actor, bool> actorIsInWorld = a => a.IsInWorld;
  		readonly Func<Actor, ActorBoundsPair> selectActorAndBounds;
  		readonly Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedMouseFrozenActors;
  		readonly SpatiallyPartitioned<Actor> partitionedMouseActors;
  		readonly Dictionary<Actor, ActorBoundsPair> partitionedMouseActorBounds = new Dictionary<Actor, ActorBoundsPair>();
  
  		readonly Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedRenderableFrozenActors;
  		readonly SpatiallyPartitioned<Actor> partitionedRenderableActors;
  		readonly SpatiallyPartitioned<IEffect> partitionedRenderableEffects;
  
  		// Updates are done in one pass to ensure all bound changes have been applied
  		readonly HashSet<Actor> addOrUpdateActors = new HashSet<Actor>();
  		readonly HashSet<Actor> removeActors = new HashSet<Actor>();
  		readonly Cache<Player, HashSet<FrozenActor>> addOrUpdateFrozenActors;
  		readonly Cache<Player, HashSet<FrozenActor>> removeFrozenActors;
  
  		WorldRenderer worldRenderer;
  
  		public ScreenMap(World world, ScreenMapInfo info)
  		{
  			var size = world.Map.Grid.TileSize;
  			var width = world.Map.MapSize.X * size.Width;
  			var height = world.Map.MapSize.Y * size.Height;
  
  			partitionedMouseFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
  				_ => new SpatiallyPartitioned<FrozenActor>(width, height, info.BinSize));
  			partitionedMouseActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
  			selectActorAndBounds = a => partitionedMouseActorBounds[a];
  
  			partitionedRenderableFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
  				_ => new SpatiallyPartitioned<FrozenActor>(width, height, info.BinSize));
  			partitionedRenderableActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
  			partitionedRenderableEffects = new SpatiallyPartitioned<IEffect>(width, height, info.BinSize);
  
  			addOrUpdateFrozenActors = new Cache<Player, HashSet<FrozenActor>>(_ => new HashSet<FrozenActor>());
  			removeFrozenActors = new Cache<Player, HashSet<FrozenActor>>(_ => new HashSet<FrozenActor>());
  		}
  
  		public void WorldLoaded(World w, WorldRenderer wr) { worldRenderer = wr; }
  
  		public void AddOrUpdate(Player viewer, FrozenActor fa)
  		{
  			if (removeFrozenActors[viewer].Contains(fa))
  				removeFrozenActors[viewer].Remove(fa);
  
  			addOrUpdateFrozenActors[viewer].Add(fa);
  		}
  
  		public void Remove(Player viewer, FrozenActor fa)
  		{
  			removeFrozenActors[viewer].Add(fa);
  		}
  
  		public void AddOrUpdate(Actor a)
  		{
  			if (removeActors.Contains(a))
  				removeActors.Remove(a);
  
  			addOrUpdateActors.Add(a);
  		}
  
  		public void Remove(Actor a)
  		{
  			removeActors.Add(a);
  		}
  
  		public void Add(IEffect effect, WPos position, Size size)
  		{
  			var screenPos = worldRenderer.ScreenPxPosition(position);
  			var screenWidth = Math.Abs(size.Width);
  			var screenHeight = Math.Abs(size.Height);
  			var screenBounds = new Rectangle(screenPos.X - screenWidth / 2, screenPos.Y - screenHeight / 2, screenWidth, screenHeight);
  			if (ValidBounds(screenBounds))
  				partitionedRenderableEffects.Add(effect, screenBounds);
  		}
  
  		public void Add(IEffect effect, WPos position, Sprite sprite)
  		{
  			var size = new Size((int)sprite.Size.X, (int)sprite.Size.Y);
  			Add(effect, position, size);
  		}
  
  		public void Update(IEffect effect, WPos position, Size size)
  		{
  			Remove(effect);
  			Add(effect, position, size);
  		}
  
  		public void Update(IEffect effect, WPos position, Sprite sprite)
  		{
  			var size = new Size((int)sprite.Size.X, (int)sprite.Size.Y);
  			Update(effect, position, size);
  		}
  
  		public void Remove(IEffect effect)
  		{
  			partitionedRenderableEffects.Remove(effect);
  		}
  
  		static bool ValidBounds(Rectangle bounds)
  		{
  			return bounds.Width > 0 && bounds.Height > 0;
  		}
  
  		public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, int2 worldPx)
  		{
  			if (viewer == null)
  				return NoFrozenActors;
  
  			return partitionedMouseFrozenActors[viewer]
  				.At(worldPx)
  				.Where(frozenActorIsValid)
  				.Where(x => x.MouseBounds.Contains(worldPx));
  		}
  
  		public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, MouseInput mi)
  		{
  			return FrozenActorsAtMouse(viewer, worldRenderer.Viewport.ViewToWorldPx(mi.Location));
  		}
  
  		public IEnumerable<ActorBoundsPair> ActorsAtMouse(int2 worldPx)
  		{
  			return partitionedMouseActors.At(worldPx)
  				.Where(actorIsInWorld)
  				.Select(selectActorAndBounds)
  				.Where(x => x.Bounds.Contains(worldPx));
  		}
  
  		public IEnumerable<ActorBoundsPair> ActorsAtMouse(MouseInput mi)
  		{
  			return ActorsAtMouse(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<ActorBoundsPair> ActorsInMouseBox(int2 a, int2 b)
  		{
  			return ActorsInMouseBox(RectWithCorners(a, b));
  		}
  
  		public IEnumerable<ActorBoundsPair> ActorsInMouseBox(Rectangle r)
  		{
  			return partitionedMouseActors.InBox(r)
  				.Where(actorIsInWorld)
  				.Select(selectActorAndBounds)
  				.Where(x => x.Bounds.IntersectsWith(r));
  		}
  
  		public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
  		{
  			return partitionedRenderableActors.InBox(RectWithCorners(a, b)).Where(actorIsInWorld);
  		}
  
  		public IEnumerable<IEffect> RenderableEffectsInBox(int2 a, int2 b)
  		{
  			return partitionedRenderableEffects.InBox(RectWithCorners(a, b));
  		}
  
  		public IEnumerable<FrozenActor> RenderableFrozenActorsInBox(Player p, int2 a, int2 b)
  		{
  			if (p == null)
  				return NoFrozenActors;
  
  			return partitionedRenderableFrozenActors[p].InBox(RectWithCorners(a, b)).Where(frozenActorIsValid);
  		}
  
  		public void TickRender()
  		{
  			foreach (var a in addOrUpdateActors)
  			{
  				var mouseBounds = a.MouseBounds(worldRenderer);
  				if (!mouseBounds.IsEmpty)
  				{
  					if (partitionedMouseActors.Contains(a))
  						partitionedMouseActors.Update(a, mouseBounds.BoundingRect);
  					else
  						partitionedMouseActors.Add(a, mouseBounds.BoundingRect);
  
  					partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
  				}
  				else
  					partitionedMouseActors.Remove(a);
  
  
gt: 				var screenBounds = a.ScreenBounds(worldRenderer).Union();