RectD.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. using System;
  2. using PixiEditor.DrawingApi.Core.Surface;
  3. namespace PixiEditor.DrawingApi.Core.Numerics;
  4. public struct RectD : IEquatable<RectD>
  5. {
  6. public static RectD Empty { get; } = new RectD();
  7. private double left;
  8. private double top;
  9. private double right;
  10. private double bottom;
  11. public double Left { readonly get => left; set => left = value; }
  12. public double Top { readonly get => top; set => top = value; }
  13. public double Right { readonly get => right; set => right = value; }
  14. public double Bottom { readonly get => bottom; set => bottom = value; }
  15. public double X { readonly get => left; set => left = value; }
  16. public double Y { readonly get => top; set => top = value; }
  17. public bool HasNaNOrInfinity =>
  18. double.IsNaN(left) || double.IsInfinity(left) ||
  19. double.IsNaN(right) || double.IsInfinity(right) ||
  20. double.IsNaN(top) || double.IsInfinity(top) ||
  21. double.IsNaN(bottom) || double.IsInfinity(bottom);
  22. public VecD Pos
  23. {
  24. readonly get => new VecD(left, top);
  25. set
  26. {
  27. right = (right - left) + value.X;
  28. bottom = (bottom - top) + value.Y;
  29. left = value.X;
  30. top = value.Y;
  31. }
  32. }
  33. public VecD TopLeft
  34. {
  35. readonly get => new VecD(left, top);
  36. set
  37. {
  38. left = value.X;
  39. top = value.Y;
  40. }
  41. }
  42. public VecD TopRight
  43. {
  44. readonly get => new VecD(right, top);
  45. set
  46. {
  47. right = value.X;
  48. top = value.Y;
  49. }
  50. }
  51. public VecD BottomLeft
  52. {
  53. readonly get => new VecD(left, bottom);
  54. set
  55. {
  56. left = value.X;
  57. bottom = value.Y;
  58. }
  59. }
  60. public VecD BottomRight
  61. {
  62. readonly get => new VecD(right, bottom);
  63. set
  64. {
  65. right = value.X;
  66. bottom = value.Y;
  67. }
  68. }
  69. public VecD Size
  70. {
  71. readonly get => new VecD(right - left, bottom - top);
  72. set
  73. {
  74. right = left + value.X;
  75. bottom = top + value.Y;
  76. }
  77. }
  78. public VecD Center { get => new VecD((left + right) / 2.0, (top + bottom) / 2.0); }
  79. public double Width { readonly get => right - left; set => right = left + value; }
  80. public double Height { readonly get => bottom - top; set => bottom = top + value; }
  81. public readonly bool IsZeroArea => left == right || top == bottom;
  82. public readonly bool IsZeroOrNegativeArea => left >= right || top >= bottom;
  83. public RectD()
  84. {
  85. left = 0d;
  86. top = 0d;
  87. right = 0d;
  88. bottom = 0d;
  89. }
  90. public RectD(VecD pos, VecD size)
  91. {
  92. left = pos.X;
  93. top = pos.Y;
  94. right = pos.X + size.X;
  95. bottom = pos.Y + size.Y;
  96. }
  97. public RectD(double x, double y, double width, double height)
  98. {
  99. left = x;
  100. top = y;
  101. right = x + width;
  102. bottom = y + height;
  103. }
  104. public static RectD Create(VecI pos, VecI size)
  105. {
  106. return new RectD(pos.X, pos.Y, size.X, size.Y);
  107. }
  108. public static RectD FromSides(double left, double right, double top, double bottom)
  109. {
  110. return new RectD()
  111. {
  112. Left = left,
  113. Right = right,
  114. Top = top,
  115. Bottom = bottom
  116. };
  117. }
  118. public static RectD FromTwoPoints(VecD point, VecD opposite)
  119. {
  120. return new RectD()
  121. {
  122. Left = Math.Min(point.X, opposite.X),
  123. Right = Math.Max(point.X, opposite.X),
  124. Top = Math.Min(point.Y, opposite.Y),
  125. Bottom = Math.Max(point.Y, opposite.Y)
  126. };
  127. }
  128. public static RectD FromCenterAndSize(VecD center, VecD size)
  129. {
  130. return new RectD()
  131. {
  132. Left = center.X - size.X / 2,
  133. Right = center.X + size.X / 2,
  134. Top = center.Y - size.Y / 2,
  135. Bottom = center.Y + size.Y / 2
  136. };
  137. }
  138. public static RectD CreateAABB(VecD first, VecD second, VecD third, VecD fourth)
  139. {
  140. VecD min = new VecD(
  141. Math.Min(first.X, Math.Min(second.X, Math.Min(third.X, fourth.X))),
  142. Math.Min(first.Y, Math.Min(second.Y, Math.Min(third.Y, fourth.Y))));
  143. VecD max = new VecD(
  144. Math.Max(first.X, Math.Max(second.X, Math.Max(third.X, fourth.X))),
  145. Math.Max(first.Y, Math.Max(second.Y, Math.Max(third.Y, fourth.Y))));
  146. return new RectD(min, max - min);
  147. }
  148. public static RectD? FromPoints(Point[] points)
  149. {
  150. if (points.Length == 0)
  151. return null;
  152. double minX, minY, maxX, maxY;
  153. minY = double.MaxValue;
  154. minX = double.MaxValue;
  155. maxY = double.MinValue;
  156. maxX = double.MinValue;
  157. foreach (Point point in points)
  158. {
  159. if (point.X < minX)
  160. minX = point.X;
  161. if (point.X > maxX)
  162. maxX = point.X;
  163. if (point.Y < minY)
  164. minY = point.Y;
  165. if (point.Y > maxY)
  166. maxY = point.Y;
  167. }
  168. return FromTwoPoints(new VecD(minX, minY), new VecD(maxX, maxY));
  169. }
  170. /// <summary>
  171. /// Converts rectangles with negative dimensions into a normal one
  172. /// </summary>
  173. public readonly RectD Standardize()
  174. {
  175. (double newLeft, double newRight) = left > right ? (right, left) : (left, right);
  176. (double newTop, double newBottom) = top > bottom ? (bottom, top) : (top, bottom);
  177. return new RectD()
  178. {
  179. Left = newLeft,
  180. Right = newRight,
  181. Top = newTop,
  182. Bottom = newBottom
  183. };
  184. }
  185. public readonly RectD ReflectX(double verLineX)
  186. {
  187. return RectD.FromTwoPoints(Pos.ReflectX(verLineX), (Pos + Size).ReflectX(verLineX));
  188. }
  189. public readonly RectD ReflectY(double horLineY)
  190. {
  191. return RectD.FromTwoPoints(Pos.ReflectY(horLineY), (Pos + Size).ReflectY(horLineY));
  192. }
  193. public readonly RectD Offset(VecD offset) => Offset(offset.X, offset.Y);
  194. public readonly RectD Offset(double x, double y)
  195. {
  196. return new RectD()
  197. {
  198. Left = left + x,
  199. Right = right + x,
  200. Top = top + y,
  201. Bottom = bottom + y
  202. };
  203. }
  204. public readonly RectD Inflate(VecD amount) => Inflate(amount.Y, amount.Y);
  205. public readonly RectD Inflate(double x, double y)
  206. {
  207. return new RectD()
  208. {
  209. Left = left - x,
  210. Right = right + x,
  211. Top = top - y,
  212. Bottom = bottom + y,
  213. };
  214. }
  215. public readonly RectD Inflate(double amount)
  216. {
  217. return new RectD()
  218. {
  219. Left = left - amount,
  220. Right = right + amount,
  221. Top = top - amount,
  222. Bottom = bottom + amount,
  223. };
  224. }
  225. public readonly RectD Scale(double multiplier)
  226. {
  227. return new RectD()
  228. {
  229. Left = left * multiplier,
  230. Right = right * multiplier,
  231. Top = top * multiplier,
  232. Bottom = bottom * multiplier
  233. };
  234. }
  235. public readonly RectD Scale(double multiplier, VecD relativeTo)
  236. {
  237. return new RectD()
  238. {
  239. Left = (left - relativeTo.X) * multiplier + relativeTo.X,
  240. Right = (right - relativeTo.X) * multiplier + relativeTo.X,
  241. Top = (top - relativeTo.Y) * multiplier + relativeTo.Y,
  242. Bottom = (bottom - relativeTo.Y) * multiplier + relativeTo.Y
  243. };
  244. }
  245. public readonly RectD Translate(VecD delta)
  246. {
  247. return new RectD()
  248. {
  249. Left = left + delta.X,
  250. Right = right + delta.X,
  251. Top = top + delta.Y,
  252. Bottom = bottom + delta.Y
  253. };
  254. }
  255. /// <summary>
  256. /// Fits passed rectangle into this rectangle while maintaining aspect ratio
  257. /// </summary>
  258. public readonly RectD AspectFit(RectD rect)
  259. {
  260. double widthRatio = Width / rect.Width;
  261. double heightRatio = Height / rect.Height;
  262. if (widthRatio > heightRatio)
  263. {
  264. double newWidth = Height * rect.Width / rect.Height;
  265. double newLeft = left + (Width - newWidth) / 2;
  266. return new RectD(new(newLeft, top), new(newWidth, Height));
  267. }
  268. else
  269. {
  270. double newHeight = Width * rect.Height / rect.Width;
  271. double newTop = top + (Height - newHeight) / 2;
  272. return new RectD(new(left, newTop), new(Width, newHeight));
  273. }
  274. }
  275. public readonly RectD Round()
  276. {
  277. return new RectD()
  278. {
  279. Left = Math.Round(left),
  280. Right = Math.Round(right),
  281. Top = Math.Round(top),
  282. Bottom = Math.Round(bottom)
  283. };
  284. }
  285. public readonly RectD RoundOutwards()
  286. {
  287. return new RectD()
  288. {
  289. Left = Math.Floor(left),
  290. Right = Math.Ceiling(right),
  291. Top = Math.Floor(top),
  292. Bottom = Math.Ceiling(bottom)
  293. };
  294. }
  295. public readonly RectD RoundInwards()
  296. {
  297. return new RectD()
  298. {
  299. Left = Math.Ceiling(left),
  300. Right = Math.Floor(right),
  301. Top = Math.Ceiling(top),
  302. Bottom = Math.Floor(bottom)
  303. };
  304. }
  305. public readonly bool ContainsInclusive(VecD point) => ContainsInclusive(point.X, point.Y);
  306. public readonly bool ContainsInclusive(double x, double y)
  307. {
  308. return x >= left && x <= right && y >= top && y <= bottom;
  309. }
  310. public readonly bool ContainsExclusive(VecD point) => ContainsExclusive(point.X, point.Y);
  311. public readonly bool ContainsExclusive(double x, double y)
  312. {
  313. return x > left && x < right && y > top && y < bottom;
  314. }
  315. public readonly bool IntersectsWithInclusive(RectD rect)
  316. {
  317. return left <= rect.right && right >= rect.left && top <= rect.bottom && bottom >= rect.top;
  318. }
  319. public readonly bool IntersectsWithExclusive(RectD rect)
  320. {
  321. return left < rect.right && right > rect.left && top < rect.bottom && bottom > rect.top;
  322. }
  323. public readonly RectD Intersect(RectD other)
  324. {
  325. double left = Math.Max(this.left, other.left);
  326. double top = Math.Max(this.top, other.top);
  327. double right = Math.Min(this.right, other.right);
  328. double bottom = Math.Min(this.bottom, other.bottom);
  329. if (left >= right || top >= bottom)
  330. return RectD.Empty;
  331. return new RectD()
  332. {
  333. Left = left,
  334. Right = right,
  335. Top = top,
  336. Bottom = bottom
  337. };
  338. }
  339. public readonly RectD Union(RectD other)
  340. {
  341. double left = Math.Min(this.left, other.left);
  342. double top = Math.Min(this.top, other.top);
  343. double right = Math.Max(this.right, other.right);
  344. double bottom = Math.Max(this.bottom, other.bottom);
  345. if (left >= right || top >= bottom)
  346. return RectD.Empty;
  347. return new RectD()
  348. {
  349. Left = left,
  350. Right = right,
  351. Top = top,
  352. Bottom = bottom
  353. };
  354. }
  355. public static explicit operator RectI(RectD rect)
  356. {
  357. return new RectI()
  358. {
  359. Left = (int)rect.left,
  360. Right = (int)rect.right,
  361. Top = (int)rect.top,
  362. Bottom = (int)rect.bottom
  363. };
  364. }
  365. public static explicit operator RectD(RectI rect)
  366. {
  367. return new RectD()
  368. {
  369. Left = rect.Left,
  370. Right = rect.Right,
  371. Top = rect.Top,
  372. Bottom = rect.Bottom
  373. };
  374. }
  375. public static bool operator ==(RectD left, RectD right)
  376. {
  377. return left.left == right.left && left.right == right.right && left.top == right.top && left.bottom == right.bottom;
  378. }
  379. public static bool operator !=(RectD left, RectD right)
  380. {
  381. return !(left.left == right.left && left.right == right.right && left.top == right.top && left.bottom == right.bottom);
  382. }
  383. public readonly override bool Equals(object? obj)
  384. {
  385. return obj is RectD rect && rect.left == left && rect.right == right && rect.top == top && rect.bottom == bottom;
  386. }
  387. public readonly bool Equals(RectD other)
  388. {
  389. return left == other.left && top == other.top && right == other.right && bottom == other.bottom;
  390. }
  391. public override int GetHashCode()
  392. {
  393. return HashCode.Combine(left, top, right, bottom);
  394. }
  395. public override string ToString()
  396. {
  397. return $"{{X: {X}, Y: {Y}, W: {Width}, H: {Height}}}";
  398. }
  399. }