/* =========================================================================== Render-world terrain drawing =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "tr_local.h" static byte RB_TerrainWeightToByte( float value ) { return (byte)idMath::ClampInt( 0, 255, (int)floorf( idMath::ClampFloat( 0.0f, 1.0f, value ) * 255.0f + 0.5f ) ); } static void RB_TerrainFillWeightPixels( idTerrainPatch *terrain, int minX, int minY, int uploadWidth, int uploadHeight, idList &pixels ) { pixels.SetNum( uploadWidth * uploadHeight * 4 ); for ( int y = 0; y < uploadHeight; y++ ) { for ( int x = 0; x < uploadWidth; x++ ) { const int sample = terrain->PaintIndex( minX + x, minY + y ); const int pixel = ( y * uploadWidth + x ) * 4; const idVec4 weights = idTerrainPatch::NormalizeLayerWeight( terrain->paintWeights[sample] ); pixels[pixel + 0] = RB_TerrainWeightToByte( weights.x ); pixels[pixel + 1] = RB_TerrainWeightToByte( weights.y ); pixels[pixel + 2] = RB_TerrainWeightToByte( weights.z ); pixels[pixel + 3] = RB_TerrainWeightToByte( weights.w ); } } } static GLuint RB_TerrainUpdateWeightTexture( idTerrainPatch *terrain ) { terrain->EnsureLayerData( "textures/common/caulk" ); bool newTexture = false; if ( terrain->paintTexture == 0 ) { glGenTextures( 1, &terrain->paintTexture ); terrain->MarkPaintTextureDirty(); newTexture = true; } if ( !terrain->paintTextureDirty ) { return terrain->paintTexture; } const int minX = idMath::ClampInt( 0, terrain->paintWidth - 1, terrain->paintDirtyMinX ); const int minY = idMath::ClampInt( 0, terrain->paintHeight - 1, terrain->paintDirtyMinY ); const int maxX = idMath::ClampInt( 0, terrain->paintWidth - 1, terrain->paintDirtyMaxX ); const int maxY = idMath::ClampInt( 0, terrain->paintHeight - 1, terrain->paintDirtyMaxY ); const bool validRect = maxX >= minX && maxY >= minY; const bool fullUpload = newTexture || !validRect || ( minX == 0 && minY == 0 && maxX == terrain->paintWidth - 1 && maxY == terrain->paintHeight - 1 ); const int uploadX = fullUpload ? 0 : minX; const int uploadY = fullUpload ? 0 : minY; const int uploadWidth = fullUpload ? terrain->paintWidth : ( maxX - minX + 1 ); const int uploadHeight = fullUpload ? terrain->paintHeight : ( maxY - minY + 1 ); GL_SelectTexture( 0 ); glBindTexture( GL_TEXTURE_2D, terrain->paintTexture ); backEnd.glState.tmu[backEnd.glState.currenttmu].current2DMap = terrain->paintTexture; backEnd.glState.tmu[backEnd.glState.currenttmu].textureType = TT_2D; glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); idList pixels; RB_TerrainFillWeightPixels( terrain, uploadX, uploadY, uploadWidth, uploadHeight, pixels ); if ( fullUpload ) { glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, terrain->paintWidth, terrain->paintHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.Ptr() ); } else { glTexSubImage2D( GL_TEXTURE_2D, 0, uploadX, uploadY, uploadWidth, uploadHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels.Ptr() ); } terrain->ClearPaintTextureDirtyRect(); return terrain->paintTexture; } static const idMaterial *RB_TerrainLayerMaterial( const char *materialName ) { if ( !materialName || !materialName[0] ) { return NULL; } const idMaterial *material = declManager->FindMaterial( materialName, false ); if ( !material || material->TestMaterialFlag( MF_DEFAULTED ) ) { return NULL; } return material; } static idImage *RB_TerrainLayerDiffuseImage( const char *materialName ) { const idMaterial *material = RB_TerrainLayerMaterial( materialName ); if ( !material ) { return NULL; } idImage *image = material->GetDiffuseImage( NULL ); return image ? image : material->GetEditorImage(); } static idImage *RB_TerrainLayerNormalImage( const char *materialName ) { idImage *flatNormal = globalImages->flatNormalMap ? globalImages->flatNormalMap : globalImages->whiteImage; const idMaterial *material = RB_TerrainLayerMaterial( materialName ); if ( !material ) { return flatNormal; } const shaderStage_t *bumpStage = material->GetBumpStage(); if ( !bumpStage || !bumpStage->texture.image ) { return flatNormal; } return bumpStage->texture.image; } static void RB_TerrainBindLayerTextures( idTerrainPatch *terrain ) { terrain->EnsureLayerData( "textures/common/caulk" ); idImage *baseImage = RB_TerrainLayerDiffuseImage( terrain->layerMaterials[0].c_str() ); if ( !baseImage ) { baseImage = globalImages->whiteImage; } for ( int layer = 0; layer < TERRAIN_MAX_LAYERS; layer++ ) { idImage *image = RB_TerrainLayerDiffuseImage( terrain->layerMaterials[layer].c_str() ); if ( !image ) { image = baseImage; } GL_SelectTexture( 0 ); image->Bind(); glTerrainLayerTextureQD3D12( layer, image->texnum ); idImage *normalImage = RB_TerrainLayerNormalImage( terrain->layerMaterials[layer].c_str() ); if ( !normalImage ) { normalImage = globalImages->flatNormalMap ? globalImages->flatNormalMap : globalImages->whiteImage; } normalImage->Bind(); glTerrainLayerNormalTextureQD3D12( layer, normalImage->texnum ); } glTerrainWeightTextureQD3D12( RB_TerrainUpdateWeightTexture( terrain ) ); } static void RB_TerrainClearLayerTextureBindings() { glTerrainBlendEnableQD3D12( GL_FALSE ); glTerrainBlendOpacityQD3D12( 1.0f ); glTerrainWeightTextureQD3D12( 0 ); for ( int layer = 0; layer < TERRAIN_MAX_LAYERS; layer++ ) { glTerrainLayerTextureQD3D12( layer, 0 ); glTerrainLayerNormalTextureQD3D12( layer, 0 ); } } static void RB_TerrainSubmitVertex( const idTerrainPatch *terrain, int x, int y ) { idVec3 vertex = terrain->Vertex( x, y ); idVec3 normal = terrain->VertexNormal( x, y ); const float u = (float)x / (float)( terrain->width - 1 ); const float v = (float)y / (float)( terrain->height - 1 ); const float layerS = terrain->WorldWidth() * u / 256.0f; const float layerT = terrain->WorldHeight() * v / 256.0f; const int paintX = idMath::ClampInt( 0, terrain->paintWidth - 1, (int)floorf( u * (float)( terrain->paintWidth - 1 ) + 0.5f ) ); const int paintY = idMath::ClampInt( 0, terrain->paintHeight - 1, (int)floorf( v * (float)( terrain->paintHeight - 1 ) + 0.5f ) ); const idVec4 weights = idTerrainPatch::NormalizeLayerWeight( terrain->SamplePaintWeight( paintX, paintY ) ); glColor4fv( weights.ToFloatPtr() ); glNormal3fv( normal.ToFloatPtr() ); glMultiTexCoord2fARB( GL_TEXTURE0_ARB, layerS, layerT ); glMultiTexCoord2fARB( GL_TEXTURE1_ARB, u, v ); glVertex3fv( vertex.ToFloatPtr() ); } static bool RB_TerrainCullPatch( const idTerrainPatch *terrain, const viewDef_t *viewDef ) { if ( !terrain || !viewDef ) { return true; } return R_CullLocalBox( terrain->bounds, viewDef->worldSpace.modelMatrix, 5, viewDef->frustum ); } static void RB_TerrainDrawPatch( idTerrainPatch *terrain ) { if ( !terrain || terrain->width < 2 || terrain->height < 2 ) { return; } RB_TerrainBindLayerTextures( terrain ); glTerrainBlendEnableQD3D12( GL_TRUE ); glTerrainBlendOpacityQD3D12( 1.0f ); GL_State( GLS_DEPTHFUNC_LESS ); GL_Cull( CT_TWO_SIDED ); GL_SelectTexture( 0 ); glEnable( GL_TEXTURE_2D ); glEnable( GL_DEPTH_TEST ); glDepthMask( GL_TRUE ); glEnable( GL_BLEND ); for ( int y = 0; y < terrain->height - 1; y++ ) { glBegin( GL_TRIANGLE_STRIP ); for ( int x = 0; x < terrain->width; x++ ) { RB_TerrainSubmitVertex( terrain, x, y ); RB_TerrainSubmitVertex( terrain, x, y + 1 ); } glEnd(); } RB_TerrainClearLayerTextureBindings(); } void RB_DrawTerrain( const viewDef_t *viewDef ) { if ( r_skipTerrain.GetBool() || !viewDef || !viewDef->renderWorld || !viewDef->viewEntitys ) { return; } idRenderWorldLocal *world = viewDef->renderWorld; if ( world->terrainModel.NumTerrains() <= 0 ) { return; } RB_LogComment( "---------- RB_DrawTerrain ----------\n" ); glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( viewDef->worldSpace.modelViewMatrix ); backEnd.currentSpace = &viewDef->worldSpace; for ( int i = 0; i < world->terrainModel.NumTerrains(); i++ ) { idTerrainPatch *terrain = world->terrainModel.Terrain( i ); if ( !terrain || RB_TerrainCullPatch( terrain, viewDef ) ) { continue; } RB_TerrainDrawPatch( terrain ); } GL_Cull( CT_FRONT_SIDED ); glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); glEnable( GL_BLEND ); GL_SelectTexture( 0 ); globalImages->BindNull(); GL_ClearStateDelta(); }