|
| 1 | +using System.Runtime.CompilerServices; |
| 2 | + |
| 3 | +namespace DtronixCommon; |
| 4 | + |
| 5 | +/// <summary> |
| 6 | +/// Boundary represented as [MinX, MinY] (Bottom Left) and [MaxX, MaxY] (Top Right) points. |
| 7 | +/// </summary> |
| 8 | +/// <remarks> |
| 9 | +/// MinY could be down and MaxY up as in cartesian coordinates, or vice vercia as is common |
| 10 | +/// in screen coordinates. Depends upon which system was selected for use. |
| 11 | +/// </remarks> |
| 12 | +public readonly struct Boundary |
| 13 | +{ |
| 14 | + /// <summary> |
| 15 | + /// Minimum X coordinate position. (Left) |
| 16 | + /// </summary> |
| 17 | + public readonly float MinX; |
| 18 | + |
| 19 | + /// <summary> |
| 20 | + /// Minimum Y coordinate position (Bottom/Top) |
| 21 | + /// </summary> |
| 22 | + public readonly float MinY; |
| 23 | + |
| 24 | + /// <summary> |
| 25 | + /// Maximum X coordinate position (Right) |
| 26 | + /// </summary> |
| 27 | + public readonly float MaxX; |
| 28 | + |
| 29 | + /// <summary> |
| 30 | + /// Maximum Y coordinate position (Top/Bottom) |
| 31 | + /// </summary> |
| 32 | + public readonly float MaxY; |
| 33 | + |
| 34 | + /// <summary> |
| 35 | + /// Width of the boundary. |
| 36 | + /// </summary> |
| 37 | + public readonly float Width => MaxX - MinX; |
| 38 | + |
| 39 | + /// <summary> |
| 40 | + /// Height of the boundary. |
| 41 | + /// </summary> |
| 42 | + public readonly float Height => MaxY - MinY; |
| 43 | + |
| 44 | + /// <summary> |
| 45 | + /// Maximum sized boundary. |
| 46 | + /// </summary> |
| 47 | + public static Boundary Max { get; } = new( |
| 48 | + float.MinValue, |
| 49 | + float.MinValue, |
| 50 | + float.MaxValue, |
| 51 | + float.MaxValue); |
| 52 | + |
| 53 | + /// <summary> |
| 54 | + /// Half the maximum size of the boundary. |
| 55 | + /// </summary> |
| 56 | + public static Boundary HalfMax { get; } = new( |
| 57 | + float.MinValue / 2, |
| 58 | + float.MinValue / 2, |
| 59 | + float.MaxValue / 2, |
| 60 | + float.MaxValue / 2); |
| 61 | + |
| 62 | + /// <summary> |
| 63 | + /// Zero sized boundary. |
| 64 | + /// </summary> |
| 65 | + public static Boundary Zero { get; } = new(0, 0, 0, 0); |
| 66 | + |
| 67 | + /// <summary> |
| 68 | + /// Empty boundary. |
| 69 | + /// </summary> |
| 70 | + public static Boundary Empty { get; } = new( |
| 71 | + float.PositiveInfinity, |
| 72 | + float.PositiveInfinity, |
| 73 | + float.NegativeInfinity, |
| 74 | + float.NegativeInfinity); |
| 75 | + |
| 76 | + /// <summary> |
| 77 | + /// Returns true if the boundary has no volume. |
| 78 | + /// </summary> |
| 79 | + public bool IsEmpty => MinY >= MaxY || MinX >= MaxX; |
| 80 | + |
| 81 | + /// <summary> |
| 82 | + /// Creates a boundary with the specified left, bottom, right, top distances from origin. |
| 83 | + /// </summary> |
| 84 | + /// <param name="minX">Left distance from origin.</param> |
| 85 | + /// <param name="minY">Bottom distance from origin.</param> |
| 86 | + /// <param name="maxX">Right distance from origin.</param> |
| 87 | + /// <param name="maxY">Top distance from origin.</param> |
| 88 | + public Boundary(float minX, float minY, float maxX, float maxY) |
| 89 | + { |
| 90 | + MinX = minX; |
| 91 | + MinY = minY; |
| 92 | + MaxX = maxX; |
| 93 | + MaxY = maxY; |
| 94 | + } |
| 95 | + |
| 96 | + /// <summary> |
| 97 | + /// IntersectsWith - Returns true if the Boundary intersects with this Boundary |
| 98 | + /// Returns false otherwise. |
| 99 | + /// Note that if one edge is coincident, this is considered an intersection. |
| 100 | + /// </summary> |
| 101 | + /// <returns> |
| 102 | + /// Returns true if the Boundary intersects with this Boundary |
| 103 | + /// Returns false otherwise. |
| 104 | + /// </returns> |
| 105 | + /// <param name="boundary"> Rect </param> |
| 106 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 107 | + public bool IntersectsWith(in Boundary boundary) |
| 108 | + { |
| 109 | + return boundary.MinX <= MaxX && |
| 110 | + boundary.MaxX >= MinX && |
| 111 | + boundary.MinY <= MaxY && |
| 112 | + boundary.MaxY >= MinY; |
| 113 | + } |
| 114 | + |
| 115 | + /// <summary> |
| 116 | + /// IntersectsWith - Returns true if the Boundary intersects with this Boundary |
| 117 | + /// Returns false otherwise. |
| 118 | + /// Note that if one edge is coincident, this is considered an intersection. |
| 119 | + /// </summary> |
| 120 | + /// <param name="boundary1">First boundary.</param> |
| 121 | + /// <param name="boundary2">Second boundary.</param> |
| 122 | + /// <returns> |
| 123 | + /// Returns true if the Boundary intersects with this Boundary |
| 124 | + /// Returns false otherwise. |
| 125 | + /// </returns> |
| 126 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 127 | + public static bool Intersects(in Boundary boundary1, in Boundary boundary2) |
| 128 | + { |
| 129 | + return boundary1.MinX <= boundary2.MaxX && |
| 130 | + boundary1.MaxX >= boundary2.MinX && |
| 131 | + boundary1.MinY <= boundary2.MaxY && |
| 132 | + boundary1.MaxY >= boundary2.MinY; |
| 133 | + } |
| 134 | + |
| 135 | + /// <summary> |
| 136 | + /// Checks if a point is contained by the boundary. |
| 137 | + /// </summary> |
| 138 | + /// <param name="x">X coordinate.</param> |
| 139 | + /// <param name="y">Y coordinate.</param> |
| 140 | + /// <returns>True if the point is contained, false otherwise.</returns> |
| 141 | + public bool Contains(float x, float y) |
| 142 | + { |
| 143 | + return x >= MinX |
| 144 | + && x <= MaxX |
| 145 | + && y >= MinY |
| 146 | + && y <= MaxY; |
| 147 | + } |
| 148 | + |
| 149 | + |
| 150 | + /// <summary> |
| 151 | + /// Takes the current boundary and offsets it by the specified X & Y vectors. |
| 152 | + /// </summary> |
| 153 | + /// <param name="x">X vector offset.</param> |
| 154 | + /// <param name="y">Y vector offset.</param> |
| 155 | + /// <returns>New offset boundary.</returns> |
| 156 | + public Boundary CreateOffset(in float x, in float y) |
| 157 | + { |
| 158 | + return new Boundary( |
| 159 | + (float)(MinX + x), |
| 160 | + (float)(MinY + y), |
| 161 | + (float)(MaxX + x), |
| 162 | + (float)(MaxY + y)); |
| 163 | + } |
| 164 | + |
| 165 | + /// <summary> |
| 166 | + /// Creates a union between two boundaries. |
| 167 | + /// </summary> |
| 168 | + /// <param name="boundary">second boundary to union with.</param> |
| 169 | + /// <returns>new union boundary.</returns> |
| 170 | + public Boundary Union(in Boundary boundary) |
| 171 | + { |
| 172 | + if (IsEmpty) |
| 173 | + return boundary; |
| 174 | + |
| 175 | + if (boundary.IsEmpty) |
| 176 | + return this; |
| 177 | + |
| 178 | + return new Boundary( |
| 179 | + Math.Min(MinX, boundary.MinX), |
| 180 | + Math.Min(MinY, boundary.MinY), |
| 181 | + Math.Max(MaxX, boundary.MaxX), |
| 182 | + Math.Max(MaxY, boundary.MaxY)); |
| 183 | + } |
| 184 | + |
| 185 | + /// <summary> |
| 186 | + /// Gets a rough approximation of the hypotenuse of the rectangle. |
| 187 | + /// Uses different algorithms based upon the passed integer. |
| 188 | + /// </summary> |
| 189 | + /// <param name="algorithm">0 - 3. Determines algorithm used. 0 is the fastest, 3 is the slowest but most accurate.</param> |
| 190 | + /// <returns>Approximate length</returns> |
| 191 | + /// <remarks> |
| 192 | + /// https://stackoverflow.com/a/26607206 |
| 193 | + /// All these assume 0 ≤ a ≤ b. |
| 194 | + /// 0. h = b + 0.337 * a // less sorting order of the a & b variables; |
| 195 | + /// 1. h = b + 0.337 * a // max error ≈ 5.5 % |
| 196 | + /// 2. h = max(b, 0.918 * (b + (a >> 1))) // max error ≈ 2.6 % |
| 197 | + /// 3. h = b + 0.428 * a* a / b // max error ≈ 1.04 % |
| 198 | + /// </remarks> |
| 199 | + public float GetHypotenuseApproximate(int algorithm) |
| 200 | + { |
| 201 | + var a = MathF.Abs(MaxX - MinX); |
| 202 | + var b = MathF.Abs(MaxY - MinY); |
| 203 | + |
| 204 | + if (algorithm == 0) |
| 205 | + return b + 0.337f * a; |
| 206 | + |
| 207 | + // Transpose variables to ensure "b" is larger than A |
| 208 | + if (a > b) |
| 209 | + (a, b) = (b, a); |
| 210 | + |
| 211 | + switch (algorithm) |
| 212 | + { |
| 213 | + case 1: |
| 214 | + return b + 0.337f * a; |
| 215 | + case 2: |
| 216 | + return MathF.Max(b, 0.918f * (b + (a / 2f))); |
| 217 | + case 3: |
| 218 | + return b + 0.428f * a * a / b; |
| 219 | + default: |
| 220 | + throw new ArgumentException("Must select algorithm 0 through 3", nameof(algorithm)); |
| 221 | + } |
| 222 | + |
| 223 | + } |
| 224 | + |
| 225 | + /// <summary> |
| 226 | + /// Rotates the boundary from its center point by the specified number of degrees. |
| 227 | + /// </summary> |
| 228 | + /// <param name="degrees">Degrees to rotate.</param> |
| 229 | + /// <returns>New rotated boundary.</returns> |
| 230 | + public Boundary Rotate(float degrees) |
| 231 | + { |
| 232 | + // Center position of the rectangle. |
| 233 | + //private const m_PosX : Number, m_PosY : Number; |
| 234 | + |
| 235 | + // Rectangle orientation, in radians. |
| 236 | + var m_Orientation = degrees * MathF.PI / 180.0f; |
| 237 | + // Half-width and half-height of the rectangle. |
| 238 | + var m_HalfSizeX = Width / 2; |
| 239 | + var m_HalfSizeY = Height / 2; |
| 240 | + var m_PosX = MinX + m_HalfSizeX; |
| 241 | + var m_PosY = MinY + m_HalfSizeY; |
| 242 | + |
| 243 | + // corner_1 is right-top corner of unrotated rectangle, relative to m_Pos. |
| 244 | + // corner_2 is right-bottom corner of unrotated rectangle, relative to m_Pos. |
| 245 | + var corner_1_x = m_HalfSizeX; |
| 246 | + var corner_2_x = m_HalfSizeX; |
| 247 | + var corner_1_y = -m_HalfSizeY; |
| 248 | + var corner_2_y = m_HalfSizeY; |
| 249 | + |
| 250 | + var sin_o = MathF.Sin(m_Orientation); |
| 251 | + var cos_o = MathF.Cos(m_Orientation); |
| 252 | + |
| 253 | + // xformed_corner_1, xformed_corner_2 are points corner_1, corner_2 rotated by angle m_Orientation. |
| 254 | + var xformed_corner_1_x = corner_1_x * cos_o - corner_1_y * sin_o; |
| 255 | + var xformed_corner_1_y = corner_1_x * sin_o + corner_1_y * cos_o; |
| 256 | + var xformed_corner_2_x = corner_2_x * cos_o - corner_2_y * sin_o; |
| 257 | + var xformed_corner_2_y = corner_2_x * sin_o + corner_2_y * cos_o; |
| 258 | + |
| 259 | + // ex, ey are extents (half-sizes) of the final AABB. |
| 260 | + var ex = MathF.Max(MathF.Abs(xformed_corner_1_x), MathF.Abs(xformed_corner_2_x)); |
| 261 | + var ey = MathF.Max(MathF.Abs(xformed_corner_1_y), MathF.Abs(xformed_corner_2_y)); |
| 262 | + return new Boundary(m_PosX - ex, m_PosY - ey, m_PosX + ex, m_PosY + ey); |
| 263 | + //var aabb_min_x = m_PosX - ex; |
| 264 | + //var aabb_max_x = m_PosX + ex; |
| 265 | + //var aabb_min_y = m_PosY - ey; |
| 266 | + //var aabb_max_y = m_PosY + ey; |
| 267 | + } |
| 268 | + |
| 269 | + /// <summary> |
| 270 | + /// Unions two boundaries. |
| 271 | + /// </summary> |
| 272 | + /// <param name="boundary1">First boundary.</param> |
| 273 | + /// <param name="boundary2">Second boundary.</param> |
| 274 | + /// <returns>New union boundary.</returns> |
| 275 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 276 | + public static Boundary Union(in Boundary boundary1, in Boundary boundary2) |
| 277 | + { |
| 278 | + return new Boundary( |
| 279 | + boundary1.MinX < boundary2.MinX ? boundary1.MinX : boundary2.MinX, |
| 280 | + boundary1.MinY < boundary2.MinY ? boundary1.MinY : boundary2.MinY, |
| 281 | + boundary1.MaxX > boundary2.MaxX ? boundary1.MaxX : boundary2.MaxX, |
| 282 | + boundary1.MaxY > boundary2.MaxY ? boundary1.MaxY : boundary2.MaxY); |
| 283 | + } |
| 284 | + |
| 285 | + /// <summary> |
| 286 | + /// Returns a String which represents the boundary instance. |
| 287 | + /// </summary> |
| 288 | + /// <returns>Value</returns> |
| 289 | + public override string ToString() |
| 290 | + { |
| 291 | + return $"MinX:{MinX:F}; MinY:{MinY:F}; MaxX:{MaxX:F}; MaxY:{MaxY:F}; Width: {MaxX - MinX:F}; Height: {MaxY - MinY:F}"; |
| 292 | + } |
| 293 | + |
| 294 | + |
| 295 | + /// <summary> |
| 296 | + /// Checks to see if the the two boundaries are equal. |
| 297 | + /// </summary> |
| 298 | + /// <param name="boundary1">First boundary.</param> |
| 299 | + /// <param name="boundary2">Second boundary.</param> |
| 300 | + /// <returns>True if the two boundaries are equal.</returns> |
| 301 | + public static bool operator ==(in Boundary boundary1, in Boundary boundary2) |
| 302 | + { |
| 303 | + return boundary1.Equals(boundary2); |
| 304 | + } |
| 305 | + |
| 306 | + /// <summary> |
| 307 | + /// Checks to see if the the two boundaries are equal. |
| 308 | + /// </summary> |
| 309 | + /// <param name="boundary1">First boundary.</param> |
| 310 | + /// <param name="boundary2">Second boundary.</param> |
| 311 | + /// <returns>True if the two boundaries are not equal.</returns> |
| 312 | + public static bool operator !=(in Boundary boundary1, in Boundary boundary2) |
| 313 | + { |
| 314 | + return boundary1.MinX != boundary2.MaxX || |
| 315 | + boundary1.MaxX != boundary2.MinX || |
| 316 | + boundary1.MinY != boundary2.MaxY || |
| 317 | + boundary1.MaxY != boundary2.MinY; |
| 318 | + } |
| 319 | + |
| 320 | + /// <summary> |
| 321 | + /// Checks to see if the other boundary is equal to this boundary. |
| 322 | + /// </summary> |
| 323 | + /// <param name="other">Other boundary.</param> |
| 324 | + /// <returns>True if the two boundaries are equal.</returns> |
| 325 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 326 | + public bool Equals(in Boundary other) |
| 327 | + { |
| 328 | + return MinX.Equals(other.MinX) |
| 329 | + && MinY.Equals(other.MinY) |
| 330 | + && MaxX.Equals(other.MaxX) |
| 331 | + && MaxY.Equals(other.MaxY); |
| 332 | + } |
| 333 | + |
| 334 | + |
| 335 | + /// <summary> |
| 336 | + /// Checks to see if the other object is a boundary and if it is, if it is equal to this boundary. |
| 337 | + /// </summary> |
| 338 | + /// <param name="other">Other boundary.</param> |
| 339 | + /// <returns>True if the two boundaries are equal.</returns> |
| 340 | + public override bool Equals(object? obj) |
| 341 | + { |
| 342 | + return obj is Boundary other && Equals(other); |
| 343 | + } |
| 344 | + |
| 345 | + /// <summary> |
| 346 | + /// Gets the hash code of this boundary. |
| 347 | + /// </summary> |
| 348 | + /// <returns>Hash.</returns> |
| 349 | + public override int GetHashCode() |
| 350 | + { |
| 351 | + return HashCode.Combine(MinX, MinY, MaxX, MaxY); |
| 352 | + } |
| 353 | + |
| 354 | + /// <summary> |
| 355 | + /// Creates a new boundary from a circle at the specified location. |
| 356 | + /// </summary> |
| 357 | + /// <param name="x">X coordinate of the circle center.</param> |
| 358 | + /// <param name="y">Y coordinate of the circle center.</param> |
| 359 | + /// <param name="radius">Radius of the circle.</param> |
| 360 | + /// <returns>New boundary.</returns> |
| 361 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 362 | + public static Boundary FromCircle(float x, float y, float radius) |
| 363 | + { |
| 364 | + return new Boundary( |
| 365 | + x - radius, |
| 366 | + y - radius, |
| 367 | + x + radius, |
| 368 | + y + radius); |
| 369 | + } |
| 370 | +} |
0 commit comments