#include "terrain.h" #include "terrain_page.h" #include "glincl.h" #include "util/loading/resource_manager.h" #include "debug.h" #include #ifdef HAVE_SDL_SDL_IMAGE_H #include #else #include #endif bool validateSize( int _s ) { _s-=1; int s = 16; while ( s <= _s ) { if (s == _s ) return true; s*=2; } return false; } void Terrain::build() { printf("SHOULD GET LOADED :)\n"); ResourceManager *MANAGER = ResourceManager::getInstance(); std::string full = MANAGER->getFullName( heightmapSource ); SDL_Surface *tmpData = IMG_Load( full.c_str() ); if ( !tmpData ) { PRINT(0)( "I' sorry, I can't load %s\n", full.c_str() ); return; } if ( !validateSize( tmpData->h ) || !validateSize( tmpData->w ) ) { PRINT(0)( "The size of the elevation map must be 2^n+1x2^m+1. and at least 17x17" ); return; } if ( tmpData->format->BytesPerPixel != 1 ) { PRINT(0)( "The elevetation map must be an 8bit image not %d", tmpData->format->BytesPerPixel*8 ); return; } PRINTF(2)( "Loaded the elevation map\n" ); heightfield.height = tmpData->h; heightfield.width = tmpData->w; heightfield.pitch = tmpData->pitch; int dataSize = heightfield.pitch*heightfield.height; heightfield.data = new Uint8[dataSize]; memcpy( heightfield.data, tmpData->pixels, sizeof(Uint8)*dataSize ); SDL_FreeSurface( tmpData ); pagesX = (heightfield.width/(pageSize-1) ); pagesZ = (heightfield.height/(pageSize-1) ); pages = new pTerrainPage[pagesX*pagesZ]; for ( int x = 0; x < pagesX; ++x ) for ( int z = 0; z < pagesZ; ++z ) pages[z*pagesX+x] = createPage( x, z ); //Inform each page about its neighbors. for ( int x = 0; x < pagesX; ++x ) for ( int z = 0; z < pagesZ; ++z ) pages[z*pagesX+x]->setNeighbors( x > 0 ? getPage( x-1, z+0 ) : NULL, x < pagesX-1 ? getPage( x+1, z+0 ) : NULL, z < pagesZ-1 ? getPage( x+0, z+1 ) : NULL, z > 0 ? getPage( x+0, z-1 ) : NULL ); root = createQuadTree( 0, 0, pagesX, pagesZ ); for ( unsigned int i = 0; i < layers.size(); ++i ) { determineLayerVisibility( i ); } activePages = NULL; } pTerrainQuad Terrain::createQuadTree( int _x0, int _z0, int _x1, int _z1, int _depth ) { int _x01 = (_x1+_x0)/2; int _z01 = (_z1+_z0)/2; pTerrainQuad node; if ( _x1-_x0 == 1 ) { node = getPage( _x0, _z0 ); node->setChildren( NULL, NULL, NULL, NULL ); } else { node = new TerrainQuad( this, _x0, _z0, _x1, _z1 ); node->setChildren( createQuadTree( _x0, _z0, _x01, _z01, _depth+1 ), createQuadTree( _x01, _z0, _x1, _z01, _depth+1 ), createQuadTree( _x0, _z01, _x01, _z1, _depth+1 ), createQuadTree( _x01, _z01, _x1, _z1, _depth+1 ) ); } node->calculateBounds(); return node; } pTerrainPage Terrain::createPage( int _xOffset, int _zOffset ) const { pTerrainPage newPage = new TerrainPage( const_cast( this ), _xOffset, _zOffset ); newPage->setScale( scale ); newPage->setPosition( Vector( scale.x*_xOffset, 0.0f, scale.z*_zOffset ) ); newPage->calculateErrors(); return newPage; } void Terrain::addLevelFourPage( int _numVertices, Vertex *_vertices, int _numIndices, unsigned short *_indices ) { assert( indices ); assert( vertices ); BufferInfo bi = buffers[current]; if ( ( MAX_VERTICES < _numVertices+bi.numVertices ) || ( MAX_INDICES < _numIndices+bi.numIndices+2 ) ) { //So, we need the next vb and ib. Lets put the old into vram... glBindBufferARB( GL_ARRAY_BUFFER_ARB, bi.vbIdentifier ); glBufferDataARB( GL_ARRAY_BUFFER_ARB, MAX_VERTICES*sizeof( Vertex ), vertices, GL_DYNAMIC_DRAW_ARB ); glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bi.ibIdentifier ); glBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, MAX_INDICES*sizeof( short ), indices, GL_DYNAMIC_DRAW_ARB ); BufferInfo newInfo; broker->acquire( newInfo.vbIdentifier, newInfo.ibIdentifier ); current++; buffers.push_back( newInfo ); bi = newInfo; } //For the vertex data, a simple copy operation is sufficient... memcpy( &vertices[bi.numVertices], _vertices, _numVertices*sizeof( Vertex ) ); //The indices need to be updated with an offset :( unsigned short *dst= &indices[bi.numIndices]; unsigned short offset = bi.numVertices; unsigned short *src= _indices; unsigned short *end= src+_numIndices; bi.numVertices+=_numVertices; if ( bi.numIndices ) { dst[0] = *(dst-1); dst[1] = *src+offset; dst+=2; bi.numIndices+=2; } while ( src < end ) { *dst= *src+offset; dst++; src++; } bi.numIndices+=_numIndices; buffers[current] = bi; } void Terrain::determineVisiblePages( pTerrainQuad _node ) { switch( _node->cull() ) { case Frustum::INTERSECT: if ( !_node->isChildless() ) { pTerrainQuad *children = _node->getChildren(); for ( int i = 0; i < 4; ++i, ++children ) determineVisiblePages( *children ); } else { showPages( _node->getXOffset(), _node->getZOffset(), 1, 1 ); } break; case Frustum::INSIDE: showPages( _node->getXOffset(), _node->getZOffset(), _node->getWidth() , _node->getHeight() ); break; case Frustum::OUTSIDE: break; } } void Terrain::tick( float _dt ) { for ( unsigned int i = 0; i < buffers.size(); ++i ) broker->release( buffers[i].vbIdentifier, buffers[i].ibIdentifier ); buffers.clear(); pTerrainPage page = NULL; //Extract the frustum planes out of the modelview matrix. frustum->extractPlanes(); // Lets see which pages are visible. determineVisiblePages( root ); int wantedLeft, wantedRight, wantedBottom, wantedTop, minLOD; pTerrainPage neighbor = NULL; page = activePages; bool dirty; current = 0; BufferInfo bi; broker->acquire( bi.vbIdentifier, bi.ibIdentifier ); buffers.push_back( bi ); int dirtyRounds = 0; do { dirtyRounds++; dirty = false; page = activePages; while ( page ) { if ( !page->isActive() ) { pTerrainPage tmp = page; page = tmp->getNext(); tmp->setVisibility( false ); continue; } wantedLeft = wantedRight = wantedBottom = wantedTop = page->getWantedLOD(); if ( ( neighbor = page->getLeft() ) && ( neighbor->isActive() ) ) wantedLeft = neighbor->getWantedLOD(); if ( ( neighbor = page->getRight() ) && ( neighbor->isActive() ) ) wantedRight = neighbor->getWantedLOD(); if ( ( neighbor = page->getTop() ) && ( neighbor->isActive() ) ) wantedTop = neighbor->getWantedLOD(); if ( ( neighbor = page->getBottom() ) && ( neighbor->isActive() ) ) wantedBottom = neighbor->getWantedLOD(); minLOD = std::min( std::min( wantedBottom, wantedTop ), std::min( wantedLeft, wantedRight ) ); if ( minLOD < page->getWantedLOD()-1 ) { page->setWantedLOD( minLOD+1 ); dirty = true; } page = page->getNext(); } } while ( dirty ); page = activePages; while ( page ) { assert( page->isActive() ); page->updateTesselation(); page = page->getNext(); } //If there is some data in the buffer, we need to upload the data //into the vram... if ( buffers[current].numIndices != 0 ) { BufferInfo bi = buffers[current]; glBindBufferARB( GL_ARRAY_BUFFER_ARB, bi.vbIdentifier ); glBufferDataARB( GL_ARRAY_BUFFER_ARB, MAX_VERTICES*sizeof( Vertex ), vertices, GL_DYNAMIC_DRAW_ARB ); glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bi.ibIdentifier ); glBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, MAX_INDICES*sizeof( short ), indices, GL_DYNAMIC_DRAW_ARB ); } } void Terrain::draw( ) { pTerrainPage page = NULL; glGetError(); /*pTerrainPage page = NULL; frustum->extractPlanes();*/ Plane far = frustum->getPlane( Frustum::FAR ); //Due to some reason, the OpenGL implementors chose the plane equation //to an array of doubles. So we will make them happy. double farPlane[] = { far.n.x, far.n.y, far.n.z, far.k }; glEnable( GL_CLIP_PLANE0 ); glClipPlane( GL_CLIP_PLANE0, farPlane ); glPushAttrib( GL_ALL_ATTRIB_BITS ); glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); /* * Enable texture and vertex arrays for the first and the second texture * units and disable the normal arrays. */ glClientActiveTextureARB( GL_TEXTURE0_ARB ); glEnableClientState( GL_VERTEX_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glDisableClientState( GL_NORMAL_ARRAY ); glClientActiveTextureARB( GL_TEXTURE1_ARB ); glEnableClientState( GL_VERTEX_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glDisableClientState( GL_NORMAL_ARRAY ); glDisable( GL_CULL_FACE ); glDisable( GL_LIGHTING ); glColor3f( 1.0f, 1.0f, 1.0f ); //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); cullCount = 0; glEnable( GL_BLEND ); glDepthFunc( GL_LEQUAL ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); for ( unsigned int i = 0; i < layers.size(); ++i ) { LayerInfo* layer= layers[i]; page = activePages; glActiveTextureARB( GL_TEXTURE1_ARB ); glClientActiveTextureARB( GL_TEXTURE1_ARB ); if ( layer->detail ) { //glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND ); glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, layer->detail->getTexture() ); glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glScalef( layer->repeatX, layer->repeatZ, 1.0f ); } else { glDisable( GL_TEXTURE_2D ); } glEnable( GL_CULL_FACE ); glCullFace( GL_BACK ); glClientActiveTextureARB( GL_TEXTURE0_ARB ); glActiveTextureARB( GL_TEXTURE0_ARB ); glEnable( GL_TEXTURE_2D ); if ( layer->alpha ) { //glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND ); glBindTexture( GL_TEXTURE_2D, layer->alpha->getTexture() ); } else { glBindTexture( GL_TEXTURE_2D, lightmap->getTexture() ); } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); for ( unsigned j = 0; j < buffers.size(); ++j ) { BufferInfo bi = buffers[j]; glBindBufferARB( GL_ARRAY_BUFFER_ARB, bi.vbIdentifier ); glClientActiveTextureARB( GL_TEXTURE0_ARB ); glInterleavedArrays( GL_T2F_V3F, 0, NULL ); glClientActiveTextureARB( GL_TEXTURE1_ARB ); glInterleavedArrays( GL_T2F_V3F, 0, NULL ); glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bi.ibIdentifier ); glDrawElements( GL_TRIANGLE_STRIP, bi.numIndices, GL_UNSIGNED_SHORT, NULL ); } while ( page ) { if ( page->hasMaterial( i ) ) page->draw(); page = page->getNext(); } } glClientActiveTextureARB( GL_TEXTURE1_ARB ); glActiveTextureARB( GL_TEXTURE1_ARB ); glDisable( GL_TEXTURE_2D ); glActiveTextureARB( GL_TEXTURE0_ARB ); glEnable( GL_LIGHTING ); glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glPopAttrib(); glPopClientAttrib(); glCullFace( GL_FRONT ); glDisable( GL_CLIP_PLANE0 ); } inline Uint8 getAlpha( const SDL_Surface *_s, int _x, int _y ) { int bpp = _s->format->BytesPerPixel; Uint8 *p = (Uint8 *)_s->pixels + _y*_s->pitch + _x * bpp; Uint32 pixel = 0; switch( bpp ) { case 1: pixel = *p; break; case 2: if ( SDL_BYTEORDER == SDL_BIG_ENDIAN ) pixel = p[0] << 8 | p[1]; else pixel = *(Uint16 *)p; break; case 3: if( SDL_BYTEORDER == SDL_BIG_ENDIAN ) pixel = p[0] << 16 | p[1] << 8 | p[2]; else pixel = p[0] | p[1] << 8 | p[2] << 16; break; case 4: pixel = *(Uint32 *)p; break; default: return 255; /* shouldn't happen, but avoids warnings */ } Uint8 r,g,b,a; SDL_GetRGBA( pixel, _s->format, &r, &g, &b, &a ); return a; } void Terrain::determineLayerVisibility( int _layer ) { LayerInfo * layer = layers[_layer]; if ( !layer->alpha ) { int numPages = pagesX*pagesZ; for ( int i = 0; i < numPages; ++i ) pages[i]->setLayerVisibility( _layer, LV_FULL ); return; } SDL_Surface *alpha = const_cast( layer->alpha->getStoredImage() ); SDL_LockSurface( alpha ); float du = ( (float)alpha->w)/pagesX; float dv = ( (float)alpha->h)/pagesZ; float u = 0.0f, v = 0.0f; for ( int pageX = 0; pageX < pagesX; ++pageX ) { v = 0.0f; for ( int pageZ = 0; pageZ < pagesZ; ++pageZ ) { bool full = true; bool has = false; for ( int x = 0; x < (int)PAGE_SIZE; ++x ) { for ( int z = 0; z < (int)PAGE_SIZE; ++z ) { Uint8 a = getAlpha( alpha, (int)u, (int)v ); if ( a ) has = true; if ( a < 255 ) full = false; } } LayerVisibility lv; if ( has ) { if ( full ) lv = LV_FULL; else lv = LV_PARTIAL; } else { lv = LV_NO; } getPage( pageX, pageZ )->setLayerVisibility( _layer, lv ); v+= dv; } u+= du; } SDL_UnlockSurface( alpha ); } void Terrain::getAltitude( Vector& _alt, Vector& _normal ) { float xScaled = _alt.x / scale.x, zScaled = _alt.z / scale.z; //The offset on the map int xOff = (int)xScaled, zOff = (int)zScaled; //The interpolation values. float u = xScaled-xOff, v = zScaled-zOff; float dX = scale.x / ( pageSize-1 ); float dZ = scale.z / ( pageSize-1 ); //If u is bigger than v, we are on the lower triangle... if ( u > v ) { float alt[] = { getAltitude( xOff+0, zOff+0 )*scale.y, getAltitude( xOff+1, zOff+0 )*scale.y, getAltitude( xOff+1, zOff+1 )*scale.y }; _alt.y = (1.0f-u-v)*alt[0]+u*alt[1]+v*alt[2]; //Since we know about the directions of some x and z-coordinates, //not the whole cross products needs to be calculated. Grab yourself //pen and paper :) _normal.x = dZ*( alt[0] - alt[1] ); _normal.y = dZ*dX; _normal.z = -dX*( alt[2] - alt[1] ); } else { float alt[] = { getAltitude( xOff+0, zOff+0 )*scale.y, getAltitude( xOff+0, zOff+1 )*scale.y, getAltitude( xOff+1, zOff+1 )*scale.y }; _alt.y = (1.0f-u-v)*alt[0]+v*alt[1]+u*alt[2]; //Since we know about the directions of some x and z-coordinates, //not the whole cross products needs to be calculated. Grab yourself //pen and paper :) _normal.x = -dZ*( alt[2] - alt[1] ); _normal.y = dZ*dX; _normal.z = dX*( alt[0] - alt[1] ); } } void Terrain::showPages( int _x0, int _z0, int _width, int _height ) { for ( int x = 0; x < _width; ++x ) { for ( int z = 0; z < _height; ++z ) { pTerrainPage page = getPage( _x0+x, _z0+z ); page->setVisibility( true ); page->chooseLOD(); } } }