Monday, 29 December 2008

Flower Bezier curves

Managed to lose my Bezier demo (Flower/Spiral generator) in November - (damn you 'Time Machine') - Actually, it turned out to be a good thing, as it made me rethink the maths - in terms of Vectors rather than equations of straight lines. The four control points for the cubic bezier are calculated by working out the tangents to lines radiating from the centre of a circle. Using the radius line and its tangent - we work out an orthogonal matrix - applying a 'tilt' (rotation matrix) to squash or expand the petal.
My lost code was horrible - all sorts of case statements to deal with angle quadrants.


The following code - is the very start of my new Demo program




int displayWidth,displayHeight ;
String mode ;
float CX,CY ;


float degreesToRadians(float th)
{
return PI * th /180.0f ;
}



float translateX(float x)
{
return x+CX ;
}


float translateY(float y)
{
return -y+displayHeight-CY ;
}


PVector [] calcControlPoints(float angle,float ro,float radius,float tilt,float petalLength)
{

PVector [] bezControlPoints = new PVector[4] ;

float halfRo = ro / 2.0f ;

PVector[] cPoints = calcPetalPointPair(angle-halfRo,tilt,petalLength) ;
PVector startPt = cPoints[0] ;
PVector fPoint = cPoints[1] ;


bezControlPoints[0] = new PVector(startPt.x * radius,startPt.y * radius) ;
bezControlPoints[1] = new PVector(startPt.x * radius + fPoint.x,startPt.y * radius + fPoint.y) ;



PVector [] cPoints2 = calcPetalPointPair(angle+halfRo,-tilt,petalLength) ;
startPt = cPoints2[0] ;
fPoint = cPoints2[1] ;

bezControlPoints[3] = new PVector(startPt.x * radius,startPt.y * radius) ;
bezControlPoints[2] = new PVector(startPt.x * radius + fPoint.x,startPt.y * radius + fPoint.y) ;


return bezControlPoints ;

}


void drawBezier(PVector [] cps)
{
drawBezier(cps[0].x,cps[0].y,cps[1].x,cps[1].y,cps[2].x,cps[2].y,cps[3].x,cps[3].y) ;
}

void drawBezier(float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4)
{
noFill() ;
stroke(0,0,0) ;
bezier(translateX(x1),translateY(y1),
translateX(x2),translateY(y2),
translateX(x3),translateY(y3),
translateX(x4),translateY(y4)) ;



}


void drawControlPoints(PVector[] cps)
{
stroke(255,0,0) ;
drawLine(cps[0].x,cps[0].y,cps[1].x,cps[1].y) ;
stroke(0,255,0) ;
drawLine(cps[2].x,cps[2].y,cps[3].x,cps[3].y) ;

}



PVector[] calcPetalPointPair(float angle,float tilt,float petalLength)
{


PVector startPt = new PVector(1.0*sin(degreesToRadians(angle)),1.0*cos(degreesToRadians(angle)));


PVector vert = new PVector(0,0,1.0f) ; // Unit Vector Perp to XY Plane
PVector nCPc = startPt.cross(vert) ; // Calculate Tangent to Circle at startPt.x,startPt.y


PMatrix2D iMat = new PMatrix2D() ;

iMat.rotate(degreesToRadians(tilt < 0 ? 180+tilt : tilt)) ; // Petal Tilt


PMatrix2D cMat = new PMatrix2D(nCPc.x,startPt.x,0f,nCPc.y,startPt.y,0.0f) ;

// Matrix
// | nCPc.x startx |
// | nCPc.y starty |


cMat.preApply(iMat) ; // Apply our orthog axis to the rotation matrix


PVector fPoint = new PVector() ;
PVector sPoint = new PVector(petalLength,0) ; // Petal Length

cMat.mult(sPoint,fPoint) ; // Apply Full transformation to petal Vector

PVector [] points = new PVector[2] ;
points[0] = startPt ;
points[1] = fPoint ;

return points;

}





void setup()
{

mode = JAVA2D ;


displayHeight = screen.height/2 ;
displayWidth = screen.width/2 ;
CX = displayWidth / 2 ;
CY = displayHeight / 2 ;

size(displayWidth, displayHeight,mode);
loop() ;

}

void drawLine(float x,float y,float x2,float y2) {
line(translateX(x),translateY(y),translateX(x2),translateY(y2)) ;
}

void drawCircle(float x,float y,float rad)
{
ellipse(translateX(x), translateY(y), rad,rad) ;
}




void draw() {

background(255);
float dt = 1/frameRate ; // We should calculate dt - time passed since last update-draw

float radius = 20.0f ;
float tilt = 70.0f ;
float petalLength = 250.0f ;
float ro = 15.0f ;
float step = 7.0f ;
float numpetals = 360.0f / step ;

noFill() ;
ellipseMode(RADIUS) ;
stroke(0,0,0) ;
drawCircle(0,0,radius) ;

for (float angle = 0.0f ; angle < step*numpetals ; angle+=step)
{
PVector [] cps = calcControlPoints(angle,ro,radius,tilt,petalLength) ;
drawBezier(cps) ;
}




}

3 comments:

monkstone said...

I only recently found PVector in the processing documentation (I don't think it's mentioned in Greenberg). Quite possibly it should be 'introduced' in the book? SVG image would be nice for your fly, I've being thinking about creating one for my saucer shape, but it would spoil the idea of creating a hybrid shape.

John Wilson said...

Thanks Martin, Yes - your blog introduced me to PVector - You can see from my earlier blogs, I had my own Vector class for 2D and 3D points.

I wanted to stay away from any copyright material including SVG/PNG/GIF images. There needs to be a resource point to pick up these files.

monkstone said...

Admittedly you can find some nice fly images as copyrighted clipart, but I wasn't thinking in terms of such perfection, SVG is only an XML file after all. What I meant about the PVector is that it seems to be a useful addition to to processing that perhaps should be introduced formally in the book. I've just be playing with PVector class, very handy for determining angle between vectors.