Script.it .Nam Myoho Renghe Kyo.

Script.it
[Lab] BezierPaths

BezierPaths, always a fun subject.
These paths can be used for smooth lines, car drive behaviours or even cable / string simulations.
To get a better understanding of the BezierPath, we need to start with a simpler version…the Cubic Bezier Curve.

 

The Cubic Bezier Curve.

The cubic bezier curve is a curve from coordinate A to coordinate B.
The curvature of the curve is defined by the control points of this curve.

So for each curve we need 4 points:
- 2 coordinates ( start + end )
- 2 control points ( 1 for start, 1 for end )

800px-bezier_curvesvg

How do we calculate this curve ?
Basicly it comes down to the follwing:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
posx = Math.pow(T,3)*(anchor2.x+3*(control1.x-control2.x)-anchor1.x)
           +3*Math.pow(T,2)*(anchor1.x-2*control1.x+control2.x)
           +3*T*(control1.x-anchor1.x)+anchor1.x;
 
posy = Math.pow(T,3)*(anchor2.y+3*(control1.y-control2.y)-anchor1.y)
          +3*Math.pow(T,2)*(anchor1.y-2*control1.y+control2.y)
          +3*T*(control1.y-anchor1.y)+anchor1.y;

Where T is a value between 0.0 and 1.0.
You basicly calculate a coordinate for each point on the curve and draw all the coordinates.
Pretty straight forward i guess.
Here’s my Cubic Bezier Curve class.

This movie requires Flash Player 9

Drag the green points to adjust the control points.
Drag the red points to adjust start and end positions.

Now for the code behind it.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
*   BezierCurve
*   Bezier Curve using "de Casteljau" algorithm.   
*
*
*   @author :  Arctic Interative - info@arctic-interactive.nl
*   @date   :  28 - 12 - 2012
*   @version:  0.0.1
*   
*/
package nl.arctic.display.curves
{
	import flash.display.Graphics;
	import flash.geom.Point;
	import flash.geom.Vector3D;
 
	public class BezierCurve
	{
		private var precision:Number = 100.0;
		private var anchor1:Vector3D, anchor2:Vector3D, control1:Vector3D, control2:Vector3D, drawP:Vector3D;
		public function BezierCurve( ){ }
		public function setPrecision( value:Number ):void {this.precision = value;}
		public function getPrecision():Number {return this.precision;}
 
 
 
		//==============================================================================
		// COMPUTE
		//-----------------------------------------------------------------------------
		public function ComputePoint( a1:Vector3D, a2:Vector3D, weight:Number ):Vector3D {
			if( weight > 1.0 ) weight = 1.0;
			if( weight < 0.0 ) weight = 0.0;
 
			return new Vector3D( (1-weight) * a1.x + weight * a2.x, 
							     (1-weight) * a1.y + weight * a2.y,
								 (1-weight) * a1.z + weight * a2.z);
		}
		public function ComputePath( anchor1:Vector3D, anchor2:Vector3D, control1:Vector3D, control2:Vector3D ):Vector. {
			this.anchor1  = anchor1;
			this.anchor2  = anchor2;
			this.control1 = control1;
			this.control2 = control2;
			var temp:Vector. = new Vector.( this.precision );
			var w:Number = 0.0;
			for( var i:int = 0; i < this.precision; i++ ){
				var drawP1:Vector3D = ComputePoint( this.anchor1 , this.control1, w );
				var drawP2:Vector3D = ComputePoint( this.control1, this.control2, w );
				var drawP3:Vector3D = ComputePoint( this.control2, this.anchor2 , w );
 
				var drawPSub1:Vector3D = ComputePoint( drawP1, drawP2, w );
				var drawPSub2:Vector3D = ComputePoint( drawP2, drawP3, w );
 
				temp[i] = ComputePoint( drawPSub1, drawPSub2, w );
				w += 1.0/this.precision;
			}
			return temp;
		}
 
 
		//==============================================================================
		// DRAW.
		//-----------------------------------------------------------------------------
		public function Draw( graphics:Graphics ):void {
			if( !this.anchor1 ) return;
			var path:Vector. = ComputePath(this.anchor1, this.anchor2, this.control1, this.control2 );
			graphics.moveTo( this.anchor1.x, this.anchor1.y );
			for( var i:int = 0; i < path.length; i++ ){
				graphics.lineTo( path[i].x, path[i].y );
			}
			graphics.lineTo( this.anchor2.x, this.anchor2.y );
		}
 
		public function DrawAnchorPoints( graphics:Graphics ):void {
			graphics.beginFill( 0xc61916 );
			graphics.drawCircle( this.anchor1.x, this.anchor1.y, 5 );
			graphics.drawCircle( this.anchor2.x, this.anchor2.y, 5 );
			graphics.endFill();
		}
 
		public function DrawControlPoints( graphics:Graphics ):void {
			graphics.beginFill( 0x00ff00 );
			graphics.drawCircle( this.control1.x, this.control1.y, 5 );
			graphics.drawCircle( this.control2.x, this.control2.y, 5 );
			graphics.endFill();
		}
	}
}

The Bezier Path.

So now we got logic behind a single cubic bezier curve. But what about a Bezier Path ?
This is basicly a set of coordinates with a curved path running through each of these coordinates.
Basicly a couple of Cubic Bezier Curves stitched together.

First, lets look at the logic behind a Bezier Path.

To make things easier for yourself.. try and divide the points above in segments of 2 points ( start + end ).
This way you’ll see that each curve uses the end point of the previous curve for its starting point.
Now try to imagine making this with the Cubic Bezier Curve class.

In the previous piece of code ( CubicBezierCurve Class ), you pass 4 points. So the control points are also passed to the class.
Within Bezier Paths you dont want to pass the control points. In a perfect world the control points are calculated for you.
So that the path runs smooth thru all the coordinates given to it.

The control points, which give the path its curvature, can be easily calculated with a little bit of trigonometry.

?View Code ACTIONSCRIPT
1
2
3
4
//first get the angle from point a to point b.
var dx:Number = nextCoordinatePoint.x - prevCoordinatePoint.x;
var dy:Number = nextCoordinatePoint.y - prevCoordinatePoint.y;
var da:Number = Math.atan2( dy, dx ) - (Math.PI/2);           //

Now you know the controlpoints for the whole path. Find out which point belongs to which set of (starting and end) points.
Then construct your Cubic Bezier Curves. A path should emerge.

Here’s my try on the Bezier Path.
3 paths each with 5 randomly placed points.

This movie requires Flash Player 9

The first curve is the final result you’ll see if you dont render the control points and coordinate points.
The second curve has the coordinate points visualized.
The third curve shows the control points and coordinate points visualized.

So here’s the bezierPath class:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
* 	BezierPath
*   Bezier Path using Bezier Curves to construct it.   
*
*
*   @author :  Arctic Interative - info@arctic-interactive.nl
*   @date   :  29 - 12 - 2012
*   @version:  0.0.1
*   
*/
package nl.arctic.display.curves
{
	import flash.display.Graphics;
	import flash.geom.Point;
	import flash.geom.Vector3D;
 
	import mx.containers.Form;
 
	public class BezierPath
	{
		private var precision:Number = 10.0;
		private var curves:Vector.;
		private var path:Vector.<Vector.>;
		private var s_points:Vector.;
		private var c_points:Vector.;
		public function BezierPath(){ }
 
 
		//==============================================================================
		// SETTERS
		//-----------------------------------------------------------------------------
		public function SetPrecision( value:Number ):void {
			this.precision = value;
		}
		public function SetPoints( p:Vector. ):void {
			this.s_points = p;
		}
 
 
 
		//==============================================================================
		// GETTERS
		//-----------------------------------------------------------------------------
		public function GetPoints():Vector. {
			return this.s_points;
		}
		public function GetControlPoints():Vector.{
			return this.c_points;
		}
		public function GetPath():Vector.<Vector.> {
			return this.path;
		}
 
 
 
 
		//==============================================================================
		// COMPUTE
		//-----------------------------------------------------------------------------
		public function ComputeControlPoints():void {
			this.c_points = new Vector.();
			for (var i:int = 0; i < GetPoints().length; i++) 
			{
				if( i == GetPoints().length-1 || i == 0) continue;
				var dx:Number = this.s_points[i+1].x - this.s_points[i-1].x;
				var dy:Number = this.s_points[i+1].y - this.s_points[i-1].y;
				//var dl:Number = Math.sqrt( dx*dx+dy*dy)/4;
				var da:Number = Math.atan2( dy, dx ) - (Math.PI/2);
 
 
				var dx1:Number = this.s_points[i].x - this.s_points[i+1].x; 
				var dy1:Number = this.s_points[i].y - this.s_points[i+1].y; 
				var dl1:Number = Math.sqrt( dx1*dx1+dy1*dy1)/2;
 
				var dx2:Number = this.s_points[i].x - this.s_points[i-1].x; 
				var dy2:Number = this.s_points[i].y - this.s_points[i-1].y; 
				var dl2:Number = Math.sqrt( dx2*dx2+dy2*dy2)/2;
 
				c_points.push( new Vector3D( this.s_points[i].x + Math.cos( da - Math.PI/2) * dl2,
											 this.s_points[i].y + Math.sin( da - Math.PI/2) * dl2,
											 0 ) );
 
				c_points.push( new Vector3D( this.s_points[i].x + Math.cos( da + Math.PI/2) * dl1,
					                         this.s_points[i].y + Math.sin( da + Math.PI/2) * dl1,
											 0 ) );
			}
		}
		public function ComputePath():void {
			var cc:uint = 0;
		       curves = new Vector.();
			path   = new Vector.<Vector.>();
			for (var i:int = 0; i < GetPoints().length; i++) 
			{
				var b:BezierCurve = new BezierCurve();
				if( i == 0 ){
					path.push( b.ComputePath( GetPoints()[i], GetPoints()[i+1], 
						                      GetPoints()[i], GetControlPoints()[cc] ));
					cc++;
				}else if( i > 0 && i <= GetPoints().length - 3 ){
					path.push( b.ComputePath(  GetPoints()[i]        , GetPoints()[i+1], 
						                       GetControlPoints()[cc], GetControlPoints()[cc+1] ));
 
					if(cc <= GetControlPoints().length -3) cc+=2;
					else cc++;
				}else if( i > 0 && i == GetPoints().length - 2){
					path.push( b.ComputePath( GetPoints()[i]        , GetPoints()[i+1], 
						                      GetControlPoints()[cc],GetPoints()[i+1] ));
 
				}
				b.setPrecision( this.precision  );
				curves.push(b);
			}
		}
 
 
 
 
 
		//==============================================================================
		// DRAWING
		//-----------------------------------------------------------------------------
		public function DrawPoints( graphics:Graphics ):void {
			graphics.lineStyle(0);
			graphics.beginFill( 0xff0000 );
			for (var i:int = 0; i < this.s_points.length; i++) graphics.drawCircle( this.s_points[i].x, this.s_points[i].y, 6 );
			graphics.endFill();
		}
		public function DrawControlPoints( graphics:Graphics ):void {
			graphics.lineStyle(0);
			graphics.beginFill( 0x0000ff );
 
			var sCount:uint = 1;
			for (var i:int = 0; i < this.c_points.length; i++){
				if(i %2 == 0 && i > 0) sCount++;
				graphics.lineStyle(0);
				graphics.drawCircle( this.c_points[i].x, this.c_points[i].y, 3 );
 
				graphics.lineStyle(1);
				graphics.moveTo( this.s_points[sCount].x,  this.s_points[sCount].y );
				graphics.lineTo( this.c_points[i].x,  this.c_points[i].y );
			}
			graphics.endFill();
		}
		public function Draw( graphics:Graphics ):void {
			graphics.lineStyle(5);
			for (var i:int = 0; i < curves.length; i++) 
				curves[i].Draw(graphics);
		}
	}
}

To use this class do the following:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//make a new instance
var bp:BezierPath = new BezierPath;
 
//set precision
bezierPath.SetPrecision( 100 );
 
//pass the coordinate points
bezierPath.SetPoints( points );
 
//compute control points
bezierPath.ComputeControlPoints();
 
//compute the path
bezierPath.ComputePath();
 
//draw the coordinate points
bezierPath.DrawPoints( this.graphics );
 
//draw the control points
bezierPath.DrawControlPoints( this.graphics );
 
//draw the bezier path
bezierPath.Draw( this.graphics );
 
//voila!

 

Happy coding!

\o
Rackdoll

Creative Commons License
[Lab] BezierPaths by Script.it, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Netherlands License.

Comments are closed.

Categories
Archives
Live Supporters