// Written in the D Programming Language /** * The 10h lesson in the NeHe tutorial series. * Originally written by Lionel Brits and Jeff Molofee. * * Authors: Lionel Brits * Jeff Molofee * Olli Aalto */ module lesson10; import derelict.opengl.gl; import derelict.opengl.glu; import derelict.sdl.sdl; import tango.stdc.stringz; import tango.stdc.stdio; import tango.text.stream.LineIterator; import tango.io.Buffer; import Integer = tango.text.convert.Integer; import Float = tango.text.convert.Float; import Math = tango.math.Math; import TextUtil = tango.text.Util; /// Build Our Vertex Structure struct Vertex { /// 3D Coordinates float x = 0.0f, y = 0.0f, z = 0.0f; /// Texture Coordinates float u = 0.0f, v = 0.0f; } /// Build Our Triangle Structure struct Triangle { /// Array Of Three Vertices Vertex[3] vertices; } /// Build Our Sector Structure struct Sector { /// An Array Of Triangles Triangle[] triangles; } /// Our sector Sector sector; /// Camera rotation variable GLfloat yrot = 0.0f; /// Camera pos variable GLfloat xpos = 0.0f, zpos = 0.0f; /// Head-bobbing variables GLfloat walkBias = 0.0f, walkBiasAngle = 0.0f; GLfloat lookUpDown = 0.0f; /// Ambient Light Values GLfloat lightAmbient[] = [0.5f, 0.5f, 0.5f, 1.0f]; /// Diffuse Light Values GLfloat lightDiffuse[] = [1.0f, 1.0f, 1.0f, 1.0f]; /// Light Position GLfloat lightPosition[] = [0.0f, 0.0f, 2.0f, 1.0f]; /// Constant used for converting to radians const float piover180 = 0.0174532925f; bool blend; bool light; /// Which Filter To Use GLuint filter; /// Storage for 3 textures GLuint[3] textures; const char[] WINDOW_TITLE = "Lionel Brits & NeHe's 3D World Tutorial (D version)"; bool running = true; /** * 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()); } // Enable key repeat so the player can keep a key down for moving if (SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL)) { throw new Exception("Failed to set key repeat: " ~ 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() { 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(); // We import the world data file at compile time. // Depending on the version of DMD you have installed, // you might need to use the -J switch or add the relative path // before the file name. // Version 1.005 of DMD is required for this. setupWorld(import("world.txt")); while (running) { processEvents(); drawGLScene(); SDL_GL_SwapBuffers(); SDL_Delay(10); } } void processEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: keyPressed(event.key.keysym.sym); break; case SDL_QUIT: running = false; break; default: break; } } } void keyPressed(int key) { switch (key) { case SDLK_ESCAPE: running = false; break; case SDLK_b: blend = !blend; if (blend) { glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); } else { glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } break; case SDLK_f: filter = ++filter % 3; break; case SDLK_l: light = !light; if (light) { glEnable(GL_LIGHTING); } else { glDisable(GL_LIGHTING); } break; case SDLK_RIGHT: yrot -= 1.5f; break; case SDLK_LEFT: yrot += 1.5f; break; case SDLK_UP: xpos -= cast(float) Math.sin(yrot * piover180) * 0.05f; zpos -= cast(float) Math.cos(yrot * piover180) * 0.05f; if (walkBiasAngle >= 359.0f) { walkBiasAngle = 0.0f; } else { walkBiasAngle += 10; } walkBias = cast(float) Math.sin(walkBiasAngle * piover180) * 0.05f; break; case SDLK_DOWN: xpos += cast(float) Math.sin(yrot * piover180) * 0.05f; zpos += cast(float) Math.cos(yrot * piover180) * 0.05f; if (walkBiasAngle <= 1.0f) { walkBiasAngle = 359.0f; } else { walkBiasAngle -= 10; } walkBias = cast(float) Math.sin(walkBiasAngle * piover180) * 0.05f; break; default: break; } } void loadGLTextures() { SDL_Surface* textureImage; if ((textureImage = SDL_LoadBMP("data/Mud.bmp")) !is null) { // Free the surface when exiting the scope scope(exit) SDL_FreeSurface(textureImage); // Create The Texture glGenTextures(3, &textures[0]); // Load in texture 1 glBindTexture(GL_TEXTURE_2D, textures[0]); // Generate The Texture glTexImage2D(GL_TEXTURE_2D, 0, 3, textureImage.w, textureImage.h, 0, GL_BGR, GL_UNSIGNED_BYTE, textureImage.pixels); // Nearest Filtering glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Load in texture 2 glBindTexture(GL_TEXTURE_2D, textures[1]); // Linear Filtering glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Generate The Texture glTexImage2D(GL_TEXTURE_2D, 0, 3, textureImage.w, textureImage.h, 0, GL_BGR, GL_UNSIGNED_BYTE, textureImage.pixels); // Load in texture 3 glBindTexture(GL_TEXTURE_2D, textures[2]); // Mipmapped Filtering glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Generate The MipMapped Texture gluBuild2DMipmaps(GL_TEXTURE_2D, 3, textureImage.w, textureImage.h, GL_BGR, GL_UNSIGNED_BYTE, textureImage.pixels); return; } throw new Exception("Failed to load textures: " ~ getSDLError()); } /** * Resize And Initialize The GL 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(); } /** * All Setup For OpenGL Goes Here. */ void initGL() { loadGLTextures(); /* Enable Texture Mapping */ glEnable(GL_TEXTURE_2D); /* Enable smooth shading */ glShadeModel(GL_SMOOTH); /* Set the background black */ 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); /* Setup The Ambient Light */ glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient.ptr); /* Setup The Diffuse Light */ glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse.ptr); /* Position The Light */ glLightfv(GL_LIGHT1, GL_POSITION, lightPosition.ptr); /* Enable Light One */ glEnable(GL_LIGHT1); /* Full Brightness, 50% Alpha */ glColor4f(1.0f, 1.0f, 1.0f, 0.5f); /* Blending Function For Translucency Based On Source Alpha Value */ glBlendFunc(GL_SRC_ALPHA, GL_ONE); // Change to texture matrix and flip and rotate the texture glMatrixMode(GL_TEXTURE); glRotatef(180.0f, 0.0f, 0.0f, 1.0f); glScalef(-1.0f, 1.0f, 1.0f); // Back to normal glMatrixMode(GL_MODELVIEW); } /** * Here's Where We Do All The Drawing. */ void drawGLScene() { /* Used For Player Translation On The X Axis */ GLfloat xtrans = -xpos; /* Used For Player Translation On The Z Axis */ GLfloat ztrans = -zpos; /* Used For Bouncing Motion Up And Down */ GLfloat ytrans = -walkBias - 0.5f; /* 360 Degree Angle For Player Direction */ GLfloat sceneRot = 360.0f - yrot; /* Clear The Screen And The Depth Buffer */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); /* Rotate Up And Down To Look Up And Down */ glRotatef(lookUpDown, 1.0f, 0.0f, 0.0f); /* Rotate Depending On Direction Player Is Facing */ glRotatef(sceneRot, 0.0f, 1.0f, 0.0f); /* Translate The Scene Based On Player Position */ glTranslatef(xtrans, ytrans, ztrans); /* Select A Texture Based On filter */ glBindTexture(GL_TEXTURE_2D, textures[filter]); /* Process Each Triangle */ foreach (t; sector.triangles) { /* Normal Pointing Forward */ glNormal3f(0.0f, 0.0f, 1.0f); /* Start Drawing Triangles */ glBegin(GL_TRIANGLES); foreach (ver; t.vertices) { /* Set The TexCoords And Vertices */ glTexCoord2f(ver.u, ver.v); glVertex3f(ver.x, ver.y, ver.z); } glEnd(); } } void createGLWindow(char[] title, int width, int height, int bits, bool fullScreen) { // Set the OpenGL attributes SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); 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. */ 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); } /** * Read lines from the iterator until one is found that isn't empty or does not * start with '/' character. */ char[] readLine(LineIterator!(char) iter) { char[] line; do { line = iter.next(); } while (line.length == 0 || line[0] == '/'); return TextUtil.trim(line); } /** * Parse the input data and initialize the Sector with Triangles and Vertices. */ void setupWorld(char[] worldData) { // Create a iterator that iterates over every line in the input data. auto iter = new LineIterator!(char)(new Buffer(worldData)); // Split the first line auto splittedLine = TextUtil.delimit!(char)(readLine(iter), " "); // Parse the number of triangles from the first line sector.triangles.length = Integer.parse(splittedLine[1]); foreach (inout tri; sector.triangles) { foreach (inout ver; tri.vertices) { // Read the vertex and texture position data to ver's fields. sscanf(readLine(iter).ptr, "%f%f%f%f%f", &ver.x, &ver.y, &ver.z, &ver.u, &ver.v); } } } /** * Get the SDL error as a D string. * * Returns: A D string containing the current SDL error. */ char[] getSDLError() { return fromStringz(SDL_GetError()); }