ArcToHelper.cs 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  1. // Copyright © 2003-2004, Luc Maisonobe
  2. // 2015 - Alexey Rozanov <[email protected]> - Adaptations for Avalonia and oval center computations
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with
  6. // or without modification, are permitted provided that
  7. // the following conditions are met:
  8. //
  9. // Redistributions of source code must retain the
  10. // above copyright notice, this list of conditions and
  11. // the following disclaimer.
  12. // Redistributions in binary form must reproduce the
  13. // above copyright notice, this list of conditions and
  14. // the following disclaimer in the documentation
  15. // and/or other materials provided with the
  16. // distribution.
  17. // Neither the names of spaceroots.org, spaceroots.com
  18. // nor the names of their contributors may be used to
  19. // endorse or promote products derived from this
  20. // software without specific prior written permission.
  21. //
  22. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  23. // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
  24. // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  25. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  26. // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  27. // THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
  28. // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  29. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  30. // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  31. // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  32. // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
  33. // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  34. // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  35. // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  36. // POSSIBILITY OF SUCH DAMAGE.
  37. // C#/WPF/Avalonia adaptation by Alexey Rozanov <[email protected]>, 2015.
  38. // I do not mind if anyone would find this adaptation useful, but
  39. // please retain the above disclaimer made by the original class
  40. // author Luc Maisonobe. He worked really hard on this subject, so
  41. // please respect him by at least keeping the above disclaimer intact
  42. // if you use his code.
  43. //
  44. // Commented out some unused values calculations.
  45. // These are not supposed to be removed from the source code,
  46. // as these may be helpful for debugging.
  47. using System;
  48. using Avalonia.Media;
  49. using Avalonia.Platform;
  50. namespace Avalonia.RenderHelpers
  51. {
  52. static class ArcToHelper
  53. {
  54. /// <summary>
  55. /// This class represents an elliptical arc on a 2D plane.
  56. ///
  57. /// This class is adapted for use with WPF StreamGeometryContext, and needs to be created explicitly
  58. /// for each particular arc.
  59. ///
  60. /// Some helpers
  61. ///
  62. /// It can handle ellipses which are not aligned with the x and y reference axes of the plane,
  63. /// as well as their parts.
  64. ///
  65. /// Another improvement is that this class can handle degenerated cases like for example very
  66. /// flat ellipses(semi-minor axis much smaller than semi-major axis) and drawing of very small
  67. /// parts of such ellipses at very high magnification scales.This imply monitoring the drawing
  68. /// approximation error for extremely small values.Such cases occur for example while drawing
  69. /// orbits of comets near the perihelion.
  70. ///
  71. /// When the arc does not cover the complete ellipse, the lines joining the center of the
  72. /// ellipse to the endpoints can optionally be included or not in the outline, hence allowing
  73. /// to use it for pie-charts rendering. If these lines are not included, the curve is not
  74. /// naturally closed.
  75. /// </summary>
  76. public sealed class EllipticalArc
  77. {
  78. private const double TwoPi = 2 * Math.PI;
  79. /// <summary>
  80. /// Coefficients for error estimation while using quadratic Bezier curves for approximation,
  81. /// 0 ≤ b/a ≤ 0.25
  82. /// </summary>
  83. private static readonly double[][][] Coeffs2Low = {
  84. new[]
  85. {
  86. new[] {3.92478, -13.5822, -0.233377, 0.0128206},
  87. new[] {-1.08814, 0.859987, 3.62265E-4, 2.29036E-4},
  88. new[] {-0.942512, 0.390456, 0.0080909, 0.00723895},
  89. new[] {-0.736228, 0.20998, 0.0129867, 0.0103456}
  90. },
  91. new[]
  92. {
  93. new[] {-0.395018, 6.82464, 0.0995293, 0.0122198},
  94. new[] {-0.545608, 0.0774863, 0.0267327, 0.0132482},
  95. new[] {0.0534754, -0.0884167, 0.012595, 0.0343396},
  96. new[] {0.209052, -0.0599987, -0.00723897, 0.00789976}
  97. }
  98. };
  99. /// <summary>
  100. /// Coefficients for error estimation while using quadratic Bezier curves for approximation,
  101. /// 0.25 ≤ b/a ≤ 1
  102. /// </summary>
  103. private static readonly double[][][] Coeffs2High = {
  104. new[]
  105. {
  106. new[] {0.0863805, -11.5595, -2.68765, 0.181224},
  107. new[] {0.242856, -1.81073, 1.56876, 1.68544},
  108. new[] {0.233337, -0.455621, 0.222856, 0.403469},
  109. new[] {0.0612978, -0.104879, 0.0446799, 0.00867312}
  110. },
  111. new[]
  112. {
  113. new[] {0.028973, 6.68407, 0.171472, 0.0211706},
  114. new[] {0.0307674, -0.0517815, 0.0216803, -0.0749348},
  115. new[] {-0.0471179, 0.1288, -0.0781702, 2.0},
  116. new[] {-0.0309683, 0.0531557, -0.0227191, 0.0434511}
  117. }
  118. };
  119. /// <summary>
  120. /// Safety factor to convert the "best" error approximation into a "max bound" error
  121. /// </summary>
  122. private static readonly double[] Safety2 = { 0.02, 2.83, 0.125, 0.01 };
  123. /// <summary>
  124. /// Coefficients for error estimation while using cubic Bezier curves for approximation,
  125. /// 0.25 ≤ b/a ≤ 1
  126. /// </summary>
  127. private static readonly double[][][] Coeffs3Low = {
  128. new[]
  129. {
  130. new[] {3.85268, -21.229, -0.330434, 0.0127842},
  131. new[] {-1.61486, 0.706564, 0.225945, 0.263682},
  132. new[] {-0.910164, 0.388383, 0.00551445, 0.00671814},
  133. new[] {-0.630184, 0.192402, 0.0098871, 0.0102527}
  134. },
  135. new[]
  136. {
  137. new[] {-0.162211, 9.94329, 0.13723, 0.0124084},
  138. new[] {-0.253135, 0.00187735, 0.0230286, 0.01264},
  139. new[] {-0.0695069, -0.0437594, 0.0120636, 0.0163087},
  140. new[] {-0.0328856, -0.00926032, -0.00173573, 0.00527385}
  141. }
  142. };
  143. /// <summary>
  144. /// Coefficients for error estimation while using cubic Bezier curves for approximation,
  145. /// 0.25 ≤ b/a ≤ 1
  146. /// </summary>
  147. private static readonly double[][][] Coeffs3High = {
  148. new[]
  149. {
  150. new[] {0.0899116, -19.2349, -4.11711, 0.183362},
  151. new[] {0.138148, -1.45804, 1.32044, 1.38474},
  152. new[] {0.230903, -0.450262, 0.219963, 0.414038},
  153. new[] {0.0590565, -0.101062, 0.0430592, 0.0204699}
  154. },
  155. new[]
  156. {
  157. new[] {0.0164649, 9.89394, 0.0919496, 0.00760802},
  158. new[] {0.0191603, -0.0322058, 0.0134667, -0.0825018},
  159. new[] {0.0156192, -0.017535, 0.00326508, -0.228157},
  160. new[] {-0.0236752, 0.0405821, -0.0173086, 0.176187}
  161. }
  162. };
  163. /// <summary>
  164. /// Safety factor to convert the "best" error approximation into a "max bound" error
  165. /// </summary>
  166. private static readonly double[] Safety3 = { 0.0010, 4.98, 0.207, 0.0067 };
  167. /// <summary>
  168. /// Abscissa of the center of the ellipse
  169. /// </summary>
  170. internal double Cx;
  171. /// <summary>
  172. /// Ordinate of the center of the ellipse
  173. /// </summary>
  174. internal double Cy;
  175. /// <summary>
  176. /// Semi-major axis
  177. /// </summary>
  178. internal double A;
  179. /// <summary>
  180. /// Semi-minor axis
  181. /// </summary>
  182. internal double B;
  183. /// <summary>
  184. /// Orientation of the major axis with respect to the x axis
  185. /// </summary>
  186. internal double Theta;
  187. /// <summary>
  188. /// Pre-calculated cosine value for the major-axis-to-X orientation (Theta)
  189. /// </summary>
  190. private readonly double _cosTheta;
  191. /// <summary>
  192. /// Pre-calculated sine value for the major-axis-to-X orientation (Theta)
  193. /// </summary>
  194. private readonly double _sinTheta;
  195. /// <summary>
  196. /// Start angle of the arc
  197. /// </summary>
  198. internal double Eta1;
  199. /// <summary>
  200. /// End angle of the arc
  201. /// </summary>
  202. internal double Eta2;
  203. /// <summary>
  204. /// Abscissa of the start point
  205. /// </summary>
  206. internal double X1;
  207. /// <summary>
  208. /// Ordinate of the start point
  209. /// </summary>
  210. internal double Y1;
  211. /// <summary>
  212. /// Abscissa of the end point
  213. /// </summary>
  214. internal double X2;
  215. /// <summary>
  216. /// Ordinate of the end point
  217. /// </summary>
  218. internal double Y2;
  219. /// <summary>
  220. /// Abscissa of the first focus
  221. /// </summary>
  222. internal double FirstFocusX;
  223. /// <summary>
  224. /// Ordinate of the first focus
  225. /// </summary>
  226. internal double FirstFocusY;
  227. /// <summary>
  228. /// Abscissa of the second focus
  229. /// </summary>
  230. internal double SecondFocusX;
  231. /// <summary>
  232. /// Ordinate of the second focus
  233. /// </summary>
  234. internal double SecondFocusY;
  235. /// <summary>
  236. /// Abscissa of the leftmost point of the arc
  237. /// </summary>
  238. private double _xLeft;
  239. /// <summary>
  240. /// Ordinate of the highest point of the arc
  241. /// </summary>
  242. private double _yUp;
  243. /// <summary>
  244. /// Horizontal width of the arc
  245. /// </summary>
  246. private double _width;
  247. /// <summary>
  248. /// Vertical height of the arc
  249. /// </summary>
  250. private double _height;
  251. /// <summary>
  252. /// Indicator for center to endpoints line inclusion
  253. /// </summary>
  254. internal bool IsPieSlice;
  255. /// <summary>
  256. /// Maximal degree for Bezier curve approximation
  257. /// </summary>
  258. private int _maxDegree;
  259. /// <summary>
  260. /// Default flatness for Bezier curve approximation
  261. /// </summary>
  262. private double _defaultFlatness;
  263. /// <summary>
  264. /// Indicator for semi-major axis significance (compared to semi-minor one).
  265. /// Computed by dividing the (A-B) difference by the value of A.
  266. /// This indicator is used for an early escape in intersection test
  267. /// </summary>
  268. internal double F;
  269. /// <summary>
  270. /// Indicator used for an early escape in intersection test
  271. /// </summary>
  272. internal double E2;
  273. /// <summary>
  274. /// Indicator used for an early escape in intersection test
  275. /// </summary>
  276. internal double G;
  277. /// <summary>
  278. /// Indicator used for an early escape in intersection test
  279. /// </summary>
  280. internal double G2;
  281. /// <summary>
  282. /// Builds an elliptical arc composed of the full unit circle around (0,0)
  283. /// </summary>
  284. public EllipticalArc()
  285. {
  286. Cx = 0;
  287. Cy = 0;
  288. A = 1;
  289. B = 1;
  290. Theta = 0;
  291. Eta1 = 0;
  292. Eta2 = TwoPi;
  293. _cosTheta = 1;
  294. _sinTheta = 0;
  295. IsPieSlice = false;
  296. _maxDegree = 3;
  297. _defaultFlatness = 0.5;
  298. ComputeFocii();
  299. ComputeEndPoints();
  300. ComputeBounds();
  301. ComputeDerivedFlatnessParameters();
  302. }
  303. /// <summary>
  304. /// Builds an elliptical arc from its canonical geometrical elements
  305. /// </summary>
  306. /// <param name="center">Center of the ellipse</param>
  307. /// <param name="a">Semi-major axis</param>
  308. /// <param name="b">Semi-minor axis</param>
  309. /// <param name="theta">Orientation of the major axis with respect to the x axis</param>
  310. /// <param name="lambda1">Start angle of the arc</param>
  311. /// <param name="lambda2">End angle of the arc</param>
  312. /// <param name="isPieSlice">If true, the lines between the center of the ellipse
  313. /// and the endpoints are part of the shape (it is pie slice like)</param>
  314. public EllipticalArc(Point center, double a, double b, double theta, double lambda1, double lambda2,
  315. bool isPieSlice) : this(center.X, center.Y, a, b, theta, lambda1,
  316. lambda2, isPieSlice)
  317. {
  318. }
  319. /// <summary>
  320. /// Builds an elliptical arc from its canonical geometrical elements
  321. /// </summary>
  322. /// <param name="cx">Abscissa of the center of the ellipse</param>
  323. /// <param name="cy">Ordinate of the center of the ellipse</param>
  324. /// <param name="a">Semi-major axis</param>
  325. /// <param name="b">Semi-minor axis</param>
  326. /// <param name="theta">Orientation of the major axis with respect to the x axis</param>
  327. /// <param name="lambda1">Start angle of the arc</param>
  328. /// <param name="lambda2">End angle of the arc</param>
  329. /// <param name="isPieSlice">If true, the lines between the center of the ellipse
  330. /// and the endpoints are part of the shape (it is pie slice like)</param>
  331. public EllipticalArc(double cx, double cy, double a, double b, double theta, double lambda1, double lambda2,
  332. bool isPieSlice)
  333. {
  334. Cx = cx;
  335. Cy = cy;
  336. A = a;
  337. B = b;
  338. Theta = theta;
  339. IsPieSlice = isPieSlice;
  340. Eta1 = Math.Atan2(Math.Sin(lambda1) / b, Math.Cos(lambda1) / a);
  341. Eta2 = Math.Atan2(Math.Sin(lambda2) / b, Math.Cos(lambda2) / a);
  342. _cosTheta = Math.Cos(theta);
  343. _sinTheta = Math.Sin(theta);
  344. _maxDegree = 3;
  345. _defaultFlatness = 0.5; // half a pixel
  346. Eta2 -= TwoPi * Math.Floor((Eta2 - Eta1) / TwoPi); //make sure we have eta1 <= eta2 <= eta1 + 2 PI
  347. // the preceding correction fails if we have exactly eta2-eta1 == 2*PI
  348. // it reduces the interval to zero length
  349. if (lambda2 - lambda1 > Math.PI && Eta2 - Eta1 < Math.PI)
  350. {
  351. Eta2 += TwoPi;
  352. }
  353. ComputeFocii();
  354. ComputeEndPoints();
  355. ComputeBounds();
  356. ComputeDerivedFlatnessParameters();
  357. }
  358. /// <summary>
  359. /// Build a full ellipse from its canonical geometrical elements
  360. /// </summary>
  361. /// <param name="center">Center of the ellipse</param>
  362. /// <param name="a">Semi-major axis</param>
  363. /// <param name="b">Semi-minor axis</param>
  364. /// <param name="theta">Orientation of the major axis with respect to the x axis</param>
  365. public EllipticalArc(Point center, double a, double b, double theta) : this(center.X, center.Y, a, b, theta)
  366. {
  367. }
  368. /// <summary>
  369. /// Build a full ellipse from its canonical geometrical elements
  370. /// </summary>
  371. /// <param name="cx">Abscissa of the center of the ellipse</param>
  372. /// <param name="cy">Ordinate of the center of the ellipse</param>
  373. /// <param name="a">Semi-major axis</param>
  374. /// <param name="b">Semi-minor axis</param>
  375. /// <param name="theta">Orientation of the major axis with respect to the x axis</param>
  376. public EllipticalArc(double cx, double cy, double a, double b, double theta)
  377. {
  378. Cx = cx;
  379. Cy = cy;
  380. A = a;
  381. B = b;
  382. Theta = theta;
  383. IsPieSlice = false;
  384. Eta1 = 0;
  385. Eta2 = TwoPi;
  386. _cosTheta = Math.Cos(theta);
  387. _sinTheta = Math.Sin(theta);
  388. _maxDegree = 3;
  389. _defaultFlatness = 0.5; //half a pixel
  390. ComputeFocii();
  391. ComputeEndPoints();
  392. ComputeBounds();
  393. ComputeDerivedFlatnessParameters();
  394. }
  395. /// <summary>
  396. /// Sets the maximal degree allowed for Bezier curve approximation.
  397. /// </summary>
  398. /// <param name="maxDegree">Maximal allowed degree (must be between 1 and 3)</param>
  399. /// <exception cref="ArgumentException">Thrown if maxDegree is not between 1 and 3</exception>
  400. public void SetMaxDegree(int maxDegree)
  401. {
  402. if (maxDegree < 1 || maxDegree > 3)
  403. {
  404. throw new ArgumentException(@"maxDegree must be between 1 and 3", nameof(maxDegree));
  405. }
  406. _maxDegree = maxDegree;
  407. }
  408. /// <summary>
  409. /// Sets the default flatness for Bezier curve approximation
  410. /// </summary>
  411. /// <param name="defaultFlatness">default flatness (must be greater than 1e-10)</param>
  412. /// <exception cref="ArgumentException">Thrown if defaultFlatness is lower than 1e-10</exception>
  413. public void SetDefaultFlatness(double defaultFlatness)
  414. {
  415. if (defaultFlatness < 1.0E-10)
  416. {
  417. throw new ArgumentException(@"defaultFlatness must be greater than 1.0e-10", nameof(defaultFlatness));
  418. }
  419. _defaultFlatness = defaultFlatness;
  420. }
  421. /// <summary>
  422. /// Computes the locations of the focii
  423. /// </summary>
  424. private void ComputeFocii()
  425. {
  426. double d = Math.Sqrt(A * A - B * B);
  427. double dx = d * _cosTheta;
  428. double dy = d * _sinTheta;
  429. FirstFocusX = Cx - dx;
  430. FirstFocusY = Cy - dy;
  431. SecondFocusX = Cx + dx;
  432. SecondFocusY = Cy + dy;
  433. }
  434. /// <summary>
  435. /// Computes the locations of the endpoints
  436. /// </summary>
  437. private void ComputeEndPoints()
  438. {
  439. double aCosEta1 = A * Math.Cos(Eta1);
  440. double bSinEta1 = B * Math.Sin(Eta1);
  441. X1 = Cx + aCosEta1 * _cosTheta - bSinEta1 * _sinTheta;
  442. Y1 = Cy + aCosEta1 * _sinTheta + bSinEta1 * _cosTheta;
  443. double aCosEta2 = A * Math.Cos(Eta2);
  444. double bSinEta2 = B * Math.Sin(Eta2);
  445. X2 = Cx + aCosEta2 * _cosTheta - bSinEta2 * _sinTheta;
  446. Y2 = Cy + aCosEta2 * _sinTheta + bSinEta2 * _cosTheta;
  447. }
  448. /// <summary>
  449. /// Computes the bounding box
  450. /// </summary>
  451. private void ComputeBounds()
  452. {
  453. double bOnA = B / A;
  454. double etaXMin;
  455. double etaXMax;
  456. double etaYMin;
  457. double etaYMax;
  458. if (Math.Abs(_sinTheta) < 0.1)
  459. {
  460. double tanTheta = _sinTheta / _cosTheta;
  461. if (_cosTheta < 0)
  462. {
  463. etaXMin = -Math.Atan(tanTheta * bOnA);
  464. etaXMax = etaXMin + Math.PI;
  465. etaYMin = 0.5 * Math.PI - Math.Atan(tanTheta / bOnA);
  466. etaYMax = etaYMin + Math.PI;
  467. }
  468. else
  469. {
  470. etaXMax = -Math.Atan(tanTheta * bOnA);
  471. etaXMin = etaXMax - Math.PI;
  472. etaYMax = 0.5 * Math.PI - Math.Atan(tanTheta / bOnA);
  473. etaYMin = etaYMax - Math.PI;
  474. }
  475. }
  476. else
  477. {
  478. double invTanTheta = _cosTheta / _sinTheta;
  479. if (_sinTheta < 0)
  480. {
  481. etaXMax = 0.5 * Math.PI + Math.Atan(invTanTheta / bOnA);
  482. etaXMin = etaXMax - Math.PI;
  483. etaYMin = Math.Atan(invTanTheta * bOnA);
  484. etaYMax = etaYMin + Math.PI;
  485. }
  486. else
  487. {
  488. etaXMin = 0.5 * Math.PI + Math.Atan(invTanTheta / bOnA);
  489. etaXMax = etaXMin + Math.PI;
  490. etaYMax = Math.Atan(invTanTheta * bOnA);
  491. etaYMin = etaYMax - Math.PI;
  492. }
  493. }
  494. etaXMin -= TwoPi * Math.Floor((etaXMin - Eta1) / TwoPi);
  495. etaYMin -= TwoPi * Math.Floor((etaYMin - Eta1) / TwoPi);
  496. etaXMax -= TwoPi * Math.Floor((etaXMax - Eta1) / TwoPi);
  497. etaYMax -= TwoPi * Math.Floor((etaYMax - Eta1) / TwoPi);
  498. _xLeft = etaXMin <= Eta2
  499. ? Cx + A * Math.Cos(etaXMin) * _cosTheta - B * Math.Sin(etaXMin) * _sinTheta
  500. : Math.Min(X1, X2);
  501. _yUp = etaYMin <= Eta2 ? Cy + A * Math.Cos(etaYMin) * _sinTheta + B * Math.Sin(etaYMin) * _cosTheta : Math.Min(Y1, Y2);
  502. _width = (etaXMax <= Eta2
  503. ? Cx + A * Math.Cos(etaXMax) * _cosTheta - B * Math.Sin(etaXMax) * _sinTheta
  504. : Math.Max(X1, X2)) - _xLeft;
  505. _height = (etaYMax <= Eta2
  506. ? Cy + A * Math.Cos(etaYMax) * _sinTheta + B * Math.Sin(etaYMax) * _cosTheta
  507. : Math.Max(Y1, Y2)) - _yUp;
  508. }
  509. /// <summary>
  510. /// Computes the flatness parameters used in intersection tests
  511. /// </summary>
  512. private void ComputeDerivedFlatnessParameters()
  513. {
  514. F = (A - B) / A;
  515. E2 = F * (2.0 - F);
  516. G = 1.0 - F;
  517. G2 = G * G;
  518. }
  519. /// <summary>
  520. /// Computes the value of a rational function.
  521. /// This method handles rational functions where the numerator is quadratic
  522. /// and the denominator is linear
  523. /// </summary>
  524. /// <param name="x">Abscissa for which the value should be computed</param>
  525. /// <param name="c">Coefficients array of the rational function</param>
  526. /// <returns></returns>
  527. private static double RationalFunction(double x, double[] c)
  528. {
  529. return (x * (x * c[0] + c[1]) + c[2]) / (x + c[3]);
  530. }
  531. /// <summary>
  532. /// Estimate the approximation error for a sub-arc of the instance
  533. /// </summary>
  534. /// <param name="degree">Degree of the Bezier curve to use (1, 2 or 3)</param>
  535. /// <param name="etaA">Start angle of the sub-arc</param>
  536. /// <param name="etaB">End angle of the sub-arc</param>
  537. /// <returns>Upper bound of the approximation error between the Bezier curve and the real ellipse</returns>
  538. public double EstimateError(int degree, double etaA, double etaB)
  539. {
  540. if (degree < 1 || degree > _maxDegree)
  541. throw new ArgumentException($"degree should be between {1} and {_maxDegree}", nameof(degree));
  542. double eta = 0.5 * (etaA + etaB);
  543. if (degree < 2)
  544. {
  545. //start point
  546. double aCosEtaA = A * Math.Cos(etaA);
  547. double bSinEtaA = B * Math.Sin(etaA);
  548. double xA = Cx + aCosEtaA * _cosTheta - bSinEtaA * _sinTheta;
  549. double yA = Cy + aCosEtaA * _sinTheta + bSinEtaA * _cosTheta;
  550. //end point
  551. double aCosEtaB = A * Math.Cos(etaB);
  552. double bSinEtaB = B * Math.Sin(etaB);
  553. double xB = Cx + aCosEtaB * _cosTheta - bSinEtaB * _sinTheta;
  554. double yB = Cy + aCosEtaB * _sinTheta + bSinEtaB * _cosTheta;
  555. //maximal error point
  556. double aCosEta = A * Math.Cos(eta);
  557. double bSinEta = B * Math.Sin(eta);
  558. double x = Cx + aCosEta * _cosTheta - bSinEta * _sinTheta;
  559. double y = Cy + aCosEta * _sinTheta + bSinEta * _cosTheta;
  560. double dx = xB - xA;
  561. double dy = yB - yA;
  562. return Math.Abs(x * dy - y * dx + xB * yA - xA * yB) / Math.Sqrt(dx * dx + dy * dy);
  563. }
  564. else
  565. {
  566. double x = B / A;
  567. double dEta = etaB - etaA;
  568. double cos2 = Math.Cos(2 * eta);
  569. double cos4 = Math.Cos(4 * eta);
  570. double cos6 = Math.Cos(6 * eta);
  571. // select the right coeficients set according to degree and b/a
  572. double[][][] coeffs;
  573. double[] safety;
  574. if (degree == 2)
  575. {
  576. coeffs = x < 0.25 ? Coeffs2Low : Coeffs2High;
  577. safety = Safety2;
  578. }
  579. else
  580. {
  581. coeffs = x < 0.25 ? Coeffs3Low : Coeffs3High;
  582. safety = Safety3;
  583. }
  584. double c0 = RationalFunction(x, coeffs[0][0]) + cos2 * RationalFunction(x, coeffs[0][1]) +
  585. cos4 * RationalFunction(x, coeffs[0][2]) + cos6 * RationalFunction(x,
  586. coeffs[0][3]);
  587. double c1 = RationalFunction(x, coeffs[1][0]) + cos2 * RationalFunction(x, coeffs[1][1]) +
  588. cos4 * RationalFunction(x, coeffs[1][2]) + cos6 * RationalFunction(x,
  589. coeffs[1][3]);
  590. return RationalFunction(x, safety) * A * Math.Exp(c0 + c1 * dEta);
  591. }
  592. }
  593. /// <summary>
  594. /// Get the elliptical arc point for a given angular parameter
  595. /// </summary>
  596. /// <param name="lambda">Angular parameter for which point is desired</param>
  597. /// <returns>The desired elliptical arc point location</returns>
  598. public Point PointAt(double lambda)
  599. {
  600. double eta = Math.Atan2(Math.Sin(lambda) / B, Math.Cos(lambda) / A);
  601. double aCosEta = A * Math.Cos(eta);
  602. double bSinEta = B * Math.Sin(eta);
  603. Point p = new Point(Cx + aCosEta * _cosTheta - bSinEta * _sinTheta, Cy + aCosEta * _sinTheta + bSinEta * _cosTheta);
  604. return p;
  605. }
  606. /// <summary>
  607. /// Tests if the specified coordinates are inside the closed shape formed by this arc.
  608. /// If this is not a pie, then a shape derived by adding a closing chord is considered.
  609. /// </summary>
  610. /// <param name="x">Abscissa of the test point</param>
  611. /// <param name="y">Ordinate of the test point</param>
  612. /// <returns>True if the specified coordinates are inside the closed shape of this arc</returns>
  613. public bool Contains(double x, double y)
  614. {
  615. // position relative to the focii
  616. double dx1 = x - FirstFocusX;
  617. double dy1 = y - FirstFocusY;
  618. double dx2 = x - SecondFocusX;
  619. double dy2 = y - SecondFocusY;
  620. if (dx1 * dx1 + dy1 * dy1 + dx2 * dx2 + dy2 * dy2 > 4 * A * A)
  621. {
  622. // the point is outside of the ellipse
  623. return false;
  624. }
  625. if (IsPieSlice)
  626. {
  627. // check the location of the test point with respect to the
  628. // angular sector counted from the centre of the ellipse
  629. double dxC = x - Cx;
  630. double dyC = y - Cy;
  631. double u = dxC * _cosTheta + dyC * _sinTheta;
  632. double v = dyC * _cosTheta - dxC * _sinTheta;
  633. double eta = Math.Atan2(v / B, u / A);
  634. eta -= TwoPi * Math.Floor((eta - Eta1) / TwoPi);
  635. return eta <= Eta2;
  636. }
  637. // check the location of the test point with respect to the
  638. // chord joining the start and end points
  639. double dx = X2 - X1;
  640. double dy = Y2 - Y1;
  641. return x * dy - y * dx + X2 * Y1 - X1 * Y2 >= 0;
  642. }
  643. /// <summary>
  644. /// Tests if a line segment intersects the arc
  645. /// </summary>
  646. /// <param name="xA">abscissa of the first point of the line segment</param>
  647. /// <param name="yA">ordinate of the first point of the line segment</param>
  648. /// <param name="xB">abscissa of the second point of the line segment</param>
  649. /// <param name="yB">ordinate of the second point of the line segment</param>
  650. /// <returns>true if the two line segments intersect</returns>
  651. private bool IntersectArc(double xA, double yA, double xB, double yB)
  652. {
  653. double dx = xA - xB;
  654. double dy = yA - yB;
  655. double l = Math.Sqrt(dx * dx + dy * dy);
  656. if (l < 1.0E-10 * A)
  657. {
  658. // too small line segment, we consider it doesn't intersect anything
  659. return false;
  660. }
  661. double cz = (dx * _cosTheta + dy * _sinTheta) / l;
  662. double sz = (dy * _cosTheta - dx * _sinTheta) / l;
  663. // express position of the first point in canonical frame
  664. dx = xA - Cx;
  665. dy = yA - Cy;
  666. double u = dx * _cosTheta + dy * _sinTheta;
  667. double v = dy * _cosTheta - dx * _sinTheta;
  668. double u2 = u * u;
  669. double v2 = v * v;
  670. double g2U2Ma2 = G2 * (u2 - A * A);
  671. //double g2U2Ma2Mv2 = g2U2Ma2 - v2;
  672. double g2U2Ma2Pv2 = g2U2Ma2 + v2;
  673. // compute intersections with the ellipse along the line
  674. // as the roots of a 2nd degree polynom : c0 k^2 - 2 c1 k + c2 = 0
  675. double c0 = 1.0 - E2 * cz * cz;
  676. double c1 = G2 * u * cz + v * sz;
  677. double c2 = g2U2Ma2Pv2;
  678. double c12 = c1 * c1;
  679. double c0C2 = c0 * c2;
  680. if (c12 < c0C2)
  681. {
  682. // the line does not intersect the ellipse at all
  683. return false;
  684. }
  685. double k = c1 >= 0 ? (c1 + Math.Sqrt(c12 - c0C2)) / c0 : c2 / (c1 - Math.Sqrt(c12 - c0C2));
  686. if (k >= 0 && k <= l)
  687. {
  688. double uIntersect = u - k * cz;
  689. double vIntersect = v - k * sz;
  690. double eta = Math.Atan2(vIntersect / B, uIntersect / A);
  691. eta -= TwoPi * Math.Floor((eta - Eta1) / TwoPi);
  692. if (eta <= Eta2)
  693. {
  694. return true;
  695. }
  696. }
  697. k = c2 / (k * c0);
  698. if (k >= 0 && k <= l)
  699. {
  700. double uIntersect = u - k * cz;
  701. double vIntersect = v - k * sz;
  702. double eta = Math.Atan2(vIntersect / B, uIntersect / A);
  703. eta -= TwoPi * Math.Floor((eta - Eta1) / TwoPi);
  704. if (eta <= Eta2)
  705. {
  706. return true;
  707. }
  708. }
  709. return false;
  710. }
  711. /// <summary>
  712. /// Tests if two line segments intersect
  713. /// </summary>
  714. /// <param name="x1">Abscissa of the first point of the first line segment</param>
  715. /// <param name="y1">Ordinate of the first point of the first line segment</param>
  716. /// <param name="x2">Abscissa of the second point of the first line segment</param>
  717. /// <param name="y2">Ordinate of the second point of the first line segment</param>
  718. /// <param name="xA">Abscissa of the first point of the second line segment</param>
  719. /// <param name="yA">Ordinate of the first point of the second line segment</param>
  720. /// <param name="xB">Abscissa of the second point of the second line segment</param>
  721. /// <param name="yB">Ordinate of the second point of the second line segment</param>
  722. /// <returns>true if the two line segments intersect</returns>
  723. private static bool Intersect(double x1, double y1, double x2, double y2, double xA, double yA, double xB,
  724. double yB)
  725. {
  726. // elements of the equation of the (1, 2) line segment
  727. double dx12 = x2 - x1;
  728. double dy12 = y2 - y1;
  729. double k12 = x2 * y1 - x1 * y2;
  730. // elements of the equation of the (A, B) line segment
  731. double dxAb = xB - xA;
  732. double dyAb = yB - yA;
  733. double kAb = xB * yA - xA * yB;
  734. // compute relative positions of endpoints versus line segments
  735. double pAvs12 = xA * dy12 - yA * dx12 + k12;
  736. double pBvs12 = xB * dy12 - yB * dx12 + k12;
  737. double p1VsAb = x1 * dyAb - y1 * dxAb + kAb;
  738. double p2VsAb = x2 * dyAb - y2 * dxAb + kAb;
  739. return pAvs12 * pBvs12 <= 0 && p1VsAb * p2VsAb <= 0;
  740. }
  741. /// <summary>
  742. /// Tests if a line segment intersects the outline
  743. /// </summary>
  744. /// <param name="xA">Abscissa of the first point of the line segment</param>
  745. /// <param name="yA">Ordinate of the first point of the line segment</param>
  746. /// <param name="xB">Abscissa of the second point of the line segment</param>
  747. /// <param name="yB">Ordinate of the second point of the line segment</param>
  748. /// <returns>true if the two line segments intersect</returns>
  749. private bool IntersectOutline(double xA, double yA, double xB, double yB)
  750. {
  751. if (IntersectArc(xA, yA, xB, yB))
  752. {
  753. return true;
  754. }
  755. if (IsPieSlice)
  756. {
  757. return Intersect(Cx, Cy, X1, Y1, xA, yA, xB, yB) || Intersect(Cx, Cy, X2, Y2, xA, yA, xB, yB);
  758. }
  759. return Intersect(X1, Y1, X2, Y2, xA, yA, xB, yB);
  760. }
  761. /// <summary>
  762. /// Tests if the interior of a closed path derived from this arc entirely contains the specified rectangular area.
  763. /// The closed path is derived with respect to the IsPieSlice value.
  764. /// </summary>
  765. /// <param name="x">Abscissa of the upper-left corner of the test rectangle</param>
  766. /// <param name="y">Ordinate of the upper-left corner of the test rectangle</param>
  767. /// <param name="w">Width of the test rectangle</param>
  768. /// <param name="h">Height of the test rectangle</param>
  769. /// <returns>true if the interior of a closed path derived from this arc entirely contains the specified rectangular area; false otherwise</returns>
  770. public bool Contains(double x, double y, double w, double h)
  771. {
  772. double xPlusW = x + w;
  773. double yPlusH = y + h;
  774. return Contains(x, y) && Contains(xPlusW, y) && Contains(x, yPlusH) && Contains(xPlusW, yPlusH) &&
  775. !IntersectOutline(x, y, xPlusW, y) && !IntersectOutline(xPlusW,
  776. y, xPlusW, yPlusH) && !IntersectOutline(xPlusW, yPlusH, x, yPlusH) &&
  777. !IntersectOutline(x, yPlusH, x, y);
  778. }
  779. /// <summary>
  780. /// Tests if a specified Point2D is inside the boundary of a closed path derived from this arc.
  781. /// The closed path is derived with respect to the IsPieSlice value.
  782. /// </summary>
  783. /// <param name="p">Test point</param>
  784. /// <returns>true if the specified point is inside a closed path derived from this arc</returns>
  785. public bool Contains(Point p)
  786. {
  787. return Contains(p.X, p.Y);
  788. }
  789. /// <summary>
  790. /// Tests if the interior of a closed path derived from this arc entirely contains the specified Rectangle2D.
  791. /// The closed path is derived with respect to the IsPieSlice value.
  792. /// </summary>
  793. /// <param name="r">Test rectangle</param>
  794. /// <returns>True if the interior of a closed path derived from this arc entirely contains the specified Rectangle2D; false otherwise</returns>
  795. public bool Contains(Rect r)
  796. {
  797. return Contains(r.X, r.Y, r.Width, r.Height);
  798. }
  799. /// <summary>
  800. /// Returns an integer Rectangle that completely encloses the closed path derived from this arc.
  801. /// The closed path is derived with respect to the IsPieSlice value.
  802. /// </summary>
  803. public Rect GetBounds()
  804. {
  805. return new Rect(_xLeft, _yUp, _width, _height);
  806. }
  807. /// <summary>
  808. /// Builds the arc outline using given StreamGeometryContext and default (max) Bezier curve degree and acceptable error of half a pixel (0.5)
  809. /// </summary>
  810. /// <param name="path">A StreamGeometryContext to output the path commands to</param>
  811. public void BuildArc(IStreamGeometryContextImpl path)
  812. {
  813. BuildArc(path, _maxDegree, _defaultFlatness, true);
  814. }
  815. /// <summary>
  816. /// Builds the arc outline using given StreamGeometryContext
  817. /// </summary>
  818. /// <param name="path">A StreamGeometryContext to output the path commands to</param>
  819. /// <param name="degree">degree of the Bezier curve to use</param>
  820. /// <param name="threshold">acceptable error</param>
  821. /// <param name="openNewFigure">if true, a new figure will be started in the specified StreamGeometryContext</param>
  822. public void BuildArc(IStreamGeometryContextImpl path, int degree, double threshold, bool openNewFigure)
  823. {
  824. if (degree < 1 || degree > _maxDegree)
  825. throw new ArgumentException($"degree should be between {1} and {_maxDegree}", nameof(degree));
  826. // find the number of Bezier curves needed
  827. bool found = false;
  828. int n = 1;
  829. double dEta;
  830. double etaB;
  831. while (!found && n < 1024)
  832. {
  833. dEta = (Eta2 - Eta1) / n;
  834. if (dEta <= 0.5 * Math.PI)
  835. {
  836. etaB = Eta1;
  837. found = true;
  838. for (int i = 0; found && i < n; ++i)
  839. {
  840. double etaA = etaB;
  841. etaB += dEta;
  842. found = EstimateError(degree, etaA, etaB) <= threshold;
  843. }
  844. }
  845. n = n << 1;
  846. }
  847. dEta = (Eta2 - Eta1) / n;
  848. etaB = Eta1;
  849. double cosEtaB = Math.Cos(etaB);
  850. double sinEtaB = Math.Sin(etaB);
  851. double aCosEtaB = A * cosEtaB;
  852. double bSinEtaB = B * sinEtaB;
  853. double aSinEtaB = A * sinEtaB;
  854. double bCosEtaB = B * cosEtaB;
  855. double xB = Cx + aCosEtaB * _cosTheta - bSinEtaB * _sinTheta;
  856. double yB = Cy + aCosEtaB * _sinTheta + bSinEtaB * _cosTheta;
  857. double xBDot = -aSinEtaB * _cosTheta - bCosEtaB * _sinTheta;
  858. double yBDot = -aSinEtaB * _sinTheta + bCosEtaB * _cosTheta;
  859. /*
  860. This controls the drawing in case of pies
  861. if (openNewFigure)
  862. {
  863. if (IsPieSlice)
  864. {
  865. path.BeginFigure(new Point(Cx, Cy), false, false);
  866. path.LineTo(new Point(xB, yB), true, true);
  867. }
  868. else
  869. {
  870. path.BeginFigure(new Point(xB, yB), false, false);
  871. }
  872. }
  873. else
  874. {
  875. //path.LineTo(new Point(xB, yB), true, true);
  876. }
  877. */
  878. //otherwise we're supposed to be already at the (xB,yB)
  879. double t = Math.Tan(0.5 * dEta);
  880. double alpha = Math.Sin(dEta) * (Math.Sqrt(4 + 3 * t * t) - 1) / 3;
  881. for (int i = 0; i < n; ++i)
  882. {
  883. //double etaA = etaB;
  884. double xA = xB;
  885. double yA = yB;
  886. double xADot = xBDot;
  887. double yADot = yBDot;
  888. etaB += dEta;
  889. cosEtaB = Math.Cos(etaB);
  890. sinEtaB = Math.Sin(etaB);
  891. aCosEtaB = A * cosEtaB;
  892. bSinEtaB = B * sinEtaB;
  893. aSinEtaB = A * sinEtaB;
  894. bCosEtaB = B * cosEtaB;
  895. xB = Cx + aCosEtaB * _cosTheta - bSinEtaB * _sinTheta;
  896. yB = Cy + aCosEtaB * _sinTheta + bSinEtaB * _cosTheta;
  897. xBDot = -aSinEtaB * _cosTheta - bCosEtaB * _sinTheta;
  898. yBDot = -aSinEtaB * _sinTheta + bCosEtaB * _cosTheta;
  899. if (degree == 1)
  900. {
  901. path.LineTo(new Point(xB, yB));
  902. }
  903. else if (degree == 2)
  904. {
  905. double k = (yBDot * (xB - xA) - xBDot * (yB - yA)) / (xADot * yBDot - yADot * xBDot);
  906. path.QuadraticBezierTo(new Point(xA + k * xADot, yA + k * yADot), new Point(xB, yB));
  907. }
  908. else
  909. {
  910. path.CubicBezierTo(
  911. new Point(xA + alpha * xADot, yA + alpha * yADot),
  912. new Point(xB - alpha * xBDot, yB - alpha * yBDot),
  913. new Point(xB, yB)
  914. );
  915. }
  916. }
  917. if (IsPieSlice)
  918. {
  919. path.LineTo(new Point(Cx, Cy));
  920. }
  921. }
  922. /// <summary>
  923. /// Calculates the angle between two vectors
  924. /// </summary>
  925. /// <param name="v1">Vector V1</param>
  926. /// <param name="v2">Vector V2</param>
  927. /// <returns>The signed angle between v2 and v1</returns>
  928. static double GetAngle(Vector v1, Vector v2)
  929. {
  930. var scalar = v1 * v2;
  931. return Math.Atan2(v1.X * v2.Y - v2.X * v1.Y, scalar);
  932. }
  933. /// <summary>
  934. /// Simple matrix used for rotate transforms.
  935. /// At some point I did not trust the WPF Matrix struct, and wrote my own simple one -_-
  936. /// This is supposed to be replaced with proper WPF Matrices everywhere
  937. /// </summary>
  938. private readonly struct SimpleMatrix
  939. {
  940. private readonly double _a, _b, _c, _d;
  941. public SimpleMatrix(double a, double b, double c, double d)
  942. {
  943. _a = a;
  944. _b = b;
  945. _c = c;
  946. _d = d;
  947. }
  948. public static Point operator *(SimpleMatrix m, Point p)
  949. {
  950. return new Point(m._a * p.X + m._b * p.Y, m._c * p.X + m._d * p.Y);
  951. }
  952. }
  953. /// <summary>
  954. /// ArcTo Helper for StreamGeometryContext
  955. /// </summary>
  956. /// <param name="path">Target path</param>
  957. /// <param name="p1">Start point</param>
  958. /// <param name="p2">End point</param>
  959. /// <param name="size">Ellipse radii</param>
  960. /// <param name="theta">Ellipse theta (angle measured from the abscissa)</param>
  961. /// <param name="isLargeArc">Large Arc Indicator</param>
  962. /// <param name="clockwise">Clockwise direction flag</param>
  963. public static void BuildArc(IStreamGeometryContextImpl path, Point p1, Point p2, Size size, double theta, bool isLargeArc, bool clockwise)
  964. {
  965. // var orthogonalizer = new RotateTransform(-theta);
  966. var orth = new SimpleMatrix(Math.Cos(theta), Math.Sin(theta), -Math.Sin(theta), Math.Cos(theta));
  967. var rest = new SimpleMatrix(Math.Cos(theta), -Math.Sin(theta), Math.Sin(theta), Math.Cos(theta));
  968. // var restorer = orthogonalizer.Inverse;
  969. // if(restorer == null) throw new InvalidOperationException("Can't get a restorer!");
  970. Point p1S = orth * (new Point((p1.X - p2.X) / 2, (p1.Y - p2.Y) / 2));
  971. double rx = size.Width;
  972. double ry = size.Height;
  973. double rx2 = rx * rx;
  974. double ry2 = ry * ry;
  975. double y1S2 = p1S.Y * p1S.Y;
  976. double x1S2 = p1S.X * p1S.X;
  977. double numerator = rx2*ry2 - rx2*y1S2 - ry2*x1S2;
  978. double denominator = rx2*y1S2 + ry2*x1S2;
  979. if (Math.Abs(denominator) < 1e-8)
  980. {
  981. path.LineTo(p2);
  982. return;
  983. }
  984. if ((numerator / denominator) < 0)
  985. {
  986. double lambda = x1S2/rx2 + y1S2/ry2;
  987. double lambdaSqrt = Math.Sqrt(lambda);
  988. if (lambda > 1)
  989. {
  990. rx *= lambdaSqrt;
  991. ry *= lambdaSqrt;
  992. rx2 = rx*rx;
  993. ry2 = ry*ry;
  994. numerator = rx2 * ry2 - rx2 * y1S2 - ry2 * x1S2;
  995. if (numerator < 0)
  996. numerator = 0;
  997. denominator = rx2 * y1S2 + ry2 * x1S2;
  998. }
  999. }
  1000. double multiplier = Math.Sqrt(numerator / denominator);
  1001. Point mulVec = new Point(rx * p1S.Y / ry, -ry * p1S.X / rx);
  1002. int sign = (clockwise != isLargeArc) ? 1 : -1;
  1003. Point cs = new Point(mulVec.X * multiplier * sign, mulVec.Y * multiplier * sign);
  1004. Vector translation = new Vector((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
  1005. Point c = rest * (cs) + translation;
  1006. // See "http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand
  1007. // how the ellipse center is calculated
  1008. // from here, W3C recommendations from the above link make less sense than Darth Vader pouring
  1009. // some sea water in a water filter while standing in the water confused
  1010. // Therefore, we are on our own with our task of finding out lambda1 and lambda2
  1011. // matching our points p1 and p2.
  1012. // Fortunately it is not so difficult now, when we already know the ellipse centre.
  1013. // We eliminate the offset, making our ellipse zero-centered, then we eliminate the theta,
  1014. // making its Y and X axes the same as global axes. Then we can easily get our angles using
  1015. // good old school formula for angles between vectors.
  1016. // We should remember that this class expects true angles, and not the t-values for ellipse equation.
  1017. // To understand how t-values are obtained, one should see Etas calculation in the constructor code.
  1018. var p1NoOffset = orth * (p1-c);
  1019. var p2NoOffset = orth * (p2-c);
  1020. // if the arc is drawn clockwise, we swap start and end points
  1021. var revisedP1 = clockwise ? p1NoOffset : p2NoOffset;
  1022. var revisedP2 = clockwise ? p2NoOffset : p1NoOffset;
  1023. var thetaStart = GetAngle(new Vector(1, 0), revisedP1);
  1024. var thetaEnd = GetAngle(new Vector(1, 0), revisedP2);
  1025. // Uncomment this to draw a pie
  1026. // path.LineTo(c, true, true);
  1027. // path.LineTo(clockwise ? p1 : p2, true,true);
  1028. path.LineTo(clockwise ? p1 : p2);
  1029. var arc = new EllipticalArc(c.X, c.Y, rx, ry, theta, thetaStart, thetaEnd, false);
  1030. arc.BuildArc(path, arc._maxDegree, arc._defaultFlatness, false);
  1031. //uncomment this to draw a pie
  1032. //path.LineTo(c, true, true);
  1033. }
  1034. /// <summary>
  1035. /// Tests if the interior of the closed path derived from this arc intersects the interior of a specified rectangular area.
  1036. /// The closed path is derived with respect to the IsPieSlice value.
  1037. /// </summary>
  1038. public bool Intersects(double x, double y, double w, double h)
  1039. {
  1040. double xPlusW = x + w;
  1041. double yPlusH = y + h;
  1042. return Contains(x, y) || Contains(xPlusW, y) || Contains(x, yPlusH) || Contains(xPlusW, yPlusH) ||
  1043. IntersectOutline(x, y, xPlusW, y) || IntersectOutline(xPlusW,
  1044. y, xPlusW, yPlusH) || IntersectOutline(xPlusW, yPlusH, x, yPlusH) ||
  1045. IntersectOutline(x, yPlusH, x, y);
  1046. }
  1047. /// <summary>
  1048. /// Tests if the interior of the closed path derived from this arc intersects the interior of a specified rectangular area.
  1049. /// The closed path is derived with respect to the IsPieSlice value.
  1050. /// </summary>
  1051. public bool Intersects(Rect r)
  1052. {
  1053. return Intersects(r.X, r.Y, r.Width, r.Height);
  1054. }
  1055. }
  1056. public static void ArcTo(IStreamGeometryContextImpl streamGeometryContextImpl, Point currentPoint, Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
  1057. {
  1058. EllipticalArc.BuildArc(streamGeometryContextImpl, currentPoint, point, size, rotationAngle*Math.PI/180,
  1059. isLargeArc,
  1060. sweepDirection == SweepDirection.Clockwise);
  1061. }
  1062. }
  1063. }