Project pages for CS351 and CS352, OpenGL Computer Graphics courses taken in Fall 2005 and Winter 2006 at Northwestern University.
Shape
Project Goals
The goal for this project is to simulate a vehicle moving in 2-D front view. Using OpenGL technology, the vehicle will move accordin to the users' keyboard input as well as mouse movement. If possible the vehicle model will have an integrated artificial intelligence which will enable itself to avoid nearby objects including other cars on the track and any other random objects that are to be put on the road. The vehicle will be able to move itself according to the track programmed.
Progress
The original plan for illustrating a car has been changed due to the complexity and elaboration the car drawing requires. Instead, a simple 3d object paper plane will be used to simulate a moving object in the air.
Method
While having the code based on the xform04 workspace, a paper plane object is created using gl_traiangles function,drawing 4 attached triangles that composes each folded faces of the plane.With the xform04 code performing translation, rotation functionality had been modified, so that it involves matrix calculation. Also, its ability to scale up and down the object has been added by using keypads m(+) and n(-).
Results
At last, the program runs, and the plane if flying around well.Though in terms of functionality, the plane moves and rotates around,enhanced graphics would have helped it look much better, more complex structured plane for instance.Also, having couple of the building structures in projection while having plane move around itself in modelviewwould have created a more realistic simulation of the object flying around.
Implementation
Project package: Download(Zip/601)
void display(void)
//------------------------------------------------------------------------------
// GLUT 'display' Callback. GLUT calls this fcn when it needs you to redraw
// the dislay window's contents. Your program should never call 'display()',
// because it will confuse GLUT--instead, call glutPostRedisplay() if you need
// to trigger a redrawing of the screen.
{
// Clear the frame-buffer
glClear(GL_COLOR_BUFFER_BIT);
// =============================================================================
// START DRAWING CODE HERE
// =============================================================================
glMatrixMode(GL_PROJECTION); // select projection matrix,
glPushMatrix(); // save current version, then
setProj.applyMatrix(); // apply results of mouse, keyboard
// Draw model-space axes:
glMatrixMode(GL_MODELVIEW); // select the modelview matrix,
glPushMatrix(); // save current version, then
setModel.applyMatrix(); // apply results of mouse, keyboard
glBegin(GL_TRIANGLES);
glColor3f (0.9, 0.9, 0.9); // inner right body
glVertex3f(0.0*setModel.x_scale, 0.0*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f(0.3*setModel.x_scale, 0.5*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f(0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glEnd();
glBegin(GL_TRIANGLES);
glColor3f (0.9, 0.9, 0.9); // inner left body
glVertex3f(0.0*setModel.x_scale, 0.0*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f(-0.3*setModel.x_scale, 0.5*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f(0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glEnd();
glBegin(GL_TRIANGLES);
glColor3f (1.0, 1.0, 1.0); // outer right wing
glVertex3f(0.8*setModel.x_scale, 0.3*setModel.y_scale, 0.2*setModel.z_scale);
glVertex3f(0.3*setModel.x_scale, 0.5*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f(0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glEnd();
glBegin(GL_TRIANGLES);
glColor3f (1.0, 1.0, 1.0); // outer left wing
glVertex3f(-0.8*setModel.x_scale, 0.3*setModel.y_scale, 0.2*setModel.z_scale);
glVertex3f(-0.3*setModel.x_scale, 0.5*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f(0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glEnd();
glBegin(GL_LINES); // draw axes in model-space.
glColor3f ( 0.8, 0.8, 0.8); // inner vertex right
glVertex3f( 0.3*setModel.x_scale, 0.5*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f( 0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glColor3f ( 0.8, 0.8, 0.8); // inner vertex left
glVertex3f( -0.3*setModel.x_scale, 0.5*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f( 0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glColor3f ( 0.8, 0.8, 0.8); // backbone
glVertex3f( 0.0*setModel.x_scale, 0.0*setModel.y_scale, 0.0*setModel.z_scale);
glVertex3f( 0.0*setModel.x_scale, 0.7*setModel.y_scale, -1.0*setModel.z_scale);
glEnd();
// Draw axes in world-space:
glLoadIdentity(); // wipe out current GL_MODELVIEW matrix so that
// model-space vertices become world-space
// vertices without change.
glBegin(GL_LINES); // start drawing lines:
glColor3f ( 1.0, 0.0, 0.0); // Red X axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 1.0, 0.0, 0.0);
glColor3f ( 0.0, 1.0, 0.0); // Green Y axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 1.0, 0.0);
glColor3f ( 0.0, 0.0, 1.0); // Blue Z axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 0.0, 1.0);
glEnd(); // end drawing lines
glPopMatrix(); // restore original MODELVIEW matrix.
glMatrixMode(GL_PROJECTION);// Restore the original GL_PROJECTION matrix
glPopMatrix();
// ================================================================================
// END DRAWING CODE HERE
// ================================================================================
cout << "Screen ReDrawn" << endl;
glFlush();
}
void keyboard(unsigned char key, int x, int y)
//------------------------------------------------------------------------------
// GLUT 'keyboard' Callback. User pressed an alphanumeric keyboard key.
// ('special' keys such as return, function keys, arrow keys? keyboardspecial)
{
switch(key) {
case 27: // Esc
case 'Q':
case 'q':
exit(0); // Quit application
break;
case 'm': // enlarge the object
setModel.x_scale *= 1.1;
setModel.y_scale *= 1.1;
setModel.z_scale *= 1.1;
break;
case 'n': // shrink the object
setModel.x_scale *= 0.9;
setModel.y_scale *= 0.9;
setModel.z_scale *= 0.9;
break;
case 'r':
setProj.reset();
break;
case 'R':
setModel.reset();
break;
default:
printf("unknown key. Try arrow keys, r, R, or q");
break;
}
// We might have changed something. Force a re-display
glutPostRedisplay();
}
void keySpecial(int key, int x, int y)
//------------------------------------------------------------------------------
// GLUT 'special' Callback. User pressed an non-alphanumeric keyboard key, such
// as function keys, arrow keys, etc.
{
static double x_pos, y_pos, z_pos;
switch(key)
{
case GLUT_KEY_UP: // up arrow key
setModel.z_pos -= 0.1;
break;
case GLUT_KEY_DOWN: // dn arrow key
setModel.z_pos += 0.1;
break;
case GLUT_KEY_LEFT: // left arrow key
setModel.x_pos -= 0.1;
break;
case GLUT_KEY_RIGHT: // right arrow key
setModel.x_pos += 0.1;
break;
default:
break;
}
printf("key=%d, setModel.x_pos=%f, setModel.z_pos=%f\n",
key,setModel.x_pos,setModel.z_pos);
// We might have changed something. Force a re-display
glutPostRedisplay();
}
void mouseClik(int buttonID,int upDown,int xpos,int ypos)
//------------------------------------------------------------------------------
// GLUT 'mouse' Callback. User caused a click/unclick event with the mouse:
// buttonID== 0 for left mouse button,
// (== 1 for middle mouse button?)
// == 2 for right mouse button;
// upDown == 0 if mouse button was pressed down,
// == 1 if mouse button released.
// xpos,ypos == position of mouse cursor, in pixel units within the window.
// *CAREFUL!* Microsoft puts origin at UPPER LEFT corner of the window.
{
if(buttonID==0) // if left mouse button,
{
if(upDown==0) // on mouse press,
{
setProj.isDragging = 1; // get set to record GL_PROJECTION changes.
setProj.m_x = xpos; // Dragging begins here.
setProj.m_y = ypos;
}
else setProj.isDragging = 0;
}
else if(buttonID==2) // if right mouse button,
{
if(upDown==0)
{
setModel.isDragging = 1;// get set to record GL_MODELVIEW changes.
setModel.m_x = xpos; // Dragging begins here.
setModel.m_y = ypos;
}
else setModel.isDragging = 0;
}
else // something else.
{
setProj.isDragging = 0; // default; DON'T change GL_PROJECTION
setModel.isDragging = 0; // or GL_MODELVIEW
}
printf("clik: buttonID=%d, upDown=%d, xpos=%d, ypos%d\n",
buttonID,upDown,xpos,ypos);
}
void mouseMove(int xpos,int ypos)
//------------------------------------------------------------------------------
// GLUT 'move' Callback. User moved the mouse while pressing 1 or more of the
// mouse buttons. xpos,ypos is the MS-Windows position of the mouse cursor in
// pixel units within the window.
// CAREFUL! MSoft puts origin at UPPER LEFT corner pixel of the window!
{
#define JT_INCR 1.0 // Degrees rotation per pixel of mouse move
if(setModel.isDragging==1) // if we're dragging the left mouse,
{ // increment the x,y rotation amounts.
setModel.x_rot += JT_INCR*(xpos - setModel.m_x);
setModel.y_rot += JT_INCR*(ypos - setModel.m_y);
setModel.m_x = xpos;
setModel.m_y = ypos;
printf("move %d, %d\n", xpos,ypos); // print what we did.
}
if(setProj.isDragging==1) // if we're dragging theright mouse,
{ // increment the x,y rotation amounts.
setProj.x_rot += JT_INCR*(xpos - setProj.m_x);
setProj.y_rot += JT_INCR*(ypos - setProj.m_y);
setProj.m_x = xpos;
setProj.m_y = ypos;
printf("move %d, %d\n", xpos,ypos); // print what we did.
}
// We might have changed something. Force a re-display
glutPostRedisplay();
#undef JT_INCR
}
View
Project Goals
The goal for this project is to simulate two planets in 3-dimensional space, such as the earth and the moon, where one revolutionizes around the other while it rotates itself. Using OpenGL technology, the spheres will move according to the users' keyboard input as well as mouse movement.
Progress
The difficulties for drawing an earth, as it is made up of multiple colors in different pattern - expressing each of the funcky shaped continents in blue background on a sphere is a complex task - had been faced. For that reason, the two spheres have been drawn using a duplicate opengl calls that illustrate a sphere using a dot orientation, and mixture of the two displays the color each sphere ought to have.
Method
While having the code based on the xform05 workspace, the two sphere object were created using duplicate gl_sphere function, each creating a different set of dots in different/mixed colors. With the xform05 code performing translation and rotation, the scaling functionality had been added by using keypads m(+) and n(-). The transformation of the objects are done by calling opengl functions while adjusting the gl_projection and gl_modelview matrix, and the orientation of the objects change while the user's viewpoint changes.
Results
Though the motivation for this project is to complete a program that lays out the understanding of the computer graphics concepts, using the opengl callbacks, the objects drawn are once again simple spheres that were drawn with two lines of opengl functions. For the upcoming projects, I intend to produce a drawing that is more complicated and elaborate, possibly something that even involves particles movins itself and follows the laws of physics.
Implementation
Project package: Download(Zip/359KB)
void display(void)
//------------------------------------------------------------------------------
// GLUT 'display' Callback. GLUT calls this fcn when it needs you to redraw
// the dislay window's contents. Your program should never call 'display()',
// because it will confuse GLUT--instead, call glutPostRedisplay() if you need
// to trigger a redrawing of the screen.
{
// Clear the frame-buffer
glClear(GL_COLOR_BUFFER_BIT);
// =============================================================================
// START DRAWING CODE HERE
// =============================================================================
glMatrixMode(GL_PROJECTION); // select projection matrix,
glPushMatrix(); // save current version, then
setProj.applyMatrix(); // apply results of mouse, keyboard
// Draw model-space axes:
glMatrixMode(GL_MODELVIEW); // select the modelview matrix,
glPushMatrix(); // save current version, then
setModel.applyMatrix(); // apply results of mouse, keyboard
// glColor3f(0.7,0.7,0.7); // Set color to light grey, and draw a
// glutWireTeapot(0.8); // Little Teapot(see OpenGL Red Book, pg 660)
// (GL_LIGHTING would make this look better)
glColor3f(0.0,0.1,1.0);
glutWireSphere(setModel.x_scale, 131, 131);
glColor3f(0.0,1.0,0.2);
glutWireSphere(setModel.x_scale, 71, 71);
glBegin(GL_LINES); // draw axes in model-space.
glColor3f ( 1.0, 1.0, 0.0); // Yellow X axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f(setModel.x_scale, 0.0, 0.0);
glColor3f ( 0.0, 1.0, 1.0); // Cyan Y axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, setModel.y_scale, 0.0);
glColor3f ( 1.0, 0.0, 1.0); // Purple Z axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 0.0, setModel.z_scale);
glEnd();
// Draw axes in world-space:
glLoadIdentity(); // wipe out current GL_MODELVIEW matrix so that
// model-space vertices become world-space
// vertices without change.
glColor3f(1.0,0.1,0.1);
glutWireSphere(0.5, 133, 133);
glColor3f(0.8,0.8,0.0);
glutWireSphere(0.5, 71, 71);
glBegin(GL_LINES); // start drawing lines:
glColor3f ( 1.0, 0.0, 0.0); // Red X axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 1.0, 0.0, 0.0);
glColor3f ( 0.0, 1.0, 0.0); // Green Y axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 1.0, 0.0);
glColor3f ( 0.0, 0.0, 1.0); // Blue Z axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 0.0, 1.0);
glEnd(); // end drawing lines
glPopMatrix(); // restore original MODELVIEW matrix.
glMatrixMode(GL_PROJECTION);// Restore the original GL_PROJECTION matrix
glPopMatrix();
// ================================================================================
// END DRAWING CODE HERE
// ================================================================================
cout << "Screen ReDrawn" << endl;
glFlush();
}
Shade
Project Goals
The goal for this project is to simulate a teapot with tea in it. While the teapot itself is going to be purely transparent glass, the tea inside of it is going to be somewhat dark and opaque. The two different materials will clearly distinguish the lighting effect the teapot receives and it is intended that the user will be able to modify their transparency, in other words, their ambient, diffuse, and specular characteristics.
Progress
After experimenting with Nate Robins' opengl tutorial programs, drawing the two objects of different materials went well without any major obstacles. However, giving users control for changing characteristics of the materials and intensities of the light seem to cause a major problem in the way the whole program runs. By making the GLfloat values as variables, the program stalls quite often.
Method
While having the code based on the xform05 workspace, two openGL functions glutSolidTorus and glutSolidTeapot were used to create a teapot and the tea in it. With the given code being capable of transformation functionalities such as translation and rotation, the lighting and corresponding shading effects changes according to the transformation caused by the user input. Despite considerable differences the other shading components can make, diffuse reflectance and emitted light intensity were used to distinguish the characteristics of the two materials. Furthermore, additional openGL functions(including GL_BLEND) were used to produce more transparent teapot.
Results
As far as the requirement for this project goes, the screen looks satisfying as it produces two complex materials with a complex lighting. Although there was an intent to allow the users to control position of light with keyboard input, it came to the fact that making light position matrix as a variable causes instability of the whole program. As the time and openGL capability permits, this will be first thing to be considered for enhancement, along with a gravity effect where the material inside the teapot would change its shape as the user twists the teapot up and down.
Implementation
Project package: Download(Zip/347KB)
void display(void)
//------------------------------------------------------------------------------
// GLUT 'display' Callback. GLUT calls this fcn when it needs you to redraw
// the dislay window's contents. Your program should never call 'display()',
// because it will confuse GLUT--instead, call glutPostRedisplay() if you need
// to trigger a redrawing of the screen.
{
// Clear the frame-buffer
glClear(GL_COLOR_BUFFER_BIT);
// =============================================================================
// START DRAWING CODE HERE
// =============================================================================
glMatrixMode(GL_PROJECTION); // select projection matrix,
glPushMatrix(); // save current version, then
setProj.applyMatrix(); // apply results of mouse, keyboard
// Draw model-space axes:
glMatrixMode(GL_MODELVIEW); // select the modelview matrix,
glPushMatrix(); // save current version, then
setModel.applyMatrix(); // apply results of mouse, keyboard
glRotated(90, 1.0, 0.0, 0.0);
glTranslatef (0.0, 0.0, 0.2);
GLfloat material_Ja[] = {0.30, 0.27, 0.12, 1.00};
GLfloat material_Jd[] = {1.00, 0.30, 0.30, 1.00};
GLfloat material_Js[] = {1.00, 1.00, 0.00, 1.00};
GLfloat material_Je[] = {0.00, 0.00, 0.00, 1.00};
GLfloat material_JSe = 10;
// glMaterialfv(GL_FRONT, GL_AMBIENT, material_Ja);
glMaterialfv(GL_FRONT, GL_DIFFUSE, material_Jd);
// glMaterialfv(GL_FRONT, GL_SPECULAR, material_Js);
glMaterialfv(GL_FRONT, GL_EMISSION, material_Je);
// glMaterialf(GL_FRONT, GL_SHININESS, material_JSe);
glColor3f(1.0,1.0,1.0); // Set color to light grey, and draw a
glutSolidTorus(0.35, 0.40, 100, 15);
glPopMatrix(); // restore original MODELVIEW matrix.
// Draw axes in world-space:
glLoadIdentity(); // wipe out current GL_MODELVIEW matrix so that
// model-space vertices become world-space
// vertices without change.
GLfloat material_Ka[] = {0.30, 0.27, 0.12, 1.00};
GLfloat material_Kd[] = {0.8, 0.8, 0.8, 0.60};
GLfloat material_Ks[] = {0.98, 0.63, 0.19, 1.00};
GLfloat material_Ke[] = {0.00, 0.30, 0.30, 0.60};
GLfloat material_Se = 10;
// glMaterialfv(GL_FRONT, GL_AMBIENT, material_Ka);
glMaterialfv(GL_FRONT, GL_DIFFUSE, material_Kd);
// glMaterialfv(GL_FRONT, GL_SPECULAR, material_Ks);
glMaterialfv(GL_FRONT, GL_EMISSION, material_Ke);
// glMaterialf(GL_FRONT, GL_SHININESS, material_Se);
glEnable (GL_BLEND);
glDepthMask (GL_FALSE);
glBlendFunc (GL_SRC_ALPHA, GL_ONE);
glColor3f(0.7,0.7,0.7); // Set color to light grey, and draw a
glutSolidTeapot(0.8); // Little Teapot(see OpenGL Red Book, pg 660)
// (GL_LIGHTING would make this look better)
glDepthMask (GL_TRUE);
glDisable (GL_BLEND);
glBegin(GL_LINES); // start drawing lines:
glColor3f ( 1.0, 0.0, 0.0); // Red X axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 1.0, 0.0, 0.0);
glColor3f ( 0.0, 1.0, 0.0); // Green Y axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 1.0, 0.0);
glColor3f ( 0.0, 0.0, 1.0); // Blue Z axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 0.0, 1.0);
glEnd(); // end drawing lines
glMatrixMode(GL_PROJECTION);// Restore the original GL_PROJECTION matrix
glPopMatrix();
// ================================================================================
// END DRAWING CODE HERE
// ================================================================================
cout << "Screen ReDrawn" << endl;
glFlush();
}
Smooth
Project Goals
The goal for this project is to create a pool table set, in which the table surface becomes interactive and each ball will be mapped with different textures.
Progress
First of all, a lot of time was spent to figure out how to load a bmp file. Error that took the most time on this project was one on bmpread due to misallocated library. When one comes across an error that looks like unresolved external symbol "unsigned char * __cdecl glmReadPPM(char *,int *,int *)" (?glmReadPPM@@YAPAEPADPAH1@Z) referenced in function _main make sure that GL.h, GLU.h, GLUT.h and GLAux.h are all included along with the pragma comments for the corresponding libraries. For example, add the following:
#pragma comment(lib,"glut32.lib")
#pragma comment(lib,"opengl32.lib") // link OpenGL libraries for corresponding header files above
#pragma comment(lib,"glu32.lib")
#pragma comment(lib,"glaux.lib")
Method
While applying texture mapping on each of the balls was the most critical part of this project, a sphere object was mapped by dividing it into smaller pieces of quadratic strips to carry little portions of texture map. The C code by Paul Bourke of Swinburne University of Australia was taken, and slightly modified. His code is available on public, and is accessible by clicking on the link above.
Results
Finally the library registration problem was solved, and a quick progress on the project has been seen. As of now, each of the 9-ball set is texture mapped according to its number, as well as the shading effect. In the future, action scripts will be added so that the user can control the white ball, and in accordance to the laws of physics, other balls can move along.
As this has been the last project of the quarter, lastly, I would thank everyone in CS 351, especially professor Tumblin for the support in and out of the class.
Results
Finally the library registration problem was solved, and a quick progress on the project has been seen. As of now, each of the 9-ball set is texture mapped according to its number, as well as the shading effect. In the future, action scripts will be added so that the user can control the white ball, and in accordance to the laws of physics, other balls can move along.
As this has been the last project of the quarter, lastly, I would thank everyone in CS 351, especially professor Tumblin for the support in and out of the class.
Source Code
The following is the code snippet for the core implementation of this project.
Downloadthe whole project package (Zip/1.06MB) for more detailed reference.
void display(void)
//------------------------------------------------------------------------------
// GLUT 'display' Callback. GLUT calls this fcn when it needs you to redraw
// the dislay window's contents. Your program should never call 'display()',
// because it will confuse GLUT--instead, call glutPostRedisplay() if you need
// to trigger a redrawing of the screen.
{
// Clear the frame-buffer
glClear(GL_COLOR_BUFFER_BIT);
// =============================================================================
// START DRAWING CODE HERE
// =============================================================================
glMatrixMode(GL_PROJECTION); // select projection matrix,
glPushMatrix(); // save current version, then
setProj.applyMatrix(); // apply results of mouse, keyboard
// Draw model-space axes:
glMatrixMode(GL_MODELVIEW); // select the modelview matrix,
glPushMatrix(); // save current version, then
setModel.applyMatrix(); // apply results of mouse, keyboard
GLfloat material_Jd[] = {0.80, 0.80, 0.80, 1.00};
GLfloat material_Je[] = {0.60, 0.60, 0.60, 0.60};
glMaterialfv(GL_FRONT, GL_DIFFUSE, material_Jd);
glMaterialfv(GL_FRONT, GL_EMISSION, material_Je);
glBindTexture(GL_TEXTURE_2D, texture[0]); // draw the white ball
DrawSphere(-2.0, 0.1, 0.0, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[9]); // draw ball #9
DrawSphere(0.6, 0.1, 0.0, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[1]); // draw ball #1
DrawSphere(0.2, 0.1, 0.0, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[2]); // draw ball #2
DrawSphere(0.4, 0.1, -0.1, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[3]); // draw ball #3
DrawSphere(0.4, 0.1, 0.1, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[4]); // draw ball #4
DrawSphere(0.6, 0.1, -0.2, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[5]); // draw ball #5
DrawSphere(0.6, 0.1, 0.2, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[6]); // draw ball #6
DrawSphere(0.8, 0.1, -0.1, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[7]); // draw ball #7
DrawSphere(0.8, 0.1, 0.1, 0.10);
glBindTexture(GL_TEXTURE_2D, texture[8]); // draw ball #8
DrawSphere(1.0, 0.1, 0.0, 0.10);
glPopMatrix(); // restore original MODELVIEW matrix.
// Draw axes in world-space:
glLoadIdentity(); // wipe out current GL_MODELVIEW matrix so that
// model-space vertices become world-space
// vertices without change.
GLfloat material_Ka[] = {0.30, 0.27, 0.12, 1.00};
GLfloat material_Kd[] = {0.4, 0.4, 0.4, 0.60};
GLfloat material_Ks[] = {0.98, 0.63, 0.19, 1.00};
GLfloat material_Ke[] = {0.40, 0.40, 0.40, 0.40};
GLfloat material_Se = 10;
glMaterialfv(GL_FRONT, GL_DIFFUSE, material_Kd);
glMaterialfv(GL_FRONT, GL_EMISSION, material_Ke);
glEnable (GL_BLEND);
glDepthMask (GL_FALSE);
glBlendFunc (GL_SRC_ALPHA, GL_ONE);
glBindTexture(GL_TEXTURE_2D, texture[10]);
glBegin(GL_QUADS); // start drawing the table
glTexCoord2f(0.0f, 0.0f); glVertex3f(3.0f, 0.0f, 2.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-3.0f, 0.0f, 2.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-3.0f, 0.0f, -2.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(3.0f, 0.0f, -2.0f);
glEnd(); // end drawing lines
glDepthMask (GL_TRUE);
glDisable (GL_BLEND);
glBegin(GL_LINES); // start drawing lines:
glColor3f ( 1.0, 0.0, 0.0); // Red X axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 1.0, 0.0, 0.0);
glColor3f ( 0.0, 1.0, 0.0); // Green Y axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 1.0, 0.0);
glColor3f ( 0.0, 0.0, 1.0); // Blue Z axis
glVertex3f( 0.0, 0.0, 0.0);
glVertex3f( 0.0, 0.0, 1.0);
glEnd(); // end drawing lines
glMatrixMode(GL_PROJECTION);// Restore the original GL_PROJECTION matrix
glPopMatrix();
// ================================================================================
// END DRAWING CODE HERE
// ================================================================================
cout << "Screen ReDrawn" << endl;
glFlush();
}
/*
Create a sphere centered at c, with radius r, and precision n
Draw a point for zero radius spheres
Use CCW facet ordering
"method" is 0 for quads, 1 for triangles
(quads look nicer in wireframe mode)
Partial spheres can be created using theta1->theta2, phi1->phi2
in radians 0 < theta < 2pi, -pi/2 < phi < pi/2
Slight modified from the source code taken from http://astronomy.swin.edu.au/~pbourke/opengl/sphere/
*/
void DrawSphere(double xc, double yc, double zc, double r)
{
int i,j,n;
double t1,t2,t3;
float e[3],p[3];
n=100;
double PI = 3.14260;
for (j=0;j<n/2;j++) {
t1 = (j*PI / (n/2)) - (PI/2);
t2 = ((j+1)*PI / (n/2)) - (PI/2);
glBegin(GL_TRIANGLE_STRIP);
for (i=0;i<=n;i++) {
t3 = 2*i*PI / n - (PI/2);
e[0] = cos(t1) * cos(t3);
e[1] = sin(t1);
e[2] = cos(t1) * sin(t3);
p[0] = xc + r * e[0];
p[1] = yc + r * e[1];
p[2] = zc + r * e[2];
glNormal3f(e[0],e[1],e[2]);
glTexCoord2f(i/(double)n,2*j/(double)n);
glVertex3f(p[0],p[1],p[2]);
e[0] = cos(t2) * cos(t3);
e[1] = sin(t2);
e[2] = cos(t2) * sin(t3);
p[0] = xc + r * e[0];
p[1] = yc + r * e[1];
p[2] = zc + r * e[2];
glNormal3f(e[0],e[1],e[2]);
glTexCoord2f(i/(double)n,2*(j+1)/(double)n);
glVertex3f(p[0],p[1],p[2]);
}
glEnd();
}
}
Particle Systems
Project Goals
Requirements for this project were to create an interactive particle system program that consists of systems in which particles move independently, move under influence of a vector field, and that are linked together. The initial plan was to create a natural scene that presents trees and leaves falling off by wind effect, and a flock of birds flying around, where the leaves coming off the tree would the system mocing under influence of a 3D vector field with an aging process.
Progress
Given the starter code for the particle system, the first step taken was to create a third object for the bouncing sphere system, which then extended to 10-particle system. The complete system with 10 particles were replicated in order to provide a base setup for 3 different particle systems. While being part of one global object, swarm, each system differs in that it uses different update function calls, which are variations of getNetForceOn.
Method
With difficulties of drawing birds or real-life looking objects, flock system and falling leaves have been replaced with system of flying teapots and snowing effect, respectably. Flying teapots move independently, and they are simulated by modifying the original particle system with higher charge and more flexible strings which are now invisible. Tree system is made with green spheres which dissemble chunks of leaves that make part of a tree, and connecting strings that become the stems. This is the system that holds particles more firmly even in explicit mode, and avoids parts of tree falling down to the ground by minimizing gravity effect. The last is snowing effect with influence of a vector field, which is gravity, and each particles are dynamically created and destroyed. They are simulated in a way that everytime a particle hits the ground, the program deletes that particle and creates another at new location.
Results
All of the functionalities discussed above have been found to work. The only confilict that is noticeable is that between particles of different system have collision avoidance issue. In order to solve this, for each particle, next movement should be computed with the rest of the world, and that brings the application to another level of complexity. Other possible future works would definitely include higher quality objects as well as more interactive viewing positions such as camera following a flying object.
Here are the screenshots taken at various timestamps.
Implementation
Project package: Download (Zip/554KB)
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/glut.h>
#include "SolverDemo.h"
void display()
//------------------------------------------------------------------------------
// GLUT calls this function when it needs the screen re-drawn. Your program
// should NEVER call this function--that would confuse GLUT. Instead, call
// glutPostRedisplay() to force GLUT to start this callback properly.
//
// All drawing commands are applied to the back buffer.
// The 'glutSwapBuffers()' call at the end of this function swaps the front
// and back buffers to display the completed drawing.
{
GLfloat A_diffuse[] = {1.0, 0.0, 0.0, 1.0}; // red,
GLfloat B_diffuse[] = {0.0, 0.0, 1.0, 1.0}; // blue,
GLfloat O_diffuse[] = {0.0, 1.0, 0.0, 1.0}; // green,
GLfloat tree_diffuse[] = {0.5, 0.3, 0.0, 1.0}; // green,
GLfloat spring_diffuse[] = {0.6, 1.0, 0.1, 1.0}; // spring,
GLfloat summer_diffuse[] = {0.4, 1.0, 0.1, 1.0}; // summer,
GLfloat fall_diffuse[] = {0.9, 0.9, 0.1, 1.0}; // autumn - leaves,
GLfloat winter_diffuse[] = {1.0, 1.0, 1.0, 1.0}; // winter - snow,
GLfloat winter_ambient[] = {1.0, 1.0, 1.0, 1.0}; // winter - snow,
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, 0.0, 10.0, // World-space x,y,z eyepoint position
0.0, 0.0, 0.0, // the x,y,z point you're looking at
0.0, 1.0, 0.0); // the camera's 'up' direction
// Clear the Z- and frame-buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
int season = glutGet(GLenum(GLUT_ELAPSED_TIME)) % 80000;
if (season < 10000)
glClearColor(0.0f, 0.5f, 1.0f, 0.0f); // Set screen-clearing color
else if (season > 15000 && season < 25000)
glClearColor(0.0f, 0.2f, 1.0f, 0.0f); // Set screen-clearing color
else if (season > 30000 && season < 40000)
glClearColor(0.2f, 0.4f, 0.6f, 0.0f); // Set screen-clearing color
else if (season > 45000 && season < 65000)
glClearColor(0.8f, 0.8f, 0.8f, 0.0f); // Set screen-clearing color
else
glClearColor(0.1f, 0.1f, 0.1f, 0.0f); // Set screen-clearing color
glRotatef(myApp.y_pos * 10.0f, 1.0f, 0.0f, 0.0f);
glRotatef(myApp.x_pos * 10.0f, 0.0f, 1.0f, 0.0f);
//glTranslatef(x_pos, y_pos, 0);
glTranslatef(0.0, -18.0, 0.0);
glPushMatrix(); // save current matrix;
if (season < 15000)
glMaterialfv(GL_FRONT, GL_DIFFUSE, spring_diffuse);
else if (season < 30000)
glMaterialfv(GL_FRONT, GL_DIFFUSE, summer_diffuse);
else if (season < 45000)
glMaterialfv(GL_FRONT, GL_DIFFUSE, fall_diffuse);
else
{
glMaterialfv(GL_FRONT, GL_DIFFUSE, winter_diffuse);
glMaterialfv(GL_FRONT, GL_AMBIENT, winter_ambient);
}
// move the modelview origin to Blob A's world-space position
glutSolidSphere(16.0, 64, 64);
// draw blob with diameter
// proportional to its mass, as if all blobs were
// made of materials with the same density.
glPopMatrix(); // go back to our original modelview matrix.
glTranslatef(0.0, 16.0, 0.0);
glPushMatrix(); // save current matrix;
glRotatef(270.0, 1.0, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_DIFFUSE, tree_diffuse);
glutSolidCone(0.5, 2.0, 16, 16);
glTranslatef(0.0, 2.0, 0.0);
glPopMatrix(); // go back to our original modelview matrix.
for (int drawt=0; drawt<10; drawt++)
{
//draw sphere A
glPushMatrix(); // save current matrix;
if (season < 15000)
glMaterialfv(GL_FRONT, GL_DIFFUSE, spring_diffuse);
else if (season < 30000)
glMaterialfv(GL_FRONT, GL_DIFFUSE, summer_diffuse);
else if (season < 45000)
glMaterialfv(GL_FRONT, GL_DIFFUSE, fall_diffuse);
else
break;
glTranslatef(myApp.treenow[drawt]->position.i,
myApp.treenow[drawt]->position.j,
myApp.treenow[drawt]->position.k);
if (drawt%2 == 1)
glutSolidSphere(0.5f, 32, 32);
else
glutSolidSphere(0.4f, 32, 32);
// draw blob with diameter
// proportional to its mass, as if all blobs were
// made of materials with the same density.
glPopMatrix(); // go back to our original modelview matrix.
}
if (season > 45000)
{
for (int drawl=0; drawl<10; drawl++)
{
//draw sphere A
glPushMatrix(); // save current matrix;
glMaterialfv(GL_FRONT, GL_DIFFUSE, winter_diffuse);
// move the modelview origin to Blob A's world-space position
glTranslatef(myApp.leafnow[drawl]->position.i,
myApp.leafnow[drawl]->position.j,
myApp.leafnow[drawl]->position.k);
glutSolidSphere(0.2f, 32, 32);
// draw blob with diameter
// proportional to its mass, as if all blobs were
// made of materials with the same density.
glPopMatrix(); // go back to our original modelview matrix.
}
}
glPushMatrix();
glDisable(GL_LIGHTING);
glBegin(GL_LINES);
glColor3f(0.3f, 0.3f, 0.3f); // gray lines
for (int drawm=0; drawm<10; drawm++)
{
glVertex3f(myApp.treenow[drawm]->pAnchor->position.i,
myApp.treenow[drawm]->pAnchor->position.j,// from Blob A's anchor pt.
myApp.treenow[drawm]->pAnchor->position.k);
glVertex3f(myApp.treenow[drawm]->position.i,// to center of blob A,
myApp.treenow[drawm]->position.j,
myApp.treenow[drawm]->position.k);
}
glEnd();
glEnable(GL_LIGHTING);
glPopMatrix();
for (int drawi=0; drawi<10; drawi++)
{
//draw sphere A
glPushMatrix(); // save current matrix;
glMaterialfv(GL_FRONT, GL_DIFFUSE, A_diffuse);
// move the modelview origin to Blob A's world-space position
glTranslatef(myApp.planenow[drawi]->position.i,
myApp.planenow[drawi]->position.j,
myApp.planenow[drawi]->position.k);
glutSolidTeapot(0.4);
// draw blob with diameter
// proportional to its mass, as if all blobs were
// made of materials with the same density.
glPopMatrix(); // go back to our original modelview matrix.
}
glEnd();
glEnable(GL_LIGHTING);
glPopMatrix();
glFlush(); // execute any unfinished drawing commands. We'll wait.
glutSwapBuffers();
// Swap the frame buffer. Make the back buffer the new front buffer and
//vice-versa. To avoid flicker we use two buffers. While we display one,
//we make changes to the second hidden buffer.
}
void reshape(int w, int h)
//------------------------------------------------------------------------------
// GLUT calls this function whenever the user resizes the display window by
// dragging a corner, etc., and is also called on window createion.
// Re-initialize the Projection matrix here.
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
// Set the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0, (double)w/(double)h, NEAR, FAR);
// Initialize the modelview matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void keyboard(unsigned char key, int x, int y)
//------------------------------------------------------------------------------
// GLUT calls this function when user hits an alpha-numeric key.
{
static float oldG;
switch(key)
{
case 'd':
case 'D':
myApp.planenow[0]->velocity.addIn( 1.0f, -1.0f, 0.01f);
myApp.planenow[1]->velocity.addIn(-1.0f, -1.0f, 0.0f);
myApp.planenow[2]->velocity.addIn(0.0f, 1.0f, 0.0f);
//myApp.treenow[1]->velocity.addIn(0.1f, 0.0f, -0.1f);
break;
case 'g':
case 'G': // toggle gravity on/off.
if( myApp.k_gravity > 0.0f) // if we have gravity already,
{
oldG = myApp.k_gravity; // save its old value, and
myApp.k_gravity = 0.0f; // zero gravity.
printf("Gravity off...\n");
}
else
{ // Restore gravity to its old value.
myApp.k_gravity = oldG;
printf("Gravity on.\n");
}
break;
case 'p': // toggle between pause and run.
case 'P':
if(myApp.isPaused==1)
{
myApp.isPaused = 0;
printf("Resumed. Hit 'p' to Pause.\n");
}
else
{
myApp.isPaused = 1;
printf("Paused! Hit 'p' to resume ...\n ");
}
break;
case 's':
case 'S':
if (myApp.solver_type == SOLV_IMPLICIT)
{
myApp.solver_type = SOLV_EXPLICIT;
printf("Switched to Explicit Solver...\n");
}
else
{
myApp.solver_type = SOLV_IMPLICIT;
printf("Switched to Implicit Solver...\n");
}
break;
case 27: // Esc
case 'Q':
case 'q':
exit(0); // quit the program
break;
}
// We might have changed something. Force a re-display.
glutPostRedisplay();
}
void special(int key, int x, int y)
//------------------------------------------------------------------------------
// GLUT calls this function when user presses a non-numeric key, such as arrow
// keys. We use arrow keys to rotate the camera around the origin.
{
switch(key)
{
case GLUT_KEY_UP:
myApp.y_pos += 1.0f;
break;
case GLUT_KEY_DOWN:
myApp.y_pos -= 1.0f;
break;
case GLUT_KEY_LEFT:
myApp.x_pos -= 1.0f;
break;
case GLUT_KEY_RIGHT:
myApp.x_pos += 1.0f;
break;
default:
break;
}
// We might have changed something. Force a re-display.
glutPostRedisplay();
}
void idle(void)
//------------------------------------------------------------------------------
// CAREFUL! if you register this function, your program will call it VERY OFTEN
// GLUT will call idle() ANYTIME it has nothing else to do; even if you put
// NOTHING in this function your program will use ~100% of CPU time.
// If you need to put something here (e.g. animation) that will change the
// displayed image contents, remember to call glutPostRedisplay() at the end
// to force screen updating.
{
//some frame-rate independent motion;
static int oldTime = 0;
int currentTime = glutGet(GLenum(GLUT_ELAPSED_TIME));
// Ensures fairly constant framerate
int timestamp = currentTime;
for(int leafcount=0; leafcount<11; leafcount++)
{
if((myApp.leafnow[leafcount] != NULL) && (myApp.leafnow[leafcount]->position.j < -2.0f))
{
delete myApp.leafnow[leafcount]; // discard any dyn. alloc'd memory...
delete myApp.leafnext[leafcount];
myApp.leafnow[leafcount] = new Blob((4.0f-(leafcount%7)), (3.0f+(leafcount%3)), ((leafcount%5)-2.0f),
myApp.lOrigin,(1.0f+(leafcount)), 1.0f, 4.0f, 50.0f);
myApp.leafnext[leafcount] = new Blob(*(myApp.leafnow[leafcount])); // copy constructor
}
}
if (currentTime - oldTime > ANIMATION_DELAY)
{
// animate the scene
if(myApp.isPaused==0) // if 'paused', change nothing,
{
if (myApp.solver_type == SOLV_EXPLICIT)
{
myApp.explicitSolve();
} else
{
myApp.implicitSolve();
}
}
oldTime = currentTime;
// notify window it has to be repainted
glutPostRedisplay();
}
}
void gl_init()
//------------------------------------------------------------------------------
//Initialize all opengl related stuff
{
// glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Set screen-clearing color
glEnable(GL_DEPTH_TEST) ; // Enable the Z-Buffer
GLfloat light_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
GLfloat light_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat light_specular[] = { 0.0f, 0.0f, 0.0f, 1.0f };
// light_position is NOT default value:
GLfloat light_position[] = { 0.0f, 0.2f, 2.4f, 0.0f };
glLightfv (GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv (GL_LIGHT0, GL_POSITION, light_position);
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void glut_init(int *argc, char **argv)
//------------------------------------------------------------------------------
// Initialize all glut related stuff, including callback functions.
{
// Call glut's initilization
glutInit(argc, argv);
// double buffer, RGB color model, depth buffer
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
// Now create a window of the required size
glutInitWindowSize(WIDTH, HEIGHT);
glutInitWindowPosition(0, 0);
glutCreateWindow(TITLE);
// register all glut Callback functions. GlUT calls
glutDisplayFunc(display); // display() to redraw screen
glutKeyboardFunc(keyboard); // keyboard() when user presses key
glutSpecialFunc(special); // special() for non-alphanumeric keys
glutReshapeFunc(reshape); // reshape() when window size changes
glutIdleFunc(idle); // idle() when all other tasks done.
// These register callback functions for mouse clicks, mouse moves.
// Implement for yourself as needed
glutMouseFunc(NULL);
glutMotionFunc(NULL);
printf("==============Keyboard Controls==========================\n");
printf(" d or D --displace the blobs a bit\n");
printf(" g or G --toggle gravity on/off\n");
printf(" p or P --toggle between paused/not paused.\n");
printf(" s or S --toggle between implicit/explicit solver \n");
printf(" (implicit at startup) \n");
printf(" ARROW keys --Rotate camera in 10 degree increments\n");
printf(" q, Q or ESC --quit/end program\n");
printf("==========================================================\n");
}
int main(int argc, char** argv)
//==============================================================================
{
// Initialize: first, set the 'pOrigin' blob fixed in space
myApp.pOrigin = new Blob( 1.0f, 5.0f, -2.0f, NULL, // position, anchor,
0.0f, 0.0f, 0.0f, 0.0f); // mass, charge, spring rest
// length, spring strength
myApp.lOrigin = new Blob( 0.0f, 1.0f, 0.0f, NULL, // position, anchor,
0.0f, 0.0f, 0.0f, 0.0f); // mass, charge, spring rest
// length, spring strength
myApp.tOrigin = new Blob( 0.0f, 0.5f, 0.0f, NULL, // position, anchor,
0.0f, 0.0f, 0.0f, 0.0f); // mass, charge, spring rest
// length, spring strength
myApp.treenow[0] = new Blob( 0.0f, 2.2f, 0.0f, myApp.tOrigin, 30.0f, 0.5f, 0.5f, 120.0f);
myApp.treenow[1] = new Blob( 0.2f, 2.0f, 0.0f, myApp.tOrigin, 30.0f, 0.5f, 0.3f, 120.0f);
myApp.treenow[2] = new Blob( 0.0f, 1.6f, 0.2f, myApp.tOrigin, 30.0f, 0.5f, 0.5f, 120.0f);
myApp.treenow[3] = new Blob( 0.0f, 1.8f, -0.2f, myApp.tOrigin, 30.0f, 0.5f, 0.5f, 120.0f);
myApp.treenow[4] = new Blob( -0.2f, 2.0f, 0.0f, myApp.tOrigin, 30.0f, 0.5f, 0.3f, 120.0f);
myApp.treenow[5] = new Blob( 0.2f, 1.6f, 0.2f, myApp.tOrigin, 30.0f, 0.5f, 0.5f, 120.0f);
myApp.treenow[6] = new Blob( 0.2f, 2.0f, -0.2f, myApp.tOrigin, 30.0f, 0.5f, 0.5f, 120.0f);
myApp.treenow[7] = new Blob( -0.2f, 1.8f, 0.2f, myApp.tOrigin, 30.0f, 0.5f, 0.3f, 120.0f);
myApp.treenow[8] = new Blob( -0.2f, 2.0f, -0.2f, myApp.tOrigin, 30.0f, 0.5f, 0.5f, 120.0f);
myApp.treenow[9] = new Blob( 0.0f, 2.4f, 0.0f, myApp.tOrigin, 30.0f, 0.5f, 0.4f, 120.0f);
myApp.treenow[10] = new Blob( 0.0f, 1.6f, 0.0f, myApp.tOrigin, 30.0f, 0.5f, 0.1f, 120.0f);
for(int mainlp=0; mainlp<11; mainlp++)
{
myApp.planenow[mainlp] = new Blob((1.5f+(0.1f*mainlp)), (2.0f-(0.1f*mainlp)), (0.8f-(0.1f*mainlp)), myApp.pOrigin,
20.0f, 4.0f, 5.0f, 180.0f);
myApp.planenext[mainlp] = new Blob(*(myApp.planenow[mainlp])); // copy constructor
myApp.leafnow[mainlp] = new Blob((0.7f+(mainlp%4)), (4.5f-(mainlp%7)), (3.5f-(mainlp%3)), myApp.lOrigin,
(1.0f+(mainlp%5)), 1.0f, 4.0f, 50.0f);
myApp.leafnext[mainlp] = new Blob(*(myApp.leafnow[mainlp])); // copy constructor
myApp.treenext[mainlp] = new Blob(*(myApp.treenow[mainlp])); // copy constructor
}
// Initialize GLUT
glut_init(&argc, argv);
// Now initialize OpenGL stuff
gl_init();
// Finally enter the GLUT infinite loop
glutMainLoop();
// until user quits.
return 0;
}
References
|
 Updating...
OpenGL-SourceCode-1Shape.zip (587k) Hoonio, Sep 22, 2011, 5:42 PM
OpenGL-SourceCode-2View.zip (350k) Hoonio, Sep 22, 2011, 5:42 PM
OpenGL-SourceCode-3Shade.zip (346k) Hoonio, Sep 22, 2011, 5:42 PM
OpenGL-SourceCode-4Smooth.zip (1089k) Hoonio, Sep 22, 2011, 5:42 PM
OpenGL-SourceCode-5ParticleSystems.zip (554k) Hoonio, Sep 22, 2011, 5:42 PM
Hoonio, Sep 22, 2011, 6:24 PM
|