// Written in the D Programming Language
|
/**
|
* The 24th lesson in the <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=24">NeHe tutorial series</a>.
|
* Originally written by Jeff Molofee.
|
*
|
* Authors: Jeff Molofee
|
* Olli Aalto
|
*/
|
module lesson24;
|
|
import derelict.opengl.gl;
|
import derelict.opengl.glu;
|
import derelict.sdl.sdl;
|
|
import tango.stdc.stringz;
|
import tango.text.Util;
|
import Int = tango.text.convert.Integer;
|
|
/// The window title
|
const char[] WINDOW_TITLE = "NeHe's Token, Extensions, Scissoring & TGA Loading Tutorial (D version)";
|
|
/// The main loop flag
|
bool running;
|
/// Used For Scrolling The Screen
|
int scroll;
|
/// Keeps Track Of The Number Of Extensions Supported
|
int maxtokens;
|
/// Scissor Width
|
int swidth;
|
/// Scissor Height
|
int sheight;
|
/// Used to control the Scrolling action
|
int scroller;
|
/// All the available extensions
|
char[][] extensions;
|
/// Your display cards name
|
char[] renderer;
|
/// Your display cards vendor
|
char[] vendor;
|
/// The version of the driver used
|
char[] driverVersion;
|
|
const RENDERER_TEXT = "Renderer";
|
const VENDOR_TEXT = "Vendor";
|
const VERSION_TEXT = "Version";
|
const NEHE_PRODUCTIONS_TEXT = "NeHe Productions";
|
|
/// Storage for font texture
|
GLuint texture;
|
/// Base Display List For The Font
|
GLuint base;
|
|
/**
|
* 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());
|
}
|
}
|
|
/**
|
* 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()
|
{
|
// Clean up our font list
|
glDeleteLists(base, 256);
|
|
// Clean up our texture
|
glDeleteTextures(1, &texture);
|
|
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();
|
|
// Get all the available extensions
|
char[] extensionString = fromStringz(glGetString(GL_EXTENSIONS));
|
// Split them, this replaces the token stuff in the c/c++ version
|
extensions = split!(char)(extensionString, " ");
|
|
// Get the name of your display card
|
renderer = fromStringz(glGetString(GL_RENDERER));
|
// Get the name of its vendor
|
vendor = fromStringz(glGetString(GL_VENDOR));
|
// Get the version of the driver used
|
driverVersion = fromStringz(glGetString(GL_VERSION));
|
|
running = true;
|
while(running)
|
{
|
processEvents();
|
|
drawGLScene();
|
|
SDL_GL_SwapBuffers();
|
SDL_Delay(10);
|
|
// Update scrolling if either the down key or up key is being pressed
|
if(scroller == -1)
|
{
|
if(scroll > 0)
|
{
|
scroll -= 2;
|
}
|
}
|
else if(scroller == 1)
|
{
|
if(scroll < 32 * (maxtokens - 9))
|
{
|
scroll += 2;
|
}
|
}
|
}
|
}
|
|
/**
|
* 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_ESCAPE:
|
running = false;
|
break;
|
case SDLK_UP:
|
case SDLK_DOWN:
|
// No scrolling
|
scroller = 0;
|
break;
|
default:
|
break;
|
}
|
}
|
|
/**
|
* Process a key pressed event.
|
*/
|
void keyPressed(int key)
|
{
|
switch(key)
|
{
|
case SDLK_UP:
|
// Scroll Up
|
scroller = -1;
|
break;
|
case SDLK_DOWN:
|
// Scroll Down
|
scroller = 1;
|
break;
|
default:
|
break;
|
}
|
}
|
|
/**
|
* Load the texture used in this tutorial.
|
*/
|
void loadGLTextures()
|
{
|
SDL_Surface* fontTexture;
|
|
// Load The Bitmap Font, Check For Errors, If the Bitmap's Not Found Quit
|
if((fontTexture = SDL_LoadBMP("data/Font.bmp")) !is null)
|
{
|
// Free the surface when exiting the scope
|
scope(exit)
|
SDL_FreeSurface(fontTexture);
|
|
// Create The Texture
|
glGenTextures(1, &texture);
|
|
// Create Nearest Filtered Texture
|
glBindTexture(GL_TEXTURE_2D, texture);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexImage2D(GL_TEXTURE_2D, 0, 3, fontTexture.w, fontTexture.h, 0, GL_BGR, GL_UNSIGNED_BYTE, fontTexture.pixels);
|
}
|
}
|
|
/**
|
* Resize and initialize the OpenGL window.
|
*/
|
void resizeGLScene(GLsizei width, GLsizei height)
|
{
|
// Set Scissor Width To Window Width
|
swidth = width;
|
// Set Scissor Height To Window Height
|
sheight = height;
|
// Reset The Current Viewport
|
glViewport(0, 0, width, height);
|
// Select The Projection Matrix
|
glMatrixMode(GL_PROJECTION);
|
// Reset The Projection Matrix
|
glLoadIdentity();
|
// Create Ortho widthxheight View (0,0 At Top Left)
|
glOrtho(0.0f, width, height, 0.0f, -1.0f, 1.0f);
|
// Select The Modelview Matrix
|
glMatrixMode(GL_MODELVIEW);
|
// Reset The Modelview Matrix
|
glLoadIdentity();
|
}
|
|
/**
|
* Function to build our font list.
|
*/
|
void buildFont()
|
{
|
// Creating 256 Display List
|
base = glGenLists(256);
|
// Select Our Font Texture
|
glBindTexture(GL_TEXTURE_2D, texture);
|
|
// Loop Through All 256 Lists
|
for(int i = 0; i < 256; i++)
|
{
|
/* NOTE:
|
* BMPs are stored with the top-leftmost pixel being the
|
* last byte and the bottom-rightmost pixel being the first
|
* byte. So an image that is displayed as
|
* 1 0
|
* 0 0
|
* is represented data-wise like
|
* 0 0
|
* 0 1
|
* And because SDL_LoadBMP loads the raw data without
|
* translating to how it is thought of when viewed we need
|
* to start at the bottom-right corner of the data and work
|
* backwards to get everything properly. So the below code has
|
* been modified to reflect this. Examine how this is done and
|
* how the original tutorial is done to grasp the differences.
|
*
|
* As a side note BMPs are also stored as BGR instead of RGB
|
* and that is why we load the texture using GL_BGR. It's
|
* bass-ackwards I know but whattaya gonna do?
|
*/
|
|
// X Position Of Current Character
|
float cx = 1 - cast(float) (i % 16) / 16.0f;
|
// Y Position Of Current Character
|
float cy = 1 - cast(float) (i / 16) / 16.0f;
|
|
// Start Building A List
|
glNewList(base + (255 - i), GL_COMPILE);
|
{
|
// Use A Quad For Each Character
|
glBegin(GL_QUADS);
|
{
|
// Texture Coord (Bottom Left)
|
glTexCoord2f(cx - 0.0625, cy);
|
// Vertex Coord (Bottom Left)
|
glVertex2i(0, 16);
|
|
// Texture Coord (Bottom Right)
|
glTexCoord2f(cx, cy);
|
// Vertex Coord (Bottom Right)
|
glVertex2i(16, 16);
|
|
// Texture Coord (Top Right)
|
glTexCoord2f(cx, cy - 0.0625f);
|
// Vertex Coord (Top Right)
|
glVertex2i(16, 0);
|
|
// Texture Coord (Top Left)
|
glTexCoord2f(cx - 0.0625f, cy - 0.0625f);
|
// Vertex Coord (Top Left)
|
glVertex2i(0, 0);
|
}
|
glEnd();
|
|
// Move To The Left Of The Character
|
glTranslated(15, 0, 0);
|
}
|
glEndList();
|
}
|
}
|
|
/**
|
* Initialize OpenGL.
|
*/
|
void initGL()
|
{
|
// Jump To Texture Loading Routine
|
loadGLTextures();
|
|
// Build The Font
|
buildFont();
|
|
// 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_LEQUAL);
|
// Really Nice Perspective Calculations
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
|
}
|
|
/**
|
* This Is Where The Printing Happens.
|
*
|
* Params:
|
* x = the x coordinate of the text
|
* y = the y coordinate of the text
|
* set = font set
|
* string = the text to be printed
|
*/
|
void glPrint(GLint x, GLint y, int set, char[] string)
|
{
|
if(set > 1)
|
{
|
set = 1;
|
}
|
// Enable textures
|
glEnable(GL_TEXTURE_2D);
|
// Select Our Font Texture
|
glBindTexture(GL_TEXTURE_2D, texture);
|
// Disables Depth Testing
|
glDisable(GL_DEPTH_TEST);
|
// Reset The Modelview Matrix
|
glLoadIdentity();
|
// Position The Text (0,0 - Bottom Left)
|
glTranslated(x, y, 0);
|
// Choose The Font Set (0 or 1)
|
glListBase(base - 32 + (128 * set));
|
// If Set 0 Is Being Used Enlarge Font
|
if(set == 0)
|
{
|
// Enlarge Font Width And Height
|
glScalef(1.5f, 2.0f, 1.0f);
|
}
|
// Write The Text To The Screen
|
glCallLists(string.length, GL_UNSIGNED_BYTE, toStringz(string));
|
// Enables Depth Testing
|
glEnable(GL_DEPTH_TEST);
|
// Disable textures
|
glDisable(GL_TEXTURE_2D);
|
}
|
|
/**
|
* 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 Screen And Depth Buffer
|
|
glColor3f(1.0f, 0.5f, 0.5f); // Set Color To Bright Red
|
glPrint(50, 16, 1, RENDERER_TEXT); // Display Renderer
|
glPrint(80, 48, 1, VENDOR_TEXT); // Display Vendor Name
|
glPrint(66, 80, 1, VERSION_TEXT); // Display Version
|
|
glColor3f(1.0f, 0.7f, 0.4f); // Set Color To Orange
|
glPrint(200, 16, 1, renderer); // Display Renderer
|
glPrint(200, 48, 1, vendor); // Display Vendor Name
|
glPrint(200, 80, 1, driverVersion); // Display Version
|
|
glColor3f(0.5f, 0.5f, 1.0f); // Set Color To Bright Blue
|
glPrint(192, 432, 1, NEHE_PRODUCTIONS_TEXT); // Write NeHe Productions At The Bottom Of The Screen
|
|
glLoadIdentity(); // Reset The ModelView Matrix
|
glColor3f(1.0f, 1.0f, 1.0f); // Set The Color To White
|
glBegin(GL_LINE_STRIP); // Start Drawing Line Strips (Something New)
|
glVertex2d(639, 417); // Top Right Of Bottom Box
|
glVertex2d(0, 417); // Top Left Of Bottom Box
|
glVertex2d(0, 480); // Lower Left Of Bottom Box
|
glVertex2d(639, 480); // Lower Right Of Bottom Box
|
glVertex2d(639, 128); // Up To Bottom Right Of Top Box
|
glEnd(); // Done First Line Strip
|
glBegin(GL_LINE_STRIP); // Start Drawing Another Line Strip
|
glVertex2d(0, 128); // Bottom Left Of Top Box
|
glVertex2d(639, 128); // Bottom Right Of Top Box
|
glVertex2d(639, 1); // Top Right Of Top Box
|
glVertex2d(0, 1); // Top Left Of Top Box
|
glVertex2d(0, 117); // Down To Top Left Of Bottom Box
|
glEnd(); // Done Second Line Strip
|
|
glScissor(1, cast(int) (0.135416f * sheight), swidth - 4, cast(int) (0.597916f * sheight)); // Define Scissor Region
|
glEnable(GL_SCISSOR_TEST); // Enable Scissor Testing
|
|
// Loop through all the extensions
|
foreach(cnt, extension; extensions)
|
{
|
// Is 'maxtokens' Less Than 'cnt'
|
if(maxtokens < cnt)
|
{
|
maxtokens = cnt; // If So, Set 'maxtokens' Equal To 'cnt'
|
}
|
|
glColor3f(0.5f, 1.0f, 0.5f); // Set Color To Bright Green
|
glPrint(0, 96 + (cnt * 32) - scroll, 0, Int.toString(cnt)); // Print Current Extension Number
|
glColor3f(1.0f, 1.0f, 0.5f); // Set Color To Yellow
|
glPrint(75, 96 + (cnt * 32) - scroll, 0, extension); // Print The Current Token (Parsed Extension Name)
|
}
|
|
glDisable(GL_SCISSOR_TEST); // Disable Scissor Testing
|
}
|
|
/**
|
* 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());
|
}
|