Introduce IMouseBounds and split/rework mouse rectangles.

The render bounds for an actor now include the area covered
by bibs, shadows, and any other widgets. In many cases this
area is much larger than we really want to consider for
tooltips and mouse selection.

An optional Margin is added to Selectable to support cases
like infantry, where we want the mouse area of the actor
to be larger than the drawn selection box.
This commit is contained in:
Paul Chote
2017-12-07 23:15:07 +00:00
committed by reaperrr
parent 8fcc80b05a
commit 6f5d035e79
25 changed files with 349 additions and 117 deletions

View File

@@ -77,6 +77,7 @@ namespace OpenRA
readonly IHealth health;
readonly IRenderModifier[] renderModifiers;
readonly IRender[] renders;
readonly IMouseBounds[] mouseBounds;
readonly IDisable[] disables;
readonly IVisibilityModifier[] visibilityModifiers;
readonly IDefaultVisibility defaultVisibility;
@@ -125,6 +126,7 @@ namespace OpenRA
health = TraitOrDefault<IHealth>();
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
renders = TraitsImplementing<IRender>().ToArray();
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
disables = TraitsImplementing<IDisable>().ToArray();
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
defaultVisibility = Trait<IDefaultVisibility>();
@@ -221,6 +223,18 @@ namespace OpenRA
yield return r;
}
public Rectangle MouseBounds(WorldRenderer wr)
{
foreach (var mb in mouseBounds)
{
var bounds = mb.MouseoverBounds(this, wr);
if (!bounds.IsEmpty)
return bounds;
}
return Rectangle.Empty;
}
public void QueueActivity(bool queued, Activity nextActivity)
{
if (!queued)

View File

@@ -102,7 +102,7 @@ namespace OpenRA.Graphics
List<IFinalizedRenderable> GenerateRenderables()
{
var actors = World.ScreenMap.ActorsInBox(Viewport.TopLeft, Viewport.BottomRight).Append(World.WorldActor);
var actors = World.ScreenMap.RenderableActorsInBox(Viewport.TopLeft, Viewport.BottomRight).Append(World.WorldActor);
if (World.RenderPlayer != null)
actors = actors.Append(World.RenderPlayer.PlayerActor);
@@ -114,7 +114,7 @@ namespace OpenRA.Graphics
worldRenderables = worldRenderables.Concat(World.UnpartitionedEffects.SelectMany(e => e.Render(this)));
// Partitioned, currently on-screen effects
var effectRenderables = World.ScreenMap.EffectsInBox(Viewport.TopLeft, Viewport.BottomRight);
var effectRenderables = World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight);
worldRenderables = worldRenderables.Concat(effectRenderables.SelectMany(e => e.Render(this)));
worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
@@ -213,12 +213,20 @@ namespace OpenRA.Graphics
}
if (debugVis.Value != null && debugVis.Value.ScreenMap)
foreach (var r in World.ScreenMap.ItemBounds(World.RenderPlayer))
{
foreach (var r in World.ScreenMap.RenderBounds(World.RenderPlayer))
Game.Renderer.WorldRgbaColorRenderer.DrawRect(
new float3(r.Left, r.Top, r.Bottom),
new float3(r.Right, r.Bottom, r.Bottom),
1 / Viewport.Zoom, Color.MediumSpringGreen);
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
Game.Renderer.WorldRgbaColorRenderer.DrawRect(
new float3(r.Left, r.Top, r.Bottom),
new float3(r.Right, r.Bottom, r.Bottom),
1 / Viewport.Zoom, Color.OrangeRed);
}
Game.Renderer.Flush();
}

View File

@@ -10,6 +10,7 @@
#endregion
using System.Collections.Generic;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Orders
@@ -62,7 +63,7 @@ namespace OpenRA.Orders
return world.Map.Contains(cell) ? Cursor : "generic-blocked";
}
public override bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
public override bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
{
// Custom order generators always override selection
return true;

View File

@@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Orders
@@ -20,14 +21,14 @@ namespace OpenRA.Orders
{
static Target TargetForInput(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAt(mi)
.Where(a => a.Info.HasTraitInfo<ITargetableInfo>() && !world.FogObscures(a))
var actor = world.ScreenMap.ActorsAtMouse(mi)
.Where(a => a.Actor.Info.HasTraitInfo<ITargetableInfo>() && !world.FogObscures(a.Actor))
.WithHighestSelectionPriority(worldPixel);
if (actor != null)
return Target.FromActor(actor);
var frozen = world.ScreenMap.FrozenActorsAt(world.RenderPlayer, mi)
var frozen = world.ScreenMap.FrozenActorsAtMouse(world.RenderPlayer, mi)
.Where(a => a.Info.HasTraitInfo<ITargetableInfo>() && a.Visible && a.HasRenderables)
.WithHighestSelectionPriority(worldPixel);
@@ -78,16 +79,18 @@ namespace OpenRA.Orders
}
// Used for classic mouse orders, determines whether or not action at xy is move or select
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
public virtual bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAt(xy).WithHighestSelectionPriority(xy);
var actor = world.ScreenMap.ActorsAtMouse(xy).WithHighestSelectionPriority(xy);
if (actor == null)
return true;
var target = Target.FromActor(actor);
var cell = world.Map.CellContaining(target.CenterPosition);
var actorsAt = world.ActorMap.GetActorsAt(cell).ToList();
var underCursor = world.Selection.Actors.WithHighestSelectionPriority(xy);
var underCursor = world.Selection.Actors
.Select(a => new ActorBoundsPair(a, a.MouseBounds(wr)))
.WithHighestSelectionPriority(xy);
var o = OrderForUnit(underCursor, target, actorsAt, cell, mi);
if (o != null)

View File

@@ -13,6 +13,8 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA.Traits
{
@@ -45,21 +47,29 @@ namespace OpenRA.Traits
}
}
public static Actor WithHighestSelectionPriority(this IEnumerable<Actor> actors, int2 selectionPixel)
public static Actor WithHighestSelectionPriority(this IEnumerable<ActorBoundsPair> actors, int2 selectionPixel)
{
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.SelectableBounds, selectionPixel));
if (!actors.Any())
return null;
return actors.MaxBy(a => CalculateActorSelectionPriority(a.Actor.Info, a.Bounds, selectionPixel)).Actor;
}
public static FrozenActor WithHighestSelectionPriority(this IEnumerable<FrozenActor> actors, int2 selectionPixel)
{
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.SelectableBounds, selectionPixel));
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.MouseBounds, selectionPixel));
}
static long CalculateActorSelectionPriority(ActorInfo info, Rectangle bounds, int2 selectionPixel)
{
var centerPixel = new int2(bounds.X, bounds.Y);
var pixelDistance = (centerPixel - selectionPixel).Length;
if (bounds.IsEmpty)
return info.SelectionPriority();
var centerPixel = new int2(
bounds.Left + bounds.Size.Width / 2,
bounds.Top + bounds.Size.Height / 2);
var pixelDistance = (centerPixel - selectionPixel).Length;
return ((long)-pixelDistance << 32) + info.SelectionPriority();
}

View File

@@ -52,6 +52,9 @@ namespace OpenRA.Traits
public IRenderable[] Renderables = NoRenderables;
public Rectangle[] ScreenBounds = NoBounds;
// TODO: Replace this with an int2[] polygon
public Rectangle MouseBounds = Rectangle.Empty;
static readonly IRenderable[] NoRenderables = new IRenderable[0];
static readonly Rectangle[] NoBounds = new Rectangle[0];
@@ -298,7 +301,7 @@ namespace OpenRA.Traits
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
return world.ScreenMap.FrozenActorsInBox(owner, wr.Viewport.TopLeft, wr.Viewport.BottomRight)
return world.ScreenMap.RenderableFrozenActorsInBox(owner, wr.Viewport.TopLeft, wr.Viewport.BottomRight)
.Where(f => f.Visible)
.SelectMany(ff => ff.Render(wr));
}

View File

@@ -9,6 +9,10 @@
*/
#endregion
using System.Collections.Generic;
using System.Drawing;
using OpenRA.Graphics;
namespace OpenRA.Traits
{
[Desc("This actor is selectable. Defines bounds of selectable area, selection class and selection priority.")]
@@ -19,6 +23,9 @@ namespace OpenRA.Traits
[Desc("Bounds for the selectable area.")]
public readonly int[] Bounds = null;
[Desc("Area outside the visible selection box that is enabled for selection")]
public readonly int Margin = 0;
[Desc("All units having the same selection class specified will be selected with select-by-type commands (e.g. double-click). "
+ "Defaults to the actor name when not defined or inherited.")]
public readonly string Class = null;
@@ -28,7 +35,7 @@ namespace OpenRA.Traits
public object Create(ActorInitializer init) { return new Selectable(init.Self, this); }
}
public class Selectable
public class Selectable : IMouseBounds
{
public readonly string Class = null;
@@ -39,5 +46,20 @@ namespace OpenRA.Traits
Class = string.IsNullOrEmpty(info.Class) ? self.Info.Name : info.Class;
Info = info;
}
Rectangle IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr)
{
if (Info.Bounds == null)
return Rectangle.Empty;
var size = new int2(Info.Bounds[0], Info.Bounds[1]);
var offset = -size / 2 - new int2(Info.Margin, Info.Margin);
if (Info.Bounds.Length > 2)
offset += new int2(Info.Bounds[2], Info.Bounds[3]);
var xy = wr.ScreenPxPosition(self.CenterPosition) + offset;
return new Rectangle(xy.X, xy.Y, size.X + 2 * Info.Margin, size.Y + 2 * Info.Margin);
}
}
}

View File

@@ -105,6 +105,10 @@ namespace OpenRA.Traits
public interface IAutoSelectionSizeInfo : ITraitInfoInterface { }
public interface IAutoSelectionSize { int2 SelectionSize(Actor self); }
// TODO: Replace Rectangle with an int2[] polygon
public interface IMouseBounds { Rectangle MouseoverBounds(Actor self, WorldRenderer wr); }
public interface IAutoMouseBounds { Rectangle AutoMouseoverBounds(Actor self, WorldRenderer wr); }
public interface IAutoRenderSizeInfo : ITraitInfoInterface { }
public interface IAutoRenderSize { int2 RenderSize(Actor self); }

View File

@@ -19,6 +19,26 @@ using OpenRA.Primitives;
namespace OpenRA.Traits
{
public struct ActorBoundsPair : IEquatable<ActorBoundsPair>
{
public readonly Actor Actor;
// TODO: Replace this with an int2[] polygon
public readonly Rectangle Bounds;
public ActorBoundsPair(Actor actor, Rectangle bounds) { Actor = actor; Bounds = bounds; }
public static bool operator ==(ActorBoundsPair me, ActorBoundsPair other) { return me.Actor == other.Actor && Equals(me.Bounds, other.Bounds); }
public static bool operator !=(ActorBoundsPair me, ActorBoundsPair other) { return !(me == other); }
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
public bool Equals(ActorBoundsPair other) { return this == other; }
public override bool Equals(object obj) { return obj is ActorBoundsPair && Equals((ActorBoundsPair)obj); }
public override string ToString() { return "{0}->{1}".F(Actor.Info.Name, Bounds.GetType().Name); }
}
public class ScreenMapInfo : ITraitInfo
{
[Desc("Size of partition bins (world pixels)")]
@@ -32,9 +52,14 @@ namespace OpenRA.Traits
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 Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedFrozenActors;
readonly SpatiallyPartitioned<Actor> partitionedActors;
readonly SpatiallyPartitioned<IEffect> partitionedEffects;
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>();
@@ -49,14 +74,19 @@ namespace OpenRA.Traits
var size = world.Map.Grid.TileSize;
var width = world.Map.MapSize.X * size.Width;
var height = world.Map.MapSize.Y * size.Height;
partitionedFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
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>());
partitionedActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
partitionedEffects = new SpatiallyPartitioned<IEffect>(width, height, info.BinSize);
}
public void WorldLoaded(World w, WorldRenderer wr) { worldRenderer = wr; }
@@ -94,7 +124,7 @@ namespace OpenRA.Traits
var screenHeight = Math.Abs(size.Height);
var screenBounds = new Rectangle(screenPos.X - screenWidth / 2, screenPos.Y - screenHeight / 2, screenWidth, screenHeight);
if (ValidBounds(screenBounds))
partitionedEffects.Add(effect, screenBounds);
partitionedRenderableEffects.Add(effect, screenBounds);
}
public void Add(IEffect effect, WPos position, Sprite sprite)
@@ -117,34 +147,41 @@ namespace OpenRA.Traits
public void Remove(IEffect effect)
{
partitionedEffects.Remove(effect);
partitionedRenderableEffects.Remove(effect);
}
bool ValidBounds(Rectangle bounds)
static bool ValidBounds(Rectangle bounds)
{
return bounds.Width > 0 && bounds.Height > 0;
}
public IEnumerable<FrozenActor> FrozenActorsAt(Player viewer, int2 worldPx)
public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, int2 worldPx)
{
if (viewer == null)
return NoFrozenActors;
return partitionedFrozenActors[viewer].At(worldPx).Where(frozenActorIsValid);
return partitionedMouseFrozenActors[viewer]
.At(worldPx)
.Where(frozenActorIsValid)
.Where(x => x.MouseBounds.Contains(worldPx));
}
public IEnumerable<FrozenActor> FrozenActorsAt(Player viewer, MouseInput mi)
public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, MouseInput mi)
{
return FrozenActorsAt(viewer, worldRenderer.Viewport.ViewToWorldPx(mi.Location));
return FrozenActorsAtMouse(viewer, worldRenderer.Viewport.ViewToWorldPx(mi.Location));
}
public IEnumerable<Actor> ActorsAt(int2 worldPx)
public IEnumerable<ActorBoundsPair> ActorsAtMouse(int2 worldPx)
{
return partitionedActors.At(worldPx).Where(actorIsInWorld);
return partitionedMouseActors.At(worldPx)
.Where(actorIsInWorld)
.Select(selectActorAndBounds)
.Where(x => x.Bounds.Contains(worldPx));
}
public IEnumerable<Actor> ActorsAt(MouseInput mi)
public IEnumerable<ActorBoundsPair> ActorsAtMouse(MouseInput mi)
{
return ActorsAt(worldRenderer.Viewport.ViewToWorldPx(mi.Location));
return ActorsAtMouse(worldRenderer.Viewport.ViewToWorldPx(mi.Location));
}
static Rectangle RectWithCorners(int2 a, int2 b)
@@ -152,36 +189,35 @@ namespace OpenRA.Traits
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<Actor> ActorsInBox(int2 a, int2 b)
public IEnumerable<ActorBoundsPair> ActorsInMouseBox(int2 a, int2 b)
{
return ActorsInBox(RectWithCorners(a, b));
return ActorsInMouseBox(RectWithCorners(a, b));
}
public IEnumerable<IEffect> EffectsInBox(int2 a, int2 b)
public IEnumerable<ActorBoundsPair> ActorsInMouseBox(Rectangle r)
{
return partitionedEffects.InBox(RectWithCorners(a, b));
return partitionedMouseActors.InBox(r)
.Where(actorIsInWorld)
.Select(selectActorAndBounds)
.Where(x => r.IntersectsWith(x.Bounds));
}
public IEnumerable<Actor> ActorsInBox(Rectangle r)
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
{
return partitionedActors.InBox(r).Where(actorIsInWorld);
return partitionedRenderableActors.InBox(RectWithCorners(a, b)).Where(actorIsInWorld);
}
public IEnumerable<FrozenActor> FrozenActorsInBox(Player p, int2 a, int2 b)
public IEnumerable<IEffect> RenderableEffectsInBox(int2 a, int2 b)
{
return FrozenActorsInBox(p, RectWithCorners(a, b));
return partitionedRenderableEffects.InBox(RectWithCorners(a, b));
}
public IEnumerable<IEffect> EffectsInBox(Rectangle r)
{
return partitionedEffects.InBox(r);
}
public IEnumerable<FrozenActor> FrozenActorsInBox(Player p, Rectangle r)
public IEnumerable<FrozenActor> RenderableFrozenActorsInBox(Player p, int2 a, int2 b)
{
if (p == null)
return NoFrozenActors;
return partitionedFrozenActors[p].InBox(r).Where(frozenActorIsValid);
return partitionedRenderableFrozenActors[p].InBox(RectWithCorners(a, b)).Where(frozenActorIsValid);
}
Rectangle AggregateBounds(IEnumerable<Rectangle> screenBounds)
@@ -196,24 +232,54 @@ namespace OpenRA.Traits
return bounds;
}
Rectangle AggregateBounds(IEnumerable<int2> vertices)
{
if (!vertices.Any())
return Rectangle.Empty;
var first = vertices.First();
var rect = new Rectangle(first.X, first.Y, 0, 0);
foreach (var v in vertices.Skip(1))
rect = Rectangle.Union(rect, new Rectangle(v.X, v.Y, 0, 0));
return rect;
}
public void Tick()
{
foreach (var a in addOrUpdateActors)
{
var bounds = AggregateBounds(a.ScreenBounds(worldRenderer));
if (!bounds.Size.IsEmpty)
var mouseBounds = a.MouseBounds(worldRenderer);
if (!mouseBounds.Size.IsEmpty)
{
if (partitionedActors.Contains(a))
partitionedActors.Update(a, bounds);
if (partitionedMouseActors.Contains(a))
partitionedMouseActors.Update(a, mouseBounds);
else
partitionedActors.Add(a, bounds);
partitionedMouseActors.Add(a, mouseBounds);
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
}
else
partitionedActors.Remove(a);
partitionedMouseActors.Remove(a);
var screenBounds = AggregateBounds(a.ScreenBounds(worldRenderer));
if (!screenBounds.Size.IsEmpty)
{
if (partitionedRenderableActors.Contains(a))
partitionedRenderableActors.Update(a, screenBounds);
else
partitionedRenderableActors.Add(a, screenBounds);
}
else
partitionedRenderableActors.Remove(a);
}
foreach (var a in removeActors)
partitionedActors.Remove(a);
{
partitionedMouseActors.Remove(a);
partitionedMouseActorBounds.Remove(a);
partitionedRenderableActors.Remove(a);
}
addOrUpdateActors.Clear();
removeActors.Clear();
@@ -222,16 +288,27 @@ namespace OpenRA.Traits
{
foreach (var fa in kv.Value)
{
var bounds = AggregateBounds(fa.ScreenBounds);
if (!bounds.Size.IsEmpty)
var mouseBounds = fa.MouseBounds;
if (!mouseBounds.Size.IsEmpty)
{
if (partitionedFrozenActors[kv.Key].Contains(fa))
partitionedFrozenActors[kv.Key].Update(fa, bounds);
if (partitionedMouseFrozenActors[kv.Key].Contains(fa))
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds);
else
partitionedFrozenActors[kv.Key].Add(fa, bounds);
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds);
}
else
partitionedFrozenActors[kv.Key].Remove(fa);
partitionedMouseFrozenActors[kv.Key].Remove(fa);
var screenBounds = AggregateBounds(fa.ScreenBounds);
if (!screenBounds.Size.IsEmpty)
{
if (partitionedRenderableFrozenActors[kv.Key].Contains(fa))
partitionedRenderableFrozenActors[kv.Key].Update(fa, screenBounds);
else
partitionedRenderableFrozenActors[kv.Key].Add(fa, screenBounds);
}
else
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
}
kv.Value.Clear();
@@ -240,18 +317,28 @@ namespace OpenRA.Traits
foreach (var kv in removeFrozenActors)
{
foreach (var fa in kv.Value)
partitionedFrozenActors[kv.Key].Remove(fa);
{
partitionedMouseFrozenActors[kv.Key].Remove(fa);
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
}
kv.Value.Clear();
}
}
public IEnumerable<Rectangle> ItemBounds(Player viewer)
public IEnumerable<Rectangle> RenderBounds(Player viewer)
{
var bounds = partitionedActors.ItemBounds
.Concat(partitionedEffects.ItemBounds);
var bounds = partitionedRenderableActors.ItemBounds
.Concat(partitionedRenderableEffects.ItemBounds);
return viewer != null ? bounds.Concat(partitionedFrozenActors[viewer].ItemBounds) : bounds;
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].ItemBounds) : bounds;
}
public IEnumerable<Rectangle> MouseBounds(Player viewer)
{
var bounds = partitionedMouseActors.ItemBounds;
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].ItemBounds) : bounds;
}
}
}