MSAShape3D

Jan 2009

Over the years OpenGL has developed lots of different ways of sending vertex data to the graphics card: Immediate mode (glBegin / glVertex / glEnd etc.), Display Lists, Vertex Arrays, Vertex Buffer Objects etc. The first of these (Immediate Mode) is quite inefficient, so it's been dropped in OpenGL ES and depreciated in OpenGL 3.0. Instead we are to use Vertex Arrays or VBO's (Vertex Buffer Objects). They are a lot more efficient, but generally not as straightforward to setup and use.

For this reason I wrote a C++ class that wraps up the functionality of Vertex Arrays (and soon VBO's) in an immediate mode style syntax. I.e. you can carry on calling glBegin / glNormal / setColor / glTexCoord / glVertex / glEnd etc. but instead of sending the data on every function call, the class just caches all the data in client-side arrays, and sends it all in one go when you call glEnd(). Later you can call draw() to redraw the information you've put in. Thus improving performance on desktop systems, and allowing immediate mode style syntax on embedded systems such as the iPhone.

In short, the following code is now not supported on OpenGL ES (e.g. iPhone), and obsolete as of OpenGL 3.0:

glBegin(GL_TRIANGLE_STRIP)
glNormal3f(0, 0, 1);
setColor3f(1, 0, 0);
glTexCoord2f(0, 0);
glVertex3f(0, 0, 0);
 
setColor3f(1, 1, 0);
glTexCoord2f(1, 0);
glVertex3f(100, 0, 0);
 
setColor3f(1, 0, 1);
glTexCoord2f(0, 1);
glVertex3f(0, 100, 0);
 
setColor3f(0, 1, 1);
glTexCoord2f(1, 1);
glVertex3f(100, 100, 0);
glEnd();

Instead, you'd have to use VA's or VBO's. I'm not going to go into details of those because there's a lot of good tutorials already out there.

However, for people like myself who have a lot of code written in immediate mode, or sometimes just prefer immediate mode syntax, I wrote this C++ class to allow you to write:

ofxMSAShape3D myShape;
 
myShape.begin(GL_TRIANGLE_STRIP);
myShape.setNormal(0, 0, 1);
myShape.setColor(1, 0, 0);
myShape.setTexCoord(0, 0);
myShape.addVertex(0, 0, 0);
 
myShape.setNormal(1, 0, 0);
myShape.setColor(1, 1, 0);
myShape.setTexCoord(1, 0);
myShape.addVertex(100, 0, 0);
 
myShape.setNormal(0, 1, 0);
myShape.setColor(1, 0, 1);
myShape.setTexCoord(0, 1);
myShape.addVertex(0, 100, 0);
 
myShape.setNormal(0, 0, -1);
myShape.setColor(0, 1, 1);
myShape.setTexCoord(1, 1);
myShape.addVertex(100, 100, 0);
myShape.end();
 
 
for(int i=0; i<180; i++) {
   glPushMatrix();
   glTranslatef(i*5, 0, 0);
   float scale = i/180.0f;
   glScalef(scale, scale, scale);
   myShape.draw();   // this redraws the mesh with all cached info
 
   glRotatef(i*2, 0, 0, 1);
   myShape2.draw();   // you can draw different shapes inbetween, glClientStates are set as required by each shape
   glPopMatrix();
}

All the data is cached, and when myShape.end() is called, the arrays are sent to the server to be drawn. So immediate style syntax can still be used on embedded systems on iPhone, and performance on desktop systems is drastically improved. As well as all of the vertex, normal, color and texture coordinate information being stored, the required glClientStates are also stored, so you can now call myShape.draw() whenever you'd like to draw that mesh.

The class can be found at http://code.google.com/p/ofxmsaof/
direct link: http://code.google.com/p/ofxmsaof/source/browse/trunk/ofxMSAShape3D/src/...

E.g.:
The 25 line code below will give exact same results on iPhone and desktop.

The 25 line code below is all thats needed for this demo to run on linux, windows, mac osx and iPhone.

#include "testApp.h"
#include "ofxMSAShape3D.h"
 
ofxMSAShape3D myObj;
ofPoint pos;
 
void testApp::setup(){	 
	ofBackground(0, 0, 0);
	ofSetBackgroundAuto(false);
 
	// enable depth testing
	glEnable(GL_DEPTH_TEST);
 
	// select normal blend mode
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
	// preallocate space for 5000 vertices.
	myObj.reserve(5000);	
 
	// this is not nessecary but will speed up the addition of the first 5000 glVertex() commands between glBegin / glEnd
	// Even after the first 5000 glVertex() calls, you can still carry on calling glVertex and add new vertices...
	// but the arrays will be resized with every call to glVertex so will be a bit slow. 
	// Best to reserve as much space as you think you'll need up front
	// Its ok to over-reserve thousands cos memory usage is quite low (reserve(1000) allocates 48KB)
	// By default space for 1000 vertices is allocated...
}
 
 
 
void testApp::draw() {
	// clear depth buffer (but not color buffer)
	glClear(GL_DEPTH_BUFFER_BIT);
 
	// enable blending
	glEnable(GL_BLEND);
 
	// choose semi-transparent black color
	myObj.setColor(0, 0, 0, 0.05f);
 
	// draw a black semi-transparent rectangle across whole screen to fade it out a bit
	myObj.drawRect(0, 0, ofGetWidth(), ofGetHeight());
 
	// disable blending
	glDisable(GL_BLEND);
 
 
	// get current time (could use ofGetElapsedTimef() but I DONT want it to be fps independant
	// cos when I'm saving frames and it's running slow I want it to behave the same
	float curTime = ofGetFrameNum() * 1.0f/60.0f;
 
	// choose start rotation based on curTime
	float theta = sin(curTime * 0.17f) * TWO_PI;
 
	// set start position offset from center of screen
	float startOffset = -50.0f * sin(curTime * 0.09f);
	pos.set(ofGetWidth()/2 + cos(theta) * startOffset, ofGetHeight()/2 + sin(theta) * startOffset, 0);
 
	// begin a triangle strip
	myObj.begin(GL_TRIANGLE_STRIP);
	for(int i=0; i<5000; i++) {
		// calculate and set colors
		// RGB components modulate sinusoidally, frequency increases with iteration count
		myObj.setColor(sin(curTime * 0.8f + i * 0.0011f) * 0.5f + 0.5f, sin(curTime * 0.7f + i * 0.0013f) * 0.5f + 0.5f, sin(curTime * 0.3f + i * 0.0017f) * 0.5f + 0.5f);
 
		// do some maths to calculate vertex positions
		// modulate theta (offset rotation) sinusoidally, frequency increases with iteration count
		// use two modulations with different frequencies to create complex harmonic motion 
		theta += sin(curTime * 0.1f + i * 0.00062f) * 2.0f * DEG_TO_RAD * i * 0.0004 + sin(curTime*0.2f + i * 0.0009f) * 3.0f * DEG_TO_RAD;
		float cos_t = cos(theta);
		float sin_t = sin(theta);
 
		// x, y position cumulatively rotates
		// z position modulates sinusoidally, frequency increases with iteration count
		pos += ofPoint(cos_t, sin_t, sin(curTime * 0.5f + i*0.002f) * 0.5);
 
		// send vertex data to myObj
		myObj.addVertex(pos.x, pos.y, pos.z);
 
		// do some math to calculate another vertex position
		// perpendicular to rotation
		// thickness modulates sinusoidally, frequency increases with iteration count
		// also modulation frequency sinusoidally
		ofPoint pos2 = pos + ofPoint(sin_t, -cos_t) * ofMap(sin(curTime * 0.4f * ( 1.0f + i * 0.001f) + i * 0.06f + sin(i*0.001f) * 0.2f), -1, 1, 5, 10 +  i * 0.01f);
 
		// place second vertex (can also pass pointer (array) to coordinates)
		myObj.addVertex2v(pos2.v);
	}
 
	// end vertices and draw to screen
	myObj.end();
}
( categories: )