BezierCurveFlattener.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows;
  4. namespace HandyControl.Expression.Drawing;
  5. internal static class BezierCurveFlattener
  6. {
  7. public const double StandardFlatteningTolerance = 0.25;
  8. private static void DoCubicForwardDifferencing(Point[] controlPoints, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection<Point> resultPolyline, ICollection<double> resultParameters)
  9. {
  10. double num14;
  11. var num = controlPoints[1].X - controlPoints[0].X;
  12. var num2 = controlPoints[1].Y - controlPoints[0].Y;
  13. var num3 = controlPoints[2].X - controlPoints[1].X;
  14. var num4 = controlPoints[2].Y - controlPoints[1].Y;
  15. var num5 = controlPoints[3].X - controlPoints[2].X;
  16. var num6 = controlPoints[3].Y - controlPoints[2].Y;
  17. var num7 = num3 - num;
  18. var num8 = num4 - num2;
  19. var num9 = num5 - num3;
  20. var num10 = num6 - num4;
  21. var num11 = num9 - num7;
  22. var num12 = num10 - num8;
  23. var vector = controlPoints[3].Subtract(controlPoints[0]);
  24. var length = vector.Length;
  25. if (!MathHelper.IsVerySmall(length))
  26. num14 = Math.Max(0.0,
  27. Math.Max(Math.Abs((num7 * vector.Y - num8 * vector.X) / length),
  28. Math.Abs((num9 * vector.Y - num10 * vector.X) / length)));
  29. else
  30. num14 = Math.Max(0.0,
  31. Math.Max(GeometryHelper.Distance(controlPoints[1], controlPoints[0]),
  32. GeometryHelper.Distance(controlPoints[2], controlPoints[0])));
  33. uint num15 = 0;
  34. if (num14 > 0.0)
  35. {
  36. var d = num14 * inverseErrorTolerance;
  37. num15 = d < 2147483647.0 ? Log4UnsignedInt32((uint) (d + 0.5)) : Log4Double(d);
  38. }
  39. var exp = (int) -num15;
  40. var num18 = exp + exp;
  41. var num19 = num18 + exp;
  42. var num20 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num7, num18);
  43. var num21 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num8, num18);
  44. var num22 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num11, num19);
  45. var num23 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num12, num19);
  46. var num24 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num, exp) + num20 +
  47. 0.16666666666666666 * num22;
  48. var num25 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num2, exp) + num21 +
  49. 0.16666666666666666 * num23;
  50. var num26 = 2.0 * num20 + num22;
  51. var num27 = 2.0 * num21 + num23;
  52. var x = controlPoints[0].X;
  53. var y = controlPoints[0].Y;
  54. var item = new Point(0.0, 0.0);
  55. var num30 = 1 << (int) num15;
  56. var num31 = num30 > 0 ? (rightParameter - leftParameter) / num30 : 0.0;
  57. var num32 = leftParameter;
  58. for (var i = 1; i < num30; i++)
  59. {
  60. x += num24;
  61. y += num25;
  62. item.X = x;
  63. item.Y = y;
  64. resultPolyline.Add(item);
  65. num32 += num31;
  66. resultParameters?.Add(num32);
  67. num24 += num26;
  68. num25 += num27;
  69. num26 += num22;
  70. num27 += num23;
  71. }
  72. }
  73. private static void DoCubicMidpointSubdivision(Point[] controlPoints, uint depth, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection<Point> resultPolyline, ICollection<double> resultParameters)
  74. {
  75. Point[] pointArray = { controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3] };
  76. var pointArray2 = new Point[4];
  77. pointArray2[3] = pointArray[3];
  78. pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
  79. pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]);
  80. pointArray[1] = GeometryHelper.Midpoint(pointArray[1], pointArray[0]);
  81. pointArray2[2] = pointArray[3];
  82. pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
  83. pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]);
  84. pointArray2[1] = pointArray[3];
  85. pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]);
  86. pointArray2[0] = pointArray[3];
  87. depth--;
  88. var num = (leftParameter + rightParameter) * 0.5;
  89. if (depth > 0)
  90. {
  91. DoCubicMidpointSubdivision(pointArray, depth, leftParameter, num, inverseErrorTolerance,
  92. resultPolyline, resultParameters);
  93. resultPolyline.Add(pointArray2[0]);
  94. resultParameters?.Add(num);
  95. DoCubicMidpointSubdivision(pointArray2, depth, num, rightParameter, inverseErrorTolerance,
  96. resultPolyline, resultParameters);
  97. }
  98. else
  99. {
  100. DoCubicForwardDifferencing(pointArray, leftParameter, num, inverseErrorTolerance,
  101. resultPolyline, resultParameters);
  102. resultPolyline.Add(pointArray2[0]);
  103. resultParameters?.Add(num);
  104. DoCubicForwardDifferencing(pointArray2, num, rightParameter, inverseErrorTolerance,
  105. resultPolyline, resultParameters);
  106. }
  107. }
  108. private static void EnsureErrorTolerance(ref double errorTolerance)
  109. {
  110. if (errorTolerance <= 0.0)
  111. {
  112. errorTolerance = 0.25;
  113. }
  114. }
  115. public static void FlattenCubic(Point[] controlPoints, double errorTolerance, ICollection<Point> resultPolyline, bool skipFirstPoint, ICollection<double> resultParameters = null)
  116. {
  117. if (resultPolyline == null) throw new ArgumentNullException(nameof(resultPolyline));
  118. if (controlPoints == null) throw new ArgumentNullException(nameof(controlPoints));
  119. if (controlPoints.Length != 4) throw new ArgumentOutOfRangeException(nameof(controlPoints));
  120. EnsureErrorTolerance(ref errorTolerance);
  121. if (!skipFirstPoint)
  122. {
  123. resultPolyline.Add(controlPoints[0]);
  124. resultParameters?.Add(0.0);
  125. }
  126. if (IsCubicChordMonotone(controlPoints, errorTolerance * errorTolerance))
  127. {
  128. var flattener = new AdaptiveForwardDifferencingCubicFlattener(controlPoints, errorTolerance,
  129. errorTolerance, true);
  130. var p = new Point();
  131. var u = 0.0;
  132. while (flattener.Next(ref p, ref u))
  133. {
  134. resultPolyline.Add(p);
  135. resultParameters?.Add(u);
  136. }
  137. }
  138. else
  139. {
  140. var x = controlPoints[3].X - controlPoints[2].X + controlPoints[1].X - controlPoints[0].X;
  141. var y = controlPoints[3].Y - controlPoints[2].Y + controlPoints[1].Y - controlPoints[0].Y;
  142. var num4 = 1.0 / errorTolerance;
  143. var depth = Log8UnsignedInt32((uint) (MathHelper.Hypotenuse(x, y) * num4 + 0.5));
  144. if (depth > 0) depth--;
  145. if (depth > 0)
  146. DoCubicMidpointSubdivision(controlPoints, depth, 0.0, 1.0, 0.75 * num4, resultPolyline,
  147. resultParameters);
  148. else
  149. DoCubicForwardDifferencing(controlPoints, 0.0, 1.0, 0.75 * num4, resultPolyline,
  150. resultParameters);
  151. }
  152. resultPolyline.Add(controlPoints[3]);
  153. resultParameters?.Add(1.0);
  154. }
  155. public static void FlattenQuadratic(Point[] controlPoints, double errorTolerance, ICollection<Point> resultPolyline, bool skipFirstPoint, ICollection<double> resultParameters = null)
  156. {
  157. if (resultPolyline == null)
  158. {
  159. throw new ArgumentNullException(nameof(resultPolyline));
  160. }
  161. if (controlPoints == null)
  162. {
  163. throw new ArgumentNullException(nameof(controlPoints));
  164. }
  165. if (controlPoints.Length != 3)
  166. {
  167. throw new ArgumentOutOfRangeException(nameof(controlPoints));
  168. }
  169. EnsureErrorTolerance(ref errorTolerance);
  170. Point[] pointArray = { controlPoints[0], GeometryHelper.Lerp(controlPoints[0], controlPoints[1], 0.66666666666666663), GeometryHelper.Lerp(controlPoints[1], controlPoints[2], 0.33333333333333331), controlPoints[2] };
  171. FlattenCubic(pointArray, errorTolerance, resultPolyline, skipFirstPoint, resultParameters);
  172. }
  173. private static bool IsCubicChordMonotone(Point[] controlPoints, double squaredTolerance)
  174. {
  175. double num = GeometryHelper.SquaredDistance(controlPoints[0], controlPoints[3]);
  176. if (num <= squaredTolerance)
  177. {
  178. return false;
  179. }
  180. Vector lhs = controlPoints[3].Subtract(controlPoints[0]);
  181. Vector rhs = controlPoints[1].Subtract(controlPoints[0]);
  182. double num2 = GeometryHelper.Dot(lhs, rhs);
  183. if ((num2 < 0.0) || (num2 > num))
  184. {
  185. return false;
  186. }
  187. Vector vector3 = controlPoints[2].Subtract(controlPoints[0]);
  188. double num3 = GeometryHelper.Dot(lhs, vector3);
  189. if ((num3 < 0.0) || (num3 > num))
  190. {
  191. return false;
  192. }
  193. if (num2 > num3)
  194. {
  195. return false;
  196. }
  197. return true;
  198. }
  199. private static uint Log4Double(double d)
  200. {
  201. uint num = 0;
  202. while (d > 1.0)
  203. {
  204. d *= 0.25;
  205. num++;
  206. }
  207. return num;
  208. }
  209. private static uint Log4UnsignedInt32(uint i)
  210. {
  211. uint num = 0;
  212. while (i > 0)
  213. {
  214. i = i >> 2;
  215. num++;
  216. }
  217. return num;
  218. }
  219. private static uint Log8UnsignedInt32(uint i)
  220. {
  221. uint num = 0;
  222. while (i > 0)
  223. {
  224. i = i >> 3;
  225. num++;
  226. }
  227. return num;
  228. }
  229. private class AdaptiveForwardDifferencingCubicFlattener
  230. {
  231. private double _aX;
  232. private double _aY;
  233. private double _bX;
  234. private double _bY;
  235. private double _cX;
  236. private double _cY;
  237. private readonly double _distanceTolerance;
  238. private readonly bool _doParameters;
  239. private double _dParameter;
  240. private double _dX;
  241. private double _dY;
  242. private readonly double _flatnessTolerance;
  243. private int _numSteps;
  244. private double _parameter;
  245. internal AdaptiveForwardDifferencingCubicFlattener(Point[] controlPoints,
  246. double flatnessTolerance, double distanceTolerance, bool doParameters)
  247. {
  248. _numSteps = 1;
  249. _dParameter = 1.0;
  250. _flatnessTolerance = 3.0 * flatnessTolerance;
  251. _distanceTolerance = distanceTolerance;
  252. _doParameters = doParameters;
  253. _aX = -controlPoints[0].X + 3.0 * (controlPoints[1].X - controlPoints[2].X) +
  254. controlPoints[3].X;
  255. _aY = -controlPoints[0].Y + 3.0 * (controlPoints[1].Y - controlPoints[2].Y) +
  256. controlPoints[3].Y;
  257. _bX = 3.0 * (controlPoints[0].X - 2.0 * controlPoints[1].X + controlPoints[2].X);
  258. _bY = 3.0 * (controlPoints[0].Y - 2.0 * controlPoints[1].Y + controlPoints[2].Y);
  259. _cX = 3.0 * (-controlPoints[0].X + controlPoints[1].X);
  260. _cY = 3.0 * (-controlPoints[0].Y + controlPoints[1].Y);
  261. _dX = controlPoints[0].X;
  262. _dY = controlPoints[0].Y;
  263. }
  264. private void DoubleStepSize()
  265. {
  266. _aX *= 8.0;
  267. _aY *= 8.0;
  268. _bX *= 4.0;
  269. _bY *= 4.0;
  270. _cX += _cX;
  271. _cY += _cY;
  272. if (_doParameters) _dParameter *= 2.0;
  273. _numSteps = _numSteps >> 1;
  274. }
  275. private void HalveStepSize()
  276. {
  277. _aX *= 0.125;
  278. _aY *= 0.125;
  279. _bX *= 0.25;
  280. _bY *= 0.25;
  281. _cX *= 0.5;
  282. _cY *= 0.5;
  283. if (_doParameters) _dParameter *= 0.5;
  284. _numSteps = _numSteps << 1;
  285. }
  286. private void IncrementDifferencesAndParameter()
  287. {
  288. _dX = _aX + _bX + _cX + _dX;
  289. _dY = _aY + _bY + _cY + _dY;
  290. _cX = _aX + _aX + _aX + _bX + _bX + _cX;
  291. _cY = _aY + _aY + _aY + _bY + _bY + _cY;
  292. _bX = _aX + _aX + _aX + _bX;
  293. _bY = _aY + _aY + _aY + _bY;
  294. _numSteps--;
  295. _parameter += _dParameter;
  296. }
  297. private bool MustSubdivide(double flatnessTolerance)
  298. {
  299. var num = -(_aY + _bY + _cY);
  300. var num2 = _aX + _bX + _cX;
  301. var num3 = Math.Abs(num) + Math.Abs(num2);
  302. if (num3 <= _distanceTolerance) return false;
  303. num3 *= flatnessTolerance;
  304. if (Math.Abs(_cX * num + _cY * num2) <= num3 &&
  305. Math.Abs((_bX + _cX + _cX) * num + (_bY + _cY + _cY) * num2) <= num3) return false;
  306. return true;
  307. }
  308. // ReSharper disable once RedundantAssignment
  309. internal bool Next(ref Point p, ref double u)
  310. {
  311. while (MustSubdivide(_flatnessTolerance)) HalveStepSize();
  312. if ((_numSteps & 1) == 0)
  313. while (_numSteps > 1 && !MustSubdivide(_flatnessTolerance * 0.25))
  314. DoubleStepSize();
  315. IncrementDifferencesAndParameter();
  316. p.X = _dX;
  317. p.Y = _dY;
  318. u = _parameter;
  319. return _numSteps != 0;
  320. }
  321. }
  322. }