Bézier Curve

 

In this chapter I want to deal with the construction of a quadratic Bézier curve. Bézier curves in general have become very popular since they have been developed in the early 60ies by Pierre Bézier, especially in computer graphics. In the following I will develop a Flash application where the user will be able to interactively manipulate the underlying control points on which the curve is based. These control points will be implemented as MovieClips that first must be placed at arbitrary locations on the screen in order to set their starting positions. Then, these MovieClips can be dragged and dropped within the window in order to manipulate the shape of the curve.

 

Quadratic Bézier curve: First, click with your mouse in the window in order to set the three control points that will constitute the curve. After this is done, you can drag and drop each of these points in order to manipulate the shape of the curve.

 

Actually, in a previous version I wanted to present an application where the user would be able to construct a general Bézier curve, i.e. with an unlimited number of control points. However, I found that for more than three control points the calculation became too intensive so that a smooth, interactive adjustment of the control points wasn't possible anymore.

 

Note: In the appendix I added a modified version: here you can place an unlimited number of control points (however, the control points in this version can not be manipulated in their position by means of 'drag & drop'). Note how the computing time increases for each subsequent control point!

 

General setup

For this application we only need one layer with a single frame. The frame rate was set to the maximum value at 120 fps. First we need to add a symbol that will serve as the graphical representation of the control points. I simply used a dark square with a light border (side length of 10 pixels). Convert this symbol into a MovieClip, name it „Square“ and place it somewhere outside the stage (see Fig. 1). This MovieClip object will act as a kind of reference and will be duplicated each time a new control point is added to the window.

As another graphical symbol I imported an image („clear“) that will serve as a button to clear the contents of the window. However, since this object will play another role in our application I also converted it into a MovieClip (instead of a button) and named it „Control“.

 

 

The ActionScript code of the main window

The ActionScript sheet of the main window contains all functions needed to construct and display the control points, a line that will connect these points, the Bézier curve itself as well as some additional functions that will be responsible for the interactive adjustments of the control points. First we need three global variables: _global.points that represents an array consisting of all MovieClips used as control points, and _global.stopFlag and _global.stopPointSet which are both Boolean variables with the start value false. The latter two variables will play a role in the coordination of the mouse events (see below):

 


_global.points = [];
_global.stopFlag = false;
_global.stopPointSet = false;

 

The first function will be called each time the mouse was clicked (up to three times): addNewPoint(pts:Array, pos:Array) has two arguments: First the array that contains all current control points (as MovieClips) and second an array that contains the x- and y-position of the screen, derived from the mouse cursor location. This function will copy the control point reference MovieClip Square (using the method duplicateMovieClip()), give it an individual instance name (here I simply used the prefix P_ and the current number of control points as an index) and place it at the current position of the mouse cursor (setting the _x and _y properties of the resulting MovieClip object P_num). After a new control point was created and placed on the sceen, the MovieClip object representing that point will be added to the global array that contains all control points (using the array method push()) and another function (connectPoints) is called:

 


function addNewPoint(pts:Array, pos:Array):Void
{
	var num:Number = pts.length;
	Square.duplicateMovieClip("P_"+num,100+num);
	var mcName:MovieClip = eval("P_"+num);
	mcName._x = pos[0];
	mcName._y = pos[1];
	
	pts.push(mcName);
		
	connectPoints(pts);	
}

 

The function connectPoints(pts:Array) is responsible—as you may already have guessed—for the drawing of a line that connects all current control points in the scene. The only argument that has to be passed to the function is the current set of control points. Within this function the first step is to create a new MovieClip object that will represent this line. Using the method createEmptyMovieClip we have to pass two arguments: The instance name of the new MovieClip (here I used the name Line) and the depth layer (here I used the quite arbitrary value -100, however, I payed heed to place this line object in a depth layer below all other elements of the scene, namely below the control points and the Bézier curve itself).

With the method lineStyle I defined the appearance of the Line object, i.e., its thickness (in pixels), its color (as a triplet of hex codes) and its amount of opacity (with an alpha value of 100).

The next code block determines the locations of the points that build up the line. These are, of course, the same as the control points. So, in the for-loop I successively call each of the current control points, tell the first one to act as the starting point of the line (with the condition if(i==0), so this point will be treated with the method moveTo()) and add the following to the line by the use of the method lineTo().

 


function connectPoints(pts:Array):Void
{
	createEmptyMovieClip("Line",-100);
	Line.lineStyle(1,0x666666,100);
	
	for(var i:Number = 0;i < pts.length;++i)
	{
		if(i==0)
			Line.moveTo(pts[i]._x,pts[i]._y);
		else
			Line.lineTo(pts[i]._x,pts[i]._y);			
	}	
}

 

The following function drawBezier(pts:Array) finally is responsible for drawing the Bézier curve. An array that contains all three control points (as MovieClips) has to be passed as argument to that function. The first thing to do within that function is to create a new MovieClip object that will represent the line (BezierCurve), again using the method createEmptyMovieClip. With the method lineStlye that line was determined to have a thickness of two pixels, a red color and a complete opaque appearance.

 


function drawBezier(pts:Array):Void
{	
	
	createEmptyMovieClip("BezierCurve",2000);
	BezierCurve.lineStyle(2,0xFF0000,100);
	
	var stepSize:Number = 0.025;
	for(var t:Number = 0;t<1 + stepSize;t=t+stepSize)
	{
		var x:Number = Math.pow(t-1,2)*pts[0]._x+2*t*(1-t)*pts[1]._x+Math.pow(t,2)*pts[2]._x;
		var y:Number = Math.pow(t-1,2)*pts[0]._y+2*t*(1-t)*pts[1]._y+Math.pow(t,2)*pts[2]._y;
				
		if(t==0)
			BezierCurve.moveTo(x,y);
		else
			BezierCurve.lineTo(x,y);
	}	
}

 

Before the rest of the code wihtin that function will be explained, here's some mathematics on which the calculation of the curve is based. The general case for an unspecified number of control points is expressed in the following equation:

Actually, this general construction equation was used for the application that I provided in the appendix (see below). For the case of a quadratic Bézier curve (i.e., a curve based on three control points) the equation can be simplified as follows:

One parameter of that function is t, a number that takes values within the interval [0,1]. This value determines something like the relative curve position where the exact location of the point on that curve position is to be calculated; t = 0 will be the starting point of the curve which is exactly at the first control point (P0) and t = 1 will be the position of the third control point (P2) which is the end point of the curve. So, for our line that we want to draw we first have to determine the resolution of the curve, i.e., how many points will be used for the curve? In other words: What will be the step size (or the increment) for a for-loop that starts with value 0 and ends with value 1? Well, it's up to you! I chose an inkrement of 0.025, that means that the resulting curve will consist of 40 segments, which will be a quite smooth curve.

So, in the following for-loop the x- and the y-coordinate for each of the respective 41 points will be calculated, dependent on the three control points that were passed as the argument of that function and parameter t. The condition if(t==0) was used in the same fashion as in the function connectPoints() to tell ActionScript to let the first point of the row be the starting point of the curve by use of the method moveTo().

So far, we have already created the core functions so that we would be able to draw a quadratic Bézier curve. But we else wanted to put some interactivity to our application: The single control points shall be able to be dragged and dropped in order to change their positions and hence manipulate the shape of the curve. Therefore, we need some additional functions:

 


function startPointMove(pts:Array):Void
{

	for(var j:Number = 0;j < pts.length;++j)
	{ 
			
		pts[j].onPress = drag;
		pts[j].onRelease = drop;
		pts[j].onMouseMove = connect;
	}	
		
	drawBezier(pts);
}


function drag()
{
	this.startDrag();	
}

function drop()
{	
	this.stopDrag();	
}

function connect()
{
	connectPoints(_global.points);
	drawBezier(_global.points);	
}

 

The function startPointMove(pts:Array) will be called as soon as the number of control points added to the scene reached the value 3 (this will be implemented in the processing of the respective mouse events, see below). It needs one argument to be passed, again the array that contains all control points (as MovieClips). Within that function each of our three MovieClips that represent the graphical symbols of the three control points (P_i) will be linked to three different mouse events: When the respective MovieClip alias control point is pressed, it shall be able to be dragged: when the mouse button is released, the drag-function is stopped and when the mouse cursor is moved, another function will be called. After these functions are applied to all of those MovieClips, the Bézier curve will be drawn anew by calling the function drawBezier().

The above-mentioned functions that will be called when the respective mouse events have taken place are called drag(), drop(), and connect(). The functions drag() and drop() have a quite intuitive code: They simply call the respective MovieClip (this) together with the built-in methods startDrag() and stopDrag(), respectively. The function connect that is called when the mouse was moved just tells our application to update the connection line (or the control polygon) as well as the Bézier curve, using the functions that have already been built.

As a remaining function I added the function reset(pts:Array) that will be called when the „clear“ button was pressed. This function will delete the MovieClips that represent the connection Line (Line) and the Bézier curve (BezierCurve) by use of the method clear(). The for-loop will then call each of the MovieClips that represent our control points (P_i, which have been passed as argument to that function) and remove them from the scene. As a last step, the global array that represents the current set of control points (_global.points) will be emptied (_global.points = [];) and the global variable _global.stopPointSet will be set to false again (see below), so that we restored the original state of our application before a control point was added to the screen:

 


function reset(pts:Array):Void
{
	Line.clear();
	BezierCurve.clear();
	for(var j:Number = 0;j < pts.length;++j)
	{	
		pts[j].removeMovieClip();
	}
	_global.stopPointSet = false;
		
	_global.points = [];
}

 

 

The ActionScript code for the „Control“ element

When we built the general setup for our application we also added a MovieClip with the instance name Control. This object has two different tasks in our application: First, it will serve as a clear button that, when pressed, will delete the current contents of the screen by calling the function reset(). The second role played by that MovieClip is to react on global mouse events: It will generate a new control point when somewhere in the window the mouse button was pressed (by calling the function addNewPoint() and passing the current position of the mouse cursor as one of the arguments to that function). When the current number of control points equals 3 (as the basis for a quadratic Bézier curve), the function startPointMove() will be called:

 


on(rollOver)
{
	_global.stopFlag = true;
}


on(rollOut)
{
	_global.stopFlag = false;
}


on(press)
{
	_level0.reset(_global.points);
}


onClipEvent(mouseDown)
{
	if(!_global.stopFlag && !_global.stopPointSet)
	{
		var xm = _level0._xmouse;
		var ym = _level0._ymouse;
	
		if(_global.points.length < 3)
			_level0.addNewPoint(_global.points,[xm,ym]);
		
		if(_global.points.length == 3)
		{
			_level0.startPointMove(_global.points);
			_global.stopPointSet = true;			
		}		
	}		
}

 

However, the problem is that the two events press (MovieClip event) and mouseDown (mouse event) will both occur when the clear button is pressed. So we have to prevent the program from setting a new control point at the location of the button when it was intended to clear the scene. Therefore, the two MovieClip events rollOver and rollOut are additionally used: When the mouse cursor is located over the Control object, the global variable stopFlag will receive the value true, otherwise it will change back to false. When this value is set on true, none of the functions for the handling of the control points will be called. The same holds when the current number of the control points has reached the value 3 which means that the control polygon consisting of three control points is complete: In this case the function startPointMove() will be called and the global variable stopPointSet will be set to true. As the code shows, the first thing that happens when the mouseDown event is evaluated is to check whether one of these two Boolean variables is false, or: The code will only be executed if both variables have the value false.

 

Appendix:

The following Flash application is similar to the one that we just developed: Click with your mouse in the window in order to set the control points for a general Bézier curve (i.e., the number of control points is not limited in this case). However, contrary to what we just programmed, this application will not allow the user to change the positions of the control points via „drag & drop“.

 

 

 

 

© 2011 G. Wendt



Write a comment
Name: Note: Your email address will not be displayed on this site nor otherwise published!
Email:
Comment:
Anti-Spam:
Color & Font
Color tool

You have difficulties finding a balanced color combination and appropriate font formats for your website?


Use the tool Color & Font on this website in order to interactively create the color and font properties of your own site. The results of your settings can be easily included as CSS listing in your web project.

Archive
MTWTFSS
1234567
891011121314
15161718192021
22232425262728
2930