diff --git a/src/DtronixCommon/Boundary.cs b/src/DtronixCommon/Boundary.cs new file mode 100644 index 0000000..d7433ba --- /dev/null +++ b/src/DtronixCommon/Boundary.cs @@ -0,0 +1,370 @@ +using System.Runtime.CompilerServices; + +namespace DtronixCommon; + +/// +/// Boundary represented as [MinX, MinY] (Bottom Left) and [MaxX, MaxY] (Top Right) points. +/// +/// +/// MinY could be down and MaxY up as in cartesian coordinates, or vice vercia as is common +/// in screen coordinates. Depends upon which system was selected for use. +/// +public readonly struct Boundary +{ + /// + /// Minimum X coordinate position. (Left) + /// + public readonly float MinX; + + /// + /// Minimum Y coordinate position (Bottom/Top) + /// + public readonly float MinY; + + /// + /// Maximum X coordinate position (Right) + /// + public readonly float MaxX; + + /// + /// Maximum Y coordinate position (Top/Bottom) + /// + public readonly float MaxY; + + /// + /// Width of the boundary. + /// + public readonly float Width => MaxX - MinX; + + /// + /// Height of the boundary. + /// + public readonly float Height => MaxY - MinY; + + /// + /// Maximum sized boundary. + /// + public static Boundary Max { get; } = new( + float.MinValue, + float.MinValue, + float.MaxValue, + float.MaxValue); + + /// + /// Half the maximum size of the boundary. + /// + public static Boundary HalfMax { get; } = new( + float.MinValue / 2, + float.MinValue / 2, + float.MaxValue / 2, + float.MaxValue / 2); + + /// + /// Zero sized boundary. + /// + public static Boundary Zero { get; } = new(0, 0, 0, 0); + + /// + /// Empty boundary. + /// + public static Boundary Empty { get; } = new( + float.PositiveInfinity, + float.PositiveInfinity, + float.NegativeInfinity, + float.NegativeInfinity); + + /// + /// Returns true if the boundary has no volume. + /// + public bool IsEmpty => MinY >= MaxY || MinX >= MaxX; + + /// + /// Creates a boundary with the specified left, bottom, right, top distances from origin. + /// + /// Left distance from origin. + /// Bottom distance from origin. + /// Right distance from origin. + /// Top distance from origin. + public Boundary(float minX, float minY, float maxX, float maxY) + { + MinX = minX; + MinY = minY; + MaxX = maxX; + MaxY = maxY; + } + + /// + /// IntersectsWith - Returns true if the Boundary intersects with this Boundary + /// Returns false otherwise. + /// Note that if one edge is coincident, this is considered an intersection. + /// + /// + /// Returns true if the Boundary intersects with this Boundary + /// Returns false otherwise. + /// + /// Rect + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(in Boundary boundary) + { + return boundary.MinX <= MaxX && + boundary.MaxX >= MinX && + boundary.MinY <= MaxY && + boundary.MaxY >= MinY; + } + + /// + /// IntersectsWith - Returns true if the Boundary intersects with this Boundary + /// Returns false otherwise. + /// Note that if one edge is coincident, this is considered an intersection. + /// + /// First boundary. + /// Second boundary. + /// + /// Returns true if the Boundary intersects with this Boundary + /// Returns false otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Intersects(in Boundary boundary1, in Boundary boundary2) + { + return boundary1.MinX <= boundary2.MaxX && + boundary1.MaxX >= boundary2.MinX && + boundary1.MinY <= boundary2.MaxY && + boundary1.MaxY >= boundary2.MinY; + } + + /// + /// Checks if a point is contained by the boundary. + /// + /// X coordinate. + /// Y coordinate. + /// True if the point is contained, false otherwise. + public bool Contains(float x, float y) + { + return x >= MinX + && x <= MaxX + && y >= MinY + && y <= MaxY; + } + + + /// + /// Takes the current boundary and offsets it by the specified X & Y vectors. + /// + /// X vector offset. + /// Y vector offset. + /// New offset boundary. + public Boundary CreateOffset(in float x, in float y) + { + return new Boundary( + (float)(MinX + x), + (float)(MinY + y), + (float)(MaxX + x), + (float)(MaxY + y)); + } + + /// + /// Creates a union between two boundaries. + /// + /// second boundary to union with. + /// new union boundary. + public Boundary Union(in Boundary boundary) + { + if (IsEmpty) + return boundary; + + if (boundary.IsEmpty) + return this; + + return new Boundary( + Math.Min(MinX, boundary.MinX), + Math.Min(MinY, boundary.MinY), + Math.Max(MaxX, boundary.MaxX), + Math.Max(MaxY, boundary.MaxY)); + } + + /// + /// Gets a rough approximation of the hypotenuse of the rectangle. + /// Uses different algorithms based upon the passed integer. + /// + /// 0 - 3. Determines algorithm used. 0 is the fastest, 3 is the slowest but most accurate. + /// Approximate length + /// + /// https://stackoverflow.com/a/26607206 + /// All these assume 0 ≤ a ≤ b. + /// 0. h = b + 0.337 * a // less sorting order of the a & b variables; + /// 1. h = b + 0.337 * a // max error ≈ 5.5 % + /// 2. h = max(b, 0.918 * (b + (a >> 1))) // max error ≈ 2.6 % + /// 3. h = b + 0.428 * a* a / b // max error ≈ 1.04 % + /// + public float GetHypotenuseApproximate(int algorithm) + { + var a = MathF.Abs(MaxX - MinX); + var b = MathF.Abs(MaxY - MinY); + + if (algorithm == 0) + return b + 0.337f * a; + + // Transpose variables to ensure "b" is larger than A + if (a > b) + (a, b) = (b, a); + + switch (algorithm) + { + case 1: + return b + 0.337f * a; + case 2: + return MathF.Max(b, 0.918f * (b + (a / 2f))); + case 3: + return b + 0.428f * a * a / b; + default: + throw new ArgumentException("Must select algorithm 0 through 3", nameof(algorithm)); + } + + } + + /// + /// Rotates the boundary from its center point by the specified number of degrees. + /// + /// Degrees to rotate. + /// New rotated boundary. + public Boundary Rotate(float degrees) + { + // Center position of the rectangle. + //private const m_PosX : Number, m_PosY : Number; + + // Rectangle orientation, in radians. + var m_Orientation = degrees * MathF.PI / 180.0f; + // Half-width and half-height of the rectangle. + var m_HalfSizeX = Width / 2; + var m_HalfSizeY = Height / 2; + var m_PosX = MinX + m_HalfSizeX; + var m_PosY = MinY + m_HalfSizeY; + + // corner_1 is right-top corner of unrotated rectangle, relative to m_Pos. + // corner_2 is right-bottom corner of unrotated rectangle, relative to m_Pos. + var corner_1_x = m_HalfSizeX; + var corner_2_x = m_HalfSizeX; + var corner_1_y = -m_HalfSizeY; + var corner_2_y = m_HalfSizeY; + + var sin_o = MathF.Sin(m_Orientation); + var cos_o = MathF.Cos(m_Orientation); + + // xformed_corner_1, xformed_corner_2 are points corner_1, corner_2 rotated by angle m_Orientation. + var xformed_corner_1_x = corner_1_x * cos_o - corner_1_y * sin_o; + var xformed_corner_1_y = corner_1_x * sin_o + corner_1_y * cos_o; + var xformed_corner_2_x = corner_2_x * cos_o - corner_2_y * sin_o; + var xformed_corner_2_y = corner_2_x * sin_o + corner_2_y * cos_o; + + // ex, ey are extents (half-sizes) of the final AABB. + var ex = MathF.Max(MathF.Abs(xformed_corner_1_x), MathF.Abs(xformed_corner_2_x)); + var ey = MathF.Max(MathF.Abs(xformed_corner_1_y), MathF.Abs(xformed_corner_2_y)); + return new Boundary(m_PosX - ex, m_PosY - ey, m_PosX + ex, m_PosY + ey); + //var aabb_min_x = m_PosX - ex; + //var aabb_max_x = m_PosX + ex; + //var aabb_min_y = m_PosY - ey; + //var aabb_max_y = m_PosY + ey; + } + + /// + /// Unions two boundaries. + /// + /// First boundary. + /// Second boundary. + /// New union boundary. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Boundary Union(in Boundary boundary1, in Boundary boundary2) + { + return new Boundary( + boundary1.MinX < boundary2.MinX ? boundary1.MinX : boundary2.MinX, + boundary1.MinY < boundary2.MinY ? boundary1.MinY : boundary2.MinY, + boundary1.MaxX > boundary2.MaxX ? boundary1.MaxX : boundary2.MaxX, + boundary1.MaxY > boundary2.MaxY ? boundary1.MaxY : boundary2.MaxY); + } + + /// + /// Returns a String which represents the boundary instance. + /// + /// Value + public override string ToString() + { + return $"MinX:{MinX:F}; MinY:{MinY:F}; MaxX:{MaxX:F}; MaxY:{MaxY:F}; Width: {MaxX - MinX:F}; Height: {MaxY - MinY:F}"; + } + + + /// + /// Checks to see if the the two boundaries are equal. + /// + /// First boundary. + /// Second boundary. + /// True if the two boundaries are equal. + public static bool operator ==(in Boundary boundary1, in Boundary boundary2) + { + return boundary1.Equals(boundary2); + } + + /// + /// Checks to see if the the two boundaries are equal. + /// + /// First boundary. + /// Second boundary. + /// True if the two boundaries are not equal. + public static bool operator !=(in Boundary boundary1, in Boundary boundary2) + { + return boundary1.MinX != boundary2.MaxX || + boundary1.MaxX != boundary2.MinX || + boundary1.MinY != boundary2.MaxY || + boundary1.MaxY != boundary2.MinY; + } + + /// + /// Checks to see if the other boundary is equal to this boundary. + /// + /// Other boundary. + /// True if the two boundaries are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(in Boundary other) + { + return MinX.Equals(other.MinX) + && MinY.Equals(other.MinY) + && MaxX.Equals(other.MaxX) + && MaxY.Equals(other.MaxY); + } + + + /// + /// Checks to see if the other object is a boundary and if it is, if it is equal to this boundary. + /// + /// Other boundary. + /// True if the two boundaries are equal. + public override bool Equals(object? obj) + { + return obj is Boundary other && Equals(other); + } + + /// + /// Gets the hash code of this boundary. + /// + /// Hash. + public override int GetHashCode() + { + return HashCode.Combine(MinX, MinY, MaxX, MaxY); + } + + /// + /// Creates a new boundary from a circle at the specified location. + /// + /// X coordinate of the circle center. + /// Y coordinate of the circle center. + /// Radius of the circle. + /// New boundary. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Boundary FromCircle(float x, float y, float radius) + { + return new Boundary( + x - radius, + y - radius, + x + radius, + y + radius); + } +} diff --git a/src/DtronixCommon/DtronixCommon.csproj b/src/DtronixCommon/DtronixCommon.csproj index 13132c4..cef6c6d 100644 --- a/src/DtronixCommon/DtronixCommon.csproj +++ b/src/DtronixCommon/DtronixCommon.csproj @@ -2,7 +2,7 @@ net5.0;net6.0 enable - 0.6.3.0 + 0.6.4.0 enable 10 Dtronix