I\'ve been struggling looking for an understandable way to do this. I have four points, a StartPt, EndPoint, and Intersection points to represent the peak and valley in the
as3 version:
package
{
import flash.geom.Vector3D;
public class DrawingUtility
{
private var x1:Number;
private var y1:Number;
private var x2:Number;
private var y2:Number;
// linear equation solver utility for ai + bj = c and di + ej = f
private function solvexy(a:Number, b:Number, c:Number, d:Number, e:Number, f:Number):Vector.<Number>
{
var returnVal:Vector.<Number> = new Vector.<Number>();
var j:Number = (c - a / d * f) / (b - a * e / d);
var i:Number = (c - (b * j)) / a;
returnVal[0] = i;
returnVal[1] = j;
return returnVal;
}
// basis functions
private function b0(t:Number):Number {
return Math.pow(1 - t, 3);
}
private function b1(t:Number):Number {
return t * (1 - t) * (1 - t) * 3;
}
private function b2(t:Number):Number {
return (1 - t) * t * t * 3;
}
private function b3(t:Number):Number {
return Math.pow(t, 3);
}
private function bez4pts1(x0:Number, y0:Number, x4:Number, y4:Number, x5:Number, y5:Number, x3:Number, y3:Number):void
{
// find chord lengths
var c1:Number = Math.sqrt((x4 - x0) * (x4 - x0) + (y4 - y0) * (y4 - y0));
var c2:Number = Math.sqrt((x5 - x4) * (x5 - x4) + (y5 - y4) * (y5 - y4));
var c3:Number = Math.sqrt((x3 - x5) * (x3 - x5) + (y3 - y5) * (y3 - y5));
// guess "best" t
var t1:Number = c1 / (c1 + c2 + c3);
var t2:Number = (c1 + c2) / (c1 + c2 + c3);
// transform x1 and x2
var x1x2:Vector.<Number> = solvexy(b1(t1), b2(t1), x4 - (x0 * b0(t1)) - (x3 * b3(t1)), b1(t2), b2(t2), x5 - (x0 * b0(t2)) - (x3 * b3(t2)));
x1 = x1x2[0];
x2 = x1x2[1];
// transform y1 and y2
var y1y2:Vector.<Number> = solvexy(b1(t1), b2(t1), y4 - (y0 * b0(t1)) - (y3 * b3(t1)), b1(t2), b2(t2), y5 - (y0 * b0(t2)) - (y3 * b3(t2)));
y1 = y1y2[0];
y2 = y1y2[1];
}
public function BezierFromIntersection(startPt:Vector3D, int1:Vector3D, int2:Vector3D, endPt:Vector3D):Vector.<Vector3D>
{
var returnVec:Vector.<Vector3D> = new Vector.<Vector3D>();
bez4pts1(startPt.x, startPt.y, int1.x, int1.y, int2.x, int2.y, endPt.x, endPt.y);
returnVec.push(startPt);
returnVec.push(new Vector3D(x1, y1));
returnVec.push(new Vector3D(x2, y2));
returnVec.push(endPt);
return returnVec;
}
}
}
Here are two good examples:
http://www.codeproject.com/KB/graphics/ClosedBezierSpline.aspx http://www.codeproject.com/KB/graphics/BezierSpline.aspx
Also see this animation to better understand how BezierSplines work http://en.wikipedia.org/wiki/B%C3%A9zier_curve
You should consider using Cardinal (Canonical) Splines, which use a set of points that exist on the path, plus a "tension" parameter that controls how sharply corners are smoothed to corner tangents.
In Windows Forms, one would use the DrawCurve and DrawClosedCurve methods. In WPF there are no direct equivalents. Here are two articles that describe using cardinal splines in WPF with C#.
Floris - AddCurve For WPF Cardinal Spline
Petzold - Canonical Splines In WPF And Silverlight
There are an infinite number of solutions to a curve passing through 4 points, but the best simple solution is to try to make the curve segment lengths proportional to the chord lengths. The code you link to is the a first order approximation that works well and is pretty fast.
Here's the C# translation of the PostScript code:
static class DrawingUtility
{
// linear equation solver utility for ai + bj = c and di + ej = f
static void solvexy(double a, double b, double c, double d, double e, double f, out double i, out double j)
{
j = (c - a / d * f) / (b - a * e / d);
i = (c - (b * j)) / a;
}
// basis functions
static double b0(double t) { return Math.Pow(1 - t, 3); }
static double b1(double t) { return t * (1 - t) * (1 - t) * 3; }
static double b2(double t) { return (1 - t) * t * t * 3; }
static double b3(double t) { return Math.Pow(t, 3); }
static void bez4pts1(double x0, double y0, double x4, double y4, double x5, double y5, double x3, double y3, out double x1, out double y1, out double x2, out double y2)
{
// find chord lengths
double c1 = Math.Sqrt((x4 - x0) * (x4 - x0) + (y4 - y0) * (y4 - y0));
double c2 = Math.Sqrt((x5 - x4) * (x5 - x4) + (y5 - y4) * (y5 - y4));
double c3 = Math.Sqrt((x3 - x5) * (x3 - x5) + (y3 - y5) * (y3 - y5));
// guess "best" t
double t1 = c1 / (c1 + c2 + c3);
double t2 = (c1 + c2) / (c1 + c2 + c3);
// transform x1 and x2
solvexy(b1(t1), b2(t1), x4 - (x0 * b0(t1)) - (x3 * b3(t1)), b1(t2), b2(t2), x5 - (x0 * b0(t2)) - (x3 * b3(t2)), out x1, out x2);
// transform y1 and y2
solvexy(b1(t1), b2(t1), y4 - (y0 * b0(t1)) - (y3 * b3(t1)), b1(t2), b2(t2), y5 - (y0 * b0(t2)) - (y3 * b3(t2)), out y1, out y2);
}
static public PathFigure BezierFromIntersection(Point startPt, Point int1, Point int2, Point endPt)
{
double x1, y1, x2, y2;
bez4pts1(startPt.X, startPt.Y, int1.X, int1.Y, int2.X, int2.Y, endPt.X, endPt.Y, out x1, out y1, out x2, out y2);
PathFigure p = new PathFigure { StartPoint = startPt };
p.Segments.Add(new BezierSegment { Point1 = new Point(x1, y1), Point2 = new Point(x2, y2), Point3 = endPt } );
return p;
}
}
I haven't tested it, but it compiles. Just call DrawingUtility.BezierFromIntersection
with the 4 points you have, and it will return a PathFigure
for drawing the curve.