#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... size, bits and so on pagesX = (heightfield.width/(pageSize-1) ); pagesZ = (heightfield.height/(pageSize-1) ); //TODO: Determine layer visibility! for ( unsigned int i = 0; i < materials.size(); ++i ) { } 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 ); 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( Triple( 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: //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_CULL_FACE ); glCullFace( GL_BACK ); 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; 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(); } //Finish the last buffer 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 ); } glPushAttrib( GL_COLOR_BUFFER_BIT ); for ( unsigned 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( pagesX, pagesZ, 1.0f ); 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 ); glEnable( GL_CULL_FACE ); 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(); } activatedCount = 0; deactivatedCount = 0; } //Get rid of the buffers for ( unsigned int i = 0; i < buffers.size(); ++i ) broker->release( buffers[i].vbIdentifier, buffers[i].ibIdentifier ); buffers.clear(); 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 ); glMatrixMode( GL_TEXTURE ); glLoadIdentity(); glPopAttrib(); } 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(); } } }