using System; using System.Runtime.Serialization; using Microsoft.Xna.Framework; using MLEM.Extensions; namespace MLEM.Misc { /// /// Represents a float-based version of /// [DataContract] public struct RectangleF : IEquatable { /// /// The empty rectangle, with an x, y, width and height of 0. /// public static RectangleF Empty => default; /// /// The x position of the top left corner of this rectangle. /// [DataMember] public float X; /// /// The y position of the top left corner of this rectangle. /// [DataMember] public float Y; /// /// The width of this rectangle. /// [DataMember] public float Width; /// /// The height of this rectangle. /// [DataMember] public float Height; /// public float Left => this.X; /// /// The x position of the bottom right corner of this rectangle. /// public float Right => this.X + this.Width; /// public float Top => this.Y; /// /// The y position of the bottom right corner of this rectangle. /// public float Bottom => this.Y + this.Height; /// /// A boolean that is true if this rectangle is empty. /// A rectangle is considered empty if the width or height is 0. /// public bool IsEmpty => this.Width <= 0 || this.Height <= 0; /// /// The top left corner of this rectangle /// public Vector2 Location { get => new Vector2(this.X, this.Y); set { this.X = value.X; this.Y = value.Y; } } /// /// The size, that is, the width and height of this rectangle. /// public Vector2 Size { get => new Vector2(this.Width, this.Height); set { this.Width = value.X; this.Height = value.Y; } } /// /// The center of this rectangle, based on the top left corner and the size. /// public Vector2 Center => new Vector2(this.X + this.Width / 2, this.Y + this.Height / 2); /// /// Creates a new rectangle with the specified location and size /// /// The x coordinate of the top left corner of the rectangle /// The y coordinate of the top left corner of the rectangle /// The width of the rectangle /// The height of the rectangle public RectangleF(float x, float y, float width, float height) { this.X = x; this.Y = y; this.Width = width; this.Height = height; } /// /// Creates a new rectangle with the specified location and size vectors /// /// The top left corner of the rectangle /// The size of the rectangle, where x represents width and the y represents height public RectangleF(Vector2 location, Vector2 size) { this.X = location.X; this.Y = location.Y; this.Width = size.X; this.Height = size.Y; } /// public bool Contains(float x, float y) { return this.X <= x && x < this.X + this.Width && this.Y <= y && y < this.Y + this.Height; } /// public bool Contains(Vector2 value) { return this.Contains(value.X, value.Y); } /// public bool Contains(RectangleF value) { return this.X <= value.X && value.X + value.Width <= this.X + this.Width && this.Y <= value.Y && value.Y + value.Height <= this.Y + this.Height; } /// Indicates whether this instance and a specified object are equal. /// The object to compare with the current instance. /// if and this instance are the same type and represent the same value; otherwise, . public override bool Equals(object obj) { return obj is RectangleF f && this == f; } /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// if the current object is equal to the parameter; otherwise, . public bool Equals(RectangleF other) { return this == other; } /// Returns the hash code for this instance. /// A 32-bit signed integer that is the hash code for this instance. public override int GetHashCode() { return (((17 * 23 + this.X.GetHashCode()) * 23 + this.Y.GetHashCode()) * 23 + this.Width.GetHashCode()) * 23 + this.Height.GetHashCode(); } /// public void Inflate(float horizontalAmount, float verticalAmount) { this.X -= horizontalAmount; this.Y -= verticalAmount; this.Width += horizontalAmount * 2; this.Height += verticalAmount * 2; } /// public bool Intersects(RectangleF value) { 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 + "}"; } /// /// Deconstruction method for . /// /// /// /// /// 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)) { var num1 = Math.Min(value1.X + value1.Width, value2.X + value2.Width); var x = Math.Max(value1.X, value2.X); var y = Math.Max(value1.Y, value2.Y); var num2 = Math.Min(value1.Y + value1.Height, value2.Y + value2.Height); return new RectangleF(x, y, num1 - x, num2 - y); } else { return RectangleF.Empty; } } /// /// 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 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); } } }