/* Cubic Bézier curve tools
* @author: Andy Woodruff (http://cartogrammar.com/blog || awoodruff@gmail.com)
* @date: May 2008
* @description: Contains methods for drawing a single cubic Bézier curve and for drawing
* a continuous series of curves through specified points.
*/
package com.cartogrammar.drawing{
import flash.display.Graphics;
import flash.geom.Point;
import fl.motion.BezierSegment;
public class CubicBezier{
/* pubic static function drawCurve
* Draws a single cubic Bézier curve
* @param:
* g:Graphics -Graphics on which to draw the curve
* p1:Point -First point in the curve
* p2:Point -Second point (control point) in the curve
* p3:Point -Third point (control point) in the curve
* p4:Point -Fourth point in the curve
* @return:
*/
public static function drawCurve(g:Graphics, p1:Point, p2:Point, p3:Point, p4:Point):void{
var bezier = new BezierSegment(p1,p2,p3,p4); // BezierSegment using the four points
g.moveTo(p1.x,p1.y);
// Construct the curve out of 100 segments (adjust number for less/more detail)
for (var t=.01;t<1.01;t+=.01){
var val = bezier.getValue(t); // x,y on the curve for a given t
g.lineTo(val.x,val.y);
}
}
/* public static function curveThroughPoints
* Draws a smooth curve through a series of points. For a closed curve, make the first and last points the same.
* @param:
* g:Graphics -Graphics on which to draw the curve
* p:Array -Array of Point instances
* z:Number -A factor (between 0 and 1) to reduce the size of curves by limiting the distance of control points from anchor points.
* For example, z=.5 limits control points to half the distance of the closer adjacent anchor point.
* I put the option here, but I recommend sticking with .5
* angleFactor:Number -Adjusts the size of curves depending on how acute the angle between points is. Curves are reduced as acuteness
* increases, and this factor controls by how much.
* 1 = curves are reduced in direct proportion to acuteness
* 0 = curves are not reduced at all based on acuteness
* in between = the reduction is basically a percentage of the full reduction
* @return:
*/
public static function curveThroughPoints(g:Graphics, points:Array/*of Points*/, z:Number = .5, angleFactor:Number = .75):void{
try {
var p:Array = points.slice(); // Local copy of points array
var duplicates:Array = new Array(); // Array to hold indices of duplicate points
// Check to make sure array contains only Points
for (var i=0; i 0){
if (p[i].x == p[i-1].x && p[i].y == p[i-1].y){
duplicates.push(i); // add index of duplicate to duplicates array
}
}
}
// Loop through duplicates array and remove points from the points array
for (i=duplicates.length-1; i>=0; i--){
p.splice(duplicates[i],1);
}
// Make sure z is between 0 and 1 (too messy otherwise)
if (z <= 0){
z = .5;
} else if (z > 1){
z = 1;
}
// Make sure angleFactor is between 0 and 1
if (angleFactor < 0){
angleFactor = 0;
} else if (angleFactor > 1){
angleFactor = 1;
}
//
// First calculate all the curve control points
//
// None of this junk will do any good if there are only two points
if (p.length > 2){
// Ordinarily, curve calculations will start with the second point and go through the second-to-last point
var firstPt = 1;
var lastPt = p.length-1;
// Check if this is a closed line (the first and last points are the same)
if (p[0].x == p[p.length-1].x && p[0].y == p[p.length-1].y){
// Include first and last points in curve calculations
firstPt = 0;
lastPt = p.length;
}
var controlPts:Array = new Array(); // An array to store the two control points (of a cubic Bézier curve) for each point
// Loop through all the points (except the first and last if not a closed line) to get curve control points for each.
for (i=firstPt; i b){
aPt.normalize(b); // Scale the segment to aPt (bPt to aPt) to the size of b (bPt to cPt) if b is shorter.
} else if (b > a){
cPt.normalize(a); // Scale the segment to cPt (bPt to cPt) to the size of a (aPt to bPt) if a is shorter.
}
// Offset aPt and cPt by the current point to get them back to their absolute position.
aPt.offset(p1.x,p1.y);
cPt.offset(p1.x,p1.y);
// Get the sum of the two vectors, which is perpendicular to the line along which our curve control points will lie.
var ax = bPt.x-aPt.x; // x component of the segment from previous to current point
var ay = bPt.y-aPt.y;
var bx = bPt.x-cPt.x; // x component of the segment from next to current point
var by = bPt.y-cPt.y;
var rx = ax + bx; // sum of x components
var ry = ay + by;
var r = Math.sqrt(rx*rx+ry*ry); // length of the summed vector - not being used, but there it is anyway
var theta = Math.atan(ry/rx); // angle of the new vector
var controlDist = Math.min(a,b)*z; // Distance of curve control points from current point: a fraction the length of the shorter adjacent triangle side
var controlScaleFactor = C/Math.PI; // Scale the distance based on the acuteness of the angle. Prevents big loops around long, sharp-angled triangles.
controlDist *= ((1-angleFactor) + angleFactor*controlScaleFactor); // Mess with this for some fine-tuning
var controlAngle = theta+Math.PI/2; // The angle from the current point to control points: the new vector angle plus 90 degrees (tangent to the curve).
var controlPoint2 = Point.polar(controlDist,controlAngle); // Control point 2, curving to the next point.
var controlPoint1 = Point.polar(controlDist,controlAngle+Math.PI); // Control point 1, curving from the previous point (180 degrees away from control point 2).
// Offset control points to put them in the correct absolute position
controlPoint1.offset(p1.x,p1.y);
controlPoint2.offset(p1.x,p1.y);
/*
Haven't quite worked out how this happens, but some control points will be reversed.
In this case controlPoint2 will be farther from the next point than controlPoint1 is.
Check for that and switch them if it's true.
*/
if (Point.distance(controlPoint2,p2) > Point.distance(controlPoint1,p2)){
controlPts[i] = new Array(controlPoint2,controlPoint1); // Add the two control points to the array in reverse order
} else {
controlPts[i] = new Array(controlPoint1,controlPoint2); // Otherwise add the two control points to the array in normal order
}
// Uncomment to draw lines showing where the control points are.
/*
g.moveTo(p1.x,p1.y);
g.lineTo(controlPoint2.x,controlPoint2.y);
g.moveTo(p1.x,p1.y);
g.lineTo(controlPoint1.x,controlPoint1.y);
*/
}
//
// Now draw the curve
//
g.moveTo(p[0].x, p[0].y);
// If this isn't a closed line
if (firstPt == 1){
// Draw a regular quadratic Bézier curve from the first to second points, using the first control point of the second point
g.curveTo(controlPts[1][0].x,controlPts[1][0].y,p[1].x,p[1].y);
}
// Loop through points to draw cubic Bézier curves through the penultimate point, or through the last point if the line is closed.
for (i=firstPt;i