//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $Workfile: $ // $Date: $ // $NoKeywords: $ //=============================================================================// #include "render_pch.h" #include "modelloader.h" #include "gl_model_private.h" #include "gl_lightmap.h" #include "disp.h" #include "mathlib/mathlib.h" #include "gl_rsurf.h" #include "gl_matsysiface.h" #include "zone.h" #include "materialsystem/imesh.h" #include "materialsystem/ivballoctracker.h" #include "mathlib/vector.h" #include "iscratchpad3d.h" #include "tier0/fasttimer.h" #include "lowpassstream.h" #include "con_nprint.h" #include "tier2/tier2.h" #include "tier0/dbg.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" void BuildTagData( CCoreDispInfo *pCoreDisp, CDispInfo *pDisp ); void SmoothDispSurfNormals( CCoreDispInfo **ppListBase, int nListSize ); // This makes sure that whoever is creating and deleting CDispInfos frees them all // (and calls their destructors) before the module is gone. class CConstructorChecker { public: CConstructorChecker() {m_nConstructedObjects = 0;} ~CConstructorChecker() {Assert(m_nConstructedObjects == 0);} int m_nConstructedObjects; } g_ConstructorChecker; //----------------------------------------------------------------------------- // Static helpers. //----------------------------------------------------------------------------- static void BuildDispGetSurfNormals( Vector points[4], Vector normals[4] ) { // // calculate the displacement surface normal // Vector tmp[2]; Vector normal; tmp[0] = points[1] - points[0]; tmp[1] = points[3] - points[0]; normal = tmp[1].Cross( tmp[0] ); VectorNormalize( normal ); for( int i = 0; i < 4; i++ ) { normals[i] = normal; } } static bool FindExtraDependency( unsigned short *pDependencies, int nDependencies, int iDisp ) { for( int i=0; i < nDependencies; i++ ) { if ( pDependencies[i] == iDisp ) return true; } return false; } static CDispGroup* FindCombo( CUtlVector &combos, int idLMPage, IMaterial *pMaterial ) { for( int i=0; i < combos.Size(); i++ ) { if( combos[i]->m_LightmapPageID == idLMPage && combos[i]->m_pMaterial == pMaterial ) return combos[i]; } return NULL; } static CDispGroup* AddCombo( CUtlVector &combos, int idLMPage, IMaterial *pMaterial ) { CDispGroup *pCombo = new CDispGroup; pCombo->m_LightmapPageID = idLMPage; pCombo->m_pMaterial = pMaterial; pCombo->m_nVisible = 0; combos.AddToTail( pCombo ); return pCombo; } static inline CDispInfo* GetModelDisp( model_t const *pWorld, int i ) { return static_cast< CDispInfo* >( DispInfo_IndexArray( pWorld->brush.pShared->hDispInfos, i ) ); } static void BuildDispSurfInit( model_t *pWorld, CCoreDispInfo *pBuildDisp, SurfaceHandle_t worldSurfID ) { if( !IS_SURF_VALID( worldSurfID ) ) return; ASSERT_SURF_VALID( worldSurfID ); Vector surfPoints[4]; Vector surfNormals[4]; Vector2D surfTexCoords[4]; Vector2D surfLightCoords[4][4]; if ( MSurf_VertCount( worldSurfID ) != 4 ) return; #ifndef SWDS BuildMSurfaceVerts( pWorld->brush.pShared, worldSurfID, surfPoints, surfTexCoords, surfLightCoords ); #endif BuildDispGetSurfNormals( surfPoints, surfNormals ); CCoreDispSurface *pDispSurf = pBuildDisp->GetSurface(); int surfFlag = pDispSurf->GetFlags(); int nLMVects = 1; if( MSurf_Flags( worldSurfID ) & SURFDRAW_BUMPLIGHT ) { surfFlag |= CCoreDispInfo::SURF_BUMPED; nLMVects = NUM_BUMP_VECTS + 1; } pDispSurf->SetPointCount( 4 ); for( int i = 0; i < 4; i++ ) { pDispSurf->SetPoint( i, surfPoints[i] ); pDispSurf->SetPointNormal( i, surfNormals[i] ); pDispSurf->SetTexCoord( i, surfTexCoords[i] ); for( int j = 0; j < nLMVects; j++ ) { pDispSurf->SetLuxelCoord( j, i, surfLightCoords[i][j] ); } } Vector vecS = MSurf_TexInfo( worldSurfID )->textureVecsTexelsPerWorldUnits[0].AsVector3D(); Vector vecT = MSurf_TexInfo( worldSurfID )->textureVecsTexelsPerWorldUnits[1].AsVector3D(); VectorNormalize( vecS ); VectorNormalize( vecT ); pDispSurf->SetSAxis( vecS ); pDispSurf->SetTAxis( vecT ); pDispSurf->SetFlags( surfFlag ); pDispSurf->FindSurfPointStartIndex(); pDispSurf->AdjustSurfPointData(); #ifndef SWDS // // adjust the lightmap coordinates -- this is currently done redundantly! // the will be fixed correctly when the displacement common code is written. // This is here to get things running for (GDC, E3) // SurfaceCtx_t ctx; SurfSetupSurfaceContext( ctx, worldSurfID ); int lightmapWidth = MSurf_LightmapExtents( worldSurfID )[0]; int lightmapHeight = MSurf_LightmapExtents( worldSurfID )[1]; Vector2D uv( 0.0f, 0.0f ); for ( int ndxLuxel = 0; ndxLuxel < 4; ndxLuxel++ ) { switch( ndxLuxel ) { case 0: { uv.Init( 0.0f, 0.0f ); break; } case 1: { uv.Init( 0.0f, ( float )lightmapHeight ); break; } case 2: { uv.Init( ( float )lightmapWidth, ( float )lightmapHeight ); break; } case 3: { uv.Init( ( float )lightmapWidth, 0.0f ); break; } } uv.x += 0.5f; uv.y += 0.5f; uv *= ctx.m_Scale; uv += ctx.m_Offset; pDispSurf->SetLuxelCoord( 0, ndxLuxel, uv ); } #endif } VertexFormat_t ComputeDisplacementStaticMeshVertexFormat( const IMaterial * pMaterial, const CDispGroup *pCombo, const ddispinfo_t *pMapDisps ) { VertexFormat_t vertexFormat = pMaterial->GetVertexFormat(); // FIXME: set VERTEX_FORMAT_COMPRESSED if there are no artifacts and if it saves enough memory (use 'mem_dumpvballocs') vertexFormat &= ~VERTEX_FORMAT_COMPRESSED; // FIXME: check for and strip unused vertex elements (TANGENT_S/T?) return vertexFormat; } void AddEmptyMesh( model_t *pWorld, CDispGroup *pCombo, const ddispinfo_t *pMapDisps, int *pDispInfos, int nDisps, int nTotalVerts, int nTotalIndices ) { CMatRenderContextPtr pRenderContext( materials ); CGroupMesh *pMesh = new CGroupMesh; pCombo->m_Meshes.AddToTail( pMesh ); VertexFormat_t vertexFormat = ComputeDisplacementStaticMeshVertexFormat( pCombo->m_pMaterial, pCombo, pMapDisps ); pMesh->m_pMesh = pRenderContext->CreateStaticMesh( vertexFormat, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_DISP ); pMesh->m_pGroup = pCombo; pMesh->m_nVisible = 0; CMeshBuilder builder; builder.Begin( pMesh->m_pMesh, MATERIAL_TRIANGLES, nTotalVerts, nTotalIndices ); // Just advance the verts and indices and leave the data blank for now. builder.AdvanceIndices( nTotalIndices ); builder.AdvanceVertices( nTotalVerts ); builder.End(); pMesh->m_DispInfos.SetSize( nDisps ); pMesh->m_Visible.SetSize( nDisps ); pMesh->m_VisibleDisps.SetSize( nDisps ); int iVertOffset = 0; int iIndexOffset = 0; for( int iDisp=0; iDisp < nDisps; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, pDispInfos[iDisp] ); const ddispinfo_t *pMapDisp = &pMapDisps[ pDispInfos[iDisp] ]; pDisp->m_pMesh = pMesh; pDisp->m_iVertOffset = iVertOffset; pDisp->m_iIndexOffset = iIndexOffset; int nVerts, nIndices; CalcMaxNumVertsAndIndices( pMapDisp->power, &nVerts, &nIndices ); iVertOffset += nVerts; iIndexOffset += nIndices; pMesh->m_DispInfos[iDisp] = pDisp; } Assert( iVertOffset == nTotalVerts ); Assert( iIndexOffset == nTotalIndices ); } void FillStaticBuffer( CGroupMesh *pMesh, CDispInfo *pDisp, const CCoreDispInfo *pCoreDisp, const CDispVert *pVerts, int nLightmaps ) { #ifndef SWDS // Put the verts into the buffer. int nVerts, nIndices; CalcMaxNumVertsAndIndices( pDisp->GetPower(), &nVerts, &nIndices ); CMeshBuilder builder; builder.BeginModify( pMesh->m_pMesh, pDisp->m_iVertOffset, nVerts, 0, 0 ); SurfaceCtx_t ctx; SurfSetupSurfaceContext( ctx, pDisp->GetParent() ); for( int i=0; i < nVerts; i++ ) { // NOTE: position comes from our system-memory buffer so when you're restoring // static buffers (from alt+tab), it includes changes from terrain mods. const Vector &vPos = pCoreDisp->GetVert( i ); builder.Position3f( vPos.x, vPos.y, vPos.z ); const Vector &vNormal = pCoreDisp->GetNormal( i ); builder.Normal3f( vNormal.x, vNormal.y, vNormal.z ); Vector vec; pCoreDisp->GetTangentS( i, vec ); builder.TangentS3f( VectorExpand( vec ) ); pCoreDisp->GetTangentT( i, vec ); builder.TangentT3f( VectorExpand( vec ) ); Vector2D texCoord; pCoreDisp->GetTexCoord( i, texCoord ); builder.TexCoord2f( 0, texCoord.x, texCoord.y ); Vector2D lightCoord; { pCoreDisp->GetLuxelCoord( 0, i, lightCoord ); builder.TexCoord2f( DISP_LMCOORDS_STAGE, lightCoord.x, lightCoord.y ); } float flAlpha = ( ( CCoreDispInfo * )pCoreDisp )->GetAlpha( i ); flAlpha *= ( 1.0f / 255.0f ); flAlpha = clamp( flAlpha, 0.0f, 1.0f ); builder.Color4f( 1.0f, 1.0f, 1.0f, flAlpha ); if( nLightmaps > 1 ) { SurfComputeLightmapCoordinate( ctx, pDisp->GetParent(), pDisp->m_Verts[i].m_vPos, lightCoord ); builder.TexCoord2f( 2, ctx.m_BumpSTexCoordOffset, 0.0f ); } builder.AdvanceVertex(); } builder.EndModify(); #endif } void CDispInfo::CopyMapDispData( const ddispinfo_t *pBuildDisp ) { m_iLightmapAlphaStart = pBuildDisp->m_iLightmapAlphaStart; m_Power = pBuildDisp->power; Assert( m_Power >= 2 && m_Power <= NUM_POWERINFOS ); m_pPowerInfo = ::GetPowerInfo( m_Power ); // Max # of indices: // Take the number of triangles (2 * (size-1) * (size-1)) // and multiply by 3! // These can be non-null in the case of task switch restore int size = GetSideLength(); m_Indices.SetSize( 6 * (size-1) * (size-1) ); // Per-node information if (m_pNodeInfo) delete[] m_pNodeInfo; m_pNodeInfo = new DispNodeInfo_t[m_pPowerInfo->m_NodeCount]; } void DispInfo_CreateMaterialGroups( model_t *pWorld, const MaterialSystem_SortInfo_t *pSortInfos ) { for ( int iDisp=0; iDisp < pWorld->brush.pShared->numDispInfos; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); int idLMPage = pSortInfos[MSurf_MaterialSortID( pDisp->m_ParentSurfID )].lightmapPageID; CDispGroup *pCombo = FindCombo( g_DispGroups, idLMPage, MSurf_TexInfo( pDisp->m_ParentSurfID )->material ); if( !pCombo ) pCombo = AddCombo( g_DispGroups, idLMPage, MSurf_TexInfo( pDisp->m_ParentSurfID )->material ); MEM_ALLOC_CREDIT(); pCombo->m_DispInfos.AddToTail( iDisp ); } } void DispInfo_LinkToParentFaces( model_t *pWorld, const ddispinfo_t *pMapDisps, int nDisplacements ) { for ( int iDisp=0; iDisp < nDisplacements; iDisp++ ) { const ddispinfo_t *pMapDisp = &pMapDisps[iDisp]; CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); // Set its parent. SurfaceHandle_t surfID = SurfaceHandleFromIndex( pMapDisp->m_iMapFace ); Assert( pMapDisp->m_iMapFace >= 0 && pMapDisp->m_iMapFace < pWorld->brush.pShared->numsurfaces ); Assert( MSurf_Flags( surfID ) & SURFDRAW_HAS_DISP ); surfID->pDispInfo = pDisp; pDisp->SetParent( surfID ); } } void DispInfo_CreateEmptyStaticBuffers( model_t *pWorld, const ddispinfo_t *pMapDisps ) { // For each combo, create empty buffers. for( int i=0; i < g_DispGroups.Size(); i++ ) { CDispGroup *pCombo = g_DispGroups[i]; int nTotalVerts=0, nTotalIndices=0; int iStart = 0; for( int iDisp=0; iDisp < pCombo->m_DispInfos.Size(); iDisp++ ) { const ddispinfo_t *pMapDisp = &pMapDisps[pCombo->m_DispInfos[iDisp]]; int nVerts, nIndices; CalcMaxNumVertsAndIndices( pMapDisp->power, &nVerts, &nIndices ); // If we're going to pass our vertex buffer limit, or we're at the last one, // make a static buffer and fill it up. if( (nTotalVerts + nVerts) > MAX_STATIC_BUFFER_VERTS || (nTotalIndices + nIndices) > MAX_STATIC_BUFFER_INDICES ) { AddEmptyMesh( pWorld, pCombo, pMapDisps, &pCombo->m_DispInfos[iStart], iDisp-iStart, nTotalVerts, nTotalIndices ); Assert( nTotalVerts > 0 && nTotalIndices > 0 ); nTotalVerts = nTotalIndices = 0; iStart = iDisp; --iDisp; } else if( iDisp == pCombo->m_DispInfos.Size()-1 ) { AddEmptyMesh( pWorld, pCombo, pMapDisps, &pCombo->m_DispInfos[iStart], iDisp-iStart+1, nTotalVerts+nVerts, nTotalIndices+nIndices ); break; } else { nTotalVerts += nVerts; nTotalIndices += nIndices; } } } } //----------------------------------------------------------------------------- // Purpose: // Input : *pWorld - // iDisp - // *pMapDisp - // *pCoreDisp - // *pVerts - // pWorld - // iDisp - // Output : Returns true on success, false on failure. // Information: Setup the CCoreDispInfo using the ddispinfo_t and have it translate the data // into a format we'll copy into the rendering structures. This roundaboutness is because // of legacy code. It should all just be stored in the map file, but it's not a high priority right now. //----------------------------------------------------------------------------- bool DispInfo_CreateFromMapDisp( model_t *pWorld, int iDisp, const ddispinfo_t *pMapDisp, CCoreDispInfo *pCoreDisp, const CDispVert *pVerts, const CDispTri *pTris,const MaterialSystem_SortInfo_t *pSortInfos, bool bRestoring ) { // Get the matching CDispInfo to fill in. CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); // Initialize the core disp info with data from the map displacement. pCoreDisp->GetSurface()->SetPointStart( pMapDisp->startPosition ); pCoreDisp->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, pVerts, pTris ); pCoreDisp->SetNeighborData( pMapDisp->m_EdgeNeighbors, pMapDisp->m_CornerNeighbors ); // Copy the allowed verts list. ErrorIfNot( pCoreDisp->GetAllowedVerts().GetNumDWords() == sizeof( pMapDisp->m_AllowedVerts ) / 4, ( "DispInfo_StoreMapData: size mismatch in 'allowed verts' list" ) ); for ( int iVert = 0; iVert < pCoreDisp->GetAllowedVerts().GetNumDWords(); ++iVert ) { pCoreDisp->GetAllowedVerts().SetDWord( iVert, pMapDisp->m_AllowedVerts[iVert] ); } // Build the reset of the intermediate data from the initial map displacement data. BuildDispSurfInit( pWorld, pCoreDisp, pDisp->GetParent() ); if ( !pCoreDisp->Create() ) return false; // Save the point start index - needed for overlays. pDisp->m_iPointStart = pCoreDisp->GetSurface()->GetPointStartIndex(); // Now setup the CDispInfo. pDisp->m_Index = static_cast( iDisp ); // Store ddispinfo_t data. pDisp->CopyMapDispData( pMapDisp ); // Store CCoreDispInfo data. if( !pDisp->CopyCoreDispData( pWorld, pSortInfos, pCoreDisp, bRestoring ) ) return false; // Initialize all the active and other verts after setting up neighbors. pDisp->InitializeActiveVerts(); pDisp->m_iLightmapSamplePositionStart = pMapDisp->m_iLightmapSamplePositionStart; return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pWorld - // iDisp - // *pCoreDisp - // *pVerts - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- void DispInfo_CreateStaticBuffersAndTags( model_t *pWorld, int iDisp, CCoreDispInfo *pCoreDisp, const CDispVert *pVerts ) { // Get the matching CDispInfo to fill in. CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); // Now copy the CCoreDisp's data into the static buffer. FillStaticBuffer( pDisp->m_pMesh, pDisp, pCoreDisp, pVerts, pDisp->NumLightMaps() ); // Now build the tagged data for visualization. BuildTagData( pCoreDisp, pDisp ); } // On the xbox, we lock the meshes ahead of time. void SetupMeshReaders( model_t *pWorld, int nDisplacements ) { for ( int iDisp=0; iDisp < nDisplacements; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); MeshDesc_t desc; memset( &desc, 0, sizeof( desc ) ); desc.m_VertexSize_Position = sizeof( CDispRenderVert ); desc.m_VertexSize_TexCoord[0] = sizeof( CDispRenderVert ); desc.m_VertexSize_TexCoord[DISP_LMCOORDS_STAGE] = sizeof( CDispRenderVert ); desc.m_VertexSize_Normal = sizeof( CDispRenderVert ); desc.m_VertexSize_TangentS = sizeof( CDispRenderVert ); desc.m_VertexSize_TangentT = sizeof( CDispRenderVert ); CDispRenderVert *pBaseVert = pDisp->m_Verts.Base(); desc.m_pPosition = (float*)&pBaseVert->m_vPos; desc.m_pTexCoord[0] = (float*)&pBaseVert->m_vTexCoord; desc.m_pTexCoord[DISP_LMCOORDS_STAGE] = (float*)&pBaseVert->m_LMCoords; desc.m_pNormal = (float*)&pBaseVert->m_vNormal; desc.m_pTangentS = (float*)&pBaseVert->m_vSVector; desc.m_pTangentT = (float*)&pBaseVert->m_vTVector; desc.m_nIndexSize = 1; desc.m_pIndices = pDisp->m_Indices.Base(); pDisp->m_MeshReader.BeginRead_Direct( desc, pDisp->NumVerts(), pDisp->m_nIndices ); } } void UpdateDispBBoxes( model_t *pWorld, int nDisplacements ) { for ( int iDisp=0; iDisp < nDisplacements; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); pDisp->UpdateBoundingBox(); } } #include "tier0/memdbgoff.h" bool DispInfo_LoadDisplacements( model_t *pWorld, bool bRestoring ) { const MaterialSystem_SortInfo_t *pSortInfos = materialSortInfoArray; int nDisplacements = CMapLoadHelper::LumpSize( LUMP_DISPINFO ) / sizeof( ddispinfo_t ); int nLuxels = CMapLoadHelper::LumpSize( LUMP_DISP_LIGHTMAP_ALPHAS ); int nSamplePositionBytes = CMapLoadHelper::LumpSize( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); // Setup the world's list of displacements. if ( bRestoring ) { /* Breakpoint-able: */ if (pWorld->brush.pShared->numDispInfos != nDisplacements) { volatile int a = 0; a = a + 1; } if ( !pWorld->brush.pShared->numDispInfos && nDisplacements ) { // Attempting to restore displacements before displacements got loaded return false; } ErrorIfNot( pWorld->brush.pShared->numDispInfos == nDisplacements, ("DispInfo_LoadDisplacments: dispcounts (%d and %d) don't match.", pWorld->brush.pShared->numDispInfos, nDisplacements) ); ErrorIfNot( g_DispLMAlpha.Count() == nLuxels, ("DispInfo_LoadDisplacements: lightmap alpha counts (%d and %d) don't match.", g_DispLMAlpha.Count(), nLuxels) ); } else { // Create the displacements. pWorld->brush.pShared->numDispInfos = nDisplacements; pWorld->brush.pShared->hDispInfos = DispInfo_CreateArray( pWorld->brush.pShared->numDispInfos ); // Load lightmap alphas. { MEM_ALLOC_CREDIT(); g_DispLMAlpha.SetSize( nLuxels ); } CMapLoadHelper lhDispLMAlphas( LUMP_DISP_LIGHTMAP_ALPHAS ); lhDispLMAlphas.LoadLumpData( 0, nLuxels, g_DispLMAlpha.Base() ); // Load lightmap sample positions. { MEM_ALLOC_CREDIT(); g_DispLightmapSamplePositions.SetSize( nSamplePositionBytes ); } CMapLoadHelper lhDispLMPositions( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); lhDispLMPositions.LoadLumpData( 0, nSamplePositionBytes, g_DispLightmapSamplePositions.Base() ); } // Free old data. DispInfo_ReleaseMaterialSystemObjects( pWorld ); // load the displacement info structures into temporary space // using temporary storage that is not the stack for compatibility with console stack #ifndef _X360 ddispinfo_t tempDisps[MAX_MAP_DISPINFO]; #else CUtlMemory< ddispinfo_t > m_DispInfoBuf( 0, MAX_MAP_DISPINFO ); ddispinfo_t *tempDisps = m_DispInfoBuf.Base(); #endif ErrorIfNot( nDisplacements <= MAX_MAP_DISPINFO, ("DispInfo_LoadDisplacements: nDisplacements (%d) > MAX_MAP_DISPINFO (%d)", nDisplacements, MAX_MAP_DISPINFO) ); CMapLoadHelper lhDispInfo( LUMP_DISPINFO ); lhDispInfo.LoadLumpData( 0, nDisplacements * sizeof( ddispinfo_t ), tempDisps ); // Now hook up the displacements to their parents. DispInfo_LinkToParentFaces( pWorld, tempDisps, nDisplacements ); // First, create "groups" (or "combos") which contain all the displacements that // use the same material and lightmap. DispInfo_CreateMaterialGroups( pWorld, pSortInfos ); // Now make the static buffers for each material/lightmap combo. if ( g_VBAllocTracker ) g_VBAllocTracker->TrackMeshAllocations( "DispInfo_LoadDisplacements" ); DispInfo_CreateEmptyStaticBuffers( pWorld, tempDisps ); if ( g_VBAllocTracker ) g_VBAllocTracker->TrackMeshAllocations( NULL ); // Now setup each displacement one at a time. // using temporary storage that is not the stack for compatibility with console stack #ifndef _X360 CDispVert tempVerts[MAX_DISPVERTS]; #else CUtlMemory< CDispVert > m_DispVertsBuf( 0, MAX_DISPVERTS ); CDispVert *tempVerts = m_DispVertsBuf.Base(); #endif #ifndef _X360 CDispTri tempTris[MAX_DISPTRIS]; #else // using temporary storage that is not the stack for compatibility with console stack CUtlMemory< CDispTri > m_DispTrisBuf( 0, MAX_DISPTRIS ); CDispTri *tempTris = m_DispTrisBuf.Base(); #endif int iCurVert = 0; int iCurTri = 0; // Core displacement list. CUtlVector aCoreDisps; int iDisp = 0; for ( iDisp = 0; iDisp < nDisplacements; ++iDisp ) { CCoreDispInfo *pCoreDisp = new CCoreDispInfo; aCoreDisps.AddToTail( pCoreDisp ); } CMapLoadHelper lhDispVerts( LUMP_DISP_VERTS ); CMapLoadHelper lhDispTris( LUMP_DISP_TRIS ); for ( iDisp = 0; iDisp < nDisplacements; ++iDisp ) { // Get the current map displacement. ddispinfo_t *pMapDisp = &tempDisps[iDisp]; if ( !pMapDisp ) continue; // Load the vertices from the file. int nVerts = NUM_DISP_POWER_VERTS( pMapDisp->power ); ErrorIfNot( nVerts <= MAX_DISPVERTS, ( "DispInfo_LoadDisplacements: invalid vertex count (%d)", nVerts ) ); lhDispVerts.LoadLumpData( iCurVert * sizeof(CDispVert), nVerts*sizeof(CDispVert), tempVerts ); iCurVert += nVerts; // Load the triangle indices from the file. int nTris = NUM_DISP_POWER_TRIS( pMapDisp->power ); ErrorIfNot( nTris <= MAX_DISPTRIS, ( "DispInfo_LoadDisplacements: invalid tri count (%d)", nTris ) ); lhDispTris.LoadLumpData( iCurTri * sizeof(CDispTri), nTris*sizeof(CDispTri), tempTris ); iCurTri += nTris; // Now create the CoreDispInfo and the base CDispInfo. if ( !DispInfo_CreateFromMapDisp( pWorld, iDisp, pMapDisp, aCoreDisps[iDisp], tempVerts, tempTris, pSortInfos, bRestoring ) ) return false; } // Smooth Normals. SmoothDispSurfNormals( aCoreDisps.Base(), nDisplacements ); // Fill in the static buffers. for ( iDisp = 0; iDisp < nDisplacements; ++iDisp ) { DispInfo_CreateStaticBuffersAndTags( pWorld, iDisp, aCoreDisps[iDisp], tempVerts ); // Copy over the now blended normals CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); pDisp->CopyCoreDispVertData( aCoreDisps[iDisp], pDisp->m_BumpSTexCoordOffset ); } // Destroy core displacement list. aCoreDisps.PurgeAndDeleteElements(); // If we're not using LOD, then maximally tesselate all the displacements and // make sure they never change. for ( iDisp=0; iDisp < nDisplacements; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); pDisp->m_ActiveVerts = pDisp->m_AllowedVerts; } for ( iDisp=0; iDisp < nDisplacements; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); pDisp->TesselateDisplacement(); } SetupMeshReaders( pWorld, nDisplacements ); UpdateDispBBoxes( pWorld, nDisplacements ); return true; } #include "tier0/memdbgon.h" void DispInfo_ReleaseMaterialSystemObjects( model_t *pWorld ) { CMatRenderContextPtr pRenderContext( materials ); // Free all the static meshes. for( int iGroup=0; iGroup < g_DispGroups.Size(); iGroup++ ) { CDispGroup *pGroup = g_DispGroups[iGroup]; for( int iMesh=0; iMesh < pGroup->m_Meshes.Size(); iMesh++ ) { CGroupMesh *pMesh = pGroup->m_Meshes[iMesh]; pRenderContext->DestroyStaticMesh( pMesh->m_pMesh ); } pGroup->m_Meshes.PurgeAndDeleteElements(); } g_DispGroups.PurgeAndDeleteElements(); // Clear pointers in the dispinfos. if( pWorld ) { for( int iDisp=0; iDisp < pWorld->brush.pShared->numDispInfos; iDisp++ ) { CDispInfo *pDisp = GetModelDisp( pWorld, iDisp ); if ( !pDisp ) { AssertOnce( 0 ); continue; } pDisp->m_pMesh = NULL; pDisp->m_iVertOffset = pDisp->m_iIndexOffset = 0; } } } //----------------------------------------------------------------------------- // CDispInfo implementation. //----------------------------------------------------------------------------- CDispInfo::CDispInfo() { m_ParentSurfID = SURFACE_HANDLE_INVALID; m_bTouched = false; ++g_ConstructorChecker.m_nConstructedObjects; m_BBoxMin.Init(); m_BBoxMax.Init(); m_idLMPage = -1; m_pPowerInfo = NULL; m_ViewerSphereCenter.Init( 1e24, 1e24, 1e24 ); m_bInUse = false; m_pNodeInfo = 0; m_pMesh = NULL; m_Tag = NULL; m_pDispArray = NULL; m_FirstDecal = DISP_DECAL_HANDLE_INVALID; m_FirstShadowDecal = DISP_SHADOW_HANDLE_INVALID; for ( int i=0; i < 4; i++ ) { m_EdgeNeighbors[i].SetInvalid(); m_CornerNeighbors[i].SetInvalid(); } } CDispInfo::~CDispInfo() { if (m_pNodeInfo) delete[] m_pNodeInfo; delete[] m_pWalkIndices; delete[] m_pBuildIndices; --g_ConstructorChecker.m_nConstructedObjects; // All the decals should have been freed through // CModelLoader::Map_UnloadModel -> R_DecalTerm Assert( m_FirstDecal == DISP_DECAL_HANDLE_INVALID ); Assert( m_FirstShadowDecal == DISP_SHADOW_HANDLE_INVALID ); } void CDispInfo::CopyCoreDispVertData( const CCoreDispInfo *pCoreDisp, float bumpSTexCoordOffset ) { #ifndef SWDS if( NumLightMaps() <= 1 ) { bumpSTexCoordOffset = 0.0f; } // Copy vertex positions (for backfacing tests). m_Verts.SetSize( m_pPowerInfo->m_MaxVerts ); m_BumpSTexCoordOffset = bumpSTexCoordOffset; for( int i=0; i < NumVerts(); i++ ) { pCoreDisp->GetVert( i, m_Verts[i].m_vPos ); pCoreDisp->GetTexCoord( i, m_Verts[i].m_vTexCoord ); pCoreDisp->GetLuxelCoord( 0, i, m_Verts[i].m_LMCoords ); // mat_normals needs this as well as the dynamic lighting code pCoreDisp->GetNormal( i, m_Verts[i].m_vNormal ); pCoreDisp->GetTangentS( i, m_Verts[i].m_vSVector ); pCoreDisp->GetTangentT( i, m_Verts[i].m_vTVector ); } #endif } bool CDispInfo::CopyCoreDispData( model_t *pWorld, const MaterialSystem_SortInfo_t *pSortInfos, const CCoreDispInfo *pCoreDisp, bool bRestoring ) { m_idLMPage = pSortInfos[MSurf_MaterialSortID( GetParent() )].lightmapPageID; #ifndef SWDS SurfaceCtx_t ctx; SurfSetupSurfaceContext( ctx, GetParent() ); #endif // Restoring is only for alt+tabbing, which can't happen on consoles if ( IsPC() && bRestoring ) { #ifndef SWDS // When restoring, have to recompute lightmap coords if( NumLightMaps() > 1 ) { m_BumpSTexCoordOffset = ctx.m_BumpSTexCoordOffset; } else { m_BumpSTexCoordOffset = 0.0f; } for( int i=0; i < NumVerts(); i++ ) { pCoreDisp->GetLuxelCoord( 0, i, m_Verts[i].m_LMCoords ); } #endif // SWDS return true; } // When restoring, leave all this data the same. const CCoreDispSurface *pSurface = pCoreDisp->GetSurface(); for( int index=0; index < 4; index++ ) { pSurface->GetTexCoord( index, m_BaseSurfaceTexCoords[index] ); m_BaseSurfacePositions[index] = pSurface->GetPoint( index ); } #ifndef SWDS CopyCoreDispVertData( pCoreDisp, ctx.m_BumpSTexCoordOffset ); #endif // Copy neighbor info. for ( int iEdge=0; iEdge < 4; iEdge++ ) { m_EdgeNeighbors[iEdge] = *pCoreDisp->GetEdgeNeighbor( iEdge ); m_CornerNeighbors[iEdge] = *pCoreDisp->GetCornerNeighbors( iEdge ); } // Copy allowed verts. m_AllowedVerts = pCoreDisp->GetAllowedVerts(); m_nIndices = 0; return true; } int CDispInfo::NumLightMaps() { return (MSurf_Flags( m_ParentSurfID ) & SURFDRAW_BUMPLIGHT) ? NUM_BUMP_VECTS+1 : 1; } //----------------------------------------------------------------------------- // Purpose: // NOTE: You cannot use the builddisp.cpp IsTriWalkable, IsTriBuildable functions // because the flags are different having been collapsed in vbsp //----------------------------------------------------------------------------- void BuildTagData( CCoreDispInfo *pCoreDisp, CDispInfo *pDisp ) { int nWalkTest = 0; int nBuildTest = 0; int iTri; for ( iTri = 0; iTri < pCoreDisp->GetTriCount(); ++iTri ) { if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_WALKABLE ) ) { nWalkTest++; } if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_BUILDABLE ) ) { nBuildTest++; } } nWalkTest *= 3; nBuildTest *= 3; pDisp->m_pWalkIndices = new unsigned short[nWalkTest]; pDisp->m_pBuildIndices = new unsigned short[nBuildTest]; int nWalkCount = 0; int nBuildCount = 0; for ( iTri = 0; iTri < pCoreDisp->GetTriCount(); ++iTri ) { if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_WALKABLE ) ) { pCoreDisp->GetTriIndices( iTri, pDisp->m_pWalkIndices[nWalkCount], pDisp->m_pWalkIndices[nWalkCount+1], pDisp->m_pWalkIndices[nWalkCount+2] ); nWalkCount += 3; } if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_BUILDABLE ) ) { pCoreDisp->GetTriIndices( iTri, pDisp->m_pBuildIndices[nBuildCount], pDisp->m_pBuildIndices[nBuildCount+1], pDisp->m_pBuildIndices[nBuildCount+2] ); nBuildCount += 3; } } Assert( nWalkCount == nWalkTest ); Assert( nBuildCount == nBuildTest ); pDisp->m_nWalkIndexCount = nWalkCount; pDisp->m_nBuildIndexCount = nBuildCount; } //----------------------------------------------------------------------------- // Purpose: // Input : *pDisp - // &vecPoint - // Output : int //----------------------------------------------------------------------------- int FindNeighborCornerVert( CCoreDispInfo *pDisp, const Vector &vecPoint ) { CDispUtilsHelper *pDispHelper = pDisp; int iClosest = 0; float flClosest = 1e24; for ( int iCorner = 0; iCorner < 4; ++iCorner ) { // Has it been touched? CVertIndex viCornerVert = pDispHelper->GetPowerInfo()->GetCornerPointIndex( iCorner ); int iCornerVert = pDispHelper->VertIndexToInt( viCornerVert ); const Vector &vecCornerVert = pDisp->GetVert( iCornerVert ); float flDist = vecCornerVert.DistTo( vecPoint ); if ( flDist < flClosest ) { iClosest = iCorner; flClosest = flDist; } } if ( flClosest <= 0.1f ) return iClosest; else return -1; } // sets a new normal/tangentS, recomputes tangent T void UpdateTangentSpace(CCoreDispInfo *pDisp, int iVert, const Vector &vNormal, const Vector &vTanS) { Vector tanT; pDisp->SetNormal( iVert, vNormal ); CrossProduct( vTanS, vNormal, tanT ); pDisp->SetTangentS(iVert, vTanS); pDisp->SetTangentT(iVert, tanT); } void UpdateTangentSpace(CCoreDispInfo *pDisp, const CVertIndex &index, const Vector &vNormal, const Vector &vTanS) { UpdateTangentSpace(pDisp, pDisp->VertIndexToInt(index), vNormal, vTanS); } //----------------------------------------------------------------------------- // Purpose: // Input : **ppListBase - // nListSize - //----------------------------------------------------------------------------- void BlendSubNeighbors( CCoreDispInfo **ppListBase, int nListSize ) { // Loop through all of the displacements in the list. for ( int iDisp = 0; iDisp < nListSize; ++iDisp ) { // Get the current displacement. CCoreDispInfo *pDisp = ppListBase[iDisp]; if ( !pDisp ) continue; // Loop through all the edges of the displacement. for ( int iEdge = 0; iEdge < 4; ++iEdge ) { // Find valid neighbors along the edge. CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); if ( !pEdge ) continue; // Check to see if we have sub-neighbors - defines a t-junction in this world. If not, // then the normal blend edges function will catch it all. if ( !pEdge->m_SubNeighbors[0].IsValid() || !pEdge->m_SubNeighbors[1].IsValid() ) continue; // Get the mid-point of the current displacement. CVertIndex viMidPoint = pDisp->GetEdgeMidPoint( iEdge ); int iMidPoint = pDisp->VertIndexToInt( viMidPoint ); const Vector &vecMidPoint = pDisp->GetVert( iMidPoint ); // Get the current sub-neighbors along the edge. CCoreDispInfo *pNeighbor1 = ppListBase[pEdge->m_SubNeighbors[0].GetNeighborIndex()]; CCoreDispInfo *pNeighbor2 = ppListBase[pEdge->m_SubNeighbors[1].GetNeighborIndex()]; // Get the current sub-neighbor corners. int iCorners[2]; iCorners[0] = FindNeighborCornerVert( pNeighbor1, vecMidPoint ); iCorners[1] = FindNeighborCornerVert( pNeighbor2, vecMidPoint ); if ( iCorners[0] != -1 && iCorners[1] != -1 ) { CVertIndex viCorners[2] = { pNeighbor1->GetCornerPointIndex( iCorners[0] ),pNeighbor2->GetCornerPointIndex( iCorners[1] ) }; // Accumulate the normals at the mid-point of the primary edge and corners of the sub-neighbors. Vector vecAverage = pDisp->GetNormal( iMidPoint ); vecAverage += pNeighbor1->GetNormal( viCorners[0] ); vecAverage += pNeighbor2->GetNormal( viCorners[1] ); // Re-normalize. VectorNormalize( vecAverage ); Vector vAvgTanS = pDisp->GetTangentS(iMidPoint); vAvgTanS += pNeighbor1->GetTangentS(viCorners[0]); vAvgTanS += pNeighbor2->GetTangentS(viCorners[1]); VectorNormalize(vAvgTanS); //vecAverage.Init( 0.0f, 0.0f, 1.0f ); // Set the new normal value back. UpdateTangentSpace( pDisp, iMidPoint, vecAverage, vAvgTanS ); UpdateTangentSpace( pNeighbor1, viCorners[0], vecAverage, vAvgTanS ); UpdateTangentSpace( pNeighbor2, viCorners[1], vecAverage, vAvgTanS ); } } } } //----------------------------------------------------------------------------- // Purpose: // Input : *pDisp - // iNeighbors[512] - // Output : int //----------------------------------------------------------------------------- int GetAllNeighbors( const CCoreDispInfo *pDisp, int iNeighbors[512] ) { int nNeighbors = 0; // Check corner neighbors. for ( int iCorner=0; iCorner < 4; iCorner++ ) { const CDispCornerNeighbors *pCorner = pDisp->GetCornerNeighbors( iCorner ); for ( int i=0; i < pCorner->m_nNeighbors; i++ ) { if ( nNeighbors < 512 ) iNeighbors[nNeighbors++] = pCorner->m_Neighbors[i]; } } for ( int iEdge=0; iEdge < 4; iEdge++ ) { const CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); for ( int i=0; i < 2; i++ ) { if ( pEdge->m_SubNeighbors[i].IsValid() ) if ( nNeighbors < 512 ) iNeighbors[nNeighbors++] = pEdge->m_SubNeighbors[i].GetNeighborIndex(); } } return nNeighbors; } //----------------------------------------------------------------------------- // Purpose: // Input : **ppListBase - // listSize - //----------------------------------------------------------------------------- void BlendCorners( CCoreDispInfo **ppListBase, int nListSize ) { CUtlVector nbCornerVerts; for ( int iDisp = 0; iDisp < nListSize; ++iDisp ) { CCoreDispInfo *pDisp = ppListBase[iDisp]; int iNeighbors[512]; int nNeighbors = GetAllNeighbors( pDisp, iNeighbors ); // Make sure we have room for all the neighbors. nbCornerVerts.RemoveAll(); nbCornerVerts.EnsureCapacity( nNeighbors ); nbCornerVerts.AddMultipleToTail( nNeighbors ); // For each corner. for ( int iCorner=0; iCorner < 4; iCorner++ ) { // Has it been touched? CVertIndex cornerVert = pDisp->GetCornerPointIndex( iCorner ); int iCornerVert = pDisp->VertIndexToInt( cornerVert ); const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); // For each displacement sharing this corner.. Vector vAverage = pDisp->GetNormal( iCornerVert ); Vector vAvgTanS; pDisp->GetTangentS( iCornerVert, vAvgTanS ); for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) { int iNBListIndex = iNeighbors[iNeighbor]; CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; // Find out which vert it is on the neighbor. int iNBCorner = FindNeighborCornerVert( pNeighbor, vCornerVert ); if ( iNBCorner == -1 ) { nbCornerVerts[iNeighbor] = -1; // remove this neighbor from the list. } else { CVertIndex viNBCornerVert = pNeighbor->GetCornerPointIndex( iNBCorner ); int iNBVert = pNeighbor->VertIndexToInt( viNBCornerVert ); nbCornerVerts[iNeighbor] = iNBVert; vAverage += pNeighbor->GetNormal( iNBVert ); vAvgTanS += pNeighbor->GetTangentS( iNBVert ); } } // Blend all the neighbor normals with this one. VectorNormalize( vAverage ); VectorNormalize( vAvgTanS ); UpdateTangentSpace(pDisp, iCornerVert, vAverage, vAvgTanS ); for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) { int iNBListIndex = iNeighbors[iNeighbor]; if ( nbCornerVerts[iNeighbor] == -1 ) continue; CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; UpdateTangentSpace(pNeighbor, nbCornerVerts[iNeighbor], vAverage, vAvgTanS); } } } } //----------------------------------------------------------------------------- // Purpose: // Input : **ppListBase - // listSize - //----------------------------------------------------------------------------- void BlendEdges( CCoreDispInfo **ppListBase, int nListSize ) { // Loop through all the displacements in the list. for ( int iDisp = 0; iDisp < nListSize; ++iDisp ) { // Get the current displacement. CCoreDispInfo *pDisp = ppListBase[iDisp]; if ( !pDisp ) continue; // Loop through all of the edges on a displacement. for ( int iEdge = 0; iEdge < 4; ++iEdge ) { // Get the current displacement edge. CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); if ( !pEdge ) continue; // Check for sub-edges. for ( int iSubEdge = 0; iSubEdge < 2; ++iSubEdge ) { // Get the current sub-edge. CDispSubNeighbor *pSubEdge = &pEdge->m_SubNeighbors[iSubEdge]; if ( !pSubEdge->IsValid() ) continue; // Get the current neighbor. CCoreDispInfo *pNeighbor = ppListBase[pSubEdge->GetNeighborIndex()]; if ( !pNeighbor ) continue; // Get the edge dimension. int iEdgeDim = g_EdgeDims[iEdge]; CDispSubEdgeIterator it; it.Start( pDisp, iEdge, iSubEdge, true ); // Get setup on the first corner vert. it.Next(); CVertIndex viPrevPos = it.GetVertIndex(); while ( it.Next() ) { // Blend the two. if ( !it.IsLastVert() ) { Vector vecAverage = pDisp->GetNormal( it.GetVertIndex() ) + pNeighbor->GetNormal( it.GetNBVertIndex() ); Vector vAvgTanS = pDisp->GetTangentS( it.GetVertIndex() ) + pNeighbor->GetTangentS( it.GetNBVertIndex() ); VectorNormalize( vecAverage ); VectorNormalize( vAvgTanS ); UpdateTangentSpace(pDisp, it.GetVertIndex(), vecAverage, vAvgTanS ); UpdateTangentSpace(pNeighbor, it.GetNBVertIndex(), vecAverage, vAvgTanS ); } // Now blend the in-between verts (if this edge is high-res). int iPrevPos = viPrevPos[!iEdgeDim]; int iCurPos = it.GetVertIndex()[!iEdgeDim]; for ( int iTween = iPrevPos+1; iTween < iCurPos; iTween++ ) { float flPercent = RemapVal( iTween, iPrevPos, iCurPos, 0, 1 ); Vector vecNormal; VectorLerp( pDisp->GetNormal( viPrevPos ), pDisp->GetNormal( it.GetVertIndex() ), flPercent, vecNormal ); VectorNormalize( vecNormal ); Vector vAvgTanS; VectorLerp( pDisp->GetTangentS( viPrevPos ), pDisp->GetTangentS( it.GetVertIndex() ), flPercent, vAvgTanS ); VectorNormalize( vAvgTanS ); CVertIndex viTween; viTween[iEdgeDim] = it.GetVertIndex()[iEdgeDim]; viTween[!iEdgeDim] = iTween; UpdateTangentSpace(pDisp, viTween, vecNormal, vAvgTanS); } viPrevPos = it.GetVertIndex(); } } } } } //----------------------------------------------------------------------------- // Purpose: // Input : **pListBase - // listSize - // NOTE: todo - this is almost the same code as found in vrad, should probably // move it up into common code at some point if the feature // continues to get used //----------------------------------------------------------------------------- void SmoothDispSurfNormals( CCoreDispInfo **ppListBase, int nListSize ) { // Setup helper list for iteration. for ( int iDisp = 0; iDisp < nListSize; ++iDisp ) { ppListBase[iDisp]->SetDispUtilsHelperInfo( ppListBase, nListSize ); } // Blend normals along t-junctions, corners, and edges. BlendSubNeighbors( ppListBase, nListSize ); BlendCorners( ppListBase, nListSize ); BlendEdges( ppListBase, nListSize ); }