/* =========================================================================== Shared terrain source model =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "TerrainModel.h" idTerrainPatch::idTerrainPatch() { name.Clear(); material.Clear(); origin.Zero(); width = TERRAIN_DEFAULT_VERTS; height = TERRAIN_DEFAULT_VERTS; cellSize = TERRAIN_DEFAULT_CELL_SIZE; paintWidth = TERRAIN_DEFAULT_PAINT_SIZE; paintHeight = TERRAIN_DEFAULT_PAINT_SIZE; paintTexture = 0; paintTextureDirty = true; paintDirtyMinX = 0; paintDirtyMinY = 0; paintDirtyMaxX = paintWidth - 1; paintDirtyMaxY = paintHeight - 1; bounds.Clear(); } int idTerrainPatch::SampleIndex( int x, int y ) const { return y * width + x; } int idTerrainPatch::PaintIndex( int x, int y ) const { return y * paintWidth + x; } float idTerrainPatch::SampleHeight( int x, int y ) const { return heights[SampleIndex( x, y )]; } void idTerrainPatch::SetHeight( int x, int y, float height ) { heights[SampleIndex( x, y )] = height; } bool idTerrainPatch::SetHeightBySample( int sample, float height ) { if ( sample < 0 || sample >= heights.Num() ) { return false; } heights[sample] = height; return true; } idVec4 idTerrainPatch::SamplePaintWeight( int x, int y ) const { return paintWeights[PaintIndex( x, y )]; } void idTerrainPatch::SetPaintWeight( int x, int y, const idVec4 &weight ) { paintWeights[PaintIndex( x, y )] = NormalizeLayerWeight( weight ); MarkPaintTextureDirtyRect( x, y, x, y ); } bool idTerrainPatch::SetPaintWeightBySample( int sample, const idVec4 &weight ) { if ( sample < 0 || sample >= paintWeights.Num() || paintWidth <= 0 ) { return false; } paintWeights[sample] = NormalizeLayerWeight( weight ); MarkPaintTextureDirtyRect( sample % paintWidth, sample / paintWidth, sample % paintWidth, sample / paintWidth ); return true; } idVec4 idTerrainPatch::BaseLayerWeight() { return idVec4( 1.0f, 0.0f, 0.0f, 0.0f ); } idVec4 idTerrainPatch::NormalizeLayerWeight( const idVec4 &weight ) { idVec4 normalized; normalized.x = weight.x > 0.0f ? weight.x : 0.0f; normalized.y = weight.y > 0.0f ? weight.y : 0.0f; normalized.z = weight.z > 0.0f ? weight.z : 0.0f; normalized.w = weight.w > 0.0f ? weight.w : 0.0f; const float total = normalized.x + normalized.y + normalized.z + normalized.w; if ( total <= 0.0001f ) { return BaseLayerWeight(); } const float invTotal = 1.0f / total; normalized.x *= invTotal; normalized.y *= invTotal; normalized.z *= invTotal; normalized.w *= invTotal; return normalized; } float idTerrainPatch::WorldWidth() const { return ( width - 1 ) * cellSize; } float idTerrainPatch::WorldHeight() const { return ( height - 1 ) * cellSize; } float idTerrainPatch::BilinearHeight( float u, float v ) const { u = idMath::ClampFloat( 0.0f, 1.0f, u ); v = idMath::ClampFloat( 0.0f, 1.0f, v ); const float fx = u * ( width - 1 ); const float fy = v * ( height - 1 ); const int x0 = idMath::ClampInt( 0, width - 1, (int)floorf( fx ) ); const int y0 = idMath::ClampInt( 0, height - 1, (int)floorf( fy ) ); const int x1 = idMath::ClampInt( 0, width - 1, x0 + 1 ); const int y1 = idMath::ClampInt( 0, height - 1, y0 + 1 ); const float tx = fx - (float)x0; const float ty = fy - (float)y0; const float h00 = SampleHeight( x0, y0 ); const float h10 = SampleHeight( x1, y0 ); const float h01 = SampleHeight( x0, y1 ); const float h11 = SampleHeight( x1, y1 ); const float h0 = h00 + ( h10 - h00 ) * tx; const float h1 = h01 + ( h11 - h01 ) * tx; return h0 + ( h1 - h0 ) * ty; } idVec3 idTerrainPatch::VertexUV( float u, float v ) const { idVec3 p; p.x = origin.x + WorldWidth() * u; p.y = origin.y + WorldHeight() * v; p.z = origin.z + BilinearHeight( u, v ); return p; } idVec3 idTerrainPatch::Vertex( int x, int y ) const { idVec3 v; v.x = origin.x + x * cellSize; v.y = origin.y + y * cellSize; v.z = origin.z + SampleHeight( x, y ); return v; } idVec3 idTerrainPatch::VertexNormal( int x, int y ) const { const int x0 = idMath::ClampInt( 0, width - 1, x - 1 ); const int x1 = idMath::ClampInt( 0, width - 1, x + 1 ); const int y0 = idMath::ClampInt( 0, height - 1, y - 1 ); const int y1 = idMath::ClampInt( 0, height - 1, y + 1 ); const idVec3 sx = Vertex( x1, y ) - Vertex( x0, y ); const idVec3 sy = Vertex( x, y1 ) - Vertex( x, y0 ); idVec3 normal = sx.Cross( sy ); if ( normal.LengthSqr() <= 0.0001f ) { normal.Set( 0.0f, 0.0f, 1.0f ); } else { normal.Normalize(); } return normal; } void idTerrainPatch::UpdateBounds() { bounds.Clear(); if ( width <= 0 || height <= 0 ) { bounds.AddPoint( origin ); return; } for ( int y = 0; y < height; y++ ) { for ( int x = 0; x < width; x++ ) { bounds.AddPoint( Vertex( x, y ) ); } } } void idTerrainPatch::EnsureLayerData( const char *defaultMaterial ) { if ( material.IsEmpty() ) { material = defaultMaterial && defaultMaterial[0] ? defaultMaterial : "textures/common/caulk"; } if ( layerMaterials[0].IsEmpty() ) { layerMaterials[0] = material; } material = layerMaterials[0]; if ( width < 2 ) { width = 2; } if ( height < 2 ) { height = 2; } const int heightSampleCount = width * height; const int oldHeightCount = heights.Num(); heights.SetNum( heightSampleCount ); for ( int i = oldHeightCount; i < heightSampleCount; i++ ) { heights[i] = 0.0f; } if ( paintWidth < 2 ) { paintWidth = TERRAIN_DEFAULT_PAINT_SIZE; } if ( paintHeight < 2 ) { paintHeight = TERRAIN_DEFAULT_PAINT_SIZE; } const int paintSampleCount = paintWidth * paintHeight; if ( paintSampleCount <= 0 ) { paintWeights.Clear(); return; } const int oldCount = paintWeights.Num(); paintWeights.SetNum( paintSampleCount ); if ( oldCount != paintSampleCount ) { MarkPaintTextureDirty(); } for ( int i = 0; i < paintSampleCount; i++ ) { if ( i >= oldCount ) { paintWeights[i] = BaseLayerWeight(); } else { paintWeights[i] = NormalizeLayerWeight( paintWeights[i] ); } } UpdateBounds(); } void idTerrainPatch::ClearLayerWeight( int layer ) { if ( layer < 0 || layer >= TERRAIN_MAX_LAYERS ) { return; } for ( int i = 0; i < paintWeights.Num(); i++ ) { idVec4 weight = paintWeights[i]; weight[layer] = 0.0f; paintWeights[i] = NormalizeLayerWeight( weight ); } MarkPaintTextureDirty(); } bool idTerrainPatch::SetLayerMaterials( const idStr newLayerMaterials[TERRAIN_MAX_LAYERS], const char *defaultMaterial ) { bool changed = false; for ( int layer = 0; layer < TERRAIN_MAX_LAYERS; layer++ ) { idStr materialName = newLayerMaterials[layer]; materialName.StripLeading( ' ' ); materialName.StripTrailing( ' ' ); if ( layer == 0 && materialName.IsEmpty() ) { materialName = defaultMaterial && defaultMaterial[0] ? defaultMaterial : "textures/common/caulk"; } if ( layerMaterials[layer].Icmp( materialName.c_str() ) == 0 ) { continue; } layerMaterials[layer] = materialName; if ( layer > 0 && materialName.IsEmpty() ) { ClearLayerWeight( layer ); } changed = true; } material = layerMaterials[0]; return changed; } void idTerrainPatch::MarkPaintTextureDirty() { paintTextureDirty = true; paintDirtyMinX = 0; paintDirtyMinY = 0; paintDirtyMaxX = paintWidth > 0 ? paintWidth - 1 : 0; paintDirtyMaxY = paintHeight > 0 ? paintHeight - 1 : 0; } void idTerrainPatch::ClearPaintTextureDirtyRect() { paintTextureDirty = false; paintDirtyMinX = paintWidth; paintDirtyMinY = paintHeight; paintDirtyMaxX = -1; paintDirtyMaxY = -1; } void idTerrainPatch::MarkPaintTextureDirtyRect( int minX, int minY, int maxX, int maxY ) { if ( paintWidth <= 0 || paintHeight <= 0 ) { return; } minX = idMath::ClampInt( 0, paintWidth - 1, minX ); maxX = idMath::ClampInt( 0, paintWidth - 1, maxX ); minY = idMath::ClampInt( 0, paintHeight - 1, minY ); maxY = idMath::ClampInt( 0, paintHeight - 1, maxY ); if ( maxX < minX || maxY < minY ) { return; } if ( !paintTextureDirty || paintDirtyMaxX < paintDirtyMinX || paintDirtyMaxY < paintDirtyMinY ) { paintDirtyMinX = minX; paintDirtyMinY = minY; paintDirtyMaxX = maxX; paintDirtyMaxY = maxY; } else { if ( minX < paintDirtyMinX ) { paintDirtyMinX = minX; } if ( minY < paintDirtyMinY ) { paintDirtyMinY = minY; } if ( maxX > paintDirtyMaxX ) { paintDirtyMaxX = maxX; } if ( maxY > paintDirtyMaxY ) { paintDirtyMaxY = maxY; } } paintTextureDirty = true; } idTerrainModel::idTerrainModel() { } idTerrainModel::~idTerrainModel() { Clear(); } void idTerrainModel::Clear() { terrains.DeleteContents( true ); } int idTerrainModel::NumTerrains() const { return terrains.Num(); } idTerrainPatch *idTerrainModel::Terrain( int index ) { if ( index < 0 || index >= terrains.Num() ) { return NULL; } return terrains[index]; } const idTerrainPatch *idTerrainModel::Terrain( int index ) const { if ( index < 0 || index >= terrains.Num() ) { return NULL; } return terrains[index]; } idList &idTerrainModel::Terrains() { return terrains; } const idList &idTerrainModel::Terrains() const { return terrains; } static float TerrainModel_SnapToGrid( float value, float gridSize ) { if ( gridSize <= 0.0f ) { gridSize = 1.0f; } return floorf( value / gridSize + ( value >= 0.0f ? 0.5f : -0.5f ) ) * gridSize; } idTerrainPatch *idTerrainModel::AllocDefaultTerrain( const idVec3 ¢er, const char *defaultMaterial, float gridSize ) { idTerrainPatch *terrain = new idTerrainPatch; terrain->name = va( "terrain_%i", terrains.Num() + 1 ); terrain->material = defaultMaterial && defaultMaterial[0] ? defaultMaterial : "textures/common/caulk"; terrain->layerMaterials[0] = terrain->material; const float worldSize = ( terrain->width - 1 ) * terrain->cellSize; terrain->origin.x = TerrainModel_SnapToGrid( center.x - worldSize * 0.5f, gridSize ); terrain->origin.y = TerrainModel_SnapToGrid( center.y - worldSize * 0.5f, gridSize ); terrain->origin.z = TerrainModel_SnapToGrid( center.z, gridSize ); terrain->heights.SetNum( terrain->width * terrain->height ); for ( int i = 0; i < terrain->heights.Num(); i++ ) { terrain->heights[i] = 0.0f; } terrain->EnsureLayerData( defaultMaterial ); return terrain; } void idTerrainModel::AddTerrain( idTerrainPatch *terrain ) { if ( terrain ) { terrains.Append( terrain ); } } idStr idTerrainModel::FileNameForMap( const char *mapFileName ) { idStr fileName = mapFileName ? mapFileName : ""; fileName.StripFileExtension(); fileName.SetFileExtension( ".terrain" ); return fileName; } bool idTerrainModel::SaveFile( const char *filename ) { if ( !filename || !filename[0] ) { return false; } const bool osPath = strstr( filename, ":" ) != NULL; idFile *file = osPath ? fileSystem->OpenExplicitFileWrite( filename ) : fileSystem->OpenFileWrite( filename ); if ( !file ) { common->Warning( "idTerrainModel::SaveFile: couldn't write %s", filename ); return false; } file->WriteFloatString( "terrainAuthoring 3 {\n" ); for ( int i = 0; i < terrains.Num(); i++ ) { idTerrainPatch *terrain = terrains[i]; if ( !terrain ) { continue; } terrain->EnsureLayerData( "textures/common/caulk" ); file->WriteFloatString( "\tterrain {\n" ); file->WriteFloatString( "\t\tname \"%s\"\n", terrain->name.c_str() ); file->WriteFloatString( "\t\tmaterial \"%s\"\n", terrain->material.c_str() ); for ( int layer = 0; layer < TERRAIN_MAX_LAYERS; layer++ ) { if ( layer == 0 || !terrain->layerMaterials[layer].IsEmpty() ) { file->WriteFloatString( "\t\tlayer %i \"%s\"\n", layer, terrain->layerMaterials[layer].c_str() ); } } file->WriteFloatString( "\t\torigin ( %f %f %f )\n", terrain->origin.x, terrain->origin.y, terrain->origin.z ); file->WriteFloatString( "\t\tsize %i %i\n", terrain->width, terrain->height ); file->WriteFloatString( "\t\tcellSize %f\n", terrain->cellSize ); file->WriteFloatString( "\t\tpaintSize %i %i\n", terrain->paintWidth, terrain->paintHeight ); file->WriteFloatString( "\t\theights {\n" ); for ( int y = 0; y < terrain->height; y++ ) { file->WriteFloatString( "\t\t\t" ); for ( int x = 0; x < terrain->width; x++ ) { file->WriteFloatString( "%f ", terrain->SampleHeight( x, y ) ); } file->WriteFloatString( "\n" ); } file->WriteFloatString( "\t\t}\n" ); file->WriteFloatString( "\t\tpaintWeights {\n" ); for ( int y = 0; y < terrain->paintHeight; y++ ) { file->WriteFloatString( "\t\t\t" ); for ( int x = 0; x < terrain->paintWidth; x++ ) { const idVec4 weights = terrain->SamplePaintWeight( x, y ); file->WriteFloatString( "%f %f %f %f ", weights.x, weights.y, weights.z, weights.w ); } file->WriteFloatString( "\n" ); } file->WriteFloatString( "\t\t}\n" ); file->WriteFloatString( "\t}\n" ); } file->WriteFloatString( "}\n" ); fileSystem->CloseFile( file ); return true; } bool idTerrainModel::LoadFile( const char *filename, const char *defaultMaterial ) { Clear(); if ( !filename || !filename[0] ) { return false; } const char *fallbackMaterial = defaultMaterial && defaultMaterial[0] ? defaultMaterial : "textures/common/caulk"; const bool osPath = strstr( filename, ":" ) != NULL; idLexer src( LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE | LEXFL_ALLOWPATHNAMES ); src.LoadFile( filename, osPath ); if ( !src.IsLoaded() ) { return false; } idToken token; if ( !src.ReadToken( &token ) || token.Icmp( "terrainAuthoring" ) ) { return false; } src.ReadToken( &token ); if ( token != "{" ) { src.ExpectTokenString( "{" ); } while ( src.ReadToken( &token ) ) { if ( token == "}" ) { break; } if ( token.Icmp( "terrain" ) ) { break; } idTerrainPatch *terrain = new idTerrainPatch; terrain->name = va( "terrain_%i", terrains.Num() + 1 ); terrain->material = fallbackMaterial; terrain->layerMaterials[0] = terrain->material; terrain->MarkPaintTextureDirty(); src.ExpectTokenString( "{" ); while ( src.ReadToken( &token ) ) { if ( token == "}" ) { break; } if ( !token.Icmp( "name" ) ) { src.ExpectAnyToken( &token ); terrain->name = token; } else if ( !token.Icmp( "material" ) ) { src.ExpectAnyToken( &token ); terrain->material = token; terrain->layerMaterials[0] = token; } else if ( !token.Icmp( "layer" ) ) { const int layer = src.ParseInt(); src.ExpectAnyToken( &token ); if ( layer >= 0 && layer < TERRAIN_MAX_LAYERS ) { terrain->layerMaterials[layer] = token; if ( layer == 0 ) { terrain->material = token; } } } else if ( !token.Icmp( "origin" ) ) { src.Parse1DMatrix( 3, terrain->origin.ToFloatPtr() ); } else if ( !token.Icmp( "size" ) ) { terrain->width = src.ParseInt(); terrain->height = src.ParseInt(); if ( terrain->width < 2 ) { terrain->width = 2; } if ( terrain->height < 2 ) { terrain->height = 2; } } else if ( !token.Icmp( "cellSize" ) ) { terrain->cellSize = src.ParseFloat(); if ( terrain->cellSize <= 0.0f ) { terrain->cellSize = TERRAIN_DEFAULT_CELL_SIZE; } } else if ( !token.Icmp( "paintSize" ) ) { terrain->paintWidth = src.ParseInt(); terrain->paintHeight = src.ParseInt(); if ( terrain->paintWidth < 2 ) { terrain->paintWidth = 2; } if ( terrain->paintHeight < 2 ) { terrain->paintHeight = 2; } } else if ( !token.Icmp( "heights" ) ) { src.ExpectTokenString( "{" ); terrain->heights.SetNum( terrain->width * terrain->height ); for ( int i = 0; i < terrain->heights.Num(); i++ ) { terrain->heights[i] = 0.0f; } int sample = 0; while ( src.ReadToken( &token ) ) { if ( token == "}" ) { break; } if ( sample < terrain->heights.Num() ) { terrain->heights[sample++] = (float)atof( token.c_str() ); } } } else if ( !token.Icmp( "weights" ) || !token.Icmp( "paintWeights" ) ) { const bool legacyWeights = !token.Icmp( "weights" ); src.ExpectTokenString( "{" ); if ( legacyWeights && terrain->paintWidth == TERRAIN_DEFAULT_PAINT_SIZE && terrain->paintHeight == TERRAIN_DEFAULT_PAINT_SIZE ) { terrain->paintWidth = terrain->width; terrain->paintHeight = terrain->height; } terrain->paintWeights.SetNum( terrain->paintWidth * terrain->paintHeight ); for ( int i = 0; i < terrain->paintWeights.Num(); i++ ) { terrain->paintWeights[i] = idTerrainPatch::BaseLayerWeight(); } int sample = 0; int component = 0; idVec4 weight; weight.Zero(); while ( src.ReadToken( &token ) ) { if ( token == "}" ) { break; } weight[component++] = (float)atof( token.c_str() ); if ( component == 4 ) { if ( sample < terrain->paintWeights.Num() ) { terrain->paintWeights[sample] = idTerrainPatch::NormalizeLayerWeight( weight ); } sample++; component = 0; weight.Zero(); } } } } terrain->EnsureLayerData( fallbackMaterial ); terrain->MarkPaintTextureDirty(); terrain->UpdateBounds(); terrains.Append( terrain ); } return true; }