/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2006 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Marco Biasini */ #include "terrain_page.h" #include "terrain.h" #include "glincl.h" #include #include #define USE_VBO #define CHECK_GL_ERROR( _d ) do { \ GLenum __err = glGetError(); \ if ( __err != GL_NO_ERROR ) \ printf( "check%s: %s\n", _d, (char*)gluErrorString( __err ) );\ }\ while ( 0 ) TerrainPage::~TerrainPage( ) { SAVE_DELETE_ARRAY( indices ); SAVE_DELETE_ARRAY( vertices ); SAVE_DELETE_ARRAY( indexHash ); SAVE_DELETE_ARRAY( errors ); } TerrainPage::TerrainPage( Terrain *_owner, int _xOffset, int _zOffset ) : TerrainQuad( _owner, _xOffset, _zOffset ) { scale = owner->getScale(); numVertices = numIndices = 0; errors = new LODError[TerrainPage::MAX_LODS]; vertices = NULL; indices = NULL; left = top = bottom = right = NULL; for ( int i = 0; i < 8; ++i ) layerVisibility[i] = LV_NO; position = Vector( scale.x*_xOffset, 0.0f, scale.z*_zOffset ); isVisible = false; next = NULL; active = false; previous = NULL; currentLOD = -1; wantedLOD = -1; hasfull = false; forceTesselation = true; vbIdentifier = 0; ibIdentifier = 0; } void TerrainPage::tesselateRow( int _z, int _xStride, int _zStride, bool _adaptLeft, bool _adaptRight ) { int xStart = 0, xEnd = owner->getPageSize(); int halfStride = _zStride >> 1; assert( _xStride > 0 ); assert( _zStride > 0 ); if ( _z ) { addAgain(); addIndex( getIndex( 0, _z ) ); } if ( _adaptLeft ) { assert( halfStride > 0 ); addIndex( getIndex( 0, _z ) ); addIndex( getIndex( 0, _z+halfStride ) ); addIndex( getIndex( _xStride, _z ) ); addIndex( getIndex( 0, _z+_zStride ) ); addIndex( getIndex( _xStride, _z+_zStride ) ); addAgain(); xStart = _xStride; } if ( _adaptRight ) xEnd-=_xStride; for ( int x = xStart; x < xEnd; x+=_xStride ) { addIndex( getIndex( x, _z) ); addIndex( getIndex( x, _z+_zStride ) ); } int w = owner->getPageSize()-1; if ( _adaptRight ) { assert( halfStride > 0 ); addIndex( getIndex( xEnd-1, _z ) ); addAgain(); addIndex( getIndex( w, _z ) ); addIndex( getIndex( xEnd-1, _z+_zStride ) ); addIndex( getIndex( w, _z+halfStride ) ); addIndex( getIndex( w, _z+_zStride ) ); } } //TODO: Perform benchmark to measure if the isVisible test should be included //in this method. void TerrainPage::determineBorderAdaption( bool _adapt[] ) { _adapt[0] = _adapt[1] = _adapt[2] = _adapt[3] = false; if ( left && left->isActive() ) { _adapt[TP_LEFT] = ( wantedLOD - left->getWantedLOD() ) > 0; assert( std::abs( wantedLOD - left->wantedLOD ) < 2 ); } if ( right && right->isActive() ) { _adapt[TP_RIGHT] = ( wantedLOD - right->getWantedLOD() ) > 0; assert( std::abs( wantedLOD - right->wantedLOD ) < 2 ); } if ( top && top->isActive() ) { _adapt[TP_TOP] = ( wantedLOD - top->getWantedLOD() ) > 0; assert( std::abs( wantedLOD - top->wantedLOD ) < 2 ); } if ( bottom && bottom->isActive() ) { _adapt[TP_BOTTOM] = ( wantedLOD - bottom->getWantedLOD() ) > 0; assert( std::abs( wantedLOD - bottom->wantedLOD ) < 2 ); } } int TerrainPage::chooseLOD() { wantedLOD = -1; Vector cam( owner->getCameraPosition() ); for ( int i = TerrainPage::MAX_LODS-1; i >= 0; --i ) { Vector distance( cam.x-errors[i].correct.x, cam.y-errors[i].correct.y, cam.z-errors[i].correct.z ); float d = distance.len(); float err = errors[i].diff / d ; if ( err*scale.y < owner->getDetail() ) { wantedLOD = i; break; } } if ( wantedLOD < 0 ) { wantedLOD = TerrainPage::MAX_LODS-1; } return wantedLOD; } void TerrainPage::activate() { pTerrainPage list = owner->getActiveList(); next = list; if ( list ) list->previous = this; owner->setActiveList( this ); } void TerrainPage::deactivate() { pTerrainPage list = owner->getActiveList(); if ( previous ) { previous->next = next; } if ( next ) { next->previous = previous; } if ( list == this ) owner->setActiveList( next ); next = NULL; previous = NULL; } void TerrainPage::calculateError( int _lod ) { float sumError = 0.0f; int numErrors = 0; int size = owner->getPageSize(); if( _lod!=0 ) { int stride = 1 << _lod, x0, y0, xi, yi; // Altough these four nested loops look very scary, they're not // that bad and require only about O(n^2). for( y0 = 0 ; y0 < size-stride; y0 += stride ) { for( x0 = 0; x0 < size-stride; x0 += stride ) { for( yi = 1; yi < stride; yi++ ) { for( xi = 1; xi < stride; xi++ ) { int x = x0+xi, y = y0+yi; float fx0 = ( float )xi/( float )stride, fx1 = 1.0f-fx0, fy0 = ( float )yi/( float )stride, fy1 = 1.0f-fy0; float height00 = getAltitude( x0, y0 ), height10 = getAltitude( x0+stride,y0 ), height01 = getAltitude( x0,y0+stride ), height11 = getAltitude( x0+stride, y0+stride ); float paintHeight = fx1*fy1 * height00 + fx0*fy1 * height10 + fx1*fy0 * height01 + fx0*fy0 * height11, correctHeight = getAltitude( x, y ); float er = ( float )fabs( correctHeight - paintHeight ); numErrors++; sumError += er; } } } } float error = sumError / numErrors; getVertex(size/2, size/2, errors[_lod].correct); errors[_lod].real = errors[_lod].correct; errors[_lod].real.y += error; errors[_lod].diff = error; } } void TerrainPage::calculateBounds() { int size = owner->getPageSize(); float alt = 0.0f; Vector min( xOffset*scale.x ,0.0f, zOffset*scale.z ), max( (xOffset+1)*scale.x, 0.0f, (zOffset+1)*scale.z); min.y = max.y = getAltitude( 0, 0 ); for ( int x = 0; x < size; ++x ) { for ( int z = 0; z < size; ++z ) { alt = getAltitude( x, z ); min.y = fmin( min.y, alt-1.0f ); max.y = fmax( max.y, alt+1.0f ); } } bounds.set( min, max ); } float TerrainPage::getAltitude( int _x, int _z ) const { assert( _x >= 0 && _x < 17 ); assert( _z >= 0 && _z < 17 ); return position.y+scale.y*owner->getAltitude( _x+(owner->getPageSize()-1)*xOffset, _z+(owner->getPageSize()-1)*zOffset ); } bool TerrainPage::needsRetesselation() { bool leftChanged = ( left && left->isActive() ) && ( left->wantedLOD != left->currentLOD ), rightChanged = ( right && right->isActive() ) && ( right->wantedLOD != right->currentLOD ), topChanged = ( top && top->isActive() ) && ( top->wantedLOD != currentLOD ), bottomChanged = ( bottom && bottom->isActive() ) && ( bottom->wantedLOD != bottom->currentLOD ), iChanged = wantedLOD != currentLOD; return ( leftChanged || rightChanged || topChanged || bottomChanged || forceTesselation || iChanged ); } void TerrainPage::updateTesselation( ) { assert( wantedLOD < 5 && wantedLOD >= 0 ); if ( needsRetesselation() ) { tesselate( wantedLOD ); } currentLOD = wantedLOD; //Check if the page is a level four page. If yes, copy the vertex and index data into //the shared level four page buffers. if ( currentLOD == TerrainPage::MAX_LODS-1 ) { owner->addLevelFourPage( numVertices, vertices, numIndices, indices ); } forceTesselation = false; } void TerrainPage::tesselateTopRow( int _z, int _stride, bool _adaptLeft, bool _adaptRight ) { int halfStride = _stride/2; int xStart = 0, xEnd= owner->getPageSize()-1; int size = xEnd; int z = xEnd-_stride ; addAgain(); addIndex( getIndex( 0, z ) ); assert(halfStride>=1); if( _adaptLeft ) { addIndex( getIndex( 0, z ) ); addIndex( getIndex( 0, z+halfStride ) ); addIndex( getIndex( _stride, z ) ); addIndex( getIndex( 0, size ) ); addIndex( getIndex( halfStride, size ) ); addAgain(); addIndex( getIndex( _stride, z ) ); addIndex( getIndex( _stride, size ) ); xStart = _stride; } if ( _adaptRight ) xEnd -= _stride; for ( int x = xStart; x < xEnd; x+=_stride ) { addIndex( getIndex( x, z ) ); addIndex( getIndex( x, size ) ); addIndex( getIndex( x+_stride, z ) ); addIndex( getIndex( x+halfStride, size ) ); addIndex( getIndex( x+_stride, size ) ); addAgain(); } if ( _adaptRight ) { addIndex( getIndex( xEnd, z ) ); addIndex( getIndex( xEnd, size ) ); addIndex( getIndex( size, z ) ); addIndex( getIndex( xEnd+halfStride, size ) ); addIndex( getIndex( size, size-halfStride ) ); addIndex( getIndex( size, size ) ); } } void TerrainPage::tesselateBottomRow( int _z, int _stride, bool _adaptLeft, bool _adaptRight ) { int halfStride = _stride/2; int xStart=0, xEnd=owner->getPageSize()-1; int size = xEnd; assert( halfStride>=1 ); if ( _adaptLeft ) { addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, _stride ) ); addIndex( getIndex( _stride, 0 ) ); addIndex( getIndex( _stride, _stride ) ); xStart = _stride; } if ( _adaptRight ) xEnd -= _stride; for ( int x = xStart; x < xEnd; x+=_stride ) { addIndex( getIndex( x, 0 ) ); addAgain(); addIndex( getIndex( x+halfStride, 0 ) ); addIndex( getIndex( x, _stride ) ); addIndex( getIndex( x+_stride ,0 ) ); addIndex( getIndex( x+_stride, _stride ) ); } if( _adaptRight ) { addIndex( getIndex( xEnd,0 ) ); addIndex( getIndex( xEnd,_stride ) ); addIndex( getIndex( xEnd+halfStride, 0 ) ); addIndex( getIndex( size ,0 ) ); addAgain(); addIndex( getIndex( xEnd, _stride ) ); addIndex( getIndex( size, halfStride ) ); addIndex( getIndex( size, _stride ) ); } } void TerrainPage::tesselateLevelFourPage( bool _adapt[] ) { assert( indices ); assert( vertices ); numIndices = numVertices = 0; const int halfStride = 8, stride = 16; enum { ADAPT_L = 1, ADAPT_R = 2, ADAPT_B = 4, ADAPT_T = 8, ADAPT_LR = 3, ADAPT_LB = 5, ADAPT_LT = 9, ADAPT_RB = 6, ADAPT_RT = 10, ADAPT_BT = 12, ADAPT_LRB = 7, ADAPT_LBT = 13, ADAPT_LRT = 11, ADAPT_RBT = 14, ADAPT_LRBT = 15, ADAPT_NONE = 0 }; int code = ( _adapt[TP_LEFT] ? ADAPT_L : 0 ) | ( _adapt[TP_RIGHT] ? ADAPT_R : 0 ) | ( _adapt[TP_BOTTOM] ? ADAPT_B : 0 ) | ( _adapt[TP_TOP] ? ADAPT_T : 0 ); switch( code ) { case ADAPT_NONE: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( stride, stride ) ); break; case ADAPT_L: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, stride ) ); addAgain( ); break; case ADAPT_R: addIndex( getIndex( stride, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, 0 ) ); addAgain( ); break; case ADAPT_LR: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( stride, stride ) ); break; case ADAPT_B: addIndex( getIndex( 0, 0 ) ); addAgain(); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( stride, stride ) ); break; case ADAPT_LB: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( stride, stride ) ); break; case ADAPT_RB: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( stride, 0 ) ); addAgain( ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( stride, stride ) ); break; case ADAPT_LRB: addIndex( getIndex( stride, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, 0 ) ); addAgain( ); break; case ADAPT_T: addIndex( getIndex( stride, stride ) ); addAgain( ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( 0, 0 ) ); break; case ADAPT_LT: addIndex( getIndex( stride, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( 0, stride ) ); addAgain( ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( 0, 0 ) ); break; case ADAPT_RT: addIndex( getIndex( stride, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( 0, 0 ) ); break; case ADAPT_LRT: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, stride ) ); addAgain( ); break; case ADAPT_BT: addIndex( getIndex( 0, 0 ) ); addAgain( ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, stride ) ); addAgain( ); break; case ADAPT_LBT: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, stride ) ); addAgain( ); break; case ADAPT_RBT: addIndex( getIndex( stride, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, 0 ) ); addAgain( ); break; case ADAPT_LRBT: addIndex( getIndex( 0, 0 ) ); addIndex( getIndex( 0, halfStride ) ); addIndex( getIndex( halfStride, 0 ) ); addIndex( getIndex( 0, stride ) ); addIndex( getIndex( stride, 0 ) ); addIndex( getIndex( halfStride, stride ) ); addIndex( getIndex( stride, halfStride ) ); addIndex( getIndex( stride, stride ) ); break; } assert( numIndices % 2 == 0 ); } void TerrainPage::tesselate( int _lod ) { memset( indexHash, 0xffff, sizeof(unsigned short)*Terrain::MAX_VERTICES ); numVertices = 0; numIndices = 0; //Calculate the pace, based on the lod. int stride = 1 << _lod; bool adapt[4]; determineBorderAdaption( adapt ); assert( isVisible ); #ifdef USE_VBO if ( _lod == TerrainPage::MAX_LODS-1 ) { if ( vbIdentifier && ibIdentifier ) { owner->getBufferBroker()->release( vbIdentifier, ibIdentifier ); vbIdentifier = ibIdentifier = 0; } if ( !vertices ) vertices = new Vertex[8]; if ( !indices ) indices = new unsigned short[8]; } else { if ( !vbIdentifier && !ibIdentifier ) { owner->getBufferBroker()->acquire( vbIdentifier, ibIdentifier ); if ( vertices ) delete[] vertices; if ( indices ) delete[] indices; vertices = NULL; indices = NULL; } assert( vbIdentifier ); assert( ibIdentifier ); glBindBufferARB( GL_ARRAY_BUFFER_ARB, vbIdentifier ); // The call to glBufferDataARB with a null argument for data is to make things faster. // Then calling call glMapBuffer() tells the driver that the previous // data aren’t valid. As a consequence, if the GPU is still working on them, there won’t // be a conflict because we invalidated these data. The function glMapBuffer() returns a // new pointer that we can use while the GPU is working on the previous set of data.. glBufferDataARB( GL_ARRAY_BUFFER_ARB, Terrain::MAX_VERTICES*sizeof( Vertex ), NULL, GL_DYNAMIC_DRAW_ARB ); vertices = (Vertex*)glMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB ); glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, ibIdentifier ); glBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, Terrain::MAX_INDICES*sizeof( short ), NULL, GL_DYNAMIC_DRAW_ARB ); indices = (unsigned short*)glMapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB ); } #endif assert( indices ); assert( vertices ); if ( _lod == TerrainPage::MAX_LODS-1 ) { tesselateLevelFourPage( adapt ); return; } int zStart = 0, zEnd = owner->getPageSize()-stride; if ( adapt[TP_BOTTOM] ) { tesselateBottomRow( 0, stride, adapt[TP_LEFT], adapt[TP_RIGHT] ); zStart+= stride; } if ( adapt[TP_TOP] ) zEnd-= stride; for ( int z = zStart; z < zEnd; z+=stride ) tesselateRow( z, stride, stride, adapt[TP_LEFT], adapt[TP_RIGHT] ); if ( adapt[TP_TOP] ) { tesselateTopRow( owner->getPageSize()-stride-1, stride, adapt[TP_LEFT], adapt[TP_RIGHT] ); } #ifdef USE_VBO if ( vbIdentifier && ibIdentifier ) { glUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); indices = NULL; glUnmapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB ); vertices = NULL; } #endif } void TerrainPage::show( ) { #ifdef USE_VBO //This is done later on... //owner->getBufferBroker()->acquire( vbIdentifier, ibIdentifier ); vertices = NULL; indices = NULL; #else vertices = new Vertex[Terrain::MAX_VERTICES]; indices = new unsigned short[Terrain::MAX_INDICES]; #endif vbIdentifier = 0; ibIdentifier = 0; indexHash = new unsigned short[Terrain::MAX_VERTICES]; forceTesselation = true; activate(); active = true; } void TerrainPage::hide( ) { #ifdef USE_VBO owner->getBufferBroker()->release( vbIdentifier, ibIdentifier ); #endif SAVE_DELETE_ARRAY( vertices ); numVertices = 0; SAVE_DELETE_ARRAY( indices ); numIndices = 0; deactivate(); } void TerrainPage::drawBox() { glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glTranslatef( bounds.corner.x, bounds.corner.y, bounds.corner.z ); glBegin( GL_QUADS ); glVertex3f( 0.0f, 0.0f, 0.0f ); glVertex3f( bounds.x, 0.0f, 0.0f ); glVertex3f( bounds.x, bounds.y, 0.0f ); glVertex3f( 0.0f, bounds.y, 0.0f ); glVertex3f( 0.0f, 0.0f, bounds.z ); glVertex3f( bounds.x, 0.0f, bounds.z ); glVertex3f( bounds.x, bounds.y, bounds.z ); glVertex3f( 0.0f, bounds.y, bounds.z ); glVertex3f( 0.0f, 0.0f, 0.0 ); glVertex3f( 0.0, 0.0f, bounds.z ); glVertex3f( 0.0f, bounds.y, bounds.z ); glVertex3f( 0.0f, bounds.y, 0.0f ); glVertex3f( bounds.x, 0.0f, 0.0 ); glVertex3f( bounds.x, 0.0f, bounds.z ); glVertex3f( bounds.x, bounds.y, bounds.z ); glVertex3f( bounds.x, bounds.y, 0.0f ); glEnd(); glPopMatrix(); } //TODO: put all the pages that have a coarses tesselation level than 4 into a // separate list! void TerrainPage::draw( ) { assert( glIsEnabled( GL_VERTEX_ARRAY ) ); assert( !glIsEnabled( GL_NORMAL_ARRAY ) ); if ( currentLOD == TerrainPage::MAX_LODS-1 ) return; assert( isVisible ); assert( numIndices > 0 ); active = false; CHECK_GL_ERROR( "1" ); #ifdef USE_VBO glBindBufferARB( GL_ARRAY_BUFFER_ARB, 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, ibIdentifier ); glDrawElements( GL_TRIANGLE_STRIP, numIndices, GL_UNSIGNED_SHORT, NULL ); #else glClientActiveTextureARB( GL_TEXTURE0_ARB ); glInterleavedArrays( GL_T2F_V3F, 0, vertices ); glClientActiveTextureARB( GL_TEXTURE1_ARB ); glInterleavedArrays( GL_T2F_V3F, 0, vertices ); glDrawElements( GL_TRIANGLE_STRIP, numIndices, GL_UNSIGNED_SHORT, indices ); #endif if ( owner->debug() ) drawBox( ); CHECK_GL_ERROR( "2" ); } unsigned short TerrainPage::getIndex( int _x, int _z ) { unsigned short index = _z*owner->getPageSize()+_x; if ( indexHash[index] == 0xffff ) { //The vertex didn't exists before, lets create it. indexHash[index] = numVertices; getVertex( _x, _z, vertices[numVertices].p ); getCoord( _x, _z, vertices[numVertices].t ); numVertices++; } return indexHash[index]; } void TerrainPage::getCoord( int _x, int _z, TexCoord& _coord ) const { owner->getCoord( _x+xOffset*(owner->getPageSize()-1 ), _z+zOffset*(owner->getPageSize()-1 ), _coord ); } void TerrainPage::getVertex( int _x, int _z, Vector& _vertex ) const { _vertex.x = position.x+scale.x*_x/ ( owner->getPageSize()-1 ); _vertex.y = getAltitude( _x, _z ); _vertex.z = position.z+scale.z*_z/( owner->getPageSize()-1 ); } void TerrainPage::setLayerVisibility( int _layer, LayerVisibility _lv ) { if ( hasfull ) return; if ( _lv == LV_FULL ) { for ( int i = 0; i < 8; ++i ) layerVisibility[i] = LV_NO; if ( _layer ) hasfull = true; } layerVisibility[_layer] = _lv; } bool TerrainPage::hasMaterial( int _layer ) { return ( layerVisibility[_layer] != LV_NO ); } void TerrainPage::calculateErrors() { for ( int i = 0; i < TerrainPage::MAX_LODS; ++i ) calculateError( i ); }