357 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |