//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $Workfile: $ // $Date: $ // $NoKeywords: $ //=============================================================================// #include "disp_vbsp.h" #include "tier0/dbg.h" #include "vbsp.h" #include "mstristrip.h" #include "writebsp.h" #include "pacifier.h" #include "disp_ivp.h" #include "builddisp.h" #include "mathlib/vector.h" // map displacement info -- runs parallel to the dispinfos struct int nummapdispinfo = 0; CUtlBlockVector mapdispinfo; CUtlVector g_CoreDispInfos; //----------------------------------------------------------------------------- // Computes the bounds for a disp info //----------------------------------------------------------------------------- void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ) { CDispBox box; // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. mapdispinfo_t *pMapDisp = &mapdispinfo[dispinfo]; CCoreDispInfo coreDispInfo; DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); GetDispBox( &coreDispInfo, box ); mins = box.m_Min; maxs = box.m_Max; } // Gets the barycentric coordinates of the position on the triangle where the lightmap // coordinates are equal to lmCoords. This always generates the coordinates but it // returns false if the point containing them does not lie inside the triangle. bool GetBarycentricCoordsFromLightmapCoords( Vector2D tri[3], Vector2D const &lmCoords, float bcCoords[3] ) { GetBarycentricCoords2D( tri[0], tri[1], tri[2], lmCoords, bcCoords ); return (bcCoords[0] >= 0.0f && bcCoords[0] <= 1.0f) && (bcCoords[1] >= 0.0f && bcCoords[1] <= 1.0f) && (bcCoords[2] >= 0.0f && bcCoords[2] <= 1.0f); } bool FindTriIndexMapByUV( CCoreDispInfo *pCoreDisp, Vector2D const &lmCoords, int &iTriangle, float flBarycentric[3] ) { const CPowerInfo *pPowerInfo = GetPowerInfo( pCoreDisp->GetPower() ); // Search all the triangles.. int nTriCount= pCoreDisp->GetTriCount(); for ( int iTri = 0; iTri < nTriCount; ++iTri ) { unsigned short iVerts[3]; // pCoreDisp->GetTriIndices( iTri, iVerts[0], iVerts[1], iVerts[2] ); CTriInfo *pTri = &pPowerInfo->m_pTriInfos[iTri]; iVerts[0] = pTri->m_Indices[0]; iVerts[1] = pTri->m_Indices[1]; iVerts[2] = pTri->m_Indices[2]; // Get this triangle's UVs. Vector2D vecUV[3]; for ( int iCoord = 0; iCoord < 3; ++iCoord ) { pCoreDisp->GetLuxelCoord( 0, iVerts[iCoord], vecUV[iCoord] ); } // See if the passed-in UVs are in this triangle's UVs. if( GetBarycentricCoordsFromLightmapCoords( vecUV, lmCoords, flBarycentric ) ) { iTriangle = iTri; return true; } } return false; } void CalculateLightmapSamplePositions( CCoreDispInfo *pCoreDispInfo, const dface_t *pFace, CUtlVector &out ) { int width = pFace->m_LightmapTextureSizeInLuxels[0] + 1; int height = pFace->m_LightmapTextureSizeInLuxels[1] + 1; // For each lightmap sample, find the triangle it sits in. Vector2D lmCoords; for( int y=0; y < height; y++ ) { lmCoords.y = y + 0.5f; for( int x=0; x < width; x++ ) { lmCoords.x = x + 0.5f; float flBarycentric[3]; int iTri; if( FindTriIndexMapByUV( pCoreDispInfo, lmCoords, iTri, flBarycentric ) ) { if( iTri < 255 ) { out.AddToTail( iTri ); } else { out.AddToTail( 255 ); out.AddToTail( iTri - 255 ); } out.AddToTail( (unsigned char)( flBarycentric[0] * 255.9f ) ); out.AddToTail( (unsigned char)( flBarycentric[1] * 255.9f ) ); out.AddToTail( (unsigned char)( flBarycentric[2] * 255.9f ) ); } else { out.AddToTail( 0 ); out.AddToTail( 0 ); out.AddToTail( 0 ); out.AddToTail( 0 ); } } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int GetDispInfoEntityNum( mapdispinfo_t *pDisp ) { return pDisp->entitynum; } // Setup a CCoreDispInfo given a mapdispinfo_t. // If pFace is non-NULL, then lightmap texture coordinates will be generated. void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ) { winding_t *pWinding = pMapDisp->face.originalface->winding; Assert( pWinding->numpoints == 4 ); // // set initial surface data // CCoreDispSurface *pSurf = pCoreDispInfo->GetSurface(); texinfo_t *pTexInfo = &texinfo[ pMapDisp->face.texinfo ]; Assert( pTexInfo != NULL ); // init material contents pMapDisp->contents = pMapDisp->face.contents; if (!(pMapDisp->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) { pMapDisp->contents |= CONTENTS_SOLID; } pSurf->SetContents( pMapDisp->contents ); // Calculate the lightmap coordinates. Vector2D tCoords[4] = {Vector2D(0,0),Vector2D(0,1),Vector2D(1,0),Vector2D(1,1)}; if( pFace ) { Assert( pFace->numedges == 4 ); Vector pt[4]; for( int i=0; i < 4; i++ ) pt[i] = pWinding->p[i]; int zeroOffset[2] = {0,0}; CalcTextureCoordsAtPoints( pTexInfo->textureVecsTexelsPerWorldUnits, zeroOffset, pt, 4, tCoords ); } // // set face point data ... // pSurf->SetPointCount( 4 ); for( int i = 0; i < 4; i++ ) { // position pSurf->SetPoint( i, pWinding->p[i] ); pSurf->SetTexCoord( i, tCoords[i] ); } // reset surface given start info pSurf->SetPointStart( pMapDisp->startPosition ); pSurf->FindSurfPointStartIndex(); pSurf->AdjustSurfPointData(); // Set the luxel coordinates on the base displacement surface. Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); Vector vecU( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); Vector vecV( pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); bool bSwap = pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); // Set the face m_LightmapExtents if ( pFace ) { pFace->m_LightmapTextureSizeInLuxels[0] = pSurf->GetLuxelU(); pFace->m_LightmapTextureSizeInLuxels[1] = pSurf->GetLuxelV(); if ( bSwap ) { if ( pSwappedTexInfos[ pMapDisp->face.texinfo ] < 0 ) { // Create a new texinfo to hold the swapped data. // We must do this because other surfaces may want the non-swapped data // This fixes a lighting bug in d2_prison_08 where many non-displacement surfaces // were pitch black, in addition to bugs in other maps I bet. // NOTE: Copy here because adding a texinfo could realloc. texinfo_t temp = *pTexInfo; memcpy( temp.lightmapVecsLuxelsPerWorldUnits[0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1], 4 * sizeof(float) ); memcpy( temp.lightmapVecsLuxelsPerWorldUnits[1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0], 4 * sizeof(float) ); temp.lightmapVecsLuxelsPerWorldUnits[1][0] *= -1.0f; temp.lightmapVecsLuxelsPerWorldUnits[1][1] *= -1.0f; temp.lightmapVecsLuxelsPerWorldUnits[1][2] *= -1.0f; temp.lightmapVecsLuxelsPerWorldUnits[1][3] *= -1.0f; pSwappedTexInfos[ pMapDisp->face.texinfo ] = texinfo.AddToTail( temp ); } pMapDisp->face.texinfo = pSwappedTexInfos[ pMapDisp->face.texinfo ]; } // NOTE: This is here to help future-proof code, since there are codepaths where // pTexInfo can be made invalid (texinfo.AddToTail above). pTexInfo = NULL; } // Setup the displacement vectors and offsets. int size = ( ( ( 1 << pMapDisp->power ) + 1 ) * ( ( 1 << pMapDisp->power ) + 1 ) ); Vector vectorDisps[2048]; float dispDists[2048]; Assert( size < sizeof(vectorDisps)/sizeof(vectorDisps[0]) ); for( int j = 0; j < size; j++ ) { Vector v; float dist; VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); VectorAdd( v, pMapDisp->vectorOffsets[j], v ); dist = VectorLength( v ); VectorNormalize( v ); vectorDisps[j] = v; dispDists[j] = dist; } // Use CCoreDispInfo to setup the actual vertex positions. pCoreDispInfo->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, pMapDisp->alphaValues, vectorDisps, dispDists, pMapDisp->flags, pMapDisp->m_vMultiBlends ); pCoreDispInfo->Create(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void EmitInitialDispInfos( void ) { int i; mapdispinfo_t *pMapDisp; ddispinfo_t *pDisp; Vector v; // Calculate the total number of verts. int nTotalVerts = 0; int nTotalTris = 0; int nTotalMultiBlend = 0; for ( i=0; i < nummapdispinfo; i++ ) { nTotalVerts += NUM_DISP_POWER_VERTS( mapdispinfo[i].power ); nTotalTris += NUM_DISP_POWER_TRIS( mapdispinfo[i].power ); if ( ( mapdispinfo[ i ].flags & DISP_INFO_FLAG_HAS_MULTIBLEND ) != 0 ) { nTotalMultiBlend += NUM_DISP_POWER_VERTS( mapdispinfo[i].power ); } } // Clear the output arrays.. g_dispinfo.Purge(); g_dispinfo.SetSize( nummapdispinfo ); g_DispVerts.SetSize( nTotalVerts ); g_DispTris.SetSize( nTotalTris ); g_DispMultiBlend.SetSize( nTotalMultiBlend ); int iCurVert = 0; int iCurTri = 0; int iCurMultiBlend = 0; for( i = 0; i < nummapdispinfo; i++ ) { pDisp = &g_dispinfo[i]; pMapDisp = &mapdispinfo[i]; CDispVert *pOutVerts = &g_DispVerts[iCurVert]; CDispTri *pOutTris = &g_DispTris[iCurTri]; // Setup the vert pointers. pDisp->m_iDispVertStart = iCurVert; pDisp->m_iDispTriStart = iCurTri; iCurVert += NUM_DISP_POWER_VERTS( pMapDisp->power ); iCurTri += NUM_DISP_POWER_TRIS( pMapDisp->power ); // // save power, minimum tesselation, and smoothing angle // pDisp->power = pMapDisp->power; // If the high bit is set - this is FLAGS! pDisp->minTess = pMapDisp->flags | DISP_INFO_FLAG_MAGIC; // pDisp->minTess = pMapDisp->minTess; pDisp->smoothingAngle = pMapDisp->smoothingAngle; pDisp->m_iMapFace = (unsigned short)-2; // get surface contents pDisp->contents = pMapDisp->face.contents; pDisp->startPosition = pMapDisp->startPosition; // // add up the vectorOffsets and displacements, save alphas (per vertex) // int size = ( ( ( 1 << pDisp->power ) + 1 ) * ( ( 1 << pDisp->power ) + 1 ) ); for( int j = 0; j < size; j++ ) { VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); VectorAdd( v, pMapDisp->vectorOffsets[j], v ); float dist = VectorLength( v ); VectorNormalize( v ); VectorCopy( v, pOutVerts[j].m_vVector ); pOutVerts[j].m_flDist = dist; pOutVerts[j].m_flAlpha = pMapDisp->alphaValues[j]; if ( ( pMapDisp->flags & DISP_INFO_FLAG_HAS_MULTIBLEND ) != 0 ) { g_DispMultiBlend[ iCurMultiBlend ] = pMapDisp->m_vMultiBlends[ j ]; iCurMultiBlend++; } } int nTriCount = ( (1 << (pDisp->power)) * (1 << (pDisp->power)) * 2 ); for ( int iTri = 0; iTri< nTriCount; ++iTri ) { pOutTris[iTri].m_uiTags = pMapDisp->triTags[iTri]; } //=================================================================== //=================================================================== // save the index for face data reference pMapDisp->face.dispinfo = i; } } void ExportCoreDispNeighborData( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) { for ( int i=0; i < 4; i++ ) { pOut->m_EdgeNeighbors[i] = *pIn->GetEdgeNeighbor( i ); pOut->m_CornerNeighbors[i] = *pIn->GetCornerNeighbors( i ); } } void ExportNeighborData( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) { FindNeighboringDispSurfs( ppListBase, listSize ); // Export the neighbor data. for ( int i=0; i < nummapdispinfo; i++ ) { ExportCoreDispNeighborData( g_CoreDispInfos[i], &pBSPDispInfos[i] ); } } void ExportCoreDispAllowedVertList( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) { ErrorIfNot( pIn->GetAllowedVerts().GetNumDWords() == sizeof( pOut->m_AllowedVerts ) / 4, ("ExportCoreDispAllowedVertList: size mismatch") ); for ( int i=0; i < pIn->GetAllowedVerts().GetNumDWords(); i++ ) pOut->m_AllowedVerts[i] = pIn->GetAllowedVerts().GetDWord( i ); } void ExportAllowedVertLists( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) { SetupAllowedVerts( ppListBase, listSize ); for ( int i=0; i < listSize; i++ ) { ExportCoreDispAllowedVertList( ppListBase[i], &pBSPDispInfos[i] ); } } bool FindEnclosingTri( const Vector2D &vert, CUtlVector &vertCoords, CUtlVector &indices, int *pStartVert, float bcCoords[3] ) { for ( int i=0; i < indices.Count(); i += 3 ) { GetBarycentricCoords2D( vertCoords[indices[i+0]], vertCoords[indices[i+1]], vertCoords[indices[i+2]], vert, bcCoords ); if ( bcCoords[0] >= 0 && bcCoords[0] <= 1 && bcCoords[1] >= 0 && bcCoords[1] <= 1 && bcCoords[2] >= 0 && bcCoords[2] <= 1 ) { *pStartVert = i; return true; } } return false; } void SnapRemainingVertsToSurface( CCoreDispInfo *pCoreDisp, ddispinfo_t *pDispInfo ) { // First, tesselate the displacement. CUtlVector indices; CVBSPTesselateHelper helper; helper.m_pIndices = &indices; helper.m_pActiveVerts = pCoreDisp->GetAllowedVerts().Base(); helper.m_pPowerInfo = pCoreDisp->GetPowerInfo(); ::TesselateDisplacement( &helper ); // Figure out which verts are actually referenced in the tesselation. CUtlVector vertsTouched; vertsTouched.SetSize( pCoreDisp->GetSize() ); memset( vertsTouched.Base(), 0, sizeof( bool ) * vertsTouched.Count() ); for ( int i=0; i < indices.Count(); i++ ) vertsTouched[ indices[i] ] = true; // Generate 2D floating point coordinates for each vertex. We use these to generate // barycentric coordinates, and the scale doesn't matter. CUtlVector vertCoords; vertCoords.SetSize( pCoreDisp->GetSize() ); for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) { for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) vertCoords[y*pCoreDisp->GetWidth()+x].Init( x, y ); } // Now, for each vert not touched, snap its position to the main surface. for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) { for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) { int index = y * pCoreDisp->GetWidth() + x; if ( !( vertsTouched[index] ) ) { float bcCoords[3]; int iStartVert = -1; if ( FindEnclosingTri( vertCoords[index], vertCoords, indices, &iStartVert, bcCoords ) ) { const Vector &A = pCoreDisp->GetVert( indices[iStartVert+0] ); const Vector &B = pCoreDisp->GetVert( indices[iStartVert+1] ); const Vector &C = pCoreDisp->GetVert( indices[iStartVert+2] ); Vector vNewPos = A*bcCoords[0] + B*bcCoords[1] + C*bcCoords[2]; // This is kind of cheesy, but it gets the job done. Since the CDispVerts store the // verts relative to some other offset, we'll just offset their position instead // of setting it directly. Vector vOffset = vNewPos - pCoreDisp->GetVert( index ); // Modify the mapfile vert. CDispVert *pVert = &g_DispVerts[pDispInfo->m_iDispVertStart + index]; pVert->m_vVector = (pVert->m_vVector * pVert->m_flDist) + vOffset; pVert->m_flDist = 1; // Modify the CCoreDispInfo vert (although it probably won't be used later). pCoreDisp->SetVert( index, vNewPos ); } else { // This shouldn't happen because it would mean that the triangulation that // disp_tesselation.h produced was missing a chunk of the space that the // displacement covers. // It also could indicate a floating-point epsilon error.. check to see if // FindEnclosingTri finds a triangle that -almost- encloses the vert. Assert( false ); } } } } } void SnapRemainingVertsToSurface( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) { //g_pPad = ScratchPad3D_Create(); for ( int i=0; i < listSize; i++ ) { SnapRemainingVertsToSurface( ppListBase[i], &pBSPDispInfos[i] ); } } void EmitDispLMAlphaAndNeighbors() { int i; Msg( "Finding displacement neighbors...\n" ); // Build the CCoreDispInfos. CUtlVector faces; // Create the core dispinfos and init them for use as CDispUtilsHelpers. for ( int iDisp = 0; iDisp < nummapdispinfo; ++iDisp ) { CCoreDispInfo *pDisp = new CCoreDispInfo; if ( !pDisp ) { g_CoreDispInfos.Purge(); return; } int nIndex = g_CoreDispInfos.AddToTail(); pDisp->SetListIndex( nIndex ); g_CoreDispInfos[nIndex] = pDisp; } for ( i=0; i < nummapdispinfo; i++ ) { g_CoreDispInfos[i]->SetDispUtilsHelperInfo( g_CoreDispInfos.Base(), nummapdispinfo ); } faces.SetSize( nummapdispinfo ); int nMemSize = texinfo.Count() * sizeof(int); int *pSwappedTexInfos = (int*)stackalloc( nMemSize ); memset( pSwappedTexInfos, 0xFF, nMemSize ); for( i = 0; i < numfaces; i++ ) { dface_t *pFace = &dfaces[i]; if( pFace->dispinfo == -1 ) continue; mapdispinfo_t *pMapDisp = &mapdispinfo[pFace->dispinfo]; // Set the displacement's face index. ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; pDisp->m_iMapFace = i; // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[pFace->dispinfo]; DispMapToCoreDispInfo( pMapDisp, pCoreDispInfo, pFace, pSwappedTexInfos ); faces[pFace->dispinfo] = pFace; } stackfree( pSwappedTexInfos ); // Generate and export neighbor data. ExportNeighborData( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); // Generate and export the active vert lists. ExportAllowedVertLists( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); // Now that we know which vertices are actually going to be around, snap the ones that won't // be around onto the slightly-reduced mesh. This is so the engine's ray test code and // overlay code works right. SnapRemainingVertsToSurface( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); Msg( "Finding lightmap sample positions...\n" ); for ( i=0; i < nummapdispinfo; i++ ) { dface_t *pFace = faces[i]; ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[i]; pDisp->m_iLightmapSamplePositionStart = g_DispLightmapSamplePositions.Count(); CalculateLightmapSamplePositions( pCoreDispInfo, pFace, g_DispLightmapSamplePositions ); } StartPacifier( "Displacement Alpha : "); // Build lightmap alphas. int dispCount = 0; // How many we've processed. for( i = 0; i < nummapdispinfo; i++ ) { dface_t *pFace = faces[i]; Assert( pFace->dispinfo == i ); ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; // Allocate space for the alpha values. pDisp->m_iLightmapAlphaStart = 0; // not used anymore ++dispCount; } EndPacifier(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void DispGetFaceInfo( mapbrush_t *pBrush ) { int i; side_t *pSide; // we don't support displacement on entities at the moment!! if( pBrush->entitynum != 0 ) { char* pszEntityName = ValueForKey( &g_LoadingMap->entities[pBrush->entitynum], "classname" ); Error( "Error: displacement found on a(n) %s entity - not supported (entity %d, brush %d)\n", pszEntityName, pBrush->entitynum, pBrush->brushnum ); } for( i = 0; i < pBrush->numsides; i++ ) { pSide = &pBrush->original_sides[i]; if( pSide->pMapDisp ) { // error checking!! if( pSide->winding->numpoints != 4 ) Error( "Trying to create a non-quad displacement! (entity %d, brush %d)\n", pBrush->entitynum, pBrush->brushnum ); pSide->pMapDisp->face.originalface = pSide; pSide->pMapDisp->face.texinfo = pSide->texinfo; pSide->pMapDisp->face.dispinfo = -1; pSide->pMapDisp->face.planenum = pSide->planenum; pSide->pMapDisp->face.numpoints = pSide->winding->numpoints; pSide->pMapDisp->face.w = CopyWinding( pSide->winding ); pSide->pMapDisp->face.contents = pBrush->contents; pSide->pMapDisp->face.merged = FALSE; pSide->pMapDisp->face.split[0] = FALSE; pSide->pMapDisp->face.split[1] = FALSE; pSide->pMapDisp->entitynum = pBrush->entitynum; pSide->pMapDisp->brushSideID = pSide->id; } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool HasDispInfo( mapbrush_t *pBrush ) { int i; side_t *pSide; for( i = 0; i < pBrush->numsides; i++ ) { pSide = &pBrush->original_sides[i]; if( pSide->pMapDisp ) return true; } return false; }