Files
HandyControl/Net40/HandyControl_Net40/Expression/Drawing/BezierCurveFlattener.cs
T
2019-01-21 11:12:38 +08:00

357 lines
14 KiB
C#

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<Point> resultPolyline, ICollection<double> 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<Point> resultPolyline, ICollection<double> 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<Point> resultPolyline, bool skipFirstPoint, ICollection<double> 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<Point> resultPolyline, bool skipFirstPoint, ICollection<double> 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;
}
}
}
}