504 lines
11 KiB
C
504 lines
11 KiB
C
|
|
||
|
/* Copyright (c) Mark J. Kilgard, 1997. */
|
||
|
|
||
|
/* This program is freely distributable without licensing fees
|
||
|
and is provided without guarantee or warrantee expressed or
|
||
|
implied. This program is -not- in the public domain. */
|
||
|
|
||
|
/* This example demonstrates how to render particle effects
|
||
|
with OpenGL. A cloud of pinkish/orange particles explodes with the
|
||
|
particles bouncing off the ground. When the EXT_point_parameters
|
||
|
is present , the particle size is attenuated based on eye distance. */
|
||
|
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <math.h> /* for cos(), sin(), and sqrt() */
|
||
|
#ifdef _WIN32
|
||
|
#include <windows.h>
|
||
|
#endif
|
||
|
#define GL_GLEXT_PROTOTYPES
|
||
|
#include <GL/glut.h>
|
||
|
|
||
|
/* Some <math.h> files do not define M_PI... */
|
||
|
#ifndef M_PI
|
||
|
#define M_PI 3.14159265
|
||
|
#endif
|
||
|
|
||
|
#if 0 /* For debugging. */
|
||
|
#undef GL_EXT_point_parameters
|
||
|
#endif
|
||
|
|
||
|
static GLfloat angle = -150; /* in degrees */
|
||
|
static int spin = 0;
|
||
|
static int moving, begin;
|
||
|
static int newModel = 1;
|
||
|
static float theTime;
|
||
|
static int repeat = 1;
|
||
|
static int blend = 1;
|
||
|
int useMipmaps = 1;
|
||
|
int linearFiltering = 1;
|
||
|
|
||
|
static GLfloat constant[3] = { 1/5.0, 0.0, 0.0 };
|
||
|
static GLfloat linear[3] = { 0.0, 1/5.0, 0.0 };
|
||
|
static GLfloat theQuad[3] = { 0.25, 0.0, 1/60.0 };
|
||
|
|
||
|
#define MAX_POINTS 2000
|
||
|
|
||
|
static int numPoints = 200;
|
||
|
|
||
|
static GLfloat pointList[MAX_POINTS][3];
|
||
|
static GLfloat pointTime[MAX_POINTS];
|
||
|
static GLfloat pointVelocity[MAX_POINTS][2];
|
||
|
static GLfloat pointDirection[MAX_POINTS][2];
|
||
|
static int colorList[MAX_POINTS];
|
||
|
static int animate = 1, motion = 0;
|
||
|
|
||
|
static GLfloat colorSet[][4] = {
|
||
|
/* Shades of red. */
|
||
|
{ 0.7, 0.2, 0.4, 0.5 },
|
||
|
{ 0.8, 0.0, 0.7, 0.5 },
|
||
|
{ 1.0, 0.0, 0.0, 0.5 },
|
||
|
{ 0.9, 0.3, 0.6, 0.5 },
|
||
|
{ 1.0, 0.4, 0.0, 0.5 },
|
||
|
{ 1.0, 0.0, 0.5, 0.5 },
|
||
|
};
|
||
|
|
||
|
#define NUM_COLORS (sizeof(colorSet)/sizeof(colorSet[0]))
|
||
|
|
||
|
#define DEAD (NUM_COLORS+1)
|
||
|
|
||
|
|
||
|
#if 0 /* drand48 might be better on Unix machines */
|
||
|
#define RANDOM_RANGE(lo, hi) ((lo) + (hi - lo) * drand48())
|
||
|
#else
|
||
|
static float float_rand(void) { return rand() / (float) RAND_MAX; }
|
||
|
#define RANDOM_RANGE(lo, hi) ((lo) + (hi - lo) * float_rand())
|
||
|
#endif
|
||
|
|
||
|
#define MEAN_VELOCITY 3.0
|
||
|
#define GRAVITY 2.0
|
||
|
|
||
|
/* Modeling units of ground extent in each X and Z direction. */
|
||
|
#define EDGE 12
|
||
|
|
||
|
static void
|
||
|
makePointList(void)
|
||
|
{
|
||
|
float angle, velocity, direction;
|
||
|
int i;
|
||
|
|
||
|
motion = 1;
|
||
|
for (i=0; i<numPoints; i++) {
|
||
|
pointList[i][0] = 0.0;
|
||
|
pointList[i][1] = 0.0;
|
||
|
pointList[i][2] = 0.0;
|
||
|
pointTime[i] = 0.0;
|
||
|
angle = (RANDOM_RANGE(60.0, 70.0)) * M_PI/180.0;
|
||
|
direction = RANDOM_RANGE(0.0, 360.0) * M_PI/180.0;
|
||
|
pointDirection[i][0] = cos(direction);
|
||
|
pointDirection[i][1] = sin(direction);
|
||
|
velocity = MEAN_VELOCITY + RANDOM_RANGE(-0.8, 1.0);
|
||
|
pointVelocity[i][0] = velocity * cos(angle);
|
||
|
pointVelocity[i][1] = velocity * sin(angle);
|
||
|
colorList[i] = rand() % NUM_COLORS;
|
||
|
}
|
||
|
theTime = 0.0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
updatePointList(void)
|
||
|
{
|
||
|
float distance;
|
||
|
int i;
|
||
|
|
||
|
static double t0 = -1.;
|
||
|
double dt, t = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
|
||
|
if (t0 < 0.0)
|
||
|
t0 = t;
|
||
|
dt = t - t0;
|
||
|
t0 = t;
|
||
|
|
||
|
motion = 0;
|
||
|
for (i=0; i<numPoints; i++) {
|
||
|
distance = pointVelocity[i][0] * theTime;
|
||
|
|
||
|
/* X and Z */
|
||
|
pointList[i][0] = pointDirection[i][0] * distance;
|
||
|
pointList[i][2] = pointDirection[i][1] * distance;
|
||
|
|
||
|
/* Z */
|
||
|
pointList[i][1] =
|
||
|
(pointVelocity[i][1] - 0.5 * GRAVITY * pointTime[i])*pointTime[i];
|
||
|
|
||
|
/* If we hit the ground, bounce the point upward again. */
|
||
|
if (pointList[i][1] <= 0.0) {
|
||
|
if (distance > EDGE) {
|
||
|
/* Particle has hit ground past the distance duration of
|
||
|
the particles. Mark particle as dead. */
|
||
|
colorList[i] = NUM_COLORS; /* Not moving. */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
pointVelocity[i][1] *= 0.8; /* 80% of previous up velocity. */
|
||
|
pointTime[i] = 0.0; /* Reset the particles sense of up time. */
|
||
|
}
|
||
|
motion = 1;
|
||
|
pointTime[i] += dt;
|
||
|
}
|
||
|
theTime += dt;
|
||
|
if (!motion && !spin) {
|
||
|
if (repeat) {
|
||
|
makePointList();
|
||
|
} else {
|
||
|
glutIdleFunc(NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
idle(void)
|
||
|
{
|
||
|
updatePointList();
|
||
|
if (spin) {
|
||
|
angle += 0.3;
|
||
|
newModel = 1;
|
||
|
}
|
||
|
glutPostRedisplay();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
visible(int vis)
|
||
|
{
|
||
|
if (vis == GLUT_VISIBLE) {
|
||
|
if (animate && (motion || spin)) {
|
||
|
glutIdleFunc(idle);
|
||
|
}
|
||
|
} else {
|
||
|
glutIdleFunc(NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
recalcModelView(void)
|
||
|
{
|
||
|
glPopMatrix();
|
||
|
glPushMatrix();
|
||
|
glRotatef(angle, 0.0, 1.0, 0.0);
|
||
|
newModel = 0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
redraw(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
|
if (newModel)
|
||
|
recalcModelView();
|
||
|
|
||
|
glDepthMask(GL_FALSE);
|
||
|
|
||
|
/* Draw the floor. */
|
||
|
/* glEnable(GL_TEXTURE_2D);*/
|
||
|
glColor3f(0.5, 1.0, 0.5);
|
||
|
glBegin(GL_QUADS);
|
||
|
glTexCoord2f(0.0, 0.0);
|
||
|
glVertex3f(-EDGE, -0.05, -EDGE);
|
||
|
glTexCoord2f(20.0, 0.0);
|
||
|
glVertex3f(EDGE, -0.05, -EDGE);
|
||
|
glTexCoord2f(20.0, 20.0);
|
||
|
glVertex3f(EDGE, -0.05, EDGE);
|
||
|
glTexCoord2f(0.0, 20.0);
|
||
|
glVertex3f(-EDGE, -0.05, EDGE);
|
||
|
glEnd();
|
||
|
|
||
|
/* Allow particles to blend with each other. */
|
||
|
glDepthMask(GL_TRUE);
|
||
|
|
||
|
if (blend)
|
||
|
glEnable(GL_BLEND);
|
||
|
|
||
|
glDisable(GL_TEXTURE_2D);
|
||
|
glBegin(GL_POINTS);
|
||
|
for (i=0; i<numPoints; i++) {
|
||
|
/* Draw alive particles. */
|
||
|
if (colorList[i] != DEAD) {
|
||
|
glColor4fv(colorSet[colorList[i]]);
|
||
|
glVertex3fv(pointList[i]);
|
||
|
}
|
||
|
}
|
||
|
glEnd();
|
||
|
|
||
|
glDisable(GL_BLEND);
|
||
|
|
||
|
glutSwapBuffers();
|
||
|
}
|
||
|
|
||
|
/* ARGSUSED2 */
|
||
|
static void
|
||
|
mouse(int button, int state, int x, int y)
|
||
|
{
|
||
|
/* Scene can be spun around Y axis using left
|
||
|
mouse button movement. */
|
||
|
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
|
||
|
moving = 1;
|
||
|
begin = x;
|
||
|
}
|
||
|
if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
|
||
|
moving = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* ARGSUSED1 */
|
||
|
static void
|
||
|
mouseMotion(int x, int y)
|
||
|
{
|
||
|
if (moving) {
|
||
|
angle = angle + (x - begin);
|
||
|
begin = x;
|
||
|
newModel = 1;
|
||
|
glutPostRedisplay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
menu(int option)
|
||
|
{
|
||
|
switch (option) {
|
||
|
case 0:
|
||
|
makePointList();
|
||
|
break;
|
||
|
#ifdef GL_ARB_point_parameters
|
||
|
case 1:
|
||
|
glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, constant);
|
||
|
break;
|
||
|
case 2:
|
||
|
glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, linear);
|
||
|
break;
|
||
|
case 3:
|
||
|
glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, theQuad);
|
||
|
break;
|
||
|
#endif
|
||
|
case 4:
|
||
|
blend = 1;
|
||
|
break;
|
||
|
case 5:
|
||
|
blend = 0;
|
||
|
break;
|
||
|
#ifdef GL_ARB_point_parameters
|
||
|
case 6:
|
||
|
glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 1.0);
|
||
|
break;
|
||
|
case 7:
|
||
|
glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 10.0);
|
||
|
break;
|
||
|
#endif
|
||
|
case 8:
|
||
|
glEnable(GL_POINT_SMOOTH);
|
||
|
break;
|
||
|
case 9:
|
||
|
glDisable(GL_POINT_SMOOTH);
|
||
|
break;
|
||
|
case 10:
|
||
|
glPointSize(2.0);
|
||
|
break;
|
||
|
case 11:
|
||
|
glPointSize(4.0);
|
||
|
break;
|
||
|
case 12:
|
||
|
glPointSize(8.0);
|
||
|
break;
|
||
|
case 13:
|
||
|
spin = 1 - spin;
|
||
|
if (animate && (spin || motion)) {
|
||
|
glutIdleFunc(idle);
|
||
|
} else {
|
||
|
glutIdleFunc(NULL);
|
||
|
}
|
||
|
break;
|
||
|
case 14:
|
||
|
numPoints = 200;
|
||
|
break;
|
||
|
case 15:
|
||
|
numPoints = 500;
|
||
|
break;
|
||
|
case 16:
|
||
|
numPoints = 1000;
|
||
|
break;
|
||
|
case 17:
|
||
|
numPoints = 2000;
|
||
|
break;
|
||
|
case 666:
|
||
|
exit(0);
|
||
|
}
|
||
|
glutPostRedisplay();
|
||
|
}
|
||
|
|
||
|
/* ARGSUSED1 */
|
||
|
static void
|
||
|
key(unsigned char c, int x, int y)
|
||
|
{
|
||
|
switch (c) {
|
||
|
case 13:
|
||
|
animate = 1 - animate; /* toggle. */
|
||
|
if (animate && (motion || spin)) {
|
||
|
glutIdleFunc(idle);
|
||
|
} else {
|
||
|
glutIdleFunc(NULL);
|
||
|
}
|
||
|
break;
|
||
|
case ' ':
|
||
|
animate = 1;
|
||
|
makePointList();
|
||
|
glutIdleFunc(idle);
|
||
|
break;
|
||
|
case 27:
|
||
|
exit(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Nice floor texture tiling pattern. */
|
||
|
static char *circles[] = {
|
||
|
"....xxxx........",
|
||
|
"..xxxxxxxx......",
|
||
|
".xxxxxxxxxx.....",
|
||
|
".xxx....xxx.....",
|
||
|
"xxx......xxx....",
|
||
|
"xxx......xxx....",
|
||
|
"xxx......xxx....",
|
||
|
"xxx......xxx....",
|
||
|
".xxx....xxx.....",
|
||
|
".xxxxxxxxxx.....",
|
||
|
"..xxxxxxxx......",
|
||
|
"....xxxx........",
|
||
|
"................",
|
||
|
"................",
|
||
|
"................",
|
||
|
"................",
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
makeFloorTexture(void)
|
||
|
{
|
||
|
GLubyte floorTexture[16][16][3];
|
||
|
GLubyte *loc;
|
||
|
int s, t;
|
||
|
|
||
|
/* Setup RGB image for the texture. */
|
||
|
loc = (GLubyte*) floorTexture;
|
||
|
for (t = 0; t < 16; t++) {
|
||
|
for (s = 0; s < 16; s++) {
|
||
|
if (circles[t][s] == 'x') {
|
||
|
/* Nice blue. */
|
||
|
loc[0] = 0x1f;
|
||
|
loc[1] = 0x1f;
|
||
|
loc[2] = 0x8f;
|
||
|
} else {
|
||
|
/* Light gray. */
|
||
|
loc[0] = 0xca;
|
||
|
loc[1] = 0xca;
|
||
|
loc[2] = 0xca;
|
||
|
}
|
||
|
loc += 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||
|
|
||
|
if (useMipmaps) {
|
||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||
|
GL_LINEAR_MIPMAP_LINEAR);
|
||
|
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 16, 16,
|
||
|
GL_RGB, GL_UNSIGNED_BYTE, floorTexture);
|
||
|
} else {
|
||
|
if (linearFiltering) {
|
||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||
|
} else {
|
||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||
|
}
|
||
|
glTexImage2D(GL_TEXTURE_2D, 0, 3, 16, 16, 0,
|
||
|
GL_RGB, GL_UNSIGNED_BYTE, floorTexture);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
main(int argc, char **argv)
|
||
|
{
|
||
|
int i;
|
||
|
glutInit(&argc, argv);
|
||
|
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_MULTISAMPLE);
|
||
|
glutInitWindowPosition(0, 0);
|
||
|
glutInitWindowSize(300, 300);
|
||
|
|
||
|
for (i=1; i<argc; i++) {
|
||
|
if(!strcmp("-noms", argv[i])) {
|
||
|
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
|
||
|
printf("forcing no multisampling\n");
|
||
|
} else if(!strcmp("-nomipmaps", argv[i])) {
|
||
|
useMipmaps = 0;
|
||
|
} else if(!strcmp("-nearest", argv[i])) {
|
||
|
linearFiltering = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
glutCreateWindow("point burst");
|
||
|
glutDisplayFunc(redraw);
|
||
|
glutMouseFunc(mouse);
|
||
|
glutMotionFunc(mouseMotion);
|
||
|
glutVisibilityFunc(visible);
|
||
|
glutKeyboardFunc(key);
|
||
|
glutCreateMenu(menu);
|
||
|
glutAddMenuEntry("Reset time", 0);
|
||
|
glutAddMenuEntry("Constant", 1);
|
||
|
glutAddMenuEntry("Linear", 2);
|
||
|
glutAddMenuEntry("Quadratic", 3);
|
||
|
glutAddMenuEntry("Blend on", 4);
|
||
|
glutAddMenuEntry("Blend off", 5);
|
||
|
glutAddMenuEntry("Threshold 1", 6);
|
||
|
glutAddMenuEntry("Threshold 10", 7);
|
||
|
glutAddMenuEntry("Point smooth on", 8);
|
||
|
glutAddMenuEntry("Point smooth off", 9);
|
||
|
glutAddMenuEntry("Point size 2", 10);
|
||
|
glutAddMenuEntry("Point size 4", 11);
|
||
|
glutAddMenuEntry("Point size 8", 12);
|
||
|
glutAddMenuEntry("Toggle spin", 13);
|
||
|
glutAddMenuEntry("200 points ", 14);
|
||
|
glutAddMenuEntry("500 points ", 15);
|
||
|
glutAddMenuEntry("1000 points ", 16);
|
||
|
glutAddMenuEntry("2000 points ", 17);
|
||
|
glutAddMenuEntry("Quit", 666);
|
||
|
glutAttachMenu(GLUT_RIGHT_BUTTON);
|
||
|
|
||
|
if (!glutExtensionSupported("GL_ARB_point_parameters")) {
|
||
|
fprintf(stderr, "Sorry, GL_ARB_point_parameters is not supported.\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
glShadeModel(GL_FLAT);
|
||
|
glEnable(GL_DEPTH_TEST);
|
||
|
glEnable(GL_POINT_SMOOTH);
|
||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
glPointSize(8.0);
|
||
|
#if GL_ARB_point_parameters
|
||
|
glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, theQuad);
|
||
|
#endif
|
||
|
glMatrixMode(GL_PROJECTION);
|
||
|
gluPerspective( /* field of view in degree */ 40.0,
|
||
|
/* aspect ratio */ 1.0,
|
||
|
/* Z near */ 0.5, /* Z far */ 40.0);
|
||
|
glMatrixMode(GL_MODELVIEW);
|
||
|
gluLookAt(0.0, 1.0, 8.0, /* eye location */
|
||
|
0.0, 1.0, 0.0, /* center is at (0,0,0) */
|
||
|
0.0, 1.0, 0.); /* up is in postivie Y direction */
|
||
|
glPushMatrix(); /* dummy push so we can pop on model
|
||
|
recalc */
|
||
|
|
||
|
makePointList();
|
||
|
makeFloorTexture();
|
||
|
|
||
|
glutMainLoop();
|
||
|
return 0; /* ANSI C requires main to return int. */
|
||
|
}
|