using System; using System.Collections.Generic; using System.Windows; namespace HandyControl.Expression.Drawing { internal static class BezierCurveFlattener { public const double StandardFlatteningTolerance = 0.25; private static void DoCubicForwardDifferencing(Point[] controlPoints, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection resultPolyline, ICollection resultParameters) { double num14; var num = controlPoints[1].X - controlPoints[0].X; var num2 = controlPoints[1].Y - controlPoints[0].Y; var num3 = controlPoints[2].X - controlPoints[1].X; var num4 = controlPoints[2].Y - controlPoints[1].Y; var num5 = controlPoints[3].X - controlPoints[2].X; var num6 = controlPoints[3].Y - controlPoints[2].Y; var num7 = num3 - num; var num8 = num4 - num2; var num9 = num5 - num3; var num10 = num6 - num4; var num11 = num9 - num7; var num12 = num10 - num8; var vector = controlPoints[3].Subtract(controlPoints[0]); var length = vector.Length; if (!MathHelper.IsVerySmall(length)) num14 = Math.Max(0.0, Math.Max(Math.Abs((num7 * vector.Y - num8 * vector.X) / length), Math.Abs((num9 * vector.Y - num10 * vector.X) / length))); else num14 = Math.Max(0.0, Math.Max(GeometryHelper.Distance(controlPoints[1], controlPoints[0]), GeometryHelper.Distance(controlPoints[2], controlPoints[0]))); uint num15 = 0; if (num14 > 0.0) { var d = num14 * inverseErrorTolerance; num15 = d < 2147483647.0 ? Log4UnsignedInt32((uint) (d + 0.5)) : Log4Double(d); } var exp = (int) -num15; var num18 = exp + exp; var num19 = num18 + exp; var num20 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num7, num18); var num21 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num8, num18); var num22 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num11, num19); var num23 = MathHelper.DoubleFromMantissaAndExponent(6.0 * num12, num19); var num24 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num, exp) + num20 + 0.16666666666666666 * num22; var num25 = MathHelper.DoubleFromMantissaAndExponent(3.0 * num2, exp) + num21 + 0.16666666666666666 * num23; var num26 = 2.0 * num20 + num22; var num27 = 2.0 * num21 + num23; var x = controlPoints[0].X; var y = controlPoints[0].Y; var item = new Point(0.0, 0.0); var num30 = 1 << (int) num15; var num31 = num30 > 0 ? (rightParameter - leftParameter) / num30 : 0.0; var num32 = leftParameter; for (var i = 1; i < num30; i++) { x += num24; y += num25; item.X = x; item.Y = y; resultPolyline.Add(item); num32 += num31; resultParameters?.Add(num32); num24 += num26; num25 += num27; num26 += num22; num27 += num23; } } private static void DoCubicMidpointSubdivision(Point[] controlPoints, uint depth, double leftParameter, double rightParameter, double inverseErrorTolerance, ICollection resultPolyline, ICollection resultParameters) { Point[] pointArray = { controlPoints[0], controlPoints[1], controlPoints[2], controlPoints[3] }; var pointArray2 = new Point[4]; pointArray2[3] = pointArray[3]; pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]); pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]); pointArray[1] = GeometryHelper.Midpoint(pointArray[1], pointArray[0]); pointArray2[2] = pointArray[3]; pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]); pointArray[2] = GeometryHelper.Midpoint(pointArray[2], pointArray[1]); pointArray2[1] = pointArray[3]; pointArray[3] = GeometryHelper.Midpoint(pointArray[3], pointArray[2]); pointArray2[0] = pointArray[3]; depth--; var num = (leftParameter + rightParameter) * 0.5; if (depth > 0) { DoCubicMidpointSubdivision(pointArray, depth, leftParameter, num, inverseErrorTolerance, resultPolyline, resultParameters); resultPolyline.Add(pointArray2[0]); resultParameters?.Add(num); DoCubicMidpointSubdivision(pointArray2, depth, num, rightParameter, inverseErrorTolerance, resultPolyline, resultParameters); } else { DoCubicForwardDifferencing(pointArray, leftParameter, num, inverseErrorTolerance, resultPolyline, resultParameters); resultPolyline.Add(pointArray2[0]); resultParameters?.Add(num); DoCubicForwardDifferencing(pointArray2, num, rightParameter, inverseErrorTolerance, resultPolyline, resultParameters); } } private static void EnsureErrorTolerance(ref double errorTolerance) { if (errorTolerance <= 0.0) { errorTolerance = 0.25; } } public static void FlattenCubic(Point[] controlPoints, double errorTolerance, ICollection resultPolyline, bool skipFirstPoint, ICollection resultParameters = null) { if (resultPolyline == null) throw new ArgumentNullException(nameof(resultPolyline)); if (controlPoints == null) throw new ArgumentNullException(nameof(controlPoints)); if (controlPoints.Length != 4) throw new ArgumentOutOfRangeException(nameof(controlPoints)); EnsureErrorTolerance(ref errorTolerance); if (!skipFirstPoint) { resultPolyline.Add(controlPoints[0]); resultParameters?.Add(0.0); } if (IsCubicChordMonotone(controlPoints, errorTolerance * errorTolerance)) { var flattener = new AdaptiveForwardDifferencingCubicFlattener(controlPoints, errorTolerance, errorTolerance, true); var p = new Point(); var u = 0.0; while (flattener.Next(ref p, ref u)) { resultPolyline.Add(p); resultParameters?.Add(u); } } else { var x = controlPoints[3].X - controlPoints[2].X + controlPoints[1].X - controlPoints[0].X; var y = controlPoints[3].Y - controlPoints[2].Y + controlPoints[1].Y - controlPoints[0].Y; var num4 = 1.0 / errorTolerance; var depth = Log8UnsignedInt32((uint) (MathHelper.Hypotenuse(x, y) * num4 + 0.5)); if (depth > 0) depth--; if (depth > 0) DoCubicMidpointSubdivision(controlPoints, depth, 0.0, 1.0, 0.75 * num4, resultPolyline, resultParameters); else DoCubicForwardDifferencing(controlPoints, 0.0, 1.0, 0.75 * num4, resultPolyline, resultParameters); } resultPolyline.Add(controlPoints[3]); resultParameters?.Add(1.0); } public static void FlattenQuadratic(Point[] controlPoints, double errorTolerance, ICollection resultPolyline, bool skipFirstPoint, ICollection resultParameters = null) { if (resultPolyline == null) { throw new ArgumentNullException(nameof(resultPolyline)); } if (controlPoints == null) { throw new ArgumentNullException(nameof(controlPoints)); } if (controlPoints.Length != 3) { throw new ArgumentOutOfRangeException(nameof(controlPoints)); } EnsureErrorTolerance(ref errorTolerance); Point[] pointArray = { controlPoints[0], GeometryHelper.Lerp(controlPoints[0], controlPoints[1], 0.66666666666666663), GeometryHelper.Lerp(controlPoints[1], controlPoints[2], 0.33333333333333331), controlPoints[2] }; FlattenCubic(pointArray, errorTolerance, resultPolyline, skipFirstPoint, resultParameters); } private static bool IsCubicChordMonotone(Point[] controlPoints, double squaredTolerance) { double num = GeometryHelper.SquaredDistance(controlPoints[0], controlPoints[3]); if (num <= squaredTolerance) { return false; } Vector lhs = controlPoints[3].Subtract(controlPoints[0]); Vector rhs = controlPoints[1].Subtract(controlPoints[0]); double num2 = GeometryHelper.Dot(lhs, rhs); if ((num2 < 0.0) || (num2 > num)) { return false; } Vector vector3 = controlPoints[2].Subtract(controlPoints[0]); double num3 = GeometryHelper.Dot(lhs, vector3); if ((num3 < 0.0) || (num3 > num)) { return false; } if (num2 > num3) { return false; } return true; } private static uint Log4Double(double d) { uint num = 0; while (d > 1.0) { d *= 0.25; num++; } return num; } private static uint Log4UnsignedInt32(uint i) { uint num = 0; while (i > 0) { i = i >> 2; num++; } return num; } private static uint Log8UnsignedInt32(uint i) { uint num = 0; while (i > 0) { i = i >> 3; num++; } return num; } private class AdaptiveForwardDifferencingCubicFlattener { private double _aX; private double _aY; private double _bX; private double _bY; private double _cX; private double _cY; private readonly double _distanceTolerance; private readonly bool _doParameters; private double _dParameter; private double _dX; private double _dY; private readonly double _flatnessTolerance; private int _numSteps; private double _parameter; internal AdaptiveForwardDifferencingCubicFlattener(Point[] controlPoints, double flatnessTolerance, double distanceTolerance, bool doParameters) { _numSteps = 1; _dParameter = 1.0; _flatnessTolerance = 3.0 * flatnessTolerance; _distanceTolerance = distanceTolerance; _doParameters = doParameters; _aX = -controlPoints[0].X + 3.0 * (controlPoints[1].X - controlPoints[2].X) + controlPoints[3].X; _aY = -controlPoints[0].Y + 3.0 * (controlPoints[1].Y - controlPoints[2].Y) + controlPoints[3].Y; _bX = 3.0 * (controlPoints[0].X - 2.0 * controlPoints[1].X + controlPoints[2].X); _bY = 3.0 * (controlPoints[0].Y - 2.0 * controlPoints[1].Y + controlPoints[2].Y); _cX = 3.0 * (-controlPoints[0].X + controlPoints[1].X); _cY = 3.0 * (-controlPoints[0].Y + controlPoints[1].Y); _dX = controlPoints[0].X; _dY = controlPoints[0].Y; } private void DoubleStepSize() { _aX *= 8.0; _aY *= 8.0; _bX *= 4.0; _bY *= 4.0; _cX += _cX; _cY += _cY; if (_doParameters) _dParameter *= 2.0; _numSteps = _numSteps >> 1; } private void HalveStepSize() { _aX *= 0.125; _aY *= 0.125; _bX *= 0.25; _bY *= 0.25; _cX *= 0.5; _cY *= 0.5; if (_doParameters) _dParameter *= 0.5; _numSteps = _numSteps << 1; } private void IncrementDifferencesAndParameter() { _dX = _aX + _bX + _cX + _dX; _dY = _aY + _bY + _cY + _dY; _cX = _aX + _aX + _aX + _bX + _bX + _cX; _cY = _aY + _aY + _aY + _bY + _bY + _cY; _bX = _aX + _aX + _aX + _bX; _bY = _aY + _aY + _aY + _bY; _numSteps--; _parameter += _dParameter; } private bool MustSubdivide(double flatnessTolerance) { var num = -(_aY + _bY + _cY); var num2 = _aX + _bX + _cX; var num3 = Math.Abs(num) + Math.Abs(num2); if (num3 <= _distanceTolerance) return false; num3 *= flatnessTolerance; if (Math.Abs(_cX * num + _cY * num2) <= num3 && Math.Abs((_bX + _cX + _cX) * num + (_bY + _cY + _cY) * num2) <= num3) return false; return true; } // ReSharper disable once RedundantAssignment internal bool Next(ref Point p, ref double u) { while (MustSubdivide(_flatnessTolerance)) HalveStepSize(); if ((_numSteps & 1) == 0) while (_numSteps > 1 && !MustSubdivide(_flatnessTolerance * 0.25)) DoubleStepSize(); IncrementDifferencesAndParameter(); p.X = _dX; p.Y = _dY; u = _parameter; return _numSteps != 0; } } } }