From 96f0c517574f49d5be16f82638656572ad2da813 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Thu, 10 Mar 2022 16:03:09 +0100 Subject: [PATCH] Added RectangleF.DistanceSquared and RectangleF.Distance --- CHANGELOG.md | 1 + MLEM.Ui/UiControls.cs | 9 +- MLEM/Misc/RectangleF.cs | 180 +++++++++++++++++++++++++--------------- 3 files changed, 117 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ab2d6..d858345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Additions - Added ColorExtensions.Multiply - Added SoundEffectInstanceHandler.Stop - Added TextureRegion.OffsetCopy +- Added RectangleF.DistanceSquared and RectangleF.Distance Improvements - Generify GenericFont's string drawing diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index bbb691e..9407cdf 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -373,12 +373,11 @@ namespace MLEM.Ui { foreach (var child in children) { if (!child.CanBeSelected || child == this.SelectedElement) continue; - var distVec = child.Area.Center - this.SelectedElement.Area.Center; - if (Math.Abs(direction.Angle() - Math.Atan2(distVec.Y, distVec.X)) >= MathHelper.PiOver2 - Element.Epsilon) + var (xOffset, yOffset) = child.Area.Center - this.SelectedElement.Area.Center; + if (Math.Abs(direction.Angle() - Math.Atan2(yOffset, xOffset)) >= MathHelper.PiOver2 - Element.Epsilon) continue; - var distSq = distVec.LengthSquared(); - // prefer navigating to elements that have the same parent as the currently selected element - if (closest == null || distSq < closestDistSq || closest.Parent != this.SelectedElement.Parent && child.Parent == this.SelectedElement.Parent) { + var distSq = child.Area.DistanceSquared(this.SelectedElement.Area); + if (closest == null || distSq < closestDistSq) { closest = child; closestDistSq = distSq; } diff --git a/MLEM/Misc/RectangleF.cs b/MLEM/Misc/RectangleF.cs index af5122e..7df4440 100644 --- a/MLEM/Misc/RectangleF.cs +++ b/MLEM/Misc/RectangleF.cs @@ -105,49 +105,6 @@ namespace MLEM.Misc { this.Height = size.Y; } - /// - /// Creates a new rectangle based on two corners that form a bounding box. - /// The resulting rectangle will encompass both corners as well as all of the space between them. - /// - /// The first corner to use - /// The second corner to use - /// - public static RectangleF FromCorners(Vector2 corner1, Vector2 corner2) { - var minX = Math.Min(corner1.X, corner2.X); - var minY = Math.Min(corner1.Y, corner2.Y); - var maxX = Math.Max(corner1.X, corner2.X); - var maxY = Math.Max(corner1.Y, corner2.Y); - return new RectangleF(minX, minY, maxX - minX, maxY - minY); - } - - /// - /// Converts a float-based rectangle to an int-based rectangle, flooring each value in the process. - /// - /// The rectangle to convert - /// The resulting rectangle - public static explicit operator Rectangle(RectangleF rect) { - return new Rectangle(rect.X.Floor(), rect.Y.Floor(), rect.Width.Floor(), rect.Height.Floor()); - } - - /// - /// Converts an int-based rectangle to a float-based rectangle. - /// - /// The rectangle to convert - /// The resulting rectangle - public static explicit operator RectangleF(Rectangle rect) { - return new RectangleF(rect.X, rect.Y, rect.Width, rect.Height); - } - - /// - public static bool operator ==(RectangleF a, RectangleF b) { - return a.X == b.X && a.Y == b.Y && a.Width == b.Width && a.Height == b.Height; - } - - /// - public static bool operator !=(RectangleF a, RectangleF b) { - return !(a == b); - } - /// public bool Contains(float x, float y) { return this.X <= x && x < this.X + this.Width && this.Y <= y && y < this.Y + this.Height; @@ -196,6 +153,92 @@ namespace MLEM.Misc { return value.Left < this.Right && this.Left < value.Right && value.Top < this.Bottom && this.Top < value.Bottom; } + /// + public void Offset(float offsetX, float offsetY) { + this.X += offsetX; + this.Y += offsetY; + } + + /// + public void Offset(Vector2 amount) { + this.X += amount.X; + this.Y += amount.Y; + } + + /// + /// Calculates the suqared distance between this rectangle and the . + /// The returned value is the smallest squared distance between any two edges or corners of the two rectangles. + /// + /// The rectangle to calculate the squared distance to. + /// The squared distance between the two rectangles. + public float DistanceSquared(RectangleF value) { + // we calculate the distance based on the quadrants that the other rectangle is in using 8 cases: + // 1 7 4 + // 3 T 6 + // 2 8 5 + var valueIsAbove = value.Bottom < this.Top; + var valueIsBelow = value.Top > this.Bottom; + if (value.Right < this.Left) { + if (valueIsAbove) // 1 + return Vector2.DistanceSquared(new Vector2(value.Right, value.Bottom), new Vector2(this.Left, this.Top)); + if (valueIsBelow) // 2 + return Vector2.DistanceSquared(new Vector2(value.Right, value.Top), new Vector2(this.Left, this.Bottom)); + return (this.Left - value.Right) * (this.Left - value.Right); // 3 + } else if (value.Left > this.Right) { + if (valueIsAbove) // 4 + return Vector2.DistanceSquared(new Vector2(value.Left, value.Bottom), new Vector2(this.Right, this.Top)); + if (valueIsBelow) // 5 + return Vector2.DistanceSquared(new Vector2(value.Left, value.Top), new Vector2(this.Right, this.Bottom)); + return (value.Left - this.Right) * (value.Left - this.Right); // 6 + } else if (valueIsAbove) { + return (this.Top - value.Bottom) * (this.Top - value.Bottom); // 7 + } else if (valueIsBelow) { + return (value.Top - this.Bottom) * (value.Top - this.Bottom); // 8 + } else { + return 0; + } + } + + /// + /// Calculates the distance between this rectangle and the . + /// The returned value is the smallest distance between any two edges or corners of the two rectangles. + /// + /// The rectangle to calculate the distance to. + /// The distance between the two rectangles. + public float Distance(RectangleF value) { + return (float) Math.Sqrt(this.DistanceSquared(value)); + } + + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString() { + return "{X:" + this.X + " Y:" + this.Y + " Width:" + this.Width + " Height:" + this.Height + "}"; + } + + /// + public void Deconstruct(out float x, out float y, out float width, out float height) { + x = this.X; + y = this.Y; + width = this.Width; + height = this.Height; + } + + /// + /// Converts a float-based rectangle to an int-based rectangle, flooring each value in the process. + /// + /// The rectangle to convert + /// The resulting rectangle + public static explicit operator Rectangle(RectangleF rect) { + return new Rectangle(rect.X.Floor(), rect.Y.Floor(), rect.Width.Floor(), rect.Height.Floor()); + } + + /// + public static RectangleF Union(RectangleF value1, RectangleF value2) { + var x = Math.Min(value1.X, value2.X); + var y = Math.Min(value1.Y, value2.Y); + return new RectangleF(x, y, Math.Max(value1.Right, value2.Right) - x, Math.Max(value1.Bottom, value2.Bottom) - y); + } + /// public static RectangleF Intersect(RectangleF value1, RectangleF value2) { if (value1.Intersects(value2)) { @@ -209,37 +252,38 @@ namespace MLEM.Misc { } } - /// - public void Offset(float offsetX, float offsetY) { - this.X += offsetX; - this.Y += offsetY; + /// + /// Creates a new rectangle based on two corners that form a bounding box. + /// The resulting rectangle will encompass both corners as well as all of the space between them. + /// + /// The first corner to use + /// The second corner to use + /// + public static RectangleF FromCorners(Vector2 corner1, Vector2 corner2) { + var minX = Math.Min(corner1.X, corner2.X); + var minY = Math.Min(corner1.Y, corner2.Y); + var maxX = Math.Max(corner1.X, corner2.X); + var maxY = Math.Max(corner1.Y, corner2.Y); + return new RectangleF(minX, minY, maxX - minX, maxY - minY); } - /// - public void Offset(Vector2 amount) { - this.X += amount.X; - this.Y += amount.Y; + /// + /// Converts an int-based rectangle to a float-based rectangle. + /// + /// The rectangle to convert + /// The resulting rectangle + public static explicit operator RectangleF(Rectangle rect) { + return new RectangleF(rect.X, rect.Y, rect.Width, rect.Height); } - /// Returns a string that represents the current object. - /// A string that represents the current object. - public override string ToString() { - return "{X:" + this.X + " Y:" + this.Y + " Width:" + this.Width + " Height:" + this.Height + "}"; + /// + public static bool operator ==(RectangleF a, RectangleF b) { + return a.X == b.X && a.Y == b.Y && a.Width == b.Width && a.Height == b.Height; } - /// - public static RectangleF Union(RectangleF value1, RectangleF value2) { - var x = Math.Min(value1.X, value2.X); - var y = Math.Min(value1.Y, value2.Y); - return new RectangleF(x, y, Math.Max(value1.Right, value2.Right) - x, Math.Max(value1.Bottom, value2.Bottom) - y); - } - - /// - public void Deconstruct(out float x, out float y, out float width, out float height) { - x = this.X; - y = this.Y; - width = this.Width; - height = this.Height; + /// + public static bool operator !=(RectangleF a, RectangleF b) { + return !(a == b); } }