CGGeometry+LOTAdditions.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. #import "CGGeometry+LOTAdditions.h"
  2. const CGSize CGSizeMax = {CGFLOAT_MAX, CGFLOAT_MAX};
  3. //
  4. // Core Graphics Geometry Additions
  5. //
  6. // CGRectIntegral returns a rectangle with the smallest integer values for its origin and size that contains the source rectangle.
  7. // For a rect with .origin={5, 5.5}, .size=(10, 10), it will return .origin={5,5}, .size={10, 11};
  8. // LOT_RectIntegral will return {5,5}, {10, 10}.
  9. CGRect LOT_RectIntegral(CGRect rect) {
  10. rect.origin = CGPointMake(rintf(rect.origin.x), rintf(rect.origin.y));
  11. rect.size = CGSizeMake(ceilf(rect.size.width), ceil(rect.size.height));
  12. return rect;
  13. }
  14. //
  15. // Centering
  16. // Returns a rectangle of the given size, centered at a point
  17. CGRect LOT_RectCenteredAtPoint(CGPoint center, CGSize size, BOOL integral) {
  18. CGRect result;
  19. result.origin.x = center.x - 0.5f * size.width;
  20. result.origin.y = center.y - 0.5f * size.height;
  21. result.size = size;
  22. if (integral) { result = LOT_RectIntegral(result); }
  23. return result;
  24. }
  25. // Returns the center point of a CGRect
  26. CGPoint LOT_RectGetCenterPoint(CGRect rect) {
  27. return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
  28. }
  29. //
  30. // Insetting
  31. // Inset the rectangle on a single edge
  32. CGRect LOT_RectInsetLeft(CGRect rect, CGFloat inset) {
  33. rect.origin.x += inset;
  34. rect.size.width -= inset;
  35. return rect;
  36. }
  37. CGRect LOT_RectInsetRight(CGRect rect, CGFloat inset) {
  38. rect.size.width -= inset;
  39. return rect;
  40. }
  41. CGRect LOT_RectInsetTop(CGRect rect, CGFloat inset) {
  42. rect.origin.y += inset;
  43. rect.size.height -= inset;
  44. return rect;
  45. }
  46. CGRect LOT_RectInsetBottom(CGRect rect, CGFloat inset) {
  47. rect.size.height -= inset;
  48. return rect;
  49. }
  50. // Inset the rectangle on two edges
  51. CGRect LOT_RectInsetHorizontal(CGRect rect, CGFloat leftInset, CGFloat rightInset) {
  52. rect.origin.x += leftInset;
  53. rect.size.width -= (leftInset + rightInset);
  54. return rect;
  55. }
  56. CGRect LOT_RectInsetVertical(CGRect rect, CGFloat topInset, CGFloat bottomInset) {
  57. rect.origin.y += topInset;
  58. rect.size.height -= (topInset + bottomInset);
  59. return rect;
  60. }
  61. // Inset the rectangle on all edges
  62. CGRect LOT_RectInsetAll(CGRect rect, CGFloat leftInset, CGFloat rightInset, CGFloat topInset, CGFloat bottomInset) {
  63. rect.origin.x += leftInset;
  64. rect.origin.y += topInset;
  65. rect.size.width -= (leftInset + rightInset);
  66. rect.size.height -= (topInset + bottomInset);
  67. return rect;
  68. }
  69. //
  70. // Framing
  71. // Returns a rectangle of size framed in the center of the given rectangle
  72. CGRect LOT_RectFramedCenteredInRect(CGRect rect, CGSize size, BOOL integral) {
  73. CGRect result;
  74. result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
  75. result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
  76. result.size = size;
  77. if (integral) { result = LOT_RectIntegral(result); }
  78. return result;
  79. }
  80. // Returns a rectangle of size framed in the given rectangle and inset
  81. CGRect LOT_RectFramedLeftInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
  82. CGRect result;
  83. result.origin.x = rect.origin.x + inset;
  84. result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
  85. result.size = size;
  86. if (integral) { result = LOT_RectIntegral(result); }
  87. return result;
  88. }
  89. CGRect LOT_RectFramedRightInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
  90. CGRect result;
  91. result.origin.x = rect.origin.x + rect.size.width - size.width - inset;
  92. result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
  93. result.size = size;
  94. if (integral) { result = LOT_RectIntegral(result); }
  95. return result;
  96. }
  97. CGRect LOT_RectFramedTopInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
  98. CGRect result;
  99. result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
  100. result.origin.y = rect.origin.y + inset;
  101. result.size = size;
  102. if (integral) { result = LOT_RectIntegral(result); }
  103. return result;
  104. }
  105. CGRect LOT_RectFramedBottomInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral) {
  106. CGRect result;
  107. result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
  108. result.origin.y = rect.origin.y + rect.size.height - size.height - inset;
  109. result.size = size;
  110. if (integral) { result = LOT_RectIntegral(result); }
  111. return result;
  112. }
  113. CGRect LOT_RectFramedTopLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
  114. CGRect result;
  115. result.origin.x = rect.origin.x + insetWidth;
  116. result.origin.y = rect.origin.y + insetHeight;
  117. result.size = size;
  118. if (integral) { result = LOT_RectIntegral(result); }
  119. return result;
  120. }
  121. CGRect LOT_RectFramedTopRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
  122. CGRect result;
  123. result.origin.x = rect.origin.x + rect.size.width - size.width - insetWidth;
  124. result.origin.y = rect.origin.y + insetHeight;
  125. result.size = size;
  126. if (integral) { result = LOT_RectIntegral(result); }
  127. return result;
  128. }
  129. CGRect LOT_RectFramedBottomLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
  130. CGRect result;
  131. result.origin.x = rect.origin.x + insetWidth;
  132. result.origin.y = rect.origin.y + rect.size.height - size.height - insetHeight;
  133. result.size = size;
  134. if (integral) { result = LOT_RectIntegral(result); }
  135. return result;
  136. }
  137. CGRect LOT_RectFramedBottomRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral) {
  138. CGRect result;
  139. result.origin.x = rect.origin.x + rect.size.width - size.width - insetWidth;
  140. result.origin.y = rect.origin.y + rect.size.height - size.height - insetHeight;
  141. result.size = size;
  142. if (integral) { result = LOT_RectIntegral(result); }
  143. return result;
  144. }
  145. // Returns a rectangle of size attached to the given rectangle
  146. CGRect LOT_RectAttachedLeftToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
  147. CGRect result;
  148. result.origin.x = rect.origin.x - size.width - margin;
  149. result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
  150. result.size = size;
  151. if (integral) { result = LOT_RectIntegral(result); }
  152. return result;
  153. }
  154. CGRect LOT_RectAttachedRightToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
  155. CGRect result;
  156. result.origin.x = rect.origin.x + rect.size.width + margin;
  157. result.origin.y = rect.origin.y + rintf(0.5f * (rect.size.height - size.height));
  158. result.size = size;
  159. if (integral) { result = LOT_RectIntegral(result); }
  160. return result;
  161. }
  162. CGRect LOT_RectAttachedTopToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
  163. CGRect result;
  164. result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
  165. result.origin.y = rect.origin.y - size.height - margin;
  166. result.size = size;
  167. if (integral) { result = LOT_RectIntegral(result); }
  168. return result;
  169. }
  170. CGRect LOT_RectAttachedTopLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
  171. CGRect result;
  172. result.origin.x = rect.origin.x + marginWidth;
  173. result.origin.y = rect.origin.y - size.height - marginHeight;
  174. result.size = size;
  175. if (integral) { result = LOT_RectIntegral(result); }
  176. return result;
  177. }
  178. CGRect LOT_RectAttachedTopRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
  179. CGRect result;
  180. result.origin.x = rect.origin.x + rect.size.width - size.width - marginWidth;
  181. result.origin.y = rect.origin.y - rect.size.height - marginHeight;
  182. result.size = size;
  183. if (integral) { result = LOT_RectIntegral(result); }
  184. return result;
  185. }
  186. CGRect LOT_RectAttachedBottomToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral) {
  187. CGRect result;
  188. result.origin.x = rect.origin.x + rintf(0.5f * (rect.size.width - size.width));
  189. result.origin.y = rect.origin.y + rect.size.height + margin;
  190. result.size = size;
  191. if (integral) { result = LOT_RectIntegral(result); }
  192. return result;
  193. }
  194. CGRect LOT_RectAttachedBottomLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
  195. CGRect result;
  196. result.origin.x = rect.origin.x + marginWidth;
  197. result.origin.y = rect.origin.y + rect.size.height + marginHeight;
  198. result.size = size;
  199. if (integral) { result = LOT_RectIntegral(result); }
  200. return result;
  201. }
  202. CGRect LOT_RectAttachedBottomRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral) {
  203. CGRect result;
  204. result.origin.x = rect.origin.x + rect.size.width - size.width - marginWidth;
  205. result.origin.y = rect.origin.y + rect.size.height + marginHeight;
  206. result.size = size;
  207. if (integral) { result = LOT_RectIntegral(result); }
  208. return result;
  209. }
  210. // Divides a rect into sections and returns the section at specified index
  211. CGRect LOT_RectDividedSection(CGRect rect, NSInteger sections, NSInteger index, CGRectEdge fromEdge) {
  212. if (sections == 0) {
  213. return CGRectZero;
  214. }
  215. CGRect r = rect;
  216. if (fromEdge == CGRectMaxXEdge || fromEdge == CGRectMinXEdge) {
  217. r.size.width = rect.size.width / sections;
  218. r.origin.x += r.size.width * index;
  219. } else {
  220. r.size.height = rect.size.height / sections;
  221. r.origin.y += r.size.height * index;
  222. }
  223. return r;
  224. }
  225. CGRect LOT_RectAddRect(CGRect rect, CGRect other) {
  226. return CGRectMake(rect.origin.x + other.origin.x, rect.origin.y + other.origin.y,
  227. rect.size.width + other.size.width, rect.size.height + other.size.height);
  228. }
  229. CGRect LOT_RectAddPoint(CGRect rect, CGPoint point) {
  230. return CGRectMake(rect.origin.x + point.x, rect.origin.y + point.y,
  231. rect.size.width, rect.size.height);
  232. }
  233. CGRect LOT_RectAddSize(CGRect rect, CGSize size) {
  234. return CGRectMake(rect.origin.x, rect.origin.y,
  235. rect.size.width + size.width, rect.size.height + size.height);
  236. }
  237. CGRect LOT_RectBounded(CGRect rect) {
  238. CGRect returnRect = rect;
  239. returnRect.origin = CGPointZero;
  240. return returnRect;
  241. }
  242. CGPoint LOT_PointAddedToPoint(CGPoint point1, CGPoint point2) {
  243. CGPoint returnPoint = point1;
  244. returnPoint.x += point2.x;
  245. returnPoint.y += point2.y;
  246. return returnPoint;
  247. }
  248. CGRect LOT_RectSetHeight(CGRect rect, CGFloat height) {
  249. return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, height);
  250. }
  251. CGFloat LOT_DegreesToRadians(CGFloat degrees) {
  252. return degrees * M_PI / 180;
  253. }
  254. CGFloat LOT_PointDistanceFromPoint(CGPoint point1, CGPoint point2) {
  255. CGFloat xDist = (point2.x - point1.x);
  256. CGFloat yDist = (point2.y - point1.y);
  257. CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
  258. return distance;
  259. }
  260. CGFloat LOT_RemapValue(CGFloat value, CGFloat low1, CGFloat high1, CGFloat low2, CGFloat high2 ) {
  261. return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
  262. }
  263. CGPoint LOT_PointByLerpingPoints(CGPoint point1, CGPoint point2, CGFloat value) {
  264. CGFloat xDiff = point2.x - point1.x;
  265. CGFloat yDiff = point2.y - point1.y;
  266. CGPoint transposed = CGPointMake(fabs(xDiff), fabs(yDiff));
  267. CGPoint returnPoint;
  268. if (xDiff == 0 || yDiff == 0) {
  269. returnPoint.x = xDiff == 0 ? point1.x : LOT_RemapValue(value, 0, 1, point1.x, point2.x);
  270. returnPoint.y = yDiff == 0 ? point1.y : LOT_RemapValue(value, 0, 1, point1.y, point2.y);
  271. } else {
  272. CGFloat rx = transposed.x / transposed.y;
  273. CGFloat yLerp = LOT_RemapValue(value, 0, 1, 0, transposed.y);
  274. CGFloat xLerp = yLerp * rx;
  275. CGPoint interpolatedPoint = CGPointMake(point2.x < point1.x ? xLerp * -1 : xLerp,
  276. point2.y < point1.y ? yLerp * -1 : yLerp);
  277. returnPoint = LOT_PointAddedToPoint(point1, interpolatedPoint);
  278. }
  279. return returnPoint;
  280. }
  281. CGPoint LOT_PointInLine(CGPoint A, CGPoint B, CGFloat T) {
  282. CGPoint C;
  283. C.x = A.x - ((A.x - B.x) * T);
  284. C.y = A.y - ((A.y - B.y) * T);
  285. return C;
  286. }
  287. CGFloat LOT_CubicBezierGetY(CGPoint cp1, CGPoint cp2, CGFloat T) {
  288. // (1-x)^3 * y0 + 3*(1-x)^2 * x * y1 + 3*(1-x) * x^2 * y2 + x^3 * y3
  289. return 3 * powf(1.f - T, 2.f) * T * cp1.y + 3.f * (1.f - T) * powf(T, 2.f) * cp2.y + powf(T, 3.f);
  290. }
  291. CGPoint LOT_PointInCubicCurve(CGPoint start, CGPoint cp1, CGPoint cp2, CGPoint end, CGFloat T) {
  292. CGPoint A = LOT_PointInLine(start, cp1, T);
  293. CGPoint B = LOT_PointInLine(cp1, cp2, T);
  294. CGPoint C = LOT_PointInLine(cp2, end, T);
  295. CGPoint D = LOT_PointInLine(A, B, T);
  296. CGPoint E = LOT_PointInLine(B, C, T);
  297. CGPoint F = LOT_PointInLine(D, E, T);
  298. return F;
  299. }
  300. CGFloat LOT_SolveCubic(CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
  301. if (a == 0) return LOT_SolveQuadratic(b, c, d);
  302. if (d == 0) return 0;
  303. b /= a;
  304. c /= a;
  305. d /= a;
  306. CGFloat q = (3.0 * c - LOT_Squared(b)) / 9.0;
  307. CGFloat r = (-27.0 * d + b * (9.0 * c - 2.0 * LOT_Squared(b))) / 54.0;
  308. CGFloat disc = LOT_Cubed(q) + LOT_Squared(r);
  309. CGFloat term1 = b / 3.0;
  310. if (disc > 0) {
  311. double s = r + sqrtf(disc);
  312. s = (s < 0) ? - LOT_CubicRoot(-s) : LOT_CubicRoot(s);
  313. double t = r - sqrtf(disc);
  314. t = (t < 0) ? - LOT_CubicRoot(-t) : LOT_CubicRoot(t);
  315. double result = -term1 + s + t;
  316. if (result >= 0 && result <= 1) return result;
  317. } else if (disc == 0) {
  318. double r13 = (r < 0) ? - LOT_CubicRoot(-r) : LOT_CubicRoot(r);
  319. double result = -term1 + 2.0 * r13;
  320. if (result >= 0 && result <= 1) return result;
  321. result = -(r13 + term1);
  322. if (result >= 0 && result <= 1) return result;
  323. } else {
  324. q = -q;
  325. double dum1 = q * q * q;
  326. dum1 = acosf(r / sqrtf(dum1));
  327. double r13 = 2.0 * sqrtf(q);
  328. double result = -term1 + r13 * cos(dum1 / 3.0);
  329. if (result >= 0 && result <= 1) return result;
  330. result = -term1 + r13 * cos((dum1 + 2.0 * M_PI) / 3.0);
  331. if (result >= 0 && result <= 1) return result;
  332. result = -term1 + r13 * cos((dum1 + 4.0 * M_PI) / 3.0);
  333. if (result >= 0 && result <= 1) return result;
  334. }
  335. return -1;
  336. }
  337. CGFloat LOT_SolveQuadratic(CGFloat a, CGFloat b, CGFloat c) {
  338. CGFloat result = (-b + sqrtf(LOT_Squared(b) - 4 * a * c)) / (2 * a);
  339. if (result >= 0 && result <= 1) return result;
  340. result = (-b - sqrtf(LOT_Squared(b) - 4 * a * c)) / (2 * a);
  341. if (result >= 0 && result <= 1) return result;
  342. return -1;
  343. }
  344. CGFloat LOT_Squared(CGFloat f) { return f * f; }
  345. CGFloat LOT_Cubed(CGFloat f) { return f * f * f; }
  346. CGFloat LOT_CubicRoot(CGFloat f) { return powf(f, 1.0 / 3.0); }
  347. CGFloat LOT_CubicBezierInterpolate(CGPoint P0, CGPoint P1, CGPoint P2, CGPoint P3, CGFloat x) {
  348. CGFloat t;
  349. if (x == P0.x) {
  350. // Handle corner cases explicitly to prevent rounding errors
  351. t = 0;
  352. } else if (x == P3.x) {
  353. t = 1;
  354. } else {
  355. // Calculate t
  356. CGFloat a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x;
  357. CGFloat b = 3 * P0.x - 6 * P1.x + 3 * P2.x;
  358. CGFloat c = -3 * P0.x + 3 * P1.x;
  359. CGFloat d = P0.x - x;
  360. CGFloat tTemp = LOT_SolveCubic(a, b, c, d);
  361. if (tTemp == -1) return -1;
  362. t = tTemp;
  363. }
  364. // Calculate y from t
  365. return LOT_Cubed(1 - t) * P0.y + 3 * t * LOT_Squared(1 - t) * P1.y + 3 * LOT_Squared(t) * (1 - t) * P2.y + LOT_Cubed(t) * P3.y;
  366. }
  367. CGFloat LOT_CubicLengthWithPrecision(CGPoint fromPoint, CGPoint toPoint, CGPoint controlPoint1, CGPoint controlPoint2, CGFloat iterations) {
  368. CGFloat length = 0;
  369. CGPoint previousPoint = fromPoint;
  370. iterations = ceilf(iterations);
  371. for (int i = 1; i <= iterations; ++i) {
  372. float s = (float)i / iterations;
  373. CGPoint p = LOT_PointInCubicCurve(fromPoint, controlPoint1, controlPoint2, toPoint, s);
  374. length += LOT_PointDistanceFromPoint(previousPoint, p);
  375. previousPoint = p;
  376. }
  377. return length;
  378. }
  379. CGFloat LOT_CubicLength(CGPoint fromPoint, CGPoint toPoint, CGPoint controlPoint1, CGPoint controlPoint2) {
  380. return LOT_CubicLengthWithPrecision(fromPoint, toPoint, controlPoint1, controlPoint2, 20);
  381. }
  382. BOOL LOT_CGPointIsZero(CGPoint point) {
  383. return CGPointEqualToPoint(point, CGPointZero);
  384. }