I've spent the past couple days here working on a bezier curve function that is easy to use and fast. I'm not sure how this compares to other functions speed-wise, but it is simple to use, so that's a success.
Preview Image:
How It Works:
Lines are casted along opposite control lines from the end points of the starting line and where corresponding lines intersect (1 to 1, 2 to 2, 3 to 3, etc.) is where a point is plotted. Points can be added/removed dynamically because it was created using vectors, and I tried to make the code as easy to read as I possibly could. Also, I get to about a 1000 vertices before taking some lag, but, let's face it, it'll look smooth with 20.
Anyway, the code:
#include "DarkGDK.h"
#include <vector>
using namespace std;
//Screen Constants
const int ScreenWid = 1024;
const int ScreenHei = 768;
const int ScreenBit = 32;
const int WindowWid = 1024;
const int WindowHei = 768;
//For demonstration purposes, layout all variables separately
int cx = ScreenWid/2; //Control point X value
int cy = ScreenHei/3; //Control point Y value
int pc = 20; //Total point count
bool VertOn = false; //Vertices On/Off for spacebar
struct Point{
float x;
float y;
};
struct Line{
float x1;
float y1;
float x2;
float y2;
float xinc; //X Increment For Slope
float yinc;
};
int FindIntersectX( float ax, float ay, float bx, float by, float cx, float cy, float dx, float dy){
float mab=(by-ay)/(bx-ax);
float mcd=(dy-cy)/(dx-cx);
float XIntersect=(mab*ax - ay - mcd*cx + cy) / (mab - mcd);
return XIntersect;
};
int FindIntersectY( float ax, float ay, float bx, float by, float cx, float cy, float dx, float dy){
float mab=(by-ay)/(bx-ax);
float mcd=(dy-cy)/(dx-cx);
float XIntersect = (mab*ax - ay - mcd*cx + cy) / (mab - mcd);
float YIntersect = mab*XIntersect - mab*ax + ay;
return YIntersect;
};
vector<Line> Bezier( int FromX, int FromY, int ToX, int ToY, int ControlX, int ControlY , int Points ){
//=====================================================================================//
//-------------------------------------------------------------------------------------//
// FromX = The starting X position of the line //
// FromY = The starting Y position of the line //
// //
// ToX = The ending X position of the line //
// ToY = The ending Y position of the line //
// //
// ControlX = Control point X position //
// ControlY = Control point Y Position //
// //
// Points = Total number of points to use, //
// *Less points = Better performance* //
// *More points = Smoother line* //
//-------------------------------------------------------------------------------------//
//=====================================================================================//
//Correct point count
Points = Points+1;
//Set up the lines
Line MainLine;
MainLine.x1 = FromX;
MainLine.y1 = FromY;
MainLine.x2 = ToX;
MainLine.y2 = ToY;
vector<Point> MLPoints;
Line CLine1; //Control Line 1
CLine1.x1 = FromX;
CLine1.y1 = FromY;
CLine1.x2 = ControlX;
CLine1.y2 = ControlY;
vector<Point> CL1Points;
Line CLine2; //Control Line 2
CLine2.x1 = ControlX;
CLine2.y1 = ControlY;
CLine2.x2 = ToX;
CLine2.y2 = ToY;
vector<Point> CL2Points;
//Resize point arrays to the appropriate length
MLPoints.resize(Points);
CL1Points.resize(Points);
CL2Points.resize(Points);
//Calculate Increments for point plotting
MainLine.xinc = (MainLine.x2 - MainLine.x1)/Points;
MainLine.yinc = (MainLine.y2 - MainLine.y1)/Points;
CLine1.xinc = (CLine1.x2 - CLine1.x1)/Points;
CLine1.yinc = (CLine1.y2 - CLine1.y1)/Points;
CLine2.xinc = (CLine2.x2 - CLine2.x1)/Points;
CLine2.yinc = (CLine2.y2 - CLine2.y1)/Points;
//Setup line arrays for tracing
vector<Line> Trace1;
vector<Line> Trace2;
vector<Line> NewLine;
Trace1.resize(Points);
Trace2.resize(Points);
NewLine.resize(Points+1);
for( int a = 1; a < Points; a++ ){
//Store all the points for each line
MLPoints[a].x = (MainLine.x1+(MainLine.xinc*a));
MLPoints[a].y = (MainLine.y1+(MainLine.yinc*a));
CL1Points[a].x = (CLine1.x1+(CLine1.xinc*a));
CL1Points[a].y = (CLine1.y1+(CLine1.yinc*a));
CL2Points[a].x = (CLine2.x1+(CLine2.xinc*a));
CL2Points[a].y = (CLine2.y1+(CLine2.yinc*a));
//Store trace lines' information
Trace1[a].x1 = MainLine.x1;
Trace1[a].y1 = MainLine.y1;
Trace1[a].x2 = CL2Points[a].x;
Trace1[a].y2 = CL2Points[a].y;
Trace2[a].x1 = CL1Points[a].x;
Trace2[a].y1 = CL1Points[a].y;
Trace2[a].x2 = MainLine.x2;
Trace2[a].y2 = MainLine.y2;
//Calculate trace lines' intersections
MLPoints[a].x = FindIntersectX( Trace1[a].x1 , Trace1[a].y1 , Trace1[a].x2 , Trace1[a].y2 ,
Trace2[a].x1 , Trace2[a].y1 , Trace2[a].x2 , Trace2[a].y2);
MLPoints[a].y = FindIntersectY( Trace1[a].x1 , Trace1[a].y1 , Trace1[a].x2 , Trace1[a].y2 ,
Trace2[a].x1 , Trace2[a].y1 , Trace2[a].x2 , Trace2[a].y2);
if( a == 1 ){
NewLine[a].x1 = MainLine.x1;
NewLine[a].y1 = MainLine.y1;
NewLine[a].x2 = MLPoints[a].x;
NewLine[a].y2 = MLPoints[a].y;
}
else{
NewLine[a].x1 = NewLine[a-1].x2;
NewLine[a].y1 = NewLine[a-1].y2;
NewLine[a].x2 = MLPoints[a].x;
NewLine[a].y2 = MLPoints[a].y;
};
};
//Connect last point
NewLine[Points].x1 = MLPoints[Points-1].x;
NewLine[Points].y1 = MLPoints[Points-1].y;
NewLine[Points].x2 = MainLine.x2;
NewLine[Points].y2 = MainLine.y2;
return NewLine;
};
void DrawBox(){
dbInk( dbRGB(0,0,0), dbRGB(255,255,255) );
dbLine( 32, 32, 32, ScreenHei-32 );
dbLine( 32, ScreenHei-32, ScreenWid-32, ScreenHei-32 );
dbLine( ScreenWid-32, ScreenHei-32, ScreenWid-32, 32 );
dbLine( ScreenWid-32, 32, 32, 32 );
};
void DarkGDK ( void )
{
dbSetWindowOn();
dbSetDisplayMode( ScreenWid, ScreenHei, ScreenBit );
dbSetWindowSize( WindowWid, WindowHei );
dbSetWindowTitle( "Bezier Curve" );
dbSetWindowPosition( 20,20);
dbSyncOn();
dbSyncRate( 60 );
dbBackdropOn();
dbColorBackdrop( dbRGB ( 255, 255, 255) );
dbSetTextFont("Arial");
dbSetTextSize(12);
//Set up new bezier curve
vector<Line> BezLine;
BezLine.swap( Bezier( 32, ScreenHei/2, ScreenWid-32, ScreenHei/2, cx, cy, pc ) );
while ( LoopGDK ( ) )
{
//Control lines and info
dbInk( dbRGB( 130, 130, 130 ), dbRGB(255,255,255));
dbText( 34, 34, "Click to re-arrange the control point.");
dbText( 34, 47, "Press 'Space' to display vertices.");
dbText( 34, 60, "Use 'W' and 'S' to increase/decrease vertices.");
dbText( 34, 112, "Vertex count: ");
dbText( 100, 112, dbStr( pc ) );
dbCircle( cx, cy, 10 );
dbLine( BezLine[1].x1, BezLine[1].y1, cx, cy );
dbLine( cx, cy, BezLine[ BezLine.size()-1 ].x2, BezLine[ BezLine.size()-1 ].y2 );
//Old line
dbInk( dbRGB( 0, 140, 100 ), dbRGB(255,255,255));
dbText(34,73,"Old Line");
dbLine( BezLine[1].x1, BezLine[1].y1, BezLine[ BezLine.size()-1 ].x2, BezLine[ BezLine.size()-1 ].y2 );
//New line
dbInk( dbRGB( 0, 60, 180 ), dbRGB(255,255,255));
dbText(34,86,"New Line");
//Draw new line
for(int a = 1; a < BezLine.size(); a++ ){
dbLine(BezLine[a].x1, BezLine[a].y1, BezLine[a].x2, BezLine[a].y2);
if( VertOn == true ){
dbCircle( BezLine[a].x1, BezLine[a].y1, 3 );
dbCircle( BezLine[BezLine.size()-1].x2, BezLine[BezLine.size()-1].y2, 3 );
};
};
//Handle all user input
if( dbMouseClick() == 1 ){
cx = dbMouseX();
cy = dbMouseY();
BezLine.swap( Bezier( 32, ScreenHei/2, ScreenWid-32, ScreenHei/2, cx, cy, pc ) );
};
if( dbScanCode() == 17 )
pc = pc + 2;
if( dbScanCode() == 31 && pc > 2)
pc = pc - 2;
if( dbSpaceKey() == 1 ){
VertOn = true;
BezLine.swap( Bezier( 32, ScreenHei/2, ScreenWid-32, ScreenHei/2, cx, cy, pc ) );
}
else
VertOn = false;
//Draw boundary box
DrawBox();
dbSync();
}
return;
}
Let me know how it works for you guys, thanks