// Written in the D Programming Language
|
/**
|
* The 25th lesson in the <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=25">NeHe tutorial series</a>.
|
* Originally written by Piotr Cieslak.
|
*
|
* Authors: Piotr Cieslak
|
* Jeff Molofee
|
* Olli Aalto
|
*/
|
module lesson25;
|
|
import derelict.opengl.gl;
|
import derelict.opengl.glu;
|
import derelict.sdl.sdl;
|
|
import tango.stdc.stringz;
|
import tango.stdc.stdio;
|
import tango.text.Util;
|
import Int = tango.text.convert.Integer;
|
import tango.math.Random;
|
|
/// The window title
|
const char[] WINDOW_TITLE = "Piotr Cieslak & NeHe's Morphing Points Tutorial (D version)";
|
|
/// The main loop flag
|
bool running;
|
|
/// Random object
|
Random rand;
|
|
/// X, Y & Z Rotation
|
GLfloat xrot = 0.0f, yrot = 0.0f, zrot = 0.0f;
|
/// X, Y & Z Spin Speed
|
GLfloat xspeed = 0.0f, yspeed = 0.0f, zspeed = 0.0f;
|
/// X, Y & Z Position
|
GLfloat cx = 0.0f, cy = 0.0f, cz = -15.0f;
|
|
/// Used To Make Sure Same Morph Key Is Not Pressed
|
int key = 1;
|
/// Step Counter And Maximum Number Of Steps
|
int step, steps = 200;
|
/// Default morph To False (Not Morphing)
|
bool morph;
|
|
/// Structure For 3D Points
|
struct Vertex
|
{
|
// X, Y & Z Points
|
float x, y, z;
|
|
static Vertex opCall()
|
{
|
Vertex v;
|
v.x = 0.0f;
|
v.y = 0.0f;
|
v.z = 0.0f;
|
return v;
|
}
|
}
|
|
/// Will Eventually Hold The Maximum Number Of Vertices
|
int maxver;
|
/// Our 4 Morphable Objects (morph 1, 2, 3 & 4)
|
Vertex[] morph1, morph2, morph3, morph4;
|
/// Helper Object, Source Object, Destination Object
|
Vertex[] helper, src, dest;
|
|
/// Imported data for the sphere
|
char[] sphereData = import("Sphere.txt");
|
/// Imported data for the torus
|
char[] torusData = import("Torus.txt");
|
/// Imported data for the tube
|
char[] tubeData = import("Tube.txt");
|
|
/**
|
* Module constructor. Here we load the GL, GLU and SDL shared libraries,
|
* and the initialize SDL.
|
*/
|
static this()
|
{
|
DerelictGL.load();
|
DerelictGLU.load();
|
DerelictSDL.load();
|
|
if(SDL_Init(SDL_INIT_VIDEO) < 0)
|
{
|
throw new Exception("Failed to initialize SDL: " ~ getSDLError());
|
}
|
|
if (SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,
|
SDL_DEFAULT_REPEAT_INTERVAL))
|
{
|
throw new Exception("Failed to set key repeat: " ~ getSDLError());
|
}
|
|
// Initialize the random object
|
rand = new Random();
|
}
|
|
/**
|
* Module destructor. SDL_Quit must be called somewhere, and as we initialized
|
* it in the module constructor so the module destructor should be a suitable
|
* place.
|
*/
|
static ~this()
|
{
|
SDL_Quit();
|
}
|
|
/**
|
* The main function. This is where the fun begins. The first order of business
|
* is the check the command line arguments if the user wanted to start in
|
* fullscreen mode. Then the window is created and OpenGL is initialized with
|
* basic settings. Finally the the function starts the main loop which will live
|
* for the duration of the application.
|
*
|
* Params:
|
* args = the command line arguments
|
*/
|
void main(char[][] args)
|
{
|
bool fullScreen = false;
|
if(args.length > 1)
|
{
|
fullScreen = args[1] == "-fullscreen";
|
}
|
|
createGLWindow(WINDOW_TITLE, 640, 480, 16, fullScreen);
|
initGL();
|
|
running = true;
|
while(running)
|
{
|
processEvents();
|
|
drawGLScene();
|
|
SDL_GL_SwapBuffers();
|
SDL_Delay(10);
|
}
|
}
|
|
/**
|
* Process all the pending events.
|
*/
|
void processEvents()
|
{
|
SDL_Event event;
|
while(SDL_PollEvent(&event))
|
{
|
switch(event.type)
|
{
|
case SDL_KEYUP:
|
keyReleased(event.key.keysym.sym);
|
break;
|
case SDL_KEYDOWN:
|
keyPressed(event.key.keysym.sym);
|
break;
|
case SDL_QUIT:
|
running = false;
|
break;
|
default:
|
break;
|
}
|
}
|
}
|
|
/**
|
* Process a key released event.
|
*/
|
void keyReleased(int key)
|
{
|
switch(key)
|
{
|
case SDLK_1:
|
// Is 1 Pressed, key Not Equal To 1 And Morph False?
|
if((key != 1) && !morph)
|
{
|
key = 1; // Sets key To 1 (To Prevent Pressing 1 2x In A Row)
|
morph = true; // Set morph To True (Starts Morphing Process)
|
dest = morph1.dup; // Destination Object To Morph To Becomes morph1
|
}
|
break;
|
case SDLK_2:
|
// Is 2 Pressed, key Not Equal To 2 And Morph False?
|
if((key != 2) && !morph)
|
{
|
key = 2; // Sets key To 2 (To Prevent Pressing 2 2x In A Row)
|
morph = true; // Set morph To True (Starts Morphing Process)
|
dest = morph2.dup; // Destination Object To Morph To Becomes morph2
|
}
|
break;
|
case SDLK_3:
|
// Is 3 Pressed, key Not Equal To 3 And Morph False?
|
if((key != 3) && !morph)
|
{
|
key = 3; // Sets key To 3 (To Prevent Pressing 3 2x In A Row)
|
morph = true; // Set morph To True (Starts Morphing Process)
|
dest = morph3.dup; // Destination Object To Morph To Becomes morph3
|
}
|
break;
|
case SDLK_4:
|
// Is 4 Pressed, key Not Equal To 4 And Morph False?
|
if((key != 4) && !morph)
|
{
|
key = 4; // Sets key To 4 (To Prevent Pressing 4 2x In A Row)
|
morph = true; // Set morph To True (Starts Morphing Process)
|
dest = morph4.dup; // Destination Object To Morph To Becomes morph4
|
}
|
break;
|
case SDLK_ESCAPE:
|
running = false;
|
break;
|
default:
|
break;
|
}
|
}
|
|
/**
|
* Process a key pressed event.
|
*/
|
void keyPressed(int key)
|
{
|
switch(key)
|
{
|
case SDLK_PAGEUP:
|
// Is Page Up Being Pressed?
|
zspeed += 0.01f; // Increase zspeed
|
break;
|
|
case SDLK_PAGEDOWN:
|
// Is Page Down Being Pressed?
|
zspeed -= 0.01f; // Decrease zspeed
|
break;
|
|
case SDLK_DOWN:
|
// Is Down Being Pressed?
|
xspeed += 0.01f; // Increase xspeed
|
break;
|
|
case SDLK_UP:
|
// Is Up Being Pressed?
|
xspeed -= 0.01f; // Decrease xspeed
|
break;
|
|
case SDLK_RIGHT:
|
// Is Right Being Pressed?
|
yspeed += 0.01f; // Increase yspeed
|
break;
|
|
case SDLK_LEFT:
|
// Is Left Being Pressed?
|
yspeed -= 0.01f; // Decrease yspeed
|
break;
|
case SDLK_q:
|
// Is Q Key Being Pressed?
|
cz -= 0.01f; // Move Object Away From Viewer
|
break;
|
|
case SDLK_z:
|
// Is Z Key Being Pressed?
|
cz += 0.01f; // Move Object Towards Viewer
|
break;
|
|
case SDLK_w:
|
// Is W Key Being Pressed?
|
cy += 0.01f; // Move Object Up
|
break;
|
|
case SDLK_s:
|
// Is S Key Being Pressed?
|
cy -= 0.01f; // Move Object Down
|
break;
|
|
case SDLK_d:
|
// Is D Key Being Pressed?
|
cx += 0.01f; // Move Object Right
|
break;
|
|
case SDLK_a:
|
// Is A Key Being Pressed?
|
cx -= 0.01f; // Move Object Left
|
break;
|
|
default:
|
break;
|
}
|
}
|
|
/**
|
* Calculates Movement Of Points During Morphing.
|
* Params:
|
* i = the index of the current point
|
*/
|
Vertex calculate(int i)
|
{
|
Vertex v; // Temporary Vertex Called v
|
v.x = (src[i].x - dest[i].x) / steps; // v.x Value Equals Source x - Destination x Divided By Steps
|
v.y = (src[i].y - dest[i].y) / steps; // v.y Value Equals Source y - Destination y Divided By Steps
|
v.z = (src[i].z - dest[i].z) / steps; // v.z Value Equals Source z - Destination z Divided By Steps
|
return v;
|
}
|
|
/**
|
* Resize and initialize the OpenGL window.
|
*/
|
void resizeGLScene(GLsizei width, GLsizei height)
|
{
|
if(height == 0)
|
{
|
height = 1;
|
}
|
// Reset The Current Viewport
|
glViewport(0, 0, width, height);
|
|
// Select The Projection Matrix
|
glMatrixMode(GL_PROJECTION);
|
|
// Reset The Projection Matrix
|
glLoadIdentity();
|
|
// Calculate The Aspect Ratio Of The Window
|
gluPerspective(45.0f, cast(GLfloat) width / cast(GLfloat) height, 0.1f, 100.0f);
|
|
// Select The Modelview Matrix
|
glMatrixMode(GL_MODELVIEW);
|
|
// Reset The Modelview Matrix
|
glLoadIdentity();
|
}
|
|
/**
|
* Initialize OpenGL.
|
*/
|
void initGL()
|
{
|
// Set The Blending Function For Translucency
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
// Enables Smooth Shading
|
glShadeModel(GL_SMOOTH);
|
// Black Background
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
// Depth Buffer Setup
|
glClearDepth(1.0f);
|
// Enables Depth Testing
|
glEnable(GL_DEPTH_TEST);
|
// The Type Of Depth Test To Do
|
glDepthFunc(GL_LESS);
|
// Really Nice Perspective Calculations
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
|
|
// Create the objects
|
createObjects();
|
}
|
|
/**
|
* Creates all the objects used.
|
*/
|
void createObjects()
|
{
|
loadObject(sphereData, morph1);
|
loadObject(torusData, morph2);
|
loadObject(tubeData, morph3);
|
|
// Set the size for morph4
|
morph4 = new Vertex[486];
|
// Loop Through morph4
|
foreach (inout v; morph4)
|
{
|
// v.x Point Becomes A Random Float Value From -7 to 7
|
v.x = (cast(float) (rand.next() % 14000) * 0.001) - 7;
|
// v.y Point Becomes A Random Float Value From -7 to 7
|
v.y = (cast(float) (rand.next() % 14000) * 0.001) - 7;
|
// v.z Point Becomes A Random Float Value From -7 to 7
|
v.z = (cast(float) (rand.next() % 14000) * 0.001) - 7;
|
}
|
|
// Make the helper a duplicate of the first morph object
|
helper = morph1.dup;
|
|
// Source & Destination Are Set To Equal First Object (morph1)
|
src = morph1.dup;
|
dest = morph1.dup;
|
}
|
|
/**
|
* Load a single object.
|
*
|
* Params:
|
* data = the object's data
|
* obj = the object to be loaded
|
*/
|
void loadObject(char[] data, inout Vertex[] obj)
|
{
|
// Split the data into lines
|
char[][] lines = splitLines!(char)(data);
|
// Split the first line so we can get the number of vertices
|
char[][] numVertices = split!(char)(lines[0], " ");
|
// Reserve memory for the object
|
obj = new Vertex[Int.parse(numVertices[1])];
|
|
// Loop through the lines, skip the first line
|
for(int i = 1; i < lines.length; i++)
|
{
|
// Read the values from the line
|
sscanf(lines[i].ptr, "%f%f%f", &obj[i - 1].x, &obj[i - 1].y, &obj[i - 1].z);
|
}
|
|
// Check if the maxver is smaller the the current object's length
|
if(maxver < obj.length)
|
{
|
// Set the maxver to current object's length
|
maxver = obj.length;
|
}
|
}
|
|
/**
|
* The drawing function. Now we only clear the color and depht buffers, so that
|
* the window stays black.
|
*/
|
void drawGLScene()
|
{
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
|
glLoadIdentity(); // Reset The View
|
|
glTranslatef(cx, cy, cz); // Translate The The Current Position To Start Drawing
|
glRotatef(xrot, 1, 0, 0); // Rotate On The X Axis By xrot
|
glRotatef(yrot, 0, 1, 0); // Rotate On The Y Axis By yrot
|
glRotatef(zrot, 0, 0, 1); // Rotate On The Z Axis By zrot
|
|
// Increase xrot,yrot & zrot by xspeed, yspeed & zspeed
|
xrot += xspeed;
|
yrot += yspeed;
|
zrot += zspeed;
|
|
glBegin(GL_POINTS); // Begin Drawing Points
|
// Loop Through All The Verts Of helper (All Objects Have
|
// The Same Amount Of Verts For Simplicity, Could Use maxver Also)
|
foreach (i, inout v ; helper)
|
{
|
// Holds Returned Calculated Values For One Vertex
|
Vertex q = morph ? calculate(i) : Vertex();
|
v.x -= q.x; // Subtract q.x Units From helper.points[i].x (Move On X Axis)
|
v.y -= q.y; // Subtract q.y Units From helper.points[i].y (Move On Y Axis)
|
v.z -= q.z; // Subtract q.z Units From helper.points[i].z (Move On Z Axis)
|
GLfloat tx = v.x; // Make Temp X Variable Equal To Helper's X Variable
|
GLfloat ty = v.y; // Make Temp Y Variable Equal To Helper's Y Variable
|
GLfloat tz = v.z; // Make Temp Z Variable Equal To Helper's Z Variable
|
|
glColor3f(0.0f, 1.0f, 1.0f); // Set Color To A Bright Shade Of Off Blue
|
glVertex3f(tx, ty, tz); // Draw A Point At The Current Temp Values (Vertex)
|
|
// Calculate Two Positions Ahead
|
tx -= 2 * q.x;
|
ty -= 2 * q.y;
|
tz -= 2 * q.z;
|
glColor3f(0.0f, 0.5f, 1.0f); // Darken Color A Bit
|
glVertex3f(tx, ty, tz); // Draw A Second Point At The Newly Calculate Position
|
|
// Calculate Two More Positions Ahead
|
tx -= 2 * q.x;
|
ty -= 2 * q.y;
|
tz -= 2 * q.z;
|
glColor3f(0.0f, 0.0f, 1.0f); // Set Color To A Very Dark Blue
|
glVertex3f(tx, ty, tz); // Draw A Third Point At The Second New Position
|
// This Creates A Ghostly Tail As Points Move
|
}
|
glEnd(); // Done Drawing Points
|
|
// If We're Morphing And We Haven't Gone Through All 200 Steps Increase Our Step Counter
|
// Otherwise Set Morphing To False, Make Source=Destination And Set The Step Counter Back To Zero.
|
if(morph && step <= steps)
|
{
|
step++;
|
}
|
else
|
{
|
morph = false;
|
src = dest.dup;
|
step = 0;
|
}
|
}
|
|
/**
|
* Initializes and opens the SDL window.
|
*/
|
void createGLWindow(char[] title, int width, int height, int bits, bool fullScreen)
|
{
|
// Set the OpenGL attributes
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
// Set the window title
|
SDL_WM_SetCaption(toStringz(title), null);
|
|
// Note the SDL_DOUBLEBUF flag is not required to enable double
|
// buffering when setting an OpenGL video mode.
|
// Double buffering is enabled or disabled using the
|
// SDL_GL_DOUBLEBUFFER attribute. (See above.)
|
int mode = SDL_OPENGL;
|
if(fullScreen)
|
{
|
mode |= SDL_FULLSCREEN;
|
}
|
// Now open a SDL OpenGL window with the given parameters
|
if(SDL_SetVideoMode(width, height, bits, mode) is null)
|
{
|
throw new Exception("Failed to open OpenGL window: " ~ getSDLError());
|
}
|
|
resizeGLScene(width, height);
|
}
|
|
/**
|
* Get the SDL error as a D string.
|
*
|
* Returns: A D string containing the current SDL error.
|
*/
|
char[] getSDLError()
|
{
|
return fromStringz(SDL_GetError());
|
}
|