#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 void Terrain::build() { ResourceManager *MANAGER = ResourceManager::getInstance(); std::string full = MANAGER->getFullName( heightmapSource ); SDL_Surface *tmpData = IMG_Load( full.c_str() ); if ( !tmpData ) { PRINTF(0)( "I' sorry, I can't load %s\n", full.c_str() ); return; } heightfield.height = tmpData->h; heightfield.width = tmpData->w; heightfield.pitch = tmpData->pitch; int dataSize = heightfield.pitch*heightfield.height; heightfield.data = new UByte[dataSize]; memcpy( heightfield.data, tmpData->pixels, sizeof(UByte)*dataSize ); SDL_FreeSurface( tmpData ); //TODO: Perform some checks... pagesX = (heightfield.width/(pageSize-1) ); pagesZ = (heightfield.height/(pageSize-1) ); //tex = (Texture*)MANAGER->load( lightmapSource ); //TODO: Determine layer visibility! for ( int i = 0; i < materials.size(); ++i ) { } printf( " * creating terrain pages ( %d, %d )...", pagesX, pagesZ ); 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 ); printf( "looks good\n" ); printf( " * inform pages about the adjacent pages..." ); //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 > 0 ? getPage( x+0, z-1 ) : NULL, z < pagesZ-1 ? getPage( x+0, z+1 ) : NULL ); printf( "looks good\n" ); printf( " * creating quad_tree data structure..." ); root = createQuadTree( 0, 0, pagesX, pagesZ ); activePages = NULL; printf( "looks good\n" ); } 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 { assert( ( _x0 % 2 ) == 0 ); assert( ( _x1 % 2 ) == 0 ); assert( ( _z0 % 2 ) == 0 ); assert( ( _z1 % 2 ) == 0 ); 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( Triple( scale.x*_xOffset, 0.0f, scale.z*_zOffset ) ); newPage->calculateErrors(); return newPage; } void Terrain::determineVisiblePages( pTerrainQuad _node ) { switch( _node->cull() ) { case Frustum::INTERSECT: //printf( "partially inside frustum\n" ); 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: //printf( "fully inside frustum\n" ); showPages( _node->getXOffset(), _node->getZOffset(), _node->getWidth() , _node->getHeight() ); break; case Frustum::OUTSIDE: cullCount+= (_node->getWidth()-1)*(_node->getHeight()-1); break; } } void Terrain::draw( ) { static float s = 0.0f; glGetError(); pTerrainPage page = NULL; frustum->extractPlanes(); /** * 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_LIGHTING ); glColor3f( 1.0f, 1.0f, 1.0f ); //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); cullCount = 0; // Lets see which pages are visible. determineVisiblePages( root ); int wantedLeft, wantedRight, wantedBottom, wantedTop, minLOD; pTerrainPage neighbor = NULL; page = activePages; bool dirty; do { 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() ) ) wantedLeft = neighbor->getWantedLOD(); if ( ( neighbor = page->getRight() ) ) wantedRight = neighbor->getWantedLOD(); if ( ( neighbor = page->getTop() ) ) wantedTop = neighbor->getWantedLOD(); if ( ( neighbor = page->getBottom() ) ) 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 ) { page->updateTesselation(); page = page->getNext(); } s = 500.0f; for ( int i = 0; i < materials.size(); ++i ) { page = activePages; //This is a hack! Remove this as soon as possible materials[i]->unselect(); materials[i]->select(); glActiveTextureARB( GL_TEXTURE1_ARB ); glClientActiveTextureARB( GL_TEXTURE1_ARB ); glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glScalef( s, s, s ); glClientActiveTextureARB( GL_TEXTURE0_ARB ); glActiveTextureARB( GL_TEXTURE0_ARB ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); while ( page ) { //Draw the page. TODO: It would be nice if all the pages with an LOD of 4 could be merged //in one index buffer. This would give us a speed boost for sure... if ( page->hasMaterial( i ) ) page->draw(); page = page->getNext(); } activatedCount = 0; deactivatedCount = 0; //printf( "%d of %d pages drawn\n", pagesX*pagesZ-cullCount, pagesX*pagesZ ); //float percentage = (float)cullCount/(float)(pagesX*pagesZ)*100.0f; //printf( "culled %f%% terrain pages away\n", percentage ); } glClientActiveTextureARB( GL_TEXTURE1_ARB ); glDisableClientState( GL_VERTEX_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glDisableClientState( GL_VERTEX_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); materials[0]->unselect(); glActiveTextureARB( GL_TEXTURE0_ARB ); glClientActiveTextureARB( GL_TEXTURE0_ARB ); glDisableClientState( GL_VERTEX_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glEnable( GL_LIGHTING ); } void Terrain::getAltitude( Triple& _alt, Triple& _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(); } } }