ports/graphics/ffff/files/patch-hole.cpp.save
Emanuel Haupt 6cdf88141e Add ffff, a fast mandelbrot fractal generator with features such as:
* OpenGL
   * realtime zoom
   * SSE/AltiVec QuadPixel
   * SSE2/3DNow! DualPixel calc
   * FPU per pixel calc
   * GPU asm (Fragment/Vertex) calc
   * multiprocessor support
   * benchmarking
   * optimized assembler code!

This port uses the standard mandelbrot fractal at near-Xaos speed. Yet every
pixel is computed. There is also an interesting parameter ray algoritymn using
your 3D card. A 3D card is strongly recommended for screen speed and additional
coprocessing power.

WWW: http://sourceforge.net/projects/ffff/

PR:		103441
Submitted by:	rossiya@gmail.com
2006-10-04 10:54:05 +00:00

1125 lines
27 KiB
Text

--- ./hole.cpp.save.orig Wed Oct 4 11:21:15 2006
+++ ./hole.cpp.save Wed Oct 4 11:22:13 2006
@@ -0,0 +1,1122 @@
+// ----------------------
+// OpenGL Black Hole Simulator.
+//
+// Written by and Copyright Chris Halsall (chalsall@chalsall.com).
+// First published on the O'Reilly Network on Linux.com
+// (oreilly.linux.com). September 2000. All rights reserved.
+//
+// This code is licensed under the GNU GPL Version 2.0.
+// See (URL: http://www.gnu.org/copyleft/gpl.html ) for details.
+//
+// Coded to the groovy tunes of Fluke: Risotto.
+//
+// Dedicated to Stephen W. Hawking, one of the greatest explorers
+// of our time.
+
+#define PROGRAM_TITLE "O'Reilly Net: Black Hole -- C.Halsall"
+
+
+#include <stdio.h> // Always a good idea.
+#include <stdlib.h> // For RAND_MAX.
+#include <time.h> // For our FPS stats.
+#include <math.h> // For M_PI
+#include <GL/gl.h> // OpenGL itself.
+#include <GL/glu.h> // GLU support library.
+#include <GL/glut.h> // GLUT support library.
+
+
+// A quick macro to populate a 1x3 vector array.
+#define ourVectInit(a,x,y,z) { (a)[0]=x; (a)[1]=y; (a)[2]=z; }
+
+// Structure to hold all the data for each particle.
+typedef struct {
+ int Running;
+ float Pos[3]; // Position.
+ float Vel[3]; // Velocity.
+ float Grav[3]; // Acceleration.
+ float Color[3];
+} Particle;
+
+
+// ------
+// Some global variables.
+
+// Window and Texture IDs, Window sizes.
+int Texture_ID;
+int Window_ID;
+int Window_Width=640;
+int Window_Height=480;
+
+// We'll request a sphere from the GLU library at run-time.
+struct GLUquadric *Black_Hole;
+
+// "Name" for first (and only) OpenGL display list.
+#define STAR_FIELD 1
+
+
+// Pointer for allocated array of Particles.
+Particle *Parts;
+
+// Particle status variables.
+int Parts_Running=0;
+int Parts_Allocated=0;
+int Parts_LastUnused=1;
+
+// Number of parts initially in the system. Make sure there's at least
+// one over a hundred (101, 801), so our Root particle doesn't get deleted.
+int Parts_Num = 801;
+
+float Parts_Brightness = 0.15;
+
+
+// Drawing flags.
+int Draw_Axis = 0;
+int Draw_Vectors = 0;
+int Draw_Stars = 1;
+int Heads_Up = 0;
+int Texture_On = 1;
+
+
+// Particle Gun variables.
+float Gun_PX = 0;
+float Gun_PY = 0;
+float Gun_PZ = 4;
+
+float Gun_VX =-0.135;
+float Gun_VY = 0.0125;
+float Gun_VZ = 0.0;
+float Gun_R = 0.005;
+float Gun_OffR = 0.050;
+
+// Backwards firing and off-target probabilities.
+float Gun_Back = 0.9, Gun_Off = 0.9;
+
+// '.' key toggels between keypad adjusting gun position and eject vect.
+int Gun_Control = 1;
+
+
+// Orbit and motion settings.
+int On_Orbit=1;
+int Move_Enable=1;
+int Move_Step=0;
+
+// Observer initial placement.
+float Obs_Angle=114.0;
+float Obs_Height=.2;
+float Obs_Dist=4.6;
+
+// Calculated observer location.
+float Obs[3];
+
+// How quickly do the orbits decay?
+// Lower number (limit 1) is faster decay.
+
+int Decay_Factor = 6000;
+
+// The force of gravity exterted by the black hole.
+float Grav = 0.075;
+
+// Somewhat arbitrary values for Event and Escape horizons.
+
+#define EVENT_HORIZON_GRAV .5
+
+#define ESCAPE_HORIZON 30
+#define ESCAPE_HORIZON_SQR (ESCAPE_HORIZON * ESCAPE_HORIZON)
+
+
+// ------
+// Frames per second (FPS) statistic variables and routine.
+
+#define FRAME_RATE_SAMPLES 50
+int FrameCount=0;
+float FrameRate=0;
+
+static void ourDoFPS(
+ void
+)
+{
+ static clock_t last=0;
+ clock_t now;
+ float delta;
+
+ if (++FrameCount >= FRAME_RATE_SAMPLES) {
+ now = clock();
+ delta= (now - last) / (float) CLOCKS_PER_SEC;
+ last = now;
+
+ FrameRate = FRAME_RATE_SAMPLES / delta;
+ FrameCount = 0;
+ }
+}
+
+
+// ------
+// String rendering routine; leverages on GLUT routine.
+
+static void ourPrintString(
+ void *font,
+ char *str
+)
+{
+ int i,l=strlen(str);
+
+ for(i=0;i<l;i++)
+ glutBitmapCharacter(font,*str++);
+}
+
+
+// ------
+// Reallocates our array of particles, adding to or removing as
+// requested.
+
+Particle *ourAllocParticles(
+ int Num
+)
+{
+ int i;
+ Particle *P;
+
+ P = realloc(Parts, sizeof(Particle) * Num);
+
+ if (!P)
+ return 0;
+
+ if (Parts_Allocated < Num)
+ memset( &P[Parts_Allocated],
+ 0,sizeof(Particle) * (Num-Parts_Allocated));
+
+ if (Parts_LastUnused > Num)
+ Parts_LastUnused = Num;
+
+ Parts_Running = 0;
+ for (i = 0; i < Num; i++)
+ if (P[i].Running)
+ Parts_Running++;
+
+ Parts_Allocated = Num;
+ Parts = P;
+
+ return P;
+}
+
+
+// ------
+// Function to return a random floating number between 0 and the passed
+// parameter.
+
+float ourRand(
+ float Max
+)
+{
+ return( (Max * rand()) / RAND_MAX );
+}
+
+
+// ------
+// Builds a Display List containing a random star field.
+//
+// Note: this could also be done by calculating the star points in this
+// routine, which would be faster than having OpenGL perform two
+// rotations (matrix multiplications) for each star. However, this
+// technique is simpler and faster for the programmer, and demonstrates
+// how successive transformations can be a powerful tool.
+
+void ourBuildStarfield(
+ int Stars
+)
+{
+ int Cnt;
+
+ glNewList(STAR_FIELD, GL_COMPILE);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+
+ for ( Cnt = 0; Cnt < Stars; Cnt++) {
+
+ // Vary the color for each star.
+ glColor4f(
+ 0.8 + ourRand(0.2),
+ 0.8 + ourRand(0.2),
+ 0.8 + ourRand(0.2),
+ .95);
+
+ // Vary the size. Ensure integer sizes to avoid alias shimmering.
+ glPointSize(ourRand(2) > 1 ? 1.0 : 2.0);
+
+ // Spin your Universe, round and round....
+ glRotatef(ourRand(100),0.0f,1.0f,0.0f);
+ glRotatef(ourRand(100),1.0f,0.0f,0.0f);
+
+ glBegin(GL_POINTS);
+ glVertex3f(15.0, 0.0f, 0.0f);
+ glEnd();
+ }
+
+ glPopMatrix();
+ glEndList();
+}
+
+
+// ------
+// Function builds a simple alpha channel texture of a dot,
+// and then creates mipmaps. This could instead load textures from
+// graphics files from disk, or render textures based on external
+// input.
+
+void ourBuildTextures(
+ void
+)
+{
+ GLenum gluerr;
+ GLubyte tex[128][128];
+ int x,y,t;
+ int hole_size = 3300; // ~ == 57.45 ^ 2.
+
+ // Generate a texture index, then bind it for future operations.
+ glGenTextures(1,&Texture_ID);
+ glBindTexture(GL_TEXTURE_2D,Texture_ID);
+
+ // Iterate across the texture array.
+
+ for(y=0;y<128;y++) {
+ for(x=0;x<128;x++) {
+
+ // Make a round dot in the texture's alpha-channel.
+
+ // Calculate distance to center (squared).
+ t = (x-64)*(x-64) + (y-64)*(y-64) ;
+
+ if ( t < hole_size) // Don't take square root; compare squared.
+ tex[x][y]= 240 - (240 * t) / hole_size + ourRand(15);
+ else
+ tex[x][y]=0; // Outside of the dot, it's transparent.
+
+ }
+ }
+
+ // The GLU library helps us build MipMaps for our texture.
+
+ if ((gluerr=gluBuild2DMipmaps(GL_TEXTURE_2D, 1, 128, 128, GL_ALPHA,
+ GL_UNSIGNED_BYTE, (void *)tex))) {
+
+ fprintf(stderr,"GLULib%s\n",gluErrorString(gluerr));
+ exit(-1);
+ }
+
+ // Some pretty standard settings for wrapping and filtering.
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
+
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+
+ // We start with GL_MODULATE mode.
+ glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
+}
+
+
+// ------
+// Callback routine executed whenever our window is resized. Lets us
+// request the newly appropriate perspective projection matrix for
+// our needs. Try removing the gluPerspective() call to see what happens.
+
+void cbResizeScene(
+ int Width,
+ int Height
+)
+{
+ // Let's not core dump, no matter what.
+ if (Height == 0)
+ Height = 1;
+
+ glViewport(0, 0, Width, Height);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.05f,100.0f);
+
+ glMatrixMode(GL_MODELVIEW);
+
+ Window_Width = Width;
+ Window_Height = Height;
+}
+
+
+// ------
+// Fires the Particle Gun, or, sets up the passed Particle to be
+// placed at the Particle Gun location, and fired in a direction
+// specified in Gun_Va, with 'a' being "X", "Y", or "Z".
+//
+// The particles are normally fired with a randomness of Gun_R.
+// Occationally (by default 10%) a larger randomness of Gun_OffR is
+// added. Also, 10% of the time, the particles are fired backwards.
+// This is used to introduce a bit of non-uniformity. Gun_R and
+// Gun_OffR can be controled with the '3' and '6', and '/' and '*'
+// keys, respectively. If set to zero, effects are removed.
+
+static void ourFireParticleGun(
+ Particle *p
+)
+{
+ float r;
+ int c;
+ int Dir = 1;
+
+ if (!p->Running) {
+ p->Running=1;
+ Parts_Running++;
+ }
+
+ if (p == Parts) { // Root part.
+ ourVectInit(p->Color,1,1,0); // Bright Yellow
+ ourVectInit(p->Pos,5.0,0,0); // Location
+ ourVectInit(p->Vel,0,Gun_VY,-Gun_VX*0.95); // Velocity
+ return;
+ }
+
+ // Regular particle.
+
+ ourVectInit(p->Pos,Gun_PX,Gun_PY,Gun_PZ);
+ r = Gun_R;
+
+// This creates a few a very unpredicatable trajectories. It actually
+// works out to be much less than a full 10 percent, as many are eatten
+// or escape within a very short period of time. Only a few enter a
+// stable orbit.
+
+ if (ourRand(1) > Gun_Off) r += Gun_OffR;
+
+ if (ourRand(1) > Gun_Back) Dir = -1;
+
+ ourVectInit(p->Vel,
+ (Gun_VX + r-ourRand(2*r)) * Dir,
+ (Gun_VY + r-ourRand(2*r)) * Dir,
+ (Gun_VZ + r-ourRand(2*r)) * Dir);
+
+ c = (int)(ourRand(5) + 1.5); // Range of 1 to 6.
+
+ // The last set of numbers bias the colors to blue. Red is nice too.
+
+ ourVectInit(p->Color,
+ (c & 0x01 ? 0.9 : 1.0) * 0.7,
+ (c & 0x02 ? 0.9 : 1.0) * 0.7,
+ (c & 0x04 ? 0.9 : 1.0) * 1.0
+ );
+}
+
+
+// ------
+// Calculates the next position based on the current velocity vector,
+// then calculates the new velocity vector based on the particle's
+// proximity to the black hole.
+//
+// We do the motion calculation before updating the velocity vector
+// (and calculating the acceleration-because-of-gravity vector) so
+// that our Vector Display option will be correct. If we didn't do
+// this, the gravity vector would not point towards (0,0,0) when
+// we drew it, outside of this function.
+
+static void ourMoveParticle(
+ Particle *p
+)
+{
+ float dp2, dsx, dsy, dsz, G, d;
+
+ // Used to randomly kill and re-create particles.
+ if (p != Parts)
+ if (ourRand(1) > 0.9998) {
+ ourFireParticleGun(p);
+ return;
+ }
+
+ // We're actually going to move this particle...
+
+ // We first move it based on the LAST iteration's
+ // calculation of the Velocity...
+ p->Pos[0] += p->Vel[0];
+ p->Pos[1] += p->Vel[1];
+ p->Pos[2] += p->Vel[2];
+
+ // ...and then proceed to calculate the force of gravity at the new
+ // position, and update our velocity vector.
+
+ dsx = p->Pos[0] * p->Pos[0];
+ dsy = p->Pos[1] * p->Pos[1];
+ dsz = p->Pos[2] * p->Pos[2];
+
+ // Calculate the square of the distance.
+ dp2 = dsx + dsy + dsz;
+
+ if (dp2) {
+ // May wish to scale dp2 (change 1.0); effects gravity gradiant.
+ G = Grav / (dp2 * 1.0);
+ d = sqrt(dp2);
+ }
+
+ // If the force of gravity is too strong, our algorithim breaks
+ // down and conservation of energy isn't maintained. We consider
+ // this the event horizon, and recycle the particle.
+ if (G > EVENT_HORIZON_GRAV) {
+ ourFireParticleGun(p);
+ return;
+ }
+
+ if (dp2 > ESCAPE_HORIZON_SQR) {
+ // Particle escaped; lucky it.
+ ourFireParticleGun(p);
+ return;
+ }
+
+ // OK, this particle is staying in the system. Calculate the
+ // vectors....
+
+ // We store the components of the force of gravity for
+ // our Vectors display. Note the negative magnitude; the vector
+ // must point _from_ our particle _towards_ (0,0,0).
+ p->Grav[0] = - G * p->Pos[0] / d;
+ p->Grav[1] = - G * p->Pos[1] / d;
+ p->Grav[2] = - G * p->Pos[2] / d;
+
+ // Simply add the gravity vector to the current velocity vector.
+ p->Vel[0] += p->Grav[0];
+ p->Vel[1] += p->Grav[1];
+ p->Vel[2] += p->Grav[2];
+
+ if (p != Parts) {
+ // This handles orbit decay; not correctly, but well enough.
+ // (Decay should be a ratio to the vector length, applied to each
+ // vector component here, rather than each component being effected
+ // based on its individual size. The effect is to circlurize the
+ // orbit, which we want anyway.)
+
+ p->Vel[0] -= p->Vel[0] / Decay_Factor;
+ p->Vel[1] -= p->Vel[1] / Decay_Factor;
+ p->Vel[2] -= p->Vel[2] / Decay_Factor;
+ }
+}
+
+
+// ------
+// Angle to Radian conversion.
+
+float ourA2R(
+ float Angle
+)
+{
+ return Angle * M_PI/180;
+}
+
+
+// ------
+// Calculates the observer's XYZ position from their Distance from
+// the origin, the angle and the height.
+
+static void ourCalcObs(void)
+{
+ Obs[0]=Obs_Dist * sin(ourA2R(Obs_Angle));
+ Obs[1]=Obs_Height;
+ Obs[2]=Obs_Dist * cos(ourA2R(Obs_Angle));
+}
+
+
+// ------
+// Draws the X, Y, and Z axis lines.
+
+void ourRenderAxis(
+ void
+)
+{
+ glBegin(GL_LINES);
+
+ glColor4f(0.5,0.5,0.0,1.0); // Mid-level yellow.
+
+ // Three primary axis lines.
+ glVertex3f(100,0,0);
+ glVertex3f(-100,0,0);
+ glVertex3f(0,100,0);
+ glVertex3f(0,-100,0);
+ glVertex3f(0,0,100);
+ glVertex3f(0,0,-100);
+
+ glColor4f(0.25,0.25,0.0,1.0); // Low-level yellow.
+
+ // Two pairs of secondary lines for X and Z axis.
+ glVertex3f(100,1,0);
+ glVertex3f(-100,1,0);
+ glVertex3f(100,-1,0);
+ glVertex3f(-100,-1,0);
+
+ glVertex3f(0,1,100);
+ glVertex3f(0,1,-100);
+ glVertex3f(0,-1,100);
+ glVertex3f(0,-1,-100);
+
+ glColor4f(0.0,0.5,0.0,1.0); // Mid-level green.
+
+ // Lable the X axis.
+ glVertex3f(1.0,0.9,0);
+ glVertex3f(1.1,0.8,0);
+ glVertex3f(1.1,0.9,0);
+ glVertex3f(1.0,0.8,0);
+
+ // And the Z.
+ glVertex3f(0,0.9,1.0);
+ glVertex3f(0,0.9,1.1);
+ glVertex3f(0,0.9,1.1);
+ glVertex3f(0,0.8,1.0);
+ glVertex3f(0,0.8,1.0);
+ glVertex3f(0,0.8,1.1);
+
+ glEnd();
+}
+
+
+// ------
+// Draws the Gravity and Velocity Vectors for each active particle.
+
+void ourRenderVectors(
+ void
+)
+{
+ int i;
+
+ glBegin(GL_LINES);
+
+ for (i=0; i<Parts_Num; i++) {
+
+ if (!Parts[i].Running) continue;
+
+ // Draw the velocity vector as green.
+ glColor4f(0.0,1.0,0.0,Parts_Brightness + .5);
+
+ glVertex3f(
+ Parts[i].Pos[0],
+ Parts[i].Pos[1],
+ Parts[i].Pos[2]
+ );
+
+ glVertex3f(
+ Parts[i].Pos[0] + Parts[i].Vel[0] ,
+ Parts[i].Pos[1] + Parts[i].Vel[1] ,
+ Parts[i].Pos[2] + Parts[i].Vel[2]
+ );
+
+ // Draw the gravity vector as red.
+ glColor4f(1.0,0.0,0.0,Parts_Brightness + .5);
+
+ glVertex3f(
+ Parts[i].Pos[0],
+ Parts[i].Pos[1],
+ Parts[i].Pos[2]
+ );
+
+ glVertex3f(
+ Parts[i].Pos[0] + Parts[i].Grav[0] ,
+ Parts[i].Pos[1] + Parts[i].Grav[1] ,
+ Parts[i].Pos[2] + Parts[i].Grav[2]
+ );
+ }
+
+ glEnd();
+}
+
+
+// ------
+// Draws the heads-up-display.
+void ourRenderHeadsUp(
+ void
+)
+{
+ char buf[80];
+
+ glLoadIdentity();
+ // We need to change the projection matrix for the text rendering.
+ glMatrixMode(GL_PROJECTION);
+
+ // But we like our current view too; so we save it here.
+ glPushMatrix();
+
+ // Now we set up a new projection for the text.
+ glLoadIdentity();
+ glOrtho(0,Window_Width,0,Window_Height,-1.0,1.0);
+
+ // No need for textured text.
+ glDisable(GL_TEXTURE_2D);
+
+ // We don't want depth-testing either.
+ glDisable(GL_DEPTH_TEST);
+
+
+ // Draw various variables for the user.
+
+ glColor3f(1.0,1.0,0.0);
+
+ sprintf(buf,"Parts: %d / %d Bright:%.2f",
+ Parts_Running, Parts_Allocated, Parts_Brightness);
+ glRasterPos2i(10,48);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ sprintf(buf,"Rnd - Normal:%.3f Extreme:%.3f",
+ Gun_R, Gun_OffR);
+ glRasterPos2i(10,6);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ if (Gun_Control)
+ glColor3f(1.0,1.0,0.0);
+ else
+ glColor3f(0.5,1.0,0.0);
+
+ sprintf(buf,"GunP: (%.3f,%.3f,%.3f)", Gun_PX, Gun_PY, Gun_PZ);
+ glRasterPos2i(10,34);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ if (!Gun_Control)
+ glColor3f(1.0,1.0,0.0);
+ else
+ glColor3f(0.5,1.0,0.0);
+
+ sprintf(buf,"GunV: (%.3f,%.3f,%.3f)",
+ Gun_VX, Gun_VY, Gun_VZ);
+ glRasterPos2i(10,20);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ // Now we want to render the calulated FPS at the top.
+
+ // To ease, simply translate up. Note we're working in screen
+ // pixels in this projection.
+
+ glTranslatef(6.0f,Window_Height - 14,0.0f);
+
+ glColor4f(0.9,0.2,0.2,.95);
+ sprintf(buf,"FPS: %f F: %2d", FrameRate, FrameCount);
+ glRasterPos2i(6,0);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ // Lets also show the current position of the Root Particle
+ sprintf(buf,"RootP: ( %-.4f, %-.4f, %-.4f)",
+ Parts->Pos[0], Parts->Pos[1],Parts->Pos[2]);
+ glRasterPos2i(6,-16);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ // And the Root Particle's velocity.
+ sprintf(buf,"RootV: ( %-.4f, %-.4f, %-.4f)",
+ Parts->Vel[0], Parts->Vel[1],Parts->Vel[2]);
+ glRasterPos2i(6,-32);
+ ourPrintString(GLUT_BITMAP_HELVETICA_12,buf);
+
+ // Done with this special projection matrix. Throw it away.
+ glPopMatrix();
+}
+
+
+// ------
+// Routine which actually does the drawing
+
+static void cbRenderScene(void)
+{
+ Particle *p;
+ int i;
+
+ // For the first few objects, we want full depth-buffer testing.
+ glEnable(GL_DEPTH_TEST);
+ glDepthMask(GL_TRUE);
+
+ // Clear the screen.
+ glClearColor(0.00,0.00,0.00,1.0);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+
+ // Ensure we're working with the model matrix.
+ glMatrixMode(GL_MODELVIEW);
+
+ // Reset to 0,0,0; no rotation, no scaling.
+ glLoadIdentity();
+
+ // Are we on-orbit, or wandering around?
+ if (On_Orbit) {
+ gluLookAt(Parts->Pos[0],Parts->Pos[1],Parts->Pos[2],
+ 0.0,0.0,0.0,
+ 0.0,1.0,0.0);
+ } else {
+ gluLookAt(Obs[0],Obs[1],Obs[2],
+ 0.0,0.0,0.0,
+ 0.0,1.0,0.0);
+ }
+
+ // No texturing.
+ glDisable(GL_TEXTURE_2D);
+
+ // Black holes are BLACK!
+ glColor4f(0.0,0.0,0,1.0);
+ gluSphere(Black_Hole,0.02,8,8);
+
+ if (Draw_Stars)
+ glCallList(STAR_FIELD);
+
+ if (Draw_Vectors)
+ ourRenderVectors();
+
+ if (Draw_Axis)
+ ourRenderAxis();
+
+ // We don't want any of the particles to obscure any others, but
+ // we DO want the black hole to block any particles behind it.
+ // Note that GL_DEPTH_TEST is still enabled.
+ glDepthMask(GL_FALSE);
+
+ // Enable the dot texture. "Oh no! Not THE DOT!"
+ if (Texture_On)
+ glEnable(GL_TEXTURE_2D);
+
+ // Iterate through the array of particles, drawing all that are
+ // active. For those that aren't active, 0.03% of the time, we
+ // introduce them into the system.
+
+ for(i=0; i<Parts_Num; i++) {
+ p = &Parts[i];
+
+ if (!p->Running) {
+ if (Move_Enable && ourRand(1) > 0.9997)
+ ourFireParticleGun(p);
+ } else {
+ // Set the part's color.
+ glColor4f(p->Color[0],p->Color[1],p->Color[2],
+ Parts_Brightness);
+
+ // Draw two intersecting quads, along XY and ZY axis.
+ glBegin(GL_QUADS);
+
+ glTexCoord2f(0.0,0.0);
+ glVertex3f(p->Pos[0]-.00,p->Pos[1]-.10,p->Pos[2]-.10);
+ glTexCoord2f(1.0,0.0);
+ glVertex3f(p->Pos[0]-.00,p->Pos[1]+.10,p->Pos[2]-.10);
+ glTexCoord2f(1.0,1.0);
+ glVertex3f(p->Pos[0]-.00,p->Pos[1]+.10,p->Pos[2]+.10);
+ glTexCoord2f(0.0,1.0);
+ glVertex3f(p->Pos[0]-.00,p->Pos[1]-.10,p->Pos[2]+.10);
+
+ glTexCoord2f(0.0,0.0);
+ glVertex3f(p->Pos[0]-.10,p->Pos[1]-.10,p->Pos[2]-.00);
+ glTexCoord2f(1.0,0.0);
+ glVertex3f(p->Pos[0]-.10,p->Pos[1]+.10,p->Pos[2]-.00);
+ glTexCoord2f(1.0,1.0);
+ glVertex3f(p->Pos[0]+.10,p->Pos[1]+.10,p->Pos[2]+.00);
+ glTexCoord2f(0.0,1.0);
+ glVertex3f(p->Pos[0]+.10,p->Pos[1]-.10,p->Pos[2]+.00);
+
+ glEnd();
+
+ if (Move_Enable)
+ ourMoveParticle(p);
+ }
+ }
+
+
+ if (Heads_Up)
+ ourRenderHeadsUp();
+
+ // All done drawing. Let's show it.
+ glutSwapBuffers();
+
+ // This handles our single-step function.
+ if (Move_Step)
+ Move_Step = Move_Enable = 0;
+
+ // Collect the FPS statistics.
+ ourDoFPS();
+}
+
+
+// ------
+// Callback function called when a normal key is pressed.
+
+void cbKeyPressed(
+ unsigned char key,
+ int x, int y
+)
+{
+ int t;
+
+ switch (key) {
+ case 27:
+ case 'q': case 'Q':
+ exit(0);
+ break;
+
+ // Toggle drawing.
+ case 'a': case 'A':
+ Draw_Axis = !Draw_Axis;
+ break;
+ case 'v': case 'V':
+ Draw_Vectors = !Draw_Vectors;
+ break;
+ case 's': case 'S':
+ Draw_Stars = !Draw_Stars;
+ break;
+
+ // Adjust particle brightness.
+ case 'b':
+ Parts_Brightness+=0.01;
+ break;
+ case 'B':
+ Parts_Brightness-=0.01;
+ break;
+
+ // Toggle being on-orbit.
+ case 'o': case 'O':
+ On_Orbit = ! On_Orbit;
+ break;
+
+ // Toggle Texture.
+ case 't': case 'T':
+ Texture_On = !Texture_On;
+ break;
+
+ // Impart an impulse on the Root particle.
+ case 'x':
+ Parts->Vel[0]+=.0001;
+ break;
+ case 'X':
+ Parts->Vel[0]-=.0001;
+ break;
+ case 'c': case 'y':
+ Parts->Vel[1]+=.0001;
+ break;
+ case 'C': case 'Y':
+ Parts->Vel[1]-=.0001;
+ break;
+ case 'z':
+ Parts->Vel[2]+=.0001;
+ break;
+ case 'Z':
+ Parts->Vel[2]-=.0001;
+ break;
+
+ case 'r': case 'R':
+ ourFireParticleGun(Parts);
+
+ // Single-step through motion calculations.
+ case 'm':
+ Move_Step = 1;
+ Move_Enable = 1;
+ break;
+
+ // Normal motion.
+ case 'M':
+ Move_Enable= !Move_Enable;
+ break;
+
+ // Heads up display.
+ case 'h': case 'H':
+ Heads_Up = !Heads_Up;
+ break;
+
+ // Inject one (or all) free particle(s).
+ case 'i': case 'I':
+ for (t=Parts_LastUnused; t<Parts_Num; t++) {
+ if (!Parts[t].Running) {
+ ourFireParticleGun(&Parts[t]);
+ Parts_LastUnused = t;
+ if (key == 'i') break;
+ }
+ }
+ break;
+
+ // Toggle gun control between ejection velocity and position.
+ case '.':
+ Gun_Control= !Gun_Control;
+ break;
+
+ // Control Particle Gun velocity vector.
+ case '7':
+ if (Gun_Control) Gun_VZ-=0.001; else Gun_PZ-=0.01;
+ break;
+ case '8':
+ if (Gun_Control) Gun_VZ+=0.001; else Gun_PZ+=0.01;
+ break;
+
+ case '4':
+ if (Gun_Control) Gun_VY-=0.001; else Gun_PY-=0.01;
+ break;
+ case '5':
+ if (Gun_Control) Gun_VY+=0.001; else Gun_PY+=0.01;
+ break;
+
+ case '1':
+ if (Gun_Control) Gun_VX-=0.001; else Gun_PX-=0.01;
+ break;
+ case '2':
+ if (Gun_Control) Gun_VX+=0.001; else Gun_PX+=0.01;
+ break;
+
+ // Range of randomness which is added to each initial velocity vector.
+ case '3':
+ Gun_R-=0.001;
+ break;
+ case '6':
+ Gun_R+=0.001;
+ break;
+
+ // Controls the large random shots which occur rarely. When set to
+ // 0, the system becomes highly controlled.
+ case '/':
+ Gun_OffR -= .001;
+ break;
+ case '*':
+ Gun_OffR += .001;
+ break;
+
+ // Adds or removes particles to/from the system.
+ case '-':
+ if (Parts_Num > 100) {
+ Parts_Num -= 100;
+ ourAllocParticles(Parts_Num);
+ }
+ break;
+
+ case '+':
+ Parts_Num += 100;
+ ourAllocParticles(Parts_Num);
+ break;
+
+ default:
+ printf("No action for %d\n", key);
+
+ }
+}
+
+
+// ------
+// Callback Function called when a special key is pressed.
+
+static void cbSpecialKeyPressed(int key, int x, int y)
+{
+ switch (key) {
+ case GLUT_KEY_PAGE_UP:
+ Obs_Dist -= 0.05f;
+ break;
+
+ case GLUT_KEY_PAGE_DOWN:
+ Obs_Dist += 0.05f;
+ break;
+
+ case GLUT_KEY_LEFT:
+ Obs_Angle-=2.0;
+ break;
+
+ case GLUT_KEY_RIGHT:
+ Obs_Angle+=2.0;
+ break;
+
+ case GLUT_KEY_DOWN:
+ Obs_Height-=0.05;
+ break;
+
+ case GLUT_KEY_UP:
+ Obs_Height+=0.06;
+ break;
+ }
+
+ // We don't know anything changed, but it never hurts.
+ ourCalcObs();
+}
+
+
+// ------
+// Does everything needed before losing control to the main
+// OpenGL event loop
+
+void ourInit(
+ int Width,
+ int Height
+)
+{
+ ourBuildTextures();
+ ourBuildStarfield(500);
+
+ glEnable(GL_BLEND);
+ glDisable(GL_ALPHA_TEST);
+
+ // Enable flat shading -- no need for smooth.
+ glShadeModel(GL_FLAT);
+
+ // Blending mode used for fire, lit gas, etc.
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE);
+
+ // Calculate the non-on-orbit observer's position.
+ ourCalcObs();
+
+ // Load up the correct perspective matrix; using a callback directly.
+ cbResizeScene(Width, Height);
+
+ if (!(Black_Hole = gluNewQuadric()))
+ exit;
+
+ // Allocate our first block of particles.
+ ourAllocParticles(Parts_Num);
+
+ // Fire off the first (Root) Particle.
+ ourFireParticleGun(Parts);
+
+}
+
+
+// ------
+// The main() function. Inits OpenGL. Calls our own init function,
+// then passes control onto OpenGL.
+
+int main(int argc,char **argv)
+{
+ glutInit(&argc,argv);
+
+ // To see OpenGL drawing, take out the GLUT_DOUBLE request.
+ glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
+ glutInitWindowSize(Window_Width,Window_Height);
+
+ // Open a window
+
+ if (!(Window_ID=glutCreateWindow( PROGRAM_TITLE ))) {
+ fprintf(stderr,"Error opening a window.\n");
+ exit(-1);
+ }
+
+ // Register the callback function to do the drawing.
+ glutDisplayFunc(&cbRenderScene);
+
+ // If there's nothing to do, draw.
+ glutIdleFunc(&cbRenderScene);
+
+ // It's a good idea to know when our window's resized.
+ glutReshapeFunc(&cbResizeScene);
+
+ // And let's get some keyboard input.
+ glutKeyboardFunc(&cbKeyPressed);
+ glutSpecialFunc(&cbSpecialKeyPressed);
+
+ // OK, OpenGL's ready to go. Let's call our own init function.
+ ourInit(Window_Width, Window_Height);
+
+ // Print out a bit of help dialog.
+ printf("\n" PROGRAM_TITLE "\n\n\
+Use arrow keys to rotate around or move along Y axis.\n\
+Page up/down will move observer towards/away from the Y axis.\n\n\
+'O' toggles observer onto non-decaying orbit of yellow root particle.\n\
+'H' toggles heads-up-display of various status variables.\n\n\
+'x'/'X', 'c'/'C', 'z'/'Z' thrusts root particle along X, Y, Z axis\n\
+in positive/negative directions, respectively; 'R' resets.\n\n\
+Numerical Keypad controls Particle Gun parameters. '.' key switches\n\
+between effecting Gun Velocity Vector and Position.\n\n\
+'+' and '-' add to or remove particles from the system.\n\n\
+Use first letter of shown display mode settings to alter.\n\n\
+Q or [Esc] to quit; OpenGL window must have focus for input.\n");
+
+ // Pass off control to OpenGL.
+ // Above functions are called as appropriate.
+ glutMainLoop();
+
+ // Free our allocated particle array.
+ ourAllocParticles(0);
+
+ return 1;
+}
+