Add support for polygon selection shapes.
This commit is contained in:
@@ -233,7 +233,7 @@ namespace OpenRA
|
|||||||
yield return r;
|
yield return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle MouseBounds(WorldRenderer wr)
|
public Polygon MouseBounds(WorldRenderer wr)
|
||||||
{
|
{
|
||||||
foreach (var mb in mouseBounds)
|
foreach (var mb in mouseBounds)
|
||||||
{
|
{
|
||||||
@@ -242,7 +242,7 @@ namespace OpenRA
|
|||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Rectangle.Empty;
|
return Polygon.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueActivity(bool queued, Activity nextActivity)
|
public void QueueActivity(bool queued, Activity nextActivity)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||||
{
|
{
|
||||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool PolygonContains(this int2[] polygon, int2 p)
|
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||||
@@ -101,6 +101,16 @@ namespace OpenRA
|
|||||||
return windingNumber != 0;
|
return windingNumber != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
|
||||||
|
{
|
||||||
|
// If line segments AB and CD intersect:
|
||||||
|
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||||
|
// - the triangles CAB and DAB must have opposite sense
|
||||||
|
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
|
||||||
|
// Assumes that lines are not colinear
|
||||||
|
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
||||||
{
|
{
|
||||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||||
|
|||||||
@@ -277,11 +277,13 @@ namespace OpenRA.Graphics
|
|||||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
|
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||||
{
|
{
|
||||||
var tl = Viewport.WorldToViewPx(new float2(r.Left, r.Top));
|
var points = b.Vertices
|
||||||
var br = Viewport.WorldToViewPx(new float2(r.Right, r.Bottom));
|
.Select(p => Viewport.WorldToViewPx(p).ToFloat2())
|
||||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.OrangeRed);
|
.ToArray();
|
||||||
|
|
||||||
|
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
114
OpenRA.Game/Primitives/Polygon.cs
Normal file
114
OpenRA.Game/Primitives/Polygon.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#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.Linq;
|
||||||
|
|
||||||
|
namespace OpenRA.Primitives
|
||||||
|
{
|
||||||
|
public struct Polygon
|
||||||
|
{
|
||||||
|
public static readonly Polygon Empty = new Polygon(Rectangle.Empty);
|
||||||
|
|
||||||
|
public readonly Rectangle BoundingRect;
|
||||||
|
public readonly int2[] Vertices;
|
||||||
|
bool isRectangle;
|
||||||
|
|
||||||
|
public Polygon(Rectangle bounds)
|
||||||
|
{
|
||||||
|
BoundingRect = bounds;
|
||||||
|
Vertices = new[] { bounds.TopLeft, bounds.BottomLeft, bounds.BottomRight, bounds.TopRight };
|
||||||
|
isRectangle = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Polygon(int2[] vertices)
|
||||||
|
{
|
||||||
|
if (vertices != null && vertices.Length > 0)
|
||||||
|
{
|
||||||
|
Vertices = vertices;
|
||||||
|
var left = int.MaxValue;
|
||||||
|
var right = int.MinValue;
|
||||||
|
var top = int.MaxValue;
|
||||||
|
var bottom = int.MinValue;
|
||||||
|
foreach (var p in vertices)
|
||||||
|
{
|
||||||
|
left = Math.Min(left, p.X);
|
||||||
|
right = Math.Max(right, p.X);
|
||||||
|
top = Math.Min(top, p.Y);
|
||||||
|
bottom = Math.Max(bottom, p.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundingRect = Rectangle.FromLTRB(left, top, right, bottom);
|
||||||
|
isRectangle = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isRectangle = true;
|
||||||
|
BoundingRect = Rectangle.Empty;
|
||||||
|
Vertices = Exts.MakeArray(4, _ => int2.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty { get { return BoundingRect.IsEmpty; } }
|
||||||
|
public bool Contains(int2 xy)
|
||||||
|
{
|
||||||
|
return isRectangle ? BoundingRect.Contains(xy) : Vertices.PolygonContains(xy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IntersectsWith(Rectangle rect)
|
||||||
|
{
|
||||||
|
var intersectsBoundingRect = BoundingRect.Left < rect.Right && BoundingRect.Right > rect.Left && BoundingRect.Top < rect.Bottom && BoundingRect.Bottom > rect.Top;
|
||||||
|
if (isRectangle)
|
||||||
|
return intersectsBoundingRect;
|
||||||
|
|
||||||
|
// Easy case 1: Rect and bounding box don't intersect
|
||||||
|
if (!intersectsBoundingRect)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Easy case 2: Rect and bounding box intersect in a cross shape
|
||||||
|
if ((rect.Left <= BoundingRect.Left && rect.Right >= BoundingRect.Right) || (rect.Top <= BoundingRect.Top && rect.Bottom >= BoundingRect.Bottom))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Easy case 3: Corner of rect is inside the polygon
|
||||||
|
if (Vertices.PolygonContains(rect.TopLeft) || Vertices.PolygonContains(rect.TopRight) || Vertices.PolygonContains(rect.BottomLeft) || Vertices.PolygonContains(rect.BottomRight))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Easy case 4: Polygon vertex is inside rect
|
||||||
|
if (Vertices.Any(p => rect.Contains(p)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Hard case: check intersection of every line segment pair
|
||||||
|
var rectVertices = new[]
|
||||||
|
{
|
||||||
|
rect.TopLeft,
|
||||||
|
rect.BottomLeft,
|
||||||
|
rect.BottomRight,
|
||||||
|
rect.TopRight
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < Vertices.Length; i++)
|
||||||
|
for (var j = 0; j < 4; j++)
|
||||||
|
if (Exts.LinesIntersect(Vertices[i], Vertices[(i + 1) % Vertices.Length], rectVertices[j], rectVertices[(j + 1) % 4]))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var code = BoundingRect.GetHashCode();
|
||||||
|
foreach (var v in Vertices)
|
||||||
|
code = ((code << 5) + code) ^ v.GetHashCode();
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,5 +139,7 @@ namespace OpenRA.Primitives
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Rectangle> ItemBounds { get { return itemBounds.Values; } }
|
public IEnumerable<Rectangle> ItemBounds { get { return itemBounds.Values; } }
|
||||||
|
|
||||||
|
public IEnumerable<T> Items { get { return itemBounds.Keys; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,14 +73,18 @@ namespace OpenRA.Traits
|
|||||||
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.MouseBounds, selectionPixel, modifiers));
|
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.MouseBounds, selectionPixel, modifiers));
|
||||||
}
|
}
|
||||||
|
|
||||||
static long CalculateActorSelectionPriority(ActorInfo info, Rectangle bounds, int2 selectionPixel, Modifiers modifiers)
|
static long CalculateActorSelectionPriority(ActorInfo info, Polygon bounds, int2 selectionPixel, Modifiers modifiers)
|
||||||
{
|
{
|
||||||
if (bounds.IsEmpty)
|
if (bounds.IsEmpty)
|
||||||
return info.SelectionPriority(modifiers);
|
return info.SelectionPriority(modifiers);
|
||||||
|
|
||||||
|
// Assume that the center of the polygon is the same as the center of the bounding box
|
||||||
|
// This isn't necessarily true for arbitrary polygons, but is fine for the hexagonal and diamond
|
||||||
|
// shapes that are currently implemented
|
||||||
|
var br = bounds.BoundingRect;
|
||||||
var centerPixel = new int2(
|
var centerPixel = new int2(
|
||||||
bounds.Left + bounds.Size.Width / 2,
|
br.Left + br.Size.Width / 2,
|
||||||
bounds.Top + bounds.Size.Height / 2);
|
br.Top + br.Size.Height / 2);
|
||||||
|
|
||||||
var pixelDistance = (centerPixel - selectionPixel).Length;
|
var pixelDistance = (centerPixel - selectionPixel).Length;
|
||||||
return info.SelectionPriority(modifiers) - (long)pixelDistance << 16;
|
return info.SelectionPriority(modifiers) - (long)pixelDistance << 16;
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ namespace OpenRA.Traits
|
|||||||
public IRenderable[] Renderables = NoRenderables;
|
public IRenderable[] Renderables = NoRenderables;
|
||||||
public Rectangle[] ScreenBounds = NoBounds;
|
public Rectangle[] ScreenBounds = NoBounds;
|
||||||
|
|
||||||
// TODO: Replace this with an int2[] polygon
|
public Polygon MouseBounds = Polygon.Empty;
|
||||||
public Rectangle MouseBounds = Rectangle.Empty;
|
|
||||||
|
|
||||||
static readonly IRenderable[] NoRenderables = new IRenderable[0];
|
static readonly IRenderable[] NoRenderables = new IRenderable[0];
|
||||||
static readonly Rectangle[] NoBounds = new Rectangle[0];
|
static readonly Rectangle[] NoBounds = new Rectangle[0];
|
||||||
|
|||||||
@@ -120,8 +120,7 @@ namespace OpenRA.Traits
|
|||||||
IEnumerable<Rectangle> ScreenBounds(Actor self, WorldRenderer wr);
|
IEnumerable<Rectangle> ScreenBounds(Actor self, WorldRenderer wr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace Rectangle with an int2[] polygon
|
public interface IMouseBounds { Polygon MouseoverBounds(Actor self, WorldRenderer wr); }
|
||||||
public interface IMouseBounds { Rectangle MouseoverBounds(Actor self, WorldRenderer wr); }
|
|
||||||
public interface IMouseBoundsInfo : ITraitInfoInterface { }
|
public interface IMouseBoundsInfo : ITraitInfoInterface { }
|
||||||
public interface IAutoMouseBounds { Rectangle AutoMouseoverBounds(Actor self, WorldRenderer wr); }
|
public interface IAutoMouseBounds { Rectangle AutoMouseoverBounds(Actor self, WorldRenderer wr); }
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ namespace OpenRA.Traits
|
|||||||
public struct ActorBoundsPair
|
public struct ActorBoundsPair
|
||||||
{
|
{
|
||||||
public readonly Actor Actor;
|
public readonly Actor Actor;
|
||||||
|
public readonly Polygon Bounds;
|
||||||
|
|
||||||
// TODO: Replace this with an int2[] polygon
|
public ActorBoundsPair(Actor actor, Polygon bounds) { Actor = actor; Bounds = bounds; }
|
||||||
public readonly Rectangle Bounds;
|
|
||||||
|
|
||||||
public ActorBoundsPair(Actor actor, Rectangle bounds) { Actor = actor; Bounds = bounds; }
|
|
||||||
|
|
||||||
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
|
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
|
||||||
|
|
||||||
@@ -192,7 +190,7 @@ namespace OpenRA.Traits
|
|||||||
return partitionedMouseActors.InBox(r)
|
return partitionedMouseActors.InBox(r)
|
||||||
.Where(actorIsInWorld)
|
.Where(actorIsInWorld)
|
||||||
.Select(selectActorAndBounds)
|
.Select(selectActorAndBounds)
|
||||||
.Where(x => r.IntersectsWith(x.Bounds));
|
.Where(x => x.Bounds.IntersectsWith(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
|
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
|
||||||
@@ -218,12 +216,12 @@ namespace OpenRA.Traits
|
|||||||
foreach (var a in addOrUpdateActors)
|
foreach (var a in addOrUpdateActors)
|
||||||
{
|
{
|
||||||
var mouseBounds = a.MouseBounds(worldRenderer);
|
var mouseBounds = a.MouseBounds(worldRenderer);
|
||||||
if (!mouseBounds.Size.IsEmpty)
|
if (!mouseBounds.IsEmpty)
|
||||||
{
|
{
|
||||||
if (partitionedMouseActors.Contains(a))
|
if (partitionedMouseActors.Contains(a))
|
||||||
partitionedMouseActors.Update(a, mouseBounds);
|
partitionedMouseActors.Update(a, mouseBounds.BoundingRect);
|
||||||
else
|
else
|
||||||
partitionedMouseActors.Add(a, mouseBounds);
|
partitionedMouseActors.Add(a, mouseBounds.BoundingRect);
|
||||||
|
|
||||||
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
|
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
|
||||||
}
|
}
|
||||||
@@ -257,12 +255,12 @@ namespace OpenRA.Traits
|
|||||||
foreach (var fa in kv.Value)
|
foreach (var fa in kv.Value)
|
||||||
{
|
{
|
||||||
var mouseBounds = fa.MouseBounds;
|
var mouseBounds = fa.MouseBounds;
|
||||||
if (!mouseBounds.Size.IsEmpty)
|
if (!mouseBounds.IsEmpty)
|
||||||
{
|
{
|
||||||
if (partitionedMouseFrozenActors[kv.Key].Contains(fa))
|
if (partitionedMouseFrozenActors[kv.Key].Contains(fa))
|
||||||
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds);
|
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds.BoundingRect);
|
||||||
else
|
else
|
||||||
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds);
|
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds.BoundingRect);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
partitionedMouseFrozenActors[kv.Key].Remove(fa);
|
partitionedMouseFrozenActors[kv.Key].Remove(fa);
|
||||||
@@ -302,11 +300,10 @@ namespace OpenRA.Traits
|
|||||||
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].ItemBounds) : bounds;
|
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].ItemBounds) : bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Rectangle> MouseBounds(Player viewer)
|
public IEnumerable<Polygon> MouseBounds(Player viewer)
|
||||||
{
|
{
|
||||||
var bounds = partitionedMouseActors.ItemBounds;
|
var bounds = partitionedMouseActorBounds.Values.Select(a => a.Bounds);
|
||||||
|
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Items.Select(fa => fa.MouseBounds)) : bounds;
|
||||||
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].ItemBounds) : bounds;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return autoBounds.Select(s => s.AutoMouseoverBounds(self, wr)).FirstOrDefault(r => !r.IsEmpty);
|
return autoBounds.Select(s => s.AutoMouseoverBounds(self, wr)).FirstOrDefault(r => !r.IsEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle Bounds(Actor self, WorldRenderer wr, int[] bounds)
|
Polygon Bounds(Actor self, WorldRenderer wr, int[] bounds)
|
||||||
{
|
{
|
||||||
if (bounds == null)
|
if (bounds == null)
|
||||||
return AutoBounds(self, wr);
|
return new Polygon(AutoBounds(self, wr));
|
||||||
|
|
||||||
var size = new int2(bounds[0], bounds[1]);
|
var size = new int2(bounds[0], bounds[1]);
|
||||||
|
|
||||||
@@ -64,17 +64,17 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
offset += new int2(bounds[2], bounds[3]);
|
offset += new int2(bounds[2], bounds[3]);
|
||||||
|
|
||||||
var xy = wr.ScreenPxPosition(self.CenterPosition) + offset;
|
var xy = wr.ScreenPxPosition(self.CenterPosition) + offset;
|
||||||
return new Rectangle(xy.X, xy.Y, size.X, size.Y);
|
return new Polygon(new Rectangle(xy.X, xy.Y, size.X, size.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr)
|
Polygon IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr)
|
||||||
{
|
{
|
||||||
return Bounds(self, wr, info.Bounds);
|
return Bounds(self, wr, info.Bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle DecorationBounds(Actor self, WorldRenderer wr)
|
public Rectangle DecorationBounds(Actor self, WorldRenderer wr)
|
||||||
{
|
{
|
||||||
return Bounds(self, wr, info.DecorationBounds ?? info.Bounds);
|
return Bounds(self, wr, info.DecorationBounds ?? info.Bounds).BoundingRect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
{
|
{
|
||||||
IRenderable[] renderables = null;
|
IRenderable[] renderables = null;
|
||||||
Rectangle[] bounds = null;
|
Rectangle[] bounds = null;
|
||||||
Rectangle mouseBounds = Rectangle.Empty;
|
var mouseBounds = Polygon.Empty;
|
||||||
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
|
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
|
||||||
{
|
{
|
||||||
var frozen = frozenStates[playerIndex].FrozenActor;
|
var frozen = frozenStates[playerIndex].FrozenActor;
|
||||||
|
|||||||
Reference in New Issue
Block a user