From c1a1fa1f7fe0d12967e14264896040bea1920d4d Mon Sep 17 00:00:00 2001 From: BobLd Date: Sun, 15 Mar 2020 15:04:03 +0000 Subject: [PATCH] - fix minimum area rectangle algo and make it public - add tests - tidy up code --- .../Geometry/PdfPointTests.cs | 459 +++++++++++++++++- .../Geometry/GeometryExtensions.cs | 121 +++-- 2 files changed, 517 insertions(+), 63 deletions(-) diff --git a/src/UglyToad.PdfPig.Tests/Geometry/PdfPointTests.cs b/src/UglyToad.PdfPig.Tests/Geometry/PdfPointTests.cs index 698884ad..a43c6fb1 100644 --- a/src/UglyToad.PdfPig.Tests/Geometry/PdfPointTests.cs +++ b/src/UglyToad.PdfPig.Tests/Geometry/PdfPointTests.cs @@ -8,6 +8,10 @@ public class PdfPointTests { + private static readonly DoubleComparer DoubleComparer = new DoubleComparer(3); + private static readonly DoubleComparer PreciseDoubleComparer = new DoubleComparer(6); + private static readonly PointComparer PointComparer = new PointComparer(DoubleComparer); + #region data public static IEnumerable GrahamScanData => new[] { @@ -86,6 +90,442 @@ new PdfPoint(286.54125213, 590.83326057), new PdfPoint(30.50024994, 37.34818875) } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(744.3155504888274, 444.2270145755189), + new PdfPoint(569.808503219366, 795.0780697464061), + new PdfPoint(979.4238467088927, 747.7769460740175), + new PdfPoint(263.9656530143659, 534.1164778047929), + new PdfPoint(700.0199185779105, 59.67088755550021), + new PdfPoint(350.4405052982569, 201.5075034147189), + new PdfPoint(951.4434324059339, 276.4851544966993), + new PdfPoint(221.2620795357345, 889.4493759697666), + new PdfPoint(26.40411497910822, 836.0708485933704), + new PdfPoint(967.4534816241033, 692.8854748787957), + }, + new PdfPoint[] + { + new PdfPoint(979.4238467088927, 747.7769460740175), + new PdfPoint(700.0199185779105, 59.67088755550021), + new PdfPoint(350.4405052982569, 201.5075034147189), + new PdfPoint(951.4434324059339, 276.4851544966993), + new PdfPoint(221.2620795357345, 889.4493759697666), + new PdfPoint(26.40411497910822, 836.0708485933704), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(120.21183721137064, 840.2513067067979), + new PdfPoint(415.52861888639114, 204.0116313873851), + new PdfPoint(415.9664683980775, 832.443368995516), + new PdfPoint(277.74879682552734, 502.12519702578516), + new PdfPoint(395.35090532250103, 384.0997616867551), + new PdfPoint(26.98104432607229, 654.2675428223525), + new PdfPoint(507.0471750863688, 822.9947002774292), + }, + new PdfPoint[] + { + new PdfPoint(120.21183721137064, 840.2513067067979), + new PdfPoint(415.52861888639114, 204.0116313873851), + new PdfPoint(415.9664683980775, 832.443368995516), + new PdfPoint(26.98104432607229, 654.2675428223525), + new PdfPoint(507.0471750863688, 822.9947002774292), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(101.52924170788602, 157.72180559576165), + new PdfPoint(89.13969305519842, 437.7399878683488), + new PdfPoint(963.8109736953475, 34.8258382181843), + new PdfPoint(323.44164253397423, 760.0469522615875), + new PdfPoint(553.8798287152455, 601.37868940649), + new PdfPoint(24.169651959285442, 205.03651103426347), + new PdfPoint(598.6426557134453, 973.1839362109574), + new PdfPoint(404.2148279309453, 642.4272597428419), + new PdfPoint(425.99787946258795, 235.338843056625), + new PdfPoint(733.837562543066, 304.97834592908157), + new PdfPoint(253.06825516770635, 639.1969849161718), + new PdfPoint(702.043917830561, 241.6302720665372), + new PdfPoint(43.233323888316356, 214.65896998517496), + new PdfPoint(192.04610854054195, 609.086536570487), + new PdfPoint(93.436372304046, 130.4501167748684), + }, + new PdfPoint[] + { + new PdfPoint(89.13969305519842, 437.7399878683488), + new PdfPoint(963.8109736953475, 34.8258382181843), + new PdfPoint(323.44164253397423, 760.0469522615875), + new PdfPoint(24.169651959285442, 205.03651103426347), + new PdfPoint(598.6426557134453, 973.1839362109574), + new PdfPoint(192.04610854054195, 609.086536570487), + new PdfPoint(93.436372304046, 130.4501167748684), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(440.61717693569, 109.0846453611043), + new PdfPoint(306.3112668458863, 236.31457456363236), + new PdfPoint(520.2265439015014, 849.1406402603602), + new PdfPoint(991.7951837792907, 716.5936071305995), + new PdfPoint(210.02069783227006, 396.9897189605061), + new PdfPoint(844.0889978638492, 40.8614368982706), + new PdfPoint(105.18279221346272, 598.2338764791806), + new PdfPoint(481.07852218200696, 791.2951914667244), + new PdfPoint(306.45291569755994, 923.936778153581), + new PdfPoint(959.6247632028106, 131.810977239897), + new PdfPoint(800.8517955224195, 350.74228914407433), + new PdfPoint(663.4680772483941, 352.304508375042), + new PdfPoint(222.58588801803648, 629.9145459539977), + new PdfPoint(158.01097092542204, 266.66678727838047), + new PdfPoint(63.28771038884462, 52.73381563522583), + new PdfPoint(931.6304534780719, 325.3140887623456), + new PdfPoint(781.7202794605689, 932.8040026798695), + new PdfPoint(691.9750284379987, 896.4394191971841), + new PdfPoint(354.5191195854491, 319.9839791539775), + new PdfPoint(177.99172848582566, 831.9975645935383), + new PdfPoint(237.7316548429551, 731.2752736686494), + new PdfPoint(989.397359729622, 217.92540678980444), + new PdfPoint(509.249200783335, 621.5474147516464), + new PdfPoint(744.8852601187607, 917.8174495950592), + new PdfPoint(308.86394317440534, 345.03042856046795), + new PdfPoint(716.4931552373788, 116.87982985630296), + new PdfPoint(8.999928995218953, 752.2282844071755), + new PdfPoint(294.01152638529305, 979.8820875548174), + new PdfPoint(919.862114089302, 218.09062242740873), + new PdfPoint(58.10245428844474, 679.4146645720332), + new PdfPoint(298.5501112825898, 76.27703572384658), + new PdfPoint(642.7813627195759, 506.9457773647057), + new PdfPoint(95.40371527671387, 849.060165476576), + new PdfPoint(462.81907479511983, 59.56530610812505), + new PdfPoint(883.4162795690498, 4.108824339342565), + new PdfPoint(255.30517910829244, 890.9400020559517), + new PdfPoint(127.06302493344879, 246.8462701575741), + new PdfPoint(995.1831247109899, 371.7538001202371), + new PdfPoint(189.37270784653683, 888.4231305357099), + new PdfPoint(924.0107481155084, 775.4673044677166), + new PdfPoint(865.7452979418968, 373.2043310431542), + new PdfPoint(409.76929412279594, 192.26266847186992), + new PdfPoint(438.8872219529338, 819.6826378850956), + }, + new PdfPoint[] + { + new PdfPoint(991.7951837792907, 716.5936071305995), + new PdfPoint(959.6247632028106, 131.810977239897), + new PdfPoint(63.28771038884462, 52.73381563522583), + new PdfPoint(781.7202794605689, 932.8040026798695), + new PdfPoint(989.397359729622, 217.92540678980444), + new PdfPoint(8.999928995218953, 752.2282844071755), + new PdfPoint(294.01152638529305, 979.8820875548174), + new PdfPoint(95.40371527671387, 849.060165476576), + new PdfPoint(883.4162795690498, 4.108824339342565), + new PdfPoint(995.1831247109899, 371.7538001202371), + } + } + }; + + public static IEnumerable MinimumAreaRectangleData => new[] + { + new object[] + { + new PdfPoint[] + { + // collinear points case: y = 15.7894 + 1.572431x + new PdfPoint(16, 40.948296), + new PdfPoint(18, 44.093158), + new PdfPoint(21, 48.810451), + new PdfPoint(30, 62.96233), + new PdfPoint(49, 92.838519), + new PdfPoint(55, 102.273105), + new PdfPoint(60, 110.13526), + new PdfPoint(64, 116.424984), + new PdfPoint(65, 117.997415), + new PdfPoint(68, 122.714708), + new PdfPoint(75, 133.721725), + new PdfPoint(84, 147.873604), + new PdfPoint(86, 151.018466), + new PdfPoint(90, 157.30819), + new PdfPoint(97, 168.315207), + new PdfPoint(99, 171.460069), + new PdfPoint(105, 180.894655), + new PdfPoint(106, 182.467086), + new PdfPoint(110, 188.75681), + new PdfPoint(113, 193.474103), + new PdfPoint(119, 202.908689), + new PdfPoint(121, 206.053551), + new PdfPoint(122, 207.625982), + new PdfPoint(123, 209.198413) + }, + new PdfPoint[] + { + new PdfPoint(16, 40.948296), + new PdfPoint(16, 40.948296), + new PdfPoint(123, 209.198413), + new PdfPoint(123, 209.198413) + } + }, + new object[] + { + new PdfPoint[] + { + // collinear points case: y = 87.483 + 99.2934520998549x + new PdfPoint(10.5114328889726, 1131.19945806204), + new PdfPoint(11.1542565096881, 1195.02763445421), + new PdfPoint(15.3153242359356, 1608.19441341462), + new PdfPoint(15.795577716642, 1655.88043939693), + new PdfPoint(16.3319701583886, 1709.14069661821), + new PdfPoint(17.1302715938114, 1788.40680195762), + new PdfPoint(17.9770489556469, 1872.48624937427), + new PdfPoint(22.4037700355884, 2312.03066688486), + new PdfPoint(23.7647419889928, 2447.16627034947), + new PdfPoint(26.9327398551415, 2761.72771472434), + new PdfPoint(28.7600875228236, 2943.17137283512), + new PdfPoint(32.8221934632313, 3346.51189445353), + new PdfPoint(34.4943972831614, 3512.55078434895), + new PdfPoint(34.5363374306664, 3516.7151663763), + new PdfPoint(36.0282475627216, 3664.85207361081) + }, + new PdfPoint[] + { + new PdfPoint(10.5114328889726, 1131.19945806204), + new PdfPoint(10.5114328889726, 1131.19945806204), + new PdfPoint(36.0282475627216, 3664.85207361081), + new PdfPoint(36.0282475627216, 3664.85207361081) + } + }, + new object[] + { + new PdfPoint[] + { + // collinear points case: vertical + new PdfPoint(446.78, 217.9), + new PdfPoint(446.78, 228.82), + new PdfPoint(446.78, 247.52), + new PdfPoint(446.78, 256.84), + new PdfPoint(446.78, 301.4), + new PdfPoint(446.78, 321.39), + new PdfPoint(446.78, 369.08), + new PdfPoint(446.78, 387.05), + new PdfPoint(446.78, 393.22), + new PdfPoint(446.78, 397.29), + new PdfPoint(446.78, 463.16), + new PdfPoint(446.78, 471.88), + new PdfPoint(446.78, 480.13), + new PdfPoint(446.78, 495.82), + new PdfPoint(446.78, 498.99) + }, + new PdfPoint[] + { + new PdfPoint(446.78, 217.9), + new PdfPoint(446.78, 217.9), + new PdfPoint(446.78, 498.99), + new PdfPoint(446.78, 498.99) + } + }, + new object[] + { + new PdfPoint[] + { + // collinear points case: horizontal + new PdfPoint(220, 208.821), + new PdfPoint(258.92, 208.821), + new PdfPoint(268.61, 208.821), + new PdfPoint(283.56, 208.821), + new PdfPoint(312.49, 208.821), + new PdfPoint(344.93, 208.821), + new PdfPoint(356, 208.821), + new PdfPoint(356.06, 208.821), + new PdfPoint(366.71, 208.821), + new PdfPoint(371.07, 208.821), + new PdfPoint(430.95, 208.821), + new PdfPoint(445.84, 208.821), + new PdfPoint(464.95, 208.821), + new PdfPoint(470.19, 208.821), + new PdfPoint(498.19, 208.821) + }, + new PdfPoint[] + { + new PdfPoint(220, 208.821), + new PdfPoint(220, 208.821), + new PdfPoint(498.19, 208.821), + new PdfPoint(498.19, 208.821) + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(433.8664544437276, 532.7739491464265), + new PdfPoint(653.8805470338659, 817.2121262831644), + new PdfPoint(531.2551432261636, 360.68316491741035), + new PdfPoint(418.79076902856593, 111.73491145462933), + }, + new PdfPoint[] + { + new PdfPoint(653.880547033866, 817.2121262831644), + new PdfPoint(461.3184174727883, 100.31182441133716), + new PdfPoint(327.3696296297328, 136.2909748899142), + new PdfPoint(519.9317591908105, 853.1912767617414), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(556.0000554986003, 83.08490003544556), + new PdfPoint(413.249212383334, 618.580307645359), + new PdfPoint(526.8827917872168, 111.78528983363456), + new PdfPoint(220.19687765118178, 377.83114567978316), + }, + new PdfPoint[] + { + new PdfPoint(556.0000554986004, 83.08490003544566), + new PdfPoint(315.83633925060445, 19.06273944752293), + new PdfPoint(173.0854961353382, 554.5581470574363), + new PdfPoint(413.24921238333417, 618.580307645359), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(14.004896521066401, 809.4941990011544), + new PdfPoint(703.95616092419, 970.7474069029789), + new PdfPoint(835.551079058811, 661.2654117428186), + new PdfPoint(200.4833132016346, 14.581989889114745), + new PdfPoint(73.40355670360321, 345.2226372321663), + }, + new PdfPoint[] + { + new PdfPoint(945.970699704068, 188.81492303383516), + new PdfPoint(153.25937285270737, 3.544961240987477), + new PdfPoint(-32.56092111592515, 798.6109846932103), + new PdfPoint(760.1504057354355, 983.8809464860581), + } + }, + new object[] + { + // duplicate points case + new PdfPoint[] + { + new PdfPoint(14.004896521066401, 809.4941990011544), + new PdfPoint(703.95616092419, 970.7474069029789), + new PdfPoint(835.551079058811, 661.2654117428186), + new PdfPoint(703.95616092419, 970.7474069029789), + new PdfPoint(703.95616092419, 970.7474069029789), + new PdfPoint(200.4833132016346, 14.581989889114745), + new PdfPoint(200.4833132016346, 14.581989889114745), + new PdfPoint(73.40355670360321, 345.2226372321663), + new PdfPoint(73.40355670360321, 345.2226372321663), + new PdfPoint(73.40355670360321, 345.2226372321663), + }, + new PdfPoint[] + { + new PdfPoint(945.970699704068, 188.81492303383516), + new PdfPoint(153.25937285270737, 3.544961240987477), + new PdfPoint(-32.56092111592515, 798.6109846932103), + new PdfPoint(760.1504057354355, 983.8809464860581), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(737.1041856902102, 648.0900313433699), + new PdfPoint(258.83885597639045, 32.15501719959235), + new PdfPoint(354.5500618726748, 908.7838113897652), + new PdfPoint(867.6475306924474, 47.361938752654595), + new PdfPoint(352.7960490248145, 283.67860449564785), + new PdfPoint(955.4087841797756, 833.327418435315), + new PdfPoint(578.2403790703082, 67.4511148622331), + new PdfPoint(722.9995401934759, 407.36102955779796), + new PdfPoint(404.3710508165602, 736.1127320695537), + new PdfPoint(56.25949705397548, 45.503737933916824), + }, + new PdfPoint[] + { + new PdfPoint(956.3312379371301, 841.5886588013605), + new PdfPoint(857.4507470077488, -43.95763180386355), + new PdfPoint(56.25949705397548, 45.503737933916824), + new PdfPoint(155.13998798335692, 931.0500285391407), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(37.79696601832361, 984.5348223984452), + new PdfPoint(881.8169100214427, 818.3604232045343), + new PdfPoint(732.8834668881201, 907.4453370173243), + new PdfPoint(142.89010125285918, 183.62301422208304), + new PdfPoint(292.4319539617013, 383.1685740348906), + new PdfPoint(658.9302664366852, 781.9569855570855), + new PdfPoint(501.7748878713084, 321.0551716869758), + new PdfPoint(104.96397346166219, 658.8420562657931), + new PdfPoint(931.1420702804029, 235.94015835854032), + new PdfPoint(489.5915692144058, 835.989512769871), + }, + new PdfPoint[] + { + new PdfPoint(931.1420702804029, 235.9401583585403), + new PdfPoint(91.18213738971586, 180.19110018351975), + new PdfPoint(37.79696601832373, 984.5348223984452), + new PdfPoint(877.7568989090107, 1040.2838805734657), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(360.50496116131137, 475.0257075944493), + new PdfPoint(463.6183053562707, 550.6502767074434), + new PdfPoint(719.7530742800635, 986.4011287537438), + new PdfPoint(746.9948030700444, 387.8044519192034), + new PdfPoint(868.5846874204865, 248.33194807842352), + new PdfPoint(485.3109455640756, 39.94793327837021), + new PdfPoint(504.8344865133781, 708.8088010613369), + new PdfPoint(352.0102119724019, 820.2239288583693), + new PdfPoint(28.306241810454267, 579.9713087166957), + new PdfPoint(801.671925406638, 886.8351079919669), + }, + new PdfPoint[] + { + new PdfPoint(1131.0092157743165, 443.1030548242194), + new PdfPoint(485.483254766563, -36.005383122939776), + new PdfPoint(28.306241810454186, 579.9713087166958), + new PdfPoint(673.8322028182079, 1059.079746663855), + } + }, + new object[] + { + new PdfPoint[] + { + new PdfPoint(384.5196462100093, 457.53938224300975), + new PdfPoint(388.62152246674617, 197.75832374394065), + new PdfPoint(40.7654267847265, 320.51104165848284), + new PdfPoint(443.45264559634137, 22.11792681360786), + new PdfPoint(345.74164027022573, 484.01784165724456), + new PdfPoint(453.33094307272717, 441.7802101118389), + new PdfPoint(470.9897811308254, 63.67713677117809), + new PdfPoint(277.98105707671505, 321.72593673466497), + new PdfPoint(447.8012370058249, 358.25102431521026), + new PdfPoint(345.94253780510235, 111.25057954480089), + }, + new PdfPoint[] + { + new PdfPoint(647.6991334648283, 297.75247084308063), + new PdfPoint(443.4526455963414, 22.117926813607856), + new PdfPoint(40.7654267847265, 320.5110416584829), + new PdfPoint(245.0119146532134, 596.1455856879556), + } } }; #endregion @@ -126,8 +566,23 @@ for (var i = 0; i < expected.Length; i++) { - Assert.Equal(expected[i].X, convexHull[i].X, 6); - Assert.Equal(expected[i].Y, convexHull[i].Y, 6); + Assert.Equal(expected[i], convexHull[i], PointComparer); + } + } + + + [Theory] + [MemberData(nameof(MinimumAreaRectangleData))] + public void MinimumAreaRectangle(PdfPoint[] points, PdfPoint[] expected) + { + expected = expected.OrderBy(p => p.X).ThenBy(p => p.Y).ToArray(); + + var marRectangle = GeometryExtensions.MinimumAreaRectangle(points); + var mar = new[] { marRectangle.BottomLeft, marRectangle.BottomRight, marRectangle.TopLeft, marRectangle.TopRight }.OrderBy(p => p.X).ThenBy(p => p.Y).ToArray(); + + for (var i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], mar[i], PointComparer); } } } diff --git a/src/UglyToad.PdfPig/Geometry/GeometryExtensions.cs b/src/UglyToad.PdfPig/Geometry/GeometryExtensions.cs index 3da754de..5a15cb15 100644 --- a/src/UglyToad.PdfPig/Geometry/GeometryExtensions.cs +++ b/src/UglyToad.PdfPig/Geometry/GeometryExtensions.cs @@ -65,57 +65,45 @@ /// The vertices of P are assumed to be in strict cyclic sequential order, either clockwise or /// counter-clockwise relative to the origin P0. /// - internal static PdfRectangle ParametricPerpendicularProjection(IReadOnlyList polygon) + private static PdfRectangle ParametricPerpendicularProjection(IReadOnlyList polygon) { if (polygon == null || polygon.Count == 0) { - throw new ArgumentException("ParametricPerpendicularProjection(): polygon cannot be null and must contain at least one point."); + throw new ArgumentException("ParametricPerpendicularProjection(): polygon cannot be null and must contain at least one point.", nameof(polygon)); } - - if (polygon.Count < 4) + else if (polygon.Count == 1) { - if (polygon.Count == 1) - { - return new PdfRectangle(polygon[0], polygon[0]); - } - else if (polygon.Count == 2) - { - return new PdfRectangle(polygon[0], polygon[1]); - } - else - { - PdfPoint p3 = polygon[0].Add(polygon[1].Subtract(polygon[2])); - return new PdfRectangle(p3, polygon[1], polygon[0], polygon[2]); - } + return new PdfRectangle(polygon[0], polygon[0]); + } + else if (polygon.Count == 2) + { + return new PdfRectangle(polygon[0], polygon[1]); } PdfPoint[] MBR = new PdfPoint[0]; - - double Amin = double.MaxValue; - double tmin = 1; - double tmax = 0; - double smax = 0; + + double Amin = double.PositiveInfinity; int j = 1; int k = 0; - int l = -1; PdfPoint Q = new PdfPoint(); PdfPoint R0 = new PdfPoint(); PdfPoint R1 = new PdfPoint(); PdfPoint u = new PdfPoint(); - int nv = polygon.Count; - while (true) { - var Pk = polygon[k]; - + PdfPoint Pk = polygon[k]; PdfPoint v = polygon[j].Subtract(Pk); double r = 1.0 / v.DotProduct(v); - for (j = 0; j < nv; j++) + double tmin = 1; + double tmax = 0; + double smax = 0; + int l = -1; + + for (j = 0; j < polygon.Count; j++) { - if (j == k) continue; PdfPoint Pj = polygon[j]; u = Pj.Subtract(Pk); double t = u.DotProduct(v) * r; @@ -143,37 +131,48 @@ } } - if (l == -1) + if (l != -1) { - // All points are colinear - rectangle has no area (need more tests) - var bottomLeft = polygon.OrderBy(p => p.X).ThenBy(p => p.Y).First(); - var topRight = polygon.OrderByDescending(p => p.Y).OrderByDescending(p => p.X).First(); - return new PdfRectangle(bottomLeft, topRight, bottomLeft, topRight); - } + PdfPoint PlMinusQ = polygon[l].Subtract(Q); + PdfPoint R2 = R1.Add(PlMinusQ); + PdfPoint R3 = R0.Add(PlMinusQ); - PdfPoint PlMinusQ = polygon[l].Subtract(Q); - PdfPoint R2 = R1.Add(PlMinusQ); - PdfPoint R3 = R0.Add(PlMinusQ); - u = R1.Subtract(R0); - double A = u.DotProduct(u) * smax; + u = R1.Subtract(R0); - if (A < Amin) - { - Amin = A; - MBR = new[] { R0, R1, R2, R3 }; + double A = u.DotProduct(u) * smax; + + if (A < Amin) + { + Amin = A; + MBR = new[] { R0, R1, R2, R3 }; + } } k++; - j = k; + j = k + 1; - if (j == nv) j = 0; - - if (k == nv) break; + if (j == polygon.Count) j = 0; + if (k == polygon.Count) break; } return new PdfRectangle(MBR[2], MBR[3], MBR[1], MBR[0]); } + /// + /// Algorithm to find the (oriented) minimum area rectangle (MAR) by first finding the convex hull of the points + /// and then finding its MAR. + /// + /// The points. + public static PdfRectangle MinimumAreaRectangle(IEnumerable points) + { + if (points == null || points.Count() == 0) + { + throw new ArgumentException("MinimumAreaRectangle(): points cannot be null and must contain at least one point.", nameof(points)); + } + + return ParametricPerpendicularProjection(GrahamScan(points.Distinct()).ToList()); + } + /// /// Algorithm to find the oriented bounding box (OBB) by first fitting a line through the points to get the slope, /// then rotating the points to obtain the axis-aligned bounding box (AABB), and then rotating back the AABB. @@ -241,10 +240,10 @@ if (points.Count() < 3) return points; - Func polarAngle = (PdfPoint point1, PdfPoint point2) => + double polarAngle(PdfPoint point1, PdfPoint point2) { return Math.Atan2(point2.Y - point1.Y, point2.X - point1.X) % Math.PI; - }; + } Stack stack = new Stack(); var sortedPoints = points.OrderBy(p => p.Y).ThenBy(p => p.X).ToList(); @@ -607,25 +606,25 @@ { if (!IntersectsWith(p11, p12, p21, p22)) return null; - var eq1 = GetSlopeIntercept(p11, p12); - var eq2 = GetSlopeIntercept(p21, p22); + var (Slope1, Intercept1) = GetSlopeIntercept(p11, p12); + var (Slope2, Intercept2) = GetSlopeIntercept(p21, p22); - if (double.IsNaN(eq1.Slope)) + if (double.IsNaN(Slope1)) { - var x = eq1.Intercept; - var y = eq2.Slope * x + eq2.Intercept; + var x = Intercept1; + var y = Slope2 * x + Intercept2; return new PdfPoint(x, y); } - else if (double.IsNaN(eq2.Slope)) + else if (double.IsNaN(Slope2)) { - var x = eq2.Intercept; - var y = eq1.Slope * x + eq1.Intercept; + var x = Intercept2; + var y = Slope1 * x + Intercept1; return new PdfPoint(x, y); } else { - var x = (eq2.Intercept - eq1.Intercept) / (eq1.Slope - eq2.Slope); - var y = eq1.Slope * x + eq1.Intercept; + var x = (Intercept2 - Intercept1) / (Slope1 - Slope2); + var y = Slope1 * x + Intercept1; return new PdfPoint(x, y); } } @@ -875,7 +874,7 @@ else // Casus irreducibilis { // François Viète's formula - Func vietTrigonometricSolution = (p_, q_, k) => 2.0 * Math.Sqrt(-p_ / 3.0) + double vietTrigonometricSolution(double p_, double q_, double k) => 2.0 * Math.Sqrt(-p_ / 3.0) * Math.Cos(OneThird * Math.Acos((3.0 * q_) / (2.0 * p_) * Math.Sqrt(-3.0 / p_)) - (2.0 * Math.PI * k) / 3.0); double p = Q * 3.0; // (3.0 * a * c - b * b) / (3.0 * aSquared);