Wednesday, March 11, 2009

Drawing a Rosette



Building the Rosette


Above is a picture of a rosette with 40 points. To make a rosette, create a set of equidistant points around a circle and then connect every point to every other point.

Candidate Points


First, we need to compute the rosette points by making points equally spaced around a circle.

struct GLFloatPoint{GLfloat x, y;};

// Compute the candidate points, by equally
// spacing them around a circle.
GLFloatPoint* points = new GLFloatPoint[numPoints];
float angleStep = (3.14159f * 2.0f / numPoints);
float currentAngle = 0.0f;
for(unsigned int i=0; i<numPoints; i++)
{
points[i].x = radius*cos(currentAngle);
points[i].y = radius*sin(currentAngle);

currentAngle += angleStep;
}

Connecting Points


If you connect every point to every other one, you end up with duplicate edges between vertices.

How do you get not create duplicate edges in the simplest possible way? Once you connect a certain point to all others, keep it out of the list of points to connect to. This can be done using two loops. The outer loop tracks the vertex we are connecting from, and in the inner loop tracks the vertex to which we are connecting.

unsigned int index = 0;
// The vertex we are connecting from.
for(unsigned int j=0; j<numPoints; j++)
{
// Connect to all other points.
// Lines have an origin and end point.
for(unsigned int k=j+1; k<numPoints; k++)
{
rosettePoints[index].x = points[j].x;
rosettePoints[index].y = points[j].y;
index++;
rosettePoints[index].x = points[k].x;
rosettePoints[index].y = points[k].y;
index++;
}
}


Drawing


Vertex Arrays


Using vertex arrays, we can build the entire rosette at startup, and then can send all the vertices to be drawn at once. Using an array of GLFloatPoint, causes the x's and y's in the structs to align in memory just as if they had been put into an array of GLfloat.

Conceptually,

/* Structs */
GLFloatPoint A, B, C;
GLFloatPoint pointArray[] = {A, B, C}
/* Manually */
GLfloat floatArray[] = {A.x,A.y,
B.x,B.y, C.x,C.y}

// You need to cast the pointers for this to work,
// but this is why I miss pointers in Java/Ruby.
GLFloatPoint* pointPtr = (GLFloatPoint*)floatArray;
pointPtr[0].x = // A.x
pointPtr[0].y = // A.y
pointPtr[1].x = // B.x

GLfloat* floatPtr = (GLfloat*)pointArray;
floatPtr[0] = // A.x
floatPtr[1] = // A.y
floatPtr[2] = // B.x
floatPtr[3] = // B.y


Struct Pointer Magic


In short, we can merrily use the array of GLFloatPoint in place of a big array of floats. This makes it easier to figure out where you are in the array quickly, since incrementing a GLFloatPoint* in a loop will move you forward one point. Also, you can use array notation, point[10], for the 11'th point, for instance.


// Allow us to use vertex arrays
glEnableClientState(GL_VERTEX_ARRAY);

// Set the array of points to use
glVertexPointer(2, GL_FLOAT, 0, rosettePoints);

// Render lines from the array vertices.
// rosetteSize = (numPoints-1)*numPoints/2*2.
// This comes from connecting every point to
// every other point without duplicates.
// The "*2" comes from two vertices per line.
glDrawArrays(GL_LINES, 0, rosetteSize);

No comments:

Post a Comment