// This code example is created for educational purpose // by Thorsten Thormaehlen (contact: www.thormae.de). // It is distributed without any warranty. #include #include // we use glut here as window manager #define _USE_MATH_DEFINES #include #include #include #include #include #include using namespace std; class Renderer { public: float t; private: GLuint texID; std::vector terrain; public: // constructor Renderer() : t(0.0), texID(0) {} //destructor ~Renderer() { if(texID !=0) glDeleteTextures( 1, &texID); } public: void init() { glEnable(GL_DEPTH_TEST); std::vector< std::string > filenames; filenames.push_back("deep_water.ppm"); filenames.push_back("shallow_water.ppm"); filenames.push_back("shore.ppm"); filenames.push_back("fields.ppm"); filenames.push_back("rocks.ppm"); filenames.push_back("snow.ppm"); texID = loadTexture3D(filenames); rebuildTerrain(); } void resize(int w, int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (30.0, (float)w/(float)h, 0.1, 50.0); } void display() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // set camera gluLookAt(1.5, -1.0, 1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); // draw scene glRotatef(t, 0.0f, 0.0f, 1.0f); drawTerrain(); } void rebuildTerrain() { //create random values int dim = 40; terrain.resize(dim*dim); for(int r=0; r < dim*dim; r++) { int rval = rand(); terrain[r] = (fabs(float(rval))/float(RAND_MAX)); } if(true) { // generate smooth terrain values std::vector smoothTerrain(dim*dim); for(unsigned k=0; k < 5; k++){ float maxVal = 0.0f; float minVal = 1.0f; for(int x = 0; x < dim; x++) { for(int y = 0; y < dim; y++) { if(x == 0 || x == dim-1) terrain[x*dim+y] = 0.0f; else if (y == 0 || y == dim-1) terrain[x*dim+y] = 0.0f; else { float a = 0.0f; int counter = 0; for(int s=-1; s <= 1; s++) { for(int r=-1; r <= 1; r++) { a += terrain[(x+s)*dim+(y+r)]; counter++; } } float val = a / float(counter); smoothTerrain[x*dim+y] = val; if(val > maxVal) maxVal = val; if(val < minVal) minVal = val; } } } for(int r=0; r < dim*dim; r++) terrain[r] = (smoothTerrain[r] - minVal) / (maxVal-minVal); } } } private: // returns a valid textureID on success, otherwise 0 GLuint loadTexture3D(std::vector< std::string > &filenames) { unsigned width = 0; unsigned height = 0; unsigned depth = unsigned(filenames.size()); int level = 0; int border = 0; std::vector imgData; std::vector data3d; unsigned prevWidth = 0; unsigned prevHeight = 0; for(unsigned i=0; i < depth; i++) { // load image data if(!loadPPMImageFlipped(filenames[i], width, height, imgData)) return 0; if(i != 0 && (prevWidth != width || prevHeight != height)) return 0; // pack 2D images subsequently into a large 3D buffer data3d.insert(data3d.end(), imgData.begin(), imgData.end()); prevWidth = width; prevHeight = height; } // data is aligned in byte order glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //request textureID GLuint textureID; glGenTextures( 1, &textureID); // bind texture glBindTexture(GL_TEXTURE_3D, textureID); //parameters the define how to warp the texture glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); GLfloat borderColor[4] = {0.0f,0.0f,0.0f,1.0f}; glTexParameterfv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, borderColor); //define how to filter the texture glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //texture colors should replace the original color values glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //GL_MODULATE // specify the 2D texture map glTexImage3D(GL_TEXTURE_3D, level, GL_RGB, width, height, depth, border, GL_RGB, GL_UNSIGNED_BYTE, &data3d[0]); // return unique texture identifier return textureID; } void drawTerrain() { glEnable(GL_TEXTURE_3D); glBindTexture(GL_TEXTURE_3D, texID); glColor3f(1.0f,0.0f,0.0f); unsigned dim = unsigned(sqrt(double(terrain.size()))); float maxHeight = 0.2f; float texHeight = 0.9f; for(unsigned x = 1; x < dim; x++) { for(unsigned y = 1; y < dim; y++) { glBegin(GL_POLYGON); glTexCoord3f(float(x-1)/float(dim), float(y-1)/float(dim), terrain[(x-1)*dim+(y-1)]*texHeight); glVertex3f(float(x-1)/float(dim)-0.5f, float(y-1)/float(dim)-0.5f, terrain[(x-1)*dim+(y-1)]*maxHeight); glTexCoord3f(float(x)/float(dim), float(y-1)/float(dim), terrain[x*dim+(y-1)]*texHeight); glVertex3f(float(x)/float(dim)-0.5f, float(y-1)/float(dim)-0.5f, terrain[x*dim+(y-1)]*maxHeight); glTexCoord3f(float(x)/float(dim), float(y)/float(dim), terrain[x*dim+y]*texHeight); glVertex3f(float(x)/float(dim)-0.5f, float(y)/float(dim)-0.5f, terrain[x*dim+y]*maxHeight); glTexCoord3f(float(x-1)/float(dim), float(y)/float(dim), terrain[(x-1)*dim+y]*texHeight); glVertex3f(float(x-1)/float(dim)-0.5f, float(y)/float(dim)-0.5f, terrain[(x-1)*dim+y]*maxHeight); glEnd(); } } glDisable(GL_TEXTURE_3D); } bool loadPPMImageFlipped(std::string &filename, unsigned &width, unsigned &height, std::vector &imgData) { ifstream input(filename.c_str(), ifstream::in | ifstream::binary); if(!input) { // cast istream to bool to see if something went wrong cerr << "Can not find texture data file " << filename.c_str() << endl; return false; } input.unsetf(std::ios_base::skipws); string line; input >> line >> std::ws; if (line != "P6") { cerr << "File is not PPM P6 raw format" << endl; return false; } width = 0; height = 0; unsigned depth = 0; unsigned readItems = 0; unsigned char lastCharBeforeBinary; while (readItems < 3) { input >> std::ws; if(input.peek() != '#') { if (readItems == 0) input >> width; if (readItems == 1) input >> height; if (readItems == 2) input >> depth >> lastCharBeforeBinary; readItems++; }else{ // skip comments std::getline(input, line); } } if(depth >= 256) { cerr << "Only 8-bit PPM format is supported" << endl; return false; } unsigned byteCount = width * height * 3; imgData.resize(byteCount); input.read((char*)&imgData[0], byteCount*sizeof(unsigned char)); // vertically flip the image because the image origin // in OpenGL is the lower-left corner unsigned char tmpData; for(unsigned y=0; y < height / 2; y++) { int sourceIndex = y * width * 3; int targetIndex = (height-1-y) * width *3; for(unsigned x=0; x < width*3; x++) { tmpData = imgData[targetIndex]; imgData[targetIndex] = imgData[sourceIndex]; imgData[sourceIndex] = tmpData; sourceIndex++; targetIndex++; } } return true; } }; //this is a static pointer to a Renderer used in the glut callback functions static Renderer *renderer; //glut static callbacks start static void glutResize(int w, int h) { renderer->resize(w,h); } static void glutDisplay() { renderer->display(); glutSwapBuffers(); glutReportErrors(); } static void timer(int v) { float offset = 0.25f; renderer->t += offset; glutDisplay(); glutTimerFunc(unsigned(20), timer, ++v); } static void glutKeyboard(unsigned char key, int x, int y) { bool redraw = false; switch(key) { case '1': renderer->rebuildTerrain(); redraw = true; break; } if(redraw) { glutDisplay(); } } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(100,100); glutInitWindowSize(320, 320); glutCreateWindow("Press 1 to generate a new random terrain"); GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "Glew error: %s\n", glewGetErrorString(err)); } glutDisplayFunc(glutDisplay); //glutIdleFunc(glutDisplay); glutReshapeFunc(glutResize); glutKeyboardFunc(glutKeyboard); renderer = new Renderer; renderer->init(); glutTimerFunc(unsigned(20), timer, 0); glutMainLoop(); }