diff --git a/demo/fitCurve.html b/demo/fitCurve.html
new file mode 100644
index 0000000..6c341ff
--- /dev/null
+++ b/demo/fitCurve.html
@@ -0,0 +1,54 @@
+
+
+
+
+ 三次bezier曲线拟合
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/js/fitCurve.js b/demo/js/fitCurve.js
new file mode 100644
index 0000000..77c793a
--- /dev/null
+++ b/demo/js/fitCurve.js
@@ -0,0 +1,161 @@
+/**
+ * @file bezier曲线拟合
+ * @author mengke01(kekee000@gmail.com)
+ */
+
+define(
+ function(require) {
+
+ var fitCurve = require('graphics/image/fitCurve');
+
+
+
+ /**
+ * 计算bezier曲线B0参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B0(u) {
+ var tmp = 1.0 - u;
+ return (tmp * tmp * tmp);
+ }
+
+ /**
+ * 计算bezier曲线B1参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B1(u) {
+ var tmp = 1.0 - u;
+ return (3 * u * (tmp * tmp));
+ }
+
+ /**
+ * 计算bezier曲线B2参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B2(u) {
+ var tmp = 1.0 - u;
+ return (3 * u * u * tmp);
+ }
+
+ /**
+ * 计算bezier曲线B3参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B3(u) {
+ return (u * u * u);
+ }
+
+ var entry = {
+
+ /**
+ * 初始化
+ */
+ init: function () {
+
+ var canvas = $('#canvas').get(0);
+ var ctx = canvas.getContext('2d');
+ var width = canvas.offsetWidth;
+ var height = canvas.offsetHeight;
+
+ var points =[{"x":167,"y":449},{"x":233,"y":331},{"x":431,"y":332},{"x":481,"y":417}];
+
+ $(points).each(function(index, item) {
+ $('[data-index="'+index+'"]').css({
+ left: points[index].x,
+ top: points[index].y
+ })
+ });
+
+ var point;
+
+ $('.point').on('mousedown', function(e) {
+ point = $(this);
+ });
+
+ $(document.body).on('mousemove', onMove);
+ $(document.body).on('mouseup', function(e) {
+ onMove.call(this, e);
+ point = null;
+ });
+
+ function onMove(e) {
+ var px = e.pageX;
+ var py = e.pageY;
+ if(point) {
+ point.css({
+ left: px,
+ top: py
+ });
+ var p = points[+point.attr('data-index')];
+ p.x = px;
+ p.y = py;
+ draw();
+ }
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, width, height);
+
+ ctx.beginPath();
+ ctx.strokeStyle='gray';
+ ctx.moveTo(points[0].x, points[0].y);
+ ctx.bezierCurveTo(points[1].x,points[1].y,points[2].x,points[2].y,points[3].x,points[3].y);
+ ctx.stroke();
+
+ var curvePoints = [];
+ var count = 40;
+ var tSegment = 1 / count;
+ for (var i = 0; i < count; i++) {
+ var t = tSegment * i;
+ curvePoints.push({
+ x: points[0].x * B0(t) + points[1].x * B1(t) + points[2].x * B2(t) + points[3].x * B3(t),
+ y: points[0].y * B0(t) + points[1].y * B1(t) + points[2].y * B2(t) + points[3].y * B3(t)
+ });
+ }
+
+ ctx.fillStyle='green';
+ for (var i = 0; i < count; i++) {
+ ctx.fillRect(curvePoints[i].x, curvePoints[i].y, 2, 2);
+ }
+
+ var result = fitCurve(curvePoints, 100);
+ result.unshift(points[0]);
+ console.log(result);
+
+ ctx.strokeStyle='blue';
+ for (var i = 3; i < result.length - 1; i+=3) {
+ var p = {
+ x: (result[i].x + result[i + 1].x) / 2,
+ y: (result[i].y + result[i + 1].y) / 2
+ };
+ result.push(p);
+ i++;
+ }
+
+ ctx.moveTo(result[0].x, result[0].y);
+ for (var i = 0; i < result.length - 1; i+=4) {
+ ctx.bezierCurveTo(result[i + 1].x, result[i + 1].y, result[i + 2].x, result[i + 2].y, result[i + 3].x, result[i + 3].y);
+ ctx.moveTo(result[i + 3].x, result[i + 3].y);
+ }
+
+ ctx.stroke();
+
+ }
+
+ draw();
+ }
+ };
+
+ entry.init();
+
+ return entry;
+ }
+);
diff --git a/src/graphics/image/fitCurve.js b/src/graphics/image/fitCurve.js
new file mode 100644
index 0000000..51729cb
--- /dev/null
+++ b/src/graphics/image/fitCurve.js
@@ -0,0 +1,534 @@
+/**
+ * @file 三次bezier曲线点拟合
+ *
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * from "Graphics Gems", Academic Press, 1990
+ *
+ * @author mengke01(kekee000@gmail.com)
+ */
+
+
+define(
+ function (require) {
+
+ var MAX_ITERATIONS = 4; // 最大的迭代次数
+
+ /**
+ * 将点转换成余弦值
+ *
+ * @param {Object} p 点
+ * @return {Object} 余弦值
+ */
+ function normalize(p) {
+ var factor = 1 / Math.sqrt(p.x * p.x + p.y * p.y);
+ p.x = p.x * factor;
+ p.y = p.y * factor;
+ return p;
+ }
+
+ /**
+ * 求两点距离
+ *
+ * @param {Object} p0 p0
+ * @param {Object} p1 p1
+ * @return {number} 距离值
+ */
+ function dist(p0, p1) {
+ return Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2));
+ }
+
+ /**
+ * 计算左侧正切点
+ *
+ * @param {Array} points 点集合
+ * @param {number} end 点索引
+ * @return {Object} 点
+ */
+ function computeLeftTangent(points, end) {
+ var x = points[end + 1].x - points[end].x;
+ var y = points[end + 1].y - points[end].y;
+ return normalize({
+ x: x,
+ y: y
+ });
+ }
+
+ /**
+ * 计算右侧正切点
+ *
+ * @param {Array} points 点集合
+ * @param {number} end 点索引
+ * @return {Object} 点
+ */
+ function computeRightTangent(points, end) {
+ var x = points[end - 1].x - points[end].x;
+ var y = points[end - 1].y - points[end].y;
+ return normalize({
+ x: x,
+ y: y
+ });
+ }
+
+ /**
+ * 计算中间正切点
+ *
+ * @param {Array} points 点集合
+ * @param {number} end 点索引
+ * @return {Object} 点
+ */
+ function computeCenterTangent(points, center) {
+ var x = (points[center - 1].x - points[center + 1].x) / 2;
+ var y = (points[center - 1].y - points[center + 1].y) / 2;
+ return normalize({
+ x: x,
+ y: y
+ });
+ }
+
+ /**
+ * 计算bezier曲线B0参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B0(u) {
+ var tmp = 1.0 - u;
+ return (tmp * tmp * tmp);
+ }
+
+ /**
+ * 计算bezier曲线B1参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B1(u) {
+ var tmp = 1.0 - u;
+ return (3 * u * (tmp * tmp));
+ }
+
+ /**
+ * 计算bezier曲线B2参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B2(u) {
+ var tmp = 1.0 - u;
+ return (3 * u * u * tmp);
+ }
+
+ /**
+ * 计算bezier曲线B3参数
+ *
+ * @param {number} u t值
+ * @return {number}
+ */
+ function B3(u) {
+ return (u * u * u);
+ }
+
+ /**
+ * 计算向量内积
+ *
+ * @param {Object} a a点
+ * @param {Object} b b点
+ */
+ function v2Dot(a, b) {
+ return (a.x * b.x) + (a.y * b.y);
+ }
+
+ /*
+ * ChordLengthParameterize :
+ * Assign parameter values to digitized points
+ * using relative distances between points.
+ * 使用线段法计算各个点上的t值
+ *
+ * @param {Array} points 点集合
+ * @param {number} first 点索引
+ * @param {number} last 点索引
+ * @return {Array} t值集合
+ */
+ function chordLengthParameterize(points, first, last)
+ {
+ var i;
+ var u = [0];
+ for (i = first + 1; i <= last; i++) {
+ u[i - first] = u[i - first - 1] + dist(points[i - 1], points[i]);
+ }
+ var length = u[last - first];
+ for (i = first + 1; i <= last; i++) {
+ u[i - first] = u[i - first] / length;
+ }
+
+ return u;
+ }
+
+ /*
+ * Bezier :
+ * Evaluate a Bezier curve at a particular parameter value
+ *
+ */
+ /**
+ * 计算bezier点在特定位置起始点位置,使用连接点法,参考bezier曲线定义
+ *
+ * @param {number} degree 深度
+ * @param {Array} bezierPoints bezier点集合
+ * @param {number} t t值
+ */
+ function bezierII(degree, bezierPoints, t) {
+ var i;
+ var j;
+ var temp = []; // Local copy of control points
+
+ for (i = 0; i <= degree; i++)
+ {
+ temp[i] = {
+ x: bezierPoints[i].x,
+ y: bezierPoints[i].y
+ };
+ }
+
+ /* Triangle computation */
+ for (i = 1; i <= degree; i++)
+ {
+ for (j = 0; j <= degree - i; j++)
+ {
+ temp[j].x = (1.0 - t) * temp[j].x + t * temp[j + 1].x;
+ temp[j].y = (1.0 - t) * temp[j].y + t * temp[j + 1].y;
+ }
+ }
+
+ return temp[0];
+ }
+
+
+ /**
+ * 计算最大错误点的位置和错误值
+ *
+ * @param {Array} points 点集合
+ * @param {number} first 起始点
+ * @param {number} last 结束点
+ * @param {Array} bezCurve bezier点参数
+ * @param {Array} u t参数集合
+ *
+ * @return {Object} 最大错误点的位置和错误值
+ */
+ function computeMaxError(points, first, last, bezCurve, u, splitPoint) {
+ var maxDist = 0; // Maximum error
+
+ var splitPoint = Math.floor((last - first + 1) / 2);
+ for (var i = first + 1; i < last; i++) {
+ var p = bezierII(3, bezCurve, u[i - first]);
+ var x = p.x - points[i].x;
+ var y = p.y - points[i].y;
+ var dist = x * x + y * y;
+ if (dist >= maxDist) {
+ maxDist = dist;
+ splitPoint = i;
+ }
+ }
+
+ return {
+ dist: maxDist,
+ index: splitPoint
+ };
+ }
+
+ /**
+ * 牛顿迭代法求优化bezier优化点
+ *
+ * @param {Array} Q bezier参数点
+ * @param {Object} P 当前点
+ * @param {number} u 当前点t值
+ * @return {u} 优化后的t值
+ */
+ function newtonRaphsonRootFind(Q, P, u) {
+ var numerator;
+ var denominator;
+ var Q1 = []; // Q'
+ var Q2 = []; // Q''
+ var Q_u; // u evaluated at Q, Q', & Q''
+ var Q1_u;
+ var Q2_u;
+ var i;
+
+ Q_u = bezierII(3, Q, u);
+
+ /* Generate control vertices for Q' */
+ for (i = 0; i <= 2; i++) {
+ Q1[i] = {
+ x: (Q[i + 1].x - Q[i].x) * 3.0,
+ y: (Q[i + 1].y - Q[i].y) * 3.0
+ };
+ }
+
+ /* Generate control vertices for Q'' */
+ for (i = 0; i <= 1; i++) {
+ Q2[i] = {
+ x: (Q[i + 1].x - Q[i].x) * 2.0,
+ y: (Q[i + 1].y - Q[i].y) * 2.0
+ };
+ }
+
+ /* Compute Q'(u) and Q''(u) */
+ Q1_u = bezierII(2, Q1, u);
+ Q2_u = bezierII(1, Q2, u);
+
+ /* Compute f(u)/f'(u) */
+ numerator = (Q_u.x - P.x) * (Q1_u.x) + (Q_u.y - P.y) * (Q1_u.y);
+ denominator = (Q1_u.x) * (Q1_u.x)
+ + (Q1_u.y) * (Q1_u.y)
+ + (Q_u.x - P.x) * (Q2_u.x)
+ + (Q_u.y - P.y) * (Q2_u.y);
+
+ if (denominator === 0) {
+ return u;
+ }
+
+ /* u = u - f(u)/f'(u) */
+ return u - (numerator / denominator);
+ }
+
+ /**
+ * 对当前的bezier参数和t值集合进行优化获取新的t值集合
+ *
+ * @param {Array} points 点集合
+ * @param {number} first 起始索引
+ * @param {number} last 结束索引
+ * @param {Array} u t值集合
+ * @param {Array} bezCurve bezier曲线点集
+ * @return {Array} 优化后的t值集合
+ */
+ function reparameterize(points, first, last, u, bezCurve) {
+ var uPrime = [];
+ for (var i = first; i <= last; i++) {
+ uPrime[i - first] = newtonRaphsonRootFind(bezCurve, points[i], u[i - first]);
+ }
+ return uPrime;
+ }
+
+
+ /**
+ * 根据当前参数集合生成bezier曲线点
+ *
+ * @param {Array} points 点集合
+ * @param {number} first 起始点
+ * @param {number} last 结束点
+ * @param {Array} uPrime t参数集合
+ * @param {Object} tHat1 左侧切线点
+ * @param {Object} tHat2 右侧切线点
+ * @return {Array} bezier曲线点
+ */
+ function generateBezier(points, first, last, uPrime, tHat1, tHat2) {
+
+ var A = [];
+ var C = [[0, 0], [0, 0]]; // Matrix C
+ var X = [0, 0]; // Matrix X
+ var tmp; // Utility variable
+ var u;
+ var nPts = last - first + 1; // number of pts in sub-curve
+
+ /* Compute the A's */
+ for (i = 0; i < nPts; i++) {
+ var v1 = {
+ x: tHat1.x,
+ y: tHat1.y
+ };
+ var v2 = {
+ x: tHat2.x,
+ y: tHat2.y
+ };
+ u = uPrime[i];
+ v1.x *= B1(u);
+ v1.y *= B1(u);
+ v2.x *= B2(u);
+ v2.y *= B2(u);
+ A[i] = [v1, v2];
+ }
+
+ var firstPoint = points[first]; // 起始点
+ var lastPoint = points[last]; // 结束点
+ for (var i = 0; i < nPts; i++) {
+ C[0][0] += v2Dot(A[i][0], A[i][0]);
+ C[0][1] += v2Dot(A[i][0], A[i][1]);
+ C[1][0] = C[0][1];
+ C[1][1] += v2Dot(A[i][1], A[i][1]);
+
+ var pi = points[first + i]; // 当前点
+ u = uPrime[i];
+ tmp = {
+ x: pi.x - (firstPoint.x * B0(u) + firstPoint.x * B1(u) + lastPoint.x * B2(u) + lastPoint.x * B3(u)),
+ y: pi.y - (firstPoint.y * B0(u) + firstPoint.y * B1(u) + lastPoint.y * B2(u) + lastPoint.y * B3(u))
+ };
+
+ X[0] += v2Dot(A[i][0], tmp);
+ X[1] += v2Dot(A[i][1], tmp);
+ }
+
+ /* Compute the determinants of C and X */
+ var det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
+ var det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
+ var det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+
+ /* Finally, derive alpha values */
+ var alpha_l = (det_C0_C1 == 0) ? 0.0 : det_X_C1 / det_C0_C1;
+ var alpha_r = (det_C0_C1 == 0) ? 0.0 : det_C0_X / det_C0_C1;
+
+ /* If alpha negative, use the Wu/Barsky heuristic (see text) */
+ /* (if alpha is 0, you get coincident control points that lead to
+ * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
+ var segLength = Math.sqrt(Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2));
+ var epsilon = 1.0e-6 * segLength;
+ var bezCurve = [];
+ bezCurve[0] = firstPoint;
+ bezCurve[3] = lastPoint;
+
+ if (alpha_l < epsilon || alpha_r < epsilon) {
+ /* fall back on standard (probably inaccurate) formula, and subdivide further if needed. */
+ var dist = segLength / 3.0;
+ bezCurve[1] = {
+ x: tHat1.x * dist + firstPoint.x,
+ y: tHat1.y * dist + firstPoint.y
+ };
+ bezCurve[2] = {
+ x: tHat2.x * dist + lastPoint.x,
+ y: tHat2.y * dist + lastPoint.y
+ };
+
+ return bezCurve;
+ }
+
+ /* First and last control points of the Bezier curve are */
+ /* positioned exactly at the first and last data points */
+ /* Control points 1 and 2 are positioned an alpha distance out */
+ /* on the tangent vectors, left and right, respectively */
+
+ bezCurve[1] = {
+ x: tHat1.x * alpha_l + firstPoint.x,
+ y: tHat1.y * alpha_l + firstPoint.y
+ };
+
+ bezCurve[2] = {
+ x: tHat1.x * alpha_r + firstPoint.x,
+ y: tHat1.y * alpha_r + firstPoint.y
+ };
+
+ return bezCurve;
+ }
+
+
+
+ /**
+ * 三次bezier曲线拟合
+ *
+ * @param {Array} points 点集合
+ * @param {number} first 起始点
+ * @param {number} last 结束点
+ * @param {Object} tHat1 起始切线点点
+ * @param {Object} tHat2 右侧切线点
+ * @param {number} error 最大错误
+ * @param {Array} result 结果点集合
+ */
+ function fitCubic(points, first, last, tHat1, tHat2, error, result) {
+ var bezCurve = []; // Control points of fitted Bezier curve
+ var nPts; // Number of points in subset
+ var i;
+
+ nPts = last - first + 1;
+
+ var firstPoint = points[first]; // 起始点
+ var lastPoint = points[last]; // 结束点
+ var segLength = Math.sqrt(Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2));
+
+ // Use heuristic if region only has two points in it
+ if (nPts == 2) {
+ var dist = segLength / 3.0;
+
+ bezCurve[0] = firstPoint;
+ bezCurve[3] = lastPoint;
+ bezCurve[1] = {
+ x: tHat1.x * dist + firstPoint.x,
+ y: tHat1.y * dist + firstPoint.y
+ };
+ bezCurve[2] = {
+ x: tHat2.x * dist + lastPoint.x,
+ y: tHat2.y * dist + lastPoint.y
+ };
+
+ result.push(bezCurve[1]);
+ result.push(bezCurve[2]);
+ result.push(bezCurve[3]);
+ return;
+ }
+
+ /* Parameterize points, and attempt to fit curve */
+ var uPrime; // Improved parameter values
+ var maxError; // Maximum fitting error
+ var iterationError = error * error; // Error below which you try iterating
+ var u = chordLengthParameterize(points, first, last); // Parameter values for point
+
+ bezCurve = generateBezier(points, first, last, u, tHat1, tHat2);
+ /* Find max deviation of points to fitted curve */
+ var maxError = computeMaxError(points, first, last, bezCurve, u);
+
+ if (maxError.dist < error) {
+ result.push(bezCurve[1]);
+ result.push(bezCurve[2]);
+ result.push(bezCurve[3]);
+ return;
+ }
+
+
+ /* If error not too large, try some reparameterization */
+ /* and iteration */
+ if (maxError.dist < iterationError) {
+ for (i = 0; i < MAX_ITERATIONS; i++) {
+ uPrime = reparameterize(points, first, last, u, bezCurve);
+ bezCurve = generateBezier(points, first, last, uPrime, tHat1, tHat2);
+ maxError = computeMaxError(points, first, last, bezCurve, uPrime);
+
+ if (maxError.dist < error) {
+ result.push(bezCurve[1]);
+ result.push(bezCurve[2]);
+ result.push(bezCurve[3]);
+ return;
+ }
+
+ u = uPrime;
+ }
+ }
+
+ /* Fitting failed -- split at max error point and fit recursively */
+ var splitPoint = maxError.index;
+ var tHatCenter = computeCenterTangent(points, splitPoint); // Unit tangent vector at splitPoint
+ fitCubic(points, first, splitPoint, tHat1, tHatCenter, error, result);
+ tHatCenter.x = -tHatCenter.x;
+ tHatCenter.y = -tHatCenter.y;
+ fitCubic(points, splitPoint, last, tHatCenter, tHat2, error, result);
+ }
+
+
+
+ /**
+ * 三次bezier曲线点拟合点集
+ *
+ * @param {Array} points 点集合
+ * @param {number} error 最大错误距离
+ *
+ * @return {Array} 结果点集
+ */
+ function fitCurve(points, error) {
+ var last = points.length - 1;
+ var tHat1 = computeLeftTangent(points, 0);
+ var tHat2 = computeRightTangent(points, last);
+ var result = [];
+ fitCubic(points, 0, last, tHat1, tHat2, error, result);
+ return result;
+ }
+
+
+ return fitCurve;
+ }
+);
diff --git a/test/fitcurve.cs b/test/fitcurve.cs
new file mode 100644
index 0000000..53715e3
--- /dev/null
+++ b/test/fitcurve.cs
@@ -0,0 +1,499 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Bezier
+{
+
+
+ public class Point
+ {
+ public double X { get; set; }
+ public double Y { get; set; }
+
+ public double Length
+ {
+ get
+ {
+ return Math.Sqrt(X * X + Y * Y);
+ }
+ }
+ public Point(double x = 0, double y = 0)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public Point(Point p)
+ {
+ X = p.X;
+ Y = p.Y;
+ }
+
+ public static Point operator *(Point left, double right)
+ {
+ return new Point(left.X * right, left.Y * right);
+ }
+
+ public static Point operator /(Point left, double right)
+ {
+ return new Point(left.X / right, left.Y / right);
+ }
+
+ public static Point operator +(Point left, Point right)
+ {
+ return new Point(left.X + right.X, left.Y + right.Y);
+ }
+
+ public static Point operator -(Point left, Point right)
+ {
+ return new Point(left.X - right.X, left.Y - right.Y);
+ }
+
+ internal void Negate()
+ {
+ X = -X;
+ Y = -Y;
+ }
+
+ internal void Normalize()
+ {
+ double factor = 1.0 / Math.Sqrt(LengthSquared);
+ X *= factor;
+ Y *= factor;
+ }
+
+ public double LengthSquared { get { return X * X + Y * Y; } }
+
+
+ public static Point Add(Point a, Point b)
+ {
+ return new Point(a.X + b.X, a.Y + b.Y);
+ }
+
+ public static Point Subtract(Point a, Point b)
+ {
+ return new Point(a.X - b.X, a.Y - b.Y);
+ }
+ }
+
+
+ /*
+ An Algorithm for Automatically Fitting Digitized Curves
+ by Philip J. Schneider
+ from "Graphics Gems", Academic Press, 1990
+ */
+ public static class FitCurves
+ {
+ /* Fit the Bezier curves */
+
+ private const int MAXPOINTS = 10000;
+ public static List FitCurve(Point[] d, double error)
+ {
+ Point tHat1, tHat2; /* Unit tangent vectors at endpoints */
+
+ tHat1 = ComputeLeftTangent(d, 0);
+ tHat2 = ComputeRightTangent(d, d.Length - 1);
+ List result = new List();
+ FitCubic(d, 0, d.Length - 1, tHat1, tHat2, error, result);
+ return result;
+ }
+
+
+ private static void FitCubic(Point[] d, int first, int last, Point tHat1, Point tHat2, double error, List result)
+ {
+ Point[] bezCurve; /*Control points of fitted Bezier curve*/
+ double[] u; /* Parameter values for point */
+ double[] uPrime; /* Improved parameter values */
+ double maxError; /* Maximum fitting error */
+ int splitPoint; /* Point to split point set at */
+ int nPts; /* Number of points in subset */
+ double iterationError; /*Error below which you try iterating */
+ int maxIterations = 4; /* Max times to try iterating */
+ Point tHatCenter; /* Unit tangent vector at splitPoint */
+ int i;
+
+ iterationError = error * error;
+ nPts = last - first + 1;
+
+ /* Use heuristic if region only has two points in it */
+ if (nPts == 2)
+ {
+ double dist = (d[first] - d[last]).Length / 3.0;
+
+ bezCurve = new Point[4];
+ bezCurve[0] = d[first];
+ bezCurve[3] = d[last];
+ bezCurve[1] = (tHat1 * dist) + bezCurve[0];
+ bezCurve[2] = (tHat2 * dist) + bezCurve[3];
+
+ result.Add(bezCurve[1]);
+ result.Add(bezCurve[2]);
+ result.Add(bezCurve[3]);
+ return;
+ }
+
+ /* Parameterize points, and attempt to fit curve */
+ u = ChordLengthParameterize(d, first, last);
+ bezCurve = GenerateBezier(d, first, last, u, tHat1, tHat2);
+
+ /* Find max deviation of points to fitted curve */
+ maxError = ComputeMaxError(d, first, last, bezCurve, u, out splitPoint);
+ if (maxError < error)
+ {
+ result.Add(bezCurve[1]);
+ result.Add(bezCurve[2]);
+ result.Add(bezCurve[3]);
+ return;
+ }
+
+
+ /* If error not too large, try some reparameterization */
+ /* and iteration */
+ if (maxError < iterationError)
+ {
+ for (i = 0; i < maxIterations; i++)
+ {
+ uPrime = Reparameterize(d, first, last, u, bezCurve);
+ bezCurve = GenerateBezier(d, first, last, uPrime, tHat1, tHat2);
+ maxError = ComputeMaxError(d, first, last,
+ bezCurve, uPrime, out splitPoint);
+ if (maxError < error)
+ {
+ result.Add(bezCurve[1]);
+ result.Add(bezCurve[2]);
+ result.Add(bezCurve[3]);
+ return;
+ }
+ u = uPrime;
+ }
+ }
+
+ /* Fitting failed -- split at max error point and fit recursively */
+ tHatCenter = ComputeCenterTangent(d, splitPoint);
+ FitCubic(d, first, splitPoint, tHat1, tHatCenter, error, result);
+ tHatCenter.Negate();
+ FitCubic(d, splitPoint, last, tHatCenter, tHat2, error, result);
+ }
+
+ static Point[] GenerateBezier(Point[] d, int first, int last, double[] uPrime, Point tHat1, Point tHat2)
+ {
+ int i;
+ Point[,] A = new Point[MAXPOINTS, 2];/* Precomputed rhs for eqn */
+
+ int nPts; /* Number of pts in sub-curve */
+ double[,] C = new double[2, 2]; /* Matrix C */
+ double[] X = new double[2]; /* Matrix X */
+ double det_C0_C1, /* Determinants of matrices */
+ det_C0_X,
+ det_X_C1;
+ double alpha_l, /* Alpha values, left and right */
+ alpha_r;
+ Point tmp; /* Utility variable */
+ Point[] bezCurve = new Point[4]; /* RETURN bezier curve ctl pts */
+ nPts = last - first + 1;
+
+ /* Compute the A's */
+ for (i = 0; i < nPts; i++)
+ {
+ Point v1, v2;
+ v1 = tHat1;
+ v2 = tHat2;
+ v1 *= B1(uPrime[i]);
+ v2 *= B2(uPrime[i]);
+ A[i, 0] = v1;
+ A[i, 1] = v2;
+ }
+
+ /* Create the C and X matrices */
+ C[0, 0] = 0.0;
+ C[0, 1] = 0.0;
+ C[1, 0] = 0.0;
+ C[1, 1] = 0.0;
+ X[0] = 0.0;
+ X[1] = 0.0;
+
+ for (i = 0; i < nPts; i++)
+ {
+ C[0, 0] += V2Dot(A[i, 0], A[i, 0]);
+ C[0, 1] += V2Dot(A[i, 0], A[i, 1]);
+ /* C[1][0] += V2Dot(&A[i][0], &A[i][9]);*/
+ C[1, 0] = C[0, 1];
+ C[1, 1] += V2Dot(A[i, 1], A[i, 1]);
+
+ tmp = ((Point)d[first + i] -
+ (
+ ((Point)d[first] * B0(uPrime[i])) +
+ (
+ ((Point)d[first] * B1(uPrime[i])) +
+ (
+ ((Point)d[last] * B2(uPrime[i])) +
+ ((Point)d[last] * B3(uPrime[i]))))));
+
+
+ X[0] += V2Dot(A[i, 0], tmp);
+ X[1] += V2Dot(A[i, 1], tmp);
+ }
+
+ /* Compute the determinants of C and X */
+ det_C0_C1 = C[0, 0] * C[1, 1] - C[1, 0] * C[0, 1];
+ det_C0_X = C[0, 0] * X[1] - C[1, 0] * X[0];
+ det_X_C1 = X[0] * C[1, 1] - X[1] * C[0, 1];
+
+ /* Finally, derive alpha values */
+ alpha_l = (det_C0_C1 == 0) ? 0.0 : det_X_C1 / det_C0_C1;
+ alpha_r = (det_C0_C1 == 0) ? 0.0 : det_C0_X / det_C0_C1;
+
+ /* If alpha negative, use the Wu/Barsky heuristic (see text) */
+ /* (if alpha is 0, you get coincident control points that lead to
+ * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
+ double segLength = (d[first] - d[last]).Length;
+ double epsilon = 1.0e-6 * segLength;
+ if (alpha_l < epsilon || alpha_r < epsilon)
+ {
+ /* fall back on standard (probably inaccurate) formula, and subdivide further if needed. */
+ double dist = segLength / 3.0;
+ bezCurve[0] = d[first];
+ bezCurve[3] = d[last];
+ bezCurve[1] = (tHat1 * dist) + bezCurve[0];
+ bezCurve[2] = (tHat2 * dist) + bezCurve[3];
+ return (bezCurve);
+ }
+
+ /* First and last control points of the Bezier curve are */
+ /* positioned exactly at the first and last data points */
+ /* Control points 1 and 2 are positioned an alpha distance out */
+ /* on the tangent vectors, left and right, respectively */
+ bezCurve[0] = d[first];
+ bezCurve[3] = d[last];
+ bezCurve[1] = (tHat1 * alpha_l) + bezCurve[0];
+ bezCurve[2] = (tHat2 * alpha_r) + bezCurve[3];
+ return (bezCurve);
+ }
+
+ /*
+ * Reparameterize:
+ * Given set of points and their parameterization, try to find
+ * a better parameterization.
+ *
+ */
+ static double[] Reparameterize(Point[] d, int first, int last, double[] u, Point[] bezCurve)
+ {
+ int nPts = last - first + 1;
+ int i;
+ double[] uPrime = new double[nPts]; /* New parameter values */
+
+ for (i = first; i <= last; i++)
+ {
+ uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
+ }
+ return uPrime;
+ }
+
+
+
+ /*
+ * NewtonRaphsonRootFind :
+ * Use Newton-Raphson iteration to find better root.
+ */
+ static double NewtonRaphsonRootFind(Point[] Q, Point P, double u)
+ {
+ double numerator, denominator;
+ Point[] Q1 = new Point[3], Q2 = new Point[2]; /* Q' and Q'' */
+ Point Q_u, Q1_u, Q2_u; /*u evaluated at Q, Q', & Q'' */
+ double uPrime; /* Improved u */
+ int i;
+
+ /* Compute Q(u) */
+ Q_u = BezierII(3, Q, u);
+
+ /* Generate control vertices for Q' */
+ for (i = 0; i <= 2; i++)
+ {
+ Q1[i] = new Point((Q[i + 1].X - Q[i].X) * 3.0,(Q[i + 1].Y - Q[i].Y) * 3.0 );
+ }
+
+ /* Generate control vertices for Q'' */
+ for (i = 0; i <= 1; i++)
+ {
+ Q2[i] = new Point((Q[i + 1].X - Q[i].X) * 2.0, (Q[i + 1].Y - Q[i].Y) * 2.0);
+ }
+
+ /* Compute Q'(u) and Q''(u) */
+ Q1_u = BezierII(2, Q1, u);
+ Q2_u = BezierII(1, Q2, u);
+
+ /* Compute f(u)/f'(u) */
+ numerator = (Q_u.X - P.X) * (Q1_u.X) + (Q_u.Y - P.Y) * (Q1_u.Y);
+ denominator = (Q1_u.X) * (Q1_u.X) + (Q1_u.Y) * (Q1_u.Y) +
+ (Q_u.X - P.X) * (Q2_u.X) + (Q_u.Y - P.Y) * (Q2_u.Y);
+ if (denominator == 0.0f) return u;
+
+ /* u = u - f(u)/f'(u) */
+ uPrime = u - (numerator / denominator);
+ return (uPrime);
+ }
+
+
+
+ /*
+ * Bezier :
+ * Evaluate a Bezier curve at a particular parameter value
+ *
+ */
+ static Point BezierII(int degree, Point[] V, double t)
+ {
+ int i, j;
+ Point Q; /* Point on curve at parameter t */
+ Point[] Vtemp; /* Local copy of control points */
+
+ /* Copy array */
+ Vtemp = new Point[degree + 1];
+ for (i = 0; i <= degree; i++)
+ {
+ Vtemp[i] = new Point(V[i]);
+ }
+
+ /* Triangle computation */
+ for (i = 1; i <= degree; i++)
+ {
+ for (j = 0; j <= degree - i; j++)
+ {
+ Vtemp[j].X = (1.0 - t) * Vtemp[j].X + t * Vtemp[j + 1].X;
+ Vtemp[j].Y = (1.0 - t) * Vtemp[j].Y + t * Vtemp[j + 1].Y;
+ }
+ }
+
+ Q = Vtemp[0];
+ return Q;
+ }
+
+
+ /*
+ * B0, B1, B2, B3 :
+ * Bezier multipliers
+ */
+ static double B0(double u)
+ {
+ double tmp = 1.0 - u;
+ return (tmp * tmp * tmp);
+ }
+
+
+ static double B1(double u)
+ {
+ double tmp = 1.0 - u;
+ return (3 * u * (tmp * tmp));
+ }
+
+ static double B2(double u)
+ {
+ double tmp = 1.0 - u;
+ return (3 * u * u * tmp);
+ }
+
+ static double B3(double u)
+ {
+ return (u * u * u);
+ }
+
+ /*
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ *Approximate unit tangents at endpoints and "center" of digitized curve
+ */
+ static Point ComputeLeftTangent(Point[] d, int end)
+ {
+ Point tHat1;
+ tHat1 = d[end + 1] - d[end];
+ tHat1.Normalize();
+ return tHat1;
+ }
+
+ static Point ComputeRightTangent(Point[] d, int end)
+ {
+ Point tHat2;
+ tHat2 = d[end - 1] - d[end];
+ tHat2.Normalize();
+ return tHat2;
+ }
+
+ static Point ComputeCenterTangent(Point[] d, int center)
+ {
+ Point V1, V2, tHatCenter = new Point();
+
+ V1 = d[center - 1] - d[center];
+ V2 = d[center] - d[center + 1];
+ tHatCenter.X = (V1.X + V2.X) / 2.0;
+ tHatCenter.Y = (V1.Y + V2.Y) / 2.0;
+ tHatCenter.Normalize();
+ return tHatCenter;
+ }
+
+
+ /*
+ * ChordLengthParameterize :
+ * Assign parameter values to digitized points
+ * using relative distances between points.
+ */
+ static double[] ChordLengthParameterize(Point[] d, int first, int last)
+ {
+ int i;
+ double[] u = new double[last - first + 1]; /* Parameterization */
+
+ u[0] = 0.0;
+ for (i = first + 1; i <= last; i++)
+ {
+ u[i - first] = u[i - first - 1] + (d[i - 1] - d[i]).Length;
+ }
+
+ for (i = first + 1; i <= last; i++)
+ {
+ u[i - first] = u[i - first] / u[last - first];
+ }
+
+ return u;
+ }
+
+
+
+
+ /*
+ * ComputeMaxError :
+ * Find the maximum squared distance of digitized points
+ * to fitted curve.
+ */
+ static double ComputeMaxError(Point[] d, int first, int last, Point[] bezCurve, double[] u, out int splitPoint)
+ {
+ int i;
+ double maxDist; /* Maximum error */
+ double dist; /* Current error */
+ Point P; /* Point on curve */
+ Point v; /* Vector from point to curve */
+
+ splitPoint = (last - first + 1) / 2;
+ maxDist = 0.0;
+ for (i = first + 1; i < last; i++)
+ {
+ P = BezierII(3, bezCurve, u[i - first]);
+ v = P - d[i];
+ dist = v.LengthSquared;
+ if (dist >= maxDist)
+ {
+ maxDist = dist;
+ splitPoint = i;
+ }
+ }
+ return maxDist;
+ }
+
+ private static double V2Dot(Point a, Point b)
+ {
+ return ((a.X * b.X) + (a.Y * b.Y));
+ }
+
+ }
+
+}